/* poppler-page.cc: qt interface to poppler
 * Copyright (C) 2005, Net Integration Technologies, Inc.
 * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
 * Copyright (C) 2005-2022, 2024-2026, Albert Astals Cid <aacid@kde.org>
 * Copyright (C) 2005, Stefan Kebekus <stefan.kebekus@math.uni-koeln.de>
 * Copyright (C) 2006-2011, Pino Toscano <pino@kde.org>
 * Copyright (C) 2008 Carlos Garcia Campos <carlosgc@gnome.org>
 * Copyright (C) 2009 Shawn Rutledge <shawn.t.rutledge@gmail.com>
 * Copyright (C) 2010, 2012, Guillermo Amaral <gamaral@kdab.com>
 * Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
 * Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
 * Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
 * Copyright (C) 2012 Tobias Koenig <tokoe@kdab.com>
 * Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 * Copyright (C) 2012, 2015 Adam Reichold <adamreichold@myopera.com>
 * Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 * Copyright (C) 2015 William Bader <williambader@hotmail.com>
 * Copyright (C) 2016 Arseniy Lartsev <arseniy@alumni.chalmers.se>
 * Copyright (C) 2016, Hanno Meyer-Thurow <h.mth@web.de>
 * Copyright (C) 2017-2021, Oliver Sander <oliver.sander@tu-dresden.de>
 * Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
 * Copyright (C) 2017, 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) 2018 Intevation GmbH <intevation@intevation.de>
 * Copyright (C) 2018, Tobias Deiminger <haxtibal@posteo.de>
 * Copyright (C) 2018, 2021 Nelson Benítez León <nbenitezl@gmail.com>
 * Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
 * Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net>
 * Copyright (C) 2021 Thomas Huxhorn <thomas.huxhorn@web.de>
 * Copyright (C) 2023 Kevin Ottens <kevin.ottens@enioka.com>. Work sponsored by De Bortoli Wines
 * Copyright (C) 2024-2026 Stefan Brüns <stefan.bruens@rwth-aachen.de>
 * Copyright (C) 2024 Pratham Gandhi <ppg.1382@gmail.com>
 * Copyright (C) 2024, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
 * Copyright (C) 2026 Aditya Tiwari <suntiwari3495@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 <QtCore/QHash>
#include <QtCore/QMap>
#include <QtCore/QVarLengthArray>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <QDebug>

#include <config.h>
#include <cfloat>
#include <poppler-config.h>
#include <PDFDoc.h>
#include <Catalog.h>
#include <Form.h>
#include <ErrorCodes.h>
#include <TextOutputDev.h>
#include <Annot.h>
#include <Link.h>
#include <QPainterOutputDev.h>
#include <Rendition.h>
#include <SplashOutputDev.h>
#include <goo/gmem.h>
#include <splash/SplashBitmap.h>

#include "poppler-private.h"
#include "poppler-page-transition-private.h"
#include "poppler-page-private.h"
#include "poppler-link-extractor-private.h"
#include "poppler-link-private.h"
#include "poppler-annotation-private.h"
#include "poppler-form.h"
#include "poppler-media.h"

namespace Poppler {

class TextExtractionAbortHelper
{
public:
    TextExtractionAbortHelper(Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA)
    {
        shouldAbortExtractionCallback = shouldAbortCallback;
        payload = payloadA;
    }

    Page::ShouldAbortQueryFunc shouldAbortExtractionCallback = nullptr;
    QVariant payload;
};

class OutputDevCallbackHelper
{
public:
    void setCallbacks(Page::RenderToImagePartialUpdateFunc callback, Page::ShouldRenderToImagePartialQueryFunc shouldDoCallback, Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA)
    {
        partialUpdateCallback = callback;
        shouldDoPartialUpdateCallback = shouldDoCallback;
        shouldAbortRenderCallback = shouldAbortCallback;
        payload = payloadA;
    }

    Page::RenderToImagePartialUpdateFunc partialUpdateCallback = nullptr;
    Page::ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback = nullptr;
    Page::ShouldAbortQueryFunc shouldAbortRenderCallback = nullptr;
    QVariant payload;
};

class Qt6SplashOutputDev : public SplashOutputDev, public OutputDevCallbackHelper
{
public:
    Qt6SplashOutputDev(SplashColorMode colorModeA, int bitmapRowPadA, bool ignorePaperColorA, SplashColorPtr paperColorA, bool bitmapTopDownA, SplashThinLineMode thinLineMode, bool overprintPreviewA)
        : SplashOutputDev(colorModeA, bitmapRowPadA, paperColorA, bitmapTopDownA, thinLineMode, overprintPreviewA), ignorePaperColor(ignorePaperColorA)
    {
    }

    ~Qt6SplashOutputDev() override;

    void dump() override
    {
        if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) {
            partialUpdateCallback(getXBGRImage(false /* takeImageData */), payload);
        }
    }

    QImage getXBGRImage(bool takeImageData)
    {
        SplashBitmap *b = getBitmap();

        // If we use DeviceN8, convert to XBGR8.
        // If requested, also transfer Splash's internal alpha channel.
        const SplashBitmap::ConversionMode mode = ignorePaperColor ? SplashBitmap::conversionAlphaPremultiplied : SplashBitmap::conversionOpaque;

        const QImage::Format format = ignorePaperColor ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;

        if (b->convertToXBGR(mode)) {
            const int bw = b->getWidth();
            const int bh = b->getHeight();
            const int brs = b->getRowSize();

            SplashColorPtr data = takeImageData ? b->takeData() : b->getDataPtr();

            if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
                // Convert byte order from RGBX to XBGR.
                for (int i = 0; i < bh; ++i) {
                    for (int j = 0; j < bw; ++j) {
                        SplashColorPtr pixel = &data[i * brs + j];

                        qSwap(pixel[0], pixel[3]);
                        qSwap(pixel[1], pixel[2]);
                    }
                }
            }

            if (takeImageData) {
                // Construct a Qt image holding (and also owning) the raw bitmap data.
                QImage i(data, bw, bh, brs, format, gfree, data);
                if (i.isNull()) {
                    gfree(data);
                }
                return i;
            }
            return QImage(data, bw, bh, brs, format).copy();
        }

        return QImage();
    }

