okular/generators/djvu/generator_djvu.cpp
2024-06-26 12:48:44 +00:00

404 lines
14 KiB
C++

/*
SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "generator_djvu.h"
#include <core/action.h>
#include <core/annotations.h>
#include <core/area.h>
#include <core/document.h>
#include <core/fileprinter.h>
#include <core/page.h>
#include <core/textpage.h>
#include <core/utils.h>
#include <QDomDocument>
#include <QMutex>
#include <QPixmap>
#include <QPrinter>
#include <QString>
#include <QUuid>
#include <KAboutData>
#include <KLocalizedString>
#include <QDebug>
#include <QDir>
#include <QTemporaryFile>
static void recurseCreateTOC(QDomDocument &maindoc, const QDomNode &parent, QDomNode &parentDestination, KDjVu *djvu)
{
QDomNode n = parent.firstChild();
while (!n.isNull()) {
QDomElement el = n.toElement();
QDomElement newel = maindoc.createElement(el.attribute(QStringLiteral("title")));
parentDestination.appendChild(newel);
QString dest;
if (!(dest = el.attribute(QStringLiteral("PageNumber"))).isEmpty()) {
Okular::DocumentViewport vp;
vp.pageNumber = dest.toInt() - 1;
newel.setAttribute(QStringLiteral("Viewport"), vp.toString());
} else if (!(dest = el.attribute(QStringLiteral("PageName"))).isEmpty()) {
Okular::DocumentViewport vp;
vp.pageNumber = djvu->pageNumber(dest);
newel.setAttribute(QStringLiteral("Viewport"), vp.toString());
} else if (!(dest = el.attribute(QStringLiteral("URL"))).isEmpty()) {
newel.setAttribute(QStringLiteral("URL"), dest);
}
if (el.hasChildNodes()) {
recurseCreateTOC(maindoc, n, newel, djvu);
}
n = n.nextSibling();
}
}
OKULAR_EXPORT_PLUGIN(DjVuGenerator, "libokularGenerator_djvu.json")
DjVuGenerator::DjVuGenerator(QObject *parent, const QVariantList &args)
: Okular::Generator(parent, args)
, m_docSyn(nullptr)
{
setFeature(TextExtraction);
setFeature(Threaded);
setFeature(PrintPostscript);
if (Okular::FilePrinter::ps2pdfAvailable()) {
setFeature(PrintToFile);
}
m_djvu = new KDjVu();
m_djvu->setCacheEnabled(false);
}
DjVuGenerator::~DjVuGenerator()
{
delete m_djvu;
}
bool DjVuGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector)
{
QMutexLocker locker(userMutex());
if (!m_djvu->openFile(fileName)) {
return false;
}
locker.unlock();
loadPages(pagesVector, 0);
return true;
}
bool DjVuGenerator::doCloseDocument()
{
userMutex()->lock();
m_djvu->closeFile();
userMutex()->unlock();
delete m_docSyn;
m_docSyn = nullptr;
return true;
}
QImage DjVuGenerator::image(Okular::PixmapRequest *request)
{
userMutex()->lock();
QImage img = m_djvu->image(request->pageNumber(), request->width(), request->height(), request->page()->rotation());
userMutex()->unlock();
return img;
}
Okular::DocumentInfo DjVuGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const
{
Okular::DocumentInfo docInfo;
if (keys.contains(Okular::DocumentInfo::MimeType)) {
docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("image/vnd.djvu"));
}
if (m_djvu) {
// compile internal structure reading properties from KDjVu
if (keys.contains(Okular::DocumentInfo::Author)) {
docInfo.set(Okular::DocumentInfo::Title, m_djvu->metaData(QStringLiteral("title")).toString());
}
if (keys.contains(Okular::DocumentInfo::Author)) {
docInfo.set(Okular::DocumentInfo::Author, m_djvu->metaData(QStringLiteral("author")).toString());
}
if (keys.contains(Okular::DocumentInfo::CreationDate)) {
docInfo.set(Okular::DocumentInfo::CreationDate, m_djvu->metaData(QStringLiteral("year")).toString());
}
if (keys.contains(Okular::DocumentInfo::CustomKeys)) {
docInfo.set(QStringLiteral("editor"), m_djvu->metaData(QStringLiteral("editor")).toString(), i18n("Editor"));
docInfo.set(QStringLiteral("publisher"), m_djvu->metaData(QStringLiteral("publisher")).toString(), i18n("Publisher"));
docInfo.set(QStringLiteral("volume"), m_djvu->metaData(QStringLiteral("volume")).toString(), i18n("Volume"));
docInfo.set(QStringLiteral("documentType"), m_djvu->metaData(QStringLiteral("documentType")).toString(), i18n("Type of document"));
QVariant numcomponents = m_djvu->metaData(QStringLiteral("componentFile"));
docInfo.set(QStringLiteral("componentFile"), numcomponents.metaType().id() != QMetaType::Int ? i18nc("Unknown number of component files", "Unknown") : numcomponents.toString(), i18n("Component Files"));
}
}
return docInfo;
}
const Okular::DocumentSynopsis *DjVuGenerator::generateDocumentSynopsis()
{
QMutexLocker locker(userMutex());
if (m_docSyn) {
return m_docSyn;
}
const QDomDocument *doc = m_djvu->documentBookmarks();
if (doc) {
m_docSyn = new Okular::DocumentSynopsis();
recurseCreateTOC(*m_docSyn, *doc, *m_docSyn, m_djvu);
}
locker.unlock();
return m_docSyn;
}
Okular::Document::PrintError DjVuGenerator::print(QPrinter &printer)
{
// Create tempfile to write to
QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));
if (!tf.open()) {
return Okular::Document::TemporaryFileOpenPrintError;
}
const QString fileName = tf.fileName();
QMutexLocker locker(userMutex());
QList<int> pageList = Okular::FilePrinter::pageList(printer, m_djvu->pages().count(), document()->currentPage() + 1, document()->bookmarkedPageList());
if (m_djvu->exportAsPostScript(&tf, pageList)) {
tf.setAutoRemove(false);
tf.close();
return Okular::FilePrinter::printFile(printer, fileName, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange());
}
return Okular::Document::UnknownPrintError;
}
QVariant DjVuGenerator::metaData(const QString &key, const QVariant &option) const
{
Q_UNUSED(option)
if (key == QLatin1String("DocumentTitle")) {
return m_djvu->metaData(QStringLiteral("title"));
}
return QVariant();
}
Okular::TextPage *DjVuGenerator::textPage(Okular::TextRequest *request)
{
userMutex()->lock();
const Okular::Page *page = request->page();
QList<KDjVu::TextEntity> te = m_djvu->textEntities(page->number(), QStringLiteral("word"));
if (te.isEmpty()) {
te = m_djvu->textEntities(page->number(), QStringLiteral("line"));
}
userMutex()->unlock();
QList<KDjVu::TextEntity>::ConstIterator it = te.constBegin();
QList<KDjVu::TextEntity>::ConstIterator itEnd = te.constEnd();
QList<Okular::TextEntity> words;
const KDjVu::Page &djvupage = m_djvu->pages().at(page->number());
for (; it != itEnd; ++it) {
const KDjVu::TextEntity &cur = *it;
words.append(Okular::TextEntity(cur.text(), Okular::NormalizedRect(cur.rect(), djvupage.width(), djvupage.height())));
}
Okular::TextPage *textpage = new Okular::TextPage(words);
return textpage;
}
void DjVuGenerator::loadPages(QVector<Okular::Page *> &pagesVector, int rotation)
{
const QVector<KDjVu::Page> &djvu_pages = m_djvu->pages();
int numofpages = djvu_pages.count();
pagesVector.resize(numofpages);
for (int i = 0; i < numofpages; ++i) {
const KDjVu::Page &p = djvu_pages.at(i);
if (pagesVector[i]) {
delete pagesVector[i];
}
int w = p.width();
int h = p.height();
if (rotation % 2 == 1) {
std::swap(w, h);
}
Okular::Page *page = new Okular::Page(i, w, h, (Okular::Rotation)(p.orientation() + rotation));
pagesVector[i] = page;
QList<KDjVu::Annotation *> annots;
QList<KDjVu::Link *> links;
userMutex()->lock();
m_djvu->linksAndAnnotationsForPage(i, &links, &annots);
userMutex()->unlock();
if (!links.isEmpty()) {
QList<Okular::ObjectRect *> rects;
QList<KDjVu::Link *>::ConstIterator it = links.constBegin();
QList<KDjVu::Link *>::ConstIterator itEnd = links.constEnd();
for (; it != itEnd; ++it) {
KDjVu::Link *curlink = (*it);
Okular::ObjectRect *newrect = convertKDjVuLink(i, curlink);
if (newrect) {
rects.append(newrect);
}
// delete the links as soon as we process them
delete curlink;
}
if (rects.count() > 0) {
page->setObjectRects(rects);
}
}
if (!annots.isEmpty()) {
QList<KDjVu::Annotation *>::ConstIterator it = annots.constBegin();
QList<KDjVu::Annotation *>::ConstIterator itEnd = annots.constEnd();
for (; it != itEnd; ++it) {
KDjVu::Annotation *ann = (*it);
Okular::Annotation *newann = convertKDjVuAnnotation(w, h, ann);
if (newann) {
page->addAnnotation(newann);
}
// delete the annotations as soon as we process them
delete ann;
}
}
}
}
Okular::ObjectRect *DjVuGenerator::convertKDjVuLink(int page, KDjVu::Link *link) const
{
Okular::Action *newlink = nullptr;
Okular::ObjectRect *newrect = nullptr;
switch (link->type()) {
case KDjVu::Link::PageLink: {
const KDjVu::PageLink *l = static_cast<KDjVu::PageLink *>(link);
bool ok = true;
QString target = l->page();
if ((target.length() > 0) && target.at(0) == QLatin1Char('#')) {
target.remove(0, 1);
}
int tmppage = target.toInt(&ok);
if (ok || target.isEmpty()) {
Okular::DocumentViewport vp;
if (!target.isEmpty()) {
vp.pageNumber = (target.at(0) == QLatin1Char('+') || target.at(0) == QLatin1Char('-')) ? page + tmppage : tmppage - 1;
}
newlink = new Okular::GotoAction(QString(), vp);
}
break;
}
case KDjVu::Link::UrlLink: {
const KDjVu::UrlLink *l = static_cast<KDjVu::UrlLink *>(link);
QString url = l->url();
newlink = new Okular::BrowseAction(QUrl(url));
break;
}
}
if (newlink) {
const KDjVu::Page &p = m_djvu->pages().at(page);
int width = p.width();
int height = p.height();
bool scape_orientation = false; // hack by tokoe, should always create default page
if (scape_orientation) {
std::swap(width, height);
}
switch (link->areaType()) {
case KDjVu::Link::RectArea:
case KDjVu::Link::EllipseArea: {
QRect r(QPoint(link->point().x(), p.height() - link->point().y() - link->size().height()), link->size());
bool ellipse = (link->areaType() == KDjVu::Link::EllipseArea);
newrect = new Okular::ObjectRect(Okular::NormalizedRect(Okular::Utils::rotateRect(r, width, height, 0), width, height), ellipse, Okular::ObjectRect::Action, newlink);
break;
}
case KDjVu::Link::PolygonArea: {
QPolygon poly = link->polygon();
QPolygonF newpoly;
for (int i = 0; i < poly.count(); ++i) {
int x = poly.at(i).x();
int y = poly.at(i).y();
if (scape_orientation) {
std::swap(x, y);
} else {
y = height - y;
}
newpoly << QPointF((double)(x) / width, (double)(y) / height);
}
if (!newpoly.isEmpty()) {
newpoly << newpoly.first();
newrect = new Okular::ObjectRect(newpoly, Okular::ObjectRect::Action, newlink);
}
break;
}
default:;
}
if (!newrect) {
delete newlink;
}
}
return newrect;
}
Okular::Annotation *DjVuGenerator::convertKDjVuAnnotation(int w, int h, KDjVu::Annotation *ann) const
{
Okular::Annotation *newann = nullptr;
switch (ann->type()) {
case KDjVu::Annotation::TextAnnotation: {
KDjVu::TextAnnotation *txtann = static_cast<KDjVu::TextAnnotation *>(ann);
Okular::TextAnnotation *newtxtann = new Okular::TextAnnotation();
// boundary
QRect rect(QPoint(txtann->point().x(), h - txtann->point().y() - txtann->size().height()), txtann->size());
newtxtann->setBoundingRectangle(Okular::NormalizedRect(Okular::Utils::rotateRect(rect, w, h, 0), w, h));
// type
newtxtann->setTextType(txtann->inlineText() ? Okular::TextAnnotation::InPlace : Okular::TextAnnotation::Linked);
newtxtann->style().setOpacity(txtann->color().alphaF());
// FIXME remove once the annotation text handling is fixed
newtxtann->setContents(ann->comment());
newann = newtxtann;
break;
}
case KDjVu::Annotation::LineAnnotation: {
const KDjVu::LineAnnotation *lineann = static_cast<KDjVu::LineAnnotation *>(ann);
Okular::LineAnnotation *newlineann = new Okular::LineAnnotation();
// boundary
QPoint a(lineann->point().x(), h - lineann->point().y());
QPoint b(lineann->point2().x(), h - lineann->point2().y());
QRect rect = QRect(a, b).normalized();
newlineann->setBoundingRectangle(Okular::NormalizedRect(Okular::Utils::rotateRect(rect, w, h, 0), w, h));
// line points
QList<Okular::NormalizedPoint> points;
points.append(Okular::NormalizedPoint(a.x(), a.y(), w, h));
points.append(Okular::NormalizedPoint(b.x(), b.y(), w, h));
newlineann->setLinePoints(points);
// arrow?
if (lineann->isArrow()) {
newlineann->setLineEndStyle(Okular::LineAnnotation::OpenArrow);
}
// width
newlineann->style().setWidth(lineann->width());
newann = newlineann;
break;
}
}
if (newann) {
// setting the common parameters
newann->style().setColor(ann->color());
newann->setContents(ann->comment());
// creating an id as name for the annotation
QString uid = QUuid::createUuid().toString();
uid.remove(0, 1);
uid.chop(1);
uid.remove(QLatin1Char('-'));
newann->setUniqueName(uid);
// is external
newann->setFlags(newann->flags() | Okular::Annotation::External);
}
return newann;
}
#include "generator_djvu.moc"