File indexing completed on 2024-05-12 04:33:58

0001 /*
0002     SPDX-FileCopyrightText: 2006-2009 Luigi Toscano <luigi.toscano@tiscali.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <core/action.h>
0008 #include <core/document.h>
0009 #include <core/fileprinter.h>
0010 #include <core/page.h>
0011 #include <core/sourcereference.h>
0012 #include <core/textpage.h>
0013 
0014 #include "TeXFont.h"
0015 #include "debug_dvi.h"
0016 #include "dviFile.h"
0017 #include "dviPageInfo.h"
0018 #include "dviRenderer.h"
0019 #include "dviexport.h"
0020 #include "generator_dvi.h"
0021 #include "pageSize.h"
0022 
0023 #include <QApplication>
0024 #include <QDir>
0025 #include <QMutex>
0026 #include <QStack>
0027 #include <QString>
0028 #include <QTemporaryFile>
0029 #include <QUrl>
0030 #include <QVector>
0031 
0032 #include <KAboutData>
0033 #include <KLocalizedString>
0034 #include <QDebug>
0035 
0036 #ifdef DVI_OPEN_BUSYLOOP
0037 #include <QThread>
0038 #endif
0039 
0040 OKULAR_EXPORT_PLUGIN(DviGenerator, "libokularGenerator_dvi.json")
0041 
0042 DviGenerator::DviGenerator(QObject *parent, const QVariantList &args)
0043     : Okular::Generator(parent, args)
0044     , m_fontExtracted(false)
0045     , m_docSynopsis(nullptr)
0046     , m_dviRenderer(nullptr)
0047 {
0048     setFeature(Threaded);
0049     setFeature(TextExtraction);
0050     setFeature(FontInfo);
0051     setFeature(PrintPostscript);
0052     if (Okular::FilePrinter::ps2pdfAvailable()) {
0053         setFeature(PrintToFile);
0054     }
0055 }
0056 
0057 bool DviGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector)
0058 {
0059     // qCDebug(OkularDviDebug) << "file:" << fileName;
0060     QUrl base(QUrl::fromLocalFile(fileName));
0061 
0062     (void)userMutex();
0063 
0064     m_dviRenderer = new dviRenderer(documentMetaData(TextHintingMetaData, QVariant()).toBool());
0065     connect(m_dviRenderer, &dviRenderer::error, this, &DviGenerator::error);
0066     connect(m_dviRenderer, &dviRenderer::warning, this, &DviGenerator::warning);
0067     connect(m_dviRenderer, &dviRenderer::notice, this, &DviGenerator::notice);
0068 #ifdef DVI_OPEN_BUSYLOOP
0069     static const ushort s_waitTime = 800; // milliseconds
0070     static const int s_maxIterations = 10;
0071     int iter = 0;
0072     for (; !m_dviRenderer->isValidFile(fileName) && iter < s_maxIterations; ++iter) {
0073         qCDebug(OkularDviDebug).nospace() << "file not valid after iteration #" << iter << "/" << s_maxIterations << ", waiting for " << s_waitTime;
0074         QThread::msleep(s_waitTime);
0075     }
0076     if (iter >= s_maxIterations && !m_dviRenderer->isValidFile(fileName)) {
0077         qCDebug(OkularDviDebug) << "file still not valid after" << s_maxIterations;
0078         delete m_dviRenderer;
0079         m_dviRenderer = 0;
0080         return false;
0081     }
0082 #else
0083     if (!m_dviRenderer->isValidFile(fileName)) {
0084         delete m_dviRenderer;
0085         m_dviRenderer = nullptr;
0086         return false;
0087     }
0088 #endif
0089     if (!m_dviRenderer->setFile(fileName, base)) {
0090         delete m_dviRenderer;
0091         m_dviRenderer = nullptr;
0092         return false;
0093     }
0094 
0095     qCDebug(OkularDviDebug) << "# of pages:" << m_dviRenderer->dviFile->total_pages;
0096 
0097     m_resolution = dpi().height();
0098     loadPages(pagesVector);
0099 
0100     return true;
0101 }
0102 
0103 bool DviGenerator::doCloseDocument()
0104 {
0105     delete m_docSynopsis;
0106     m_docSynopsis = nullptr;
0107     delete m_dviRenderer;
0108     m_dviRenderer = nullptr;
0109 
0110     m_linkGenerated.clear();
0111     m_fontExtracted = false;
0112 
0113     return true;
0114 }
0115 
0116 void DviGenerator::fillViewportFromAnchor(Okular::DocumentViewport &vp, const Anchor anch, const Okular::Page *page) const
0117 {
0118     fillViewportFromAnchor(vp, anch, page->width(), page->height());
0119 }
0120 
0121 void DviGenerator::fillViewportFromAnchor(Okular::DocumentViewport &vp, const Anchor anch, int pW, int pH) const
0122 {
0123     vp.pageNumber = static_cast<quint16>(anch.page) - 1;
0124 
0125     SimplePageSize ps = m_dviRenderer->sizeOfPage(PageNumber(vp.pageNumber));
0126     double resolution = 0;
0127     if (ps.isValid()) {
0128         resolution = (double)(pW) / ps.width().getLength_in_inch();
0129     } else {
0130         resolution = m_resolution;
0131     }
0132 
0133     double py = (double)anch.distance_from_top.getLength_in_inch() * resolution + 0.5;
0134 
0135     vp.rePos.normalizedX = 0.5;
0136     vp.rePos.normalizedY = py / (double)pH;
0137     vp.rePos.enabled = true;
0138     vp.rePos.pos = Okular::DocumentViewport::Center;
0139 }
0140 
0141 QList<Okular::ObjectRect *> DviGenerator::generateDviLinks(const dviPageInfo *pageInfo)
0142 {
0143     QList<Okular::ObjectRect *> dviLinks;
0144 
0145     int pageWidth = pageInfo->width, pageHeight = pageInfo->height;
0146 
0147     for (const Hyperlink &dviLink : std::as_const(pageInfo->hyperLinkList)) {
0148         QRect boxArea = dviLink.box;
0149         double nl = (double)boxArea.left() / pageWidth, nt = (double)boxArea.top() / pageHeight, nr = (double)boxArea.right() / pageWidth, nb = (double)boxArea.bottom() / pageHeight;
0150 
0151         QString linkText = dviLink.linkText;
0152         if (linkText.startsWith(QLatin1String("#"))) {
0153             linkText = linkText.mid(1);
0154         }
0155         Anchor anch = m_dviRenderer->findAnchor(linkText);
0156 
0157         Okular::Action *okuLink = nullptr;
0158 
0159         /* distinguish between local (-> anchor) and remote links */
0160         if (anch.isValid()) {
0161             /* internal link */
0162             Okular::DocumentViewport vp;
0163             fillViewportFromAnchor(vp, anch, pageWidth, pageHeight);
0164 
0165             okuLink = new Okular::GotoAction(QLatin1String(""), vp);
0166         } else {
0167             okuLink = new Okular::BrowseAction(QUrl::fromUserInput(dviLink.linkText));
0168         }
0169         if (okuLink) {
0170             Okular::ObjectRect *orlink = new Okular::ObjectRect(nl, nt, nr, nb, false, Okular::ObjectRect::Action, okuLink);
0171             dviLinks.push_front(orlink);
0172         }
0173     }
0174     return dviLinks;
0175 }
0176 
0177 QImage DviGenerator::image(Okular::PixmapRequest *request)
0178 {
0179     dviPageInfo *pageInfo = new dviPageInfo();
0180     pageSize ps;
0181     QImage ret;
0182 
0183     pageInfo->width = request->width();
0184     pageInfo->height = request->height();
0185 
0186     pageInfo->pageNumber = request->pageNumber() + 1;
0187 
0188     //  pageInfo->resolution = m_resolution;
0189 
0190     QMutexLocker lock(userMutex());
0191 
0192     if (m_dviRenderer) {
0193         SimplePageSize s = m_dviRenderer->sizeOfPage(pageInfo->pageNumber);
0194 
0195         /*       if ( s.width() != pageInfo->width) */
0196         //   if (!useDocumentSpecifiedSize)
0197         //    s = userPreferredSize;
0198 
0199         if (s.isValid()) {
0200             pageInfo->resolution = (double)(pageInfo->width) / s.width().getLength_in_inch();
0201         } else {
0202             pageInfo->resolution = (double)(pageInfo->width) / ps.width().getLength_in_inch();
0203         }
0204 
0205         m_dviRenderer->drawPage(pageInfo);
0206 
0207         if (!pageInfo->img.isNull()) {
0208             qCDebug(OkularDviDebug) << "Image OK";
0209 
0210             ret = pageInfo->img;
0211 
0212             if (!m_linkGenerated[request->pageNumber()]) {
0213                 request->page()->setObjectRects(generateDviLinks(pageInfo));
0214                 m_linkGenerated[request->pageNumber()] = true;
0215             }
0216         }
0217     }
0218 
0219     lock.unlock();
0220 
0221     delete pageInfo;
0222 
0223     return ret;
0224 }
0225 
0226 Okular::TextPage *DviGenerator::textPage(Okular::TextRequest *request)
0227 {
0228     const Okular::Page *page = request->page();
0229 
0230     qCDebug(OkularDviDebug);
0231     dviPageInfo pageInfo;
0232 
0233     pageInfo.width = page->width();
0234     pageInfo.height = page->height();
0235 
0236     pageInfo.pageNumber = page->number() + 1;
0237 
0238     pageInfo.resolution = m_resolution;
0239 
0240     QMutexLocker lock(userMutex());
0241 
0242     // get page text from m_dviRenderer
0243     Okular::TextPage *ktp = nullptr;
0244     if (m_dviRenderer) {
0245         SimplePageSize s = m_dviRenderer->sizeOfPage(pageInfo.pageNumber);
0246         pageInfo.resolution = (double)(pageInfo.width) / s.width().getLength_in_inch();
0247 
0248         m_dviRenderer->getText(&pageInfo);
0249         lock.unlock();
0250 
0251         ktp = extractTextFromPage(pageInfo);
0252     }
0253     return ktp;
0254 }
0255 
0256 Okular::TextPage *DviGenerator::extractTextFromPage(const dviPageInfo &pageInfo)
0257 {
0258     QList<Okular::TextEntity> textOfThePage;
0259 
0260     int pageWidth = pageInfo.width, pageHeight = pageInfo.height;
0261 
0262     for (const TextBox &curTB : std::as_const(pageInfo.textBoxList)) {
0263         textOfThePage.push_back(Okular::TextEntity(curTB.text, Okular::NormalizedRect(curTB.box, pageWidth, pageHeight)));
0264     }
0265 
0266     Okular::TextPage *ktp = new Okular::TextPage(textOfThePage);
0267 
0268     return ktp;
0269 }
0270 
0271 Okular::DocumentInfo DviGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const
0272 {
0273     Okular::DocumentInfo docInfo;
0274 
0275     if (keys.contains(Okular::DocumentInfo::MimeType)) {
0276         docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("application/x-dvi"));
0277     }
0278 
0279     QMutexLocker lock(userMutex());
0280 
0281     if (m_dviRenderer && m_dviRenderer->dviFile) {
0282         dvifile *dvif = m_dviRenderer->dviFile;
0283 
0284         // read properties from dvif
0285         // docInfo.set( "filename", dvif->filename, i18n("Filename") );
0286         if (keys.contains(Okular::DocumentInfo::CustomKeys)) {
0287             docInfo.set(QStringLiteral("generatorDate"), dvif->generatorString, i18n("Generator/Date"));
0288         }
0289         if (keys.contains(Okular::DocumentInfo::Pages)) {
0290             docInfo.set(Okular::DocumentInfo::Pages, QString::number(dvif->total_pages));
0291         }
0292     }
0293     return docInfo;
0294 }
0295 
0296 const Okular::DocumentSynopsis *DviGenerator::generateDocumentSynopsis()
0297 {
0298     if (m_docSynopsis) {
0299         return m_docSynopsis;
0300     }
0301 
0302     m_docSynopsis = new Okular::DocumentSynopsis();
0303 
0304     userMutex()->lock();
0305 
0306     QVector<PreBookmark> prebookmarks = m_dviRenderer->getPrebookmarks();
0307 
0308     userMutex()->unlock();
0309 
0310     if (prebookmarks.isEmpty()) {
0311         return m_docSynopsis;
0312     }
0313 
0314     QStack<QDomElement> stack;
0315 
0316     QVector<PreBookmark>::ConstIterator it = prebookmarks.constBegin();
0317     QVector<PreBookmark>::ConstIterator itEnd = prebookmarks.constEnd();
0318     for (; it != itEnd; ++it) {
0319         QDomElement domel = m_docSynopsis->createElement((*it).title);
0320         Anchor a = m_dviRenderer->findAnchor((*it).anchorName);
0321         if (a.isValid()) {
0322             Okular::DocumentViewport vp;
0323 
0324             const Okular::Page *p = document()->page(static_cast<quint16>(a.page) - 1);
0325 
0326             fillViewportFromAnchor(vp, a, (int)p->width(), (int)p->height());
0327             domel.setAttribute(QStringLiteral("Viewport"), vp.toString());
0328         }
0329         if (stack.isEmpty()) {
0330             m_docSynopsis->appendChild(domel);
0331         } else {
0332             stack.top().appendChild(domel);
0333             stack.pop();
0334         }
0335         for (int i = 0; i < (*it).noOfChildren; ++i) {
0336             stack.push(domel);
0337         }
0338     }
0339 
0340     return m_docSynopsis;
0341 }
0342 
0343 Okular::FontInfo::List DviGenerator::fontsForPage(int page)
0344 {
0345     Q_UNUSED(page);
0346 
0347     Okular::FontInfo::List list;
0348 
0349     // the list of the fonts is extracted once
0350     if (m_fontExtracted) {
0351         return list;
0352     }
0353 
0354     if (m_dviRenderer && m_dviRenderer->dviFile && m_dviRenderer->dviFile->font_pool) {
0355         const QList<TeXFontDefinition *> fonts = m_dviRenderer->dviFile->font_pool->fontList;
0356         for (const TeXFontDefinition *font : fonts) {
0357             Okular::FontInfo of;
0358             QString name;
0359             int zoom = (int)(font->enlargement * 100 + 0.5);
0360 #ifdef HAVE_FREETYPE
0361             if (font->getFullFontName().isEmpty()) {
0362                 name = QStringLiteral("%1, %2%").arg(font->fontname).arg(zoom);
0363             } else {
0364                 name = QStringLiteral("%1 (%2), %3%").arg(font->fontname, font->getFullFontName(), QString::number(zoom));
0365             }
0366 #else
0367             name = QString("%1, %2%").arg(font->fontname).arg(zoom);
0368 #endif
0369             of.setName(name);
0370 
0371             QString fontFileName;
0372             if (!(font->flags & TeXFontDefinition::FONT_VIRTUAL)) {
0373                 if (font->font != nullptr) {
0374                     fontFileName = font->font->errorMessage;
0375                 } else {
0376                     fontFileName = i18n("Font file not found");
0377                 }
0378 
0379                 if (fontFileName.isEmpty()) {
0380                     fontFileName = font->filename;
0381                 }
0382             }
0383 
0384             of.setFile(fontFileName);
0385 
0386             Okular::FontInfo::FontType ft;
0387             switch (font->getFontType()) {
0388             case TeXFontDefinition::TEX_PK:
0389                 ft = Okular::FontInfo::TeXPK;
0390                 break;
0391             case TeXFontDefinition::TEX_VIRTUAL:
0392                 ft = Okular::FontInfo::TeXVirtual;
0393                 break;
0394             case TeXFontDefinition::TEX_FONTMETRIC:
0395                 ft = Okular::FontInfo::TeXFontMetric;
0396                 break;
0397             case TeXFontDefinition::FREETYPE:
0398                 ft = Okular::FontInfo::TeXFreeTypeHandled;
0399                 break;
0400             }
0401             of.setType(ft);
0402 
0403             // DVI has not the concept of "font embedding"
0404             of.setEmbedType(Okular::FontInfo::NotEmbedded);
0405             of.setCanBeExtracted(false);
0406 
0407             list.append(of);
0408         }
0409 
0410         m_fontExtracted = true;
0411     }
0412 
0413     return list;
0414 }
0415 
0416 void DviGenerator::loadPages(QVector<Okular::Page *> &pagesVector)
0417 {
0418     QSize pageRequiredSize;
0419 
0420     int numofpages = m_dviRenderer->dviFile->total_pages;
0421     pagesVector.resize(numofpages);
0422 
0423     m_linkGenerated.fill(false, numofpages);
0424 
0425     // qCDebug(OkularDviDebug) << "resolution:" << m_resolution << ", dviFile->preferred?";
0426 
0427     /* get the suggested size */
0428     if (m_dviRenderer->dviFile->suggestedPageSize) {
0429         pageRequiredSize = m_dviRenderer->dviFile->suggestedPageSize->sizeInPixel(m_resolution);
0430     } else {
0431         pageSize ps;
0432         pageRequiredSize = ps.sizeInPixel(m_resolution);
0433     }
0434 
0435     for (int i = 0; i < numofpages; ++i) {
0436         // qCDebug(OkularDviDebug) << "getting status of page" << i << ":";
0437 
0438         if (pagesVector[i]) {
0439             delete pagesVector[i];
0440         }
0441 
0442         Okular::Page *page = new Okular::Page(i, pageRequiredSize.width(), pageRequiredSize.height(), Okular::Rotation0);
0443 
0444         pagesVector[i] = page;
0445     }
0446     qCDebug(OkularDviDebug) << "pagesVector successfully inizialized!";
0447 
0448     // filling the pages with the source references rects
0449     const QVector<DVI_SourceFileAnchor> &sourceAnchors = m_dviRenderer->sourceAnchors();
0450     QVector<QList<Okular::SourceRefObjectRect *>> refRects(numofpages);
0451     for (const DVI_SourceFileAnchor &sfa : sourceAnchors) {
0452         if (sfa.page < 1 || (int)sfa.page > numofpages) {
0453             continue;
0454         }
0455 
0456         Okular::NormalizedPoint p(-1.0, (double)sfa.distance_from_top.getLength_in_pixel(dpi().height()) / (double)pageRequiredSize.height());
0457         Okular::SourceReference *sourceRef = new Okular::SourceReference(sfa.fileName, sfa.line);
0458         refRects[sfa.page - 1].append(new Okular::SourceRefObjectRect(p, sourceRef));
0459     }
0460     for (int i = 0; i < refRects.size(); ++i) {
0461         if (!refRects.at(i).isEmpty()) {
0462             pagesVector[i]->setSourceReferences(refRects.at(i));
0463         }
0464     }
0465 }
0466 
0467 Okular::Document::PrintError DviGenerator::print(QPrinter &printer)
0468 {
0469     // Create tempfile to write to
0470     QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));
0471     if (!tf.open()) {
0472         return Okular::Document::TemporaryFileOpenPrintError;
0473     }
0474 
0475     const QList<int> pageList = Okular::FilePrinter::pageList(printer, static_cast<quint16>(m_dviRenderer->totalPages()), document()->currentPage() + 1, document()->bookmarkedPageList());
0476     QString pages;
0477     QStringList printOptions;
0478     // List of pages to print.
0479     for (const int p : pageList) {
0480         pages += QStringLiteral(",%1").arg(p);
0481     }
0482     if (!pages.isEmpty()) {
0483         printOptions << QStringLiteral("-pp") << pages.mid(1);
0484     }
0485 
0486     QEventLoop el;
0487     m_dviRenderer->setEventLoop(&el);
0488     m_dviRenderer->exportPS(tf.fileName(), printOptions, &printer, document()->orientation());
0489 
0490     tf.close();
0491 
0492     // Error messages are handled by the generator - ugly, but it works.
0493     return Okular::Document::NoPrintError;
0494 }
0495 
0496 QVariant DviGenerator::metaData(const QString &key, const QVariant &option) const
0497 {
0498     if (key == QLatin1String("NamedViewport") && !option.toString().isEmpty()) {
0499         const Anchor anchor = m_dviRenderer->parseReference(option.toString());
0500         if (anchor.isValid()) {
0501             const Okular::Page *page = document()->page(static_cast<quint16>(anchor.page) - 1);
0502             Q_ASSERT_X(page, "DviGenerator::metaData()", "NULL page as result of valid Anchor");
0503             Okular::DocumentViewport viewport;
0504             fillViewportFromAnchor(viewport, anchor, page);
0505             if (viewport.isValid()) {
0506                 return viewport.toString();
0507             }
0508         }
0509     }
0510     return QVariant();
0511 }
0512 
0513 Q_LOGGING_CATEGORY(OkularDviDebug, "org.kde.okular.generators.dvi.core", QtWarningMsg)
0514 Q_LOGGING_CATEGORY(OkularDviShellDebug, "org.kde.okular.generators.dvi.shell", QtWarningMsg)
0515 
0516 #include "generator_dvi.moc"