private:
    bool ignorePaperColor;
};

Qt6SplashOutputDev::~Qt6SplashOutputDev() = default;

class QImageDumpingQPainterOutputDev : public QPainterOutputDev, public OutputDevCallbackHelper
{
public:
    QImageDumpingQPainterOutputDev(QPainter *painter, QImage *i) : QPainterOutputDev(painter), image(i) { }
    ~QImageDumpingQPainterOutputDev() override;

    void dump() override
    {
        if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) {
            partialUpdateCallback(*image, payload);
        }
    }

private:
    QImage *image;
};

QImageDumpingQPainterOutputDev::~QImageDumpingQPainterOutputDev() = default;

std::unique_ptr<Link> PageData::convertLinkActionToLink(const ::LinkAction *a, const QRectF &linkArea) const
{
    return convertLinkActionToLink(a, parentDoc, linkArea);
}

std::unique_ptr<Link> PageData::convertLinkActionToLink(const ::LinkAction *a, DocumentData *parentDoc, const QRectF &linkArea)
{
    if (!a) {
        return nullptr;
    }

    std::unique_ptr<Link> popplerLink;
    switch (a->getKind()) {
    case actionGoTo: {
        const auto *g = static_cast<const LinkGoTo *>(a);
        const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, false);
        // create link: no ext file, namedDest, object pointer
        popplerLink = std::make_unique<LinkGoto>(linkArea, QString(), LinkDestination(ldd));
    } break;

    case actionGoToR: {
        const auto *g = static_cast<const LinkGoToR *>(a);
        // copy link file
        const QString fileName = UnicodeParsedString(g->getFileName());
        const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, !fileName.isEmpty());
        // create link: fileName, namedDest, object pointer
        popplerLink = std::make_unique<LinkGoto>(linkArea, fileName, LinkDestination(ldd));
    } break;

    case actionLaunch: {
        const auto *e = static_cast<const LinkLaunch *>(a);
        const GooString *p = e->getParams();
        popplerLink = std::make_unique<LinkExecute>(linkArea, UnicodeParsedString(e->getFileName()), p ? QString::fromStdString(p->toStr()) : QString {});
    } break;

    case actionNamed: {
        const std::string &name = (static_cast<const LinkNamed *>(a))->getName();
        if (name == "NextPage") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageNext);
        } else if (name == "PrevPage") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PagePrev);
        } else if (name == "FirstPage") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageFirst);
        } else if (name == "LastPage") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageLast);
        } else if (name == "GoBack") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::HistoryBack);
        } else if (name == "GoForward") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::HistoryForward);
        } else if (name == "Quit") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Quit);
        } else if (name == "GoToPage") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::GoToPage);
        } else if (name == "Find") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Find);
        } else if (name == "FullScreen") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Presentation);
        } else if (name == "Print") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Print);
        } else if (name == "Close") {
            // acroread closes the document always, doesn't care whether
            // its presentation mode or not
            // popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::EndPresentation);
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Close);
        } else if (name == "SaveAs") {
            popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::SaveAs);
        } else {
            qWarning() << "Unhandled action name" << name.c_str();
        }
    } break;

    case actionURI: {
        popplerLink = std::make_unique<LinkBrowse>(linkArea, QString::fromStdString((static_cast<const LinkURI *>(a))->getURI()));
    } break;

    case actionSound: {
        const auto *ls = static_cast<const ::LinkSound *>(a);
        popplerLink = std::make_unique<LinkSound>(linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject(ls->getSound()));
    } break;

    case actionJavaScript: {
        const auto *ljs = static_cast<const ::LinkJavaScript *>(a);
        popplerLink = std::make_unique<LinkJavaScript>(linkArea, UnicodeParsedString(ljs->getScript()));
    } break;

    case actionMovie: {
        const auto *lm = static_cast<const ::LinkMovie *>(a);

        const QString title = (lm->hasAnnotTitle() ? UnicodeParsedString(lm->getAnnotTitle()) : QString());

        Ref reference = Ref::INVALID();
        if (lm->hasAnnotRef()) {
            reference = *lm->getAnnotRef();
        }

        LinkMovie::Operation operation = LinkMovie::Play;
        switch (lm->getOperation()) {
        case ::LinkMovie::operationTypePlay:
            operation = LinkMovie::Play;
            break;
        case ::LinkMovie::operationTypePause:
            operation = LinkMovie::Pause;
            break;
        case ::LinkMovie::operationTypeResume:
            operation = LinkMovie::Resume;
            break;
        case ::LinkMovie::operationTypeStop:
            operation = LinkMovie::Stop;
            break;
        };

        popplerLink = std::make_unique<LinkMovie>(linkArea, operation, title, reference);
    } break;

    case actionRendition: {
        const auto *lrn = static_cast<const ::LinkRendition *>(a);

        Ref reference = Ref::INVALID();
        if (lrn->hasScreenAnnot()) {
            reference = lrn->getScreenAnnot();
        }

        popplerLink = std::make_unique<LinkRendition>(linkArea, lrn->getMedia() ? lrn->getMedia()->copy() : std::unique_ptr<::MediaRendition> {}, lrn->getOperation(), UnicodeParsedString(lrn->getScript()), reference);
    } break;

    case actionOCGState: {
        const auto *plocg = static_cast<const ::LinkOCGState *>(a);

        auto *locgp = new LinkOCGStatePrivate(linkArea, plocg->getStateList(), plocg->getPreserveRB());
        popplerLink = std::make_unique<LinkOCGState>(locgp);
    } break;

    case actionHide: {
        const auto *lh = static_cast<const ::LinkHide *>(a);

        auto *lhp = new LinkHidePrivate(linkArea, lh->hasTargetName() ? UnicodeParsedString(lh->getTargetName()) : QString(), lh->isShowAction());
        popplerLink = std::make_unique<LinkHide>(lhp);
    } break;

    case actionResetForm: {
        const auto *lrf = static_cast<const ::LinkResetForm *>(a);
        std::vector<std::string> stdStringFields = lrf->getFields();
        QStringList qStringFields;
        for (const std::string &str : stdStringFields) {
            qStringFields << QString::fromStdString(str);
        }
        auto *lrfp = new LinkResetFormPrivate(linkArea, qStringFields, lrf->getExclude());
        popplerLink = std::make_unique<LinkResetForm>(lrfp);
    } break;

    case actionSubmitForm: {
        const auto *lsf = static_cast<const ::LinkSubmitForm *>(a);
        const std::vector<std::string> &stdStringFields = lsf->getFields();
        QVector<int> fieldIds;
        fieldIds.reserve(stdStringFields.size());
        Form *form = parentDoc->doc->getCatalog()->getForm();
        for (const std::string &fieldStr : stdStringFields) {
            ::FormField *field = form->findFieldByFullyQualifiedNameOrRef(fieldStr);
            if (field && !field->getNoExport()) {
                int numWidgets = field->getNumWidgets();
                for (int i = 0; i < numWidgets; i++) {
                    ::FormWidget *widget = field->getWidget(i);
                    if (widget) {
                        fieldIds.append(field->getWidget(i)->getID());
                    }
                }
            }
        }
        QString qStringUrl = QString::fromStdString(lsf->getUrl());
        auto qFlags = static_cast<LinkSubmitForm::SubmitFormFlags>(lsf->getFlags());
        auto *lsfp = new LinkSubmitFormPrivate(linkArea, fieldIds, qStringUrl, qFlags);
        popplerLink = std::make_unique<LinkSubmitForm>(lsfp);
    } break;

    case actionUnknown:
        break;
    }

    if (popplerLink) {
        std::vector<std::unique_ptr<Link>> links;
        for (const std::unique_ptr<::LinkAction> &nextAction : a->nextActions()) {
            links.push_back(convertLinkActionToLink(nextAction.get(), parentDoc, linkArea));
        }
        LinkPrivate::get(popplerLink.get())->nextLinks = std::move(links);
    }

    return popplerLink;
}

