/* 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, 2025, 2026 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>
 * Copyright (C) 2024 Pratham Gandhi <ppg.1382@gmail.com>
 * Copyright (C) 2024 Stefan Brüns <stefan.bruens@rwth-aachen.de>
 * Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
 * Copyright (C) 2025 Arnav V <arnav0872@gmail.com>
 *
 * 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 <config.h>
#include <poppler-config.h>
#include <ErrorCodes.h>
#include <Form.h>
#include <GlobalParams.h>
#include <OptionalContent.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 <QTimeZone>

#include "poppler-form.h"
#include "poppler-private.h"
#include "poppler-link-private.h"
#include "poppler-page-private.h"
#include "poppler-outline-private.h"

#if USE_CMS
#    include <lcms2.h>
#endif

namespace Poppler {

std::unique_ptr<Document> Document::load(const QString &filePath, const QByteArray &ownerPassword, const QByteArray &userPassword)
{
    auto *doc = new DocumentData(filePath, GooString(ownerPassword.data()), GooString(userPassword.data()));
    return DocumentData::checkDocument(doc);
}

std::unique_ptr<Document> Document::load(QIODevice *device, const QByteArray &ownerPassword, const QByteArray &userPassword)
{
    auto *doc = new DocumentData(device, GooString(ownerPassword.data()), GooString(userPassword.data()));
    return DocumentData::checkDocument(doc);
}

std::unique_ptr<Document> Document::loadFromData(const QByteArray &fileContents, const QByteArray &ownerPassword, const QByteArray &userPassword)
{
    // create stream
    auto *doc = new DocumentData(fileContents, GooString(ownerPassword.data()), GooString(userPassword.data()));
    return DocumentData::checkDocument(doc);
}

std::unique_ptr<Document> DocumentData::checkDocument(DocumentData *doc)
{
    if (doc->doc->isOk() || doc->doc->getErrorCode() == errEncrypted) {
        auto pdoc = std::unique_ptr<Document>(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;
    }
    delete doc;

    return nullptr;
}

Document::Document(DocumentData *dataA)
{
    m_doc = dataA;
}

Document::~Document()
{
    delete m_doc;
}

std::unique_ptr<Page> Document::page(int index) const
{
    // Cannot call std::make_unique, because the constructor of Page is private
    auto page = std::unique_ptr<Page>(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<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;
}

std::unique_ptr<FontIterator> Document::newFontIterator(int startPage) const
{
    // Cannot use std::make_unique, because the FontIterator constructor is private
    return std::unique_ptr<FontIterator>(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()) {
            Stream *stream = strObj.getStream();
            if (stream->rewind()) {
                int c;
                while ((c = stream->getChar()) != EOF) {
                    result.append(static_cast<char>(c));
                }
                stream->close();
            }
        }
        delete xref;
    }
    return result;
}

QString Document::info(const QString &type) const
{
    if (m_doc->locked) {
        return QString();
    }

    std::optional<std::string> goo(m_doc->doc->getDocInfoStringEntry(type.toStdString()));
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

bool Document::setInfo(const QString &key, const QString &val)
{
    if (m_doc->locked) {
        return false;
    }

    std::unique_ptr<GooString> goo = QStringToUnicodeGooString(val);
    m_doc->doc->setDocInfoStringEntry(key.toLatin1().constData(), std::move(goo));
    return true;
}

QString Document::title() const
{
    if (m_doc->locked) {
        return QString();
    }

    std::optional<std::string> goo(m_doc->doc->getDocInfoTitle());
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

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::optional<std::string> goo(m_doc->doc->getDocInfoAuthor());
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

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::optional<std::string> goo(m_doc->doc->getDocInfoSubject());
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

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::optional<std::string> goo(m_doc->doc->getDocInfoKeywords());
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

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::optional<std::string> goo(m_doc->doc->getDocInfoCreator());
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

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::optional<std::string> goo(m_doc->doc->getDocInfoProducer());
    return goo ? UnicodeParsedString(goo.value()) : QString {};
}

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::optional<std::string> goo(m_doc->doc->getDocInfoStringEntry(type.toStdString()));
    QString str = goo ? UnicodeParsedString(goo.value()) : QString();
    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::optional<std::string> goo(m_doc->doc->getDocInfoCreatDate());
    QString str = goo ? UnicodeParsedString(goo.value()) : QString {};
    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::optional<std::string> goo(m_doc->doc->getDocInfoModDate());
    QString str = goo.has_value() ? UnicodeParsedString(goo.value()) : QString {};
    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 { .major = m_doc->doc->getPDFMajorVersion(), .minor = m_doc->doc->getPDFMinorVersion() };
}

std::unique_ptr<Page> Document::page(const QString &label) const
{
    const GooString label_g(label.toLatin1().data());
    int index;

    if (!m_doc->doc->getCatalog()->labelToIndex(label_g.toStr(), &index)) {
        const std::unique_ptr<GooString> label_ug(QStringToUnicodeGooString(label));
        if (!m_doc->doc->getCatalog()->labelToIndex(label_ug->toStr(), &index)) {
            return nullptr;
        }
    }

    return page(index);
}

bool Document::hasEmbeddedFiles() const
{
    return (!(0 == m_doc->doc->getCatalog()->numEmbeddedFiles()));
}

QVector<OutlineItem> Document::outline() const
{
    QVector<OutlineItem> result;

    if (::Outline *outline = m_doc->doc->getOutline()) {
        if (const std::vector<::OutlineItem *> *items = outline->getItems()) {
            for (::OutlineItem *item : *items) {
                result.push_back(OutlineItem { new OutlineItemData { item, m_doc } });
            }
        }
    }

    return result;
}

std::unique_ptr<LinkDestination> Document::linkDestination(const QString &name)
{
    const std::unique_ptr<GooString> namedDest = QStringToGooString(name);
    LinkDestinationData ldd(nullptr, namedDest.get(), m_doc, false);
    auto ld = std::make_unique<LinkDestination>(ldd);
    return ld;
}

void Document::setPaperColor(const QColor &color)
{
    m_doc->setPaperColor(color);
}

void Document::setColorDisplayProfile(void *outputProfileA)
{
#if 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 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 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 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::RenderBackend> Document::availableRenderBackends()
{
    QSet<Document::RenderBackend> 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 & ~static_cast<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<PSConverter> Document::psConverter() const
{
    // Cannot use std::make_unique, because the PSConverter constructor is private
    return std::unique_ptr<PSConverter>(new PSConverter(m_doc));
}

std::unique_ptr<PDFConverter> Document::pdfConverter() const
{
    // Cannot use std::make_unique, because the PDFConverter constructor is private
    return std::unique_ptr<PDFConverter>(new PDFConverter(m_doc));
}

QString Document::metadata() const
{
    QString result;
    Catalog *catalog = m_doc->doc->getCatalog();
    if (catalog && catalog->isOk()) {
        std::optional<std::string> s = catalog->readMetadata();
        if (s) {
            result = UnicodeParsedString(s.value());
        }
    }
    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 m_doc->m_optContentModel.get();
}

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<std::string> 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) {
        std::string s = catalog->getJS(i);
        if (!s.empty()) {
            scripts.append(UnicodeParsedString(s));
        }
    }
    return scripts;
}

std::unique_ptr<Link> 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<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;
}

std::vector<std::unique_ptr<FormFieldSignature>> Document::signatures() const
{
    std::vector<std::unique_ptr<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.push_back(std::make_unique<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;

    std::string 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()) {
            int tzSecs = 0;
            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
                    tzSecs = (tzHours * 3600) + (tzMins * 60);
                } else if ('-' == tz) {
                    // local time is behind UTC
                    tzSecs = (tzHours * -3600) + (tzMins * -60);
                } else {
                    qWarning("unexpected tz val");
                }
            }
            return QDateTime(d, t, QTimeZone(tzSecs));
        }
    }
    return QDateTime();
}

bool isCmsAvailable()
{
#if USE_CMS
    return true;
#else
    return false;
#endif
}

bool isOverprintPreviewAvailable()
{
    return true;
}

}
