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

0001 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*-
0002 //
0003 // Class: dviRenderer
0004 //
0005 // Class for rendering TeX DVI files.
0006 // Part of KDVI- A previewer for TeX DVI files.
0007 //
0008 // SPDX-FileCopyrightText: 2001-2005 Stefan Kebekus
0009 // SPDX-License-Identifier: GPL-2.0-or-later
0010 
0011 #include <config.h>
0012 
0013 #include "debug_dvi.h"
0014 #include "dviFile.h"
0015 #include "dviRenderer.h"
0016 #include "dvisourcesplitter.h"
0017 #include "hyperlink.h"
0018 #include "psgs.h"
0019 //#include "renderedDviPagePixmap.h"
0020 
0021 #include <KConfig>
0022 #include <KLocalizedString>
0023 #include <QMimeDatabase>
0024 #include <QMimeType>
0025 #include <QTime>
0026 #include <QVBoxLayout>
0027 #include <math.h>
0028 
0029 #include <QApplication>
0030 #include <QCheckBox>
0031 #include <QEventLoop>
0032 #include <QFileInfo>
0033 #include <QHBoxLayout>
0034 #include <QLabel>
0035 #include <QPainter>
0036 #include <QProgressBar>
0037 #include <QRegularExpression>
0038 
0039 //------ now comes the dviRenderer class implementation ----------
0040 
0041 dviRenderer::dviRenderer(bool useFontHinting)
0042     : dviFile(nullptr)
0043     , font_pool(useFontHinting)
0044     , resolutionInDPI(0)
0045     , embedPS_progress(nullptr)
0046     , embedPS_numOfProgressedFiles(0)
0047     , shrinkfactor(3)
0048     , source_href(nullptr)
0049     , HTML_href(nullptr)
0050     , editorCommand(QLatin1String(""))
0051     , PostScriptOutPutString(nullptr)
0052     , PS_interface(new ghostscript_interface)
0053     , _postscript(true)
0054     , line_boundary_encountered(false)
0055     , word_boundary_encountered(false)
0056     , current_page(0)
0057     , penWidth_in_mInch(0)
0058     , number_of_elements_in_path(0)
0059     , currentlyDrawnPage(nullptr)
0060     , m_eventLoop(nullptr)
0061     , foreGroundPainter(nullptr)
0062     , fontpoolLocateFontsDone(false)
0063 {
0064 #ifdef DEBUG_DVIRENDERER
0065     // qCDebug(OkularDviDebug) << "dviRenderer( parent=" << par << " )";
0066 #endif
0067 
0068     connect(&font_pool, &fontPool::error, this, &dviRenderer::error);
0069     connect(&font_pool, &fontPool::warning, this, &dviRenderer::warning);
0070     connect(PS_interface, &ghostscript_interface::error, this, &dviRenderer::error);
0071 }
0072 
0073 dviRenderer::~dviRenderer()
0074 {
0075 #ifdef DEBUG_DVIRENDERER
0076     qCDebug(OkularDviDebug) << "~dviRenderer";
0077 #endif
0078 
0079     QMutexLocker locker(&mutex);
0080 
0081     delete PS_interface;
0082     delete dviFile;
0083 }
0084 
0085 //------ this function calls the dvi interpreter ----------
0086 
0087 void dviRenderer::drawPage(RenderedDocumentPagePixmap *page)
0088 {
0089 #ifdef DEBUG_DVIRENDERER
0090     // qCDebug(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called, page number " << page->pageNumber;
0091 #endif
0092 
0093     // Paranoid safety checks
0094     if (page == nullptr) {
0095         qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called with argument == 0";
0096         return;
0097     }
0098     // Paranoid safety checks
0099     if (!page->pageNumber.isValid()) {
0100         qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number 0";
0101         return;
0102     }
0103 
0104     QMutexLocker locker(&mutex);
0105 
0106     if (dviFile == nullptr) {
0107         qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called, but no dviFile class allocated.";
0108         page->clear();
0109         return;
0110     }
0111     if (static_cast<quint16>(page->pageNumber) > dviFile->total_pages) {
0112         qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number " << static_cast<quint16>(page->pageNumber) << " but the current dviFile has only " << dviFile->total_pages
0113                                    << " pages.";
0114         return;
0115     }
0116     if (dviFile->dvi_Data() == nullptr) {
0117         qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called, but no dviFile is loaded yet.";
0118         page->clear();
0119         return;
0120     }
0121 
0122     /* locateFonts() is here just once (if it has not been executed
0123        not been executed yet), so that it is possible to easily intercept
0124        the cancel signal (because for example the user tries to open
0125        another document); it would not have been possible (or more
0126        complicated) in case it was still in the document loading section.
0127      */
0128     if (!fontpoolLocateFontsDone) {
0129         font_pool.locateFonts();
0130         fontpoolLocateFontsDone = true;
0131     }
0132 
0133     double resolution = page->resolution;
0134 
0135     if (resolution != resolutionInDPI) {
0136         setResolution(resolution);
0137     }
0138 
0139     currentlyDrawnPage = page;
0140     shrinkfactor = 1200 / resolutionInDPI;
0141     current_page = static_cast<quint16>(page->pageNumber) - 1;
0142 
0143     // Reset colors
0144     colorStack.clear();
0145     globalColor = Qt::black;
0146 
0147     int pageWidth = page->width;
0148     int pageHeight = page->height;
0149 
0150     QImage img(pageWidth, pageHeight, QImage::Format_RGB32);
0151     foreGroundPainter = new QPainter(&img);
0152     if (foreGroundPainter != nullptr) {
0153         errorMsg.clear();
0154         draw_page();
0155         delete foreGroundPainter;
0156         foreGroundPainter = nullptr;
0157     } else {
0158         qCDebug(OkularDviDebug) << "painter creation failed.";
0159     }
0160     page->img = img;
0161     // page->setImage(img);
0162 
0163     // Postprocess hyperlinks
0164     // Without that, based on the way TeX draws certain characters like german "Umlaute",
0165     // some hyperlinks would be broken into two overlapping parts, in the middle of a word.
0166     QVector<Hyperlink>::iterator i = page->hyperLinkList.begin();
0167     QVector<Hyperlink>::iterator j;
0168     while (i != page->hyperLinkList.end()) {
0169         // Iterator j always points to the element after i.
0170         j = i;
0171         j++;
0172 
0173         if (j == page->hyperLinkList.end()) {
0174             break;
0175         }
0176 
0177         Hyperlink &hi = *i;
0178         Hyperlink &hj = *j;
0179 
0180         bool merged = false;
0181 
0182         // Merge all hyperlinks that point to the same target, and have the same baseline.
0183         while (hi.linkText == hj.linkText && hi.baseline == hj.baseline) {
0184             merged = true;
0185             hi.box = hi.box.united(hj.box);
0186 
0187             j++;
0188             if (j == page->hyperLinkList.end()) {
0189                 break;
0190             }
0191 
0192             hj = *j;
0193         }
0194 
0195         if (merged) {
0196             i = page->hyperLinkList.erase(++i, j);
0197         } else {
0198             i++;
0199         }
0200     }
0201 
0202     if (errorMsg.isEmpty() != true) {
0203         Q_EMIT error(i18n("File corruption. %1", errorMsg), -1);
0204         errorMsg.clear();
0205         currentlyDrawnPage = nullptr;
0206         return;
0207     }
0208     currentlyDrawnPage = nullptr;
0209 }
0210 
0211 void dviRenderer::getText(RenderedDocumentPagePixmap *page)
0212 {
0213     bool postscriptBackup = _postscript;
0214     // Disable postscript-specials temporarily to speed up text extraction.
0215     _postscript = false;
0216 
0217     drawPage(page);
0218 
0219     _postscript = postscriptBackup;
0220 }
0221 
0222 /*
0223 void dviRenderer::showThatSourceInformationIsPresent()
0224 {
0225   // In principle, we should use a KMessagebox here, but we want to
0226   // add a button "Explain in more detail..." which opens the
0227   // Helpcenter. Thus, we practically re-implement the KMessagebox
0228   // here. Most of the code is stolen from there.
0229 
0230   // Check if the 'Don't show again' feature was used
0231   KConfig *config = KSharedConfig::openConfig();
0232   KConfigGroup saver(config, "Notification Messages");
0233   bool showMsg = config->readEntry( "KDVI-info_on_source_specials", true);
0234 
0235   if (showMsg) {
0236     KDialogBase dialog(i18n("KDVI: Information"), KDialogBase::Yes, KDialogBase::Yes, KDialogBase::Yes,
0237                        parentWidget, "information", true, true, KStandardGuiItem::ok());
0238 
0239     QWidget *topcontents = new QWidget (&dialog);
0240     QVBoxLayout *topcontentsVBoxLayout = new QVBoxLayout(topcontents);
0241     topcontentsVBoxLayout->setContentsMargins(0, 0, 0, 0);
0242     topcontentsVBoxLayout->setSpacing(KDialog::spacingHint()*2);
0243     topcontentsVBoxLayout->setContentsMargins(KDialog::marginHint()*2, KDialog::marginHint()*2, KDialog::marginHint()*2, KDialog::marginHint()*2);
0244 
0245     QWidget *contents = new QWidget(topcontents);
0246     topcontentsVBoxLayout->addWidget(contents);
0247     QHBoxLayout * lay = new QHBoxLayout(contents);
0248     lay->setSpacing(KDialog::spacingHint()*2);
0249 
0250     lay->addStretch(1);
0251     QLabel *label1 = new QLabel( contents);
0252     label1->setPixmap(QMessageBox::standardIcon(QMessageBox::Information));
0253     lay->addWidget(label1);
0254     QLabel *label2 = new QLabel( i18n("<qt>This DVI file contains source file information. You may click into the text with the "
0255                                       "middle mouse button, and an editor will open the TeX-source file immediately.</qt>"),
0256                                  contents);
0257     label2->setMinimumSize(label2->sizeHint());
0258     lay->addWidget(label2);
0259     lay->addStretch(1);
0260     QSize extraSize = QSize(50,30);
0261     QCheckBox *checkbox = new QCheckBox(i18n("Do not show this message again"), topcontents);
0262     topcontentsVBoxLayout->addWidget(checkbox);
0263     extraSize = QSize(50,0);
0264     dialog.setHelpLinkText(i18n("Explain in more detail..."));
0265     dialog.setHelp("inverse-search", "kdvi");
0266     dialog.enableLinkedHelp(true);
0267     dialog.setMainWidget(topcontents);
0268     dialog.enableButtonSeparator(false);
0269     dialog.incInitialSize( extraSize );
0270     dialog.exec();
0271 
0272     showMsg = !checkbox->isChecked();
0273     if (!showMsg) {
0274       KConfigGroup saver(config, "Notification Messages");
0275       config->writeEntry( "KDVI-info_on_source_specials", showMsg);
0276     }
0277     config->sync();
0278   }
0279 }
0280 */
0281 
0282 void dviRenderer::embedPostScript()
0283 {
0284 #ifdef DEBUG_DVIRENDERER
0285     qCDebug(OkularDviDebug) << "dviRenderer::embedPostScript()";
0286 #endif
0287 
0288     if (!dviFile) {
0289         return;
0290     }
0291 
0292     /*  embedPS_progress = new QProgressDialog(parentWidget);
0293     embedPS_progress->setWindowTitle(i18n("Embedding PostScript Files"));
0294     embedPS_progress->setLabelText(QString());
0295     */
0296     if (!embedPS_progress) {
0297         return;
0298     }
0299     embedPS_progress->setCancelButton(nullptr);
0300     embedPS_progress->setCancelButton(nullptr);
0301     embedPS_progress->setMinimumDuration(400);
0302     embedPS_progress->setMaximum(dviFile->numberOfExternalPSFiles);
0303     embedPS_progress->setValue(0);
0304     embedPS_numOfProgressedFiles = 0;
0305 
0306     quint16 currPageSav = current_page;
0307     errorMsg.clear();
0308     for (current_page = 0; current_page < dviFile->total_pages; current_page++) {
0309         if (current_page < dviFile->total_pages) {
0310             command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)];
0311             end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page + 1)];
0312         } else {
0313             command_pointer = end_pointer = nullptr;
0314         }
0315 
0316         memset((char *)&currinf.data, 0, sizeof(currinf.data));
0317         currinf.fonttable = &(dviFile->tn_table);
0318         currinf._virtual = nullptr;
0319         prescan(&dviRenderer::prescan_embedPS);
0320     }
0321 
0322     delete embedPS_progress;
0323     embedPS_progress = nullptr;
0324 
0325     if (!errorMsg.isEmpty()) {
0326         Q_EMIT warning(i18n("Not all PostScript files could be embedded into your document. %1", errorMsg), -1);
0327         errorMsg.clear();
0328     } else {
0329         Q_EMIT notice(i18n("All external PostScript files were embedded into your document."), -1);
0330     }
0331 
0332     // Prescan phase starts here
0333 #ifdef PERFORMANCE_MEASUREMENT
0334     // qCDebug(OkularDviDebug) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms";
0335     // QTime preScanTimer;
0336     // preScanTimer.start();
0337 #endif
0338     dviFile->numberOfExternalPSFiles = 0;
0339     prebookmarks.clear();
0340     for (current_page = 0; current_page < dviFile->total_pages; current_page++) {
0341         PostScriptOutPutString = new QString();
0342 
0343         if (current_page < dviFile->total_pages) {
0344             command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)];
0345             end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page + 1)];
0346         } else {
0347             command_pointer = end_pointer = nullptr;
0348         }
0349 
0350         memset((char *)&currinf.data, 0, sizeof(currinf.data));
0351         currinf.fonttable = &(dviFile->tn_table);
0352         currinf._virtual = nullptr;
0353 
0354         prescan(&dviRenderer::prescan_parseSpecials);
0355 
0356         if (!PostScriptOutPutString->isEmpty()) {
0357             PS_interface->setPostScript(current_page, *PostScriptOutPutString);
0358         }
0359         delete PostScriptOutPutString;
0360     }
0361     PostScriptOutPutString = nullptr;
0362 
0363 #ifdef PERFORMANCE_MEASUREMENT
0364     // qCDebug(OkularDviDebug) << "Time required for prescan phase: " << preScanTimer.restart() << "ms";
0365 #endif
0366     current_page = currPageSav;
0367     _isModified = true;
0368 }
0369 
0370 bool dviRenderer::isValidFile(const QString &filename) const
0371 {
0372     QFile f(filename);
0373     if (!f.open(QIODevice::ReadOnly)) {
0374         return false;
0375     }
0376 
0377     unsigned char test[4];
0378     if (f.read((char *)test, 2) < 2 || test[0] != 247 || test[1] != 2) {
0379         return false;
0380     }
0381 
0382     int n = f.size();
0383     if (n < 134) { // Too short for a dvi file
0384         return false;
0385     }
0386     f.seek(n - 4);
0387 
0388     unsigned char trailer[4] = {0xdf, 0xdf, 0xdf, 0xdf};
0389 
0390     if (f.read((char *)test, 4) < 4 || strncmp((char *)test, (char *)trailer, 4) != 0) {
0391         return false;
0392     }
0393     // We suppose now that the dvi file is complete and OK
0394     return true;
0395 }
0396 
0397 bool dviRenderer::setFile(const QString &fname, const QUrl &base)
0398 {
0399 #ifdef DEBUG_DVIRENDERER
0400     qCDebug(OkularDviDebug) << "dviRenderer::setFile( fname='" << fname << "' )"; //, ref='" << ref << "', sourceMarker=" << sourceMarker << " )";
0401 #endif
0402 
0403     // QMutexLocker lock(&mutex);
0404 
0405     QFileInfo fi(fname);
0406     QString filename = fi.absoluteFilePath();
0407 
0408     // If fname is the empty string, then this means: "close". Delete
0409     // the dvifile and the pixmap.
0410     if (fname.isEmpty()) {
0411         // Delete DVI file
0412         delete dviFile;
0413         dviFile = nullptr;
0414         return true;
0415     }
0416 
0417     // Make sure the file actually exists.
0418     if (!fi.exists() || fi.isDir()) {
0419         Q_EMIT error(i18n("The specified file '%1' does not exist.", filename), -1);
0420         return false;
0421     }
0422 
0423     QApplication::setOverrideCursor(Qt::WaitCursor);
0424     dvifile *dviFile_new = new dvifile(filename, &font_pool);
0425 
0426     if ((dviFile == nullptr) || (dviFile->filename != filename)) {
0427         dviFile_new->sourceSpecialMarker = true;
0428     } else {
0429         dviFile_new->sourceSpecialMarker = false;
0430     }
0431 
0432     if ((dviFile_new->dvi_Data() == nullptr) || (dviFile_new->errorMsg.isEmpty() != true)) {
0433         QApplication::restoreOverrideCursor();
0434         if (dviFile_new->errorMsg.isEmpty() != true) {
0435             Q_EMIT error(i18n("File corruption. %1", dviFile_new->errorMsg), -1);
0436         }
0437         delete dviFile_new;
0438         return false;
0439     }
0440 
0441     delete dviFile;
0442     dviFile = dviFile_new;
0443     numPages = dviFile->total_pages;
0444     _isModified = false;
0445     baseURL = base;
0446 
0447     font_pool.setExtraSearchPath(fi.absolutePath());
0448     font_pool.setCMperDVIunit(dviFile->getCmPerDVIunit());
0449 
0450     // Extract PostScript from the DVI file, and store the PostScript
0451     // specials in PostScriptDirectory, and the headers in the
0452     // PostScriptHeaderString.
0453     PS_interface->clear();
0454 
0455     // If the DVI file comes from a remote URL (e.g. downloaded from a
0456     // web server), we limit the PostScript files that can be accessed
0457     // by this file to the download directory, in order to limit the
0458     // possibilities of a denial of service attack.
0459     QString includePath;
0460     if (!baseURL.isLocalFile()) {
0461         includePath = filename;
0462         includePath.truncate(includePath.lastIndexOf(QLatin1Char('/')));
0463     }
0464     PS_interface->setIncludePath(includePath);
0465 
0466     // We will also generate a list of hyperlink-anchors and source-file
0467     // anchors in the document. So declare the existing lists empty.
0468     // anchorList.clear();
0469     sourceHyperLinkAnchors.clear();
0470     // bookmarks.clear();
0471     prebookmarks.clear();
0472 
0473     if (dviFile->page_offset.isEmpty() == true) {
0474         return false;
0475     }
0476 
0477     // We should pre-scan the document now (to extract embedded,
0478     // PostScript, Hyperlinks, ets).
0479 
0480     // PRESCAN STARTS HERE
0481 #ifdef PERFORMANCE_MEASUREMENT
0482     // qCDebug(OkularDviDebug) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms";
0483     // QTime preScanTimer;
0484     // preScanTimer.start();
0485 #endif
0486     dviFile->numberOfExternalPSFiles = 0;
0487     quint16 currPageSav = current_page;
0488     prebookmarks.clear();
0489 
0490     for (current_page = 0; current_page < dviFile->total_pages; current_page++) {
0491         PostScriptOutPutString = new QString();
0492 
0493         if (current_page < dviFile->total_pages) {
0494             command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)];
0495             end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page + 1)];
0496         } else {
0497             command_pointer = end_pointer = nullptr;
0498         }
0499 
0500         memset((char *)&currinf.data, 0, sizeof(currinf.data));
0501         currinf.fonttable = &(dviFile->tn_table);
0502         currinf._virtual = nullptr;
0503         prescan(&dviRenderer::prescan_parseSpecials);
0504 
0505         if (!PostScriptOutPutString->isEmpty()) {
0506             PS_interface->setPostScript(current_page, *PostScriptOutPutString);
0507         }
0508         delete PostScriptOutPutString;
0509     }
0510     PostScriptOutPutString = nullptr;
0511 
0512 #ifdef PERFORMANCE_MEASUREMENT
0513     // qCDebug(OkularDviDebug) << "Time required for prescan phase: " << preScanTimer.restart() << "ms";
0514 #endif
0515     current_page = currPageSav;
0516     // PRESCAN ENDS HERE
0517 
0518     pageSizes.resize(0);
0519     if (dviFile->suggestedPageSize != nullptr) {
0520         // Fill the vector pageSizes with total_pages identical entries
0521         pageSizes.fill(*(dviFile->suggestedPageSize), dviFile->total_pages);
0522     }
0523     QApplication::restoreOverrideCursor();
0524     return true;
0525 }
0526 
0527 Anchor dviRenderer::parseReference(const QString &reference)
0528 {
0529     QMutexLocker locker(&mutex);
0530 
0531 #ifdef DEBUG_DVIRENDERER
0532     qCCritical(OkularDviDebug) << "dviRenderer::parseReference( " << reference << " ) called";
0533 #endif
0534 
0535     if (dviFile == nullptr) {
0536         return Anchor();
0537     }
0538 
0539     // case 1: The reference is a number, which we'll interpret as a
0540     // page number.
0541     bool ok;
0542     int page = reference.toInt(&ok);
0543     if (ok == true) {
0544         if (page < 0) {
0545             page = 0;
0546         }
0547         if (page > dviFile->total_pages) {
0548             page = dviFile->total_pages;
0549         }
0550 
0551         return Anchor(page, Length());
0552     }
0553 
0554     // case 2: The reference is of form "src:1111Filename", where "1111"
0555     // points to line number 1111 in the file "Filename". KDVI then
0556     // looks for source specials of the form "src:xxxxFilename", and
0557     // tries to find the special with the biggest xxxx
0558     if (reference.indexOf(QStringLiteral("src:"), 0, Qt::CaseInsensitive) == 0) {
0559         // Extract the file name and the numeral part from the reference string
0560         DVI_SourceFileSplitter splitter(reference, dviFile->filename);
0561         quint32 refLineNumber = splitter.line();
0562         QString refFileName = splitter.filePath();
0563 
0564         if (sourceHyperLinkAnchors.isEmpty()) {
0565             Q_EMIT warning(i18n("You have asked Okular to locate the place in the DVI file which corresponds to "
0566                                 "line %1 in the TeX-file %2. It seems, however, that the DVI file "
0567                                 "does not contain the necessary source file information. ",
0568                                 refLineNumber,
0569                                 refFileName),
0570                            -1);
0571             return Anchor();
0572         }
0573 
0574         // Go through the list of source file anchors, and find the anchor
0575         // whose line number is the biggest among those that are smaller
0576         // than the refLineNumber. That way, the position in the DVI file
0577         // which is highlighted is always a little further up than the
0578         // position in the editor, e.g. if the DVI file contains
0579         // positional information at the beginning of every paragraph,
0580         // KDVI jumps to the beginning of the paragraph that the cursor is
0581         // in, and never to the next paragraph. If source file anchors for
0582         // the refFileName can be found, but none of their line numbers is
0583         // smaller than the refLineNumber, the reason is most likely, that
0584         // the cursor in the editor stands somewhere in the preamble of
0585         // the LaTeX file. In that case, we jump to the beginning of the
0586         // document.
0587         bool anchorForRefFileFound = false; // Flag that is set if source file anchors for the refFileName could be found at all
0588 
0589         QVector<DVI_SourceFileAnchor>::iterator bestMatch = sourceHyperLinkAnchors.end();
0590         QVector<DVI_SourceFileAnchor>::iterator it;
0591         for (it = sourceHyperLinkAnchors.begin(); it != sourceHyperLinkAnchors.end(); ++it) {
0592             if (refFileName.trimmed() == it->fileName.trimmed() || refFileName.trimmed() == it->fileName.trimmed() + QStringLiteral(".tex")) {
0593                 anchorForRefFileFound = true;
0594 
0595                 if ((it->line <= refLineNumber) && ((bestMatch == sourceHyperLinkAnchors.end()) || (it->line > bestMatch->line))) {
0596                     bestMatch = it;
0597                 }
0598             }
0599         }
0600 
0601         if (bestMatch != sourceHyperLinkAnchors.end()) {
0602             return Anchor(bestMatch->page, bestMatch->distance_from_top);
0603         } else if (anchorForRefFileFound == false) {
0604             Q_EMIT warning(i18n("Okular was not able to locate the place in the DVI file which corresponds to "
0605                                 "line %1 in the TeX-file %2.",
0606                                 refLineNumber,
0607                                 refFileName),
0608                            -1);
0609         } else {
0610             return Anchor();
0611         }
0612         return Anchor();
0613     }
0614     return Anchor();
0615 }
0616 
0617 void dviRenderer::setResolution(double resolution_in_DPI)
0618 {
0619     // Ignore minute changes. The difference to the current value would
0620     // hardly be visible anyway. That saves a lot of re-painting,
0621     // e.g. when the user resizes the window, and a flickery mouse
0622     // changes the window size by 1 pixel all the time.
0623     if (fabs(resolutionInDPI - resolution_in_DPI) < 1) {
0624         return;
0625     }
0626 
0627     resolutionInDPI = resolution_in_DPI;
0628 
0629     // Pass the information on to the font pool.
0630     font_pool.setDisplayResolution(resolutionInDPI);
0631     shrinkfactor = 1200 / resolutionInDPI;
0632     return;
0633 }
0634 
0635 void dviRenderer::handleSRCLink(const QString &linkText, const QPoint point, DocumentWidget *widget)
0636 {
0637     Q_UNUSED(linkText);
0638     Q_UNUSED(point);
0639     Q_UNUSED(widget);
0640 }
0641 
0642 QString dviRenderer::PDFencodingToQString(const QString &_pdfstring)
0643 {
0644     // This method locates special PDF characters in a string and
0645     // replaces them by UTF8. See Section 3.2.3 of the PDF reference
0646     // guide for information.
0647     QString pdfstring = _pdfstring;
0648     pdfstring = pdfstring.replace(QLatin1String("\\n"), QLatin1String("\n"));
0649     pdfstring = pdfstring.replace(QLatin1String("\\r"), QLatin1String("\n"));
0650     pdfstring = pdfstring.replace(QLatin1String("\\t"), QLatin1String("\t"));
0651     pdfstring = pdfstring.replace(QLatin1String("\\f"), QLatin1String("\f"));
0652     pdfstring = pdfstring.replace(QLatin1String("\\("), QLatin1String("("));
0653     pdfstring = pdfstring.replace(QLatin1String("\\)"), QLatin1String(")"));
0654     pdfstring = pdfstring.replace(QLatin1String("\\\\"), QLatin1String("\\"));
0655 
0656     // Now replace octal character codes with the characters they encode
0657     static const QRegularExpression xyzRegex(QStringLiteral("(\\\\)(\\d\\d\\d)")); // matches "\xyz" where x,y,z are numbers
0658     QRegularExpressionMatch match;
0659     while ((match = xyzRegex.match(pdfstring)).hasMatch()) {
0660         pdfstring = pdfstring.replace(match.capturedStart(0), 4, QChar(match.captured(2).toInt(nullptr, 8)));
0661     }
0662     static const QRegularExpression xyRegex(QStringLiteral("(\\\\)(\\d\\d)")); // matches "\xy" where x,y are numbers
0663     while ((match = xyRegex.match(pdfstring)).hasMatch()) {
0664         pdfstring = pdfstring.replace(match.capturedStart(0), 3, QChar(match.captured(2).toInt(nullptr, 8)));
0665     }
0666     static const QRegularExpression xRegex(QStringLiteral("(\\\\)(\\d)")); // matches "\x" where x is a number
0667     while ((match = xRegex.match(pdfstring)).hasMatch()) {
0668         pdfstring = pdfstring.replace(match.capturedStart(0), 2, QChar(match.captured(2).toInt(nullptr, 8)));
0669     }
0670 
0671     return pdfstring;
0672 }
0673 
0674 void dviRenderer::exportPDF()
0675 {
0676     /*
0677       QExplicitlySharedDataPointer<DVIExport> exporter(new DVIExportToPDF(*this, parentWidget));
0678       if (exporter->started())
0679         all_exports_[exporter.data()] = exporter;
0680     */
0681 }
0682 
0683 void dviRenderer::exportPS(const QString &fname, const QStringList &options, QPrinter *printer, QPageLayout::Orientation orientation)
0684 {
0685     QExplicitlySharedDataPointer<DVIExport> exporter(new DVIExportToPS(*this, fname, options, printer, font_pool.getUseFontHints(), orientation));
0686     if (exporter->started()) {
0687         all_exports_[exporter.data()] = exporter;
0688     }
0689 }
0690 
0691 /*
0692 void dviRenderer::editor_finished(const DVISourceEditor*)
0693 {
0694   editor_.attach(0);
0695 }
0696 */
0697 
0698 void dviRenderer::export_finished(const DVIExport *key)
0699 {
0700     typedef QMap<const DVIExport *, QExplicitlySharedDataPointer<DVIExport>> ExportMap;
0701     ExportMap::iterator it = all_exports_.find(key);
0702     if (it != all_exports_.end()) {
0703         all_exports_.remove(key);
0704     }
0705 }
0706 
0707 void dviRenderer::setEventLoop(QEventLoop *el)
0708 {
0709     if (el == nullptr) {
0710         delete m_eventLoop;
0711         m_eventLoop = nullptr;
0712     } else {
0713         m_eventLoop = el;
0714     }
0715 }