inline std::unique_ptr<TextPage> PageData::prepareTextSearch(const QString &text, Page::Rotation rotate, QVector<Unicode> *u) const
{
    *u = text.toUcs4();

    const int rotation = static_cast<int>(rotate) * 90;

    // fetch ourselves a textpage
    TextOutputDev td(nullptr, true, 0, false, false);
    parentDoc->doc->displayPage(&td, index + 1, 72, 72, rotation, false, true, false, nullptr, nullptr, nullptr, nullptr, true);
    std::unique_ptr<TextPage> textPage = td.takeText();

    return textPage;
}

inline bool PageData::performSingleTextSearch(TextPage *textPage, QVector<Unicode> &u, double &sLeft, double &sTop, double &sRight, double &sBottom, Page::SearchDirection direction, bool sCase, bool sWords, bool sDiacritics,
                                              bool sAcrossLines)
{
    if (direction == Page::FromTop) {
        return textPage->findText(u.data(), u.size(), true, true, false, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr);
    }
    if (direction == Page::NextResult) {
        return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr);
    }
    if (direction == Page::PreviousResult) {
        return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, true, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr);
    }

    return false;
}

inline QList<QRectF> PageData::performMultipleTextSearch(TextPage *textPage, QVector<Unicode> &u, bool sCase, bool sWords, bool sDiacritics, bool sAcrossLines)
{
    QList<QRectF> results;
    double sLeft = 0.0, sTop = 0.0, sRight = 0.0, sBottom = 0.0;
    bool sIgnoredHyphen = false;
    PDFRectangle continueMatch;
    continueMatch.x1 = DBL_MAX; // we use this to detect valid return values

    while (textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, &continueMatch, &sIgnoredHyphen)) {
        QRectF result;

        result.setLeft(sLeft);
        result.setTop(sTop);
        result.setRight(sRight);
        result.setBottom(sBottom);

        results.append(result);

        if (sAcrossLines && continueMatch.x1 != DBL_MAX) {
            QRectF resultN;

            resultN.setLeft(continueMatch.x1);
            resultN.setTop(continueMatch.y2);
            resultN.setRight(continueMatch.x2);
            resultN.setBottom(continueMatch.y1);

            results.append(resultN);
            continueMatch.x1 = DBL_MAX;
        }
    }

    return results;
}

