/* poppler-document.cc: qt interface to poppler * Copyright (C) 2005, Net Integration Technologies, Inc. * Copyright (C) 2005, 2008, Brad Hards * Copyright (C) 2005-2010, 2012, 2013, 2015, 2017-2022, Albert Astals Cid * Copyright (C) 2006-2010, Pino Toscano * Copyright (C) 2010, 2011 Hib Eris * Copyright (C) 2012 Koji Otani * Copyright (C) 2012, 2013 Thomas Freitag * Copyright (C) 2012 Fabio D'Urso * Copyright (C) 2014, 2018, 2020 Adam Reichold * Copyright (C) 2015 William Bader * Copyright (C) 2016 Jakub Alba * Copyright (C) 2017, 2021 Adrian Johnson * Copyright (C) 2017 Suzuki Toshiya * Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by the LiMux project of the city of Munich * Copyright (C) 2019-2021 Oliver Sander * Copyright (C) 2019 Alexander Volkov * Copyright (C) 2020 Philipp Knechtges * Copyright (C) 2020 Katarina Behrens * Copyright (C) 2020 Thorsten Behrens * Copyright (C) 2021 Mahmoud Khalil * Copyright (C) 2021 Hubert Figuiere * Copyright (C) 2024 Pratham Gandhi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "poppler-qt6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "poppler-form.h" #include "poppler-private.h" #include "poppler-link-private.h" #include "poppler-page-private.h" #include "poppler-outline-private.h" #if defined(USE_CMS) # include #endif namespace Poppler { std::unique_ptr Document::load(const QString &filePath, const QByteArray &ownerPassword, const QByteArray &userPassword) { DocumentData *doc = new DocumentData(filePath, GooString(ownerPassword.data()), GooString(userPassword.data())); return DocumentData::checkDocument(doc); } std::unique_ptr Document::load(QIODevice *device, const QByteArray &ownerPassword, const QByteArray &userPassword) { DocumentData *doc = new DocumentData(device, GooString(ownerPassword.data()), GooString(userPassword.data())); return DocumentData::checkDocument(doc); } std::unique_ptr Document::loadFromData(const QByteArray &fileContents, const QByteArray &ownerPassword, const QByteArray &userPassword) { // create stream DocumentData *doc = new DocumentData(fileContents, GooString(ownerPassword.data()), GooString(userPassword.data())); return DocumentData::checkDocument(doc); } std::unique_ptr DocumentData::checkDocument(DocumentData *doc) { if (doc->doc->isOk() || doc->doc->getErrorCode() == errEncrypted) { auto pdoc = std::unique_ptr(new Document(doc)); if (doc->doc->getErrorCode() == errEncrypted) { pdoc->m_doc->locked = true; } else { pdoc->m_doc->locked = false; pdoc->m_doc->fillMembers(); } return pdoc; } else { delete doc; } return nullptr; } Document::Document(DocumentData *dataA) { m_doc = dataA; } Document::~Document() { delete m_doc; } std::unique_ptr Document::page(int index) const { // Cannot call std::make_unique, because the constructor of Page is private auto page = std::unique_ptr(new Page(m_doc, index)); if (page->m_page->page == nullptr) { page.reset(); } return page; } bool Document::isLocked() const { return m_doc->locked; } bool Document::unlock(const QByteArray &ownerPassword, const QByteArray &userPassword) { if (m_doc->locked) { /* racier then it needs to be */ DocumentData *doc2; if (!m_doc->fileContents.isEmpty()) { doc2 = new DocumentData(m_doc->fileContents, GooString(ownerPassword.data()), GooString(userPassword.data())); } else if (m_doc->m_device) { doc2 = new DocumentData(m_doc->m_device, GooString(ownerPassword.data()), GooString(userPassword.data())); } else { doc2 = new DocumentData(m_doc->m_filePath, GooString(ownerPassword.data()), GooString(userPassword.data())); } if (!doc2->doc->isOk()) { delete doc2; } else { delete m_doc; m_doc = doc2; m_doc->locked = false; m_doc->fillMembers(); } } return m_doc->locked; } Document::PageMode Document::pageMode() const { switch (m_doc->doc->getCatalog()->getPageMode()) { case Catalog::pageModeNone: return UseNone; case Catalog::pageModeOutlines: return UseOutlines; case Catalog::pageModeThumbs: return UseThumbs; case Catalog::pageModeFullScreen: return FullScreen; case Catalog::pageModeOC: return UseOC; case Catalog::pageModeAttach: return UseAttach; default: return UseNone; } } Document::PageLayout Document::pageLayout() const { switch (m_doc->doc->getCatalog()->getPageLayout()) { case Catalog::pageLayoutNone: return NoLayout; case Catalog::pageLayoutSinglePage: return SinglePage; case Catalog::pageLayoutOneColumn: return OneColumn; case Catalog::pageLayoutTwoColumnLeft: return TwoColumnLeft; case Catalog::pageLayoutTwoColumnRight: return TwoColumnRight; case Catalog::pageLayoutTwoPageLeft: return TwoPageLeft; case Catalog::pageLayoutTwoPageRight: return TwoPageRight; default: return NoLayout; } } Qt::LayoutDirection Document::textDirection() const { if (!m_doc->doc->getCatalog()->getViewerPreferences()) { return Qt::LayoutDirectionAuto; } switch (m_doc->doc->getCatalog()->getViewerPreferences()->getDirection()) { case ViewerPreferences::directionL2R: return Qt::LeftToRight; case ViewerPreferences::directionR2L: return Qt::RightToLeft; default: return Qt::LayoutDirectionAuto; } } int Document::numPages() const { return m_doc->doc->getNumPages(); } QList Document::fonts() const { QList ourList; FontIterator it(0, m_doc); while (it.hasNext()) { ourList += it.next(); } return ourList; } QList Document::embeddedFiles() const { return m_doc->m_embeddedFiles; } std::unique_ptr Document::newFontIterator(int startPage) const { // Cannot use std::make_unique, because the FontIterator constructor is private return std::unique_ptr(new FontIterator(startPage, m_doc)); } QByteArray Document::fontData(const FontInfo &fi) const { QByteArray result; if (fi.isEmbedded()) { XRef *xref = m_doc->doc->getXRef()->copy(); Object refObj(fi.m_data->embRef); Object strObj = refObj.fetch(xref); if (strObj.isStream()) { int c; strObj.streamReset(); while ((c = strObj.streamGetChar()) != EOF) { result.append((char)c); } strObj.streamClose(); } delete xref; } return result; } QString Document::info(const QString &type) const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoStringEntry(type.toLatin1().constData())); return UnicodeParsedString(goo.get()); } bool Document::setInfo(const QString &key, const QString &val) { if (m_doc->locked) { return false; } GooString *goo = QStringToUnicodeGooString(val); m_doc->doc->setDocInfoStringEntry(key.toLatin1().constData(), goo); return true; } QString Document::title() const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoTitle()); return UnicodeParsedString(goo.get()); } bool Document::setTitle(const QString &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoTitle(QStringToUnicodeGooString(val)); return true; } QString Document::author() const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoAuthor()); return UnicodeParsedString(goo.get()); } bool Document::setAuthor(const QString &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoAuthor(QStringToUnicodeGooString(val)); return true; } QString Document::subject() const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoSubject()); return UnicodeParsedString(goo.get()); } bool Document::setSubject(const QString &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoSubject(QStringToUnicodeGooString(val)); return true; } QString Document::keywords() const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoKeywords()); return UnicodeParsedString(goo.get()); } bool Document::setKeywords(const QString &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoKeywords(QStringToUnicodeGooString(val)); return true; } QString Document::creator() const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoCreator()); return UnicodeParsedString(goo.get()); } bool Document::setCreator(const QString &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoCreator(QStringToUnicodeGooString(val)); return true; } QString Document::producer() const { if (m_doc->locked) { return QString(); } std::unique_ptr goo(m_doc->doc->getDocInfoProducer()); return UnicodeParsedString(goo.get()); } bool Document::setProducer(const QString &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoProducer(QStringToUnicodeGooString(val)); return true; } bool Document::removeInfo() { if (m_doc->locked) { return false; } m_doc->doc->removeDocInfo(); return true; } QStringList Document::infoKeys() const { QStringList keys; if (m_doc->locked) { return QStringList(); } QScopedPointer xref(m_doc->doc->getXRef()->copy()); if (!xref) { return QStringList(); } Object info = xref->getDocInfo(); if (!info.isDict()) { return QStringList(); } Dict *infoDict = info.getDict(); // somehow iterate over keys in infoDict keys.reserve(infoDict->getLength()); for (int i = 0; i < infoDict->getLength(); ++i) { keys.append(QString::fromLatin1(infoDict->getKey(i))); } return keys; } QDateTime Document::date(const QString &type) const { if (m_doc->locked) { return QDateTime(); } std::unique_ptr goo(m_doc->doc->getDocInfoStringEntry(type.toLatin1().constData())); QString str = UnicodeParsedString(goo.get()); return Poppler::convertDate(str.toLatin1().constData()); } bool Document::setDate(const QString &key, const QDateTime &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoStringEntry(key.toLatin1().constData(), QDateTimeToUnicodeGooString(val)); return true; } QDateTime Document::creationDate() const { if (m_doc->locked) { return QDateTime(); } std::unique_ptr goo(m_doc->doc->getDocInfoCreatDate()); QString str = UnicodeParsedString(goo.get()); return Poppler::convertDate(str.toLatin1().constData()); } bool Document::setCreationDate(const QDateTime &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoCreatDate(QDateTimeToUnicodeGooString(val)); return true; } QDateTime Document::modificationDate() const { if (m_doc->locked) { return QDateTime(); } std::unique_ptr goo(m_doc->doc->getDocInfoModDate()); QString str = UnicodeParsedString(goo.get()); return Poppler::convertDate(str.toLatin1().constData()); } bool Document::setModificationDate(const QDateTime &val) { if (m_doc->locked) { return false; } m_doc->doc->setDocInfoModDate(QDateTimeToUnicodeGooString(val)); return true; } bool Document::isEncrypted() const { return m_doc->doc->isEncrypted(); } bool Document::isLinearized() const { return m_doc->doc->isLinearized(); } bool Document::okToPrint() const { return m_doc->doc->okToPrint(); } bool Document::okToPrintHighRes() const { return m_doc->doc->okToPrintHighRes(); } bool Document::okToChange() const { return m_doc->doc->okToChange(); } bool Document::okToCopy() const { return m_doc->doc->okToCopy(); } bool Document::okToAddNotes() const { return m_doc->doc->okToAddNotes(); } bool Document::okToFillForm() const { return m_doc->doc->okToFillForm(); } bool Document::okToCreateFormFields() const { return (okToFillForm() && okToChange()); } bool Document::okToExtractForAccessibility() const { return m_doc->doc->okToAccessibility(); } bool Document::okToAssemble() const { return m_doc->doc->okToAssemble(); } Document::PdfVersion Document::getPdfVersion() const { return PdfVersion { m_doc->doc->getPDFMajorVersion(), m_doc->doc->getPDFMinorVersion() }; } std::unique_ptr Document::page(const QString &label) const { GooString label_g(label.toLatin1().data()); int index; if (!m_doc->doc->getCatalog()->labelToIndex(&label_g, &index)) { std::unique_ptr label_ug(QStringToUnicodeGooString(label)); if (!m_doc->doc->getCatalog()->labelToIndex(label_ug.get(), &index)) { return nullptr; } } return page(index); } bool Document::hasEmbeddedFiles() const { return (!(0 == m_doc->doc->getCatalog()->numEmbeddedFiles())); } QVector Document::outline() const { QVector result; if (::Outline *outline = m_doc->doc->getOutline()) { if (const std::vector<::OutlineItem *> *items = outline->getItems()) { for (void *item : *items) { result.push_back(OutlineItem { new OutlineItemData { static_cast<::OutlineItem *>(item), m_doc } }); } } } return result; } std::unique_ptr Document::linkDestination(const QString &name) { GooString *namedDest = QStringToGooString(name); LinkDestinationData ldd(nullptr, namedDest, m_doc, false); auto ld = std::make_unique(ldd); delete namedDest; return ld; } void Document::setPaperColor(const QColor &color) { m_doc->setPaperColor(color); } void Document::setColorDisplayProfile(void *outputProfileA) { #if defined(USE_CMS) if (m_doc->m_sRGBProfile && m_doc->m_sRGBProfile.get() == outputProfileA) { // Catch the special case that the user passes the sRGB profile m_doc->m_displayProfile = m_doc->m_sRGBProfile; return; } if (m_doc->m_displayProfile && m_doc->m_displayProfile.get() == outputProfileA) { // Catch the special case that the user passes the display profile return; } m_doc->m_displayProfile = make_GfxLCMSProfilePtr(outputProfileA); #else Q_UNUSED(outputProfileA); #endif } void Document::setColorDisplayProfileName(const QString &name) { #if defined(USE_CMS) void *rawprofile = cmsOpenProfileFromFile(name.toLocal8Bit().constData(), "r"); m_doc->m_displayProfile = make_GfxLCMSProfilePtr(rawprofile); #else Q_UNUSED(name); #endif } void *Document::colorRgbProfile() const { #if defined(USE_CMS) if (!m_doc->m_sRGBProfile) { m_doc->m_sRGBProfile = make_GfxLCMSProfilePtr(cmsCreate_sRGBProfile()); } return m_doc->m_sRGBProfile.get(); #else return nullptr; #endif } void *Document::colorDisplayProfile() const { #if defined(USE_CMS) return m_doc->m_displayProfile.get(); #else return nullptr; #endif } QColor Document::paperColor() const { return m_doc->paperColor; } void Document::setRenderBackend(Document::RenderBackend backend) { // no need to delete the outputdev as for the moment we always create a splash one // as the QPainter one does not allow "precaching" due to its signature // delete m_doc->m_outputDev; // m_doc->m_outputDev = NULL; m_doc->m_backend = backend; } Document::RenderBackend Document::renderBackend() const { return m_doc->m_backend; } QSet Document::availableRenderBackends() { QSet ret; ret << Document::SplashBackend; ret << Document::QPainterBackend; return ret; } void Document::setRenderHint(Document::RenderHint hint, bool on) { const bool touchesOverprinting = hint & Document::OverprintPreview; int hintForOperation = hint; if (touchesOverprinting && !isOverprintPreviewAvailable()) { hintForOperation = hintForOperation & ~(int)Document::OverprintPreview; } if (on) { m_doc->m_hints |= hintForOperation; } else { m_doc->m_hints &= ~hintForOperation; } } Document::RenderHints Document::renderHints() const { return Document::RenderHints(m_doc->m_hints); } std::unique_ptr Document::psConverter() const { // Cannot use std::make_unique, because the PSConverter constructor is private return std::unique_ptr(new PSConverter(m_doc)); } std::unique_ptr Document::pdfConverter() const { // Cannot use std::make_unique, because the PDFConverter constructor is private return std::unique_ptr(new PDFConverter(m_doc)); } QString Document::metadata() const { QString result; Catalog *catalog = m_doc->doc->getCatalog(); if (catalog && catalog->isOk()) { std::unique_ptr s = catalog->readMetadata(); if (s) { result = UnicodeParsedString(s.get()); } } return result; } bool Document::hasOptionalContent() const { return (m_doc->doc->getOptContentConfig() && m_doc->doc->getOptContentConfig()->hasOCGs()); } OptContentModel *Document::optionalContentModel() { if (m_doc->m_optContentModel.isNull()) { m_doc->m_optContentModel = new OptContentModel(m_doc->doc->getOptContentConfig(), nullptr); } return (OptContentModel *)m_doc->m_optContentModel; } void Document::applyResetFormsLink(const LinkResetForm &link) { const LinkResetFormPrivate *lrfp = link.d_func(); Catalog *catalog = m_doc->doc->getCatalog(); if (catalog && catalog->isOk()) { Form *form = catalog->getForm(); if (form) { std::vector stdStringFields; const QStringList fields = lrfp->m_fields; stdStringFields.reserve(fields.size()); for (const auto &field : fields) { stdStringFields.emplace_back(field.toStdString()); } form->reset(stdStringFields, lrfp->m_exclude); } } } QStringList Document::scripts() const { Catalog *catalog = m_doc->doc->getCatalog(); const int numScripts = catalog->numJS(); QStringList scripts; for (int i = 0; i < numScripts; ++i) { GooString *s = catalog->getJS(i); if (s) { scripts.append(UnicodeParsedString(s)); delete s; } } return scripts; } std::unique_ptr Document::additionalAction(DocumentAdditionalActionsType type) const { Catalog::DocumentAdditionalActionsType actionType; switch (type) { case CloseDocument: actionType = Catalog::actionCloseDocument; break; case SaveDocumentStart: actionType = Catalog::actionSaveDocumentStart; break; case SaveDocumentFinish: actionType = Catalog::actionSaveDocumentFinish; break; case PrintDocumentStart: actionType = Catalog::actionPrintDocumentStart; break; case PrintDocumentFinish: actionType = Catalog::actionPrintDocumentFinish; break; default: return {}; } if (std::unique_ptr<::LinkAction> act = m_doc->doc->getCatalog()->getAdditionalAction(actionType)) { return PageData::convertLinkActionToLink(act.get(), m_doc, QRectF()); } return {}; } bool Document::getPdfId(QByteArray *permanentId, QByteArray *updateId) const { GooString gooPermanentId; GooString gooUpdateId; if (!m_doc->doc->getID(permanentId ? &gooPermanentId : nullptr, updateId ? &gooUpdateId : nullptr)) { return false; } if (permanentId) { *permanentId = gooPermanentId.c_str(); } if (updateId) { *updateId = gooUpdateId.c_str(); } return true; } Document::FormType Document::formType() const { switch (m_doc->doc->getCatalog()->getFormType()) { case Catalog::NoForm: return Document::NoForm; case Catalog::AcroForm: return Document::AcroForm; case Catalog::XfaForm: return Document::XfaForm; } return Document::NoForm; // make gcc happy } QVector Document::formCalculateOrder() const { Form *form = m_doc->doc->getCatalog()->getForm(); if (!form) { return {}; } QVector result; const std::vector &calculateOrder = form->getCalculateOrder(); for (Ref r : calculateOrder) { FormWidget *w = form->findWidgetByRef(r); if (w) { result << w->getID(); } } return result; } std::vector> Document::signatures() const { std::vector> result; const std::vector<::FormFieldSignature *> pSignatures = m_doc->doc->getSignatureFields(); for (::FormFieldSignature *pSignature : pSignatures) { ::FormWidget *fw = pSignature->getCreateWidget(); ::Page *p = m_doc->doc->getPage(fw->getWidgetAnnotation()->getPageNum()); result.push_back(std::make_unique(m_doc, p, static_cast(fw))); } return result; } bool Document::xrefWasReconstructed() const { return m_doc->xrefReconstructed; } void Document::setXRefReconstructedCallback(const std::function &callback) { m_doc->xrefReconstructedCallback = callback; } QDateTime convertDate(const char *dateString) { int year, mon, day, hour, min, sec, tzHours, tzMins; char tz; GooString date(dateString); if (parseDateString(&date, &year, &mon, &day, &hour, &min, &sec, &tz, &tzHours, &tzMins)) { QDate d(year, mon, day); QTime t(hour, min, sec); if (d.isValid() && t.isValid()) { QDateTime dt(d, t, Qt::UTC); if (tz) { // then we have some form of timezone if ('Z' == tz) { // We are already at UTC } else if ('+' == tz) { // local time is ahead of UTC dt = dt.addSecs(-1 * ((tzHours * 60) + tzMins) * 60); } else if ('-' == tz) { // local time is behind UTC dt = dt.addSecs(((tzHours * 60) + tzMins) * 60); } else { qWarning("unexpected tz val"); } } return dt; } } return QDateTime(); } bool isCmsAvailable() { #if defined(USE_CMS) return true; #else return false; #endif } bool isOverprintPreviewAvailable() { return true; } }