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"