Page::Page(DocumentData *doc, int index)
{
    m_page = new PageData();
    m_page->index = index;
    m_page->parentDoc = doc;
    m_page->page = doc->doc->getPage(m_page->index + 1);
    m_page->transition = nullptr;
}

Page::~Page()
{
    delete m_page->transition;
    delete m_page;
}

// Callback that filters out everything but form fields
static auto annotDisplayDecideCbk = [](Annot *annot, void * /*user_data*/) {
    // Hide everything but forms
    return (annot->getType() == Annot::typeWidget);
};

// A nullptr, but with the type of a function pointer
// Needed to make the ternary operator happy.
static bool (*nullAnnotCallBack)(Annot *annot, void *user_data) = nullptr;

static auto shouldAbortRenderInternalCallback = [](void *user_data) {
    auto *helper = reinterpret_cast<OutputDevCallbackHelper *>(user_data);
    return helper->shouldAbortRenderCallback(helper->payload);
};

static auto shouldAbortExtractionInternalCallback = [](void *user_data) {
    auto *helper = reinterpret_cast<TextExtractionAbortHelper *>(user_data);
    return helper->shouldAbortExtractionCallback(helper->payload);
};

// A nullptr, but with the type of a function pointer
// Needed to make the ternary operator happy.
static bool (*nullAbortCallBack)(void *user_data) = nullptr;

