blob: 6daed69834dcafe5c4d3dd179dcdd24b3964a771 [file] [log] [blame]
/* poppler-document.cc: qt interface to poppler
* Copyright (C) 2005, Net Integration Technologies, Inc.
* Copyright (C) 2005, 2008, Brad Hards <bradh@frogmouth.net>
* Copyright (C) 2005-2010, 2012, 2013, 2015, 2017-2022, Albert Astals Cid <aacid@kde.org>
* Copyright (C) 2006-2010, Pino Toscano <pino@kde.org>
* Copyright (C) 2010, 2011 Hib Eris <hib@hiberis.nl>
* Copyright (C) 2012 Koji Otani <sho@bbr.jp>
* Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
* Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
* Copyright (C) 2014, 2018, 2020 Adam Reichold <adam.reichold@t-online.de>
* Copyright (C) 2015 William Bader <williambader@hotmail.com>
* Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
* Copyright (C) 2017, 2021 Adrian Johnson <ajohnson@redneon.com>
* Copyright (C) 2017 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
* Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
* Copyright (C) 2019-2021 Oliver Sander <oliver.sander@tu-dresden.de>
* Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
* Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
* Copyright (C) 2020 Katarina Behrens <Katarina.Behrens@cib.de>
* Copyright (C) 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de>
* Copyright (C) 2021 Mahmoud Khalil <mahmoudkhalil11@gmail.com>
* Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net>
*
* 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-qt5.h"
#include <config.h>
#include <poppler-config.h>
#include <ErrorCodes.h>
#include <GlobalParams.h>
#include <Outline.h>
#include <PDFDoc.h>
#include <Stream.h>
#include <Catalog.h>
#include <ViewerPreferences.h>
#include <DateInfo.h>
#include <GfxState.h>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QByteArray>
#include "poppler-form.h"
#include "poppler-private.h"
#include "poppler-page-private.h"
#include "poppler-outline-private.h"
#if defined(USE_CMS)
# include <lcms2.h>
#endif
namespace Poppler {
Document *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);
}
Document *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);
}
Document *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);
}
Document *DocumentData::checkDocument(DocumentData *doc)
{
Document *pdoc;
if (doc->doc->isOk() || doc->doc->getErrorCode() == errEncrypted) {
pdoc = 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;
}
Page *Document::page(int index) const
{
Page *page = new Page(m_doc, index);
if (page->m_page->page == nullptr) {
delete page;
return nullptr;
}
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<FontInfo> Document::fonts() const
{
QList<FontInfo> ourList;
FontIterator it(0, m_doc);
while (it.hasNext()) {
ourList += it.next();
}
return ourList;
}
QList<EmbeddedFile *> Document::embeddedFiles() const
{
return m_doc->m_embeddedFiles;
}
FontIterator *Document::newFontIterator(int startPage) const
{
return 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<GooString> 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<GooString> 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<GooString> 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<GooString> 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<GooString> 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<GooString> 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<GooString> 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> 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<GooString> 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<GooString> 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<GooString> 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();
}
void Document::getPdfVersion(int *major, int *minor) const
{
if (major) {
*major = m_doc->doc->getPDFMajorVersion();
}
if (minor) {
*minor = m_doc->doc->getPDFMinorVersion();
}
}
Document::PdfVersion Document::getPdfVersion() const
{
return PdfVersion { m_doc->doc->getPDFMajorVersion(), m_doc->doc->getPDFMinorVersion() };
}
Page *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<GooString> 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()));
}
QDomDocument *Document::toc() const
{
Outline *outline = m_doc->doc->getOutline();
if (!outline) {
return nullptr;
}
const std::vector<::OutlineItem *> *items = outline->getItems();
if (!items || items->size() < 1) {
return nullptr;
}
QDomDocument *toc = new QDomDocument();
if (items->size() > 0) {
m_doc->addTocChildren(toc, toc, items);
}
return toc;
}
QVector<OutlineItem> Document::outline() const
{
QVector<OutlineItem> 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;
}
LinkDestination *Document::linkDestination(const QString &name)
{
GooString *namedDest = QStringToGooString(name);
LinkDestinationData ldd(nullptr, namedDest, m_doc, false);
LinkDestination *ld = new LinkDestination(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 it's 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::RenderBackend> Document::availableRenderBackends()
{
QSet<Document::RenderBackend> ret;
ret << Document::SplashBackend;
ret << Document::QPainterBackend;
ret << Document::ArthurBackend; // For backward compatibility
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);
}
PSConverter *Document::psConverter() const
{
return new PSConverter(m_doc);
}
PDFConverter *Document::pdfConverter() const
{
return new PDFConverter(m_doc);
}
QString Document::metadata() const
{
QString result;
Catalog *catalog = m_doc->doc->getCatalog();
if (catalog && catalog->isOk()) {
std::unique_ptr<GooString> 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;
}
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;
}
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<int> Document::formCalculateOrder() const
{
Form *form = m_doc->doc->getCatalog()->getForm();
if (!form) {
return {};
}
QVector<int> result;
const std::vector<Ref> &calculateOrder = form->getCalculateOrder();
for (Ref r : calculateOrder) {
FormWidget *w = form->findWidgetByRef(r);
if (w) {
result << w->getID();
}
}
return result;
}
QVector<FormFieldSignature *> Document::signatures() const
{
QVector<FormFieldSignature *> 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.append(new FormFieldSignature(m_doc, p, static_cast<FormWidgetSignature *>(fw)));
}
return result;
}
bool Document::xrefWasReconstructed() const
{
return m_doc->xrefReconstructed;
}
void Document::setXRefReconstructedCallback(const std::function<void()> &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();
}
QDateTime convertDate(char *dateString)
{
return convertDate((const char *)dateString);
}
bool isCmsAvailable()
{
#if defined(USE_CMS)
return true;
#else
return false;
#endif
}
bool isOverprintPreviewAvailable()
{
return true;
}
}