/* SPDX-FileCopyrightText: 2006-2009 Luigi Toscano SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include "TeXFont.h" #include "debug_dvi.h" #include "dviFile.h" #include "dviPageInfo.h" #include "dviRenderer.h" #include "dviexport.h" #include "generator_dvi.h" #include "pageSize.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef DVI_OPEN_BUSYLOOP #include #endif OKULAR_EXPORT_PLUGIN(DviGenerator, "libokularGenerator_dvi.json") DviGenerator::DviGenerator(QObject *parent, const QVariantList &args) : Okular::Generator(parent, args) , m_fontExtracted(false) , m_docSynopsis(nullptr) , m_dviRenderer(nullptr) { setFeature(Threaded); setFeature(TextExtraction); setFeature(FontInfo); setFeature(PrintPostscript); if (Okular::FilePrinter::ps2pdfAvailable()) setFeature(PrintToFile); } bool DviGenerator::loadDocument(const QString &fileName, QVector &pagesVector) { // qCDebug(OkularDviDebug) << "file:" << fileName; QUrl base(QUrl::fromLocalFile(fileName)); (void)userMutex(); m_dviRenderer = new dviRenderer(documentMetaData(TextHintingMetaData, QVariant()).toBool()); connect(m_dviRenderer, &dviRenderer::error, this, &DviGenerator::error); connect(m_dviRenderer, &dviRenderer::warning, this, &DviGenerator::warning); connect(m_dviRenderer, &dviRenderer::notice, this, &DviGenerator::notice); #ifdef DVI_OPEN_BUSYLOOP static const ushort s_waitTime = 800; // milliseconds static const int s_maxIterations = 10; int iter = 0; for (; !m_dviRenderer->isValidFile(fileName) && iter < s_maxIterations; ++iter) { qCDebug(OkularDviDebug).nospace() << "file not valid after iteration #" << iter << "/" << s_maxIterations << ", waiting for " << s_waitTime; QThread::msleep(s_waitTime); } if (iter >= s_maxIterations && !m_dviRenderer->isValidFile(fileName)) { qCDebug(OkularDviDebug) << "file still not valid after" << s_maxIterations; delete m_dviRenderer; m_dviRenderer = 0; return false; } #else if (!m_dviRenderer->isValidFile(fileName)) { delete m_dviRenderer; m_dviRenderer = nullptr; return false; } #endif if (!m_dviRenderer->setFile(fileName, base)) { delete m_dviRenderer; m_dviRenderer = nullptr; return false; } qCDebug(OkularDviDebug) << "# of pages:" << m_dviRenderer->dviFile->total_pages; m_resolution = dpi().height(); loadPages(pagesVector); return true; } bool DviGenerator::doCloseDocument() { delete m_docSynopsis; m_docSynopsis = nullptr; delete m_dviRenderer; m_dviRenderer = nullptr; m_linkGenerated.clear(); m_fontExtracted = false; return true; } void DviGenerator::fillViewportFromAnchor(Okular::DocumentViewport &vp, const Anchor anch, const Okular::Page *page) const { fillViewportFromAnchor(vp, anch, page->width(), page->height()); } void DviGenerator::fillViewportFromAnchor(Okular::DocumentViewport &vp, const Anchor anch, int pW, int pH) const { vp.pageNumber = anch.page - 1; SimplePageSize ps = m_dviRenderer->sizeOfPage(vp.pageNumber); double resolution = 0; if (ps.isValid()) resolution = (double)(pW) / ps.width().getLength_in_inch(); else resolution = m_resolution; double py = (double)anch.distance_from_top.getLength_in_inch() * resolution + 0.5; vp.rePos.normalizedX = 0.5; vp.rePos.normalizedY = py / (double)pH; vp.rePos.enabled = true; vp.rePos.pos = Okular::DocumentViewport::Center; } QLinkedList DviGenerator::generateDviLinks(const dviPageInfo *pageInfo) { QLinkedList dviLinks; int pageWidth = pageInfo->width, pageHeight = pageInfo->height; for (const Hyperlink &dviLink : qAsConst(pageInfo->hyperLinkList)) { QRect boxArea = dviLink.box; double nl = (double)boxArea.left() / pageWidth, nt = (double)boxArea.top() / pageHeight, nr = (double)boxArea.right() / pageWidth, nb = (double)boxArea.bottom() / pageHeight; QString linkText = dviLink.linkText; if (linkText.startsWith(QLatin1String("#"))) linkText = linkText.mid(1); Anchor anch = m_dviRenderer->findAnchor(linkText); Okular::Action *okuLink = nullptr; /* distinguish between local (-> anchor) and remote links */ if (anch.isValid()) { /* internal link */ Okular::DocumentViewport vp; fillViewportFromAnchor(vp, anch, pageWidth, pageHeight); okuLink = new Okular::GotoAction(QLatin1String(""), vp); } else { okuLink = new Okular::BrowseAction(QUrl::fromUserInput(dviLink.linkText)); } if (okuLink) { Okular::ObjectRect *orlink = new Okular::ObjectRect(nl, nt, nr, nb, false, Okular::ObjectRect::Action, okuLink); dviLinks.push_front(orlink); } } return dviLinks; } QImage DviGenerator::image(Okular::PixmapRequest *request) { dviPageInfo *pageInfo = new dviPageInfo(); pageSize ps; QImage ret; pageInfo->width = request->width(); pageInfo->height = request->height(); pageInfo->pageNumber = request->pageNumber() + 1; // pageInfo->resolution = m_resolution; QMutexLocker lock(userMutex()); if (m_dviRenderer) { SimplePageSize s = m_dviRenderer->sizeOfPage(pageInfo->pageNumber); /* if ( s.width() != pageInfo->width) */ // if (!useDocumentSpecifiedSize) // s = userPreferredSize; if (s.isValid()) { pageInfo->resolution = (double)(pageInfo->width) / s.width().getLength_in_inch(); } else { pageInfo->resolution = (double)(pageInfo->width) / ps.width().getLength_in_inch(); } m_dviRenderer->drawPage(pageInfo); if (!pageInfo->img.isNull()) { qCDebug(OkularDviDebug) << "Image OK"; ret = pageInfo->img; if (!m_linkGenerated[request->pageNumber()]) { request->page()->setObjectRects(generateDviLinks(pageInfo)); m_linkGenerated[request->pageNumber()] = true; } } } lock.unlock(); delete pageInfo; return ret; } Okular::TextPage *DviGenerator::textPage(Okular::TextRequest *request) { const Okular::Page *page = request->page(); qCDebug(OkularDviDebug); dviPageInfo *pageInfo = new dviPageInfo(); pageSize ps; pageInfo->width = page->width(); pageInfo->height = page->height(); pageInfo->pageNumber = page->number() + 1; pageInfo->resolution = m_resolution; QMutexLocker lock(userMutex()); // get page text from m_dviRenderer Okular::TextPage *ktp = nullptr; if (m_dviRenderer) { SimplePageSize s = m_dviRenderer->sizeOfPage(pageInfo->pageNumber); pageInfo->resolution = (double)(pageInfo->width) / ps.width().getLength_in_inch(); m_dviRenderer->getText(pageInfo); lock.unlock(); ktp = extractTextFromPage(pageInfo); } delete pageInfo; return ktp; } Okular::TextPage *DviGenerator::extractTextFromPage(dviPageInfo *pageInfo) { QList textOfThePage; QVector::ConstIterator it = pageInfo->textBoxList.constBegin(); QVector::ConstIterator itEnd = pageInfo->textBoxList.constEnd(); int pageWidth = pageInfo->width, pageHeight = pageInfo->height; for (; it != itEnd; ++it) { TextBox curTB = *it; textOfThePage.push_back(new Okular::TextEntity(curTB.text, new Okular::NormalizedRect(curTB.box, pageWidth, pageHeight))); } Okular::TextPage *ktp = new Okular::TextPage(textOfThePage); return ktp; } Okular::DocumentInfo DviGenerator::generateDocumentInfo(const QSet &keys) const { Okular::DocumentInfo docInfo; if (keys.contains(Okular::DocumentInfo::MimeType)) docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("application/x-dvi")); QMutexLocker lock(userMutex()); if (m_dviRenderer && m_dviRenderer->dviFile) { dvifile *dvif = m_dviRenderer->dviFile; // read properties from dvif // docInfo.set( "filename", dvif->filename, i18n("Filename") ); if (keys.contains(Okular::DocumentInfo::CustomKeys)) docInfo.set(QStringLiteral("generatorDate"), dvif->generatorString, i18n("Generator/Date")); if (keys.contains(Okular::DocumentInfo::Pages)) docInfo.set(Okular::DocumentInfo::Pages, QString::number(dvif->total_pages)); } return docInfo; } const Okular::DocumentSynopsis *DviGenerator::generateDocumentSynopsis() { if (m_docSynopsis) return m_docSynopsis; m_docSynopsis = new Okular::DocumentSynopsis(); userMutex()->lock(); QVector prebookmarks = m_dviRenderer->getPrebookmarks(); userMutex()->unlock(); if (prebookmarks.isEmpty()) return m_docSynopsis; QStack stack; QVector::ConstIterator it = prebookmarks.constBegin(); QVector::ConstIterator itEnd = prebookmarks.constEnd(); for (; it != itEnd; ++it) { QDomElement domel = m_docSynopsis->createElement((*it).title); Anchor a = m_dviRenderer->findAnchor((*it).anchorName); if (a.isValid()) { Okular::DocumentViewport vp; const Okular::Page *p = document()->page(a.page - 1); fillViewportFromAnchor(vp, a, (int)p->width(), (int)p->height()); domel.setAttribute(QStringLiteral("Viewport"), vp.toString()); } if (stack.isEmpty()) m_docSynopsis->appendChild(domel); else { stack.top().appendChild(domel); stack.pop(); } for (int i = 0; i < (*it).noOfChildren; ++i) stack.push(domel); } return m_docSynopsis; } Okular::FontInfo::List DviGenerator::fontsForPage(int page) { Q_UNUSED(page); Okular::FontInfo::List list; // the list of the fonts is extracted once if (m_fontExtracted) return list; if (m_dviRenderer && m_dviRenderer->dviFile && m_dviRenderer->dviFile->font_pool) { const QList fonts = m_dviRenderer->dviFile->font_pool->fontList; for (const TeXFontDefinition *font : fonts) { Okular::FontInfo of; QString name; int zoom = (int)(font->enlargement * 100 + 0.5); #ifdef HAVE_FREETYPE if (font->getFullFontName().isEmpty()) { name = QStringLiteral("%1, %2%").arg(font->fontname).arg(zoom); } else { name = QStringLiteral("%1 (%2), %3%").arg(font->fontname, font->getFullFontName(), QString::number(zoom)); } #else name = QString("%1, %2%").arg(font->fontname).arg(zoom); #endif of.setName(name); QString fontFileName; if (!(font->flags & TeXFontDefinition::FONT_VIRTUAL)) { if (font->font != nullptr) fontFileName = font->font->errorMessage; else fontFileName = i18n("Font file not found"); if (fontFileName.isEmpty()) fontFileName = font->filename; } of.setFile(fontFileName); Okular::FontInfo::FontType ft; switch (font->getFontType()) { case TeXFontDefinition::TEX_PK: ft = Okular::FontInfo::TeXPK; break; case TeXFontDefinition::TEX_VIRTUAL: ft = Okular::FontInfo::TeXVirtual; break; case TeXFontDefinition::TEX_FONTMETRIC: ft = Okular::FontInfo::TeXFontMetric; break; case TeXFontDefinition::FREETYPE: ft = Okular::FontInfo::TeXFreeTypeHandled; break; } of.setType(ft); // DVI has not the concept of "font embedding" of.setEmbedType(Okular::FontInfo::NotEmbedded); of.setCanBeExtracted(false); list.append(of); } m_fontExtracted = true; } return list; } void DviGenerator::loadPages(QVector &pagesVector) { QSize pageRequiredSize; int numofpages = m_dviRenderer->dviFile->total_pages; pagesVector.resize(numofpages); m_linkGenerated.fill(false, numofpages); // qCDebug(OkularDviDebug) << "resolution:" << m_resolution << ", dviFile->preferred?"; /* get the suggested size */ if (m_dviRenderer->dviFile->suggestedPageSize) { pageRequiredSize = m_dviRenderer->dviFile->suggestedPageSize->sizeInPixel(m_resolution); } else { pageSize ps; pageRequiredSize = ps.sizeInPixel(m_resolution); } for (int i = 0; i < numofpages; ++i) { // qCDebug(OkularDviDebug) << "getting status of page" << i << ":"; if (pagesVector[i]) { delete pagesVector[i]; } Okular::Page *page = new Okular::Page(i, pageRequiredSize.width(), pageRequiredSize.height(), Okular::Rotation0); pagesVector[i] = page; } qCDebug(OkularDviDebug) << "pagesVector successfully inizialized!"; // filling the pages with the source references rects const QVector &sourceAnchors = m_dviRenderer->sourceAnchors(); QVector> refRects(numofpages); for (const DVI_SourceFileAnchor &sfa : sourceAnchors) { if (sfa.page < 1 || (int)sfa.page > numofpages) continue; Okular::NormalizedPoint p(-1.0, (double)sfa.distance_from_top.getLength_in_pixel(dpi().height()) / (double)pageRequiredSize.height()); Okular::SourceReference *sourceRef = new Okular::SourceReference(sfa.fileName, sfa.line); refRects[sfa.page - 1].append(new Okular::SourceRefObjectRect(p, sourceRef)); } for (int i = 0; i < refRects.size(); ++i) if (!refRects.at(i).isEmpty()) pagesVector[i]->setSourceReferences(refRects.at(i)); } bool DviGenerator::print(QPrinter &printer) { // Create tempfile to write to QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); if (!tf.open()) return false; const QList pageList = Okular::FilePrinter::pageList(printer, m_dviRenderer->totalPages(), document()->currentPage() + 1, document()->bookmarkedPageList()); QString pages; QStringList printOptions; // List of pages to print. for (const int p : pageList) { pages += QStringLiteral(",%1").arg(p); } if (!pages.isEmpty()) printOptions << QStringLiteral("-pp") << pages.mid(1); QEventLoop el; m_dviRenderer->setEventLoop(&el); m_dviRenderer->exportPS(tf.fileName(), printOptions, &printer, document()->orientation()); tf.close(); // Error messages are handled by the generator - ugly, but it works. return true; } QVariant DviGenerator::metaData(const QString &key, const QVariant &option) const { if (key == QLatin1String("NamedViewport") && !option.toString().isEmpty()) { const Anchor anchor = m_dviRenderer->parseReference(option.toString()); if (anchor.isValid()) { const Okular::Page *page = document()->page(anchor.page - 1); Q_ASSERT_X(page, "DviGenerator::metaData()", "NULL page as result of valid Anchor"); Okular::DocumentViewport viewport; fillViewportFromAnchor(viewport, anchor, page); if (viewport.isValid()) { return viewport.toString(); } } } return QVariant(); } Q_LOGGING_CATEGORY(OkularDviDebug, "org.kde.okular.generators.dvi.core", QtWarningMsg) Q_LOGGING_CATEGORY(OkularDviShellDebug, "org.kde.okular.generators.dvi.shell", QtWarningMsg) #include "generator_dvi.moc"