static bool renderToQPainter(QImageDumpingQPainterOutputDev *qpainter_output, QPainter *painter, PageData *page, double xres, double yres, int x, int y, int w, int h, Page::Rotation rotate, Page::PainterFlags flags)
{
    const bool savePainter = !(flags & Page::DontSaveAndRestore);
    if (savePainter) {
        painter->save();
    }
    if (page->parentDoc->m_hints & Document::Antialiasing) {
        painter->setRenderHint(QPainter::Antialiasing);
    }
    if (page->parentDoc->m_hints & Document::TextAntialiasing) {
        painter->setRenderHint(QPainter::TextAntialiasing);
    }
    painter->translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y);

    qpainter_output->startDoc(page->parentDoc->doc.get());

    const bool hideAnnotations = page->parentDoc->m_hints & Document::HideAnnotations;

    OutputDevCallbackHelper *abortHelper = qpainter_output;
    page->parentDoc->doc->displayPageSlice(qpainter_output, page->index + 1, xres, yres, static_cast<int>(rotate) * 90, false, true, false, x, y, w, h,
                                           abortHelper->shouldAbortRenderCallback ? shouldAbortRenderInternalCallback : nullAbortCallBack, abortHelper, hideAnnotations ? annotDisplayDecideCbk : nullAnnotCallBack, nullptr, true);
    if (savePainter) {
        painter->restore();
    }
    return true;
}

QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate) const
{
    return renderToImage(xres, yres, x, y, w, h, rotate, nullptr, nullptr, QVariant());
}

QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,
                           const QVariant &payload) const
{
    return renderToImage(xres, yres, x, y, w, h, rotate, partialUpdateCallback, shouldDoPartialUpdateCallback, nullptr, payload);
}

// Translate the text hinting settings from poppler-speak to Qt-speak
static QFont::HintingPreference QFontHintingFromPopplerHinting(int renderHints)
{
    QFont::HintingPreference result = QFont::PreferNoHinting;

    if (renderHints & Document::TextHinting) {
        result = (renderHints & Document::TextSlightHinting) ? QFont::PreferVerticalHinting : QFont::PreferFullHinting;
    }

    return result;
}

