File indexing completed on 2024-05-12 16:06:34

0001 /*
0002     SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "generator_djvu.h"
0008 
0009 #include <core/action.h>
0010 #include <core/annotations.h>
0011 #include <core/area.h>
0012 #include <core/document.h>
0013 #include <core/fileprinter.h>
0014 #include <core/page.h>
0015 #include <core/textpage.h>
0016 #include <core/utils.h>
0017 
0018 #include <QDomDocument>
0019 #include <QMutex>
0020 #include <QPixmap>
0021 #include <QPrinter>
0022 #include <QString>
0023 #include <QUuid>
0024 
0025 #include <KAboutData>
0026 #include <KLocalizedString>
0027 #include <QDebug>
0028 #include <QDir>
0029 #include <QTemporaryFile>
0030 
0031 static void recurseCreateTOC(QDomDocument &maindoc, const QDomNode &parent, QDomNode &parentDestination, KDjVu *djvu)
0032 {
0033     QDomNode n = parent.firstChild();
0034     while (!n.isNull()) {
0035         QDomElement el = n.toElement();
0036 
0037         QDomElement newel = maindoc.createElement(el.attribute(QStringLiteral("title")));
0038         parentDestination.appendChild(newel);
0039 
0040         QString dest;
0041         if (!(dest = el.attribute(QStringLiteral("PageNumber"))).isEmpty()) {
0042             Okular::DocumentViewport vp;
0043             vp.pageNumber = dest.toInt() - 1;
0044             newel.setAttribute(QStringLiteral("Viewport"), vp.toString());
0045         } else if (!(dest = el.attribute(QStringLiteral("PageName"))).isEmpty()) {
0046             Okular::DocumentViewport vp;
0047             vp.pageNumber = djvu->pageNumber(dest);
0048             newel.setAttribute(QStringLiteral("Viewport"), vp.toString());
0049         } else if (!(dest = el.attribute(QStringLiteral("URL"))).isEmpty()) {
0050             newel.setAttribute(QStringLiteral("URL"), dest);
0051         }
0052 
0053         if (el.hasChildNodes()) {
0054             recurseCreateTOC(maindoc, n, newel, djvu);
0055         }
0056         n = n.nextSibling();
0057     }
0058 }
0059 
0060 OKULAR_EXPORT_PLUGIN(DjVuGenerator, "libokularGenerator_djvu.json")
0061 
0062 DjVuGenerator::DjVuGenerator(QObject *parent, const QVariantList &args)
0063     : Okular::Generator(parent, args)
0064     , m_docSyn(nullptr)
0065 {
0066     setFeature(TextExtraction);
0067     setFeature(Threaded);
0068     setFeature(PrintPostscript);
0069     if (Okular::FilePrinter::ps2pdfAvailable()) {
0070         setFeature(PrintToFile);
0071     }
0072 
0073     m_djvu = new KDjVu();
0074     m_djvu->setCacheEnabled(false);
0075 }
0076 
0077 DjVuGenerator::~DjVuGenerator()
0078 {
0079     delete m_djvu;
0080 }
0081 
0082 bool DjVuGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector)
0083 {
0084     QMutexLocker locker(userMutex());
0085     if (!m_djvu->openFile(fileName)) {
0086         return false;
0087     }
0088 
0089     locker.unlock();
0090 
0091     loadPages(pagesVector, 0);
0092 
0093     return true;
0094 }
0095 
0096 bool DjVuGenerator::doCloseDocument()
0097 {
0098     userMutex()->lock();
0099     m_djvu->closeFile();
0100     userMutex()->unlock();
0101 
0102     delete m_docSyn;
0103     m_docSyn = nullptr;
0104 
0105     return true;
0106 }
0107 
0108 QImage DjVuGenerator::image(Okular::PixmapRequest *request)
0109 {
0110     userMutex()->lock();
0111     QImage img = m_djvu->image(request->pageNumber(), request->width(), request->height(), request->page()->rotation());
0112     userMutex()->unlock();
0113     return img;
0114 }
0115 
0116 Okular::DocumentInfo DjVuGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const
0117 {
0118     Okular::DocumentInfo docInfo;
0119 
0120     if (keys.contains(Okular::DocumentInfo::MimeType)) {
0121         docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("image/vnd.djvu"));
0122     }
0123 
0124     if (m_djvu) {
0125         // compile internal structure reading properties from KDjVu
0126         if (keys.contains(Okular::DocumentInfo::Author)) {
0127             docInfo.set(Okular::DocumentInfo::Title, m_djvu->metaData(QStringLiteral("title")).toString());
0128         }
0129         if (keys.contains(Okular::DocumentInfo::Author)) {
0130             docInfo.set(Okular::DocumentInfo::Author, m_djvu->metaData(QStringLiteral("author")).toString());
0131         }
0132         if (keys.contains(Okular::DocumentInfo::CreationDate)) {
0133             docInfo.set(Okular::DocumentInfo::CreationDate, m_djvu->metaData(QStringLiteral("year")).toString());
0134         }
0135         if (keys.contains(Okular::DocumentInfo::CustomKeys)) {
0136             docInfo.set(QStringLiteral("editor"), m_djvu->metaData(QStringLiteral("editor")).toString(), i18n("Editor"));
0137             docInfo.set(QStringLiteral("publisher"), m_djvu->metaData(QStringLiteral("publisher")).toString(), i18n("Publisher"));
0138             docInfo.set(QStringLiteral("volume"), m_djvu->metaData(QStringLiteral("volume")).toString(), i18n("Volume"));
0139             docInfo.set(QStringLiteral("documentType"), m_djvu->metaData(QStringLiteral("documentType")).toString(), i18n("Type of document"));
0140             QVariant numcomponents = m_djvu->metaData(QStringLiteral("componentFile"));
0141             docInfo.set(QStringLiteral("componentFile"), numcomponents.metaType().id() != QMetaType::Int ? i18nc("Unknown number of component files", "Unknown") : numcomponents.toString(), i18n("Component Files"));
0142         }
0143     }
0144 
0145     return docInfo;
0146 }
0147 
0148 const Okular::DocumentSynopsis *DjVuGenerator::generateDocumentSynopsis()
0149 {
0150     QMutexLocker locker(userMutex());
0151     if (m_docSyn) {
0152         return m_docSyn;
0153     }
0154 
0155     const QDomDocument *doc = m_djvu->documentBookmarks();
0156     if (doc) {
0157         m_docSyn = new Okular::DocumentSynopsis();
0158         recurseCreateTOC(*m_docSyn, *doc, *m_docSyn, m_djvu);
0159     }
0160     locker.unlock();
0161 
0162     return m_docSyn;
0163 }
0164 
0165 Okular::Document::PrintError DjVuGenerator::print(QPrinter &printer)
0166 {
0167     // Create tempfile to write to
0168     QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));
0169     if (!tf.open()) {
0170         return Okular::Document::TemporaryFileOpenPrintError;
0171     }
0172     const QString fileName = tf.fileName();
0173 
0174     QMutexLocker locker(userMutex());
0175     QList<int> pageList = Okular::FilePrinter::pageList(printer, m_djvu->pages().count(), document()->currentPage() + 1, document()->bookmarkedPageList());
0176 
0177     if (m_djvu->exportAsPostScript(&tf, pageList)) {
0178         tf.setAutoRemove(false);
0179         tf.close();
0180         return Okular::FilePrinter::printFile(printer, fileName, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange());
0181     }
0182 
0183     return Okular::Document::UnknownPrintError;
0184 }
0185 
0186 QVariant DjVuGenerator::metaData(const QString &key, const QVariant &option) const
0187 {
0188     Q_UNUSED(option)
0189     if (key == QLatin1String("DocumentTitle")) {
0190         return m_djvu->metaData(QStringLiteral("title"));
0191     }
0192     return QVariant();
0193 }
0194 
0195 Okular::TextPage *DjVuGenerator::textPage(Okular::TextRequest *request)
0196 {
0197     userMutex()->lock();
0198     const Okular::Page *page = request->page();
0199     QList<KDjVu::TextEntity> te;
0200     if (te.isEmpty()) {
0201         te = m_djvu->textEntities(page->number(), QStringLiteral("word"));
0202     }
0203     if (te.isEmpty()) {
0204         te = m_djvu->textEntities(page->number(), QStringLiteral("line"));
0205     }
0206     userMutex()->unlock();
0207     QList<KDjVu::TextEntity>::ConstIterator it = te.constBegin();
0208     QList<KDjVu::TextEntity>::ConstIterator itEnd = te.constEnd();
0209     QList<Okular::TextEntity> words;
0210     const KDjVu::Page *djvupage = m_djvu->pages().at(page->number());
0211     for (; it != itEnd; ++it) {
0212         const KDjVu::TextEntity &cur = *it;
0213         words.append(Okular::TextEntity(cur.text(), Okular::NormalizedRect(cur.rect(), djvupage->width(), djvupage->height())));
0214     }
0215     Okular::TextPage *textpage = new Okular::TextPage(words);
0216     return textpage;
0217 }
0218 
0219 void DjVuGenerator::loadPages(QVector<Okular::Page *> &pagesVector, int rotation)
0220 {
0221     const QVector<KDjVu::Page *> &djvu_pages = m_djvu->pages();
0222     int numofpages = djvu_pages.count();
0223     pagesVector.resize(numofpages);
0224 
0225     for (int i = 0; i < numofpages; ++i) {
0226         const KDjVu::Page *p = djvu_pages.at(i);
0227         if (pagesVector[i]) {
0228             delete pagesVector[i];
0229         }
0230         int w = p->width();
0231         int h = p->height();
0232         if (rotation % 2 == 1) {
0233             qSwap(w, h);
0234         }
0235         Okular::Page *page = new Okular::Page(i, w, h, (Okular::Rotation)(p->orientation() + rotation));
0236         pagesVector[i] = page;
0237 
0238         QList<KDjVu::Annotation *> annots;
0239         QList<KDjVu::Link *> links;
0240         userMutex()->lock();
0241         m_djvu->linksAndAnnotationsForPage(i, &links, &annots);
0242         userMutex()->unlock();
0243         if (!links.isEmpty()) {
0244             QList<Okular::ObjectRect *> rects;
0245             QList<KDjVu::Link *>::ConstIterator it = links.constBegin();
0246             QList<KDjVu::Link *>::ConstIterator itEnd = links.constEnd();
0247             for (; it != itEnd; ++it) {
0248                 KDjVu::Link *curlink = (*it);
0249                 Okular::ObjectRect *newrect = convertKDjVuLink(i, curlink);
0250                 if (newrect) {
0251                     rects.append(newrect);
0252                 }
0253                 // delete the links as soon as we process them
0254                 delete curlink;
0255             }
0256             if (rects.count() > 0) {
0257                 page->setObjectRects(rects);
0258             }
0259         }
0260         if (!annots.isEmpty()) {
0261             QList<KDjVu::Annotation *>::ConstIterator it = annots.constBegin();
0262             QList<KDjVu::Annotation *>::ConstIterator itEnd = annots.constEnd();
0263             for (; it != itEnd; ++it) {
0264                 KDjVu::Annotation *ann = (*it);
0265                 Okular::Annotation *newann = convertKDjVuAnnotation(w, h, ann);
0266                 if (newann) {
0267                     page->addAnnotation(newann);
0268                 }
0269                 // delete the annotations as soon as we process them
0270                 delete ann;
0271             }
0272         }
0273     }
0274 }
0275 
0276 Okular::ObjectRect *DjVuGenerator::convertKDjVuLink(int page, KDjVu::Link *link) const
0277 {
0278     Okular::Action *newlink = nullptr;
0279     Okular::ObjectRect *newrect = nullptr;
0280     switch (link->type()) {
0281     case KDjVu::Link::PageLink: {
0282         KDjVu::PageLink *l = static_cast<KDjVu::PageLink *>(link);
0283         bool ok = true;
0284         QString target = l->page();
0285         if ((target.length() > 0) && target.at(0) == QLatin1Char('#')) {
0286             target.remove(0, 1);
0287         }
0288         int tmppage = target.toInt(&ok);
0289         if (ok || target.isEmpty()) {
0290             Okular::DocumentViewport vp;
0291             if (!target.isEmpty()) {
0292                 vp.pageNumber = (target.at(0) == QLatin1Char('+') || target.at(0) == QLatin1Char('-')) ? page + tmppage : tmppage - 1;
0293             }
0294             newlink = new Okular::GotoAction(QString(), vp);
0295         }
0296         break;
0297     }
0298     case KDjVu::Link::UrlLink: {
0299         KDjVu::UrlLink *l = static_cast<KDjVu::UrlLink *>(link);
0300         QString url = l->url();
0301         newlink = new Okular::BrowseAction(QUrl(url));
0302         break;
0303     }
0304     }
0305     if (newlink) {
0306         const KDjVu::Page *p = m_djvu->pages().at(page);
0307         int width = p->width();
0308         int height = p->height();
0309         bool scape_orientation = false; // hack by tokoe, should always create default page
0310         if (scape_orientation) {
0311             qSwap(width, height);
0312         }
0313         switch (link->areaType()) {
0314         case KDjVu::Link::RectArea:
0315         case KDjVu::Link::EllipseArea: {
0316             QRect r(QPoint(link->point().x(), p->height() - link->point().y() - link->size().height()), link->size());
0317             bool ellipse = (link->areaType() == KDjVu::Link::EllipseArea);
0318             newrect = new Okular::ObjectRect(Okular::NormalizedRect(Okular::Utils::rotateRect(r, width, height, 0), width, height), ellipse, Okular::ObjectRect::Action, newlink);
0319             break;
0320         }
0321         case KDjVu::Link::PolygonArea: {
0322             QPolygon poly = link->polygon();
0323             QPolygonF newpoly;
0324             for (int i = 0; i < poly.count(); ++i) {
0325                 int x = poly.at(i).x();
0326                 int y = poly.at(i).y();
0327                 if (scape_orientation) {
0328                     qSwap(x, y);
0329                 } else {
0330                     y = height - y;
0331                 }
0332                 newpoly << QPointF((double)(x) / width, (double)(y) / height);
0333             }
0334             if (!newpoly.isEmpty()) {
0335                 newpoly << newpoly.first();
0336                 newrect = new Okular::ObjectRect(newpoly, Okular::ObjectRect::Action, newlink);
0337             }
0338             break;
0339         }
0340         default:;
0341         }
0342         if (!newrect) {
0343             delete newlink;
0344         }
0345     }
0346     return newrect;
0347 }
0348 
0349 Okular::Annotation *DjVuGenerator::convertKDjVuAnnotation(int w, int h, KDjVu::Annotation *ann) const
0350 {
0351     Okular::Annotation *newann = nullptr;
0352     switch (ann->type()) {
0353     case KDjVu::Annotation::TextAnnotation: {
0354         KDjVu::TextAnnotation *txtann = static_cast<KDjVu::TextAnnotation *>(ann);
0355         Okular::TextAnnotation *newtxtann = new Okular::TextAnnotation();
0356         // boundary
0357         QRect rect(QPoint(txtann->point().x(), h - txtann->point().y() - txtann->size().height()), txtann->size());
0358         newtxtann->setBoundingRectangle(Okular::NormalizedRect(Okular::Utils::rotateRect(rect, w, h, 0), w, h));
0359         // type
0360         newtxtann->setTextType(txtann->inlineText() ? Okular::TextAnnotation::InPlace : Okular::TextAnnotation::Linked);
0361         newtxtann->style().setOpacity(txtann->color().alphaF());
0362         // FIXME remove once the annotation text handling is fixed
0363         newtxtann->setContents(ann->comment());
0364         newann = newtxtann;
0365         break;
0366     }
0367     case KDjVu::Annotation::LineAnnotation: {
0368         KDjVu::LineAnnotation *lineann = static_cast<KDjVu::LineAnnotation *>(ann);
0369         Okular::LineAnnotation *newlineann = new Okular::LineAnnotation();
0370         // boundary
0371         QPoint a(lineann->point().x(), h - lineann->point().y());
0372         QPoint b(lineann->point2().x(), h - lineann->point2().y());
0373         QRect rect = QRect(a, b).normalized();
0374         newlineann->setBoundingRectangle(Okular::NormalizedRect(Okular::Utils::rotateRect(rect, w, h, 0), w, h));
0375         // line points
0376         QList<Okular::NormalizedPoint> points;
0377         points.append(Okular::NormalizedPoint(a.x(), a.y(), w, h));
0378         points.append(Okular::NormalizedPoint(b.x(), b.y(), w, h));
0379         newlineann->setLinePoints(points);
0380         // arrow?
0381         if (lineann->isArrow()) {
0382             newlineann->setLineEndStyle(Okular::LineAnnotation::OpenArrow);
0383         }
0384         // width
0385         newlineann->style().setWidth(lineann->width());
0386         newann = newlineann;
0387         break;
0388     }
0389     }
0390     if (newann) {
0391         // setting the common parameters
0392         newann->style().setColor(ann->color());
0393         newann->setContents(ann->comment());
0394         // creating an id as name for the annotation
0395         QString uid = QUuid::createUuid().toString();
0396         uid.remove(0, 1);
0397         uid.chop(1);
0398         uid.remove(QLatin1Char('-'));
0399         newann->setUniqueName(uid);
0400         // is external
0401         newann->setFlags(newann->flags() | Okular::Annotation::External);
0402     }
0403     return newann;
0404 }
0405 
0406 #include "generator_djvu.moc"