QImage Page::renderToImage(double xres, double yres, int xPos, int yPos, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,
                           ShouldAbortQueryFunc shouldAbortRenderCallback, const QVariant &payload) const
{
    int rotation = static_cast<int>(rotate) * 90;
    QImage img;
    switch (m_page->parentDoc->m_backend) {
    case Poppler::Document::SplashBackend: {
        SplashColor bgColor;
        const bool overprintPreview = (m_page->parentDoc->m_hints & Document::OverprintPreview) != 0;
        if (overprintPreview) {
            unsigned char c, m, y, k;

            c = 255 - m_page->parentDoc->paperColor.blue();
            m = 255 - m_page->parentDoc->paperColor.red();
            y = 255 - m_page->parentDoc->paperColor.green();
            k = c;
            if (m < k) {
                k = m;
            }
            if (y < k) {
                k = y;
            }
            bgColor[0] = c - k;
            bgColor[1] = m - k;
            bgColor[2] = y - k;
            bgColor[3] = k;
            for (int i = 4; i < SPOT_NCOMPS + 4; i++) {
                bgColor[i] = 0;
            }
        } else {
            bgColor[0] = m_page->parentDoc->paperColor.blue();
            bgColor[1] = m_page->parentDoc->paperColor.green();
            bgColor[2] = m_page->parentDoc->paperColor.red();
        }

        const SplashColorMode colorMode = overprintPreview ? splashModeDeviceN8 : splashModeXBGR8;

        SplashThinLineMode thinLineMode = splashThinLineDefault;
        if (m_page->parentDoc->m_hints & Document::ThinLineShape) {
            thinLineMode = splashThinLineShape;
        }
        if (m_page->parentDoc->m_hints & Document::ThinLineSolid) {
            thinLineMode = splashThinLineSolid;
        }

        const bool ignorePaperColor = m_page->parentDoc->m_hints & Document::IgnorePaperColor;

        Qt6SplashOutputDev splash_output(colorMode, 4, ignorePaperColor, ignorePaperColor ? nullptr : bgColor, true, thinLineMode, overprintPreview);

        splash_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload);

        splash_output.setFontAntialias((m_page->parentDoc->m_hints & Document::TextAntialiasing) != 0);
        splash_output.setVectorAntialias((m_page->parentDoc->m_hints & Document::Antialiasing) != 0);
        splash_output.setFreeTypeHinting((m_page->parentDoc->m_hints & Document::TextHinting) != 0, (m_page->parentDoc->m_hints & Document::TextSlightHinting) != 0);

#if USE_CMS
        splash_output.setDisplayProfile(m_page->parentDoc->m_displayProfile);
#endif

        splash_output.startDoc(m_page->parentDoc->doc.get());

        const bool hideAnnotations = m_page->parentDoc->m_hints & Document::HideAnnotations;

        OutputDevCallbackHelper *abortHelper = &splash_output;
        m_page->parentDoc->doc->displayPageSlice(&splash_output, m_page->index + 1, xres, yres, rotation, false, true, false, xPos, yPos, w, h, shouldAbortRenderCallback ? shouldAbortRenderInternalCallback : nullAbortCallBack, abortHelper,
                                                 hideAnnotations ? annotDisplayDecideCbk : nullAnnotCallBack, nullptr, true);

        img = splash_output.getXBGRImage(true /* takeImageData */);
        break;
    }
    case Poppler::Document::QPainterBackend: {
        QSize size = pageSize();
        QImage tmpimg(w == -1 ? qRound(size.width() * xres / 72.0) : w, h == -1 ? qRound(size.height() * yres / 72.0) : h, QImage::Format_ARGB32);

        QColor bgColor(m_page->parentDoc->paperColor.red(), m_page->parentDoc->paperColor.green(), m_page->parentDoc->paperColor.blue(), m_page->parentDoc->paperColor.alpha());

        tmpimg.fill(bgColor);

        QPainter painter(&tmpimg);
        QImageDumpingQPainterOutputDev qpainter_output(&painter, &tmpimg);

        qpainter_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints));

#if USE_CMS
        qpainter_output.setDisplayProfile(m_page->parentDoc->m_displayProfile);
#endif

        qpainter_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload);
        renderToQPainter(&qpainter_output, &painter, m_page, xres, yres, xPos, yPos, w, h, rotate, DontSaveAndRestore);
        painter.end();
        img = tmpimg;
        break;
    }
    }

    if (shouldAbortRenderCallback && shouldAbortRenderCallback(payload)) {
        return QImage();
    }

    return img;
}

bool Page::renderToPainter(QPainter *painter, double xres, double yres, int x, int y, int w, int h, Rotation rotate, PainterFlags flags) const
{
    if (!painter) {
        return false;
    }

    switch (m_page->parentDoc->m_backend) {
    case Poppler::Document::SplashBackend:
        return false;
    case Poppler::Document::QPainterBackend: {
        QImageDumpingQPainterOutputDev qpainter_output(painter, nullptr);

        qpainter_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints));

        return renderToQPainter(&qpainter_output, painter, m_page, xres, yres, x, y, w, h, rotate, flags);
    }
    }
    return false;
}

QImage Page::thumbnail() const
{
    unsigned char *data = nullptr;
    int w = 0;
    int h = 0;
    int rowstride = 0;
    bool r = m_page->page->loadThumb(&data, &w, &h, &rowstride);
    QImage ret;
    if (r) {
        // first construct a temporary image with the data got,
        // then force a copy of it so we can free the raw thumbnail data
        ret = QImage(data, w, h, rowstride, QImage::Format_RGB888).copy();
        gfree(data);
    }
    return ret;
}

QString Page::text(const QRectF &r, TextLayout textLayout) const
{
    TextOutputDev *output_dev;
    GooString s;
    QString result;

    const bool rawOrder = textLayout == RawOrderLayout;
    const bool physLayout = textLayout == PhysicalLayout;
    output_dev = new TextOutputDev(nullptr, physLayout, 0, rawOrder, false);
    m_page->parentDoc->doc->displayPageSlice(output_dev, m_page->index + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1, nullptr, nullptr, nullptr, nullptr, true);
    if (r.isNull()) {
        s = output_dev->getText(std::nullopt);
    } else {
        if (textLayout == ReadingOrder) {
            qWarning() << "ReadingOrder is not supported with non null rect";
            return {};
        }
        s = output_dev->getText(PDFRectangle { r.left(), r.top(), r.right(), r.bottom() });
    }

    result = QString::fromStdString(s.toStr());

    delete output_dev;
    return result;
}

QString Page::text(const QRectF &r) const
{
    return text(r, PhysicalLayout);
}

bool Page::search(const QString &text, double &sLeft, double &sTop, double &sRight, double &sBottom, SearchDirection direction, SearchFlags flags, Rotation rotate) const
{
    const bool sCase = !flags.testFlag(IgnoreCase);
    const bool sWords = flags.testFlag(WholeWords);
    const bool sDiacritics = flags.testFlag(IgnoreDiacritics);
    const bool sAcrossLines = flags.testFlag(AcrossLines);

    QVector<Unicode> u;
    std::unique_ptr<TextPage> textPage = m_page->prepareTextSearch(text, rotate, &u);

    const bool found = Poppler::PageData::performSingleTextSearch(textPage.get(), u, sLeft, sTop, sRight, sBottom, direction, sCase, sWords, sDiacritics, sAcrossLines);

    return found;
}

QList<QRectF> Page::search(const QString &text, SearchFlags flags, Rotation rotate) const
{
    const bool sCase = !flags.testFlag(IgnoreCase);
    const bool sWords = flags.testFlag(WholeWords);
    const bool sDiacritics = flags.testFlag(IgnoreDiacritics);
    const bool sAcrossLines = flags.testFlag(AcrossLines);

    QVector<Unicode> u;
    std::unique_ptr<TextPage> textPage = m_page->prepareTextSearch(text, rotate, &u);

    QList<QRectF> results = Poppler::PageData::performMultipleTextSearch(textPage.get(), u, sCase, sWords, sDiacritics, sAcrossLines);

    return results;
}

std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate) const
{
    return textList(rotate, nullptr, QVariant());
}

std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const
{
    std::vector<std::unique_ptr<TextBox>> output_list;

    TextOutputDev output_dev(nullptr, false, 0, false, false);

    int rotation = static_cast<int>(rotate) * 90;

    TextExtractionAbortHelper abortHelper(shouldAbortExtractionCallback, closure);
    m_page->parentDoc->doc->displayPageSlice(&output_dev, m_page->index + 1, 72, 72, rotation, false, false, false, -1, -1, -1, -1, shouldAbortExtractionCallback ? shouldAbortExtractionInternalCallback : nullAbortCallBack, &abortHelper,
                                             nullptr, nullptr, true);

    std::unique_ptr<TextWordList> word_list = output_dev.makeWordList();

    if (shouldAbortExtractionCallback && shouldAbortExtractionCallback(closure)) {
        return output_list;
    }

    QHash<const TextWord *, TextBox *> wordBoxMap;

    const std::vector<TextWord *> &words = word_list->getWords();
    output_list.reserve(words.size());
    for (const TextWord *word : words) {
        const std::unique_ptr<std::string> wordText = word->getText();
        const QString string = QString::fromUtf8(wordText->c_str());
        double xMin, yMin, xMax, yMax;
        word->getBBox(&xMin, &yMin, &xMax, &yMax);

        auto text_box = std::make_unique<TextBox>(string, QRectF(xMin, yMin, xMax - xMin, yMax - yMin));
        text_box->m_data->hasSpaceAfter = word->hasSpaceAfter();
        text_box->m_data->charBBoxes.reserve(word->getLength());
        for (int j = 0; j < word->getLength(); ++j) {
            word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax);
            text_box->m_data->charBBoxes.append(QRectF(xMin, yMin, xMax - xMin, yMax - yMin));
        }

        wordBoxMap.insert(word, text_box.get());

        output_list.push_back(std::move(text_box));
    }

    for (const TextWord *word : words) {
        TextBox *text_box = wordBoxMap.value(word);
        text_box->m_data->nextWord = wordBoxMap.value(word->nextWord());
    }

    return output_list;
}

PageTransition *Page::transition() const
{
    if (!m_page->transition) {
        Object o = m_page->page->getTrans();
        PageTransitionParams params;
        params.dictObj = &o;
        if (params.dictObj->isDict()) {
            m_page->transition = new PageTransition(params);
        }
    }
    return m_page->transition;
}

std::unique_ptr<Link> Page::action(PageAction act) const
{
    if (act == Page::Opening || act == Page::Closing) {
        Object o = m_page->page->getActions();
        if (!o.isDict()) {
            return nullptr;
        }
        Dict *dict = o.getDict();
        const char *key = act == Page::Opening ? "O" : "C";
        Object o2 = dict->lookup(const_cast<char *>(key));
        std::unique_ptr<::LinkAction> lact = ::LinkAction::parseAction(&o2, m_page->parentDoc->doc->getCatalog()->getBaseURI());
        if (lact != nullptr) {
            return m_page->convertLinkActionToLink(lact.get(), QRectF());
        }
    }
    return nullptr;
}

QSizeF Page::pageSizeF() const
{
    Page::Orientation orient = orientation();
    if ((Page::Landscape == orient) || (Page::Seascape == orient)) {
        return QSizeF(m_page->page->getCropHeight(), m_page->page->getCropWidth());
    }
    return QSizeF(m_page->page->getCropWidth(), m_page->page->getCropHeight());
}

QSize Page::pageSize() const
{
    return pageSizeF().toSize();
}

Page::Orientation Page::orientation() const
{
    const int rotation = m_page->page->getRotate();
    switch (rotation) {
    case 90:
        return Page::Landscape;
        break;
    case 180:
        return Page::UpsideDown;
        break;
    case 270:
        return Page::Seascape;
        break;
    default:
        return Page::Portrait;
    }
}

void Page::defaultCTM(double *CTM, double dpiX, double dpiY, int rotate, bool upsideDown)
{
    m_page->page->getDefaultCTM(CTM, dpiX, dpiY, rotate, false, upsideDown);
}

std::vector<std::unique_ptr<Link>> Page::links() const
{
    LinkExtractorOutputDev link_dev(m_page);
    m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1);
    return link_dev.links();
}

std::vector<std::unique_ptr<Annotation>> Page::annotations() const
{
    return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, QSet<Annotation::SubType>());
}

std::vector<std::unique_ptr<Annotation>> Page::annotations(const QSet<Annotation::SubType> &subtypes) const
{
    return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, subtypes);
}

void Page::addAnnotation(const Annotation *ann)
{
    AnnotationPrivate::addAnnotationToPage(m_page->page, m_page->parentDoc, ann);
}

void Page::removeAnnotation(const Annotation *ann)
{
    AnnotationPrivate::removeAnnotationFromPage(m_page->page, ann);
}

std::vector<std::unique_ptr<FormField>> Page::formFields() const
{
    std::vector<std::unique_ptr<FormField>> fields;
    ::Page *p = m_page->page;
    const std::unique_ptr<FormPageWidgets> form = p->getFormWidgets();
    int formcount = form->getNumWidgets();
    for (int i = 0; i < formcount; ++i) {
        ::FormWidget *fm = form->getWidget(i);
        std::unique_ptr<FormField> ff;
        switch (fm->getType()) {
        case formButton: {
            ff = std::make_unique<FormFieldButton>(m_page->parentDoc, p, static_cast<FormWidgetButton *>(fm));
        } break;

        case formText: {
            ff = std::make_unique<FormFieldText>(m_page->parentDoc, p, static_cast<FormWidgetText *>(fm));
        } break;

        case formChoice: {
            ff = std::make_unique<FormFieldChoice>(m_page->parentDoc, p, static_cast<FormWidgetChoice *>(fm));
        } break;

        case formSignature: {
            ff = std::make_unique<FormFieldSignature>(m_page->parentDoc, p, static_cast<FormWidgetSignature *>(fm));
        } break;

        default:;
        }

        if (ff) {
            fields.push_back(std::move(ff));
        }
    }

    return fields;
}

double Page::duration() const
{
    return m_page->page->getDuration();
}

QString Page::label() const
{
    std::string label;
    if (!m_page->parentDoc->doc->getCatalog()->indexToLabel(m_page->index, &label)) {
        return QString();
    }

    return UnicodeParsedString(label);
}

int Page::index() const
{
    return m_page->index;
}

}
