Qt6 frontend

Basically a copy of qt5 to qt6

Tested with the tests and the demo, seems to work relatively well

Changes:
 * Changed a few QLinkedList to QVector, we don't need the features that
   QLinkedList provided
 * Adapt code to QByteArray behaviour change in [] with indexes past the
   size
 * Removed a few deprecated functions from our API
 * Use more modern cmake syntax to link against the libraries
 * QDate::toString is gone, use QLocale::toString with a date
 * Use the QDateTime variants of secsSinceEpoch instead of time_t
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 45de21a..61457be 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@
   - echo 'deb-src http://deb.debian.org/debian unstable main' >> /etc/apt/sources.list
   - apt-get update
   - apt-get build-dep --yes --no-install-recommends poppler
-  - apt-get install --yes --no-install-recommends ninja-build libcurl4-openssl-dev git ca-certificates locales libc++-dev libc++abi-dev clang libgtk-3-dev clang-tidy
+  - apt-get install --yes --no-install-recommends ninja-build libcurl4-openssl-dev git ca-certificates locales libc++-dev libc++abi-dev clang libgtk-3-dev clang-tidy wget p7zip-full
   - echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen
   - locale-gen
 
@@ -38,9 +38,13 @@
 build:
   stage: build
   script:
+    - wget http://download.qt.io/online/qtsdkrepository/linux_x64/desktop/qt6_600/qt.qt6.600.gcc_64/6.0.0-0-202006250811qtbase-Linux-RHEL_7_6-GCC-Linux-RHEL_7_6-X86_64.7z
+    - 7z x 6.0.0-0-202006250811qtbase-Linux-RHEL_7_6-GCC-Linux-RHEL_7_6-X86_64.7z
+    - wget http://download.qt.io/online/qtsdkrepository/linux_x64/desktop/qt6_600/qt.qt6.600.gcc_64/6.0.0-0-202006250811icu-linux-Rhel7.2-x64.7z
+    - 7z x 6.0.0-0-202006250811icu-linux-Rhel7.2-x64.7z
     - git clone --branch ${CI_COMMIT_REF_NAME} --depth 1 ${TEST_DATA_URL} test-data || git clone --depth 1 ${UPSTREAM_TEST_DATA_URL} test-data
     - mkdir -p build && cd build
-    - cmake -G Ninja -DTESTDATADIR=$PWD/../test-data ..
+    - cmake -G Ninja -DTESTDATADIR=$PWD/../test-data .. -DCMAKE_PREFIX_PATH=$PWD/../6.0.0/gcc_64/lib/cmake
     - ninja
     - ctest --output-on-failure
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b5ae42c..b3f608e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,6 +47,7 @@
 option(ENABLE_UNSTABLE_API_ABI_HEADERS "Install API/ABI unstable xpdf headers." OFF)
 option(BUILD_GTK_TESTS "Whether to compile the GTK+ test programs." ON)
 option(BUILD_QT5_TESTS "Whether to compile the Qt5 test programs." ON)
+option(BUILD_QT6_TESTS "Whether to compile the Qt6 test programs." ON)
 option(BUILD_CPP_TESTS "Whether to compile the CPP test programs." ON)
 option(ENABLE_SPLASH "Build the Splash graphics backend." ON)
 option(ENABLE_UTILS "Compile poppler command line utils." ON)
@@ -55,6 +56,7 @@
 option(ENABLE_GOBJECT_INTROSPECTION "Whether to generate GObject introspection." ON)
 option(ENABLE_GTK_DOC "Whether to generate glib API documentation." OFF)
 option(ENABLE_QT5 "Compile poppler qt5 wrapper." ON)
+option(ENABLE_QT6 "Compile poppler qt6 wrapper." ON)
 set(ENABLE_LIBOPENJPEG "openjpeg2" CACHE STRING "Use libopenjpeg for JPX streams. Possible values: openjpeg2, unmaintained, none. 'unmaintained' gives you the internal unmaintained decoder. Use at your own risk. 'none' compiles no JPX decoder at all. Default: openjpeg2")
 set(ENABLE_CMS "lcms2" CACHE STRING "Use color management system. Possible values: lcms2, none. 'none' disables color management system.")
 set(ENABLE_DCTDECODER "libjpeg" CACHE STRING "Use libjpeg for DCT streams. Possible values: libjpeg, unmaintained, none. will use libjpeg if available or fail if not. 'unmaintained' gives you the internal unmaintained decoder. Use at your own risk. 'none' compiles no DCT decoder at all. Default: libjpeg")
@@ -160,6 +162,14 @@
   endif()
 endif()
 
+if (ENABLE_QT6)
+  find_package(Qt6 COMPONENTS Core Gui Xml Widgets Test QUIET)
+  if (NOT (Qt6Core_FOUND AND Qt6Gui_FOUND AND Qt6Xml_FOUND AND Qt6Widgets_FOUND AND Qt6Test_FOUND))
+    message("-- Package Qt6Core or Qt6Gui or Qt6Xml or Qt6Widgets or Qt6Test not found")
+    set(ENABLE_QT6 OFF)
+  endif()
+endif()
+
 macro_optional_find_package(Cairo ${CAIRO_VERSION})
 if(CAIRO_FOUND)
   set(HAVE_CAIRO ${CAIRO_FOUND})
@@ -724,6 +734,9 @@
 if(ENABLE_QT5)
   add_subdirectory(qt5)
 endif()
+if(ENABLE_QT6)
+  add_subdirectory(qt6)
+endif()
 if(ENABLE_CPP)
   add_subdirectory(cpp)
 endif()
@@ -767,6 +780,7 @@
 show_end_message_yesno("splash output" ENABLE_SPLASH)
 show_end_message_yesno("cairo output" CAIRO_FOUND)
 show_end_message_yesno("qt5 wrapper" ENABLE_QT5)
+show_end_message_yesno("qt6 wrapper" ENABLE_QT6)
 show_end_message_yesno("glib wrapper" ENABLE_GLIB)
 show_end_message_yesno("  introspection" INTROSPECTION_FOUND)
 show_end_message_yesno("  gtk-doc" ENABLE_GTK_DOC)
diff --git a/qt6/.gitignore b/qt6/.gitignore
new file mode 100644
index 0000000..5540f35
--- /dev/null
+++ b/qt6/.gitignore
@@ -0,0 +1,4 @@
+Makefile
+Makefile.in
+*~
+
diff --git a/qt6/CMakeLists.txt b/qt6/CMakeLists.txt
new file mode 100644
index 0000000..89e612d
--- /dev/null
+++ b/qt6/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(CMAKE_AUTOMOC ON)
+
+set(ENABLE_QT_STRICT_ITERATORS ON CACHE BOOL "Select whether to compile with QT_STRICT_ITERATORS. Leave it ON, unless your Qt lacks support, or your compiler can't do SRA optimization.")
+if(ENABLE_QT_STRICT_ITERATORS)
+  add_definitions(-DQT_STRICT_ITERATORS)
+endif()
+
+add_subdirectory(src)
+
+add_subdirectory(tests)
+add_subdirectory(demos)
diff --git a/qt6/demos/.gitignore b/qt6/demos/.gitignore
new file mode 100644
index 0000000..8560879
--- /dev/null
+++ b/qt6/demos/.gitignore
@@ -0,0 +1,4 @@
+.deps
+.libs
+*moc
+poppler_qt6viewer
diff --git a/qt6/demos/CMakeLists.txt b/qt6/demos/CMakeLists.txt
new file mode 100644
index 0000000..692a271
--- /dev/null
+++ b/qt6/demos/CMakeLists.txt
@@ -0,0 +1,25 @@
+include_directories(
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/../src
+  ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+set(poppler_qt6viewer_SRCS
+  abstractinfodock.cpp
+  documentobserver.cpp
+  embeddedfiles.cpp
+  fonts.cpp
+  info.cpp
+  main_viewer.cpp
+  metadata.cpp
+  navigationtoolbar.cpp
+  optcontent.cpp
+  pageview.cpp
+  permissions.cpp
+  thumbnails.cpp
+  toc.cpp
+  viewer.cpp
+)
+
+poppler_add_test(poppler_qt6viewer BUILD_QT6_TESTS ${poppler_qt6viewer_SRCS})
+target_link_libraries(poppler_qt6viewer poppler-qt6 Qt6::Widgets)
diff --git a/qt6/demos/abstractinfodock.cpp b/qt6/demos/abstractinfodock.cpp
new file mode 100644
index 0000000..8a2e216
--- /dev/null
+++ b/qt6/demos/abstractinfodock.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "fonts.h"
+
+AbstractInfoDock::AbstractInfoDock(QWidget *parent) : QDockWidget(parent), m_filled(false)
+{
+    connect(this, &AbstractInfoDock::visibilityChanged, this, &AbstractInfoDock::slotVisibilityChanged);
+}
+
+AbstractInfoDock::~AbstractInfoDock() { }
+
+void AbstractInfoDock::documentLoaded()
+{
+    if (!isHidden()) {
+        fillInfo();
+        m_filled = true;
+    }
+}
+
+void AbstractInfoDock::documentClosed()
+{
+    m_filled = false;
+}
+
+void AbstractInfoDock::pageChanged(int page)
+{
+    Q_UNUSED(page)
+}
+
+void AbstractInfoDock::slotVisibilityChanged(bool visible)
+{
+    if (visible && document() && !m_filled) {
+        fillInfo();
+        m_filled = true;
+    }
+}
diff --git a/qt6/demos/abstractinfodock.h b/qt6/demos/abstractinfodock.h
new file mode 100644
index 0000000..557fca7
--- /dev/null
+++ b/qt6/demos/abstractinfodock.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef ABSTRACTINFODOCK_H
+#define ABSTRACTINFODOCK_H
+
+#include <QtWidgets/QDockWidget>
+
+#include "documentobserver.h"
+
+class AbstractInfoDock : public QDockWidget, public DocumentObserver
+{
+    Q_OBJECT
+
+public:
+    AbstractInfoDock(QWidget *parent = nullptr);
+    ~AbstractInfoDock() override;
+
+    void documentLoaded() override;
+    void documentClosed() override;
+    void pageChanged(int page) override;
+
+protected:
+    virtual void fillInfo() = 0;
+
+private Q_SLOTS:
+    void slotVisibilityChanged(bool visible);
+
+private:
+    bool m_filled;
+};
+
+#endif
diff --git a/qt6/demos/documentobserver.cpp b/qt6/demos/documentobserver.cpp
new file mode 100644
index 0000000..5bf41ac
--- /dev/null
+++ b/qt6/demos/documentobserver.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "documentobserver.h"
+
+#include "viewer.h"
+
+DocumentObserver::DocumentObserver() : m_viewer(nullptr) { }
+
+DocumentObserver::~DocumentObserver() { }
+
+Poppler::Document *DocumentObserver::document() const
+{
+    return m_viewer->m_doc;
+}
+
+void DocumentObserver::setPage(int page)
+{
+    m_viewer->setPage(page);
+}
+
+int DocumentObserver::page() const
+{
+    return m_viewer->page();
+}
+
+void DocumentObserver::reloadPage()
+{
+    m_viewer->setPage(m_viewer->page());
+}
diff --git a/qt6/demos/documentobserver.h b/qt6/demos/documentobserver.h
new file mode 100644
index 0000000..83ac12e
--- /dev/null
+++ b/qt6/demos/documentobserver.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2018, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef DOCUMENTOBSERVER_H
+#define DOCUMENTOBSERVER_H
+
+class PdfViewer;
+namespace Poppler {
+class Document;
+}
+
+class DocumentObserver
+{
+    friend class PdfViewer;
+
+public:
+    virtual ~DocumentObserver();
+    DocumentObserver(const DocumentObserver &) = delete;
+    DocumentObserver &operator=(const DocumentObserver &) = delete;
+
+    virtual void documentLoaded() = 0;
+    virtual void documentClosed() = 0;
+    virtual void pageChanged(int page) = 0;
+
+protected:
+    DocumentObserver();
+
+    Poppler::Document *document() const;
+    void setPage(int page);
+    int page() const;
+    void reloadPage();
+
+private:
+    PdfViewer *m_viewer;
+};
+
+#endif
diff --git a/qt6/demos/embeddedfiles.cpp b/qt6/demos/embeddedfiles.cpp
new file mode 100644
index 0000000..22df1f6
--- /dev/null
+++ b/qt6/demos/embeddedfiles.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "embeddedfiles.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QTableWidget>
+
+EmbeddedFilesDock::EmbeddedFilesDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_table = new QTableWidget(this);
+    setWidget(m_table);
+    setWindowTitle(tr("Embedded files"));
+    m_table->setColumnCount(6);
+    m_table->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Description") << tr("Size") << tr("Creation date") << tr("Modification date") << tr("Checksum"));
+    m_table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+}
+
+EmbeddedFilesDock::~EmbeddedFilesDock() { }
+
+void EmbeddedFilesDock::fillInfo()
+{
+    m_table->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Description") << tr("Size") << tr("Creation date") << tr("Modification date") << tr("Checksum"));
+    if (!document()->hasEmbeddedFiles()) {
+        m_table->setItem(0, 0, new QTableWidgetItem(tr("No files")));
+        return;
+    }
+
+    const QList<Poppler::EmbeddedFile *> files = document()->embeddedFiles();
+    m_table->setRowCount(files.count());
+    int i = 0;
+    Q_FOREACH (Poppler::EmbeddedFile *file, files) {
+        m_table->setItem(i, 0, new QTableWidgetItem(file->name()));
+        m_table->setItem(i, 1, new QTableWidgetItem(file->description()));
+        m_table->setItem(i, 2, new QTableWidgetItem(QString::number(file->size())));
+        m_table->setItem(i, 3, new QTableWidgetItem(QLocale().toString(file->createDate(), QLocale::ShortFormat)));
+        m_table->setItem(i, 4, new QTableWidgetItem(QLocale().toString(file->modDate(), QLocale::ShortFormat)));
+        const QByteArray checksum = file->checksum();
+        const QString checksumString = !checksum.isEmpty() ? QString::fromLatin1(checksum.toHex()) : QStringLiteral("n/a");
+        m_table->setItem(i, 5, new QTableWidgetItem(checksumString));
+        ++i;
+    }
+}
+
+void EmbeddedFilesDock::documentLoaded()
+{
+    if (document()->pageMode() == Poppler::Document::UseAttach) {
+        show();
+    }
+}
+
+void EmbeddedFilesDock::documentClosed()
+{
+    m_table->clear();
+    m_table->setRowCount(0);
+    AbstractInfoDock::documentClosed();
+}
diff --git a/qt6/demos/embeddedfiles.h b/qt6/demos/embeddedfiles.h
new file mode 100644
index 0000000..7ddb6b1
--- /dev/null
+++ b/qt6/demos/embeddedfiles.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef ATTACHMENTS_H
+#define ATTACHMENTS_H
+
+#include "abstractinfodock.h"
+
+class QTableWidget;
+
+class EmbeddedFilesDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    EmbeddedFilesDock(QWidget *parent = nullptr);
+    ~EmbeddedFilesDock() override;
+
+    void documentLoaded() override;
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private:
+    QTableWidget *m_table;
+};
+
+#endif
diff --git a/qt6/demos/fonts.cpp b/qt6/demos/fonts.cpp
new file mode 100644
index 0000000..9c282cf
--- /dev/null
+++ b/qt6/demos/fonts.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "fonts.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QTableWidget>
+
+static QString yesNoStatement(bool value)
+{
+    return value ? QStringLiteral("yes") : QStringLiteral("no");
+}
+
+FontsDock::FontsDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_table = new QTableWidget(this);
+    setWidget(m_table);
+    setWindowTitle(tr("Fonts"));
+    m_table->setColumnCount(5);
+    m_table->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Type") << tr("Embedded") << tr("Subset") << tr("File"));
+    m_table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+}
+
+FontsDock::~FontsDock() { }
+
+void FontsDock::fillInfo()
+{
+    const QList<Poppler::FontInfo> fonts = document()->fonts();
+    m_table->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Type") << tr("Embedded") << tr("Subset") << tr("File"));
+    m_table->setRowCount(fonts.count());
+    int i = 0;
+    Q_FOREACH (const Poppler::FontInfo &font, fonts) {
+        if (font.name().isNull()) {
+            m_table->setItem(i, 0, new QTableWidgetItem(QStringLiteral("[none]")));
+        } else {
+            m_table->setItem(i, 0, new QTableWidgetItem(font.name()));
+        }
+        m_table->setItem(i, 1, new QTableWidgetItem(font.typeName()));
+        m_table->setItem(i, 2, new QTableWidgetItem(yesNoStatement(font.isEmbedded())));
+        m_table->setItem(i, 3, new QTableWidgetItem(yesNoStatement(font.isSubset())));
+        m_table->setItem(i, 4, new QTableWidgetItem(font.file()));
+        ++i;
+    }
+}
+
+void FontsDock::documentClosed()
+{
+    m_table->clear();
+    m_table->setRowCount(0);
+    AbstractInfoDock::documentClosed();
+}
diff --git a/qt6/demos/fonts.h b/qt6/demos/fonts.h
new file mode 100644
index 0000000..e056e01
--- /dev/null
+++ b/qt6/demos/fonts.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef FONTS_H
+#define FONTS_H
+
+#include "abstractinfodock.h"
+
+class QTableWidget;
+
+class FontsDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    FontsDock(QWidget *parent = nullptr);
+    ~FontsDock() override;
+
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private:
+    QTableWidget *m_table;
+};
+
+#endif
diff --git a/qt6/demos/info.cpp b/qt6/demos/info.cpp
new file mode 100644
index 0000000..e00f5d8
--- /dev/null
+++ b/qt6/demos/info.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "info.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QTableWidget>
+
+InfoDock::InfoDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_table = new QTableWidget(this);
+    setWidget(m_table);
+    setWindowTitle(tr("Information"));
+    m_table->setColumnCount(2);
+    m_table->setHorizontalHeaderLabels(QStringList() << tr("Key") << tr("Value"));
+    m_table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+}
+
+InfoDock::~InfoDock() { }
+
+void InfoDock::fillInfo()
+{
+    QStringList keys = document()->infoKeys();
+    m_table->setHorizontalHeaderLabels(QStringList() << tr("Key") << tr("Value"));
+    m_table->setRowCount(keys.count());
+    QStringList dateKeys;
+    dateKeys << QStringLiteral("CreationDate");
+    dateKeys << QStringLiteral("ModDate");
+    int i = 0;
+    Q_FOREACH (const QString &date, dateKeys) {
+        const int id = keys.indexOf(date);
+        if (id != -1) {
+            m_table->setItem(i, 0, new QTableWidgetItem(date));
+            m_table->setItem(i, 1, new QTableWidgetItem(QLocale().toString(document()->date(date), QLocale::ShortFormat)));
+            ++i;
+            keys.removeAt(id);
+        }
+    }
+    Q_FOREACH (const QString &key, keys) {
+        m_table->setItem(i, 0, new QTableWidgetItem(key));
+        m_table->setItem(i, 1, new QTableWidgetItem(document()->info(key)));
+        ++i;
+    }
+}
+
+void InfoDock::documentClosed()
+{
+    m_table->clear();
+    m_table->setRowCount(0);
+    AbstractInfoDock::documentClosed();
+}
diff --git a/qt6/demos/info.h b/qt6/demos/info.h
new file mode 100644
index 0000000..d964a9f
--- /dev/null
+++ b/qt6/demos/info.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef INFO_H
+#define INFO_H
+
+#include "abstractinfodock.h"
+
+class QTableWidget;
+
+class InfoDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    InfoDock(QWidget *parent = nullptr);
+    ~InfoDock() override;
+
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private:
+    QTableWidget *m_table;
+};
+
+#endif
diff --git a/qt6/demos/main_viewer.cpp b/qt6/demos/main_viewer.cpp
new file mode 100644
index 0000000..ad2f388
--- /dev/null
+++ b/qt6/demos/main_viewer.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "viewer.h"
+
+#include <QtWidgets/QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+    const QStringList args = QCoreApplication::arguments();
+    PdfViewer *viewer = new PdfViewer();
+    viewer->show();
+    if (args.count() > 1) {
+        viewer->loadDocument(args.at(1));
+    }
+    return app.exec();
+}
diff --git a/qt6/demos/metadata.cpp b/qt6/demos/metadata.cpp
new file mode 100644
index 0000000..57460bf
--- /dev/null
+++ b/qt6/demos/metadata.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "metadata.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QTextEdit>
+
+MetadataDock::MetadataDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_edit = new QTextEdit(this);
+    setWidget(m_edit);
+    setWindowTitle(tr("Metadata"));
+    m_edit->setAcceptRichText(false);
+    m_edit->setReadOnly(true);
+}
+
+MetadataDock::~MetadataDock() { }
+
+void MetadataDock::fillInfo()
+{
+    m_edit->setPlainText(document()->metadata());
+}
+
+void MetadataDock::documentClosed()
+{
+    m_edit->clear();
+    AbstractInfoDock::documentClosed();
+}
diff --git a/qt6/demos/metadata.h b/qt6/demos/metadata.h
new file mode 100644
index 0000000..f5dc391
--- /dev/null
+++ b/qt6/demos/metadata.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef METADATA_H
+#define METADATA_H
+
+#include "abstractinfodock.h"
+
+class QTextEdit;
+
+class MetadataDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    MetadataDock(QWidget *parent = nullptr);
+    ~MetadataDock() override;
+
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private:
+    QTextEdit *m_edit;
+};
+
+#endif
diff --git a/qt6/demos/navigationtoolbar.cpp b/qt6/demos/navigationtoolbar.cpp
new file mode 100644
index 0000000..00250a2
--- /dev/null
+++ b/qt6/demos/navigationtoolbar.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013, Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2019, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "navigationtoolbar.h"
+
+#include <poppler-qt6.h>
+
+#include <QAction>
+#include <QComboBox>
+#include <QDebug>
+
+NavigationToolBar::NavigationToolBar(QWidget *parent) : QToolBar(parent)
+{
+    m_firstAct = addAction(tr("First"), this, SLOT(slotGoFirst()));
+    m_prevAct = addAction(tr("Previous"), this, SLOT(slotGoPrev()));
+    m_pageCombo = new QComboBox(this);
+    connect(m_pageCombo, &QComboBox::activated, this, &NavigationToolBar::slotComboActivated);
+    addWidget(m_pageCombo);
+    m_nextAct = addAction(tr("Next"), this, SLOT(slotGoNext()));
+    m_lastAct = addAction(tr("Last"), this, SLOT(slotGoLast()));
+
+    addSeparator();
+
+    m_zoomCombo = new QComboBox(this);
+    m_zoomCombo->setEditable(true);
+    m_zoomCombo->addItem(tr("10%"));
+    m_zoomCombo->addItem(tr("25%"));
+    m_zoomCombo->addItem(tr("33%"));
+    m_zoomCombo->addItem(tr("50%"));
+    m_zoomCombo->addItem(tr("66%"));
+    m_zoomCombo->addItem(tr("75%"));
+    m_zoomCombo->addItem(tr("100%"));
+    m_zoomCombo->addItem(tr("125%"));
+    m_zoomCombo->addItem(tr("150%"));
+    m_zoomCombo->addItem(tr("200%"));
+    m_zoomCombo->addItem(tr("300%"));
+    m_zoomCombo->addItem(tr("400%"));
+    m_zoomCombo->setCurrentIndex(6); // "100%"
+    connect(m_zoomCombo, &QComboBox::activated, this, &NavigationToolBar::slotZoomComboActivated);
+    addWidget(m_zoomCombo);
+
+    m_rotationCombo = new QComboBox(this);
+    // NOTE: \302\260 = degree symbol
+    m_rotationCombo->addItem(tr("0\302\260"));
+    m_rotationCombo->addItem(tr("90\302\260"));
+    m_rotationCombo->addItem(tr("180\302\260"));
+    m_rotationCombo->addItem(tr("270\302\260"));
+    connect(m_rotationCombo, &QComboBox::currentIndexChanged, this, &NavigationToolBar::slotRotationComboChanged);
+    addWidget(m_rotationCombo);
+
+    documentClosed();
+}
+
+NavigationToolBar::~NavigationToolBar() { }
+
+void NavigationToolBar::documentLoaded()
+{
+    const int pageCount = document()->numPages();
+    for (int i = 0; i < pageCount; ++i) {
+        m_pageCombo->addItem(QString::number(i + 1));
+    }
+    m_pageCombo->setEnabled(true);
+}
+
+void NavigationToolBar::documentClosed()
+{
+    m_firstAct->setEnabled(false);
+    m_prevAct->setEnabled(false);
+    m_nextAct->setEnabled(false);
+    m_lastAct->setEnabled(false);
+    m_pageCombo->clear();
+    m_pageCombo->setEnabled(false);
+}
+
+void NavigationToolBar::pageChanged(int page)
+{
+    const int pageCount = document()->numPages();
+    m_firstAct->setEnabled(page > 0);
+    m_prevAct->setEnabled(page > 0);
+    m_nextAct->setEnabled(page < (pageCount - 1));
+    m_lastAct->setEnabled(page < (pageCount - 1));
+    m_pageCombo->setCurrentIndex(page);
+}
+
+void NavigationToolBar::slotGoFirst()
+{
+    setPage(0);
+}
+
+void NavigationToolBar::slotGoPrev()
+{
+    setPage(page() - 1);
+}
+
+void NavigationToolBar::slotGoNext()
+{
+    setPage(page() + 1);
+}
+
+void NavigationToolBar::slotGoLast()
+{
+    setPage(document()->numPages() - 1);
+}
+
+void NavigationToolBar::slotComboActivated(int index)
+{
+    setPage(index);
+}
+
+void NavigationToolBar::slotZoomComboActivated(int index)
+{
+    QString text = m_zoomCombo->currentText();
+    text.remove(QLatin1Char('%'));
+    bool ok = false;
+    int value = text.toInt(&ok);
+    if (ok && value >= 10) {
+        emit zoomChanged(qreal(value) / 100);
+    }
+}
+
+void NavigationToolBar::slotRotationComboChanged(int idx)
+{
+    emit rotationChanged(idx * 90);
+}
diff --git a/qt6/demos/navigationtoolbar.h b/qt6/demos/navigationtoolbar.h
new file mode 100644
index 0000000..7ca7301
--- /dev/null
+++ b/qt6/demos/navigationtoolbar.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013, Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2019, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef NAVIGATIONTOOLBAR_H
+#define NAVIGATIONTOOLBAR_H
+
+#include <QtWidgets/QToolBar>
+
+#include "documentobserver.h"
+
+class QAction;
+class QComboBox;
+
+class NavigationToolBar : public QToolBar, public DocumentObserver
+{
+    Q_OBJECT
+
+public:
+    NavigationToolBar(QWidget *parent = nullptr);
+    ~NavigationToolBar() override;
+
+    void documentLoaded() override;
+    void documentClosed() override;
+    void pageChanged(int page) override;
+
+Q_SIGNALS:
+    void zoomChanged(qreal value); // NOLINT(readability-inconsistent-declaration-parameter-name)
+    void rotationChanged(int rotation); // NOLINT(readability-inconsistent-declaration-parameter-name)
+
+private Q_SLOTS:
+    void slotGoFirst();
+    void slotGoPrev();
+    void slotGoNext();
+    void slotGoLast();
+    void slotComboActivated(int index);
+    void slotZoomComboActivated(int index);
+    void slotRotationComboChanged(int idx);
+
+private:
+    QAction *m_firstAct;
+    QAction *m_prevAct;
+    QComboBox *m_pageCombo;
+    QAction *m_nextAct;
+    QAction *m_lastAct;
+    QComboBox *m_zoomCombo;
+    QComboBox *m_rotationCombo;
+};
+
+#endif
diff --git a/qt6/demos/optcontent.cpp b/qt6/demos/optcontent.cpp
new file mode 100644
index 0000000..39fc3e6
--- /dev/null
+++ b/qt6/demos/optcontent.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "optcontent.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QTreeView>
+
+OptContentDock::OptContentDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_view = new QTreeView(this);
+    setWidget(m_view);
+    setWindowTitle(tr("Optional content"));
+    m_view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+}
+
+OptContentDock::~OptContentDock() { }
+
+void OptContentDock::documentLoaded()
+{
+    AbstractInfoDock::documentLoaded();
+    if (document()->pageMode() == Poppler::Document::UseOC) {
+        show();
+    }
+}
+
+void OptContentDock::fillInfo()
+{
+    if (!document()->hasOptionalContent()) {
+        return;
+    }
+
+    m_view->setModel(document()->optionalContentModel());
+    connect(m_view->model(), &QAbstractItemModel::dataChanged, this, &OptContentDock::reloadImage);
+    m_view->expandToDepth(1);
+}
+
+void OptContentDock::documentClosed()
+{
+    m_view->setModel(nullptr);
+    AbstractInfoDock::documentClosed();
+}
+
+void OptContentDock::reloadImage()
+{
+    reloadPage();
+}
diff --git a/qt6/demos/optcontent.h b/qt6/demos/optcontent.h
new file mode 100644
index 0000000..7764a42
--- /dev/null
+++ b/qt6/demos/optcontent.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef OPTCONTENT_H
+#define OPTCONTENT_H
+
+#include "abstractinfodock.h"
+
+class QTreeView;
+
+class OptContentDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    OptContentDock(QWidget *parent = nullptr);
+    ~OptContentDock() override;
+
+    void documentLoaded() override;
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private Q_SLOTS:
+    void reloadImage();
+
+private:
+    QTreeView *m_view;
+};
+
+#endif
diff --git a/qt6/demos/pageview.cpp b/qt6/demos/pageview.cpp
new file mode 100644
index 0000000..1287b2c
--- /dev/null
+++ b/qt6/demos/pageview.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013, Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2017, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "pageview.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QApplication>
+#include <QtGui/QImage>
+#include <QtWidgets/QLabel>
+#include <QtGui/QPixmap>
+#include <QDebug>
+
+PageView::PageView(QWidget *parent) : QScrollArea(parent), m_zoom(1.0), m_rotation(0), m_dpiX(QApplication::desktop()->physicalDpiX()), m_dpiY(QApplication::desktop()->physicalDpiY())
+{
+    m_imageLabel = new QLabel(this);
+    m_imageLabel->resize(0, 0);
+    setWidget(m_imageLabel);
+}
+
+PageView::~PageView() { }
+
+void PageView::documentLoaded() { }
+
+void PageView::documentClosed()
+{
+    m_imageLabel->clear();
+    m_imageLabel->resize(0, 0);
+}
+
+void PageView::pageChanged(int page)
+{
+    Poppler::Page *popplerPage = document()->page(page);
+
+    if (!popplerPage) {
+        qDebug() << "Page" << page << "is malformed";
+        return;
+    }
+    const double resX = m_dpiX * m_zoom;
+    const double resY = m_dpiY * m_zoom;
+
+    Poppler::Page::Rotation rot;
+    if (m_rotation == 0)
+        rot = Poppler::Page::Rotate0;
+    else if (m_rotation == 90)
+        rot = Poppler::Page::Rotate90;
+    else if (m_rotation == 180)
+        rot = Poppler::Page::Rotate180;
+    else // m_rotation == 270
+        rot = Poppler::Page::Rotate270;
+
+    QImage image = popplerPage->renderToImage(resX, resY, -1, -1, -1, -1, rot);
+    if (!image.isNull()) {
+        m_imageLabel->resize(image.size());
+        m_imageLabel->setPixmap(QPixmap::fromImage(image));
+    } else {
+        m_imageLabel->resize(0, 0);
+        m_imageLabel->setPixmap(QPixmap());
+    }
+    delete popplerPage;
+}
+
+void PageView::slotZoomChanged(qreal value)
+{
+    m_zoom = value;
+    if (!document()) {
+        return;
+    }
+    reloadPage();
+}
+
+void PageView::slotRotationChanged(int value)
+{
+    m_rotation = value;
+    if (!document()) {
+        return;
+    }
+    reloadPage();
+}
diff --git a/qt6/demos/pageview.h b/qt6/demos/pageview.h
new file mode 100644
index 0000000..9149ec0
--- /dev/null
+++ b/qt6/demos/pageview.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013, Fabio D'Urso <fabiodurso@hotmail.it>
+ *
+ * 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.
+ */
+
+#ifndef PAGEVIEW_H
+#define PAGEVIEW_H
+
+#include <QtWidgets/QScrollArea>
+
+#include "documentobserver.h"
+
+class QLabel;
+
+class PageView : public QScrollArea, public DocumentObserver
+{
+    Q_OBJECT
+
+public:
+    PageView(QWidget *parent = nullptr);
+    ~PageView() override;
+
+    void documentLoaded() override;
+    void documentClosed() override;
+    void pageChanged(int page) override;
+
+public Q_SLOTS:
+    void slotZoomChanged(qreal value);
+    void slotRotationChanged(int value);
+
+private:
+    QLabel *m_imageLabel;
+    qreal m_zoom;
+    int m_rotation;
+    int m_dpiX;
+    int m_dpiY;
+};
+
+#endif
diff --git a/qt6/demos/permissions.cpp b/qt6/demos/permissions.cpp
new file mode 100644
index 0000000..9c0ed93
--- /dev/null
+++ b/qt6/demos/permissions.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ *
+ * 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 "permissions.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QListWidget>
+
+PermissionsDock::PermissionsDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_table = new QListWidget(this);
+    setWidget(m_table);
+    setWindowTitle(tr("Permissions"));
+    m_table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+}
+
+PermissionsDock::~PermissionsDock() { }
+
+void PermissionsDock::fillInfo()
+{
+#define ADD_ROW(title, function)                                                                                                                                                                                                               \
+    do {                                                                                                                                                                                                                                       \
+        QListWidgetItem *item = new QListWidgetItem();                                                                                                                                                                                         \
+        item->setFlags(item->flags() & ~Qt::ItemIsEnabled);                                                                                                                                                                                    \
+        item->setText(QStringLiteral(title));                                                                                                                                                                                                  \
+        item->setCheckState(document()->function() ? Qt::Checked : Qt::Unchecked);                                                                                                                                                             \
+        m_table->addItem(item);                                                                                                                                                                                                                \
+    } while (0)
+    ADD_ROW("Print", okToPrint);
+    ADD_ROW("PrintHiRes", okToPrintHighRes);
+    ADD_ROW("Change", okToChange);
+    ADD_ROW("Copy", okToCopy);
+    ADD_ROW("Add Notes", okToAddNotes);
+    ADD_ROW("Fill Forms", okToFillForm);
+    ADD_ROW("Create Forms", okToCreateFormFields);
+    ADD_ROW("Extract for accessibility", okToExtractForAccessibility);
+    ADD_ROW("Assemble", okToAssemble);
+#undef ADD_ROW
+}
+
+void PermissionsDock::documentClosed()
+{
+    m_table->clear();
+    AbstractInfoDock::documentClosed();
+}
diff --git a/qt6/demos/permissions.h b/qt6/demos/permissions.h
new file mode 100644
index 0000000..9c5072b
--- /dev/null
+++ b/qt6/demos/permissions.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef PERMISSIONS_H
+#define PERMISSIONS_H
+
+#include "abstractinfodock.h"
+
+class QListWidget;
+
+class PermissionsDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    PermissionsDock(QWidget *parent = nullptr);
+    ~PermissionsDock() override;
+
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private:
+    QListWidget *m_table;
+};
+
+#endif
diff --git a/qt6/demos/thumbnails.cpp b/qt6/demos/thumbnails.cpp
new file mode 100644
index 0000000..4aca61e
--- /dev/null
+++ b/qt6/demos/thumbnails.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009, Shawn Rutledge <shawn.t.rutledge@gmail.com>
+ * Copyright (C) 2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2020, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "thumbnails.h"
+
+#include <poppler-qt6.h>
+
+#include <QtWidgets/QListWidget>
+
+static const int PageRole = Qt::UserRole + 1;
+
+ThumbnailsDock::ThumbnailsDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_list = new QListWidget(this);
+    setWidget(m_list);
+    setWindowTitle(tr("Thumbnails"));
+    m_list->setViewMode(QListView::ListMode);
+    m_list->setMovement(QListView::Static);
+    m_list->setVerticalScrollMode(QListView::ScrollPerPixel);
+    connect(m_list, &QListWidget::itemActivated, this, &ThumbnailsDock::slotItemActivated);
+}
+
+ThumbnailsDock::~ThumbnailsDock() { }
+
+void ThumbnailsDock::fillInfo()
+{
+    const int num = document()->numPages();
+    QSize maxSize;
+    for (int i = 0; i < num; ++i) {
+        const Poppler::Page *page = document()->page(i);
+        const QImage image = page ? page->thumbnail() : QImage();
+        if (!image.isNull()) {
+            QListWidgetItem *item = new QListWidgetItem();
+            item->setText(QString::number(i + 1));
+            item->setData(Qt::DecorationRole, QPixmap::fromImage(image));
+            item->setData(PageRole, i);
+            m_list->addItem(item);
+            maxSize.setWidth(qMax(maxSize.width(), image.width()));
+            maxSize.setHeight(qMax(maxSize.height(), image.height()));
+        }
+        delete page;
+    }
+    if (num > 0) {
+        m_list->setGridSize(maxSize);
+        m_list->setIconSize(maxSize);
+    }
+}
+
+void ThumbnailsDock::documentClosed()
+{
+    m_list->clear();
+    AbstractInfoDock::documentClosed();
+}
+
+void ThumbnailsDock::slotItemActivated(QListWidgetItem *item)
+{
+    if (!item) {
+        return;
+    }
+
+    setPage(item->data(PageRole).toInt());
+}
diff --git a/qt6/demos/thumbnails.h b/qt6/demos/thumbnails.h
new file mode 100644
index 0000000..3eda6cf
--- /dev/null
+++ b/qt6/demos/thumbnails.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009, Shawn Rutledge <shawn.t.rutledge@gmail.com>
+ * Copyright (C) 2009, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef THUMBNAILS_H
+#define THUMBNAILS_H
+
+#include "abstractinfodock.h"
+
+class QListWidget;
+class QListWidgetItem;
+
+class ThumbnailsDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    ThumbnailsDock(QWidget *parent = nullptr);
+    ~ThumbnailsDock() override;
+
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+
+private Q_SLOTS:
+    void slotItemActivated(QListWidgetItem *item);
+
+private:
+    QListWidget *m_list;
+};
+
+#endif
diff --git a/qt6/demos/toc.cpp b/qt6/demos/toc.cpp
new file mode 100644
index 0000000..7cff1a6
--- /dev/null
+++ b/qt6/demos/toc.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2018, Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "toc.h"
+
+#include <poppler-qt6.h>
+
+#include <QtGui/QStandardItemModel>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QTreeWidget>
+
+#include <QDebug>
+
+struct Node
+{
+    Node(Poppler::OutlineItem &&item, int row, Node *parent) : m_row(row), m_parent(parent), m_item(std::move(item)) { }
+
+    ~Node() { qDeleteAll(m_children); }
+
+    Node(const Node &) = delete;
+    Node &operator=(const Node &) = delete;
+
+    int m_row;
+    Node *m_parent;
+    Poppler::OutlineItem m_item;
+    QVector<Node *> m_children;
+};
+
+class TocModel : public QAbstractItemModel
+{
+    Q_OBJECT
+public:
+    TocModel(QVector<Poppler::OutlineItem> &&items, QObject *parent) : QAbstractItemModel(parent)
+    {
+        for (int i = 0; i < items.count(); ++i) {
+            m_topItems << new Node(std::move(items[i]), i, nullptr);
+        }
+    }
+
+    ~TocModel() override { qDeleteAll(m_topItems); }
+
+    QVariant data(const QModelIndex &index, int role) const override
+    {
+        if (role != Qt::DisplayRole)
+            return {};
+
+        Node *n = static_cast<Node *>(index.internalPointer());
+        return n->m_item.name();
+    }
+
+    QModelIndex index(int row, int column, const QModelIndex &parent) const override
+    {
+        Node *p = static_cast<Node *>(parent.internalPointer());
+        const QVector<Node *> &children = p ? p->m_children : m_topItems;
+
+        return createIndex(row, column, children[row]);
+    }
+
+    QModelIndex parent(const QModelIndex &child) const override
+    {
+        Node *n = static_cast<Node *>(child.internalPointer());
+        if (n->m_parent == nullptr)
+            return QModelIndex();
+        else
+            return createIndex(n->m_parent->m_row, 0, n->m_parent);
+    }
+
+    int rowCount(const QModelIndex &parent) const override
+    {
+        Node *n = static_cast<Node *>(parent.internalPointer());
+        if (!n) {
+            return m_topItems.count();
+        }
+
+        if (n->m_children.isEmpty() && !n->m_item.isNull()) {
+            QVector<Poppler::OutlineItem> items = n->m_item.children();
+            for (int i = 0; i < items.count(); ++i) {
+                n->m_children << new Node(std::move(items[i]), i, n);
+            }
+        }
+
+        return n->m_children.count();
+    }
+
+    bool hasChildren(const QModelIndex &parent) const override
+    {
+        Node *n = static_cast<Node *>(parent.internalPointer());
+        if (!n)
+            return true;
+
+        return n->m_item.hasChildren();
+    }
+
+    int columnCount(const QModelIndex &parent) const override { return 1; }
+
+private:
+    QVector<Node *> m_topItems;
+};
+
+TocDock::TocDock(QWidget *parent) : AbstractInfoDock(parent)
+{
+    m_tree = new QTreeView(this);
+    setWidget(m_tree);
+    m_tree->setAlternatingRowColors(true);
+    m_tree->header()->hide();
+    setWindowTitle(tr("TOC"));
+    m_tree->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+}
+
+TocDock::~TocDock() { }
+
+void TocDock::expandItemModels(const QModelIndex &parent)
+{
+    TocModel *model = static_cast<TocModel *>(m_tree->model());
+    for (int i = 0; i < model->rowCount(parent); ++i) {
+        QModelIndex index = model->index(i, 0, parent);
+        Node *n = static_cast<Node *>(index.internalPointer());
+        if (n->m_item.isOpen()) {
+            m_tree->setExpanded(index, true);
+            expandItemModels(index);
+        }
+    }
+}
+
+void TocDock::fillInfo()
+{
+    auto outline = document()->outline();
+    if (!outline.isEmpty()) {
+        TocModel *model = new TocModel(std::move(outline), this);
+        m_tree->setModel(model);
+
+        expandItemModels(QModelIndex());
+    } else {
+        QStandardItemModel *model = new QStandardItemModel(this);
+        QStandardItem *item = new QStandardItem(tr("No TOC"));
+        item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+        model->appendRow(item);
+        m_tree->setModel(model);
+    }
+}
+
+void TocDock::documentClosed()
+{
+    m_tree->setModel(nullptr);
+    AbstractInfoDock::documentClosed();
+}
+
+#include "toc.moc"
diff --git a/qt6/demos/toc.h b/qt6/demos/toc.h
new file mode 100644
index 0000000..b6c77ba
--- /dev/null
+++ b/qt6/demos/toc.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2019, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef TOC_H
+#define TOC_H
+
+#include "abstractinfodock.h"
+
+class QTreeView;
+
+class TocDock : public AbstractInfoDock
+{
+    Q_OBJECT
+
+public:
+    TocDock(QWidget *parent = nullptr);
+    ~TocDock() override;
+
+    void documentClosed() override;
+
+protected:
+    void fillInfo() override;
+    void expandItemModels(const QModelIndex &parent);
+
+private:
+    QTreeView *m_tree;
+};
+
+#endif
diff --git a/qt6/demos/viewer.cpp b/qt6/demos/viewer.cpp
new file mode 100644
index 0000000..ff850d6
--- /dev/null
+++ b/qt6/demos/viewer.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2008-2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, 2019, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2009, Shawn Rutledge <shawn.t.rutledge@gmail.com>
+ * Copyright (C) 2013, Fabio D'Urso <fabiodurso@hotmail.it>
+ *
+ * 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 "viewer.h"
+
+#include "embeddedfiles.h"
+#include "fonts.h"
+#include "info.h"
+#include "metadata.h"
+#include "navigationtoolbar.h"
+#include "optcontent.h"
+#include "pageview.h"
+#include "permissions.h"
+#include "thumbnails.h"
+#include "toc.h"
+
+#include <poppler-qt6.h>
+
+#include <QAction>
+#include <QActionGroup>
+#include <QApplication>
+#include <QDir>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+
+PdfViewer::PdfViewer(QWidget *parent) : QMainWindow(parent), m_currentPage(0), m_doc(nullptr)
+{
+    setWindowTitle(tr("Poppler-Qt6 Demo"));
+
+    // setup the menus
+    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
+    m_fileOpenAct = fileMenu->addAction(tr("&Open"), this, &PdfViewer::slotOpenFile);
+    m_fileOpenAct->setShortcut(Qt::CTRL + Qt::Key_O);
+    fileMenu->addSeparator();
+    m_fileSaveCopyAct = fileMenu->addAction(tr("&Save a Copy..."), this, &PdfViewer::slotSaveCopy);
+    m_fileSaveCopyAct->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_S);
+    m_fileSaveCopyAct->setEnabled(false);
+    fileMenu->addSeparator();
+    QAction *act = fileMenu->addAction(tr("&Quit"), qApp, &QApplication::closeAllWindows);
+    act->setShortcut(Qt::CTRL + Qt::Key_Q);
+
+    QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
+
+    QMenu *settingsMenu = menuBar()->addMenu(tr("&Settings"));
+    m_settingsTextAAAct = settingsMenu->addAction(tr("Text Antialias"));
+    m_settingsTextAAAct->setCheckable(true);
+    connect(m_settingsTextAAAct, &QAction::toggled, this, &PdfViewer::slotToggleTextAA);
+    m_settingsGfxAAAct = settingsMenu->addAction(tr("Graphics Antialias"));
+    m_settingsGfxAAAct->setCheckable(true);
+    connect(m_settingsGfxAAAct, &QAction::toggled, this, &PdfViewer::slotToggleGfxAA);
+    QMenu *settingsRenderMenu = settingsMenu->addMenu(tr("Render Backend"));
+    m_settingsRenderBackendGrp = new QActionGroup(settingsRenderMenu);
+    m_settingsRenderBackendGrp->setExclusive(true);
+    act = settingsRenderMenu->addAction(tr("Splash"));
+    act->setCheckable(true);
+    act->setChecked(true);
+    act->setData(QVariant::fromValue(0));
+    m_settingsRenderBackendGrp->addAction(act);
+    act = settingsRenderMenu->addAction(tr("Arthur"));
+    act->setCheckable(true);
+    act->setData(QVariant::fromValue(1));
+    m_settingsRenderBackendGrp->addAction(act);
+    connect(m_settingsRenderBackendGrp, &QActionGroup::triggered, this, &PdfViewer::slotRenderBackend);
+
+    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
+    act = helpMenu->addAction(tr("&About"), this, &PdfViewer::slotAbout);
+    act = helpMenu->addAction(tr("About &Qt"), this, &PdfViewer::slotAboutQt);
+
+    NavigationToolBar *navbar = new NavigationToolBar(this);
+    addToolBar(navbar);
+    m_observers.append(navbar);
+
+    PageView *view = new PageView(this);
+    setCentralWidget(view);
+    m_observers.append(view);
+
+    InfoDock *infoDock = new InfoDock(this);
+    addDockWidget(Qt::LeftDockWidgetArea, infoDock);
+    infoDock->hide();
+    viewMenu->addAction(infoDock->toggleViewAction());
+    m_observers.append(infoDock);
+
+    TocDock *tocDock = new TocDock(this);
+    addDockWidget(Qt::LeftDockWidgetArea, tocDock);
+    tocDock->hide();
+    viewMenu->addAction(tocDock->toggleViewAction());
+    m_observers.append(tocDock);
+
+    FontsDock *fontsDock = new FontsDock(this);
+    addDockWidget(Qt::LeftDockWidgetArea, fontsDock);
+    fontsDock->hide();
+    viewMenu->addAction(fontsDock->toggleViewAction());
+    m_observers.append(fontsDock);
+
+    PermissionsDock *permissionsDock = new PermissionsDock(this);
+    addDockWidget(Qt::LeftDockWidgetArea, permissionsDock);
+    permissionsDock->hide();
+    viewMenu->addAction(permissionsDock->toggleViewAction());
+    m_observers.append(permissionsDock);
+
+    ThumbnailsDock *thumbnailsDock = new ThumbnailsDock(this);
+    addDockWidget(Qt::LeftDockWidgetArea, thumbnailsDock);
+    thumbnailsDock->hide();
+    viewMenu->addAction(thumbnailsDock->toggleViewAction());
+    m_observers.append(thumbnailsDock);
+
+    EmbeddedFilesDock *embfilesDock = new EmbeddedFilesDock(this);
+    addDockWidget(Qt::BottomDockWidgetArea, embfilesDock);
+    embfilesDock->hide();
+    viewMenu->addAction(embfilesDock->toggleViewAction());
+    m_observers.append(embfilesDock);
+
+    MetadataDock *metadataDock = new MetadataDock(this);
+    addDockWidget(Qt::BottomDockWidgetArea, metadataDock);
+    metadataDock->hide();
+    viewMenu->addAction(metadataDock->toggleViewAction());
+    m_observers.append(metadataDock);
+
+    OptContentDock *optContentDock = new OptContentDock(this);
+    addDockWidget(Qt::LeftDockWidgetArea, optContentDock);
+    optContentDock->hide();
+    viewMenu->addAction(optContentDock->toggleViewAction());
+    m_observers.append(optContentDock);
+
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->m_viewer = this;
+    }
+
+    connect(navbar, &NavigationToolBar::zoomChanged, view, &PageView::slotZoomChanged);
+    connect(navbar, &NavigationToolBar::rotationChanged, view, &PageView::slotRotationChanged);
+
+    // activate AA by default
+    m_settingsTextAAAct->setChecked(true);
+    m_settingsGfxAAAct->setChecked(true);
+}
+
+PdfViewer::~PdfViewer()
+{
+    closeDocument();
+}
+
+QSize PdfViewer::sizeHint() const
+{
+    return QSize(500, 600);
+}
+
+void PdfViewer::loadDocument(const QString &file)
+{
+    Poppler::Document *newdoc = Poppler::Document::load(file);
+    if (!newdoc) {
+        QMessageBox msgbox(QMessageBox::Critical, tr("Open Error"), tr("Cannot open:\n") + file, QMessageBox::Ok, this);
+        msgbox.exec();
+        return;
+    }
+
+    while (newdoc->isLocked()) {
+        bool ok = true;
+        QString password = QInputDialog::getText(this, tr("Document Password"), tr("Please insert the password of the document:"), QLineEdit::Password, QString(), &ok);
+        if (!ok) {
+            delete newdoc;
+            return;
+        }
+        newdoc->unlock(password.toLatin1(), password.toLatin1());
+    }
+
+    closeDocument();
+
+    m_doc = newdoc;
+
+    m_doc->setRenderHint(Poppler::Document::TextAntialiasing, m_settingsTextAAAct->isChecked());
+    m_doc->setRenderHint(Poppler::Document::Antialiasing, m_settingsGfxAAAct->isChecked());
+    m_doc->setRenderBackend((Poppler::Document::RenderBackend)m_settingsRenderBackendGrp->checkedAction()->data().toInt());
+
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->documentLoaded();
+        obs->pageChanged(0);
+    }
+
+    m_fileSaveCopyAct->setEnabled(true);
+}
+
+void PdfViewer::closeDocument()
+{
+    if (!m_doc) {
+        return;
+    }
+
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->documentClosed();
+    }
+
+    m_currentPage = 0;
+    delete m_doc;
+    m_doc = nullptr;
+
+    m_fileSaveCopyAct->setEnabled(false);
+}
+
+void PdfViewer::slotOpenFile()
+{
+    QString fileName = QFileDialog::getOpenFileName(this, tr("Open PDF Document"), QDir::homePath(), tr("PDF Documents (*.pdf)"));
+    if (fileName.isEmpty()) {
+        return;
+    }
+
+    loadDocument(fileName);
+}
+
+void PdfViewer::slotSaveCopy()
+{
+    if (!m_doc) {
+        return;
+    }
+
+    QString fileName = QFileDialog::getSaveFileName(this, tr("Save Copy"), QDir::homePath(), tr("PDF Documents (*.pdf)"));
+    if (fileName.isEmpty()) {
+        return;
+    }
+
+    Poppler::PDFConverter *converter = m_doc->pdfConverter();
+    converter->setOutputFileName(fileName);
+    converter->setPDFOptions(converter->pdfOptions() & ~Poppler::PDFConverter::WithChanges);
+    if (!converter->convert()) {
+        QMessageBox msgbox(QMessageBox::Critical, tr("Save Error"), tr("Cannot export to:\n%1").arg(fileName), QMessageBox::Ok, this);
+    }
+    delete converter;
+}
+
+void PdfViewer::slotAbout()
+{
+    QMessageBox::about(this, tr("About Poppler-Qt6 Demo"), tr("This is a demo of the Poppler-Qt6 library."));
+}
+
+void PdfViewer::slotAboutQt()
+{
+    QMessageBox::aboutQt(this);
+}
+
+void PdfViewer::slotToggleTextAA(bool value)
+{
+    if (!m_doc) {
+        return;
+    }
+
+    m_doc->setRenderHint(Poppler::Document::TextAntialiasing, value);
+
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->pageChanged(m_currentPage);
+    }
+}
+
+void PdfViewer::slotToggleGfxAA(bool value)
+{
+    if (!m_doc) {
+        return;
+    }
+
+    m_doc->setRenderHint(Poppler::Document::Antialiasing, value);
+
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->pageChanged(m_currentPage);
+    }
+}
+
+void PdfViewer::slotRenderBackend(QAction *act)
+{
+    if (!m_doc || !act) {
+        return;
+    }
+
+    m_doc->setRenderBackend((Poppler::Document::RenderBackend)act->data().toInt());
+
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->pageChanged(m_currentPage);
+    }
+}
+
+void PdfViewer::setPage(int page)
+{
+    Q_FOREACH (DocumentObserver *obs, m_observers) {
+        obs->pageChanged(page);
+    }
+
+    m_currentPage = page;
+}
+
+int PdfViewer::page() const
+{
+    return m_currentPage;
+}
diff --git a/qt6/demos/viewer.h b/qt6/demos/viewer.h
new file mode 100644
index 0000000..105b1bb
--- /dev/null
+++ b/qt6/demos/viewer.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef PDFVIEWER_H
+#define PDFVIEWER_H
+
+#include <QtWidgets/QMainWindow>
+
+class QAction;
+class QActionGroup;
+class QLabel;
+class DocumentObserver;
+namespace Poppler {
+class Document;
+}
+
+class PdfViewer : public QMainWindow
+{
+    Q_OBJECT
+
+    friend class DocumentObserver;
+
+public:
+    PdfViewer(QWidget *parent = nullptr);
+    ~PdfViewer() override;
+
+    QSize sizeHint() const override;
+
+    void loadDocument(const QString &file);
+    void closeDocument();
+
+private Q_SLOTS:
+    void slotOpenFile();
+    void slotSaveCopy();
+    void slotAbout();
+    void slotAboutQt();
+    void slotToggleTextAA(bool value);
+    void slotToggleGfxAA(bool value);
+    void slotRenderBackend(QAction *act);
+
+private:
+    void setPage(int page);
+    int page() const;
+
+    int m_currentPage;
+
+    QAction *m_fileOpenAct;
+    QAction *m_fileSaveCopyAct;
+    QAction *m_settingsTextAAAct;
+    QAction *m_settingsGfxAAAct;
+    QActionGroup *m_settingsRenderBackendGrp;
+
+    QList<DocumentObserver *> m_observers;
+
+    Poppler::Document *m_doc;
+};
+
+#endif
diff --git a/qt6/src/.gitignore b/qt6/src/.gitignore
new file mode 100644
index 0000000..3d124dd
--- /dev/null
+++ b/qt6/src/.gitignore
@@ -0,0 +1,9 @@
+.deps
+.libs
+*.la
+*.lo
+Makefile
+Makefile.in
+APIDOCS-html
+APIDOCS-latex
+*.moc
diff --git a/qt6/src/ArthurOutputDev.cc b/qt6/src/ArthurOutputDev.cc
new file mode 100644
index 0000000..a5a856b
--- /dev/null
+++ b/qt6/src/ArthurOutputDev.cc
@@ -0,0 +1,1155 @@
+//========================================================================
+//
+// ArthurOutputDev.cc
+//
+// Copyright 2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2005 Brad Hards <bradh@frogmouth.net>
+// Copyright (C) 2005-2009, 2011, 2012, 2014, 2015, 2018, 2019 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2008, 2010 Pino Toscano <pino@kde.org>
+// Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
+// Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
+// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org>
+// Copyright (C) 2013 Mihai Niculescu <q.quark@gmail.com>
+// Copyright (C) 2017, 2018, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
+// 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) 2018 Adam Reichold <adam.reichold@t-online.de>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+#include <config.h>
+
+#include <cstring>
+#include <cmath>
+
+#include <array>
+
+#include "goo/gfile.h"
+#include "GlobalParams.h"
+#include "Error.h"
+#include "Object.h"
+#include "GfxState.h"
+#include "GfxFont.h"
+#include "Link.h"
+#include "FontEncodingTables.h"
+#include <fofi/FoFiTrueType.h>
+#include <fofi/FoFiType1C.h>
+#include "ArthurOutputDev.h"
+#include "Page.h"
+#include "Gfx.h"
+#include "PDFDoc.h"
+
+#include <QtCore/QtDebug>
+#include <QRawFont>
+#include <QGlyphRun>
+#include <QtGui/QPainterPath>
+#include <QPicture>
+
+class ArthurType3Font
+{
+public:
+    ArthurType3Font(PDFDoc *doc, Gfx8BitFont *font);
+
+    const QPicture &getGlyph(int gid) const;
+
+private:
+    PDFDoc *m_doc;
+    Gfx8BitFont *m_font;
+
+    mutable std::vector<std::unique_ptr<QPicture>> glyphs;
+
+public:
+    std::vector<int> codeToGID;
+};
+
+ArthurType3Font::ArthurType3Font(PDFDoc *doc, Gfx8BitFont *font) : m_doc(doc), m_font(font)
+{
+    char *name;
+    const Dict *charProcs = font->getCharProcs();
+
+    // Storage for the rendered glyphs
+    glyphs.resize(charProcs->getLength());
+
+    // Compute the code-to-GID map
+    char **enc = font->getEncoding();
+
+    codeToGID.resize(256);
+
+    for (int i = 0; i < 256; ++i) {
+        codeToGID[i] = 0;
+        if (charProcs && (name = enc[i])) {
+            for (int j = 0; j < charProcs->getLength(); j++) {
+                if (strcmp(name, charProcs->getKey(j)) == 0) {
+                    codeToGID[i] = j;
+                }
+            }
+        }
+    }
+}
+
+const QPicture &ArthurType3Font::getGlyph(int gid) const
+{
+    if (!glyphs[gid]) {
+
+        // Glyph has not been rendered before: render it now
+
+        // Smallest box that contains all the glyphs from this font
+        const double *fontBBox = m_font->getFontBBox();
+        PDFRectangle box(fontBBox[0], fontBBox[1], fontBBox[2], fontBBox[3]);
+
+        Dict *resDict = m_font->getResources();
+
+        QPainter glyphPainter;
+        glyphs[gid] = std::make_unique<QPicture>();
+        glyphPainter.begin(glyphs[gid].get());
+        auto output_dev = std::make_unique<ArthurOutputDev>(&glyphPainter);
+
+        auto gfx = std::make_unique<Gfx>(m_doc, output_dev.get(), resDict,
+                                         &box, // pagebox
+                                         nullptr // cropBox
+        );
+
+        output_dev->startDoc(m_doc);
+
+        output_dev->startPage(1, gfx->getState(), gfx->getXRef());
+
+        const Dict *charProcs = m_font->getCharProcs();
+        Object charProc = charProcs->getVal(gid);
+        gfx->display(&charProc);
+
+        glyphPainter.end();
+    }
+
+    return *glyphs[gid];
+}
+
+//------------------------------------------------------------------------
+// ArthurOutputDev
+//------------------------------------------------------------------------
+
+ArthurOutputDev::ArthurOutputDev(QPainter *painter) : m_lastTransparencyGroupPicture(nullptr), m_hintingPreference(QFont::PreferDefaultHinting)
+{
+    m_painter.push(painter);
+    m_currentBrush = QBrush(Qt::SolidPattern);
+
+    auto error = FT_Init_FreeType(&m_ftLibrary);
+    if (error) {
+        qCritical() << "An error occurred will initializing the FreeType library";
+    }
+
+    // as of FT 2.1.8, CID fonts are indexed by CID instead of GID
+    FT_Int major, minor, patch;
+    FT_Library_Version(m_ftLibrary, &major, &minor, &patch);
+    m_useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7)));
+}
+
+ArthurOutputDev::~ArthurOutputDev()
+{
+    for (auto &codeToGID : m_codeToGIDCache) {
+        gfree(const_cast<int *>(codeToGID.second));
+    }
+
+    FT_Done_FreeType(m_ftLibrary);
+}
+
+void ArthurOutputDev::startDoc(PDFDoc *doc)
+{
+    xref = doc->getXRef();
+    m_doc = doc;
+
+    for (auto &codeToGID : m_codeToGIDCache) {
+        gfree(const_cast<int *>(codeToGID.second));
+    }
+    m_codeToGIDCache.clear();
+}
+
+void ArthurOutputDev::startPage(int pageNum, GfxState *state, XRef *) { }
+
+void ArthurOutputDev::endPage() { }
+
+void ArthurOutputDev::saveState(GfxState *state)
+{
+    m_currentPenStack.push(m_currentPen);
+    m_currentBrushStack.push(m_currentBrush);
+    m_rawFontStack.push(m_rawFont);
+    m_type3FontStack.push(m_currentType3Font);
+    m_codeToGIDStack.push(m_codeToGID);
+
+    m_painter.top()->save();
+}
+
+void ArthurOutputDev::restoreState(GfxState *state)
+{
+    m_painter.top()->restore();
+
+    m_codeToGID = m_codeToGIDStack.top();
+    m_codeToGIDStack.pop();
+    m_rawFont = m_rawFontStack.top();
+    m_rawFontStack.pop();
+    m_currentType3Font = m_type3FontStack.top();
+    m_type3FontStack.pop();
+    m_currentBrush = m_currentBrushStack.top();
+    m_currentBrushStack.pop();
+    m_currentPen = m_currentPenStack.top();
+    m_currentPenStack.pop();
+}
+
+void ArthurOutputDev::updateAll(GfxState *state)
+{
+    OutputDev::updateAll(state);
+    m_needFontUpdate = true;
+}
+
+// Set CTM (Current Transformation Matrix) to a fixed matrix
+void ArthurOutputDev::setDefaultCTM(const double *ctm)
+{
+    m_painter.top()->setTransform(QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]));
+}
+
+// Update the CTM (Current Transformation Matrix), i.e., compose the old
+// CTM with a new matrix.
+void ArthurOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32)
+{
+    updateLineDash(state);
+    updateLineJoin(state);
+    updateLineCap(state);
+    updateLineWidth(state);
+
+    QTransform update(m11, m12, m21, m22, m31, m32);
+
+    // We could also set (rather than update) the painter transformation to state->getCMT();
+    m_painter.top()->setTransform(update, true);
+}
+
+void ArthurOutputDev::updateLineDash(GfxState *state)
+{
+    double *dashPattern;
+    int dashLength;
+    double dashStart;
+    state->getLineDash(&dashPattern, &dashLength, &dashStart);
+
+    // Special handling for zero-length patterns, i.e., solid lines.
+    // Simply calling QPen::setDashPattern with an empty pattern does *not*
+    // result in a solid line.  Rather, the current pattern is unchanged.
+    // See the implementation of the setDashPattern method in the file qpen.cpp.
+    if (dashLength == 0) {
+        m_currentPen.setStyle(Qt::SolidLine);
+        m_painter.top()->setPen(m_currentPen);
+        return;
+    }
+
+    QVector<qreal> pattern(dashLength);
+    double scaling = state->getLineWidth();
+
+    //  Negative line widths are not allowed, width 0 counts as 'one pixel width'.
+    if (scaling <= 0) {
+        scaling = 1.0;
+    }
+
+    for (int i = 0; i < dashLength; ++i) {
+        // pdf measures the dash pattern in dots, but Qt uses the
+        // line width as the unit.
+        pattern[i] = dashPattern[i] / scaling;
+    }
+    m_currentPen.setDashPattern(pattern);
+    m_currentPen.setDashOffset(dashStart);
+    m_painter.top()->setPen(m_currentPen);
+}
+
+void ArthurOutputDev::updateFlatness(GfxState *state)
+{
+    // qDebug() << "updateFlatness";
+}
+
+void ArthurOutputDev::updateLineJoin(GfxState *state)
+{
+    switch (state->getLineJoin()) {
+    case 0:
+        // The correct style here is Qt::SvgMiterJoin, *not* Qt::MiterJoin.
+        // The two differ in what to do if the miter limit is exceeded.
+        // See https://bugs.freedesktop.org/show_bug.cgi?id=102356
+        m_currentPen.setJoinStyle(Qt::SvgMiterJoin);
+        break;
+    case 1:
+        m_currentPen.setJoinStyle(Qt::RoundJoin);
+        break;
+    case 2:
+        m_currentPen.setJoinStyle(Qt::BevelJoin);
+        break;
+    }
+    m_painter.top()->setPen(m_currentPen);
+}
+
+void ArthurOutputDev::updateLineCap(GfxState *state)
+{
+    switch (state->getLineCap()) {
+    case 0:
+        m_currentPen.setCapStyle(Qt::FlatCap);
+        break;
+    case 1:
+        m_currentPen.setCapStyle(Qt::RoundCap);
+        break;
+    case 2:
+        m_currentPen.setCapStyle(Qt::SquareCap);
+        break;
+    }
+    m_painter.top()->setPen(m_currentPen);
+}
+
+void ArthurOutputDev::updateMiterLimit(GfxState *state)
+{
+    m_currentPen.setMiterLimit(state->getMiterLimit());
+    m_painter.top()->setPen(m_currentPen);
+}
+
+void ArthurOutputDev::updateLineWidth(GfxState *state)
+{
+    m_currentPen.setWidthF(state->getLineWidth());
+    m_painter.top()->setPen(m_currentPen);
+    // The updateLineDash method needs to know the line width, but it is sometimes
+    // called before the updateLineWidth method.  To make sure that the last call
+    // to updateLineDash before a drawing operation is always with the correct line
+    // width, we call it here, right after a change to the line width.
+    updateLineDash(state);
+}
+
+void ArthurOutputDev::updateFillColor(GfxState *state)
+{
+    GfxRGB rgb;
+    QColor brushColour = m_currentBrush.color();
+    state->getFillRGB(&rgb);
+    brushColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), brushColour.alphaF());
+    m_currentBrush.setColor(brushColour);
+}
+
+void ArthurOutputDev::updateStrokeColor(GfxState *state)
+{
+    GfxRGB rgb;
+    QColor penColour = m_currentPen.color();
+    state->getStrokeRGB(&rgb);
+    penColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), penColour.alphaF());
+    m_currentPen.setColor(penColour);
+    m_painter.top()->setPen(m_currentPen);
+}
+
+void ArthurOutputDev::updateBlendMode(GfxState *state)
+{
+    GfxBlendMode blendMode = state->getBlendMode();
+
+    // missing composition modes in QPainter:
+    // - CompositionMode_Hue
+    // - CompositionMode_Color
+    // - CompositionMode_Luminosity
+    // - CompositionMode_Saturation
+
+    switch (blendMode) {
+    case gfxBlendMultiply:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Multiply);
+        break;
+    case gfxBlendScreen:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Screen);
+        break;
+    case gfxBlendDarken:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Darken);
+        break;
+    case gfxBlendLighten:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Lighten);
+        break;
+    case gfxBlendColorDodge:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorDodge);
+        break;
+    case gfxBlendColorBurn:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorBurn);
+        break;
+    case gfxBlendHardLight:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_HardLight);
+        break;
+    case gfxBlendSoftLight:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_SoftLight);
+        break;
+    case gfxBlendDifference:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Difference);
+        break;
+    case gfxBlendExclusion:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Exclusion);
+        break;
+    case gfxBlendColor:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_Plus);
+        break;
+    default:
+        qDebug() << "Unsupported blend mode, falling back to CompositionMode_SourceOver";
+    case gfxBlendNormal:
+        m_painter.top()->setCompositionMode(QPainter::CompositionMode_SourceOver);
+        break;
+    }
+}
+
+void ArthurOutputDev::updateFillOpacity(GfxState *state)
+{
+    QColor brushColour = m_currentBrush.color();
+    brushColour.setAlphaF(state->getFillOpacity());
+    m_currentBrush.setColor(brushColour);
+}
+
+void ArthurOutputDev::updateStrokeOpacity(GfxState *state)
+{
+    QColor penColour = m_currentPen.color();
+    penColour.setAlphaF(state->getStrokeOpacity());
+    m_currentPen.setColor(penColour);
+    m_painter.top()->setPen(m_currentPen);
+}
+
+void ArthurOutputDev::updateFont(GfxState *state)
+{
+    GfxFont *gfxFont = state->getFont();
+    if (!gfxFont) {
+        return;
+    }
+
+    // The key to look in the font caches
+    ArthurFontID fontID = { *gfxFont->getID(), state->getFontSize() };
+
+    // Current font is a type3 font
+    if (gfxFont->getType() == fontType3) {
+        auto cacheEntry = m_type3FontCache.find(fontID);
+
+        if (cacheEntry != m_type3FontCache.end()) {
+
+            // Take the font from the cache
+            m_currentType3Font = cacheEntry->second.get();
+
+        } else {
+
+            m_currentType3Font = new ArthurType3Font(m_doc, (Gfx8BitFont *)gfxFont);
+            m_type3FontCache.insert(std::make_pair(fontID, std::unique_ptr<ArthurType3Font>(m_currentType3Font)));
+        }
+
+        return;
+    }
+
+    // Non-type3: is the font in the cache?
+    auto cacheEntry = m_rawFontCache.find(fontID);
+
+    if (cacheEntry != m_rawFontCache.end()) {
+
+        // Take the font from the cache
+        m_rawFont = cacheEntry->second.get();
+
+    } else {
+
+        // New font: load it into the cache
+        float fontSize = state->getFontSize();
+
+        std::unique_ptr<GfxFontLoc> fontLoc(gfxFont->locateFont(xref, nullptr));
+
+        if (fontLoc) {
+            // load the font from respective location
+            switch (fontLoc->locType) {
+            case gfxFontLocEmbedded: { // if there is an embedded font, read it to memory
+                int fontDataLen;
+                const char *fontData = gfxFont->readEmbFontFile(xref, &fontDataLen);
+
+                m_rawFont = new QRawFont(QByteArray(fontData, fontDataLen), fontSize, m_hintingPreference);
+                m_rawFontCache.insert(std::make_pair(fontID, std::unique_ptr<QRawFont>(m_rawFont)));
+
+                // Free the font data, it was copied in the QByteArray constructor
+                free((char *)fontData);
+                break;
+            }
+            case gfxFontLocExternal: { // font is in an external font file
+                QString fontFile(fontLoc->path->c_str());
+                m_rawFont = new QRawFont(fontFile, fontSize, m_hintingPreference);
+                m_rawFontCache.insert(std::make_pair(fontID, std::unique_ptr<QRawFont>(m_rawFont)));
+                break;
+            }
+            case gfxFontLocResident: { // font resides in a PS printer
+                qDebug() << "Font Resident Encoding:" << fontLoc->encoding->c_str() << ", not implemented yet!";
+
+                break;
+            }
+            } // end switch
+
+        } else {
+            qDebug() << "Font location not found!";
+            return;
+        }
+    }
+
+    if (!m_rawFont->isValid()) {
+        qDebug() << "RawFont is not valid";
+    }
+
+    // *****************************************************************************
+    //  We have now successfully loaded the font into a QRawFont object.  This
+    //  allows us to draw all the glyphs in the font.  However, what is missing is
+    //  the charcode-to-glyph-index mapping.  Apparently, Qt does not provide this
+    //  information at all.  Therefore, we need to figure it ourselves, using
+    //  FoFi and FreeType.
+    // *****************************************************************************
+
+    m_needFontUpdate = false;
+
+    GfxFontType fontType = gfxFont->getType();
+
+    // Default: no codeToGID table
+    m_codeToGID = nullptr;
+
+    // check the font file cache
+    Ref id = *gfxFont->getID();
+
+    auto codeToGIDIt = m_codeToGIDCache.find(id);
+
+    if (codeToGIDIt != m_codeToGIDCache.end()) {
+
+        m_codeToGID = codeToGIDIt->second;
+
+    } else {
+
+        std::unique_ptr<char[], void (*)(char *)> tmpBuf(nullptr, [](char *b) { free(b); });
+        int tmpBufLen = 0;
+
+        std::unique_ptr<GfxFontLoc> fontLoc(gfxFont->locateFont(xref, nullptr));
+        if (!fontLoc) {
+            error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
+            return;
+        }
+
+        // embedded font
+        if (fontLoc->locType == gfxFontLocEmbedded) {
+            // if there is an embedded font, read it to memory
+            tmpBuf.reset(gfxFont->readEmbFontFile(xref, &tmpBufLen));
+            if (!tmpBuf) {
+                return;
+            }
+
+            // external font
+        } else { // gfxFontLocExternal
+            // Hmm, fontType has already been set to gfxFont->getType() above.
+            // Can it really assume a different value here?
+            fontType = fontLoc->fontType;
+        }
+
+        switch (fontType) {
+        case fontType1:
+        case fontType1C:
+        case fontType1COT: {
+            // Load the font face using FreeType
+            const int faceIndex = 0; // We always load the zero-th face from a font
+            FT_Face freeTypeFace;
+
+            if (fontLoc->locType != gfxFontLocEmbedded) {
+                if (FT_New_Face(m_ftLibrary, fontLoc->path->c_str(), faceIndex, &freeTypeFace)) {
+                    error(errSyntaxError, -1, "Couldn't create a FreeType face for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
+                    return;
+                }
+            } else {
+                if (FT_New_Memory_Face(m_ftLibrary, (const FT_Byte *)tmpBuf.get(), tmpBufLen, faceIndex, &freeTypeFace)) {
+                    error(errSyntaxError, -1, "Couldn't create a FreeType face for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
+                    return;
+                }
+            }
+
+            const char *name;
+
+            int *codeToGID = (int *)gmallocn(256, sizeof(int));
+            for (int i = 0; i < 256; ++i) {
+                codeToGID[i] = 0;
+                if ((name = ((const char **)((Gfx8BitFont *)gfxFont)->getEncoding())[i])) {
+                    codeToGID[i] = (int)FT_Get_Name_Index(freeTypeFace, (char *)name);
+                    if (codeToGID[i] == 0) {
+                        name = GfxFont::getAlternateName(name);
+                        if (name) {
+                            codeToGID[i] = FT_Get_Name_Index(freeTypeFace, (char *)name);
+                        }
+                    }
+                }
+            }
+
+            FT_Done_Face(freeTypeFace);
+
+            m_codeToGIDCache[id] = codeToGID;
+
+            break;
+        }
+        case fontTrueType:
+        case fontTrueTypeOT: {
+            auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str())) : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen));
+
+            m_codeToGIDCache[id] = (ff) ? ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff.get()) : nullptr;
+
+            break;
+        }
+        case fontCIDType0:
+        case fontCIDType0C: {
+            int *cidToGIDMap = nullptr;
+            int nCIDs = 0;
+
+            // check for a CFF font
+            if (!m_useCIDs) {
+                auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? std::unique_ptr<FoFiType1C>(FoFiType1C::load(fontLoc->path->c_str())) : std::unique_ptr<FoFiType1C>(FoFiType1C::make(tmpBuf.get(), tmpBufLen));
+
+                cidToGIDMap = (ff) ? ff->getCIDToGIDMap(&nCIDs) : nullptr;
+            }
+
+            m_codeToGIDCache[id] = cidToGIDMap;
+
+            break;
+        }
+        case fontCIDType0COT: {
+            int *codeToGID = nullptr;
+
+            if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
+                int codeToGIDLen = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
+                codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int));
+                memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), codeToGIDLen * sizeof(int));
+            }
+
+            int *cidToGIDMap = nullptr;
+            int nCIDs = 0;
+
+            if (!codeToGID && !m_useCIDs) {
+                auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str())) : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen));
+
+                if (ff && ff->isOpenTypeCFF()) {
+                    cidToGIDMap = ff->getCIDToGIDMap(&nCIDs);
+                }
+            }
+
+            m_codeToGIDCache[id] = codeToGID ? codeToGID : cidToGIDMap;
+
+            break;
+        }
+        case fontCIDType2:
+        case fontCIDType2OT: {
+            int *codeToGID = nullptr;
+            int codeToGIDLen = 0;
+            if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
+                codeToGIDLen = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
+                if (codeToGIDLen) {
+                    codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int));
+                    memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), codeToGIDLen * sizeof(int));
+                }
+            } else {
+                auto ff = (fontLoc->locType != gfxFontLocEmbedded) ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str())) : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen));
+                if (!ff) {
+                    return;
+                }
+                codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff.get(), &codeToGIDLen);
+            }
+
+            m_codeToGIDCache[id] = codeToGID;
+
+            break;
+        }
+        default:
+            // this shouldn't happen
+            return;
+        }
+
+        m_codeToGID = m_codeToGIDCache[id];
+    }
+}
+
+static QPainterPath convertPath(GfxState *state, const GfxPath *path, Qt::FillRule fillRule)
+{
+    int i, j;
+
+    QPainterPath qPath;
+    qPath.setFillRule(fillRule);
+    for (i = 0; i < path->getNumSubpaths(); ++i) {
+        const GfxSubpath *subpath = path->getSubpath(i);
+        if (subpath->getNumPoints() > 0) {
+            qPath.moveTo(subpath->getX(0), subpath->getY(0));
+            j = 1;
+            while (j < subpath->getNumPoints()) {
+                if (subpath->getCurve(j)) {
+                    qPath.cubicTo(subpath->getX(j), subpath->getY(j), subpath->getX(j + 1), subpath->getY(j + 1), subpath->getX(j + 2), subpath->getY(j + 2));
+                    j += 3;
+                } else {
+                    qPath.lineTo(subpath->getX(j), subpath->getY(j));
+                    ++j;
+                }
+            }
+            if (subpath->isClosed()) {
+                qPath.closeSubpath();
+            }
+        }
+    }
+    return qPath;
+}
+
+void ArthurOutputDev::stroke(GfxState *state)
+{
+    m_painter.top()->strokePath(convertPath(state, state->getPath(), Qt::OddEvenFill), m_currentPen);
+}
+
+void ArthurOutputDev::fill(GfxState *state)
+{
+    m_painter.top()->fillPath(convertPath(state, state->getPath(), Qt::WindingFill), m_currentBrush);
+}
+
+void ArthurOutputDev::eoFill(GfxState *state)
+{
+    m_painter.top()->fillPath(convertPath(state, state->getPath(), Qt::OddEvenFill), m_currentBrush);
+}
+
+bool ArthurOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax)
+{
+    double x0, y0, x1, y1;
+    shading->getCoords(&x0, &y0, &x1, &y1);
+
+    // get the clip region bbox
+    double xMin, yMin, xMax, yMax;
+    state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+
+    // get the function domain
+    double t0 = shading->getDomain0();
+    double t1 = shading->getDomain1();
+
+    // Max number of splits along the t axis
+    constexpr int maxSplits = 256;
+
+    // Max delta allowed in any color component
+    const double colorDelta = (dblToCol(1 / 256.0));
+
+    // Number of color space components
+    auto nComps = shading->getColorSpace()->getNComps();
+    // If the clipping region is a stroke, then the current operation counts as a stroke
+    // rather than as a fill, and the opacity has to be set accordingly.
+    // See https://gitlab.freedesktop.org/poppler/poppler/-/issues/178
+    auto opacity = (state->getStrokePattern()) ? state->getStrokeOpacity() : state->getFillOpacity();
+
+    // Helper function to test two color objects for 'almost-equality'
+    auto isSameGfxColor = [&nComps, &colorDelta](const GfxColor &colorA, const GfxColor &colorB) {
+        for (int k = 0; k < nComps; ++k) {
+            if (abs(colorA.c[k] - colorB.c[k]) > colorDelta) {
+                return false;
+            }
+        }
+        return true;
+    };
+
+    // Helper function: project a number into an interval
+    // With C++17 this is part of the standard library
+    auto clamp = [](double v, double lo, double hi) { return std::min(std::max(v, lo), hi); };
+
+    // ta stores all parameter values where we evaluate the input shading function.
+    // In between, QLinearGradient will interpolate linearly.
+    // We set up the array with three values.
+    std::array<double, maxSplits + 1> ta;
+    ta[0] = tMin;
+    std::array<int, maxSplits + 1> next;
+    next[0] = maxSplits / 2;
+    ta[maxSplits / 2] = 0.5 * (tMin + tMax);
+    next[maxSplits / 2] = maxSplits;
+    ta[maxSplits] = tMax;
+
+    // compute the color at t = tMin
+    double tt = clamp(t0 + (t1 - t0) * tMin, t0, t1);
+
+    GfxColor color0, color1;
+    shading->getColor(tt, &color0);
+
+    // Construct a gradient object and set its color at one parameter end
+    QLinearGradient gradient(QPointF(x0 + tMin * (x1 - x0), y0 + tMin * (y1 - y0)), QPointF(x0 + tMax * (x1 - x0), y0 + tMax * (y1 - y0)));
+
+    GfxRGB rgb;
+    shading->getColorSpace()->getRGB(&color0, &rgb);
+    QColor qColor(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b), dblToByte(opacity));
+    gradient.setColorAt(0, qColor);
+
+    // Look for more relevant parameter values by bisection
+    int i = 0;
+    while (i < maxSplits) {
+
+        int j = next[i];
+        while (j > i + 1) {
+
+            // Next parameter value to try
+            tt = clamp(t0 + (t1 - t0) * ta[j], t0, t1);
+            shading->getColor(tt, &color1);
+
+            // j is a good next color stop if the input shading can be approximated well
+            // on the interval (ta[i], ta[j]) by a linear interpolation.
+            // We test this by comparing the real color in the middle between ta[i] and ta[j]
+            // with the linear interpolant there.
+            auto midPoint = 0.5 * (ta[i] + ta[j]);
+            GfxColor colorAtMidPoint;
+            shading->getColor(midPoint, &colorAtMidPoint);
+
+            GfxColor linearlyInterpolatedColor;
+            for (int ii = 0; ii < nComps; ii++)
+                linearlyInterpolatedColor.c[ii] = 0.5 * (color0.c[ii] + color1.c[ii]);
+
+            // If the two colors are equal, ta[j] is a good place for the next color stop; take it!
+            if (isSameGfxColor(colorAtMidPoint, linearlyInterpolatedColor))
+                break;
+
+            // Otherwise: bisect further
+            int k = (i + j) / 2;
+            ta[k] = midPoint;
+            next[i] = k;
+            next[k] = j;
+            j = k;
+        }
+
+        // set the color
+        shading->getColorSpace()->getRGB(&color1, &rgb);
+        qColor.setRgb(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b), dblToByte(opacity));
+        gradient.setColorAt((ta[j] - tMin) / (tMax - tMin), qColor);
+
+        // Move to the next parameter region
+        color0 = color1;
+        i = next[i];
+    }
+
+    state->moveTo(xMin, yMin);
+    state->lineTo(xMin, yMax);
+    state->lineTo(xMax, yMax);
+    state->lineTo(xMax, yMin);
+    state->closePath();
+
+    // Actually paint the shaded region
+    QBrush newBrush(gradient);
+    m_painter.top()->fillPath(convertPath(state, state->getPath(), Qt::WindingFill), newBrush);
+
+    state->clearPath();
+
+    // True means: The shaded region has been painted
+    return true;
+}
+
+void ArthurOutputDev::clip(GfxState *state)
+{
+    m_painter.top()->setClipPath(convertPath(state, state->getPath(), Qt::WindingFill), Qt::IntersectClip);
+}
+
+void ArthurOutputDev::eoClip(GfxState *state)
+{
+    m_painter.top()->setClipPath(convertPath(state, state->getPath(), Qt::OddEvenFill), Qt::IntersectClip);
+}
+
+void ArthurOutputDev::clipToStrokePath(GfxState *state)
+{
+    QPainterPath clipPath = convertPath(state, state->getPath(), Qt::WindingFill);
+
+    // Get the outline of 'clipPath' as a separate path
+    QPainterPathStroker stroker;
+    stroker.setWidth(state->getLineWidth());
+    stroker.setCapStyle(m_currentPen.capStyle());
+    stroker.setJoinStyle(m_currentPen.joinStyle());
+    stroker.setMiterLimit(state->getMiterLimit());
+    stroker.setDashPattern(m_currentPen.dashPattern());
+    stroker.setDashOffset(m_currentPen.dashOffset());
+    QPainterPath clipPathOutline = stroker.createStroke(clipPath);
+
+    // The interior of the outline is the desired clipping region
+    m_painter.top()->setClipPath(clipPathOutline, Qt::IntersectClip);
+}
+
+void ArthurOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, const Unicode *u, int uLen)
+{
+
+    // First handle type3 fonts
+    GfxFont *gfxFont = state->getFont();
+
+    GfxFontType fontType = gfxFont->getType();
+    if (fontType == fontType3) {
+
+        /////////////////////////////////////////////////////////////////////
+        //  Draw the QPicture that contains the glyph onto the page
+        /////////////////////////////////////////////////////////////////////
+
+        // Store the QPainter state; we need to modify it temporarily
+        m_painter.top()->save();
+
+        // Make the glyph position the coordinate origin -- that's our center of scaling
+        m_painter.top()->translate(QPointF(x - originX, y - originY));
+
+        const double *mat = gfxFont->getFontMatrix();
+        QTransform fontMatrix(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+
+        // Scale with the font size
+        fontMatrix.scale(state->getFontSize(), state->getFontSize());
+        m_painter.top()->setTransform(fontMatrix, true);
+
+        // Apply the text matrix on top
+        const double *textMat = state->getTextMat();
+
+        QTransform textTransform(textMat[0] * state->getHorizScaling(), textMat[1] * state->getHorizScaling(), textMat[2], textMat[3], 0, 0);
+
+        m_painter.top()->setTransform(textTransform, true);
+
+        // Actually draw the glyph
+        int gid = m_currentType3Font->codeToGID[code];
+        m_painter.top()->drawPicture(QPointF(0, 0), m_currentType3Font->getGlyph(gid));
+
+        // Restore transformation
+        m_painter.top()->restore();
+
+        return;
+    }
+
+    // check for invisible text -- this is used by Acrobat Capture
+    int render = state->getRender();
+    if (render == 3 || !m_rawFont) {
+        qDebug() << "Invisible text found!";
+        return;
+    }
+
+    if (!(render & 1)) {
+        quint32 glyphIndex = (m_codeToGID) ? m_codeToGID[code] : code;
+        QPointF glyphPosition = QPointF(x - originX, y - originY);
+
+        // QGlyphRun objects can hold an entire sequence of glyphs, and it would possibly
+        // be more efficient to simply note the glyph and glyph position here and then
+        // draw several glyphs at once in the endString method.  What keeps us from doing
+        // that is the transformation below: each glyph needs to be drawn upside down,
+        // i.e., reflected at its own baseline.  Since we have no guarantee that this
+        // baseline is the same for all glyphs in a string we have to do it one by one.
+        QGlyphRun glyphRun;
+        glyphRun.setRawData(&glyphIndex, &glyphPosition, 1);
+        glyphRun.setRawFont(*m_rawFont);
+
+        // Store the QPainter state; we need to modify it temporarily
+        m_painter.top()->save();
+
+        // Apply the text matrix to the glyph.  The glyph is not scaled by the font size,
+        // because the font in m_rawFont already has the correct size.
+        // Additionally, the CTM is upside down, i.e., it contains a negative Y-scaling
+        // entry.  Therefore, Qt will paint the glyphs upside down.  We need to temporarily
+        // reflect the page at glyphPosition.y().
+
+        // Make the glyph position the coordinate origin -- that's our center of scaling
+        const double *textMat = state->getTextMat();
+
+        m_painter.top()->translate(QPointF(glyphPosition.x(), glyphPosition.y()));
+
+        QTransform textTransform(textMat[0] * state->getHorizScaling(), textMat[1] * state->getHorizScaling(),
+                                 -textMat[2], // reflect at the horizontal axis,
+                                 -textMat[3], // because CTM is upside-down.
+                                 0, 0);
+
+        m_painter.top()->setTransform(textTransform, true);
+
+        // We are painting a filled glyph here.  But QPainter uses the pen to draw even filled text,
+        // not the brush.  (see, e.g.,  http://doc.qt.io/qt-5/qpainter.html#setPen )
+        // Therefore we have to temporarily overwrite the pen color.
+
+        // Since we are drawing a filled glyph, one would really expect to have m_currentBrush
+        // have the correct color.  However, somehow state->getFillRGB can change without
+        // updateFillColor getting called.  Then m_currentBrush may not contain the correct color.
+        GfxRGB rgb;
+        state->getFillRGB(&rgb);
+        QColor fontColor;
+        fontColor.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getFillOpacity());
+        m_painter.top()->setPen(fontColor);
+
+        // Actually draw the glyph
+        m_painter.top()->drawGlyphRun(QPointF(-glyphPosition.x(), -glyphPosition.y()), glyphRun);
+
+        // Restore transformation and pen color
+        m_painter.top()->restore();
+    }
+}
+
+void ArthurOutputDev::type3D0(GfxState *state, double wx, double wy) { }
+
+void ArthurOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { }
+
+void ArthurOutputDev::endTextObject(GfxState *state) { }
+
+void ArthurOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg)
+{
+    auto imgStr = std::make_unique<ImageStream>(str, width,
+                                                1, // numPixelComps
+                                                1 // getBits
+    );
+    imgStr->reset();
+
+    // TODO: Would using QImage::Format_Mono be more efficient here?
+    QImage image(width, height, QImage::Format_ARGB32);
+    unsigned int *data = reinterpret_cast<unsigned int *>(image.bits());
+    int stride = image.bytesPerLine() / 4;
+
+    QRgb fillColor = m_currentBrush.color().rgb();
+
+    for (int y = 0; y < height; y++) {
+
+        unsigned char *pix = imgStr->getLine();
+
+        // Invert the vertical coordinate: y is increasing from top to bottom
+        // on the page, but y is increasing bottom to top in the picture.
+        unsigned int *dest = data + (height - 1 - y) * stride;
+
+        for (int x = 0; x < width; x++) {
+
+            bool opaque = ((bool)pix[x]) == invert;
+            dest[x] = (opaque) ? fillColor : 0;
+        }
+    }
+
+    // At this point, the QPainter coordinate transformation (CTM) is such
+    // that QRect(0,0,1,1) is exactly the area of the image.
+    m_painter.top()->drawImage(QRect(0, 0, 1, 1), image);
+    imgStr->close();
+}
+
+// TODO: lots more work here.
+void ArthurOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg)
+{
+    unsigned int *data;
+    unsigned int *line;
+    int x, y;
+    unsigned char *pix;
+    int i;
+    QImage image;
+    int stride;
+
+    /* TODO: Do we want to cache these? */
+    auto imgStr = std::make_unique<ImageStream>(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
+    imgStr->reset();
+
+    image = QImage(width, height, QImage::Format_ARGB32);
+    data = reinterpret_cast<unsigned int *>(image.bits());
+    stride = image.bytesPerLine() / 4;
+    for (y = 0; y < height; y++) {
+        pix = imgStr->getLine();
+        // Invert the vertical coordinate: y is increasing from top to bottom
+        // on the page, but y is increasing bottom to top in the picture.
+        line = data + (height - 1 - y) * stride;
+        colorMap->getRGBLine(pix, line, width);
+
+        if (maskColors) {
+            for (x = 0; x < width; x++) {
+                for (i = 0; i < colorMap->getNumPixelComps(); ++i) {
+                    if (pix[i] < maskColors[2 * i] * 255 || pix[i] > maskColors[2 * i + 1] * 255) {
+                        *line = *line | 0xff000000;
+                        break;
+                    }
+                }
+                pix += colorMap->getNumPixelComps();
+                line++;
+            }
+        } else {
+            for (x = 0; x < width; x++) {
+                *line = *line | 0xff000000;
+                line++;
+            }
+        }
+    }
+
+    // At this point, the QPainter coordinate transformation (CTM) is such
+    // that QRect(0,0,1,1) is exactly the area of the image.
+    m_painter.top()->drawImage(QRect(0, 0, 1, 1), image);
+}
+
+void ArthurOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap,
+                                          bool maskInterpolate)
+{
+    // Bail out if the image size doesn't match the mask size.  I don't know
+    // what to do in this case.
+    if (width != maskWidth || height != maskHeight) {
+        qDebug() << "Soft mask size does not match image size!";
+        drawImage(state, ref, str, width, height, colorMap, interpolate, nullptr, false);
+        return;
+    }
+
+    // Bail out if the mask isn't a single channel.  I don't know
+    // what to do in this case.
+    if (maskColorMap->getColorSpace()->getNComps() != 1) {
+        qDebug() << "Soft mask is not a single 8-bit channel!";
+        drawImage(state, ref, str, width, height, colorMap, interpolate, nullptr, false);
+        return;
+    }
+
+    /* TODO: Do we want to cache these? */
+    auto imgStr = std::make_unique<ImageStream>(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
+    imgStr->reset();
+
+    auto maskImageStr = std::make_unique<ImageStream>(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits());
+    maskImageStr->reset();
+
+    QImage image(width, height, QImage::Format_ARGB32);
+    unsigned int *data = reinterpret_cast<unsigned int *>(image.bits());
+    int stride = image.bytesPerLine() / 4;
+
+    std::vector<unsigned char> maskLine(maskWidth);
+
+    for (int y = 0; y < height; y++) {
+
+        unsigned char *pix = imgStr->getLine();
+        unsigned char *maskPix = maskImageStr->getLine();
+
+        // Invert the vertical coordinate: y is increasing from top to bottom
+        // on the page, but y is increasing bottom to top in the picture.
+        unsigned int *line = data + (height - 1 - y) * stride;
+        colorMap->getRGBLine(pix, line, width);
+
+        // Apply the mask values to the image alpha channel
+        maskColorMap->getGrayLine(maskPix, maskLine.data(), width);
+        for (int x = 0; x < width; x++) {
+            *line = *line | (maskLine[x] << 24);
+            line++;
+        }
+    }
+
+    // At this point, the QPainter coordinate transformation (CTM) is such
+    // that QRect(0,0,1,1) is exactly the area of the image.
+    m_painter.top()->drawImage(QRect(0, 0, 1, 1), image);
+}
+
+void ArthurOutputDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/, GfxColorSpace * /*blendingColorSpace*/, bool /*isolated*/, bool /*knockout*/, bool /*forSoftMask*/)
+{
+    // The entire transparency group will be painted into a
+    // freshly created QPicture object.  Since an existing painter
+    // cannot change its paint device, we need to construct a
+    // new QPainter object as well.
+    m_qpictures.push(new QPicture);
+    m_painter.push(new QPainter(m_qpictures.top()));
+}
+
+void ArthurOutputDev::endTransparencyGroup(GfxState * /*state*/)
+{
+    // Stop painting into the group
+    m_painter.top()->end();
+
+    // Kill the painter that has been used for the transparency group
+    delete (m_painter.top());
+    m_painter.pop();
+
+    // Store the QPicture object that holds the result of the transparency group
+    // painting.  It will be painted and deleted in the method paintTransparencyGroup.
+    if (m_lastTransparencyGroupPicture) {
+        qDebug() << "Found a transparency group that has not been painted";
+        delete (m_lastTransparencyGroupPicture);
+    }
+    m_lastTransparencyGroupPicture = m_qpictures.top();
+    m_qpictures.pop();
+}
+
+void ArthurOutputDev::paintTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/)
+{
+    // Actually draw the transparency group
+    m_painter.top()->drawPicture(0, 0, *m_lastTransparencyGroupPicture);
+
+    // And delete it
+    delete (m_lastTransparencyGroupPicture);
+    m_lastTransparencyGroupPicture = nullptr;
+}
diff --git a/qt6/src/ArthurOutputDev.h b/qt6/src/ArthurOutputDev.h
new file mode 100644
index 0000000..141191b
--- /dev/null
+++ b/qt6/src/ArthurOutputDev.h
@@ -0,0 +1,206 @@
+//========================================================================
+//
+// ArthurOutputDev.h
+//
+// Copyright 2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2005 Brad Hards <bradh@frogmouth.net>
+// Copyright (C) 2005, 2018, 2019 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright (C) 2010 Pino Toscano <pino@kde.org>
+// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Mihai Niculescu <q.quark@gmail.com>
+// Copyright (C) 2017, 2018, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+#ifndef ARTHUROUTPUTDEV_H
+#define ARTHUROUTPUTDEV_H
+
+#include <memory>
+#include <map>
+#include <stack>
+
+#include "OutputDev.h"
+#include "GfxState.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <QtGui/QPainter>
+
+class GfxState;
+class PDFDoc;
+
+class QRawFont;
+
+class ArthurType3Font;
+
+//------------------------------------------------------------------------
+// ArthurOutputDev - QPainter renderer
+//------------------------------------------------------------------------
+
+class ArthurOutputDev : public OutputDev
+{
+public:
+    // Constructor.
+    ArthurOutputDev(QPainter *painter);
+
+    // Destructor.
+    ~ArthurOutputDev() override;
+
+    void setHintingPreference(QFont::HintingPreference hintingPreference) { m_hintingPreference = hintingPreference; }
+
+    //----- get info about output device
+
+    // Does this device use upside-down coordinates?
+    // (Upside-down means (0,0) is the top left corner of the page.)
+    bool upsideDown() override { return true; }
+
+    // Does this device use drawChar() or drawString()?
+    bool useDrawChar() override { return true; }
+
+    // Does this device implement shaded fills (aka gradients) natively?
+    // If this returns false, these shaded fills
+    // will be reduced to a series of other drawing operations.
+    // type==2 is 'axial shading'
+    bool useShadedFills(int type) override { return type == 2; }
+
+    // Does this device use beginType3Char/endType3Char?  Otherwise,
+    // text in Type 3 fonts will be drawn with drawChar/drawString.
+    bool interpretType3Chars() override { return false; }
+
+    //----- initialization and control
+
+    // Set Current Transformation Matrix to a fixed matrix given in ctm[0],...,ctm[5]
+    void setDefaultCTM(const double *ctm) override;
+
+    // Start a page.
+    void startPage(int pageNum, GfxState *state, XRef *xref) override;
+
+    // End a page.
+    void endPage() override;
+
+    //----- save/restore graphics state
+    void saveState(GfxState *state) override;
+    void restoreState(GfxState *state) override;
+
+    //----- update graphics state
+    void updateAll(GfxState *state) override;
+    void updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) override;
+    void updateLineDash(GfxState *state) override;
+    void updateFlatness(GfxState *state) override;
+    void updateLineJoin(GfxState *state) override;
+    void updateLineCap(GfxState *state) override;
+    void updateMiterLimit(GfxState *state) override;
+    void updateLineWidth(GfxState *state) override;
+    void updateFillColor(GfxState *state) override;
+    void updateStrokeColor(GfxState *state) override;
+    void updateBlendMode(GfxState *state) override;
+    void updateFillOpacity(GfxState *state) override;
+    void updateStrokeOpacity(GfxState *state) override;
+
+    //----- update text state
+    void updateFont(GfxState *state) override;
+
+    //----- path painting
+    void stroke(GfxState *state) override;
+    void fill(GfxState *state) override;
+    void eoFill(GfxState *state) override;
+    bool axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) override;
+
+    //----- path clipping
+    void clip(GfxState *state) override;
+    void eoClip(GfxState *state) override;
+    void clipToStrokePath(GfxState *state) override;
+
+    //----- text drawing
+    //   virtual void drawString(GfxState *state, GooString *s);
+    void drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, const Unicode *u, int uLen) override;
+    void endTextObject(GfxState *state) override;
+
+    //----- image drawing
+    void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg) override;
+    void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg) override;
+
+    void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap,
+                             bool maskInterpolate) override;
+
+    //----- Type 3 font operators
+    void type3D0(GfxState *state, double wx, double wy) override;
+    void type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) override;
+
+    //----- transparency groups and soft masks
+    void beginTransparencyGroup(GfxState *state, const double *bbox, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, bool forSoftMask) override;
+    void endTransparencyGroup(GfxState *state) override;
+    void paintTransparencyGroup(GfxState *state, const double *bbox) override;
+
+    //----- special access
+
+    // Called to indicate that a new PDF document has been loaded.
+    void startDoc(PDFDoc *doc);
+
+    bool isReverseVideo() { return false; }
+
+private:
+    // The stack of QPainters is used to implement transparency groups.  When such a group
+    // is opened, annew Painter that paints onto a QPicture is pushed onto the stack.
+    // It is popped again when the transparency group ends.
+    std::stack<QPainter *> m_painter;
+
+    // This is the corresponding stack of QPicture objects
+    std::stack<QPicture *> m_qpictures;
+
+    // endTransparencyGroup removes a QPicture from the stack, but stores
+    // it here for later use in paintTransparencyGroup.
+    QPicture *m_lastTransparencyGroupPicture;
+
+    QFont::HintingPreference m_hintingPreference;
+
+    QPen m_currentPen;
+    // The various stacks are used to implement the 'saveState' and 'restoreState' methods
+    std::stack<QPen> m_currentPenStack;
+
+    QBrush m_currentBrush;
+    std::stack<QBrush> m_currentBrushStack;
+
+    bool m_needFontUpdate; // set when the font needs to be updated
+    PDFDoc *m_doc;
+    XRef *xref; // xref table for current document
+
+    // The current font in use
+    QRawFont *m_rawFont;
+    std::stack<QRawFont *> m_rawFontStack;
+
+    ArthurType3Font *m_currentType3Font;
+    std::stack<ArthurType3Font *> m_type3FontStack;
+
+    // Cache all fonts by their Ref and font size
+    using ArthurFontID = std::pair<Ref, double>;
+    std::map<ArthurFontID, std::unique_ptr<QRawFont>> m_rawFontCache;
+    std::map<ArthurFontID, std::unique_ptr<ArthurType3Font>> m_type3FontCache;
+    std::map<Ref, const int *> m_codeToGIDCache;
+
+    // The table that maps character codes to glyph indexes
+    const int *m_codeToGID;
+    std::stack<const int *> m_codeToGIDStack;
+
+    FT_Library m_ftLibrary;
+    // as of FT 2.1.8, CID fonts are indexed by CID instead of GID
+    bool m_useCIDs;
+};
+
+#endif
diff --git a/qt6/src/CMakeLists.txt b/qt6/src/CMakeLists.txt
new file mode 100644
index 0000000..4af085e
--- /dev/null
+++ b/qt6/src/CMakeLists.txt
@@ -0,0 +1,68 @@
+add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS)
+
+include_directories(
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+set(CMAKE_C_VISIBILITY_PRESET hidden)
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
+
+configure_file(poppler-version.h.in ${CMAKE_CURRENT_BINARY_DIR}/poppler-version.h @ONLY)
+
+set(poppler_qt6_SRCS
+  poppler-annotation.cc
+  poppler-document.cc
+  poppler-embeddedfile.cc
+  poppler-fontinfo.cc
+  poppler-form.cc
+  poppler-link.cc
+  poppler-link-extractor.cc
+  poppler-movie.cc
+  poppler-optcontent.cc
+  poppler-page.cc
+  poppler-base-converter.cc
+  poppler-pdf-converter.cc
+  poppler-private.cc
+  poppler-ps-converter.cc
+  poppler-qiodeviceinstream.cc
+  poppler-qiodeviceoutstream.cc
+  poppler-sound.cc
+  poppler-textbox.cc
+  poppler-page-transition.cc
+  poppler-media.cc
+  poppler-outline.cc
+  ArthurOutputDev.cc
+  poppler-version.cpp
+)
+add_library(poppler-qt6 ${poppler_qt6_SRCS})
+set_target_properties(poppler-qt6 PROPERTIES VERSION 1.0.0 SOVERSION 1)
+if(MINGW AND BUILD_SHARED_LIBS)
+    get_target_property(POPPLER_QT6_SOVERSION poppler-qt6 SOVERSION)
+    set_target_properties(poppler-qt6 PROPERTIES SUFFIX "-${POPPLER_QT6_SOVERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}")
+endif()
+target_link_libraries(poppler-qt6 poppler Qt6::Core Qt6::Gui Qt6::Xml ${FREETYPE_LIBRARIES})
+if(MSVC)
+target_link_libraries(poppler-qt6 poppler ${poppler_LIBS})
+endif()
+if (ENABLE_NSS3)
+    target_include_directories(poppler-qt6 SYSTEM PRIVATE ${NSS3_INCLUDE_DIRS})
+endif()
+if(USE_CMS)
+    target_link_libraries(poppler-qt6 poppler ${LCMS2_LIBRARIES})
+endif()
+install(TARGETS poppler-qt6 RUNTIME DESTINATION bin LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+install(FILES
+  poppler-qt6.h
+  poppler-link.h
+  poppler-annotation.h
+  poppler-form.h
+  poppler-optcontent.h
+  poppler-export.h
+  poppler-page-transition.h
+  poppler-media.h
+  ${CMAKE_CURRENT_BINARY_DIR}/poppler-version.h
+  DESTINATION include/poppler/qt6)
+
diff --git a/qt6/src/Doxyfile b/qt6/src/Doxyfile
new file mode 100644
index 0000000..cca4a85
--- /dev/null
+++ b/qt6/src/Doxyfile
@@ -0,0 +1,1637 @@
+# Doxyfile 1.7.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = "Poppler Qt6"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = 0.89.0
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = NO
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = YES
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = NO
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = YES
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = YES
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = Mainpage.dox \
+                         poppler-annotation.h \
+                         poppler-form.h \
+                         poppler-link.h \
+                         poppler-qt6.h \
+                         poppler-optcontent.h \
+                         poppler-page-transition.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = APIDOCS-html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = YES
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               = poppler-qt6.qch
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.freedesktop.poppler.qt6
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   = "Poppler 0.15.0"
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  = poppler
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  = poppler
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           = qhelpgenerator
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvances is that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = APIDOCS-latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = YES
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             = "Q_DECL_DEPRECATED=" \
+                         "POPPLER_QT6_EXPORT="
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = FreeSans.ttf
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/qt6/src/Mainpage.dox b/qt6/src/Mainpage.dox
new file mode 100644
index 0000000..0d7f207
--- /dev/null
+++ b/qt6/src/Mainpage.dox
@@ -0,0 +1,85 @@
+/**
+@mainpage The Poppler Qt6 interface library
+
+The %Poppler Qt6 interface library, libpoppler-qt6, is a library that
+allows Qt6 programmers to easily load and render PDF files. The
+%Poppler Qt6 interface library uses poppler internally to do its job,
+but the Qt6 programmer will never have to worry about poppler
+internals.
+
+
+@section help Current Status
+
+The %Poppler Qt6 interface library is quite stable and working.
+
+@section refimpl Example Programs
+
+Examples programs can be found in the qt6/test directory. The %Poppler
+Qt6 interface library is also used in the KDE's
+document viewer <a href="http://okular.kde.org">Okular</a>. The source files
+for Okular's PDF plugin (%Poppler-based) can be found on the git server
+of the KDE project, under
+<a
+href="http://quickgit.kde.org/?p=okular.git&a=tree&f=generators/poppler">this
+URL</a>.
+
+
+@section req How to use the Poppler Qt6 interface library in three easy steps
+
+Programmer who would like to use the %Poppler Qt6 interface library
+simply need to add the following line to their C++ source files:
+
+@code
+#include <poppler-qt6.h>
+@endcode
+
+A PDF document can then be loaded as follows:
+@code
+QString filename;
+
+Poppler::Document* document = Poppler::Document::load(filename);
+if (!document || document->isLocked()) {
+
+  // ... error message ....
+
+  delete document;
+  return;
+}
+@endcode
+
+Pages can be rendered to QImages with the following commands:
+
+@code
+// Paranoid safety check
+if (document == 0) {
+  // ... error message ...
+  return;
+}
+
+// Access page of the PDF file
+Poppler::Page* pdfPage = document->page(pageNumber);  // Document starts at page 0
+if (pdfPage == 0) {
+  // ... error message ...
+  return;
+}
+
+// Generate a QImage of the rendered page
+QImage image = pdfPage->renderToImage(xres, yres, x, y, width, height);
+if (image.isNull()) {
+  // ... error message ...
+  return;
+}
+
+// ... use image ...
+
+// after the usage, the page must be deleted
+delete pdfPage;
+@endcode
+
+Finally, don't forget to destroy the document:
+
+@code
+delete document;
+@endcode
+ */
+
diff --git a/qt6/src/poppler-annotation-helper.h b/qt6/src/poppler-annotation-helper.h
new file mode 100644
index 0000000..c96b7df
--- /dev/null
+++ b/qt6/src/poppler-annotation-helper.h
@@ -0,0 +1,73 @@
+/* poppler-annotation-helper.h: qt interface to poppler
+ * Copyright (C) 2006, 2008, 2017-2019, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2012, Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2018, Dileep Sankhla <sankhla.dileep96@gmail.com>
+ * Copyright (C) 2018, Carlos Garcia Campos <carlosgc@gnome.org>
+ * Copyright (C) 2018, 2019, Oliver Sander <oliver.sander@tu-dresden.de>
+ * Adapting code from
+ *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_ANNOTATION_HELPER_H_
+#define _POPPLER_ANNOTATION_HELPER_H_
+
+#include <memory>
+
+#include <QtCore/QDebug>
+
+#include <Object.h>
+
+class QColor;
+
+class AnnotColor;
+
+namespace Poppler {
+
+class XPDFReader
+{
+public:
+    // transform from user coords to normalized ones using the matrix M
+    static inline void transform(double *M, double x, double y, QPointF &res);
+    static inline void invTransform(const double *M, const QPointF p, double &x, double &y);
+};
+
+void XPDFReader::transform(double *M, double x, double y, QPointF &res)
+{
+    res.setX(M[0] * x + M[2] * y + M[4]);
+    res.setY(M[1] * x + M[3] * y + M[5]);
+}
+
+void XPDFReader::invTransform(const double *M, const QPointF p, double &x, double &y)
+{
+    const double det = M[0] * M[3] - M[1] * M[2];
+    Q_ASSERT(det != 0);
+
+    const double invM[4] = { M[3] / det, -M[1] / det, -M[2] / det, M[0] / det };
+    const double xt = p.x() - M[4];
+    const double yt = p.y() - M[5];
+
+    x = invM[0] * xt + invM[2] * yt;
+    y = invM[1] * xt + invM[3] * yt;
+}
+
+QColor convertAnnotColor(const AnnotColor *color);
+std::unique_ptr<AnnotColor> convertQColor(const QColor &color);
+
+}
+
+#endif
diff --git a/qt6/src/poppler-annotation-private.h b/qt6/src/poppler-annotation-private.h
new file mode 100644
index 0000000..32d1036
--- /dev/null
+++ b/qt6/src/poppler-annotation-private.h
@@ -0,0 +1,112 @@
+/* poppler-annotation-private.h: qt interface to poppler
+ * Copyright (C) 2007, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2012, Tobias Koenig <tokoe@kdab.com>
+ * Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2012, 2014, 2018, 2019, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_ANNOTATION_PRIVATE_H_
+#define _POPPLER_ANNOTATION_PRIVATE_H_
+
+#include <QtCore/QPointF>
+#include <QtCore/QSharedDataPointer>
+
+#include "poppler-annotation.h"
+
+#include <Object.h>
+
+class Annot;
+class AnnotPath;
+class Page;
+class PDFRectangle;
+
+namespace Poppler {
+class DocumentData;
+
+class AnnotationPrivate : public QSharedData
+{
+public:
+    AnnotationPrivate();
+    virtual ~AnnotationPrivate();
+
+    AnnotationPrivate(const AnnotationPrivate &) = delete;
+    AnnotationPrivate &operator=(const AnnotationPrivate &) = delete;
+
+    void addRevision(Annotation *ann, Annotation::RevScope scope, Annotation::RevType type);
+
+    /* Returns an Annotation of the right subclass whose d_ptr points to
+     * this AnnotationPrivate */
+    virtual Annotation *makeAlias() = 0;
+
+    /* properties: contents related */
+    QString author;
+    QString contents;
+    QString uniqueName;
+    QDateTime modDate; // before or equal to currentDateTime()
+    QDateTime creationDate; // before or equal to modifyDate
+
+    /* properties: look/interaction related */
+    int flags;
+    QRectF boundary;
+
+    /* style and popup */
+    Annotation::Style style;
+    Annotation::Popup popup;
+
+    /* revisions */
+    Annotation::RevScope revisionScope;
+    Annotation::RevType revisionType;
+    QList<Annotation *> revisions;
+
+    /* After this call, the Annotation object will behave like a wrapper for
+     * the specified Annot object. All cached values are discarded */
+    void tieToNativeAnnot(Annot *ann, ::Page *page, DocumentData *doc);
+
+    /* Creates a new Annot object on the specified page, flushes current
+     * values to that object and ties this Annotation to that object */
+    virtual Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) = 0;
+
+    /* Inited to 0 (i.e. untied annotation) */
+    Annot *pdfAnnot;
+    ::Page *pdfPage;
+    DocumentData *parentDoc;
+
+    /* The following helpers only work if pdfPage is set */
+    void flushBaseAnnotationProperties();
+    void fillNormalizationMTX(double MTX[6], int pageRotation) const;
+    void fillTransformationMTX(double MTX[6]) const;
+    QRectF fromPdfRectangle(const PDFRectangle &r) const;
+    PDFRectangle boundaryToPdfRectangle(const QRectF &r, int flags) const;
+    AnnotPath *toAnnotPath(const QVector<QPointF> &l) const;
+
+    /* Scan page for annotations, parentId=0 searches for root annotations, subtypes empty means all subtypes */
+    static QList<Annotation *> findAnnotations(::Page *pdfPage, DocumentData *doc, const QSet<Annotation::SubType> &subtypes, int parentId = -1);
+
+    /* Add given annotation to given page */
+    static void addAnnotationToPage(::Page *pdfPage, DocumentData *doc, const Annotation *ann);
+
+    /* Remove annotation from page and destroy ann */
+    static void removeAnnotationFromPage(::Page *pdfPage, const Annotation *ann);
+
+    Ref pdfObjectReference() const;
+
+    Link *additionalAction(Annotation::AdditionalActionType type) const;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-annotation-private.h.orig b/qt6/src/poppler-annotation-private.h.orig
new file mode 100644
index 0000000..937edad
--- /dev/null
+++ b/qt6/src/poppler-annotation-private.h.orig
@@ -0,0 +1,115 @@
+/* poppler-annotation-private.h: qt interface to poppler
+ * Copyright (C) 2007, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2012, Tobias Koenig <tokoe@kdab.com>
+ * Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2012, 2014, 2018, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_ANNOTATION_PRIVATE_H_
+#define _POPPLER_ANNOTATION_PRIVATE_H_
+
+#include <QtCore/QLinkedList>
+#include <QtCore/QPointF>
+#include <QtCore/QSharedDataPointer>
+
+#include "poppler-annotation.h"
+
+#include <Object.h>
+
+class Annot;
+class AnnotPath;
+class Link;
+class Page;
+class PDFRectangle;
+
+namespace Poppler
+{
+class DocumentData;
+
+class AnnotationPrivate : public QSharedData
+{
+    public:
+        AnnotationPrivate();
+        virtual ~AnnotationPrivate();
+
+        AnnotationPrivate(const AnnotationPrivate &) = delete;
+        AnnotationPrivate& operator=(const AnnotationPrivate &) = delete;
+
+        void addRevision(Annotation *ann, Annotation::RevScope scope, Annotation::RevType type);
+
+        /* Returns an Annotation of the right subclass whose d_ptr points to
+         * this AnnotationPrivate */
+        virtual Annotation * makeAlias() = 0;
+
+        /* properties: contents related */
+        QString author;
+        QString contents;
+        QString uniqueName;
+        QDateTime modDate;       // before or equal to currentDateTime()
+        QDateTime creationDate;  // before or equal to modifyDate
+
+        /* properties: look/interaction related */
+        int flags;
+        QRectF boundary;
+
+        /* style and popup */
+        Annotation::Style style;
+        Annotation::Popup popup;
+
+        /* revisions */
+        Annotation::RevScope revisionScope;
+        Annotation::RevType revisionType;
+        QList<Annotation*> revisions;
+
+        /* After this call, the Annotation object will behave like a wrapper for
+         * the specified Annot object. All cached values are discarded */
+        void tieToNativeAnnot(Annot *ann, ::Page *page, DocumentData *doc);
+
+        /* Creates a new Annot object on the specified page, flushes current
+         * values to that object and ties this Annotation to that object */
+        virtual Annot* createNativeAnnot(::Page *destPage, DocumentData *doc) = 0;
+
+        /* Inited to 0 (i.e. untied annotation) */
+        Annot *pdfAnnot;
+        ::Page *pdfPage;
+        DocumentData * parentDoc;
+
+        /* The following helpers only work if pdfPage is set */
+        void flushBaseAnnotationProperties();
+        void fillNormalizationMTX(double MTX[6], int pageRotation) const;
+        void fillTransformationMTX(double MTX[6]) const;
+        QRectF fromPdfRectangle(const PDFRectangle &r) const;
+        PDFRectangle boundaryToPdfRectangle(const QRectF &r, int flags) const;
+        AnnotPath * toAnnotPath(const QLinkedList<QPointF> &l) const;
+
+        /* Scan page for annotations, parentId=0 searches for root annotations, subtypes empty means all subtypes */
+        static QList<Annotation*> findAnnotations(::Page *pdfPage, DocumentData *doc, const QSet<Annotation::SubType> &subtypes, int parentId = -1);
+
+        /* Add given annotation to given page */
+        static void addAnnotationToPage(::Page *pdfPage, DocumentData *doc, const Annotation * ann);
+
+        /* Remove annotation from page and destroy ann */
+        static void removeAnnotationFromPage(::Page *pdfPage, const Annotation * ann);
+
+        Ref pdfObjectReference() const;
+
+        Link* additionalAction( Annotation::AdditionalActionType type ) const;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-annotation.cc b/qt6/src/poppler-annotation.cc
new file mode 100644
index 0000000..4c3f934
--- /dev/null
+++ b/qt6/src/poppler-annotation.cc
@@ -0,0 +1,4636 @@
+/* poppler-annotation.cc: qt interface to poppler
+ * Copyright (C) 2006, 2009, 2012-2015, 2018, 2019 Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2006, 2008, 2010 Pino Toscano <pino@kde.org>
+ * Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
+ * Copyright (C) 2012-2014 Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2012, 2015, Tobias Koenig <tobias.koenig@kdab.com>
+ * Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
+ * 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) 2018 Intevation GmbH <intevation@intevation.de>
+ * Copyright (C) 2018 Dileep Sankhla <sankhla.dileep96@gmail.com>
+ * Copyright (C) 2018, 2019 Tobias Deiminger <haxtibal@posteo.de>
+ * Copyright (C) 2018 Carlos Garcia Campos <carlosgc@gnome.org>
+ * Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Adapting code from
+ *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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.
+ */
+
+// qt/kde includes
+#include <QtCore/QRegExp>
+#include <QtCore/QtAlgorithms>
+#include <QtXml/QDomElement>
+#include <QtGui/QColor>
+#include <QtGui/QTransform>
+
+// local includes
+#include "poppler-annotation.h"
+#include "poppler-link.h"
+#include "poppler-qt6.h"
+#include "poppler-annotation-helper.h"
+#include "poppler-annotation-private.h"
+#include "poppler-page-private.h"
+#include "poppler-private.h"
+
+// poppler includes
+#include <Page.h>
+#include <Annot.h>
+#include <Gfx.h>
+#include <Error.h>
+#include <FileSpec.h>
+#include <Link.h>
+
+/* Almost all getters directly query the underlying poppler annotation, with
+ * the exceptions of link, file attachment, sound, movie and screen annotations,
+ * Whose data retrieval logic has not been moved yet. Their getters return
+ * static data set at creation time by findAnnotations
+ */
+
+namespace Poppler {
+
+// BEGIN AnnotationUtils implementation
+Annotation *AnnotationUtils::createAnnotation(const QDomElement &annElement)
+{
+    // safety check on annotation element
+    if (!annElement.hasAttribute(QStringLiteral("type")))
+        return nullptr;
+
+    // build annotation of given type
+    Annotation *annotation = nullptr;
+    int typeNumber = annElement.attribute(QStringLiteral("type")).toInt();
+    switch (typeNumber) {
+    case Annotation::AText:
+        annotation = new TextAnnotation(annElement);
+        break;
+    case Annotation::ALine:
+        annotation = new LineAnnotation(annElement);
+        break;
+    case Annotation::AGeom:
+        annotation = new GeomAnnotation(annElement);
+        break;
+    case Annotation::AHighlight:
+        annotation = new HighlightAnnotation(annElement);
+        break;
+    case Annotation::AStamp:
+        annotation = new StampAnnotation(annElement);
+        break;
+    case Annotation::AInk:
+        annotation = new InkAnnotation(annElement);
+        break;
+    case Annotation::ACaret:
+        annotation = new CaretAnnotation(annElement);
+        break;
+    }
+
+    // return created annotation
+    return annotation;
+}
+
+void AnnotationUtils::storeAnnotation(const Annotation *ann, QDomElement &annElement, QDomDocument &document)
+{
+    // save annotation's type as element's attribute
+    annElement.setAttribute(QStringLiteral("type"), (uint)ann->subType());
+
+    // append all annotation data as children of this node
+    ann->store(annElement, document);
+}
+
+QDomElement AnnotationUtils::findChildElement(const QDomNode &parentNode, const QString &name)
+{
+    // loop through the whole children and return a 'name' named element
+    QDomNode subNode = parentNode.firstChild();
+    while (subNode.isElement()) {
+        QDomElement element = subNode.toElement();
+        if (element.tagName() == name)
+            return element;
+        subNode = subNode.nextSibling();
+    }
+    // if the name can't be found, return a dummy null element
+    return QDomElement();
+}
+// END AnnotationUtils implementation
+
+// BEGIN Annotation implementation
+AnnotationPrivate::AnnotationPrivate() : flags(0), revisionScope(Annotation::Root), revisionType(Annotation::None), pdfAnnot(nullptr), pdfPage(nullptr), parentDoc(nullptr) { }
+
+void AnnotationPrivate::addRevision(Annotation *ann, Annotation::RevScope scope, Annotation::RevType type)
+{
+    /* Since ownership stays with the caller, create an alias of ann */
+    revisions.append(ann->d_ptr->makeAlias());
+
+    /* Set revision properties */
+    revisionScope = scope;
+    revisionType = type;
+}
+
+AnnotationPrivate::~AnnotationPrivate()
+{
+    // Delete all children revisions
+    qDeleteAll(revisions);
+
+    // Release Annot object
+    if (pdfAnnot)
+        pdfAnnot->decRefCnt();
+}
+
+void AnnotationPrivate::tieToNativeAnnot(Annot *ann, ::Page *page, Poppler::DocumentData *doc)
+{
+    if (pdfAnnot) {
+        error(errIO, -1, "Annotation is already tied");
+        return;
+    }
+
+    pdfAnnot = ann;
+    pdfPage = page;
+    parentDoc = doc;
+
+    pdfAnnot->incRefCnt();
+}
+
+/* This method is called when a new annotation is created, after pdfAnnot and
+ * pdfPage have been set */
+void AnnotationPrivate::flushBaseAnnotationProperties()
+{
+    Q_ASSERT(pdfPage);
+
+    Annotation *q = makeAlias(); // Setters are defined in the public class
+
+    // Since pdfAnnot has been set, this calls will write in the Annot object
+    q->setAuthor(author);
+    q->setContents(contents);
+    q->setUniqueName(uniqueName);
+    q->setModificationDate(modDate);
+    q->setCreationDate(creationDate);
+    q->setFlags(flags);
+    // q->setBoundary(boundary); -- already set by subclass-specific code
+    q->setStyle(style);
+    q->setPopup(popup);
+
+    // Flush revisions
+    foreach (Annotation *r, revisions) {
+        // TODO: Flush revision
+        delete r; // Object is no longer needed
+    }
+
+    delete q;
+
+    // Clear some members to save memory
+    author.clear();
+    contents.clear();
+    uniqueName.clear();
+    revisions.clear();
+}
+
+// Returns matrix to convert from user space coords (oriented according to the
+// specified rotation) to normalized coords
+void AnnotationPrivate::fillNormalizationMTX(double MTX[6], int pageRotation) const
+{
+    Q_ASSERT(pdfPage);
+
+    // build a normalized transform matrix for this page at 100% scale
+    GfxState *gfxState = new GfxState(72.0, 72.0, pdfPage->getCropBox(), pageRotation, true);
+    const double *gfxCTM = gfxState->getCTM();
+
+    double w = pdfPage->getCropWidth();
+    double h = pdfPage->getCropHeight();
+
+    // Swap width and height if the page is rotated landscape or seascape
+    if (pageRotation == 90 || pageRotation == 270) {
+        double t = w;
+        w = h;
+        h = t;
+    }
+
+    for (int i = 0; i < 6; i += 2) {
+        MTX[i] = gfxCTM[i] / w;
+        MTX[i + 1] = gfxCTM[i + 1] / h;
+    }
+    delete gfxState;
+}
+
+// Returns matrix to convert from user space coords (i.e. those that are stored
+// in the PDF file) to normalized coords (i.e. those that we expose to clients).
+// This method also applies a rotation around the top-left corner if the
+// FixedRotation flag is set.
+void AnnotationPrivate::fillTransformationMTX(double MTX[6]) const
+{
+    Q_ASSERT(pdfPage);
+    Q_ASSERT(pdfAnnot);
+
+    const int pageRotate = pdfPage->getRotate();
+
+    if (pageRotate == 0 || (pdfAnnot->getFlags() & Annot::flagNoRotate) == 0) {
+        // Use the normalization matrix for this page's rotation
+        fillNormalizationMTX(MTX, pageRotate);
+    } else {
+        // Clients expect coordinates relative to this page's rotation, but
+        // FixedRotation annotations internally use unrotated coordinates:
+        // construct matrix to both normalize and rotate coordinates using the
+        // top-left corner as rotation pivot
+
+        double MTXnorm[6];
+        fillNormalizationMTX(MTXnorm, pageRotate);
+
+        QTransform transform(MTXnorm[0], MTXnorm[1], MTXnorm[2], MTXnorm[3], MTXnorm[4], MTXnorm[5]);
+        transform.translate(+pdfAnnot->getXMin(), +pdfAnnot->getYMax());
+        transform.rotate(pageRotate);
+        transform.translate(-pdfAnnot->getXMin(), -pdfAnnot->getYMax());
+
+        MTX[0] = transform.m11();
+        MTX[1] = transform.m12();
+        MTX[2] = transform.m21();
+        MTX[3] = transform.m22();
+        MTX[4] = transform.dx();
+        MTX[5] = transform.dy();
+    }
+}
+
+QRectF AnnotationPrivate::fromPdfRectangle(const PDFRectangle &r) const
+{
+    double swp, MTX[6];
+    fillTransformationMTX(MTX);
+
+    QPointF p1, p2;
+    XPDFReader::transform(MTX, r.x1, r.y1, p1);
+    XPDFReader::transform(MTX, r.x2, r.y2, p2);
+
+    double tl_x = p1.x();
+    double tl_y = p1.y();
+    double br_x = p2.x();
+    double br_y = p2.y();
+
+    if (tl_x > br_x) {
+        swp = tl_x;
+        tl_x = br_x;
+        br_x = swp;
+    }
+
+    if (tl_y > br_y) {
+        swp = tl_y;
+        tl_y = br_y;
+        br_y = swp;
+    }
+
+    return QRectF(QPointF(tl_x, tl_y), QPointF(br_x, br_y));
+}
+
+// This function converts a boundary QRectF in normalized coords to a
+// PDFRectangle in user coords. If the FixedRotation flag is set, this function
+// also applies a rotation around the top-left corner: it's the inverse of
+// the transformation produced by fillTransformationMTX, but we can't use
+// fillTransformationMTX here because it relies on the native annotation
+// object's boundary rect to be already set up.
+PDFRectangle AnnotationPrivate::boundaryToPdfRectangle(const QRectF &r, int rFlags) const
+{
+    Q_ASSERT(pdfPage);
+
+    const int pageRotate = pdfPage->getRotate();
+
+    double MTX[6];
+    fillNormalizationMTX(MTX, pageRotate);
+
+    double tl_x, tl_y, br_x, br_y, swp;
+    XPDFReader::invTransform(MTX, r.topLeft(), tl_x, tl_y);
+    XPDFReader::invTransform(MTX, r.bottomRight(), br_x, br_y);
+
+    if (tl_x > br_x) {
+        swp = tl_x;
+        tl_x = br_x;
+        br_x = swp;
+    }
+
+    if (tl_y > br_y) {
+        swp = tl_y;
+        tl_y = br_y;
+        br_y = swp;
+    }
+
+    const int rotationFixUp = (rFlags & Annotation::FixedRotation) ? pageRotate : 0;
+    const double width = br_x - tl_x;
+    const double height = br_y - tl_y;
+
+    if (rotationFixUp == 0)
+        return PDFRectangle(tl_x, tl_y, br_x, br_y);
+    else if (rotationFixUp == 90)
+        return PDFRectangle(tl_x, tl_y - width, tl_x + height, tl_y);
+    else if (rotationFixUp == 180)
+        return PDFRectangle(br_x, tl_y - height, br_x + width, tl_y);
+    else // rotationFixUp == 270
+        return PDFRectangle(br_x, br_y - width, br_x + height, br_y);
+}
+
+AnnotPath *AnnotationPrivate::toAnnotPath(const QVector<QPointF> &list) const
+{
+    const int count = list.size();
+    std::vector<AnnotCoord> ac;
+    ac.reserve(count);
+
+    double MTX[6];
+    fillTransformationMTX(MTX);
+
+    foreach (const QPointF &p, list) {
+        double x, y;
+        XPDFReader::invTransform(MTX, p, x, y);
+        ac.emplace_back(x, y);
+    }
+
+    return new AnnotPath(std::move(ac));
+}
+
+QList<Annotation *> AnnotationPrivate::findAnnotations(::Page *pdfPage, DocumentData *doc, const QSet<Annotation::SubType> &subtypes, int parentID)
+{
+    Annots *annots = pdfPage->getAnnots();
+    const uint numAnnotations = annots->getNumAnnots();
+    if (numAnnotations == 0) {
+        return QList<Annotation *>();
+    }
+
+    const bool wantTextAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AText);
+    const bool wantLineAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ALine);
+    const bool wantGeomAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AGeom);
+    const bool wantHighlightAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AHighlight);
+    const bool wantStampAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AStamp);
+    const bool wantInkAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AInk);
+    const bool wantLinkAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ALink);
+    const bool wantCaretAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ACaret);
+    const bool wantFileAttachmentAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AFileAttachment);
+    const bool wantSoundAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ASound);
+    const bool wantMovieAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AMovie);
+    const bool wantScreenAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AScreen);
+    const bool wantWidgetAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AWidget);
+
+    // Create Annotation objects and tie to their native Annot
+    QList<Annotation *> res;
+    for (uint k = 0; k < numAnnotations; k++) {
+        // get the j-th annotation
+        Annot *ann = annots->getAnnot(k);
+        if (!ann) {
+            error(errInternal, -1, "Annot {0:ud} is null", k);
+            continue;
+        }
+
+        // Check parent annotation
+        AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(ann);
+        if (!markupann) {
+            // Assume it's a root annotation, and skip if user didn't request it
+            if (parentID != -1)
+                continue;
+        } else if (markupann->getInReplyToID() != parentID)
+            continue;
+
+        /* Create Annotation of the right subclass */
+        Annotation *annotation = nullptr;
+        Annot::AnnotSubtype subType = ann->getType();
+
+        switch (subType) {
+        case Annot::typeText:
+            if (!wantTextAnnotations)
+                continue;
+            annotation = new TextAnnotation(TextAnnotation::Linked);
+            break;
+        case Annot::typeFreeText:
+            if (!wantTextAnnotations)
+                continue;
+            annotation = new TextAnnotation(TextAnnotation::InPlace);
+            break;
+        case Annot::typeLine:
+            if (!wantLineAnnotations)
+                continue;
+            annotation = new LineAnnotation(LineAnnotation::StraightLine);
+            break;
+        case Annot::typePolygon:
+        case Annot::typePolyLine:
+            if (!wantLineAnnotations)
+                continue;
+            annotation = new LineAnnotation(LineAnnotation::Polyline);
+            break;
+        case Annot::typeSquare:
+        case Annot::typeCircle:
+            if (!wantGeomAnnotations)
+                continue;
+            annotation = new GeomAnnotation();
+            break;
+        case Annot::typeHighlight:
+        case Annot::typeUnderline:
+        case Annot::typeSquiggly:
+        case Annot::typeStrikeOut:
+            if (!wantHighlightAnnotations)
+                continue;
+            annotation = new HighlightAnnotation();
+            break;
+        case Annot::typeStamp:
+            if (!wantStampAnnotations)
+                continue;
+            annotation = new StampAnnotation();
+            break;
+        case Annot::typeInk:
+            if (!wantInkAnnotations)
+                continue;
+            annotation = new InkAnnotation();
+            break;
+        case Annot::typeLink: /* TODO: Move logic to getters */
+        {
+            if (!wantLinkAnnotations)
+                continue;
+            // parse Link params
+            AnnotLink *linkann = static_cast<AnnotLink *>(ann);
+            LinkAnnotation *l = new LinkAnnotation();
+            annotation = l;
+
+            // -> hlMode
+            l->setLinkHighlightMode((LinkAnnotation::HighlightMode)linkann->getLinkEffect());
+
+            // -> link region
+            // TODO
+
+            // reading link action
+            if (linkann->getAction()) {
+                Link *popplerLink = PageData::convertLinkActionToLink(linkann->getAction(), doc, QRectF());
+                if (popplerLink) {
+                    l->setLinkDestination(popplerLink);
+                }
+            }
+            break;
+        }
+        case Annot::typeCaret:
+            if (!wantCaretAnnotations)
+                continue;
+            annotation = new CaretAnnotation();
+            break;
+        case Annot::typeFileAttachment: /* TODO: Move logic to getters */
+        {
+            if (!wantFileAttachmentAnnotations)
+                continue;
+            AnnotFileAttachment *attachann = static_cast<AnnotFileAttachment *>(ann);
+            FileAttachmentAnnotation *f = new FileAttachmentAnnotation();
+            annotation = f;
+            // -> fileIcon
+            f->setFileIconName(QString::fromLatin1(attachann->getName()->c_str()));
+            // -> embeddedFile
+            FileSpec *filespec = new FileSpec(attachann->getFile());
+            f->setEmbeddedFile(new EmbeddedFile(*new EmbeddedFileData(filespec)));
+            break;
+        }
+        case Annot::typeSound: /* TODO: Move logic to getters */
+        {
+            if (!wantSoundAnnotations)
+                continue;
+            AnnotSound *soundann = static_cast<AnnotSound *>(ann);
+            SoundAnnotation *s = new SoundAnnotation();
+            annotation = s;
+
+            // -> soundIcon
+            s->setSoundIconName(QString::fromLatin1(soundann->getName()->c_str()));
+            // -> sound
+            s->setSound(new SoundObject(soundann->getSound()));
+            break;
+        }
+        case Annot::typeMovie: /* TODO: Move logic to getters */
+        {
+            if (!wantMovieAnnotations)
+                continue;
+            AnnotMovie *movieann = static_cast<AnnotMovie *>(ann);
+            MovieAnnotation *m = new MovieAnnotation();
+            annotation = m;
+
+            // -> movie
+            MovieObject *movie = new MovieObject(movieann);
+            m->setMovie(movie);
+            // -> movieTitle
+            const GooString *movietitle = movieann->getTitle();
+            if (movietitle)
+                m->setMovieTitle(QString::fromLatin1(movietitle->c_str()));
+            break;
+        }
+        case Annot::typeScreen: {
+            if (!wantScreenAnnotations)
+                continue;
+            AnnotScreen *screenann = static_cast<AnnotScreen *>(ann);
+            // TODO Support other link types than Link::Rendition in ScreenAnnotation
+            if (!screenann->getAction() || screenann->getAction()->getKind() != actionRendition)
+                continue;
+            ScreenAnnotation *s = new ScreenAnnotation();
+            annotation = s;
+
+            // -> screen
+            Link *popplerLink = PageData::convertLinkActionToLink(screenann->getAction(), doc, QRectF());
+            s->setAction(static_cast<Poppler::LinkRendition *>(popplerLink));
+
+            // -> screenTitle
+            const GooString *screentitle = screenann->getTitle();
+            if (screentitle)
+                s->setScreenTitle(UnicodeParsedString(screentitle));
+            break;
+        }
+        case Annot::typePopup:
+            continue; // popups are parsed by Annotation's window() getter
+        case Annot::typeUnknown:
+            continue; // special case for ignoring unknown annotations
+        case Annot::typeWidget:
+            if (!wantWidgetAnnotations)
+                continue;
+            annotation = new WidgetAnnotation();
+            break;
+        case Annot::typeRichMedia: {
+            const AnnotRichMedia *annotRichMedia = static_cast<AnnotRichMedia *>(ann);
+
+            RichMediaAnnotation *richMediaAnnotation = new RichMediaAnnotation;
+
+            const AnnotRichMedia::Settings *annotSettings = annotRichMedia->getSettings();
+            if (annotSettings) {
+                RichMediaAnnotation::Settings *settings = new RichMediaAnnotation::Settings;
+
+                if (annotSettings->getActivation()) {
+                    RichMediaAnnotation::Activation *activation = new RichMediaAnnotation::Activation;
+
+                    switch (annotSettings->getActivation()->getCondition()) {
+                    case AnnotRichMedia::Activation::conditionPageOpened:
+                        activation->setCondition(RichMediaAnnotation::Activation::PageOpened);
+                        break;
+                    case AnnotRichMedia::Activation::conditionPageVisible:
+                        activation->setCondition(RichMediaAnnotation::Activation::PageVisible);
+                        break;
+                    case AnnotRichMedia::Activation::conditionUserAction:
+                        activation->setCondition(RichMediaAnnotation::Activation::UserAction);
+                        break;
+                    }
+
+                    settings->setActivation(activation);
+                }
+
+                if (annotSettings->getDeactivation()) {
+                    RichMediaAnnotation::Deactivation *deactivation = new RichMediaAnnotation::Deactivation;
+
+                    switch (annotSettings->getDeactivation()->getCondition()) {
+                    case AnnotRichMedia::Deactivation::conditionPageClosed:
+                        deactivation->setCondition(RichMediaAnnotation::Deactivation::PageClosed);
+                        break;
+                    case AnnotRichMedia::Deactivation::conditionPageInvisible:
+                        deactivation->setCondition(RichMediaAnnotation::Deactivation::PageInvisible);
+                        break;
+                    case AnnotRichMedia::Deactivation::conditionUserAction:
+                        deactivation->setCondition(RichMediaAnnotation::Deactivation::UserAction);
+                        break;
+                    }
+
+                    settings->setDeactivation(deactivation);
+                }
+
+                richMediaAnnotation->setSettings(settings);
+            }
+
+            const AnnotRichMedia::Content *annotContent = annotRichMedia->getContent();
+            if (annotContent) {
+                RichMediaAnnotation::Content *content = new RichMediaAnnotation::Content;
+
+                const int configurationsCount = annotContent->getConfigurationsCount();
+                if (configurationsCount > 0) {
+                    QList<RichMediaAnnotation::Configuration *> configurations;
+
+                    for (int i = 0; i < configurationsCount; ++i) {
+                        const AnnotRichMedia::Configuration *annotConfiguration = annotContent->getConfiguration(i);
+                        if (!annotConfiguration)
+                            continue;
+
+                        RichMediaAnnotation::Configuration *configuration = new RichMediaAnnotation::Configuration;
+
+                        if (annotConfiguration->getName())
+                            configuration->setName(UnicodeParsedString(annotConfiguration->getName()));
+
+                        switch (annotConfiguration->getType()) {
+                        case AnnotRichMedia::Configuration::type3D:
+                            configuration->setType(RichMediaAnnotation::Configuration::Type3D);
+                            break;
+                        case AnnotRichMedia::Configuration::typeFlash:
+                            configuration->setType(RichMediaAnnotation::Configuration::TypeFlash);
+                            break;
+                        case AnnotRichMedia::Configuration::typeSound:
+                            configuration->setType(RichMediaAnnotation::Configuration::TypeSound);
+                            break;
+                        case AnnotRichMedia::Configuration::typeVideo:
+                            configuration->setType(RichMediaAnnotation::Configuration::TypeVideo);
+                            break;
+                        }
+
+                        const int instancesCount = annotConfiguration->getInstancesCount();
+                        if (instancesCount > 0) {
+                            QList<RichMediaAnnotation::Instance *> instances;
+
+                            for (int j = 0; j < instancesCount; ++j) {
+                                const AnnotRichMedia::Instance *annotInstance = annotConfiguration->getInstance(j);
+                                if (!annotInstance)
+                                    continue;
+
+                                RichMediaAnnotation::Instance *instance = new RichMediaAnnotation::Instance;
+
+                                switch (annotInstance->getType()) {
+                                case AnnotRichMedia::Instance::type3D:
+                                    instance->setType(RichMediaAnnotation::Instance::Type3D);
+                                    break;
+                                case AnnotRichMedia::Instance::typeFlash:
+                                    instance->setType(RichMediaAnnotation::Instance::TypeFlash);
+                                    break;
+                                case AnnotRichMedia::Instance::typeSound:
+                                    instance->setType(RichMediaAnnotation::Instance::TypeSound);
+                                    break;
+                                case AnnotRichMedia::Instance::typeVideo:
+                                    instance->setType(RichMediaAnnotation::Instance::TypeVideo);
+                                    break;
+                                }
+
+                                const AnnotRichMedia::Params *annotParams = annotInstance->getParams();
+                                if (annotParams) {
+                                    RichMediaAnnotation::Params *params = new RichMediaAnnotation::Params;
+
+                                    if (annotParams->getFlashVars())
+                                        params->setFlashVars(UnicodeParsedString(annotParams->getFlashVars()));
+
+                                    instance->setParams(params);
+                                }
+
+                                instances.append(instance);
+                            }
+
+                            configuration->setInstances(instances);
+                        }
+
+                        configurations.append(configuration);
+                    }
+
+                    content->setConfigurations(configurations);
+                }
+
+                const int assetsCount = annotContent->getAssetsCount();
+                if (assetsCount > 0) {
+                    QList<RichMediaAnnotation::Asset *> assets;
+
+                    for (int i = 0; i < assetsCount; ++i) {
+                        const AnnotRichMedia::Asset *annotAsset = annotContent->getAsset(i);
+                        if (!annotAsset)
+                            continue;
+
+                        RichMediaAnnotation::Asset *asset = new RichMediaAnnotation::Asset;
+
+                        if (annotAsset->getName())
+                            asset->setName(UnicodeParsedString(annotAsset->getName()));
+
+                        FileSpec *fileSpec = new FileSpec(annotAsset->getFileSpec());
+                        asset->setEmbeddedFile(new EmbeddedFile(*new EmbeddedFileData(fileSpec)));
+
+                        assets.append(asset);
+                    }
+
+                    content->setAssets(assets);
+                }
+
+                richMediaAnnotation->setContent(content);
+            }
+
+            annotation = richMediaAnnotation;
+
+            break;
+        }
+        default: {
+#define CASE_FOR_TYPE(thetype)                                                                                                                                                                                                                 \
+    case Annot::type##thetype:                                                                                                                                                                                                                 \
+        error(errUnimplemented, -1, "Annotation " #thetype " not supported");                                                                                                                                                                  \
+        break;
+            switch (subType) {
+                CASE_FOR_TYPE(PrinterMark)
+                CASE_FOR_TYPE(TrapNet)
+                CASE_FOR_TYPE(Watermark)
+                CASE_FOR_TYPE(3D)
+            default:
+                error(errUnimplemented, -1, "Annotation {0:d} not supported", subType);
+            }
+            continue;
+#undef CASE_FOR_TYPE
+        }
+        }
+
+        annotation->d_ptr->tieToNativeAnnot(ann, pdfPage, doc);
+        res.append(annotation);
+    }
+
+    return res;
+}
+
+Ref AnnotationPrivate::pdfObjectReference() const
+{
+    if (pdfAnnot == nullptr) {
+        return Ref::INVALID();
+    }
+
+    return pdfAnnot->getRef();
+}
+
+Link *AnnotationPrivate::additionalAction(Annotation::AdditionalActionType type) const
+{
+    if (pdfAnnot->getType() != Annot::typeScreen && pdfAnnot->getType() != Annot::typeWidget)
+        return nullptr;
+
+    const Annot::AdditionalActionsType actionType = toPopplerAdditionalActionType(type);
+
+    std::unique_ptr<::LinkAction> linkAction = nullptr;
+    if (pdfAnnot->getType() == Annot::typeScreen)
+        linkAction = static_cast<AnnotScreen *>(pdfAnnot)->getAdditionalAction(actionType);
+    else
+        linkAction = static_cast<AnnotWidget *>(pdfAnnot)->getAdditionalAction(actionType);
+
+    Link *link = nullptr;
+
+    if (linkAction)
+        link = PageData::convertLinkActionToLink(linkAction.get(), parentDoc, QRectF());
+
+    return link;
+}
+
+void AnnotationPrivate::addAnnotationToPage(::Page *pdfPage, DocumentData *doc, const Annotation *ann)
+{
+    if (ann->d_ptr->pdfAnnot != nullptr) {
+        error(errIO, -1, "Annotation is already tied");
+        return;
+    }
+
+    // Unimplemented annotations can't be created by the user because their ctor
+    // is private. Therefore, createNativeAnnot will never return 0
+    Annot *nativeAnnot = ann->d_ptr->createNativeAnnot(pdfPage, doc);
+    Q_ASSERT(nativeAnnot);
+    pdfPage->addAnnot(nativeAnnot);
+}
+
+void AnnotationPrivate::removeAnnotationFromPage(::Page *pdfPage, const Annotation *ann)
+{
+    if (ann->d_ptr->pdfAnnot == nullptr) {
+        error(errIO, -1, "Annotation is not tied");
+        return;
+    }
+
+    if (ann->d_ptr->pdfPage != pdfPage) {
+        error(errIO, -1, "Annotation doesn't belong to the specified page");
+        return;
+    }
+
+    // Remove annotation
+    pdfPage->removeAnnot(ann->d_ptr->pdfAnnot);
+
+    // Destroy object
+    delete ann;
+}
+
+class Annotation::Style::Private : public QSharedData
+{
+public:
+    Private() : opacity(1.0), width(1.0), lineStyle(Solid), xCorners(0.0), yCorners(0.0), lineEffect(NoEffect), effectIntensity(1.0)
+    {
+        dashArray.resize(1);
+        dashArray[0] = 3;
+    }
+
+    QColor color;
+    double opacity;
+    double width;
+    Annotation::LineStyle lineStyle;
+    double xCorners;
+    double yCorners;
+    QVector<double> dashArray;
+    Annotation::LineEffect lineEffect;
+    double effectIntensity;
+};
+
+Annotation::Style::Style() : d(new Private) { }
+
+Annotation::Style::Style(const Style &other) : d(other.d) { }
+
+Annotation::Style &Annotation::Style::operator=(const Style &other)
+{
+    if (this != &other)
+        d = other.d;
+
+    return *this;
+}
+
+Annotation::Style::~Style() { }
+
+QColor Annotation::Style::color() const
+{
+    return d->color;
+}
+
+void Annotation::Style::setColor(const QColor &color)
+{
+    d->color = color;
+}
+
+double Annotation::Style::opacity() const
+{
+    return d->opacity;
+}
+
+void Annotation::Style::setOpacity(double opacity)
+{
+    d->opacity = opacity;
+}
+
+double Annotation::Style::width() const
+{
+    return d->width;
+}
+
+void Annotation::Style::setWidth(double width)
+{
+    d->width = width;
+}
+
+Annotation::LineStyle Annotation::Style::lineStyle() const
+{
+    return d->lineStyle;
+}
+
+void Annotation::Style::setLineStyle(Annotation::LineStyle style)
+{
+    d->lineStyle = style;
+}
+
+double Annotation::Style::xCorners() const
+{
+    return d->xCorners;
+}
+
+void Annotation::Style::setXCorners(double radius)
+{
+    d->xCorners = radius;
+}
+
+double Annotation::Style::yCorners() const
+{
+    return d->yCorners;
+}
+
+void Annotation::Style::setYCorners(double radius)
+{
+    d->yCorners = radius;
+}
+
+const QVector<double> &Annotation::Style::dashArray() const
+{
+    return d->dashArray;
+}
+
+void Annotation::Style::setDashArray(const QVector<double> &array)
+{
+    d->dashArray = array;
+}
+
+Annotation::LineEffect Annotation::Style::lineEffect() const
+{
+    return d->lineEffect;
+}
+
+void Annotation::Style::setLineEffect(Annotation::LineEffect effect)
+{
+    d->lineEffect = effect;
+}
+
+double Annotation::Style::effectIntensity() const
+{
+    return d->effectIntensity;
+}
+
+void Annotation::Style::setEffectIntensity(double intens)
+{
+    d->effectIntensity = intens;
+}
+
+class Annotation::Popup::Private : public QSharedData
+{
+public:
+    Private() : flags(-1) { }
+
+    int flags;
+    QRectF geometry;
+    QString title;
+    QString summary;
+    QString text;
+};
+
+Annotation::Popup::Popup() : d(new Private) { }
+
+Annotation::Popup::Popup(const Popup &other) : d(other.d) { }
+
+Annotation::Popup &Annotation::Popup::operator=(const Popup &other)
+{
+    if (this != &other)
+        d = other.d;
+
+    return *this;
+}
+
+Annotation::Popup::~Popup() { }
+
+int Annotation::Popup::flags() const
+{
+    return d->flags;
+}
+
+void Annotation::Popup::setFlags(int flags)
+{
+    d->flags = flags;
+}
+
+QRectF Annotation::Popup::geometry() const
+{
+    return d->geometry;
+}
+
+void Annotation::Popup::setGeometry(const QRectF &geom)
+{
+    d->geometry = geom;
+}
+
+QString Annotation::Popup::title() const
+{
+    return d->title;
+}
+
+void Annotation::Popup::setTitle(const QString &title)
+{
+    d->title = title;
+}
+
+QString Annotation::Popup::summary() const
+{
+    return d->summary;
+}
+
+void Annotation::Popup::setSummary(const QString &summary)
+{
+    d->summary = summary;
+}
+
+QString Annotation::Popup::text() const
+{
+    return d->text;
+}
+
+void Annotation::Popup::setText(const QString &text)
+{
+    d->text = text;
+}
+
+Annotation::Annotation(AnnotationPrivate &dd) : d_ptr(&dd) { }
+
+Annotation::~Annotation() { }
+
+Annotation::Annotation(AnnotationPrivate &dd, const QDomNode &annNode) : d_ptr(&dd)
+{
+    Q_D(Annotation);
+
+    // get the [base] element of the annotation node
+    QDomElement e = AnnotationUtils::findChildElement(annNode, QStringLiteral("base"));
+    if (e.isNull())
+        return;
+
+    Style s;
+    Popup w;
+
+    // parse -contents- attributes
+    if (e.hasAttribute(QStringLiteral("author")))
+        setAuthor(e.attribute(QStringLiteral("author")));
+    if (e.hasAttribute(QStringLiteral("contents")))
+        setContents(e.attribute(QStringLiteral("contents")));
+    if (e.hasAttribute(QStringLiteral("uniqueName")))
+        setUniqueName(e.attribute(QStringLiteral("uniqueName")));
+    if (e.hasAttribute(QStringLiteral("modifyDate")))
+        setModificationDate(QDateTime::fromString(e.attribute(QStringLiteral("modifyDate"))));
+    if (e.hasAttribute(QStringLiteral("creationDate")))
+        setCreationDate(QDateTime::fromString(e.attribute(QStringLiteral("creationDate"))));
+
+    // parse -other- attributes
+    if (e.hasAttribute(QStringLiteral("flags")))
+        setFlags(e.attribute(QStringLiteral("flags")).toInt());
+    if (e.hasAttribute(QStringLiteral("color")))
+        s.setColor(QColor(e.attribute(QStringLiteral("color"))));
+    if (e.hasAttribute(QStringLiteral("opacity")))
+        s.setOpacity(e.attribute(QStringLiteral("opacity")).toDouble());
+
+    // parse -the-subnodes- (describing Style, Window, Revision(s) structures)
+    // Note: all subnodes if present must be 'attributes complete'
+    QDomNode eSubNode = e.firstChild();
+    while (eSubNode.isElement()) {
+        QDomElement ee = eSubNode.toElement();
+        eSubNode = eSubNode.nextSibling();
+
+        // parse boundary
+        if (ee.tagName() == QLatin1String("boundary")) {
+            QRectF brect;
+            brect.setLeft(ee.attribute(QStringLiteral("l")).toDouble());
+            brect.setTop(ee.attribute(QStringLiteral("t")).toDouble());
+            brect.setRight(ee.attribute(QStringLiteral("r")).toDouble());
+            brect.setBottom(ee.attribute(QStringLiteral("b")).toDouble());
+            setBoundary(brect);
+        }
+        // parse penStyle if not default
+        else if (ee.tagName() == QLatin1String("penStyle")) {
+            s.setWidth(ee.attribute(QStringLiteral("width")).toDouble());
+            s.setLineStyle((LineStyle)ee.attribute(QStringLiteral("style")).toInt());
+            s.setXCorners(ee.attribute(QStringLiteral("xcr")).toDouble());
+            s.setYCorners(ee.attribute(QStringLiteral("ycr")).toDouble());
+
+            // Try to parse dash array (new format)
+            QVector<double> dashArray;
+
+            QDomNode eeSubNode = ee.firstChild();
+            while (eeSubNode.isElement()) {
+                QDomElement eee = eeSubNode.toElement();
+                eeSubNode = eeSubNode.nextSibling();
+
+                if (eee.tagName() != QLatin1String("dashsegm"))
+                    continue;
+
+                dashArray.append(eee.attribute(QStringLiteral("len")).toDouble());
+            }
+
+            // If no segments were found use marks/spaces (old format)
+            if (dashArray.size() == 0) {
+                dashArray.append(ee.attribute(QStringLiteral("marks")).toDouble());
+                dashArray.append(ee.attribute(QStringLiteral("spaces")).toDouble());
+            }
+
+            s.setDashArray(dashArray);
+        }
+        // parse effectStyle if not default
+        else if (ee.tagName() == QLatin1String("penEffect")) {
+            s.setLineEffect((LineEffect)ee.attribute(QStringLiteral("effect")).toInt());
+            s.setEffectIntensity(ee.attribute(QStringLiteral("intensity")).toDouble());
+        }
+        // parse window if present
+        else if (ee.tagName() == QLatin1String("window")) {
+            QRectF geom;
+            geom.setX(ee.attribute(QStringLiteral("top")).toDouble());
+            geom.setY(ee.attribute(QStringLiteral("left")).toDouble());
+
+            if (ee.hasAttribute(QStringLiteral("widthDouble")))
+                geom.setWidth(ee.attribute(QStringLiteral("widthDouble")).toDouble());
+            else
+                geom.setWidth(ee.attribute(QStringLiteral("width")).toDouble());
+
+            if (ee.hasAttribute(QStringLiteral("widthDouble")))
+                geom.setHeight(ee.attribute(QStringLiteral("heightDouble")).toDouble());
+            else
+                geom.setHeight(ee.attribute(QStringLiteral("height")).toDouble());
+
+            w.setGeometry(geom);
+
+            w.setFlags(ee.attribute(QStringLiteral("flags")).toInt());
+            w.setTitle(ee.attribute(QStringLiteral("title")));
+            w.setSummary(ee.attribute(QStringLiteral("summary")));
+            // parse window subnodes
+            QDomNode winNode = ee.firstChild();
+            for (; winNode.isElement(); winNode = winNode.nextSibling()) {
+                QDomElement winElement = winNode.toElement();
+                if (winElement.tagName() == QLatin1String("text"))
+                    w.setText(winElement.firstChild().toCDATASection().data());
+            }
+        }
+    }
+
+    setStyle(s); // assign parsed style
+    setPopup(w); // assign parsed window
+
+    // get the [revisions] element of the annotation node
+    QDomNode revNode = annNode.firstChild();
+    for (; revNode.isElement(); revNode = revNode.nextSibling()) {
+        QDomElement revElement = revNode.toElement();
+        if (revElement.tagName() != QLatin1String("revision"))
+            continue;
+
+        Annotation *reply = AnnotationUtils::createAnnotation(revElement);
+
+        if (reply) // if annotation is valid, add as a revision of this annotation
+        {
+            RevScope scope = (RevScope)revElement.attribute(QStringLiteral("revScope")).toInt();
+            RevType type = (RevType)revElement.attribute(QStringLiteral("revType")).toInt();
+            d->addRevision(reply, scope, type);
+            delete reply;
+        }
+    }
+}
+
+void Annotation::storeBaseAnnotationProperties(QDomNode &annNode, QDomDocument &document) const
+{
+    // create [base] element of the annotation node
+    QDomElement e = document.createElement(QStringLiteral("base"));
+    annNode.appendChild(e);
+
+    const Style s = style();
+    const Popup w = popup();
+
+    // store -contents- attributes
+    if (!author().isEmpty())
+        e.setAttribute(QStringLiteral("author"), author());
+    if (!contents().isEmpty())
+        e.setAttribute(QStringLiteral("contents"), contents());
+    if (!uniqueName().isEmpty())
+        e.setAttribute(QStringLiteral("uniqueName"), uniqueName());
+    if (modificationDate().isValid())
+        e.setAttribute(QStringLiteral("modifyDate"), modificationDate().toString());
+    if (creationDate().isValid())
+        e.setAttribute(QStringLiteral("creationDate"), creationDate().toString());
+
+    // store -other- attributes
+    if (flags())
+        e.setAttribute(QStringLiteral("flags"), flags());
+    if (s.color().isValid())
+        e.setAttribute(QStringLiteral("color"), s.color().name());
+    if (s.opacity() != 1.0)
+        e.setAttribute(QStringLiteral("opacity"), QString::number(s.opacity()));
+
+    // Sub-Node-1 - boundary
+    const QRectF brect = boundary();
+    QDomElement bE = document.createElement(QStringLiteral("boundary"));
+    e.appendChild(bE);
+    bE.setAttribute(QStringLiteral("l"), QString::number((double)brect.left()));
+    bE.setAttribute(QStringLiteral("t"), QString::number((double)brect.top()));
+    bE.setAttribute(QStringLiteral("r"), QString::number((double)brect.right()));
+    bE.setAttribute(QStringLiteral("b"), QString::number((double)brect.bottom()));
+
+    // Sub-Node-2 - penStyle
+    const QVector<double> &dashArray = s.dashArray();
+    if (s.width() != 1 || s.lineStyle() != Solid || s.xCorners() != 0 || s.yCorners() != 0.0 || dashArray.size() != 1 || dashArray[0] != 3) {
+        QDomElement psE = document.createElement(QStringLiteral("penStyle"));
+        e.appendChild(psE);
+        psE.setAttribute(QStringLiteral("width"), QString::number(s.width()));
+        psE.setAttribute(QStringLiteral("style"), (int)s.lineStyle());
+        psE.setAttribute(QStringLiteral("xcr"), QString::number(s.xCorners()));
+        psE.setAttribute(QStringLiteral("ycr"), QString::number(s.yCorners()));
+
+        int marks = 3, spaces = 0; // Do not break code relying on marks/spaces
+        if (dashArray.size() != 0)
+            marks = (int)dashArray[0];
+        if (dashArray.size() > 1)
+            spaces = (int)dashArray[1];
+
+        psE.setAttribute(QStringLiteral("marks"), marks);
+        psE.setAttribute(QStringLiteral("spaces"), spaces);
+
+        foreach (double segm, dashArray) {
+            QDomElement pattE = document.createElement(QStringLiteral("dashsegm"));
+            pattE.setAttribute(QStringLiteral("len"), QString::number(segm));
+            psE.appendChild(pattE);
+        }
+    }
+
+    // Sub-Node-3 - penEffect
+    if (s.lineEffect() != NoEffect || s.effectIntensity() != 1.0) {
+        QDomElement peE = document.createElement(QStringLiteral("penEffect"));
+        e.appendChild(peE);
+        peE.setAttribute(QStringLiteral("effect"), (int)s.lineEffect());
+        peE.setAttribute(QStringLiteral("intensity"), QString::number(s.effectIntensity()));
+    }
+
+    // Sub-Node-4 - window
+    if (w.flags() != -1 || !w.title().isEmpty() || !w.summary().isEmpty() || !w.text().isEmpty()) {
+        QDomElement wE = document.createElement(QStringLiteral("window"));
+        const QRectF geom = w.geometry();
+        e.appendChild(wE);
+        wE.setAttribute(QStringLiteral("flags"), w.flags());
+        wE.setAttribute(QStringLiteral("top"), QString::number(geom.x()));
+        wE.setAttribute(QStringLiteral("left"), QString::number(geom.y()));
+        wE.setAttribute(QStringLiteral("width"), (int)geom.width());
+        wE.setAttribute(QStringLiteral("height"), (int)geom.height());
+        wE.setAttribute(QStringLiteral("widthDouble"), QString::number(geom.width()));
+        wE.setAttribute(QStringLiteral("heightDouble"), QString::number(geom.height()));
+        wE.setAttribute(QStringLiteral("title"), w.title());
+        wE.setAttribute(QStringLiteral("summary"), w.summary());
+        // store window.text as a subnode, because we need escaped data
+        if (!w.text().isEmpty()) {
+            QDomElement escapedText = document.createElement(QStringLiteral("text"));
+            wE.appendChild(escapedText);
+            QDomCDATASection textCData = document.createCDATASection(w.text());
+            escapedText.appendChild(textCData);
+        }
+    }
+
+    const QList<Annotation *> revs = revisions();
+
+    // create [revision] element of the annotation node (if any)
+    if (revs.isEmpty())
+        return;
+
+    // add all revisions as children of revisions element
+    foreach (const Annotation *rev, revs) {
+        QDomElement r = document.createElement(QStringLiteral("revision"));
+        annNode.appendChild(r);
+        // set element attributes
+        r.setAttribute(QStringLiteral("revScope"), (int)rev->revisionScope());
+        r.setAttribute(QStringLiteral("revType"), (int)rev->revisionType());
+        // use revision as the annotation element, so fill it up
+        AnnotationUtils::storeAnnotation(rev, r, document);
+        delete rev;
+    }
+}
+
+QString Annotation::author() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->author;
+
+    const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
+    return markupann ? UnicodeParsedString(markupann->getLabel()) : QString();
+}
+
+void Annotation::setAuthor(const QString &author)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->author = author;
+        return;
+    }
+
+    AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(d->pdfAnnot);
+    if (markupann) {
+        GooString *s = QStringToUnicodeGooString(author);
+        markupann->setLabel(s);
+        delete s;
+    }
+}
+
+QString Annotation::contents() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->contents;
+
+    return UnicodeParsedString(d->pdfAnnot->getContents());
+}
+
+void Annotation::setContents(const QString &contents)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->contents = contents;
+        return;
+    }
+
+    GooString *s = QStringToUnicodeGooString(contents);
+    d->pdfAnnot->setContents(s);
+    delete s;
+}
+
+QString Annotation::uniqueName() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->uniqueName;
+
+    return UnicodeParsedString(d->pdfAnnot->getName());
+}
+
+void Annotation::setUniqueName(const QString &uniqueName)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->uniqueName = uniqueName;
+        return;
+    }
+
+    QByteArray ascii = uniqueName.toLatin1();
+    GooString s(ascii.constData());
+    d->pdfAnnot->setName(&s);
+}
+
+QDateTime Annotation::modificationDate() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->modDate;
+
+    if (d->pdfAnnot->getModified())
+        return convertDate(d->pdfAnnot->getModified()->c_str());
+    else
+        return QDateTime();
+}
+
+void Annotation::setModificationDate(const QDateTime &date)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->modDate = date;
+        return;
+    }
+
+#if 0 // TODO: Conversion routine is broken
+    if (d->pdfAnnot)
+    {
+        time_t t = date.toTime_t();
+        GooString *s = timeToDateString(&t);
+        d->pdfAnnot->setModified(s);
+        delete s;
+    }
+#endif
+}
+
+QDateTime Annotation::creationDate() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->creationDate;
+
+    const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
+
+    if (markupann && markupann->getDate())
+        return convertDate(markupann->getDate()->c_str());
+
+    return modificationDate();
+}
+
+void Annotation::setCreationDate(const QDateTime &date)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->creationDate = date;
+        return;
+    }
+
+#if 0 // TODO: Conversion routine is broken
+    AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
+    if (markupann)
+    {
+        time_t t = date.toTime_t();
+        GooString *s = timeToDateString(&t);
+        markupann->setDate(s);
+        delete s;
+    }
+#endif
+}
+
+static int fromPdfFlags(int flags)
+{
+    int qtflags = 0;
+
+    if (flags & Annot::flagHidden)
+        qtflags |= Annotation::Hidden;
+    if (flags & Annot::flagNoZoom)
+        qtflags |= Annotation::FixedSize;
+    if (flags & Annot::flagNoRotate)
+        qtflags |= Annotation::FixedRotation;
+    if (!(flags & Annot::flagPrint))
+        qtflags |= Annotation::DenyPrint;
+    if (flags & Annot::flagReadOnly)
+        qtflags |= (Annotation::DenyWrite | Annotation::DenyDelete);
+    if (flags & Annot::flagLocked)
+        qtflags |= Annotation::DenyDelete;
+    if (flags & Annot::flagToggleNoView)
+        qtflags |= Annotation::ToggleHidingOnMouse;
+
+    return qtflags;
+}
+
+static int toPdfFlags(int qtflags)
+{
+    int flags = 0;
+
+    if (qtflags & Annotation::Hidden)
+        flags |= Annot::flagHidden;
+    if (qtflags & Annotation::FixedSize)
+        flags |= Annot::flagNoZoom;
+    if (qtflags & Annotation::FixedRotation)
+        flags |= Annot::flagNoRotate;
+    if (!(qtflags & Annotation::DenyPrint))
+        flags |= Annot::flagPrint;
+    if (qtflags & Annotation::DenyWrite)
+        flags |= Annot::flagReadOnly;
+    if (qtflags & Annotation::DenyDelete)
+        flags |= Annot::flagLocked;
+    if (qtflags & Annotation::ToggleHidingOnMouse)
+        flags |= Annot::flagToggleNoView;
+
+    return flags;
+}
+
+int Annotation::flags() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->flags;
+
+    return fromPdfFlags(d->pdfAnnot->getFlags());
+}
+
+void Annotation::setFlags(int flags)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->flags = flags;
+        return;
+    }
+
+    d->pdfAnnot->setFlags(toPdfFlags(flags));
+}
+
+QRectF Annotation::boundary() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->boundary;
+
+    const PDFRectangle *rect = d->pdfAnnot->getRect();
+    return d->fromPdfRectangle(*rect);
+}
+
+void Annotation::setBoundary(const QRectF &boundary)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->boundary = boundary;
+        return;
+    }
+
+    PDFRectangle rect = d->boundaryToPdfRectangle(boundary, flags());
+    d->pdfAnnot->setRect(&rect);
+}
+
+Annotation::Style Annotation::style() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->style;
+
+    Style s;
+    s.setColor(convertAnnotColor(d->pdfAnnot->getColor()));
+
+    const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
+    if (markupann)
+        s.setOpacity(markupann->getOpacity());
+
+    const AnnotBorder *border = d->pdfAnnot->getBorder();
+    if (border) {
+        if (border->getType() == AnnotBorder::typeArray) {
+            const AnnotBorderArray *border_array = static_cast<const AnnotBorderArray *>(border);
+            s.setXCorners(border_array->getHorizontalCorner());
+            s.setYCorners(border_array->getVerticalCorner());
+        }
+
+        s.setWidth(border->getWidth());
+        s.setLineStyle((Annotation::LineStyle)(1 << border->getStyle()));
+
+        const int dashArrLen = border->getDashLength();
+        const double *dashArrData = border->getDash();
+        QVector<double> dashArrVect(dashArrLen);
+        for (int i = 0; i < dashArrLen; ++i)
+            dashArrVect[i] = dashArrData[i];
+        s.setDashArray(dashArrVect);
+    }
+
+    AnnotBorderEffect *border_effect;
+    switch (d->pdfAnnot->getType()) {
+    case Annot::typeFreeText:
+        border_effect = static_cast<AnnotFreeText *>(d->pdfAnnot)->getBorderEffect();
+        break;
+    case Annot::typeSquare:
+    case Annot::typeCircle:
+        border_effect = static_cast<AnnotGeometry *>(d->pdfAnnot)->getBorderEffect();
+        break;
+    default:
+        border_effect = nullptr;
+    }
+    if (border_effect) {
+        s.setLineEffect((Annotation::LineEffect)border_effect->getEffectType());
+        s.setEffectIntensity(border_effect->getIntensity());
+    }
+
+    return s;
+}
+
+void Annotation::setStyle(const Annotation::Style &style)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->style = style;
+        return;
+    }
+
+    d->pdfAnnot->setColor(convertQColor(style.color()));
+
+    AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(d->pdfAnnot);
+    if (markupann)
+        markupann->setOpacity(style.opacity());
+
+    auto border = std::make_unique<AnnotBorderArray>();
+    border->setWidth(style.width());
+    border->setHorizontalCorner(style.xCorners());
+    border->setVerticalCorner(style.yCorners());
+    d->pdfAnnot->setBorder(std::move(border));
+}
+
+Annotation::Popup Annotation::popup() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->popup;
+
+    Popup w;
+    AnnotPopup *popup = nullptr;
+    int flags = -1; // Not initialized
+
+    const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
+    if (markupann) {
+        popup = markupann->getPopup();
+        w.setSummary(UnicodeParsedString(markupann->getSubject()));
+    }
+
+    if (popup) {
+        flags = fromPdfFlags(popup->getFlags()) & (Annotation::Hidden | Annotation::FixedSize | Annotation::FixedRotation);
+
+        if (!popup->getOpen())
+            flags |= Annotation::Hidden;
+
+        const PDFRectangle *rect = popup->getRect();
+        w.setGeometry(d->fromPdfRectangle(*rect));
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeText) {
+        const AnnotText *textann = static_cast<const AnnotText *>(d->pdfAnnot);
+
+        // Text annotations default to same rect as annotation
+        if (flags == -1) {
+            flags = 0;
+            w.setGeometry(boundary());
+        }
+
+        // If text is not 'opened', force window hiding. if the window
+        // was parsed from popup, the flag should already be set
+        if (!textann->getOpen() && flags != -1)
+            flags |= Annotation::Hidden;
+    }
+
+    w.setFlags(flags);
+
+    return w;
+}
+
+void Annotation::setPopup(const Annotation::Popup &popup)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->popup = popup;
+        return;
+    }
+
+#if 0 /* TODO: Remove old popup and add AnnotPopup to page */
+    AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
+    if (!markupann)
+        return;
+
+    // Create a new AnnotPopup and assign it to pdfAnnot
+    PDFRectangle rect = d->toPdfRectangle( popup.geometry() );
+    AnnotPopup * p = new AnnotPopup( d->pdfPage->getDoc(), &rect );
+    p->setOpen( !(popup.flags() & Annotation::Hidden) );
+    if (!popup.summary().isEmpty())
+    {
+        GooString *s = QStringToUnicodeGooString(popup.summary());
+        markupann->setLabel(s);
+        delete s;
+    }
+    markupann->setPopup(p);
+#endif
+}
+
+Annotation::RevScope Annotation::revisionScope() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->revisionScope;
+
+    const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
+
+    if (markupann && markupann->isInReplyTo()) {
+        switch (markupann->getReplyTo()) {
+        case AnnotMarkup::replyTypeR:
+            return Annotation::Reply;
+        case AnnotMarkup::replyTypeGroup:
+            return Annotation::Group;
+        }
+    }
+
+    return Annotation::Root; // It's not a revision
+}
+
+Annotation::RevType Annotation::revisionType() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot)
+        return d->revisionType;
+
+    const AnnotText *textann = dynamic_cast<const AnnotText *>(d->pdfAnnot);
+
+    if (textann && textann->isInReplyTo()) {
+        switch (textann->getState()) {
+        case AnnotText::stateMarked:
+            return Annotation::Marked;
+        case AnnotText::stateUnmarked:
+            return Annotation::Unmarked;
+        case AnnotText::stateAccepted:
+            return Annotation::Accepted;
+        case AnnotText::stateRejected:
+            return Annotation::Rejected;
+        case AnnotText::stateCancelled:
+            return Annotation::Cancelled;
+        case AnnotText::stateCompleted:
+            return Annotation::Completed;
+        default:
+            break;
+        }
+    }
+
+    return Annotation::None;
+}
+
+QList<Annotation *> Annotation::revisions() const
+{
+    Q_D(const Annotation);
+
+    if (!d->pdfAnnot) {
+        /* Return aliases, whose ownership goes to the caller */
+        QList<Annotation *> res;
+        foreach (Annotation *rev, d->revisions)
+            res.append(rev->d_ptr->makeAlias());
+        return res;
+    }
+
+    /* If the annotation doesn't live in a object on its own (eg bug51361), it
+     * has no ref, therefore it can't have revisions */
+    if (!d->pdfAnnot->getHasRef())
+        return QList<Annotation *>();
+
+    return AnnotationPrivate::findAnnotations(d->pdfPage, d->parentDoc, QSet<Annotation::SubType>(), d->pdfAnnot->getId());
+}
+
+// END Annotation implementation
+
+/** TextAnnotation [Annotation] */
+class TextAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    TextAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+    void setDefaultAppearanceToNative();
+    std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const;
+
+    // data fields
+    TextAnnotation::TextType textType;
+    QString textIcon;
+    QFont textFont;
+    QColor textColor;
+    int inplaceAlign; // 0:left, 1:center, 2:right
+    QVector<QPointF> inplaceCallout;
+    TextAnnotation::InplaceIntent inplaceIntent;
+};
+
+TextAnnotationPrivate::TextAnnotationPrivate() : AnnotationPrivate(), textType(TextAnnotation::Linked), textIcon(QStringLiteral("Note")), inplaceAlign(0), inplaceIntent(TextAnnotation::Unknown) { }
+
+Annotation *TextAnnotationPrivate::makeAlias()
+{
+    return new TextAnnotation(*this);
+}
+
+Annot *TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    // Setters are defined in the public class
+    TextAnnotation *q = static_cast<TextAnnotation *>(makeAlias());
+
+    // Set page and contents
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    if (textType == TextAnnotation::Linked) {
+        pdfAnnot = new AnnotText { destPage->getDoc(), &rect };
+    } else {
+        DefaultAppearance da { { objName, "Invalid_font" }, static_cast<double>(textFont.pointSize()), std::unique_ptr<AnnotColor> { convertQColor(textColor) } };
+        pdfAnnot = new AnnotFreeText { destPage->getDoc(), &rect, da };
+    }
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setTextIcon(textIcon);
+    q->setInplaceAlign(inplaceAlign);
+    q->setCalloutPoints(inplaceCallout);
+    q->setInplaceIntent(inplaceIntent);
+
+    delete q;
+
+    inplaceCallout.clear(); // Free up memory
+
+    return pdfAnnot;
+}
+
+void TextAnnotationPrivate::setDefaultAppearanceToNative()
+{
+    if (pdfAnnot && pdfAnnot->getType() == Annot::typeFreeText) {
+        AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(pdfAnnot);
+        DefaultAppearance da { { objName, "Invalid_font" }, static_cast<double>(textFont.pointSize()), std::unique_ptr<AnnotColor> { convertQColor(textColor) } };
+        ftextann->setDefaultAppearance(da);
+    }
+}
+
+std::unique_ptr<DefaultAppearance> TextAnnotationPrivate::getDefaultAppearanceFromNative() const
+{
+    if (pdfAnnot && pdfAnnot->getType() == Annot::typeFreeText) {
+        AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(pdfAnnot);
+        return ftextann->getDefaultAppearance();
+    } else {
+        return {};
+    }
+}
+
+TextAnnotation::TextAnnotation(TextAnnotation::TextType type) : Annotation(*new TextAnnotationPrivate())
+{
+    setTextType(type);
+}
+
+TextAnnotation::TextAnnotation(TextAnnotationPrivate &dd) : Annotation(dd) { }
+
+TextAnnotation::TextAnnotation(const QDomNode &node) : Annotation(*new TextAnnotationPrivate, node)
+{
+    // loop through the whole children looking for a 'text' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("text"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("type")))
+            setTextType((TextAnnotation::TextType)e.attribute(QStringLiteral("type")).toInt());
+        if (e.hasAttribute(QStringLiteral("icon")))
+            setTextIcon(e.attribute(QStringLiteral("icon")));
+        if (e.hasAttribute(QStringLiteral("font"))) {
+            QFont font;
+            font.fromString(e.attribute(QStringLiteral("font")));
+            setTextFont(font);
+            if (e.hasAttribute(QStringLiteral("fontColor"))) {
+                const QColor color = QColor(e.attribute(QStringLiteral("fontColor")));
+                setTextColor(color);
+            }
+        }
+        if (e.hasAttribute(QStringLiteral("align")))
+            setInplaceAlign(e.attribute(QStringLiteral("align")).toInt());
+        if (e.hasAttribute(QStringLiteral("intent")))
+            setInplaceIntent((TextAnnotation::InplaceIntent)e.attribute(QStringLiteral("intent")).toInt());
+
+        // parse the subnodes
+        QDomNode eSubNode = e.firstChild();
+        while (eSubNode.isElement()) {
+            QDomElement ee = eSubNode.toElement();
+            eSubNode = eSubNode.nextSibling();
+
+            if (ee.tagName() == QLatin1String("escapedText")) {
+                setContents(ee.firstChild().toCDATASection().data());
+            } else if (ee.tagName() == QLatin1String("callout")) {
+                QVector<QPointF> points(3);
+                points[0] = QPointF(ee.attribute(QStringLiteral("ax")).toDouble(), ee.attribute(QStringLiteral("ay")).toDouble());
+                points[1] = QPointF(ee.attribute(QStringLiteral("bx")).toDouble(), ee.attribute(QStringLiteral("by")).toDouble());
+                points[2] = QPointF(ee.attribute(QStringLiteral("cx")).toDouble(), ee.attribute(QStringLiteral("cy")).toDouble());
+                setCalloutPoints(points);
+            }
+        }
+
+        // loading complete
+        break;
+    }
+}
+
+TextAnnotation::~TextAnnotation() { }
+
+void TextAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [text] element
+    QDomElement textElement = document.createElement(QStringLiteral("text"));
+    node.appendChild(textElement);
+
+    // store the optional attributes
+    if (textType() != Linked)
+        textElement.setAttribute(QStringLiteral("type"), (int)textType());
+    if (textIcon() != QLatin1String("Note"))
+        textElement.setAttribute(QStringLiteral("icon"), textIcon());
+    if (inplaceAlign())
+        textElement.setAttribute(QStringLiteral("align"), inplaceAlign());
+    if (inplaceIntent() != Unknown)
+        textElement.setAttribute(QStringLiteral("intent"), (int)inplaceIntent());
+
+    textElement.setAttribute(QStringLiteral("font"), textFont().toString());
+    textElement.setAttribute(QStringLiteral("fontColor"), textColor().name());
+
+    // Sub-Node-1 - escapedText
+    if (!contents().isEmpty()) {
+        QDomElement escapedText = document.createElement(QStringLiteral("escapedText"));
+        textElement.appendChild(escapedText);
+        QDomCDATASection textCData = document.createCDATASection(contents());
+        escapedText.appendChild(textCData);
+    }
+
+    // Sub-Node-2 - callout
+    if (calloutPoint(0).x() != 0.0) {
+        QDomElement calloutElement = document.createElement(QStringLiteral("callout"));
+        textElement.appendChild(calloutElement);
+        calloutElement.setAttribute(QStringLiteral("ax"), QString::number(calloutPoint(0).x()));
+        calloutElement.setAttribute(QStringLiteral("ay"), QString::number(calloutPoint(0).y()));
+        calloutElement.setAttribute(QStringLiteral("bx"), QString::number(calloutPoint(1).x()));
+        calloutElement.setAttribute(QStringLiteral("by"), QString::number(calloutPoint(1).y()));
+        calloutElement.setAttribute(QStringLiteral("cx"), QString::number(calloutPoint(2).x()));
+        calloutElement.setAttribute(QStringLiteral("cy"), QString::number(calloutPoint(2).y()));
+    }
+}
+
+Annotation::SubType TextAnnotation::subType() const
+{
+    return AText;
+}
+
+TextAnnotation::TextType TextAnnotation::textType() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->textType;
+
+    return d->pdfAnnot->getType() == Annot::typeText ? TextAnnotation::Linked : TextAnnotation::InPlace;
+}
+
+void TextAnnotation::setTextType(TextAnnotation::TextType type)
+{
+    Q_D(TextAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->textType = type;
+        return;
+    }
+
+    // Type cannot be changed if annotation is already tied
+}
+
+QString TextAnnotation::textIcon() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->textIcon;
+
+    if (d->pdfAnnot->getType() == Annot::typeText) {
+        const AnnotText *textann = static_cast<const AnnotText *>(d->pdfAnnot);
+        return QString::fromLatin1(textann->getIcon()->c_str());
+    }
+
+    return QString();
+}
+
+void TextAnnotation::setTextIcon(const QString &icon)
+{
+    Q_D(TextAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->textIcon = icon;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeText) {
+        AnnotText *textann = static_cast<AnnotText *>(d->pdfAnnot);
+        QByteArray encoded = icon.toLatin1();
+        GooString s(encoded.constData());
+        textann->setIcon(&s);
+    }
+}
+
+QFont TextAnnotation::textFont() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->textFont;
+
+    double fontSize { AnnotFreeText::undefinedFontPtSize };
+    if (d->pdfAnnot->getType() == Annot::typeFreeText) {
+        std::unique_ptr<DefaultAppearance> da { d->getDefaultAppearanceFromNative() };
+        if (da && da->getFontPtSize() > 0) {
+            fontSize = da->getFontPtSize();
+        }
+    }
+
+    QFont font;
+    font.setPointSizeF(fontSize);
+    return font;
+}
+
+void TextAnnotation::setTextFont(const QFont &font)
+{
+    Q_D(TextAnnotation);
+    d->textFont = font;
+    d->textColor = Qt::black;
+
+    d->setDefaultAppearanceToNative();
+}
+
+QColor TextAnnotation::textColor() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->textColor;
+
+    if (std::unique_ptr<DefaultAppearance> da { d->getDefaultAppearanceFromNative() }) {
+        return convertAnnotColor(da->getFontColor());
+    }
+
+    return {};
+}
+
+void TextAnnotation::setTextColor(const QColor &color)
+{
+    Q_D(TextAnnotation);
+    d->textColor = color;
+
+    d->setDefaultAppearanceToNative();
+}
+
+int TextAnnotation::inplaceAlign() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->inplaceAlign;
+
+    if (d->pdfAnnot->getType() == Annot::typeFreeText) {
+        const AnnotFreeText *ftextann = static_cast<const AnnotFreeText *>(d->pdfAnnot);
+        return ftextann->getQuadding();
+    }
+
+    return 0;
+}
+
+void TextAnnotation::setInplaceAlign(int align)
+{
+    Q_D(TextAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->inplaceAlign = align;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeFreeText) {
+        AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(d->pdfAnnot);
+        ftextann->setQuadding((AnnotFreeText::AnnotFreeTextQuadding)align);
+    }
+}
+
+QPointF TextAnnotation::calloutPoint(int id) const
+{
+    const QVector<QPointF> points = calloutPoints();
+    if (id < 0 || id >= points.size())
+        return QPointF();
+    else
+        return points[id];
+}
+
+QVector<QPointF> TextAnnotation::calloutPoints() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->inplaceCallout;
+
+    if (d->pdfAnnot->getType() == Annot::typeText)
+        return QVector<QPointF>();
+
+    const AnnotFreeText *ftextann = static_cast<const AnnotFreeText *>(d->pdfAnnot);
+    const AnnotCalloutLine *callout = ftextann->getCalloutLine();
+
+    if (!callout)
+        return QVector<QPointF>();
+
+    double MTX[6];
+    d->fillTransformationMTX(MTX);
+
+    const AnnotCalloutMultiLine *callout_v6 = dynamic_cast<const AnnotCalloutMultiLine *>(callout);
+    QVector<QPointF> res(callout_v6 ? 3 : 2);
+    XPDFReader::transform(MTX, callout->getX1(), callout->getY1(), res[0]);
+    XPDFReader::transform(MTX, callout->getX2(), callout->getY2(), res[1]);
+    if (callout_v6)
+        XPDFReader::transform(MTX, callout_v6->getX3(), callout_v6->getY3(), res[2]);
+    return res;
+}
+
+void TextAnnotation::setCalloutPoints(const QVector<QPointF> &points)
+{
+    Q_D(TextAnnotation);
+    if (!d->pdfAnnot) {
+        d->inplaceCallout = points;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() != Annot::typeFreeText)
+        return;
+
+    AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(d->pdfAnnot);
+    const int count = points.size();
+
+    if (count == 0) {
+        ftextann->setCalloutLine(nullptr);
+        return;
+    }
+
+    if (count != 2 && count != 3) {
+        error(errSyntaxError, -1, "Expected zero, two or three points for callout");
+        return;
+    }
+
+    AnnotCalloutLine *callout;
+    double x1, y1, x2, y2;
+    double MTX[6];
+    d->fillTransformationMTX(MTX);
+
+    XPDFReader::invTransform(MTX, points[0], x1, y1);
+    XPDFReader::invTransform(MTX, points[1], x2, y2);
+    if (count == 3) {
+        double x3, y3;
+        XPDFReader::invTransform(MTX, points[2], x3, y3);
+        callout = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3);
+    } else {
+        callout = new AnnotCalloutLine(x1, y1, x2, y2);
+    }
+
+    ftextann->setCalloutLine(callout);
+    delete callout;
+}
+
+TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const
+{
+    Q_D(const TextAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->inplaceIntent;
+
+    if (d->pdfAnnot->getType() == Annot::typeFreeText) {
+        const AnnotFreeText *ftextann = static_cast<const AnnotFreeText *>(d->pdfAnnot);
+        return (TextAnnotation::InplaceIntent)ftextann->getIntent();
+    }
+
+    return TextAnnotation::Unknown;
+}
+
+void TextAnnotation::setInplaceIntent(TextAnnotation::InplaceIntent intent)
+{
+    Q_D(TextAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->inplaceIntent = intent;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeFreeText) {
+        AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(d->pdfAnnot);
+        ftextann->setIntent((AnnotFreeText::AnnotFreeTextIntent)intent);
+    }
+}
+
+/** LineAnnotation [Annotation] */
+class LineAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    LineAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields (note uses border for rendering style)
+    QVector<QPointF> linePoints;
+    LineAnnotation::TermStyle lineStartStyle;
+    LineAnnotation::TermStyle lineEndStyle;
+    bool lineClosed : 1; // (if true draw close shape)
+    bool lineShowCaption : 1;
+    LineAnnotation::LineType lineType;
+    QColor lineInnerColor;
+    double lineLeadingFwdPt;
+    double lineLeadingBackPt;
+    LineAnnotation::LineIntent lineIntent;
+};
+
+LineAnnotationPrivate::LineAnnotationPrivate()
+    : AnnotationPrivate(), lineStartStyle(LineAnnotation::None), lineEndStyle(LineAnnotation::None), lineClosed(false), lineShowCaption(false), lineLeadingFwdPt(0), lineLeadingBackPt(0), lineIntent(LineAnnotation::Unknown)
+{
+}
+
+Annotation *LineAnnotationPrivate::makeAlias()
+{
+    return new LineAnnotation(*this);
+}
+
+Annot *LineAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    // Setters are defined in the public class
+    LineAnnotation *q = static_cast<LineAnnotation *>(makeAlias());
+
+    // Set page and document
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    if (lineType == LineAnnotation::StraightLine) {
+        pdfAnnot = new AnnotLine(doc->doc, &rect);
+    } else {
+        pdfAnnot = new AnnotPolygon(doc->doc, &rect, lineClosed ? Annot::typePolygon : Annot::typePolyLine);
+    }
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setLinePoints(linePoints);
+    q->setLineStartStyle(lineStartStyle);
+    q->setLineEndStyle(lineEndStyle);
+    q->setLineInnerColor(lineInnerColor);
+    q->setLineLeadingForwardPoint(lineLeadingFwdPt);
+    q->setLineLeadingBackPoint(lineLeadingBackPt);
+    q->setLineShowCaption(lineShowCaption);
+    q->setLineIntent(lineIntent);
+
+    delete q;
+
+    linePoints.clear(); // Free up memory
+
+    return pdfAnnot;
+}
+
+LineAnnotation::LineAnnotation(LineAnnotation::LineType type) : Annotation(*new LineAnnotationPrivate())
+{
+    setLineType(type);
+}
+
+LineAnnotation::LineAnnotation(LineAnnotationPrivate &dd) : Annotation(dd) { }
+
+LineAnnotation::LineAnnotation(const QDomNode &node) : Annotation(*new LineAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'line' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("line"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("startStyle")))
+            setLineStartStyle((LineAnnotation::TermStyle)e.attribute(QStringLiteral("startStyle")).toInt());
+        if (e.hasAttribute(QStringLiteral("endStyle")))
+            setLineEndStyle((LineAnnotation::TermStyle)e.attribute(QStringLiteral("endStyle")).toInt());
+        if (e.hasAttribute(QStringLiteral("closed")))
+            setLineClosed(e.attribute(QStringLiteral("closed")).toInt());
+        if (e.hasAttribute(QStringLiteral("innerColor")))
+            setLineInnerColor(QColor(e.attribute(QStringLiteral("innerColor"))));
+        if (e.hasAttribute(QStringLiteral("leadFwd")))
+            setLineLeadingForwardPoint(e.attribute(QStringLiteral("leadFwd")).toDouble());
+        if (e.hasAttribute(QStringLiteral("leadBack")))
+            setLineLeadingBackPoint(e.attribute(QStringLiteral("leadBack")).toDouble());
+        if (e.hasAttribute(QStringLiteral("showCaption")))
+            setLineShowCaption(e.attribute(QStringLiteral("showCaption")).toInt());
+        if (e.hasAttribute(QStringLiteral("intent")))
+            setLineIntent((LineAnnotation::LineIntent)e.attribute(QStringLiteral("intent")).toInt());
+
+        // parse all 'point' subnodes
+        QVector<QPointF> points;
+        QDomNode pointNode = e.firstChild();
+        while (pointNode.isElement()) {
+            QDomElement pe = pointNode.toElement();
+            pointNode = pointNode.nextSibling();
+
+            if (pe.tagName() != QLatin1String("point"))
+                continue;
+
+            QPointF p(pe.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble(), pe.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble());
+            points.append(p);
+        }
+        setLinePoints(points);
+        setLineType(points.size() == 2 ? StraightLine : Polyline);
+
+        // loading complete
+        break;
+    }
+}
+
+LineAnnotation::~LineAnnotation() { }
+
+void LineAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [line] element
+    QDomElement lineElement = document.createElement(QStringLiteral("line"));
+    node.appendChild(lineElement);
+
+    // store the attributes
+    if (lineStartStyle() != None)
+        lineElement.setAttribute(QStringLiteral("startStyle"), (int)lineStartStyle());
+    if (lineEndStyle() != None)
+        lineElement.setAttribute(QStringLiteral("endStyle"), (int)lineEndStyle());
+    if (isLineClosed())
+        lineElement.setAttribute(QStringLiteral("closed"), isLineClosed());
+    if (lineInnerColor().isValid())
+        lineElement.setAttribute(QStringLiteral("innerColor"), lineInnerColor().name());
+    if (lineLeadingForwardPoint() != 0.0)
+        lineElement.setAttribute(QStringLiteral("leadFwd"), QString::number(lineLeadingForwardPoint()));
+    if (lineLeadingBackPoint() != 0.0)
+        lineElement.setAttribute(QStringLiteral("leadBack"), QString::number(lineLeadingBackPoint()));
+    if (lineShowCaption())
+        lineElement.setAttribute(QStringLiteral("showCaption"), lineShowCaption());
+    if (lineIntent() != Unknown)
+        lineElement.setAttribute(QStringLiteral("intent"), lineIntent());
+
+    // append the list of points
+    const QVector<QPointF> points = linePoints();
+    if (points.count() > 1) {
+        QVector<QPointF>::const_iterator it = points.begin(), end = points.end();
+        while (it != end) {
+            const QPointF &p = *it;
+            QDomElement pElement = document.createElement(QStringLiteral("point"));
+            lineElement.appendChild(pElement);
+            pElement.setAttribute(QStringLiteral("x"), QString::number(p.x()));
+            pElement.setAttribute(QStringLiteral("y"), QString::number(p.y()));
+            ++it;
+        }
+    }
+}
+
+Annotation::SubType LineAnnotation::subType() const
+{
+    return ALine;
+}
+
+LineAnnotation::LineType LineAnnotation::lineType() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineType;
+
+    return (d->pdfAnnot->getType() == Annot::typeLine) ? LineAnnotation::StraightLine : LineAnnotation::Polyline;
+}
+
+void LineAnnotation::setLineType(LineAnnotation::LineType type)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineType = type;
+        return;
+    }
+
+    // Type cannot be changed if annotation is already tied
+}
+
+QVector<QPointF> LineAnnotation::linePoints() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->linePoints;
+
+    double MTX[6];
+    d->fillTransformationMTX(MTX);
+
+    QVector<QPointF> res;
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        QPointF p;
+        XPDFReader::transform(MTX, lineann->getX1(), lineann->getY1(), p);
+        res.append(p);
+        XPDFReader::transform(MTX, lineann->getX2(), lineann->getY2(), p);
+        res.append(p);
+    } else {
+        const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
+        const AnnotPath *vertices = polyann->getVertices();
+
+        for (int i = 0; i < vertices->getCoordsLength(); ++i) {
+            QPointF p;
+            XPDFReader::transform(MTX, vertices->getX(i), vertices->getY(i), p);
+            res.append(p);
+        }
+    }
+
+    return res;
+}
+
+void LineAnnotation::setLinePoints(const QVector<QPointF> &points)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->linePoints = points;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        if (points.size() != 2) {
+            error(errSyntaxError, -1, "Expected two points for a straight line");
+            return;
+        }
+        double x1, y1, x2, y2;
+        double MTX[6];
+        d->fillTransformationMTX(MTX);
+        XPDFReader::invTransform(MTX, points.first(), x1, y1);
+        XPDFReader::invTransform(MTX, points.last(), x2, y2);
+        lineann->setVertices(x1, y1, x2, y2);
+    } else {
+        AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
+        AnnotPath *p = d->toAnnotPath(points);
+        polyann->setVertices(p);
+        delete p;
+    }
+}
+
+LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineStartStyle;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        return (LineAnnotation::TermStyle)lineann->getStartStyle();
+    } else {
+        const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
+        return (LineAnnotation::TermStyle)polyann->getStartStyle();
+    }
+}
+
+void LineAnnotation::setLineStartStyle(LineAnnotation::TermStyle style)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineStartStyle = style;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setStartEndStyle((AnnotLineEndingStyle)style, lineann->getEndStyle());
+    } else {
+        AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
+        polyann->setStartEndStyle((AnnotLineEndingStyle)style, polyann->getEndStyle());
+    }
+}
+
+LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineEndStyle;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        return (LineAnnotation::TermStyle)lineann->getEndStyle();
+    } else {
+        const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
+        return (LineAnnotation::TermStyle)polyann->getEndStyle();
+    }
+}
+
+void LineAnnotation::setLineEndStyle(LineAnnotation::TermStyle style)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineEndStyle = style;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setStartEndStyle(lineann->getStartStyle(), (AnnotLineEndingStyle)style);
+    } else {
+        AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
+        polyann->setStartEndStyle(polyann->getStartStyle(), (AnnotLineEndingStyle)style);
+    }
+}
+
+bool LineAnnotation::isLineClosed() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineClosed;
+
+    return d->pdfAnnot->getType() == Annot::typePolygon;
+}
+
+void LineAnnotation::setLineClosed(bool closed)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineClosed = closed;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() != Annot::typeLine) {
+        AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
+
+        // Set new subtype and switch intent if necessary
+        if (closed) {
+            polyann->setType(Annot::typePolygon);
+            if (polyann->getIntent() == AnnotPolygon::polylineDimension)
+                polyann->setIntent(AnnotPolygon::polygonDimension);
+        } else {
+            polyann->setType(Annot::typePolyLine);
+            if (polyann->getIntent() == AnnotPolygon::polygonDimension)
+                polyann->setIntent(AnnotPolygon::polylineDimension);
+        }
+    }
+}
+
+QColor LineAnnotation::lineInnerColor() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineInnerColor;
+
+    AnnotColor *c;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        c = lineann->getInteriorColor();
+    } else {
+        const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
+        c = polyann->getInteriorColor();
+    }
+
+    return convertAnnotColor(c);
+}
+
+void LineAnnotation::setLineInnerColor(const QColor &color)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineInnerColor = color;
+        return;
+    }
+
+    auto c = convertQColor(color);
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setInteriorColor(std::move(c));
+    } else {
+        AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
+        polyann->setInteriorColor(std::move(c));
+    }
+}
+
+double LineAnnotation::lineLeadingForwardPoint() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineLeadingFwdPt;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        return lineann->getLeaderLineLength();
+    }
+
+    return 0;
+}
+
+void LineAnnotation::setLineLeadingForwardPoint(double point)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineLeadingFwdPt = point;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setLeaderLineLength(point);
+    }
+}
+
+double LineAnnotation::lineLeadingBackPoint() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineLeadingBackPt;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        return lineann->getLeaderLineExtension();
+    }
+
+    return 0;
+}
+
+void LineAnnotation::setLineLeadingBackPoint(double point)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineLeadingBackPt = point;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setLeaderLineExtension(point);
+    }
+}
+
+bool LineAnnotation::lineShowCaption() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineShowCaption;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        return lineann->getCaption();
+    }
+
+    return false;
+}
+
+void LineAnnotation::setLineShowCaption(bool show)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineShowCaption = show;
+        return;
+    }
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setCaption(show);
+    }
+}
+
+LineAnnotation::LineIntent LineAnnotation::lineIntent() const
+{
+    Q_D(const LineAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->lineIntent;
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
+        return (LineAnnotation::LineIntent)(lineann->getIntent() + 1);
+    } else {
+        const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
+        if (polyann->getIntent() == AnnotPolygon::polygonCloud)
+            return LineAnnotation::PolygonCloud;
+        else // AnnotPolygon::polylineDimension, AnnotPolygon::polygonDimension
+            return LineAnnotation::Dimension;
+    }
+}
+
+void LineAnnotation::setLineIntent(LineAnnotation::LineIntent intent)
+{
+    Q_D(LineAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->lineIntent = intent;
+        return;
+    }
+
+    if (intent == LineAnnotation::Unknown)
+        return; // Do not set (actually, it should clear the property)
+
+    if (d->pdfAnnot->getType() == Annot::typeLine) {
+        AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
+        lineann->setIntent((AnnotLine::AnnotLineIntent)(intent - 1));
+    } else {
+        AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
+        if (intent == LineAnnotation::PolygonCloud)
+            polyann->setIntent(AnnotPolygon::polygonCloud);
+        else // LineAnnotation::Dimension
+        {
+            if (d->pdfAnnot->getType() == Annot::typePolygon)
+                polyann->setIntent(AnnotPolygon::polygonDimension);
+            else // Annot::typePolyLine
+                polyann->setIntent(AnnotPolygon::polylineDimension);
+        }
+    }
+}
+
+/** GeomAnnotation [Annotation] */
+class GeomAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    GeomAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields (note uses border for rendering style)
+    GeomAnnotation::GeomType geomType;
+    QColor geomInnerColor;
+};
+
+GeomAnnotationPrivate::GeomAnnotationPrivate() : AnnotationPrivate(), geomType(GeomAnnotation::InscribedSquare) { }
+
+Annotation *GeomAnnotationPrivate::makeAlias()
+{
+    return new GeomAnnotation(*this);
+}
+
+Annot *GeomAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    // Setters are defined in the public class
+    GeomAnnotation *q = static_cast<GeomAnnotation *>(makeAlias());
+
+    // Set page and document
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    Annot::AnnotSubtype type;
+    if (geomType == GeomAnnotation::InscribedSquare)
+        type = Annot::typeSquare;
+    else // GeomAnnotation::InscribedCircle
+        type = Annot::typeCircle;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    pdfAnnot = new AnnotGeometry(destPage->getDoc(), &rect, type);
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setGeomInnerColor(geomInnerColor);
+
+    delete q;
+    return pdfAnnot;
+}
+
+GeomAnnotation::GeomAnnotation() : Annotation(*new GeomAnnotationPrivate()) { }
+
+GeomAnnotation::GeomAnnotation(GeomAnnotationPrivate &dd) : Annotation(dd) { }
+
+GeomAnnotation::GeomAnnotation(const QDomNode &node) : Annotation(*new GeomAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'geom' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("geom"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("type")))
+            setGeomType((GeomAnnotation::GeomType)e.attribute(QStringLiteral("type")).toInt());
+        if (e.hasAttribute(QStringLiteral("color")))
+            setGeomInnerColor(QColor(e.attribute(QStringLiteral("color"))));
+
+        // loading complete
+        break;
+    }
+}
+
+GeomAnnotation::~GeomAnnotation() { }
+
+void GeomAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [geom] element
+    QDomElement geomElement = document.createElement(QStringLiteral("geom"));
+    node.appendChild(geomElement);
+
+    // append the optional attributes
+    if (geomType() != InscribedSquare)
+        geomElement.setAttribute(QStringLiteral("type"), (int)geomType());
+    if (geomInnerColor().isValid())
+        geomElement.setAttribute(QStringLiteral("color"), geomInnerColor().name());
+}
+
+Annotation::SubType GeomAnnotation::subType() const
+{
+    return AGeom;
+}
+
+GeomAnnotation::GeomType GeomAnnotation::geomType() const
+{
+    Q_D(const GeomAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->geomType;
+
+    if (d->pdfAnnot->getType() == Annot::typeSquare)
+        return GeomAnnotation::InscribedSquare;
+    else // Annot::typeCircle
+        return GeomAnnotation::InscribedCircle;
+}
+
+void GeomAnnotation::setGeomType(GeomAnnotation::GeomType type)
+{
+    Q_D(GeomAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->geomType = type;
+        return;
+    }
+
+    AnnotGeometry *geomann = static_cast<AnnotGeometry *>(d->pdfAnnot);
+    if (type == GeomAnnotation::InscribedSquare)
+        geomann->setType(Annot::typeSquare);
+    else // GeomAnnotation::InscribedCircle
+        geomann->setType(Annot::typeCircle);
+}
+
+QColor GeomAnnotation::geomInnerColor() const
+{
+    Q_D(const GeomAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->geomInnerColor;
+
+    const AnnotGeometry *geomann = static_cast<const AnnotGeometry *>(d->pdfAnnot);
+    return convertAnnotColor(geomann->getInteriorColor());
+}
+
+void GeomAnnotation::setGeomInnerColor(const QColor &color)
+{
+    Q_D(GeomAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->geomInnerColor = color;
+        return;
+    }
+
+    AnnotGeometry *geomann = static_cast<AnnotGeometry *>(d->pdfAnnot);
+    geomann->setInteriorColor(convertQColor(color));
+}
+
+/** HighlightAnnotation [Annotation] */
+class HighlightAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    HighlightAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    HighlightAnnotation::HighlightType highlightType;
+    QList<HighlightAnnotation::Quad> highlightQuads; // not empty
+
+    // helpers
+    static Annot::AnnotSubtype toAnnotSubType(HighlightAnnotation::HighlightType type);
+    QList<HighlightAnnotation::Quad> fromQuadrilaterals(AnnotQuadrilaterals *quads) const;
+    AnnotQuadrilaterals *toQuadrilaterals(const QList<HighlightAnnotation::Quad> &quads) const;
+};
+
+HighlightAnnotationPrivate::HighlightAnnotationPrivate() : AnnotationPrivate(), highlightType(HighlightAnnotation::Highlight) { }
+
+Annotation *HighlightAnnotationPrivate::makeAlias()
+{
+    return new HighlightAnnotation(*this);
+}
+
+Annot::AnnotSubtype HighlightAnnotationPrivate::toAnnotSubType(HighlightAnnotation::HighlightType type)
+{
+    switch (type) {
+    default: // HighlightAnnotation::Highlight:
+        return Annot::typeHighlight;
+    case HighlightAnnotation::Underline:
+        return Annot::typeUnderline;
+    case HighlightAnnotation::Squiggly:
+        return Annot::typeSquiggly;
+    case HighlightAnnotation::StrikeOut:
+        return Annot::typeStrikeOut;
+    }
+}
+
+QList<HighlightAnnotation::Quad> HighlightAnnotationPrivate::fromQuadrilaterals(AnnotQuadrilaterals *hlquads) const
+{
+    QList<HighlightAnnotation::Quad> quads;
+
+    if (!hlquads || !hlquads->getQuadrilateralsLength())
+        return quads;
+    const int quadsCount = hlquads->getQuadrilateralsLength();
+
+    double MTX[6];
+    fillTransformationMTX(MTX);
+
+    quads.reserve(quadsCount);
+    for (int q = 0; q < quadsCount; ++q) {
+        HighlightAnnotation::Quad quad;
+        XPDFReader::transform(MTX, hlquads->getX1(q), hlquads->getY1(q), quad.points[0]);
+        XPDFReader::transform(MTX, hlquads->getX2(q), hlquads->getY2(q), quad.points[1]);
+        XPDFReader::transform(MTX, hlquads->getX3(q), hlquads->getY3(q), quad.points[2]);
+        XPDFReader::transform(MTX, hlquads->getX4(q), hlquads->getY4(q), quad.points[3]);
+        // ### PDF1.6 specs says that point are in ccw order, but in fact
+        // points 3 and 4 are swapped in every PDF around!
+        QPointF tmpPoint = quad.points[2];
+        quad.points[2] = quad.points[3];
+        quad.points[3] = tmpPoint;
+        // initialize other properties and append quad
+        quad.capStart = true; // unlinked quads are always capped
+        quad.capEnd = true; // unlinked quads are always capped
+        quad.feather = 0.1; // default feather
+        quads.append(quad);
+    }
+
+    return quads;
+}
+
+AnnotQuadrilaterals *HighlightAnnotationPrivate::toQuadrilaterals(const QList<HighlightAnnotation::Quad> &quads) const
+{
+    const int count = quads.size();
+    auto ac = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(count);
+
+    double MTX[6];
+    fillTransformationMTX(MTX);
+
+    int pos = 0;
+    foreach (const HighlightAnnotation::Quad &q, quads) {
+        double x1, y1, x2, y2, x3, y3, x4, y4;
+        XPDFReader::invTransform(MTX, q.points[0], x1, y1);
+        XPDFReader::invTransform(MTX, q.points[1], x2, y2);
+        // Swap points 3 and 4 (see HighlightAnnotationPrivate::fromQuadrilaterals)
+        XPDFReader::invTransform(MTX, q.points[3], x3, y3);
+        XPDFReader::invTransform(MTX, q.points[2], x4, y4);
+        ac[pos++] = AnnotQuadrilaterals::AnnotQuadrilateral(x1, y1, x2, y2, x3, y3, x4, y4);
+    }
+
+    return new AnnotQuadrilaterals(std::move(ac), count);
+}
+
+Annot *HighlightAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    // Setters are defined in the public class
+    HighlightAnnotation *q = static_cast<HighlightAnnotation *>(makeAlias());
+
+    // Set page and document
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    pdfAnnot = new AnnotTextMarkup(destPage->getDoc(), &rect, toAnnotSubType(highlightType));
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setHighlightQuads(highlightQuads);
+
+    highlightQuads.clear(); // Free up memory
+
+    delete q;
+
+    return pdfAnnot;
+}
+
+HighlightAnnotation::HighlightAnnotation() : Annotation(*new HighlightAnnotationPrivate()) { }
+
+HighlightAnnotation::HighlightAnnotation(HighlightAnnotationPrivate &dd) : Annotation(dd) { }
+
+HighlightAnnotation::HighlightAnnotation(const QDomNode &node) : Annotation(*new HighlightAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'hl' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("hl"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("type")))
+            setHighlightType((HighlightAnnotation::HighlightType)e.attribute(QStringLiteral("type")).toInt());
+
+        // parse all 'quad' subnodes
+        QList<HighlightAnnotation::Quad> quads;
+        QDomNode quadNode = e.firstChild();
+        for (; quadNode.isElement(); quadNode = quadNode.nextSibling()) {
+            QDomElement qe = quadNode.toElement();
+            if (qe.tagName() != QLatin1String("quad"))
+                continue;
+
+            Quad q;
+            q.points[0].setX(qe.attribute(QStringLiteral("ax"), QStringLiteral("0.0")).toDouble());
+            q.points[0].setY(qe.attribute(QStringLiteral("ay"), QStringLiteral("0.0")).toDouble());
+            q.points[1].setX(qe.attribute(QStringLiteral("bx"), QStringLiteral("0.0")).toDouble());
+            q.points[1].setY(qe.attribute(QStringLiteral("by"), QStringLiteral("0.0")).toDouble());
+            q.points[2].setX(qe.attribute(QStringLiteral("cx"), QStringLiteral("0.0")).toDouble());
+            q.points[2].setY(qe.attribute(QStringLiteral("cy"), QStringLiteral("0.0")).toDouble());
+            q.points[3].setX(qe.attribute(QStringLiteral("dx"), QStringLiteral("0.0")).toDouble());
+            q.points[3].setY(qe.attribute(QStringLiteral("dy"), QStringLiteral("0.0")).toDouble());
+            q.capStart = qe.hasAttribute(QStringLiteral("start"));
+            q.capEnd = qe.hasAttribute(QStringLiteral("end"));
+            q.feather = qe.attribute(QStringLiteral("feather"), QStringLiteral("0.1")).toDouble();
+            quads.append(q);
+        }
+        setHighlightQuads(quads);
+
+        // loading complete
+        break;
+    }
+}
+
+HighlightAnnotation::~HighlightAnnotation() { }
+
+void HighlightAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [hl] element
+    QDomElement hlElement = document.createElement(QStringLiteral("hl"));
+    node.appendChild(hlElement);
+
+    // append the optional attributes
+    if (highlightType() != Highlight)
+        hlElement.setAttribute(QStringLiteral("type"), (int)highlightType());
+
+    const QList<HighlightAnnotation::Quad> quads = highlightQuads();
+    if (quads.count() < 1)
+        return;
+    // append highlight quads, all children describe quads
+    QList<HighlightAnnotation::Quad>::const_iterator it = quads.begin(), end = quads.end();
+    for (; it != end; ++it) {
+        QDomElement quadElement = document.createElement(QStringLiteral("quad"));
+        hlElement.appendChild(quadElement);
+        const Quad &q = *it;
+        quadElement.setAttribute(QStringLiteral("ax"), QString::number(q.points[0].x()));
+        quadElement.setAttribute(QStringLiteral("ay"), QString::number(q.points[0].y()));
+        quadElement.setAttribute(QStringLiteral("bx"), QString::number(q.points[1].x()));
+        quadElement.setAttribute(QStringLiteral("by"), QString::number(q.points[1].y()));
+        quadElement.setAttribute(QStringLiteral("cx"), QString::number(q.points[2].x()));
+        quadElement.setAttribute(QStringLiteral("cy"), QString::number(q.points[2].y()));
+        quadElement.setAttribute(QStringLiteral("dx"), QString::number(q.points[3].x()));
+        quadElement.setAttribute(QStringLiteral("dy"), QString::number(q.points[3].y()));
+        if (q.capStart)
+            quadElement.setAttribute(QStringLiteral("start"), 1);
+        if (q.capEnd)
+            quadElement.setAttribute(QStringLiteral("end"), 1);
+        quadElement.setAttribute(QStringLiteral("feather"), QString::number(q.feather));
+    }
+}
+
+Annotation::SubType HighlightAnnotation::subType() const
+{
+    return AHighlight;
+}
+
+HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const
+{
+    Q_D(const HighlightAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->highlightType;
+
+    Annot::AnnotSubtype subType = d->pdfAnnot->getType();
+
+    if (subType == Annot::typeHighlight)
+        return HighlightAnnotation::Highlight;
+    else if (subType == Annot::typeUnderline)
+        return HighlightAnnotation::Underline;
+    else if (subType == Annot::typeSquiggly)
+        return HighlightAnnotation::Squiggly;
+    else // Annot::typeStrikeOut
+        return HighlightAnnotation::StrikeOut;
+}
+
+void HighlightAnnotation::setHighlightType(HighlightAnnotation::HighlightType type)
+{
+    Q_D(HighlightAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->highlightType = type;
+        return;
+    }
+
+    AnnotTextMarkup *hlann = static_cast<AnnotTextMarkup *>(d->pdfAnnot);
+    hlann->setType(HighlightAnnotationPrivate::toAnnotSubType(type));
+}
+
+QList<HighlightAnnotation::Quad> HighlightAnnotation::highlightQuads() const
+{
+    Q_D(const HighlightAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->highlightQuads;
+
+    const AnnotTextMarkup *hlann = static_cast<AnnotTextMarkup *>(d->pdfAnnot);
+    return d->fromQuadrilaterals(hlann->getQuadrilaterals());
+}
+
+void HighlightAnnotation::setHighlightQuads(const QList<HighlightAnnotation::Quad> &quads)
+{
+    Q_D(HighlightAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->highlightQuads = quads;
+        return;
+    }
+
+    AnnotTextMarkup *hlann = static_cast<AnnotTextMarkup *>(d->pdfAnnot);
+    AnnotQuadrilaterals *quadrilaterals = d->toQuadrilaterals(quads);
+    hlann->setQuadrilaterals(quadrilaterals);
+    delete quadrilaterals;
+}
+
+/** StampAnnotation [Annotation] */
+class StampAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    StampAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    QString stampIconName;
+};
+
+StampAnnotationPrivate::StampAnnotationPrivate() : AnnotationPrivate(), stampIconName(QStringLiteral("Draft")) { }
+
+Annotation *StampAnnotationPrivate::makeAlias()
+{
+    return new StampAnnotation(*this);
+}
+
+Annot *StampAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    StampAnnotation *q = static_cast<StampAnnotation *>(makeAlias());
+
+    // Set page and document
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    pdfAnnot = new AnnotStamp(destPage->getDoc(), &rect);
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setStampIconName(stampIconName);
+
+    delete q;
+
+    stampIconName.clear(); // Free up memory
+
+    return pdfAnnot;
+}
+
+StampAnnotation::StampAnnotation() : Annotation(*new StampAnnotationPrivate()) { }
+
+StampAnnotation::StampAnnotation(StampAnnotationPrivate &dd) : Annotation(dd) { }
+
+StampAnnotation::StampAnnotation(const QDomNode &node) : Annotation(*new StampAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'stamp' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("stamp"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("icon")))
+            setStampIconName(e.attribute(QStringLiteral("icon")));
+
+        // loading complete
+        break;
+    }
+}
+
+StampAnnotation::~StampAnnotation() { }
+
+void StampAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [stamp] element
+    QDomElement stampElement = document.createElement(QStringLiteral("stamp"));
+    node.appendChild(stampElement);
+
+    // append the optional attributes
+    if (stampIconName() != QLatin1String("Draft"))
+        stampElement.setAttribute(QStringLiteral("icon"), stampIconName());
+}
+
+Annotation::SubType StampAnnotation::subType() const
+{
+    return AStamp;
+}
+
+QString StampAnnotation::stampIconName() const
+{
+    Q_D(const StampAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->stampIconName;
+
+    const AnnotStamp *stampann = static_cast<const AnnotStamp *>(d->pdfAnnot);
+    return QString::fromLatin1(stampann->getIcon()->c_str());
+}
+
+void StampAnnotation::setStampIconName(const QString &name)
+{
+    Q_D(StampAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->stampIconName = name;
+        return;
+    }
+
+    AnnotStamp *stampann = static_cast<AnnotStamp *>(d->pdfAnnot);
+    QByteArray encoded = name.toLatin1();
+    GooString s(encoded.constData());
+    stampann->setIcon(&s);
+}
+
+/** InkAnnotation [Annotation] */
+class InkAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    InkAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    QList<QVector<QPointF>> inkPaths;
+
+    // helper
+    AnnotPath **toAnnotPaths(const QList<QVector<QPointF>> &paths);
+};
+
+InkAnnotationPrivate::InkAnnotationPrivate() : AnnotationPrivate() { }
+
+Annotation *InkAnnotationPrivate::makeAlias()
+{
+    return new InkAnnotation(*this);
+}
+
+// Note: Caller is required to delete array elements and the array itself after use
+AnnotPath **InkAnnotationPrivate::toAnnotPaths(const QList<QVector<QPointF>> &paths)
+{
+    const int pathsNumber = paths.size();
+    AnnotPath **res = new AnnotPath *[pathsNumber];
+    for (int i = 0; i < pathsNumber; ++i)
+        res[i] = toAnnotPath(paths[i]);
+    return res;
+}
+
+Annot *InkAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    // Setters are defined in the public class
+    InkAnnotation *q = static_cast<InkAnnotation *>(makeAlias());
+
+    // Set page and document
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    pdfAnnot = new AnnotInk(destPage->getDoc(), &rect);
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setInkPaths(inkPaths);
+
+    inkPaths.clear(); // Free up memory
+
+    delete q;
+
+    return pdfAnnot;
+}
+
+InkAnnotation::InkAnnotation() : Annotation(*new InkAnnotationPrivate()) { }
+
+InkAnnotation::InkAnnotation(InkAnnotationPrivate &dd) : Annotation(dd) { }
+
+InkAnnotation::InkAnnotation(const QDomNode &node) : Annotation(*new InkAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'ink' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("ink"))
+            continue;
+
+        // parse the 'path' subnodes
+        QList<QVector<QPointF>> paths;
+        QDomNode pathNode = e.firstChild();
+        while (pathNode.isElement()) {
+            QDomElement pathElement = pathNode.toElement();
+            pathNode = pathNode.nextSibling();
+
+            if (pathElement.tagName() != QLatin1String("path"))
+                continue;
+
+            // build each path parsing 'point' subnodes
+            QVector<QPointF> path;
+            QDomNode pointNode = pathElement.firstChild();
+            while (pointNode.isElement()) {
+                QDomElement pointElement = pointNode.toElement();
+                pointNode = pointNode.nextSibling();
+
+                if (pointElement.tagName() != QLatin1String("point"))
+                    continue;
+
+                QPointF p(pointElement.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble(), pointElement.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble());
+                path.append(p);
+            }
+
+            // add the path to the path list if it contains at least 2 nodes
+            if (path.count() >= 2)
+                paths.append(path);
+        }
+        setInkPaths(paths);
+
+        // loading complete
+        break;
+    }
+}
+
+InkAnnotation::~InkAnnotation() { }
+
+void InkAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [ink] element
+    QDomElement inkElement = document.createElement(QStringLiteral("ink"));
+    node.appendChild(inkElement);
+
+    // append the optional attributes
+    const QList<QVector<QPointF>> paths = inkPaths();
+    if (paths.count() < 1)
+        return;
+    QList<QVector<QPointF>>::const_iterator pIt = paths.begin(), pEnd = paths.end();
+    for (; pIt != pEnd; ++pIt) {
+        QDomElement pathElement = document.createElement(QStringLiteral("path"));
+        inkElement.appendChild(pathElement);
+        const QVector<QPointF> &path = *pIt;
+        QVector<QPointF>::const_iterator iIt = path.begin(), iEnd = path.end();
+        for (; iIt != iEnd; ++iIt) {
+            const QPointF &point = *iIt;
+            QDomElement pointElement = document.createElement(QStringLiteral("point"));
+            pathElement.appendChild(pointElement);
+            pointElement.setAttribute(QStringLiteral("x"), QString::number(point.x()));
+            pointElement.setAttribute(QStringLiteral("y"), QString::number(point.y()));
+        }
+    }
+}
+
+Annotation::SubType InkAnnotation::subType() const
+{
+    return AInk;
+}
+
+QList<QVector<QPointF>> InkAnnotation::inkPaths() const
+{
+    Q_D(const InkAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->inkPaths;
+
+    const AnnotInk *inkann = static_cast<const AnnotInk *>(d->pdfAnnot);
+
+    const AnnotPath *const *paths = inkann->getInkList();
+    if (!paths || !inkann->getInkListLength())
+        return {};
+
+    double MTX[6];
+    d->fillTransformationMTX(MTX);
+
+    const int pathsNumber = inkann->getInkListLength();
+    QList<QVector<QPointF>> inkPaths;
+    inkPaths.reserve(pathsNumber);
+    for (int m = 0; m < pathsNumber; ++m) {
+        // transform each path in a list of normalized points ..
+        QVector<QPointF> localList;
+        const AnnotPath *path = paths[m];
+        const int pointsNumber = path ? path->getCoordsLength() : 0;
+        for (int n = 0; n < pointsNumber; ++n) {
+            QPointF point;
+            XPDFReader::transform(MTX, path->getX(n), path->getY(n), point);
+            localList.append(point);
+        }
+        // ..and add it to the annotation
+        inkPaths.append(localList);
+    }
+    return inkPaths;
+}
+
+void InkAnnotation::setInkPaths(const QList<QVector<QPointF>> &paths)
+{
+    Q_D(InkAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->inkPaths = paths;
+        return;
+    }
+
+    AnnotInk *inkann = static_cast<AnnotInk *>(d->pdfAnnot);
+    AnnotPath **annotpaths = d->toAnnotPaths(paths);
+    const int pathsNumber = paths.size();
+    inkann->setInkList(annotpaths, pathsNumber);
+
+    for (int i = 0; i < pathsNumber; ++i)
+        delete annotpaths[i];
+    delete[] annotpaths;
+}
+
+/** LinkAnnotation [Annotation] */
+class LinkAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    LinkAnnotationPrivate();
+    ~LinkAnnotationPrivate() override;
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    Link *linkDestination;
+    LinkAnnotation::HighlightMode linkHLMode;
+    QPointF linkRegion[4];
+};
+
+LinkAnnotationPrivate::LinkAnnotationPrivate() : AnnotationPrivate(), linkDestination(nullptr), linkHLMode(LinkAnnotation::Invert) { }
+
+LinkAnnotationPrivate::~LinkAnnotationPrivate()
+{
+    delete linkDestination;
+}
+
+Annotation *LinkAnnotationPrivate::makeAlias()
+{
+    return new LinkAnnotation(*this);
+}
+
+Annot *LinkAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    return nullptr; // Not implemented
+}
+
+LinkAnnotation::LinkAnnotation() : Annotation(*new LinkAnnotationPrivate()) { }
+
+LinkAnnotation::LinkAnnotation(LinkAnnotationPrivate &dd) : Annotation(dd) { }
+
+LinkAnnotation::LinkAnnotation(const QDomNode &node) : Annotation(*new LinkAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'link' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("link"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("hlmode")))
+            setLinkHighlightMode((LinkAnnotation::HighlightMode)e.attribute(QStringLiteral("hlmode")).toInt());
+
+        // parse all 'quad' subnodes
+        QDomNode quadNode = e.firstChild();
+        for (; quadNode.isElement(); quadNode = quadNode.nextSibling()) {
+            QDomElement qe = quadNode.toElement();
+            if (qe.tagName() == QLatin1String("quad")) {
+                setLinkRegionPoint(0, QPointF(qe.attribute(QStringLiteral("ax"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("ay"), QStringLiteral("0.0")).toDouble()));
+                setLinkRegionPoint(1, QPointF(qe.attribute(QStringLiteral("bx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("by"), QStringLiteral("0.0")).toDouble()));
+                setLinkRegionPoint(2, QPointF(qe.attribute(QStringLiteral("cx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("cy"), QStringLiteral("0.0")).toDouble()));
+                setLinkRegionPoint(3, QPointF(qe.attribute(QStringLiteral("dx"), QStringLiteral("0.0")).toDouble(), qe.attribute(QStringLiteral("dy"), QStringLiteral("0.0")).toDouble()));
+            } else if (qe.tagName() == QLatin1String("link")) {
+                QString type = qe.attribute(QStringLiteral("type"));
+                if (type == QLatin1String("GoTo")) {
+                    Poppler::LinkGoto *go = new Poppler::LinkGoto(QRect(), qe.attribute(QStringLiteral("filename")), LinkDestination(qe.attribute(QStringLiteral("destination"))));
+                    setLinkDestination(go);
+                } else if (type == QLatin1String("Exec")) {
+                    Poppler::LinkExecute *exec = new Poppler::LinkExecute(QRect(), qe.attribute(QStringLiteral("filename")), qe.attribute(QStringLiteral("parameters")));
+                    setLinkDestination(exec);
+                } else if (type == QLatin1String("Browse")) {
+                    Poppler::LinkBrowse *browse = new Poppler::LinkBrowse(QRect(), qe.attribute(QStringLiteral("url")));
+                    setLinkDestination(browse);
+                } else if (type == QLatin1String("Action")) {
+                    Poppler::LinkAction::ActionType act;
+                    QString actString = qe.attribute(QStringLiteral("action"));
+                    bool found = true;
+                    if (actString == QLatin1String("PageFirst"))
+                        act = Poppler::LinkAction::PageFirst;
+                    else if (actString == QLatin1String("PagePrev"))
+                        act = Poppler::LinkAction::PagePrev;
+                    else if (actString == QLatin1String("PageNext"))
+                        act = Poppler::LinkAction::PageNext;
+                    else if (actString == QLatin1String("PageLast"))
+                        act = Poppler::LinkAction::PageLast;
+                    else if (actString == QLatin1String("HistoryBack"))
+                        act = Poppler::LinkAction::HistoryBack;
+                    else if (actString == QLatin1String("HistoryForward"))
+                        act = Poppler::LinkAction::HistoryForward;
+                    else if (actString == QLatin1String("Quit"))
+                        act = Poppler::LinkAction::Quit;
+                    else if (actString == QLatin1String("Presentation"))
+                        act = Poppler::LinkAction::Presentation;
+                    else if (actString == QLatin1String("EndPresentation"))
+                        act = Poppler::LinkAction::EndPresentation;
+                    else if (actString == QLatin1String("Find"))
+                        act = Poppler::LinkAction::Find;
+                    else if (actString == QLatin1String("GoToPage"))
+                        act = Poppler::LinkAction::GoToPage;
+                    else if (actString == QLatin1String("Close"))
+                        act = Poppler::LinkAction::Close;
+                    else if (actString == QLatin1String("Print"))
+                        act = Poppler::LinkAction::Print;
+                    else
+                        found = false;
+                    if (found) {
+                        Poppler::LinkAction *action = new Poppler::LinkAction(QRect(), act);
+                        setLinkDestination(action);
+                    }
+                } else {
+                    qWarning("Loading annotations of type %s from DOM nodes is not yet implemented.", type.toLocal8Bit().constData());
+                }
+            }
+        }
+
+        // loading complete
+        break;
+    }
+}
+
+LinkAnnotation::~LinkAnnotation() { }
+
+void LinkAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [hl] element
+    QDomElement linkElement = document.createElement(QStringLiteral("link"));
+    node.appendChild(linkElement);
+
+    // append the optional attributes
+    if (linkHighlightMode() != Invert)
+        linkElement.setAttribute(QStringLiteral("hlmode"), (int)linkHighlightMode());
+
+    // saving region
+    QDomElement quadElement = document.createElement(QStringLiteral("quad"));
+    linkElement.appendChild(quadElement);
+    quadElement.setAttribute(QStringLiteral("ax"), QString::number(linkRegionPoint(0).x()));
+    quadElement.setAttribute(QStringLiteral("ay"), QString::number(linkRegionPoint(0).y()));
+    quadElement.setAttribute(QStringLiteral("bx"), QString::number(linkRegionPoint(1).x()));
+    quadElement.setAttribute(QStringLiteral("by"), QString::number(linkRegionPoint(1).y()));
+    quadElement.setAttribute(QStringLiteral("cx"), QString::number(linkRegionPoint(2).x()));
+    quadElement.setAttribute(QStringLiteral("cy"), QString::number(linkRegionPoint(2).y()));
+    quadElement.setAttribute(QStringLiteral("dx"), QString::number(linkRegionPoint(3).x()));
+    quadElement.setAttribute(QStringLiteral("dy"), QString::number(linkRegionPoint(3).y()));
+
+    // saving link
+    QDomElement hyperlinkElement = document.createElement(QStringLiteral("link"));
+    linkElement.appendChild(hyperlinkElement);
+    if (linkDestination()) {
+        switch (linkDestination()->linkType()) {
+        case Poppler::Link::Goto: {
+            Poppler::LinkGoto *go = static_cast<Poppler::LinkGoto *>(linkDestination());
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("GoTo"));
+            hyperlinkElement.setAttribute(QStringLiteral("filename"), go->fileName());
+            hyperlinkElement.setAttribute(QStringLiteral("destination"), go->destination().toString());
+            break;
+        }
+        case Poppler::Link::Execute: {
+            Poppler::LinkExecute *exec = static_cast<Poppler::LinkExecute *>(linkDestination());
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Exec"));
+            hyperlinkElement.setAttribute(QStringLiteral("filename"), exec->fileName());
+            hyperlinkElement.setAttribute(QStringLiteral("parameters"), exec->parameters());
+            break;
+        }
+        case Poppler::Link::Browse: {
+            Poppler::LinkBrowse *browse = static_cast<Poppler::LinkBrowse *>(linkDestination());
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Browse"));
+            hyperlinkElement.setAttribute(QStringLiteral("url"), browse->url());
+            break;
+        }
+        case Poppler::Link::Action: {
+            Poppler::LinkAction *action = static_cast<Poppler::LinkAction *>(linkDestination());
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Action"));
+            switch (action->actionType()) {
+            case Poppler::LinkAction::PageFirst:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("PageFirst"));
+                break;
+            case Poppler::LinkAction::PagePrev:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("PagePrev"));
+                break;
+            case Poppler::LinkAction::PageNext:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("PageNext"));
+                break;
+            case Poppler::LinkAction::PageLast:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("PageLast"));
+                break;
+            case Poppler::LinkAction::HistoryBack:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("HistoryBack"));
+                break;
+            case Poppler::LinkAction::HistoryForward:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("HistoryForward"));
+                break;
+            case Poppler::LinkAction::Quit:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("Quit"));
+                break;
+            case Poppler::LinkAction::Presentation:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("Presentation"));
+                break;
+            case Poppler::LinkAction::EndPresentation:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("EndPresentation"));
+                break;
+            case Poppler::LinkAction::Find:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("Find"));
+                break;
+            case Poppler::LinkAction::GoToPage:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("GoToPage"));
+                break;
+            case Poppler::LinkAction::Close:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("Close"));
+                break;
+            case Poppler::LinkAction::Print:
+                hyperlinkElement.setAttribute(QStringLiteral("action"), QStringLiteral("Print"));
+                break;
+            }
+            break;
+        }
+        case Poppler::Link::Movie: {
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Movie"));
+            break;
+        }
+        case Poppler::Link::Rendition: {
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Rendition"));
+            break;
+        }
+        case Poppler::Link::Sound: {
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Sound"));
+            break;
+        }
+        case Poppler::Link::JavaScript: {
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("JavaScript"));
+            break;
+        }
+        case Poppler::Link::OCGState: {
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("OCGState"));
+            break;
+        }
+        case Poppler::Link::Hide: {
+            hyperlinkElement.setAttribute(QStringLiteral("type"), QStringLiteral("Hide"));
+            break;
+        }
+        case Poppler::Link::None:
+            break;
+        }
+    }
+}
+
+Annotation::SubType LinkAnnotation::subType() const
+{
+    return ALink;
+}
+
+Link *LinkAnnotation::linkDestination() const
+{
+    Q_D(const LinkAnnotation);
+    return d->linkDestination;
+}
+
+void LinkAnnotation::setLinkDestination(Link *link)
+{
+    Q_D(LinkAnnotation);
+    delete d->linkDestination;
+    d->linkDestination = link;
+}
+
+LinkAnnotation::HighlightMode LinkAnnotation::linkHighlightMode() const
+{
+    Q_D(const LinkAnnotation);
+    return d->linkHLMode;
+}
+
+void LinkAnnotation::setLinkHighlightMode(LinkAnnotation::HighlightMode mode)
+{
+    Q_D(LinkAnnotation);
+    d->linkHLMode = mode;
+}
+
+QPointF LinkAnnotation::linkRegionPoint(int id) const
+{
+    if (id < 0 || id >= 4)
+        return QPointF();
+
+    Q_D(const LinkAnnotation);
+    return d->linkRegion[id];
+}
+
+void LinkAnnotation::setLinkRegionPoint(int id, const QPointF point)
+{
+    if (id < 0 || id >= 4)
+        return;
+
+    Q_D(LinkAnnotation);
+    d->linkRegion[id] = point;
+}
+
+/** CaretAnnotation [Annotation] */
+class CaretAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    CaretAnnotationPrivate();
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    CaretAnnotation::CaretSymbol symbol;
+};
+
+static QString caretSymbolToString(CaretAnnotation::CaretSymbol symbol)
+{
+    switch (symbol) {
+    case CaretAnnotation::None:
+        return QStringLiteral("None");
+    case CaretAnnotation::P:
+        return QStringLiteral("P");
+    }
+    return QString();
+}
+
+static CaretAnnotation::CaretSymbol caretSymbolFromString(const QString &symbol)
+{
+    if (symbol == QLatin1String("None"))
+        return CaretAnnotation::None;
+    else if (symbol == QLatin1String("P"))
+        return CaretAnnotation::P;
+    return CaretAnnotation::None;
+}
+
+CaretAnnotationPrivate::CaretAnnotationPrivate() : AnnotationPrivate(), symbol(CaretAnnotation::None) { }
+
+Annotation *CaretAnnotationPrivate::makeAlias()
+{
+    return new CaretAnnotation(*this);
+}
+
+Annot *CaretAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    // Setters are defined in the public class
+    CaretAnnotation *q = static_cast<CaretAnnotation *>(makeAlias());
+
+    // Set page and document
+    pdfPage = destPage;
+    parentDoc = doc;
+
+    // Set pdfAnnot
+    PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
+    pdfAnnot = new AnnotCaret(destPage->getDoc(), &rect);
+
+    // Set properties
+    flushBaseAnnotationProperties();
+    q->setCaretSymbol(symbol);
+
+    delete q;
+    return pdfAnnot;
+}
+
+CaretAnnotation::CaretAnnotation() : Annotation(*new CaretAnnotationPrivate()) { }
+
+CaretAnnotation::CaretAnnotation(CaretAnnotationPrivate &dd) : Annotation(dd) { }
+
+CaretAnnotation::CaretAnnotation(const QDomNode &node) : Annotation(*new CaretAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'caret' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("caret"))
+            continue;
+
+        // parse the attributes
+        if (e.hasAttribute(QStringLiteral("symbol")))
+            setCaretSymbol(caretSymbolFromString(e.attribute(QStringLiteral("symbol"))));
+
+        // loading complete
+        break;
+    }
+}
+
+CaretAnnotation::~CaretAnnotation() { }
+
+void CaretAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [caret] element
+    QDomElement caretElement = document.createElement(QStringLiteral("caret"));
+    node.appendChild(caretElement);
+
+    // append the optional attributes
+    if (caretSymbol() != CaretAnnotation::None)
+        caretElement.setAttribute(QStringLiteral("symbol"), caretSymbolToString(caretSymbol()));
+}
+
+Annotation::SubType CaretAnnotation::subType() const
+{
+    return ACaret;
+}
+
+CaretAnnotation::CaretSymbol CaretAnnotation::caretSymbol() const
+{
+    Q_D(const CaretAnnotation);
+
+    if (!d->pdfAnnot)
+        return d->symbol;
+
+    const AnnotCaret *caretann = static_cast<const AnnotCaret *>(d->pdfAnnot);
+    return (CaretAnnotation::CaretSymbol)caretann->getSymbol();
+}
+
+void CaretAnnotation::setCaretSymbol(CaretAnnotation::CaretSymbol symbol)
+{
+    Q_D(CaretAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->symbol = symbol;
+        return;
+    }
+
+    AnnotCaret *caretann = static_cast<AnnotCaret *>(d->pdfAnnot);
+    caretann->setSymbol((AnnotCaret::AnnotCaretSymbol)symbol);
+}
+
+/** FileAttachmentAnnotation [Annotation] */
+class FileAttachmentAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    FileAttachmentAnnotationPrivate();
+    ~FileAttachmentAnnotationPrivate() override;
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    QString icon;
+    EmbeddedFile *embfile;
+};
+
+FileAttachmentAnnotationPrivate::FileAttachmentAnnotationPrivate() : AnnotationPrivate(), icon(QStringLiteral("PushPin")), embfile(nullptr) { }
+
+FileAttachmentAnnotationPrivate::~FileAttachmentAnnotationPrivate()
+{
+    delete embfile;
+}
+
+Annotation *FileAttachmentAnnotationPrivate::makeAlias()
+{
+    return new FileAttachmentAnnotation(*this);
+}
+
+Annot *FileAttachmentAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    return nullptr; // Not implemented
+}
+
+FileAttachmentAnnotation::FileAttachmentAnnotation() : Annotation(*new FileAttachmentAnnotationPrivate()) { }
+
+FileAttachmentAnnotation::FileAttachmentAnnotation(FileAttachmentAnnotationPrivate &dd) : Annotation(dd) { }
+
+FileAttachmentAnnotation::FileAttachmentAnnotation(const QDomNode &node) : Annotation(*new FileAttachmentAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'fileattachment' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("fileattachment"))
+            continue;
+
+        // loading complete
+        break;
+    }
+}
+
+FileAttachmentAnnotation::~FileAttachmentAnnotation() { }
+
+void FileAttachmentAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [fileattachment] element
+    QDomElement fileAttachmentElement = document.createElement(QStringLiteral("fileattachment"));
+    node.appendChild(fileAttachmentElement);
+}
+
+Annotation::SubType FileAttachmentAnnotation::subType() const
+{
+    return AFileAttachment;
+}
+
+QString FileAttachmentAnnotation::fileIconName() const
+{
+    Q_D(const FileAttachmentAnnotation);
+    return d->icon;
+}
+
+void FileAttachmentAnnotation::setFileIconName(const QString &icon)
+{
+    Q_D(FileAttachmentAnnotation);
+    d->icon = icon;
+}
+
+EmbeddedFile *FileAttachmentAnnotation::embeddedFile() const
+{
+    Q_D(const FileAttachmentAnnotation);
+    return d->embfile;
+}
+
+void FileAttachmentAnnotation::setEmbeddedFile(EmbeddedFile *ef)
+{
+    Q_D(FileAttachmentAnnotation);
+    d->embfile = ef;
+}
+
+/** SoundAnnotation [Annotation] */
+class SoundAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    SoundAnnotationPrivate();
+    ~SoundAnnotationPrivate() override;
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    QString icon;
+    SoundObject *sound;
+};
+
+SoundAnnotationPrivate::SoundAnnotationPrivate() : AnnotationPrivate(), icon(QStringLiteral("Speaker")), sound(nullptr) { }
+
+SoundAnnotationPrivate::~SoundAnnotationPrivate()
+{
+    delete sound;
+}
+
+Annotation *SoundAnnotationPrivate::makeAlias()
+{
+    return new SoundAnnotation(*this);
+}
+
+Annot *SoundAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    return nullptr; // Not implemented
+}
+
+SoundAnnotation::SoundAnnotation() : Annotation(*new SoundAnnotationPrivate()) { }
+
+SoundAnnotation::SoundAnnotation(SoundAnnotationPrivate &dd) : Annotation(dd) { }
+
+SoundAnnotation::SoundAnnotation(const QDomNode &node) : Annotation(*new SoundAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'sound' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("sound"))
+            continue;
+
+        // loading complete
+        break;
+    }
+}
+
+SoundAnnotation::~SoundAnnotation() { }
+
+void SoundAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [sound] element
+    QDomElement soundElement = document.createElement(QStringLiteral("sound"));
+    node.appendChild(soundElement);
+}
+
+Annotation::SubType SoundAnnotation::subType() const
+{
+    return ASound;
+}
+
+QString SoundAnnotation::soundIconName() const
+{
+    Q_D(const SoundAnnotation);
+    return d->icon;
+}
+
+void SoundAnnotation::setSoundIconName(const QString &icon)
+{
+    Q_D(SoundAnnotation);
+    d->icon = icon;
+}
+
+SoundObject *SoundAnnotation::sound() const
+{
+    Q_D(const SoundAnnotation);
+    return d->sound;
+}
+
+void SoundAnnotation::setSound(SoundObject *s)
+{
+    Q_D(SoundAnnotation);
+    d->sound = s;
+}
+
+/** MovieAnnotation [Annotation] */
+class MovieAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    MovieAnnotationPrivate();
+    ~MovieAnnotationPrivate() override;
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    MovieObject *movie;
+    QString title;
+};
+
+MovieAnnotationPrivate::MovieAnnotationPrivate() : AnnotationPrivate(), movie(nullptr) { }
+
+MovieAnnotationPrivate::~MovieAnnotationPrivate()
+{
+    delete movie;
+}
+
+Annotation *MovieAnnotationPrivate::makeAlias()
+{
+    return new MovieAnnotation(*this);
+}
+
+Annot *MovieAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    return nullptr; // Not implemented
+}
+
+MovieAnnotation::MovieAnnotation() : Annotation(*new MovieAnnotationPrivate()) { }
+
+MovieAnnotation::MovieAnnotation(MovieAnnotationPrivate &dd) : Annotation(dd) { }
+
+MovieAnnotation::MovieAnnotation(const QDomNode &node) : Annotation(*new MovieAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'movie' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("movie"))
+            continue;
+
+        // loading complete
+        break;
+    }
+}
+
+MovieAnnotation::~MovieAnnotation() { }
+
+void MovieAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [movie] element
+    QDomElement movieElement = document.createElement(QStringLiteral("movie"));
+    node.appendChild(movieElement);
+}
+
+Annotation::SubType MovieAnnotation::subType() const
+{
+    return AMovie;
+}
+
+MovieObject *MovieAnnotation::movie() const
+{
+    Q_D(const MovieAnnotation);
+    return d->movie;
+}
+
+void MovieAnnotation::setMovie(MovieObject *movie)
+{
+    Q_D(MovieAnnotation);
+    d->movie = movie;
+}
+
+QString MovieAnnotation::movieTitle() const
+{
+    Q_D(const MovieAnnotation);
+    return d->title;
+}
+
+void MovieAnnotation::setMovieTitle(const QString &title)
+{
+    Q_D(MovieAnnotation);
+    d->title = title;
+}
+
+/** ScreenAnnotation [Annotation] */
+class ScreenAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    ScreenAnnotationPrivate();
+    ~ScreenAnnotationPrivate() override;
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+
+    // data fields
+    LinkRendition *action;
+    QString title;
+};
+
+ScreenAnnotationPrivate::ScreenAnnotationPrivate() : AnnotationPrivate(), action(nullptr) { }
+
+ScreenAnnotationPrivate::~ScreenAnnotationPrivate()
+{
+    delete action;
+}
+
+ScreenAnnotation::ScreenAnnotation(ScreenAnnotationPrivate &dd) : Annotation(dd) { }
+
+Annotation *ScreenAnnotationPrivate::makeAlias()
+{
+    return new ScreenAnnotation(*this);
+}
+
+Annot *ScreenAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    return nullptr; // Not implemented
+}
+
+ScreenAnnotation::ScreenAnnotation() : Annotation(*new ScreenAnnotationPrivate()) { }
+
+ScreenAnnotation::~ScreenAnnotation() { }
+
+void ScreenAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [screen] element
+    QDomElement screenElement = document.createElement(QStringLiteral("screen"));
+    node.appendChild(screenElement);
+}
+
+Annotation::SubType ScreenAnnotation::subType() const
+{
+    return AScreen;
+}
+
+LinkRendition *ScreenAnnotation::action() const
+{
+    Q_D(const ScreenAnnotation);
+    return d->action;
+}
+
+void ScreenAnnotation::setAction(LinkRendition *action)
+{
+    Q_D(ScreenAnnotation);
+    d->action = action;
+}
+
+QString ScreenAnnotation::screenTitle() const
+{
+    Q_D(const ScreenAnnotation);
+    return d->title;
+}
+
+void ScreenAnnotation::setScreenTitle(const QString &title)
+{
+    Q_D(ScreenAnnotation);
+    d->title = title;
+}
+
+Link *ScreenAnnotation::additionalAction(AdditionalActionType type) const
+{
+    Q_D(const ScreenAnnotation);
+    return d->additionalAction(type);
+}
+
+/** WidgetAnnotation [Annotation] */
+class WidgetAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    Annotation *makeAlias() override;
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
+};
+
+Annotation *WidgetAnnotationPrivate::makeAlias()
+{
+    return new WidgetAnnotation(*this);
+}
+
+Annot *WidgetAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
+{
+    return nullptr; // Not implemented
+}
+
+WidgetAnnotation::WidgetAnnotation(WidgetAnnotationPrivate &dd) : Annotation(dd) { }
+
+WidgetAnnotation::WidgetAnnotation() : Annotation(*new WidgetAnnotationPrivate()) { }
+
+WidgetAnnotation::~WidgetAnnotation() { }
+
+void WidgetAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [widget] element
+    QDomElement widgetElement = document.createElement(QStringLiteral("widget"));
+    node.appendChild(widgetElement);
+}
+
+Annotation::SubType WidgetAnnotation::subType() const
+{
+    return AWidget;
+}
+
+Link *WidgetAnnotation::additionalAction(AdditionalActionType type) const
+{
+    Q_D(const WidgetAnnotation);
+    return d->additionalAction(type);
+}
+
+/** RichMediaAnnotation [Annotation] */
+class RichMediaAnnotation::Params::Private
+{
+public:
+    Private() { }
+
+    QString flashVars;
+};
+
+RichMediaAnnotation::Params::Params() : d(new Private) { }
+
+RichMediaAnnotation::Params::~Params() { }
+
+void RichMediaAnnotation::Params::setFlashVars(const QString &flashVars)
+{
+    d->flashVars = flashVars;
+}
+
+QString RichMediaAnnotation::Params::flashVars() const
+{
+    return d->flashVars;
+}
+
+class RichMediaAnnotation::Instance::Private
+{
+public:
+    Private() : params(nullptr) { }
+
+    ~Private() { delete params; }
+
+    Private(const Private &) = delete;
+    Private &operator=(const Private &) = delete;
+
+    RichMediaAnnotation::Instance::Type type;
+    RichMediaAnnotation::Params *params;
+};
+
+RichMediaAnnotation::Instance::Instance() : d(new Private) { }
+
+RichMediaAnnotation::Instance::~Instance() { }
+
+void RichMediaAnnotation::Instance::setType(Type type)
+{
+    d->type = type;
+}
+
+RichMediaAnnotation::Instance::Type RichMediaAnnotation::Instance::type() const
+{
+    return d->type;
+}
+
+void RichMediaAnnotation::Instance::setParams(RichMediaAnnotation::Params *params)
+{
+    delete d->params;
+    d->params = params;
+}
+
+RichMediaAnnotation::Params *RichMediaAnnotation::Instance::params() const
+{
+    return d->params;
+}
+
+class RichMediaAnnotation::Configuration::Private
+{
+public:
+    Private() { }
+    ~Private()
+    {
+        qDeleteAll(instances);
+        instances.clear();
+    }
+
+    Private(const Private &) = delete;
+    Private &operator=(const Private &) = delete;
+
+    RichMediaAnnotation::Configuration::Type type;
+    QString name;
+    QList<RichMediaAnnotation::Instance *> instances;
+};
+
+RichMediaAnnotation::Configuration::Configuration() : d(new Private) { }
+
+RichMediaAnnotation::Configuration::~Configuration() { }
+
+void RichMediaAnnotation::Configuration::setType(Type type)
+{
+    d->type = type;
+}
+
+RichMediaAnnotation::Configuration::Type RichMediaAnnotation::Configuration::type() const
+{
+    return d->type;
+}
+
+void RichMediaAnnotation::Configuration::setName(const QString &name)
+{
+    d->name = name;
+}
+
+QString RichMediaAnnotation::Configuration::name() const
+{
+    return d->name;
+}
+
+void RichMediaAnnotation::Configuration::setInstances(const QList<RichMediaAnnotation::Instance *> &instances)
+{
+    qDeleteAll(d->instances);
+    d->instances.clear();
+
+    d->instances = instances;
+}
+
+QList<RichMediaAnnotation::Instance *> RichMediaAnnotation::Configuration::instances() const
+{
+    return d->instances;
+}
+
+class RichMediaAnnotation::Asset::Private
+{
+public:
+    Private() : embeddedFile(nullptr) { }
+
+    ~Private() { delete embeddedFile; }
+
+    Private(const Private &) = delete;
+    Private &operator=(const Private &) = delete;
+
+    QString name;
+    EmbeddedFile *embeddedFile;
+};
+
+RichMediaAnnotation::Asset::Asset() : d(new Private) { }
+
+RichMediaAnnotation::Asset::~Asset() { }
+
+void RichMediaAnnotation::Asset::setName(const QString &name)
+{
+    d->name = name;
+}
+
+QString RichMediaAnnotation::Asset::name() const
+{
+    return d->name;
+}
+
+void RichMediaAnnotation::Asset::setEmbeddedFile(EmbeddedFile *embeddedFile)
+{
+    delete d->embeddedFile;
+    d->embeddedFile = embeddedFile;
+}
+
+EmbeddedFile *RichMediaAnnotation::Asset::embeddedFile() const
+{
+    return d->embeddedFile;
+}
+
+class RichMediaAnnotation::Content::Private
+{
+public:
+    Private() { }
+    ~Private()
+    {
+        qDeleteAll(configurations);
+        configurations.clear();
+
+        qDeleteAll(assets);
+        assets.clear();
+    }
+
+    Private(const Private &) = delete;
+    Private &operator=(const Private &) = delete;
+
+    QList<RichMediaAnnotation::Configuration *> configurations;
+    QList<RichMediaAnnotation::Asset *> assets;
+};
+
+RichMediaAnnotation::Content::Content() : d(new Private) { }
+
+RichMediaAnnotation::Content::~Content() { }
+
+void RichMediaAnnotation::Content::setConfigurations(const QList<RichMediaAnnotation::Configuration *> &configurations)
+{
+    qDeleteAll(d->configurations);
+    d->configurations.clear();
+
+    d->configurations = configurations;
+}
+
+QList<RichMediaAnnotation::Configuration *> RichMediaAnnotation::Content::configurations() const
+{
+    return d->configurations;
+}
+
+void RichMediaAnnotation::Content::setAssets(const QList<RichMediaAnnotation::Asset *> &assets)
+{
+    qDeleteAll(d->assets);
+    d->assets.clear();
+
+    d->assets = assets;
+}
+
+QList<RichMediaAnnotation::Asset *> RichMediaAnnotation::Content::assets() const
+{
+    return d->assets;
+}
+
+class RichMediaAnnotation::Activation::Private
+{
+public:
+    Private() : condition(RichMediaAnnotation::Activation::UserAction) { }
+
+    RichMediaAnnotation::Activation::Condition condition;
+};
+
+RichMediaAnnotation::Activation::Activation() : d(new Private) { }
+
+RichMediaAnnotation::Activation::~Activation() { }
+
+void RichMediaAnnotation::Activation::setCondition(Condition condition)
+{
+    d->condition = condition;
+}
+
+RichMediaAnnotation::Activation::Condition RichMediaAnnotation::Activation::condition() const
+{
+    return d->condition;
+}
+
+class RichMediaAnnotation::Deactivation::Private : public QSharedData
+{
+public:
+    Private() : condition(RichMediaAnnotation::Deactivation::UserAction) { }
+
+    RichMediaAnnotation::Deactivation::Condition condition;
+};
+
+RichMediaAnnotation::Deactivation::Deactivation() : d(new Private) { }
+
+RichMediaAnnotation::Deactivation::~Deactivation() { }
+
+void RichMediaAnnotation::Deactivation::setCondition(Condition condition)
+{
+    d->condition = condition;
+}
+
+RichMediaAnnotation::Deactivation::Condition RichMediaAnnotation::Deactivation::condition() const
+{
+    return d->condition;
+}
+
+class RichMediaAnnotation::Settings::Private : public QSharedData
+{
+public:
+    Private() : activation(nullptr), deactivation(nullptr) { }
+
+    RichMediaAnnotation::Activation *activation;
+    RichMediaAnnotation::Deactivation *deactivation;
+};
+
+RichMediaAnnotation::Settings::Settings() : d(new Private) { }
+
+RichMediaAnnotation::Settings::~Settings() { }
+
+void RichMediaAnnotation::Settings::setActivation(RichMediaAnnotation::Activation *activation)
+{
+    delete d->activation;
+    d->activation = activation;
+}
+
+RichMediaAnnotation::Activation *RichMediaAnnotation::Settings::activation() const
+{
+    return d->activation;
+}
+
+void RichMediaAnnotation::Settings::setDeactivation(RichMediaAnnotation::Deactivation *deactivation)
+{
+    delete d->deactivation;
+    d->deactivation = deactivation;
+}
+
+RichMediaAnnotation::Deactivation *RichMediaAnnotation::Settings::deactivation() const
+{
+    return d->deactivation;
+}
+
+class RichMediaAnnotationPrivate : public AnnotationPrivate
+{
+public:
+    RichMediaAnnotationPrivate() : settings(nullptr), content(nullptr) { }
+
+    ~RichMediaAnnotationPrivate() override
+    {
+        delete settings;
+        delete content;
+    }
+
+    Annotation *makeAlias() override { return new RichMediaAnnotation(*this); }
+
+    Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override
+    {
+        Q_UNUSED(destPage);
+        Q_UNUSED(doc);
+
+        return nullptr;
+    }
+
+    RichMediaAnnotation::Settings *settings;
+    RichMediaAnnotation::Content *content;
+};
+
+RichMediaAnnotation::RichMediaAnnotation() : Annotation(*new RichMediaAnnotationPrivate()) { }
+
+RichMediaAnnotation::RichMediaAnnotation(RichMediaAnnotationPrivate &dd) : Annotation(dd) { }
+
+RichMediaAnnotation::RichMediaAnnotation(const QDomNode &node) : Annotation(*new RichMediaAnnotationPrivate(), node)
+{
+    // loop through the whole children looking for a 'richMedia' element
+    QDomNode subNode = node.firstChild();
+    while (subNode.isElement()) {
+        QDomElement e = subNode.toElement();
+        subNode = subNode.nextSibling();
+        if (e.tagName() != QLatin1String("richMedia"))
+            continue;
+
+        // loading complete
+        break;
+    }
+}
+
+RichMediaAnnotation::~RichMediaAnnotation() { }
+
+void RichMediaAnnotation::store(QDomNode &node, QDomDocument &document) const
+{
+    // store base annotation properties
+    storeBaseAnnotationProperties(node, document);
+
+    // create [richMedia] element
+    QDomElement richMediaElement = document.createElement(QStringLiteral("richMedia"));
+    node.appendChild(richMediaElement);
+}
+
+Annotation::SubType RichMediaAnnotation::subType() const
+{
+    return ARichMedia;
+}
+
+void RichMediaAnnotation::setSettings(RichMediaAnnotation::Settings *settings)
+{
+    Q_D(RichMediaAnnotation);
+
+    delete d->settings;
+    d->settings = settings;
+}
+
+RichMediaAnnotation::Settings *RichMediaAnnotation::settings() const
+{
+    Q_D(const RichMediaAnnotation);
+
+    return d->settings;
+}
+
+void RichMediaAnnotation::setContent(RichMediaAnnotation::Content *content)
+{
+    Q_D(RichMediaAnnotation);
+
+    delete d->content;
+    d->content = content;
+}
+
+RichMediaAnnotation::Content *RichMediaAnnotation::content() const
+{
+    Q_D(const RichMediaAnnotation);
+
+    return d->content;
+}
+
+// BEGIN utility annotation functions
+QColor convertAnnotColor(const AnnotColor *color)
+{
+    if (!color)
+        return QColor();
+
+    QColor newcolor;
+    const double *color_data = color->getValues();
+    switch (color->getSpace()) {
+    case AnnotColor::colorTransparent: // = 0,
+        newcolor = Qt::transparent;
+        break;
+    case AnnotColor::colorGray: // = 1,
+        newcolor.setRgbF(color_data[0], color_data[0], color_data[0]);
+        break;
+    case AnnotColor::colorRGB: // = 3,
+        newcolor.setRgbF(color_data[0], color_data[1], color_data[2]);
+        break;
+    case AnnotColor::colorCMYK: // = 4
+        newcolor.setCmykF(color_data[0], color_data[1], color_data[2], color_data[3]);
+        break;
+    }
+    return newcolor;
+}
+
+std::unique_ptr<AnnotColor> convertQColor(const QColor &c)
+{
+    if (c.alpha() == 0)
+        return {}; // Transparent
+
+    switch (c.spec()) {
+    case QColor::Rgb:
+    case QColor::Hsl:
+    case QColor::Hsv:
+        return std::make_unique<AnnotColor>(c.redF(), c.greenF(), c.blueF());
+    case QColor::Cmyk:
+        return std::make_unique<AnnotColor>(c.cyanF(), c.magentaF(), c.yellowF(), c.blackF());
+    case QColor::Invalid:
+    default:
+        return {};
+    }
+}
+// END utility annotation functions
+
+}
diff --git a/qt6/src/poppler-annotation.h b/qt6/src/poppler-annotation.h
new file mode 100644
index 0000000..76e8733
--- /dev/null
+++ b/qt6/src/poppler-annotation.h
@@ -0,0 +1,1397 @@
+/* poppler-annotation.h: qt interface to poppler
+ * Copyright (C) 2006-2008, 2012, 2013, 2018, 2019 Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2006, 2008 Pino Toscano <pino@kde.org>
+ * Copyright (C) 2007, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2010, Philip Lorenz <lorenzph+freedesktop@gmail.com>
+ * Copyright (C) 2012, 2015, Tobias Koenig <tobias.koenig@kdab.com>
+ * Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
+ * Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2013, Anthony Granger <grangeranthony@gmail.com>
+ * Copyright (C) 2018, Dileep Sankhla <sankhla.dileep96@gmail.com>
+ * Adapting code from
+ *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_ANNOTATION_H_
+#define _POPPLER_ANNOTATION_H_
+
+#include <QtCore/QDateTime>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QList>
+#include <QtCore/QPointF>
+#include <QtCore/QRectF>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QVector>
+#include <QtGui/QColor>
+#include <QtGui/QFont>
+#include <QtXml/QDomDocument>
+#include "poppler-export.h"
+
+namespace Poppler {
+
+class Annotation;
+class AnnotationPrivate;
+class TextAnnotationPrivate;
+class LineAnnotationPrivate;
+class GeomAnnotationPrivate;
+class HighlightAnnotationPrivate;
+class StampAnnotationPrivate;
+class InkAnnotationPrivate;
+class LinkAnnotationPrivate;
+class CaretAnnotationPrivate;
+class FileAttachmentAnnotationPrivate;
+class SoundAnnotationPrivate;
+class MovieAnnotationPrivate;
+class ScreenAnnotationPrivate;
+class WidgetAnnotationPrivate;
+class RichMediaAnnotationPrivate;
+class EmbeddedFile;
+class Link;
+class SoundObject;
+class MovieObject;
+class LinkRendition;
+class Page;
+
+/**
+ * \short Helper class for (recursive) Annotation retrieval/storage.
+ *
+ */
+class POPPLER_QT6_EXPORT AnnotationUtils
+{
+public:
+    /**
+     * Restore an Annotation (with revisions if needed) from the DOM
+     * element \p annElement.
+     * \returns a pointer to the complete Annotation or 0 if element is
+     * invalid.
+     */
+    static Annotation *createAnnotation(const QDomElement &annElement);
+
+    /**
+     * Save the Annotation \p ann as a child of \p annElement taking
+     * care of saving all revisions if \p ann has any.
+     */
+    static void storeAnnotation(const Annotation *ann, QDomElement &annElement, QDomDocument &document);
+
+    /**
+     * Returns an element called \p name from the direct children of
+     * \p parentNode or a null element if not found.
+     */
+    static QDomElement findChildElement(const QDomNode &parentNode, const QString &name);
+};
+
+/**
+ * \short Annotation class holding properties shared by all annotations.
+ *
+ * An Annotation is an object (text note, highlight, sound, popup window, ..)
+ * contained by a Page in the document.
+ *
+ * \warning Different Annotation objects might point to the same annotation.
+ *
+ * \section annotCreation How to add annotations
+ *
+ * Create an Annotation object of the desired subclass (for example
+ * TextAnnotation) and set its properties:
+ * @code
+ * Poppler::TextAnnotation* myann = new Poppler::TextAnnotation(Poppler::TextAnnotation::InPlace);
+ * myann->setBoundary(QRectF(0.1, 0.1, 0.2, 0.2)); // normalized coordinates: (0,0) is top-left, (1,1) is bottom-right
+ * myann->setContents("Hello, world!");
+ * @endcode
+ * \note Always set a boundary rectangle, or nothing will be shown!
+ *
+ * Obtain a pointer to the Page where you want to add the annotation (refer to
+ * \ref req for instructions) and add the annotation:
+ * @code
+ * Poppler::Page* mypage = ...;
+ * mypage->addAnnotation(myann);
+ * @endcode
+ *
+ * You can keep on editing the annotation after it has been added to the page:
+ * @code
+ * myann->setContents("World, hello!"); // Let's change text...
+ * myann->setAuthor("Your name here");  // ...and set an author too
+ * @endcode
+ *
+ * When you're done with editing the annotation, you must destroy the Annotation
+ * object:
+ * @code
+ * delete myann;
+ * @endcode
+ *
+ * Use the PDFConverter class to save the modified document.
+ *
+ * \section annotFixedRotation FixedRotation flag specifics
+ *
+ * According to the PDF specification, annotations whose
+ * Annotation::FixedRotation flag is set must always be shown in their original
+ * orientation, no matter what the current rendering rotation or the page's
+ * Page::orientation() values are. In comparison with regular annotations, such
+ * annotations should therefore be transformed by an extra rotation at rendering
+ * time to "undo" such context-related rotations, which is equal to
+ * <code>-(rendering_rotation + page_orientation)</code>. The rotation pivot
+ * is the top-left corner of the boundary rectangle.
+ *
+ * In practice, %Poppler's \ref Page::renderToImage only "unrotates" the
+ * page orientation, and does <b>not</b> unrotate the rendering rotation.
+ * This ensures consistent renderings at different Page::Rotation values:
+ * annotations are always positioned as if they were being positioned at the
+ * default page orientation.
+ *
+ * Just like regular annotations, %Poppler Qt6 exposes normalized coordinates
+ * relative to the page's default orientation. However, behind the scenes, the
+ * coordinate system is different and %Poppler transparently transforms each
+ * shape. If you never call either Annotation::setFlags or
+ * Annotation::setBoundary, you don't need to worry about this; but if you do
+ * call them, then you need to adhere to the following rules:
+ *  - Whenever you toggle the Annotation::FixedRotation flag, you <b>must</b>
+ *    set again the boundary rectangle first, and then you <b>must</b> set
+ *    again any other geometry-related property.
+ *  - Whenever you modify the boundary rectangle of an annotation whose
+ *    Annotation::FixedRotation flag is set, you <b>must</b> set again any other
+ *    geometry-related property.
+ *
+ * These two rules are necessary to make %Poppler's transparent coordinate
+ * conversion work properly.
+ */
+class POPPLER_QT6_EXPORT Annotation
+{
+    friend class AnnotationUtils;
+    friend class LinkMovie;
+    friend class LinkRendition;
+
+public:
+    // enum definitions
+    /**
+     * Annotation subclasses
+     *
+     * \sa subType()
+     */
+    // WARNING!!! oKular uses that very same values so if you change them notify the author!
+    enum SubType
+    {
+        AText = 1, ///< TextAnnotation
+        ALine = 2, ///< LineAnnotation
+        AGeom = 3, ///< GeomAnnotation
+        AHighlight = 4, ///< HighlightAnnotation
+        AStamp = 5, ///< StampAnnotation
+        AInk = 6, ///< InkAnnotation
+        ALink = 7, ///< LinkAnnotation
+        ACaret = 8, ///< CaretAnnotation
+        AFileAttachment = 9, ///< FileAttachmentAnnotation
+        ASound = 10, ///< SoundAnnotation
+        AMovie = 11, ///< MovieAnnotation
+        AScreen = 12, ///< ScreenAnnotation
+        AWidget = 13, ///< WidgetAnnotation
+        ARichMedia = 14, ///< RichMediaAnnotation
+        A_BASE = 0
+    };
+
+    /**
+     * Annotation flags
+     *
+     * They can be OR'd together (e.g. Annotation::FixedRotation | Annotation::DenyPrint).
+     *
+     * \sa flags(), setFlags(int)
+     */
+    // NOTE: Only flags that are known to work are documented
+    enum Flag
+    {
+        Hidden = 1, ///< Do not display or print the annotation
+        FixedSize = 2,
+        FixedRotation = 4, ///< Do not rotate the annotation according to page orientation and rendering rotation \warning Extra care is needed with this flag: see \ref annotFixedRotation
+        DenyPrint = 8, ///< Do not print the annotation
+        DenyWrite = 16,
+        DenyDelete = 32,
+        ToggleHidingOnMouse = 64,
+        External = 128
+    };
+
+    enum LineStyle
+    {
+        Solid = 1,
+        Dashed = 2,
+        Beveled = 4,
+        Inset = 8,
+        Underline = 16
+    };
+    enum LineEffect
+    {
+        NoEffect = 1,
+        Cloudy = 2
+    };
+    enum RevScope
+    {
+        Root = 0,
+        Reply = 1,
+        Group = 2,
+        Delete = 4
+    };
+    enum RevType
+    {
+        None = 1,
+        Marked = 2,
+        Unmarked = 4,
+        Accepted = 8,
+        Rejected = 16,
+        Cancelled = 32,
+        Completed = 64
+    };
+
+    /**
+     * Returns the author of the annotation.
+     */
+    QString author() const;
+    /**
+     * Sets a new author for the annotation.
+     */
+    void setAuthor(const QString &author);
+
+    QString contents() const;
+    void setContents(const QString &contents);
+
+    /**
+     * Returns the unique name (ID) of the annotation.
+     */
+    QString uniqueName() const;
+    /**
+     * Sets a new unique name for the annotation.
+     *
+     * \note no check of the new uniqueName is done
+     */
+    void setUniqueName(const QString &uniqueName);
+
+    QDateTime modificationDate() const;
+    void setModificationDate(const QDateTime &date);
+
+    QDateTime creationDate() const;
+    void setCreationDate(const QDateTime &date);
+
+    /**
+     * Returns this annotation's flags
+     *
+     * \sa Flag, setFlags(int)
+     */
+    int flags() const;
+    /**
+     * Sets this annotation's flags
+     *
+     * \sa Flag, flags(), \ref annotFixedRotation
+     */
+    void setFlags(int flags);
+
+    /**
+     * Returns this annotation's boundary rectangle in normalized coordinates
+     *
+     * \sa setBoundary(const QRectF&)
+     */
+    QRectF boundary() const;
+    /**
+     * Sets this annotation's boundary rectangle
+     *
+     * The boundary rectangle is the smallest rectangle that contains the
+     * annotation.
+     *
+     * \warning This property is mandatory: you must always set this.
+     *
+     * \sa boundary(), \ref annotFixedRotation
+     */
+    void setBoundary(const QRectF &boundary);
+
+    /**
+     * \short Container class for Annotation style information
+     */
+    class POPPLER_QT6_EXPORT Style
+    {
+    public:
+        Style();
+        Style(const Style &other);
+        Style &operator=(const Style &other);
+        ~Style();
+
+        // appearance properties
+        QColor color() const; // black
+        void setColor(const QColor &color);
+        double opacity() const; // 1.0
+        void setOpacity(double opacity);
+
+        // pen properties
+        double width() const; // 1.0
+        void setWidth(double width);
+        LineStyle lineStyle() const; // LineStyle::Solid
+        void setLineStyle(LineStyle style);
+        double xCorners() const; // 0.0
+        void setXCorners(double radius);
+        double yCorners() const; // 0.0
+        void setYCorners(double radius);
+        const QVector<double> &dashArray() const; // [ 3 ]
+        void setDashArray(const QVector<double> &array);
+
+        // pen effects
+        LineEffect lineEffect() const; // LineEffect::NoEffect
+        void setLineEffect(LineEffect effect);
+        double effectIntensity() const; // 1.0
+        void setEffectIntensity(double intens);
+
+    private:
+        class Private;
+        QSharedDataPointer<Private> d;
+    };
+
+    Style style() const;
+    void setStyle(const Style &style);
+
+    /**
+     * \short Container class for Annotation pop-up window information
+     */
+    class POPPLER_QT6_EXPORT Popup
+    {
+    public:
+        Popup();
+        Popup(const Popup &other);
+        Popup &operator=(const Popup &other);
+        ~Popup();
+
+        // window state (Hidden, FixedRotation, Deny* flags allowed)
+        int flags() const; // -1 (never initialized) -> 0 (if inited and shown)
+        void setFlags(int flags);
+
+        // geometric properties
+        QRectF geometry() const; // no default
+        void setGeometry(const QRectF &geom);
+
+        // window contents/override properties
+        QString title() const; // '' text in the titlebar (overrides author)
+        void setTitle(const QString &title);
+        QString summary() const; // '' short description (displayed if not empty)
+        void setSummary(const QString &summary);
+        QString text() const; // '' text for the window (overrides annot->contents)
+        void setText(const QString &text);
+
+    private:
+        class Private;
+        QSharedDataPointer<Private> d;
+    };
+
+    Popup popup() const;
+    /// \warning Currently does nothing \since 0.20
+    void setPopup(const Popup &popup);
+
+    RevScope revisionScope() const; // Root
+
+    RevType revisionType() const; // None
+
+    /**
+     * Returns the revisions of this annotation
+     *
+     * \note The caller owns the returned annotations and they should
+     *       be deleted when no longer required.
+     */
+    QList<Annotation *> revisions() const;
+
+    /**
+     * The type of the annotation.
+     */
+    virtual SubType subType() const = 0;
+
+    /**
+     * Destructor.
+     */
+    virtual ~Annotation();
+
+    /**
+     * Describes the flags from an annotations 'AA' dictionary.
+     *
+     * This flag is used by the additionalAction() method for ScreenAnnotation
+     * and WidgetAnnotation.
+     */
+    enum AdditionalActionType
+    {
+        CursorEnteringAction, ///< Performed when the cursor enters the annotation's active area
+        CursorLeavingAction, ///< Performed when the cursor exists the annotation's active area
+        MousePressedAction, ///< Performed when the mouse button is pressed inside the annotation's active area
+        MouseReleasedAction, ///< Performed when the mouse button is released inside the annotation's active area
+        FocusInAction, ///< Performed when the annotation receives the input focus
+        FocusOutAction, ///< Performed when the annotation loses the input focus
+        PageOpeningAction, ///< Performed when the page containing the annotation is opened
+        PageClosingAction, ///< Performed when the page containing the annotation is closed
+        PageVisibleAction, ///< Performed when the page containing the annotation becomes visible
+        PageInvisibleAction ///< Performed when the page containing the annotation becomes invisible
+    };
+
+protected:
+    /// \cond PRIVATE
+    Annotation(AnnotationPrivate &dd);
+    Annotation(AnnotationPrivate &dd, const QDomNode &annNode);
+    void storeBaseAnnotationProperties(QDomNode &annNode, QDomDocument &document) const;
+    Q_DECLARE_PRIVATE(Annotation)
+    QExplicitlySharedDataPointer<AnnotationPrivate> d_ptr;
+    /// \endcond
+
+private:
+    virtual void store(QDomNode &parentNode, QDomDocument &document) const = 0;
+    Q_DISABLE_COPY(Annotation)
+};
+
+/**
+ * \short Annotation containing text.
+ *
+ * A text annotation is an object showing some text directly on the page, or
+ * linked to the contents using an icon shown on a page.
+ */
+class POPPLER_QT6_EXPORT TextAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    // local enums
+    enum TextType
+    {
+        Linked,
+        InPlace
+    };
+    enum InplaceIntent
+    {
+        Unknown,
+        Callout,
+        TypeWriter
+    };
+
+    TextAnnotation(TextType type);
+    ~TextAnnotation() override;
+    SubType subType() const override;
+
+    /**
+       The type of text annotation represented by this object
+    */
+    TextType textType() const;
+
+    /**
+       The name of the icon for this text annotation.
+
+       Standard names for text annotation icons are:
+       - Comment
+       - Help
+       - Insert
+       - Key
+       - NewParagraph
+       - Note (this is the default icon to use)
+       - Paragraph
+    */
+    QString textIcon() const;
+
+    /**
+       Set the name of the icon to use for this text annotation.
+
+       \sa textIcon for the list of standard names
+    */
+    void setTextIcon(const QString &icon);
+
+    QFont textFont() const;
+    void setTextFont(const QFont &font);
+    QColor textColor() const;
+    void setTextColor(const QColor &color);
+
+    int inplaceAlign() const;
+    void setInplaceAlign(int align);
+
+    QPointF calloutPoint(int id) const;
+    QVector<QPointF> calloutPoints() const;
+    void setCalloutPoints(const QVector<QPointF> &points);
+
+    InplaceIntent inplaceIntent() const;
+    void setInplaceIntent(InplaceIntent intent);
+
+private:
+    TextAnnotation(const QDomNode &node);
+    TextAnnotation(TextAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    void setTextType(TextType type);
+    Q_DECLARE_PRIVATE(TextAnnotation)
+    Q_DISABLE_COPY(TextAnnotation)
+};
+
+/**
+ * \short Polygon/polyline annotation.
+ *
+ * This annotation represents a polygon (or polyline) to be drawn on a page.
+ */
+class POPPLER_QT6_EXPORT LineAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    // local enums
+    enum LineType
+    {
+        StraightLine,
+        Polyline
+    };
+    enum TermStyle
+    {
+        Square,
+        Circle,
+        Diamond,
+        OpenArrow,
+        ClosedArrow,
+        None,
+        Butt,
+        ROpenArrow,
+        RClosedArrow,
+        Slash
+    };
+    enum LineIntent
+    {
+        Unknown,
+        Arrow,
+        Dimension,
+        PolygonCloud
+    };
+
+    LineAnnotation(LineType type);
+    ~LineAnnotation() override;
+    SubType subType() const override;
+
+    LineType lineType() const;
+
+    QVector<QPointF> linePoints() const;
+    void setLinePoints(const QVector<QPointF> &points);
+
+    TermStyle lineStartStyle() const;
+    void setLineStartStyle(TermStyle style);
+
+    TermStyle lineEndStyle() const;
+    void setLineEndStyle(TermStyle style);
+
+    bool isLineClosed() const;
+    void setLineClosed(bool closed);
+
+    QColor lineInnerColor() const;
+    void setLineInnerColor(const QColor &color);
+
+    double lineLeadingForwardPoint() const;
+    void setLineLeadingForwardPoint(double point);
+
+    double lineLeadingBackPoint() const;
+    void setLineLeadingBackPoint(double point);
+
+    bool lineShowCaption() const;
+    void setLineShowCaption(bool show);
+
+    LineIntent lineIntent() const;
+    void setLineIntent(LineIntent intent);
+
+private:
+    LineAnnotation(const QDomNode &node);
+    LineAnnotation(LineAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    void setLineType(LineType type);
+    Q_DECLARE_PRIVATE(LineAnnotation)
+    Q_DISABLE_COPY(LineAnnotation)
+};
+
+/**
+ * \short Geometric annotation.
+ *
+ * The geometric annotation represents a geometric figure, like a rectangle or
+ * an ellipse.
+ */
+class POPPLER_QT6_EXPORT GeomAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    GeomAnnotation();
+    ~GeomAnnotation() override;
+    SubType subType() const override;
+
+    // common enums
+    enum GeomType
+    {
+        InscribedSquare,
+        InscribedCircle
+    };
+
+    GeomType geomType() const;
+    void setGeomType(GeomType type);
+
+    QColor geomInnerColor() const;
+    void setGeomInnerColor(const QColor &color);
+
+private:
+    GeomAnnotation(const QDomNode &node);
+    GeomAnnotation(GeomAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(GeomAnnotation)
+    Q_DISABLE_COPY(GeomAnnotation)
+};
+
+/**
+ * \short Text highlight annotation.
+ *
+ * The highlight annotation represents some areas of text being "highlighted".
+ */
+class POPPLER_QT6_EXPORT HighlightAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    HighlightAnnotation();
+    ~HighlightAnnotation() override;
+    SubType subType() const override;
+
+    /**
+       The type of highlight
+    */
+    enum HighlightType
+    {
+        Highlight, ///< highlighter pen style annotation
+        Squiggly, ///< jagged or squiggly underline
+        Underline, ///< straight line underline
+        StrikeOut ///< straight line through-line
+    };
+
+    /**
+       Structure corresponding to a QuadPoints array. This matches a
+       quadrilateral that describes the area around a word (or set of
+       words) that are to be highlighted.
+    */
+    struct Quad
+    {
+        QPointF points[4]; // 8 valid coords
+        bool capStart; // false (vtx 1-4) [K]
+        bool capEnd; // false (vtx 2-3) [K]
+        double feather; // 0.1 (in range 0..1) [K]
+    };
+
+    /**
+       The type (style) of highlighting to use for this area
+       or these areas.
+    */
+    HighlightType highlightType() const;
+
+    /**
+       Set the type of highlighting to use for the given area
+       or areas.
+    */
+    void setHighlightType(HighlightType type);
+
+    /**
+       The list of areas to highlight.
+    */
+    QList<Quad> highlightQuads() const;
+
+    /**
+       Set the areas to highlight.
+    */
+    void setHighlightQuads(const QList<Quad> &quads);
+
+private:
+    HighlightAnnotation(const QDomNode &node);
+    HighlightAnnotation(HighlightAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(HighlightAnnotation)
+    Q_DISABLE_COPY(HighlightAnnotation)
+};
+
+/**
+ * \short Stamp annotation.
+ *
+ * A simple annotation drawing a stamp on a page.
+ */
+class POPPLER_QT6_EXPORT StampAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    StampAnnotation();
+    ~StampAnnotation() override;
+    SubType subType() const override;
+
+    /**
+       The name of the icon for this stamp annotation.
+
+       Standard names for stamp annotation icons are:
+       - Approved
+       - AsIs
+       - Confidential
+       - Departmental
+       - Draft (this is the default icon type)
+       - Experimental
+       - Expired
+       - Final
+       - ForComment
+       - ForPublicRelease
+       - NotApproved
+       - NotForPublicRelease
+       - Sold
+       - TopSecret
+    */
+    QString stampIconName() const;
+
+    /**
+       Set the icon type for this stamp annotation.
+
+       \sa stampIconName for the list of standard icon names
+    */
+    void setStampIconName(const QString &name);
+
+private:
+    StampAnnotation(const QDomNode &node);
+    StampAnnotation(StampAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(StampAnnotation)
+    Q_DISABLE_COPY(StampAnnotation)
+};
+
+/**
+ * \short Ink Annotation.
+ *
+ * Annotation representing an ink path on a page.
+ */
+class POPPLER_QT6_EXPORT InkAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    InkAnnotation();
+    ~InkAnnotation() override;
+    SubType subType() const override;
+
+    QList<QVector<QPointF>> inkPaths() const;
+    void setInkPaths(const QList<QVector<QPointF>> &paths);
+
+private:
+    InkAnnotation(const QDomNode &node);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    InkAnnotation(InkAnnotationPrivate &dd);
+    Q_DECLARE_PRIVATE(InkAnnotation)
+    Q_DISABLE_COPY(InkAnnotation)
+};
+
+class POPPLER_QT6_EXPORT LinkAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    ~LinkAnnotation() override;
+    SubType subType() const override;
+
+    // local enums
+    enum HighlightMode
+    {
+        None,
+        Invert,
+        Outline,
+        Push
+    };
+
+    Link *linkDestination() const;
+    void setLinkDestination(Link *link);
+
+    HighlightMode linkHighlightMode() const;
+    void setLinkHighlightMode(HighlightMode mode);
+
+    QPointF linkRegionPoint(int id) const;
+    void setLinkRegionPoint(int id, const QPointF point);
+
+private:
+    LinkAnnotation();
+    LinkAnnotation(const QDomNode &node);
+    LinkAnnotation(LinkAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(LinkAnnotation)
+    Q_DISABLE_COPY(LinkAnnotation)
+};
+
+/**
+ * \short Caret annotation.
+ *
+ * The caret annotation represents a symbol to indicate the presence of text.
+ */
+class POPPLER_QT6_EXPORT CaretAnnotation : public Annotation
+{
+    friend class AnnotationUtils;
+    friend class AnnotationPrivate;
+
+public:
+    CaretAnnotation();
+    ~CaretAnnotation() override;
+    SubType subType() const override;
+
+    /**
+     * The symbols for the caret annotation.
+     */
+    enum CaretSymbol
+    {
+        None,
+        P
+    };
+
+    CaretSymbol caretSymbol() const;
+    void setCaretSymbol(CaretSymbol symbol);
+
+private:
+    CaretAnnotation(const QDomNode &node);
+    CaretAnnotation(CaretAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(CaretAnnotation)
+    Q_DISABLE_COPY(CaretAnnotation)
+};
+
+/**
+ * \short File attachment annotation.
+ *
+ * The file attachment annotation represents a file embedded in the document.
+ */
+class POPPLER_QT6_EXPORT FileAttachmentAnnotation : public Annotation
+{
+    friend class AnnotationPrivate;
+
+public:
+    ~FileAttachmentAnnotation() override;
+    SubType subType() const override;
+
+    /**
+     * Returns the name of the icon of this annotation.
+     */
+    QString fileIconName() const;
+    /**
+     * Sets a new name for the icon of this annotation.
+     */
+    void setFileIconName(const QString &icon);
+
+    /**
+     * Returns the EmbeddedFile of this annotation.
+     */
+    EmbeddedFile *embeddedFile() const;
+    /**
+     * Sets a new EmbeddedFile for this annotation.
+     *
+     * \note FileAttachmentAnnotation takes ownership of the object
+     */
+    void setEmbeddedFile(EmbeddedFile *ef);
+
+private:
+    FileAttachmentAnnotation();
+    FileAttachmentAnnotation(const QDomNode &node);
+    FileAttachmentAnnotation(FileAttachmentAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(FileAttachmentAnnotation)
+    Q_DISABLE_COPY(FileAttachmentAnnotation)
+};
+
+/**
+ * \short Sound annotation.
+ *
+ * The sound annotation represents a sound to be played when activated.
+ */
+class POPPLER_QT6_EXPORT SoundAnnotation : public Annotation
+{
+    friend class AnnotationPrivate;
+
+public:
+    ~SoundAnnotation() override;
+    SubType subType() const override;
+
+    /**
+     * Returns the name of the icon of this annotation.
+     */
+    QString soundIconName() const;
+    /**
+     * Sets a new name for the icon of this annotation.
+     */
+    void setSoundIconName(const QString &icon);
+
+    /**
+     * Returns the SoundObject of this annotation.
+     */
+    SoundObject *sound() const;
+    /**
+     * Sets a new SoundObject for this annotation.
+     *
+     * \note SoundAnnotation takes ownership of the object
+     */
+    void setSound(SoundObject *s);
+
+private:
+    SoundAnnotation();
+    SoundAnnotation(const QDomNode &node);
+    SoundAnnotation(SoundAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(SoundAnnotation)
+    Q_DISABLE_COPY(SoundAnnotation)
+};
+
+/**
+ * \short Movie annotation.
+ *
+ * The movie annotation represents a movie to be played when activated.
+ */
+class POPPLER_QT6_EXPORT MovieAnnotation : public Annotation
+{
+    friend class AnnotationPrivate;
+
+public:
+    ~MovieAnnotation() override;
+    SubType subType() const override;
+
+    /**
+     * Returns the MovieObject of this annotation.
+     */
+    MovieObject *movie() const;
+    /**
+     * Sets a new MovieObject for this annotation.
+     *
+     * \note MovieAnnotation takes ownership of the object
+     */
+    void setMovie(MovieObject *movie);
+
+    /**
+     * Returns the title of the movie of this annotation.
+     */
+    QString movieTitle() const;
+    /**
+     * Sets a new title for the movie of this annotation.
+     */
+    void setMovieTitle(const QString &title);
+
+private:
+    MovieAnnotation();
+    MovieAnnotation(const QDomNode &node);
+    MovieAnnotation(MovieAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(MovieAnnotation)
+    Q_DISABLE_COPY(MovieAnnotation)
+};
+
+/**
+ * \short Screen annotation.
+ *
+ * The screen annotation represents a screen to be played when activated.
+ */
+class POPPLER_QT6_EXPORT ScreenAnnotation : public Annotation
+{
+    friend class AnnotationPrivate;
+
+public:
+    ~ScreenAnnotation() override;
+
+    SubType subType() const override;
+
+    /**
+     * Returns the LinkRendition of this annotation.
+     */
+    LinkRendition *action() const;
+
+    /**
+     * Sets a new LinkRendition for this annotation.
+     *
+     * \note ScreenAnnotation takes ownership of the object
+     */
+    void setAction(LinkRendition *action);
+
+    /**
+     * Returns the title of the screen of this annotation.
+     */
+    QString screenTitle() const;
+
+    /**
+     * Sets a new title for the screen of this annotation.
+     */
+    void setScreenTitle(const QString &title);
+
+    /**
+     * Returns the additional action of the given @p type fo the annotation or
+     * @c 0 if no action has been defined.
+     */
+    Link *additionalAction(AdditionalActionType type) const;
+
+private:
+    ScreenAnnotation();
+    ScreenAnnotation(ScreenAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override; // stub
+    Q_DECLARE_PRIVATE(ScreenAnnotation)
+    Q_DISABLE_COPY(ScreenAnnotation)
+};
+
+/**
+ * \short Widget annotation.
+ *
+ * The widget annotation represents a widget (form field) on a page.
+ *
+ * \note This class is just provided for consistency of the annotation API,
+ *       use the FormField classes to get all the form-related information.
+ */
+class POPPLER_QT6_EXPORT WidgetAnnotation : public Annotation
+{
+    friend class AnnotationPrivate;
+
+public:
+    ~WidgetAnnotation() override;
+
+    SubType subType() const override;
+
+    /**
+     * Returns the additional action of the given @p type fo the annotation or
+     * @c 0 if no action has been defined.
+     */
+    Link *additionalAction(AdditionalActionType type) const;
+
+private:
+    WidgetAnnotation();
+    WidgetAnnotation(WidgetAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override; // stub
+    Q_DECLARE_PRIVATE(WidgetAnnotation)
+    Q_DISABLE_COPY(WidgetAnnotation)
+};
+
+/**
+ * \short RichMedia annotation.
+ *
+ * The RichMedia annotation represents a video or sound on a page.
+ */
+class POPPLER_QT6_EXPORT RichMediaAnnotation : public Annotation
+{
+    friend class AnnotationPrivate;
+
+public:
+    ~RichMediaAnnotation() override;
+
+    SubType subType() const override;
+
+    /**
+     * The params object of a RichMediaAnnotation::Instance object.
+     *
+     * The params object provides media specific parameters, to play
+     * back the media inside the PDF viewer.
+     *
+     * At the moment only parameters for flash player are supported.
+     */
+    class POPPLER_QT6_EXPORT Params
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        Params();
+        ~Params();
+
+        /**
+         * Returns the parameters for the flash player.
+         */
+        QString flashVars() const;
+
+    private:
+        void setFlashVars(const QString &flashVars);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The instance object of a RichMediaAnnotation::Configuration object.
+     *
+     * The instance object represents one media object, that should be shown
+     * on the page. It has a media type and a Params object, to define the
+     * media specific parameters.
+     */
+    class POPPLER_QT6_EXPORT Instance
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        /**
+         * Describes the media type of the instance.
+         */
+        enum Type
+        {
+            Type3D, ///< A 3D media file.
+            TypeFlash, ///< A Flash media file.
+            TypeSound, ///< A sound media file.
+            TypeVideo ///< A video media file.
+        };
+
+        Instance();
+        ~Instance();
+
+        /**
+         * Returns the media type of the instance.
+         */
+        Type type() const;
+
+        /**
+         * Returns the params object of the instance or @c 0 if it doesn't exist.
+         */
+        RichMediaAnnotation::Params *params() const;
+
+    private:
+        void setType(Type type);
+        void setParams(RichMediaAnnotation::Params *params);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The configuration object of a RichMediaAnnotation::Content object.
+     *
+     * The configuration object provides access to the various Instance objects
+     * of the rich media annotation.
+     */
+    class POPPLER_QT6_EXPORT Configuration
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        /**
+         * Describes the media type of the configuration.
+         */
+        enum Type
+        {
+            Type3D, ///< A 3D media file.
+            TypeFlash, ///< A Flash media file.
+            TypeSound, ///< A sound media file.
+            TypeVideo ///< A video media file.
+        };
+
+        Configuration();
+        ~Configuration();
+
+        /**
+         * Returns the media type of the configuration.
+         */
+        Type type() const;
+
+        /**
+         * Returns the name of the configuration.
+         */
+        QString name() const;
+
+        /**
+         * Returns the list of Instance objects of the configuration.
+         */
+        QList<RichMediaAnnotation::Instance *> instances() const;
+
+    private:
+        void setType(Type type);
+        void setName(const QString &name);
+        void setInstances(const QList<RichMediaAnnotation::Instance *> &instances);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The asset object of a RichMediaAnnotation::Content object.
+     *
+     * The asset object provides a mapping between identifier name, as
+     * used in the flash vars string of RichMediaAnnotation::Params,  and the
+     * associated file spec object.
+     */
+    class POPPLER_QT6_EXPORT Asset
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        Asset();
+        ~Asset();
+
+        /**
+         * Returns the identifier name of the asset.
+         */
+        QString name() const;
+
+        /**
+         * Returns the embedded file the asset points to.
+         */
+        EmbeddedFile *embeddedFile() const;
+
+    private:
+        void setName(const QString &name);
+        void setEmbeddedFile(EmbeddedFile *embeddedFile);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The content object of a RichMediaAnnotation.
+     *
+     * The content object provides access to the list of configurations
+     * and assets of the rich media annotation.
+     */
+    class POPPLER_QT6_EXPORT Content
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        Content();
+        ~Content();
+
+        /**
+         * Returns the list of configuration objects of the content object.
+         */
+        QList<RichMediaAnnotation::Configuration *> configurations() const;
+
+        /**
+         * Returns the list of asset objects of the content object.
+         */
+        QList<RichMediaAnnotation::Asset *> assets() const;
+
+    private:
+        void setConfigurations(const QList<RichMediaAnnotation::Configuration *> &configurations);
+        void setAssets(const QList<RichMediaAnnotation::Asset *> &assets);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The activation object of the RichMediaAnnotation::Settings object.
+     *
+     * The activation object is a wrapper around the settings for the activation
+     * state. At the moment it provides only the activation condition.
+     */
+    class POPPLER_QT6_EXPORT Activation
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        /**
+         * Describes the condition for activating the rich media.
+         */
+        enum Condition
+        {
+            PageOpened, ///< Activate when page is opened.
+            PageVisible, ///< Activate when page becomes visible.
+            UserAction ///< Activate when user interacts with the annotation.
+        };
+
+        Activation();
+        ~Activation();
+
+        /**
+         * Returns the activation condition.
+         */
+        Condition condition() const;
+
+    private:
+        void setCondition(Condition condition);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The deactivation object of the RichMediaAnnotation::Settings object.
+     *
+     * The deactivation object is a wrapper around the settings for the deactivation
+     * state. At the moment it provides only the deactivation condition.
+     */
+    class POPPLER_QT6_EXPORT Deactivation
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        /**
+         * Describes the condition for deactivating the rich media.
+         */
+        enum Condition
+        {
+            PageClosed, ///< Deactivate when page is closed.
+            PageInvisible, ///< Deactivate when page becomes invisible.
+            UserAction ///< Deactivate when user interacts with the annotation.
+        };
+
+        Deactivation();
+        ~Deactivation();
+
+        /**
+         * Returns the deactivation condition.
+         */
+        Condition condition() const;
+
+    private:
+        void setCondition(Condition condition);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * The settings object of a RichMediaAnnotation.
+     *
+     * The settings object provides access to the configuration objects
+     * for annotation activation and deactivation.
+     */
+    class POPPLER_QT6_EXPORT Settings
+    {
+        friend class AnnotationPrivate;
+
+    public:
+        Settings();
+        ~Settings();
+
+        /**
+         * Returns the Activation object of the settings object or @c 0 if it doesn't exist.
+         */
+        RichMediaAnnotation::Activation *activation() const;
+
+        /**
+         * Returns the Deactivation object of the settings object or @c 0 if it doesn't exist.
+         */
+        RichMediaAnnotation::Deactivation *deactivation() const;
+
+    private:
+        void setActivation(RichMediaAnnotation::Activation *activation);
+        void setDeactivation(RichMediaAnnotation::Deactivation *deactivation);
+
+        class Private;
+        QScopedPointer<Private> d;
+    };
+
+    /**
+     * Returns the Settings object of the rich media annotation or @c 0 if it doesn't exist.
+     */
+    RichMediaAnnotation::Settings *settings() const;
+
+    /**
+     * Returns the Content object of the rich media annotation or @c 0 if it doesn't exist.
+     */
+    RichMediaAnnotation::Content *content() const;
+
+private:
+    void setSettings(RichMediaAnnotation::Settings *settings);
+    void setContent(RichMediaAnnotation::Content *content);
+
+    RichMediaAnnotation();
+    RichMediaAnnotation(const QDomNode &node);
+    RichMediaAnnotation(RichMediaAnnotationPrivate &dd);
+    void store(QDomNode &parentNode, QDomDocument &document) const override;
+    Q_DECLARE_PRIVATE(RichMediaAnnotation)
+    Q_DISABLE_COPY(RichMediaAnnotation)
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-base-converter.cc b/qt6/src/poppler-base-converter.cc
new file mode 100644
index 0000000..2324c50
--- /dev/null
+++ b/qt6/src/poppler-base-converter.cc
@@ -0,0 +1,89 @@
+/* poppler-base-converter.cc: qt interface to poppler
+ * Copyright (C) 2007, 2009, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "poppler-converter-private.h"
+
+#include <QtCore/QFile>
+
+namespace Poppler {
+
+BaseConverterPrivate::BaseConverterPrivate() : document(nullptr), iodev(nullptr), ownIodev(true) { }
+
+BaseConverterPrivate::~BaseConverterPrivate() { }
+
+QIODevice *BaseConverterPrivate::openDevice()
+{
+    if (!iodev) {
+        Q_ASSERT(!outputFileName.isEmpty());
+        QFile *f = new QFile(outputFileName);
+        iodev = f;
+        ownIodev = true;
+    }
+    Q_ASSERT(iodev);
+    if (!iodev->isOpen()) {
+        if (!iodev->open(QIODevice::WriteOnly)) {
+            if (ownIodev) {
+                delete iodev;
+                iodev = nullptr;
+            } else {
+                return nullptr;
+            }
+        }
+    }
+    return iodev;
+}
+
+void BaseConverterPrivate::closeDevice()
+{
+    if (ownIodev) {
+        iodev->close();
+        delete iodev;
+        iodev = nullptr;
+    }
+}
+
+BaseConverter::BaseConverter(BaseConverterPrivate &dd) : d_ptr(&dd) { }
+
+BaseConverter::~BaseConverter()
+{
+    delete d_ptr;
+}
+
+void BaseConverter::setOutputFileName(const QString &outputFileName)
+{
+    Q_D(BaseConverter);
+    d->outputFileName = outputFileName;
+}
+
+void BaseConverter::setOutputDevice(QIODevice *device)
+{
+    Q_D(BaseConverter);
+    d->iodev = device;
+    d->ownIodev = false;
+}
+
+BaseConverter::Error BaseConverter::lastError() const
+{
+    Q_D(const BaseConverter);
+    return d->lastError;
+}
+
+}
diff --git a/qt6/src/poppler-converter-private.h b/qt6/src/poppler-converter-private.h
new file mode 100644
index 0000000..ed51906
--- /dev/null
+++ b/qt6/src/poppler-converter-private.h
@@ -0,0 +1,52 @@
+/* poppler-converter-private.h: Qt interface to poppler
+ * Copyright (C) 2007, 2009, 2018, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef POPPLER_QT6_CONVERTER_PRIVATE_H
+#define POPPLER_QT6_CONVERTER_PRIVATE_H
+
+#include <QtCore/QString>
+
+class QIODevice;
+
+namespace Poppler {
+
+class DocumentData;
+
+class BaseConverterPrivate
+{
+public:
+    BaseConverterPrivate();
+    virtual ~BaseConverterPrivate();
+
+    BaseConverterPrivate(const BaseConverterPrivate &) = delete;
+    BaseConverterPrivate &operator=(const BaseConverterPrivate &) = delete;
+
+    QIODevice *openDevice();
+    void closeDevice();
+
+    DocumentData *document;
+    QString outputFileName;
+    QIODevice *iodev;
+    bool ownIodev : 1;
+    BaseConverter::Error lastError;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-document.cc b/qt6/src/poppler-document.cc
new file mode 100644
index 0000000..c377819
--- /dev/null
+++ b/qt6/src/poppler-document.cc
@@ -0,0 +1,873 @@
+/* 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-2020, 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 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 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>
+ *
+ * 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 <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, new GooString(ownerPassword.data()), new GooString(userPassword.data()));
+    return DocumentData::checkDocument(doc);
+}
+
+Document *Document::load(QIODevice *device, const QByteArray &ownerPassword, const QByteArray &userPassword)
+{
+    DocumentData *doc = new DocumentData(device, new GooString(ownerPassword.data()), new 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, new GooString(ownerPassword.data()), new 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, new GooString(ownerPassword.data()), new GooString(userPassword.data()));
+        } else if (m_doc->m_device) {
+            doc2 = new DocumentData(m_doc->m_device, new GooString(ownerPassword.data()), new GooString(userPassword.data()));
+        } else {
+            doc2 = new DocumentData(m_doc->m_filePath, new GooString(ownerPassword.data()), new 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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoStringEntry(type.toLatin1().constData()));
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoTitle());
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoAuthor());
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoSubject());
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoKeywords());
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoCreator());
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoProducer());
+    return UnicodeParsedString(goo.data());
+}
+
+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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoStringEntry(type.toLatin1().constData()));
+    QString str = UnicodeParsedString(goo.data());
+    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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoCreatDate());
+    QString str = UnicodeParsedString(goo.data());
+    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();
+    }
+
+    QScopedPointer<GooString> goo(m_doc->doc->getDocInfoModDate());
+    QString str = UnicodeParsedString(goo.data());
+    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();
+}
+
+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 arthur 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;
+#if defined(HAVE_SPLASH)
+    ret << Document::SplashBackend;
+#endif
+    ret << Document::ArthurBackend;
+    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()) {
+        GooString *s = catalog->readMetadata();
+        if (s)
+            result = UnicodeParsedString(s);
+        delete s;
+    }
+    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
+{
+    QVector<int> result;
+
+    Form *form = m_doc->doc->getCatalog()->getForm();
+    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->getWidget(0);
+        ::Page *p = m_doc->doc->getPage(fw->getWidgetAnnotation()->getPageNum());
+        result.append(new FormFieldSignature(m_doc, p, static_cast<FormWidgetSignature *>(fw)));
+    }
+
+    return result;
+}
+
+QDateTime convertDate(const char *dateString)
+{
+    int year, mon, day, hour, min, sec, tzHours, tzMins;
+    char tz;
+
+    if (parseDateString(dateString, &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;
+}
+
+}
diff --git a/qt6/src/poppler-embeddedfile-private.h b/qt6/src/poppler-embeddedfile-private.h
new file mode 100644
index 0000000..531b483
--- /dev/null
+++ b/qt6/src/poppler-embeddedfile-private.h
@@ -0,0 +1,44 @@
+/* poppler-embeddedfile-private.h: Qt interface to poppler
+ * Copyright (C) 2005, 2008, 2009, 2012, 2018, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2008, 2011, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef POPPLER_EMBEDDEDFILE_PRIVATE_H
+#define POPPLER_EMBEDDEDFILE_PRIVATE_H
+
+class FileSpec;
+
+namespace Poppler {
+
+class EmbeddedFileData
+{
+public:
+    EmbeddedFileData(FileSpec *fs);
+    ~EmbeddedFileData();
+
+    EmbeddedFileData(const EmbeddedFileData &) = delete;
+    EmbeddedFileData &operator=(const EmbeddedFileData &) = delete;
+
+    EmbFile *embFile() const;
+
+    FileSpec *filespec;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-embeddedfile.cc b/qt6/src/poppler-embeddedfile.cc
new file mode 100644
index 0000000..f1f3f32
--- /dev/null
+++ b/qt6/src/poppler-embeddedfile.cc
@@ -0,0 +1,129 @@
+/* poppler-document.cc: qt interface to poppler
+ * Copyright (C) 2005, 2008, 2009, 2012, 2013, 2018, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2008, 2011, Pino Toscano <pino@kde.org>
+ * 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
+ *
+ * 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/QString>
+#include <QtCore/QDateTime>
+
+#include "Object.h"
+#include "Stream.h"
+#include "Catalog.h"
+#include "FileSpec.h"
+
+#include "poppler-private.h"
+#include "poppler-embeddedfile-private.h"
+
+namespace Poppler {
+
+EmbeddedFileData::EmbeddedFileData(FileSpec *fs) : filespec(fs) { }
+
+EmbeddedFileData::~EmbeddedFileData()
+{
+    delete filespec;
+}
+
+EmbFile *EmbeddedFileData::embFile() const
+{
+    return filespec->isOk() ? filespec->getEmbeddedFile() : nullptr;
+}
+
+EmbeddedFile::EmbeddedFile(EmbFile *embfile) : m_embeddedFile(nullptr)
+{
+    assert(!"You must not use this private constructor!");
+}
+
+EmbeddedFile::EmbeddedFile(EmbeddedFileData &dd) : m_embeddedFile(&dd) { }
+
+EmbeddedFile::~EmbeddedFile()
+{
+    delete m_embeddedFile;
+}
+
+QString EmbeddedFile::name() const
+{
+    const GooString *goo = m_embeddedFile->filespec->getFileName();
+    return goo ? UnicodeParsedString(goo) : QString();
+}
+
+QString EmbeddedFile::description() const
+{
+    const GooString *goo = m_embeddedFile->filespec->getDescription();
+    return goo ? UnicodeParsedString(goo) : QString();
+}
+
+int EmbeddedFile::size() const
+{
+    return m_embeddedFile->embFile() ? m_embeddedFile->embFile()->size() : -1;
+}
+
+QDateTime EmbeddedFile::modDate() const
+{
+    const GooString *goo = m_embeddedFile->embFile() ? m_embeddedFile->embFile()->modDate() : nullptr;
+    return goo ? convertDate(goo->c_str()) : QDateTime();
+}
+
+QDateTime EmbeddedFile::createDate() const
+{
+    const GooString *goo = m_embeddedFile->embFile() ? m_embeddedFile->embFile()->createDate() : nullptr;
+    return goo ? convertDate(goo->c_str()) : QDateTime();
+}
+
+QByteArray EmbeddedFile::checksum() const
+{
+    const GooString *goo = m_embeddedFile->embFile() ? m_embeddedFile->embFile()->checksum() : nullptr;
+    return goo ? QByteArray::fromRawData(goo->c_str(), goo->getLength()) : QByteArray();
+}
+
+QString EmbeddedFile::mimeType() const
+{
+    const GooString *goo = m_embeddedFile->embFile() ? m_embeddedFile->embFile()->mimeType() : nullptr;
+    return goo ? QString(goo->c_str()) : QString();
+}
+
+QByteArray EmbeddedFile::data()
+{
+    if (!isValid())
+        return QByteArray();
+    Stream *stream = m_embeddedFile->embFile() ? m_embeddedFile->embFile()->stream() : nullptr;
+    if (!stream)
+        return QByteArray();
+
+    stream->reset();
+    int dataLen = 0;
+    QByteArray fileArray;
+    int i;
+    while ((i = stream->getChar()) != EOF) {
+        if (dataLen >= fileArray.size())
+            fileArray.resize(dataLen + 32768);
+        fileArray[dataLen] = (char)i;
+        ++dataLen;
+    }
+    fileArray.resize(dataLen);
+    return fileArray;
+}
+
+bool EmbeddedFile::isValid() const
+{
+    return m_embeddedFile->filespec->isOk();
+}
+
+}
diff --git a/qt6/src/poppler-export.h b/qt6/src/poppler-export.h
new file mode 100644
index 0000000..29eca23
--- /dev/null
+++ b/qt6/src/poppler-export.h
@@ -0,0 +1,20 @@
+/*
+ * This file is used to set the poppler_qt6_EXPORT macros right.
+ * This is needed for setting the visibility on windows, it will have no effect on other platforms.
+ */
+#if defined(_WIN32)
+#    define _POPPLER_QT6_LIB_EXPORT __declspec(dllexport)
+#    define _POPPLER_QT6_LIB_IMPORT __declspec(dllimport)
+#elif defined(__GNUC__)
+#    define _POPPLER_QT6_LIB_EXPORT __attribute__((visibility("default")))
+#    define _POPPLER_QT6_LIB_IMPORT
+#else
+#    define _POPPLER_QT6_LIB_EXPORT
+#    define _POPPLER_QT6_LIB_IMPORT
+#endif
+
+#ifdef poppler_qt6_EXPORTS
+#    define POPPLER_QT6_EXPORT _POPPLER_QT6_LIB_EXPORT
+#else
+#    define POPPLER_QT6_EXPORT _POPPLER_QT6_LIB_IMPORT
+#endif
diff --git a/qt6/src/poppler-fontinfo.cc b/qt6/src/poppler-fontinfo.cc
new file mode 100644
index 0000000..c099785
--- /dev/null
+++ b/qt6/src/poppler-fontinfo.cc
@@ -0,0 +1,153 @@
+/* poppler-qt.h: qt interface to poppler
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2005, Tobias Koening <tokoe@kde.org>
+ * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2005-2008, 2015, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, 2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2018, Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019, Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2019, Jan Grulich <jgrulich@redhat.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 "poppler-private.h"
+
+namespace Poppler {
+
+FontInfo::FontInfo()
+{
+    m_data = new FontInfoData();
+}
+
+FontInfo::FontInfo(const FontInfoData &fid)
+{
+    m_data = new FontInfoData(fid);
+}
+
+FontInfo::FontInfo(const FontInfo &fi)
+{
+    m_data = new FontInfoData(*fi.m_data);
+}
+
+FontInfo::~FontInfo()
+{
+    delete m_data;
+}
+
+QString FontInfo::name() const
+{
+    return m_data->fontName;
+}
+
+QString FontInfo::substituteName() const
+{
+    return m_data->fontSubstituteName;
+}
+
+QString FontInfo::file() const
+{
+    return m_data->fontFile;
+}
+
+bool FontInfo::isEmbedded() const
+{
+    return m_data->isEmbedded;
+}
+
+bool FontInfo::isSubset() const
+{
+    return m_data->isSubset;
+}
+
+FontInfo::Type FontInfo::type() const
+{
+    return m_data->type;
+}
+
+QString FontInfo::typeName() const
+{
+    switch (type()) {
+    case unknown:
+        return QObject::tr("unknown");
+    case Type1:
+        return QObject::tr("Type 1");
+    case Type1C:
+        return QObject::tr("Type 1C");
+    case Type3:
+        return QObject::tr("Type 3");
+    case TrueType:
+        return QObject::tr("TrueType");
+    case CIDType0:
+        return QObject::tr("CID Type 0");
+    case CIDType0C:
+        return QObject::tr("CID Type 0C");
+    case CIDTrueType:
+        return QObject::tr("CID TrueType");
+    case Type1COT:
+        return QObject::tr("Type 1C (OpenType)");
+    case TrueTypeOT:
+        return QObject::tr("TrueType (OpenType)");
+    case CIDType0COT:
+        return QObject::tr("CID Type 0C (OpenType)");
+    case CIDTrueTypeOT:
+        return QObject::tr("CID TrueType (OpenType)");
+    }
+    return QObject::tr("Bug: unexpected font type. Notify poppler mailing list!");
+}
+
+FontInfo &FontInfo::operator=(const FontInfo &fi)
+{
+    if (this == &fi)
+        return *this;
+
+    *m_data = *fi.m_data;
+    return *this;
+}
+
+FontIterator::FontIterator(int startPage, DocumentData *dd) : d(new FontIteratorData(startPage, dd)) { }
+
+FontIterator::~FontIterator()
+{
+    delete d;
+}
+
+QList<FontInfo> FontIterator::next()
+{
+    ++d->currentPage;
+
+    QList<FontInfo> fonts;
+    const std::vector<::FontInfo *> items = d->fontInfoScanner.scan(1);
+    fonts.reserve(items.size());
+    for (::FontInfo *entry : items) {
+        fonts.append(FontInfo(FontInfoData(entry)));
+        delete entry;
+    }
+
+    return fonts;
+}
+
+bool FontIterator::hasNext() const
+{
+    return (d->currentPage + 1) < d->totalPages;
+}
+
+int FontIterator::currentPage() const
+{
+    return d->currentPage;
+}
+
+}
diff --git a/qt6/src/poppler-form.cc b/qt6/src/poppler-form.cc
new file mode 100644
index 0000000..de0b540
--- /dev/null
+++ b/qt6/src/poppler-form.cc
@@ -0,0 +1,1015 @@
+/* poppler-form.h: qt interface to poppler
+ * Copyright (C) 2007-2008, 2011, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, 2011, 2012, 2015-2020 Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2011 Carlos Garcia Campos <carlosgc@gnome.org>
+ * Copyright (C) 2012, Adam Reichold <adamreichold@myopera.com>
+ * Copyright (C) 2016, Hanno Meyer-Thurow <h.mth@web.de>
+ * Copyright (C) 2017, Hans-Ulrich Jüttner <huj@froreich-bioscientia.de>
+ * Copyright (C) 2018, Andre Heinecke <aheinecke@intevation.de>
+ * 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) 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com>
+ * Copyright (C) 2018, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2019 João Netto <joaonetto901@gmail.com>
+ * Copyright (C) 2020 David García Garzón <voki@canvoki.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-qt6.h"
+
+#include <QtCore/QSizeF>
+
+#include <Form.h>
+#include <Object.h>
+#include <Link.h>
+#include <SignatureInfo.h>
+#include <CertificateInfo.h>
+
+#include "poppler-form.h"
+#include "poppler-page-private.h"
+#include "poppler-private.h"
+#include "poppler-annotation-helper.h"
+
+#include <cmath>
+#include <cctype>
+
+#ifdef ENABLE_NSS3
+#    include <hasht.h>
+#endif
+
+namespace {
+
+Qt::Alignment formTextAlignment(::FormWidget *fm)
+{
+    Qt::Alignment qtalign = Qt::AlignLeft;
+    switch (fm->getField()->getTextQuadding()) {
+    case quaddingCentered:
+        qtalign = Qt::AlignHCenter;
+        break;
+    case quaddingRightJustified:
+        qtalign = Qt::AlignRight;
+        break;
+    case quaddingLeftJustified:
+        qtalign = Qt::AlignLeft;
+    }
+    return qtalign;
+}
+
+}
+
+namespace Poppler {
+
+FormFieldIcon::FormFieldIcon(FormFieldIconData *data) : d_ptr(data) { }
+
+FormFieldIcon::FormFieldIcon(const FormFieldIcon &ffIcon)
+{
+    d_ptr = new FormFieldIconData;
+    d_ptr->icon = ffIcon.d_ptr->icon;
+}
+
+FormFieldIcon &FormFieldIcon::operator=(const FormFieldIcon &ffIcon)
+{
+    if (this != &ffIcon) {
+        delete d_ptr;
+        d_ptr = nullptr;
+
+        d_ptr = new FormFieldIconData;
+        *d_ptr = *ffIcon.d_ptr;
+    }
+
+    return *this;
+}
+
+FormFieldIcon::~FormFieldIcon()
+{
+    delete d_ptr;
+}
+
+FormField::FormField(std::unique_ptr<FormFieldData> dd) : m_formData(std::move(dd))
+{
+    if (m_formData->page) {
+        const int rotation = m_formData->page->getRotate();
+        // reading the coords
+        double left, top, right, bottom;
+        m_formData->fm->getRect(&left, &bottom, &right, &top);
+        // build a normalized transform matrix for this page at 100% scale
+        GfxState gfxState(72.0, 72.0, m_formData->page->getCropBox(), rotation, true);
+        const double *gfxCTM = gfxState.getCTM();
+        double MTX[6];
+        double pageWidth = m_formData->page->getCropWidth();
+        double pageHeight = m_formData->page->getCropHeight();
+        // landscape and seascape page rotation: be sure to use the correct (== rotated) page size
+        if (((rotation / 90) % 2) == 1)
+            qSwap(pageWidth, pageHeight);
+        for (int i = 0; i < 6; i += 2) {
+            MTX[i] = gfxCTM[i] / pageWidth;
+            MTX[i + 1] = gfxCTM[i + 1] / pageHeight;
+        }
+        QPointF topLeft;
+        XPDFReader::transform(MTX, qMin(left, right), qMax(top, bottom), topLeft);
+        QPointF bottomRight;
+        XPDFReader::transform(MTX, qMax(left, right), qMin(top, bottom), bottomRight);
+        m_formData->box = QRectF(topLeft, QSizeF(bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y()));
+    }
+}
+
+FormField::~FormField() = default;
+
+QRectF FormField::rect() const
+{
+    return m_formData->box;
+}
+
+int FormField::id() const
+{
+    return m_formData->fm->getID();
+}
+
+QString FormField::name() const
+{
+    QString name;
+    if (const GooString *goo = m_formData->fm->getPartialName()) {
+        name = UnicodeParsedString(goo);
+    }
+    return name;
+}
+
+void FormField::setName(const QString &name) const
+{
+    GooString *goo = QStringToGooString(name);
+    m_formData->fm->setPartialName(*goo);
+    delete goo;
+}
+
+QString FormField::fullyQualifiedName() const
+{
+    QString name;
+    if (GooString *goo = m_formData->fm->getFullyQualifiedName()) {
+        name = UnicodeParsedString(goo);
+    }
+    return name;
+}
+
+QString FormField::uiName() const
+{
+    QString name;
+    if (const GooString *goo = m_formData->fm->getAlternateUiName()) {
+        name = UnicodeParsedString(goo);
+    }
+    return name;
+}
+
+bool FormField::isReadOnly() const
+{
+    return m_formData->fm->isReadOnly();
+}
+
+void FormField::setReadOnly(bool value)
+{
+    m_formData->fm->setReadOnly(value);
+}
+
+bool FormField::isVisible() const
+{
+    return !(m_formData->fm->getWidgetAnnotation()->getFlags() & Annot::flagHidden);
+}
+
+void FormField::setVisible(bool value)
+{
+    unsigned int flags = m_formData->fm->getWidgetAnnotation()->getFlags();
+    if (value) {
+        flags &= ~Annot::flagHidden;
+    } else {
+        flags |= Annot::flagHidden;
+    }
+    m_formData->fm->getWidgetAnnotation()->setFlags(flags);
+}
+
+bool FormField::isPrintable() const
+{
+    return (m_formData->fm->getWidgetAnnotation()->getFlags() & Annot::flagPrint);
+}
+
+void FormField::setPrintable(bool value)
+{
+    unsigned int flags = m_formData->fm->getWidgetAnnotation()->getFlags();
+    if (value) {
+        flags |= Annot::flagPrint;
+    } else {
+        flags &= ~Annot::flagPrint;
+    }
+    m_formData->fm->getWidgetAnnotation()->setFlags(flags);
+}
+
+Link *FormField::activationAction() const
+{
+    Link *action = nullptr;
+    if (::LinkAction *act = m_formData->fm->getActivationAction()) {
+        action = PageData::convertLinkActionToLink(act, m_formData->doc, QRectF());
+    }
+    return action;
+}
+
+Link *FormField::additionalAction(AdditionalActionType type) const
+{
+    Annot::FormAdditionalActionsType actionType = Annot::actionFieldModified;
+    switch (type) {
+    case FieldModified:
+        actionType = Annot::actionFieldModified;
+        break;
+    case FormatField:
+        actionType = Annot::actionFormatField;
+        break;
+    case ValidateField:
+        actionType = Annot::actionValidateField;
+        break;
+    case CalculateField:
+        actionType = Annot::actionCalculateField;
+        break;
+    }
+
+    Link *action = nullptr;
+    if (std::unique_ptr<::LinkAction> act = m_formData->fm->getAdditionalAction(actionType)) {
+        action = PageData::convertLinkActionToLink(act.get(), m_formData->doc, QRectF());
+    }
+    return action;
+}
+
+Link *FormField::additionalAction(Annotation::AdditionalActionType type) const
+{
+    ::AnnotWidget *w = m_formData->fm->getWidgetAnnotation();
+    if (!w) {
+        return nullptr;
+    }
+
+    const Annot::AdditionalActionsType actionType = toPopplerAdditionalActionType(type);
+
+    Link *action = nullptr;
+    if (std::unique_ptr<::LinkAction> act = w->getAdditionalAction(actionType)) {
+        action = PageData::convertLinkActionToLink(act.get(), m_formData->doc, QRectF());
+    }
+    return action;
+}
+
+FormFieldButton::FormFieldButton(DocumentData *doc, ::Page *p, ::FormWidgetButton *w) : FormField(std::make_unique<FormFieldData>(doc, p, w)) { }
+
+FormFieldButton::~FormFieldButton() { }
+
+FormFieldButton::FormType FormFieldButton::type() const
+{
+    return FormField::FormButton;
+}
+
+FormFieldButton::ButtonType FormFieldButton::buttonType() const
+{
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    switch (fwb->getButtonType()) {
+    case formButtonCheck:
+        return FormFieldButton::CheckBox;
+        break;
+    case formButtonPush:
+        return FormFieldButton::Push;
+        break;
+    case formButtonRadio:
+        return FormFieldButton::Radio;
+        break;
+    }
+    return FormFieldButton::CheckBox;
+}
+
+QString FormFieldButton::caption() const
+{
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    QString ret;
+    if (fwb->getButtonType() == formButtonPush) {
+        Dict *dict = m_formData->fm->getObj()->getDict();
+        Object obj1 = dict->lookup("MK");
+        if (obj1.isDict()) {
+            AnnotAppearanceCharacs appearCharacs(obj1.getDict());
+            if (appearCharacs.getNormalCaption()) {
+                ret = UnicodeParsedString(appearCharacs.getNormalCaption());
+            }
+        }
+    } else {
+        if (const char *goo = fwb->getOnStr()) {
+            ret = QString::fromUtf8(goo);
+        }
+    }
+    return ret;
+}
+
+FormFieldIcon FormFieldButton::icon() const
+{
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    if (fwb->getButtonType() == formButtonPush) {
+        Dict *dict = m_formData->fm->getObj()->getDict();
+        FormFieldIconData *data = new FormFieldIconData;
+        data->icon = dict;
+        return FormFieldIcon(data);
+    }
+    return FormFieldIcon(nullptr);
+}
+
+void FormFieldButton::setIcon(const FormFieldIcon &icon)
+{
+    if (FormFieldIconData::getData(icon) == nullptr)
+        return;
+
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    if (fwb->getButtonType() == formButtonPush) {
+        ::AnnotWidget *w = m_formData->fm->getWidgetAnnotation();
+        FormFieldIconData *data = FormFieldIconData::getData(icon);
+        if (data->icon != nullptr)
+            w->setNewAppearance(data->icon->lookup("AP"));
+    }
+}
+
+bool FormFieldButton::state() const
+{
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    return fwb->getState();
+}
+
+void FormFieldButton::setState(bool state)
+{
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    fwb->setState((bool)state);
+}
+
+QList<int> FormFieldButton::siblings() const
+{
+    FormWidgetButton *fwb = static_cast<FormWidgetButton *>(m_formData->fm);
+    ::FormFieldButton *ffb = static_cast<::FormFieldButton *>(fwb->getField());
+    if (fwb->getButtonType() == formButtonPush)
+        return QList<int>();
+
+    QList<int> ret;
+    for (int i = 0; i < ffb->getNumSiblings(); ++i) {
+        ::FormFieldButton *sibling = static_cast<::FormFieldButton *>(ffb->getSibling(i));
+        for (int j = 0; j < sibling->getNumWidgets(); ++j) {
+            FormWidget *w = sibling->getWidget(j);
+            if (w)
+                ret.append(w->getID());
+        }
+    }
+
+    return ret;
+}
+
+FormFieldText::FormFieldText(DocumentData *doc, ::Page *p, ::FormWidgetText *w) : FormField(std::make_unique<FormFieldData>(doc, p, w)) { }
+
+FormFieldText::~FormFieldText() { }
+
+FormField::FormType FormFieldText::type() const
+{
+    return FormField::FormText;
+}
+
+FormFieldText::TextType FormFieldText::textType() const
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    if (fwt->isFileSelect())
+        return FormFieldText::FileSelect;
+    else if (fwt->isMultiline())
+        return FormFieldText::Multiline;
+    return FormFieldText::Normal;
+}
+
+QString FormFieldText::text() const
+{
+    const GooString *goo = static_cast<FormWidgetText *>(m_formData->fm)->getContent();
+    return UnicodeParsedString(goo);
+}
+
+void FormFieldText::setText(const QString &text)
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    GooString *goo = QStringToUnicodeGooString(text);
+    fwt->setContent(goo);
+    delete goo;
+}
+
+void FormFieldText::setAppearanceText(const QString &text)
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    GooString *goo = QStringToUnicodeGooString(text);
+    fwt->setAppearanceContent(goo);
+    delete goo;
+}
+
+bool FormFieldText::isPassword() const
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    return fwt->isPassword();
+}
+
+bool FormFieldText::isRichText() const
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    return fwt->isRichText();
+}
+
+int FormFieldText::maximumLength() const
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    const int maxlen = fwt->getMaxLen();
+    return maxlen > 0 ? maxlen : -1;
+}
+
+Qt::Alignment FormFieldText::textAlignment() const
+{
+    return formTextAlignment(m_formData->fm);
+}
+
+bool FormFieldText::canBeSpellChecked() const
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    return !fwt->noSpellCheck();
+}
+
+double FormFieldText::getFontSize() const
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    return fwt->getTextFontSize();
+}
+
+void FormFieldText::setFontSize(int fontSize)
+{
+    FormWidgetText *fwt = static_cast<FormWidgetText *>(m_formData->fm);
+    fwt->setTextFontSize(fontSize);
+}
+
+FormFieldChoice::FormFieldChoice(DocumentData *doc, ::Page *p, ::FormWidgetChoice *w) : FormField(std::make_unique<FormFieldData>(doc, p, w)) { }
+
+FormFieldChoice::~FormFieldChoice() { }
+
+FormFieldChoice::FormType FormFieldChoice::type() const
+{
+    return FormField::FormChoice;
+}
+
+FormFieldChoice::ChoiceType FormFieldChoice::choiceType() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    if (fwc->isCombo())
+        return FormFieldChoice::ComboBox;
+    return FormFieldChoice::ListBox;
+}
+
+QStringList FormFieldChoice::choices() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    QStringList ret;
+    int num = fwc->getNumChoices();
+    ret.reserve(num);
+    for (int i = 0; i < num; ++i) {
+        ret.append(UnicodeParsedString(fwc->getChoice(i)));
+    }
+    return ret;
+}
+
+QVector<QPair<QString, QString>> FormFieldChoice::choicesWithExportValues() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    QVector<QPair<QString, QString>> ret;
+    const int num = fwc->getNumChoices();
+    ret.reserve(num);
+    for (int i = 0; i < num; ++i) {
+        const QString display = UnicodeParsedString(fwc->getChoice(i));
+        const GooString *exportValueG = fwc->getExportVal(i);
+        const QString exportValue = exportValueG ? UnicodeParsedString(exportValueG) : display;
+        ret.append({ display, exportValue });
+    }
+    return ret;
+}
+
+bool FormFieldChoice::isEditable() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    return fwc->isCombo() ? fwc->hasEdit() : false;
+}
+
+bool FormFieldChoice::multiSelect() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    return !fwc->isCombo() ? fwc->isMultiSelect() : false;
+}
+
+QList<int> FormFieldChoice::currentChoices() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    int num = fwc->getNumChoices();
+    QList<int> choices;
+    for (int i = 0; i < num; ++i)
+        if (fwc->isSelected(i))
+            choices.append(i);
+    return choices;
+}
+
+void FormFieldChoice::setCurrentChoices(const QList<int> &choice)
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    fwc->deselectAll();
+    for (int i = 0; i < choice.count(); ++i)
+        fwc->select(choice.at(i));
+}
+
+QString FormFieldChoice::editChoice() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+
+    if (fwc->isCombo() && fwc->hasEdit())
+        return UnicodeParsedString(fwc->getEditChoice());
+    else
+        return QString();
+}
+
+void FormFieldChoice::setEditChoice(const QString &text)
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+
+    if (fwc->isCombo() && fwc->hasEdit()) {
+        GooString *goo = QStringToUnicodeGooString(text);
+        fwc->setEditChoice(goo);
+        delete goo;
+    }
+}
+
+Qt::Alignment FormFieldChoice::textAlignment() const
+{
+    return formTextAlignment(m_formData->fm);
+}
+
+bool FormFieldChoice::canBeSpellChecked() const
+{
+    FormWidgetChoice *fwc = static_cast<FormWidgetChoice *>(m_formData->fm);
+    return !fwc->noSpellCheck();
+}
+
+class CertificateInfoPrivate
+{
+public:
+    struct EntityInfo
+    {
+        QString common_name;
+        QString email_address;
+        QString org_name;
+        QString distinguished_name;
+    };
+
+    EntityInfo issuer_info;
+    EntityInfo subject_info;
+    QByteArray certificate_der;
+    QByteArray serial_number;
+    QByteArray public_key;
+    QDateTime validity_start;
+    QDateTime validity_end;
+    int public_key_type;
+    int public_key_strength;
+    int ku_extensions;
+    int version;
+    bool is_self_signed;
+    bool is_null;
+};
+
+CertificateInfo::CertificateInfo(CertificateInfoPrivate *priv) : d_ptr(priv) { }
+
+CertificateInfo::CertificateInfo(const CertificateInfo &other) : d_ptr(other.d_ptr) { }
+
+CertificateInfo::~CertificateInfo() = default;
+
+CertificateInfo &CertificateInfo::operator=(const CertificateInfo &other)
+{
+    if (this != &other)
+        d_ptr = other.d_ptr;
+
+    return *this;
+}
+
+bool CertificateInfo::isNull() const
+{
+    Q_D(const CertificateInfo);
+    return d->is_null;
+}
+
+int CertificateInfo::version() const
+{
+    Q_D(const CertificateInfo);
+    return d->version;
+}
+
+QByteArray CertificateInfo::serialNumber() const
+{
+    Q_D(const CertificateInfo);
+    return d->serial_number;
+}
+
+QString CertificateInfo::issuerInfo(EntityInfoKey key) const
+{
+    Q_D(const CertificateInfo);
+    switch (key) {
+    case CommonName:
+        return d->issuer_info.common_name;
+    case DistinguishedName:
+        return d->issuer_info.distinguished_name;
+    case EmailAddress:
+        return d->issuer_info.email_address;
+    case Organization:
+        return d->issuer_info.org_name;
+    default:
+        return QString();
+    }
+}
+
+QString CertificateInfo::subjectInfo(EntityInfoKey key) const
+{
+    Q_D(const CertificateInfo);
+    switch (key) {
+    case CommonName:
+        return d->subject_info.common_name;
+    case DistinguishedName:
+        return d->subject_info.distinguished_name;
+    case EmailAddress:
+        return d->subject_info.email_address;
+    case Organization:
+        return d->subject_info.org_name;
+    default:
+        return QString();
+    }
+}
+
+QDateTime CertificateInfo::validityStart() const
+{
+    Q_D(const CertificateInfo);
+    return d->validity_start;
+}
+
+QDateTime CertificateInfo::validityEnd() const
+{
+    Q_D(const CertificateInfo);
+    return d->validity_end;
+}
+
+CertificateInfo::KeyUsageExtensions CertificateInfo::keyUsageExtensions() const
+{
+    Q_D(const CertificateInfo);
+
+    KeyUsageExtensions kuExtensions = KuNone;
+    if (d->ku_extensions & KU_DIGITAL_SIGNATURE)
+        kuExtensions |= KuDigitalSignature;
+    if (d->ku_extensions & KU_NON_REPUDIATION)
+        kuExtensions |= KuNonRepudiation;
+    if (d->ku_extensions & KU_KEY_ENCIPHERMENT)
+        kuExtensions |= KuKeyEncipherment;
+    if (d->ku_extensions & KU_DATA_ENCIPHERMENT)
+        kuExtensions |= KuDataEncipherment;
+    if (d->ku_extensions & KU_KEY_AGREEMENT)
+        kuExtensions |= KuKeyAgreement;
+    if (d->ku_extensions & KU_KEY_CERT_SIGN)
+        kuExtensions |= KuKeyCertSign;
+    if (d->ku_extensions & KU_CRL_SIGN)
+        kuExtensions |= KuClrSign;
+    if (d->ku_extensions & KU_ENCIPHER_ONLY)
+        kuExtensions |= KuEncipherOnly;
+
+    return kuExtensions;
+}
+
+QByteArray CertificateInfo::publicKey() const
+{
+    Q_D(const CertificateInfo);
+    return d->public_key;
+}
+
+CertificateInfo::PublicKeyType CertificateInfo::publicKeyType() const
+{
+    Q_D(const CertificateInfo);
+    switch (d->public_key_type) {
+    case RSAKEY:
+        return RsaKey;
+    case DSAKEY:
+        return DsaKey;
+    case ECKEY:
+        return EcKey;
+    default:
+        return OtherKey;
+    }
+}
+
+int CertificateInfo::publicKeyStrength() const
+{
+    Q_D(const CertificateInfo);
+    return d->public_key_strength;
+}
+
+bool CertificateInfo::isSelfSigned() const
+{
+    Q_D(const CertificateInfo);
+    return d->is_self_signed;
+}
+
+QByteArray CertificateInfo::certificateData() const
+{
+    Q_D(const CertificateInfo);
+    return d->certificate_der;
+}
+
+class SignatureValidationInfoPrivate
+{
+public:
+    SignatureValidationInfoPrivate(CertificateInfo &&ci) : cert_info(ci) { }
+
+    SignatureValidationInfo::SignatureStatus signature_status;
+    SignatureValidationInfo::CertificateStatus certificate_status;
+    CertificateInfo cert_info;
+
+    QByteArray signature;
+    QString signer_name;
+    QString signer_subject_dn;
+    QString location;
+    QString reason;
+    int hash_algorithm;
+    time_t signing_time;
+    QList<qint64> range_bounds;
+    qint64 docLength;
+};
+
+SignatureValidationInfo::SignatureValidationInfo(SignatureValidationInfoPrivate *priv) : d_ptr(priv) { }
+
+SignatureValidationInfo::SignatureValidationInfo(const SignatureValidationInfo &other) : d_ptr(other.d_ptr) { }
+
+SignatureValidationInfo::~SignatureValidationInfo() { }
+
+SignatureValidationInfo::SignatureStatus SignatureValidationInfo::signatureStatus() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->signature_status;
+}
+
+SignatureValidationInfo::CertificateStatus SignatureValidationInfo::certificateStatus() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->certificate_status;
+}
+
+QString SignatureValidationInfo::signerName() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->signer_name;
+}
+
+QString SignatureValidationInfo::signerSubjectDN() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->signer_subject_dn;
+}
+
+QString SignatureValidationInfo::location() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->location;
+}
+
+QString SignatureValidationInfo::reason() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->reason;
+}
+
+SignatureValidationInfo::HashAlgorithm SignatureValidationInfo::hashAlgorithm() const
+{
+#ifdef ENABLE_NSS3
+    Q_D(const SignatureValidationInfo);
+
+    switch (d->hash_algorithm) {
+    case HASH_AlgMD2:
+        return HashAlgorithmMd2;
+    case HASH_AlgMD5:
+        return HashAlgorithmMd5;
+    case HASH_AlgSHA1:
+        return HashAlgorithmSha1;
+    case HASH_AlgSHA256:
+        return HashAlgorithmSha256;
+    case HASH_AlgSHA384:
+        return HashAlgorithmSha384;
+    case HASH_AlgSHA512:
+        return HashAlgorithmSha512;
+    case HASH_AlgSHA224:
+        return HashAlgorithmSha224;
+    }
+#endif
+    return HashAlgorithmUnknown;
+}
+
+time_t SignatureValidationInfo::signingTime() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->signing_time;
+}
+
+QByteArray SignatureValidationInfo::signature() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->signature;
+}
+
+QList<qint64> SignatureValidationInfo::signedRangeBounds() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->range_bounds;
+}
+
+bool SignatureValidationInfo::signsTotalDocument() const
+{
+    Q_D(const SignatureValidationInfo);
+    if (d->range_bounds.size() == 4 && d->range_bounds.value(0) == 0 && d->range_bounds.value(1) >= 0 && d->range_bounds.value(2) > d->range_bounds.value(1) && d->range_bounds.value(3) >= d->range_bounds.value(2)) {
+        // The range from d->range_bounds.value(1) to d->range_bounds.value(2) is
+        // not authenticated by the signature and should only contain the signature
+        // itself padded with 0 bytes. This has been checked in readSignature().
+        // If it failed, d->signature is empty.
+        // A potential range after d->range_bounds.value(3) would be also not
+        // authenticated. Therefore d->range_bounds.value(3) should coincide with
+        // the end of the document.
+        if (d->docLength == d->range_bounds.value(3) && !d->signature.isEmpty())
+            return true;
+    }
+    return false;
+}
+
+CertificateInfo SignatureValidationInfo::certificateInfo() const
+{
+    Q_D(const SignatureValidationInfo);
+    return d->cert_info;
+}
+
+SignatureValidationInfo &SignatureValidationInfo::operator=(const SignatureValidationInfo &other)
+{
+    if (this != &other)
+        d_ptr = other.d_ptr;
+
+    return *this;
+}
+
+FormFieldSignature::FormFieldSignature(DocumentData *doc, ::Page *p, ::FormWidgetSignature *w) : FormField(std::make_unique<FormFieldData>(doc, p, w)) { }
+
+FormFieldSignature::~FormFieldSignature() { }
+
+FormField::FormType FormFieldSignature::type() const
+{
+    return FormField::FormSignature;
+}
+
+FormFieldSignature::SignatureType FormFieldSignature::signatureType() const
+{
+    SignatureType sigType = AdbePkcs7detached;
+    FormWidgetSignature *fws = static_cast<FormWidgetSignature *>(m_formData->fm);
+    switch (fws->signatureType()) {
+    case adbe_pkcs7_sha1:
+        sigType = AdbePkcs7sha1;
+        break;
+    case adbe_pkcs7_detached:
+        sigType = AdbePkcs7detached;
+        break;
+    case ETSI_CAdES_detached:
+        sigType = EtsiCAdESdetached;
+        break;
+    case unknown_signature_type:
+        sigType = UnknownSignatureType;
+        break;
+    }
+    return sigType;
+}
+
+SignatureValidationInfo FormFieldSignature::validate(ValidateOptions opt) const
+{
+    return validate(opt, QDateTime());
+}
+
+SignatureValidationInfo FormFieldSignature::validate(int opt, const QDateTime &validationTime) const
+{
+    FormWidgetSignature *fws = static_cast<FormWidgetSignature *>(m_formData->fm);
+    const time_t validationTimeT = validationTime.isValid() ? validationTime.toSecsSinceEpoch() : -1;
+    SignatureInfo *si = fws->validateSignature(opt & ValidateVerifyCertificate, opt & ValidateForceRevalidation, validationTimeT);
+
+    // get certificate info
+    const X509CertificateInfo *ci = si->getCertificateInfo();
+    CertificateInfoPrivate *certPriv = new CertificateInfoPrivate;
+    certPriv->is_null = true;
+    if (ci) {
+        certPriv->version = ci->getVersion();
+        certPriv->ku_extensions = ci->getKeyUsageExtensions();
+
+        const GooString &certSerial = ci->getSerialNumber();
+        certPriv->serial_number = QByteArray(certSerial.c_str(), certSerial.getLength());
+
+        const X509CertificateInfo::EntityInfo &issuerInfo = ci->getIssuerInfo();
+        certPriv->issuer_info.common_name = issuerInfo.commonName.c_str();
+        certPriv->issuer_info.distinguished_name = issuerInfo.distinguishedName.c_str();
+        certPriv->issuer_info.email_address = issuerInfo.email.c_str();
+        certPriv->issuer_info.org_name = issuerInfo.organization.c_str();
+
+        const X509CertificateInfo::EntityInfo &subjectInfo = ci->getSubjectInfo();
+        certPriv->subject_info.common_name = subjectInfo.commonName.c_str();
+        certPriv->subject_info.distinguished_name = subjectInfo.distinguishedName.c_str();
+        certPriv->subject_info.email_address = subjectInfo.email.c_str();
+        certPriv->subject_info.org_name = subjectInfo.organization.c_str();
+
+        X509CertificateInfo::Validity certValidity = ci->getValidity();
+        certPriv->validity_start = QDateTime::fromSecsSinceEpoch(certValidity.notBefore, Qt::UTC);
+        certPriv->validity_end = QDateTime::fromSecsSinceEpoch(certValidity.notAfter, Qt::UTC);
+
+        const X509CertificateInfo::PublicKeyInfo &pkInfo = ci->getPublicKeyInfo();
+        certPriv->public_key = QByteArray(pkInfo.publicKey.c_str(), pkInfo.publicKey.getLength());
+        certPriv->public_key_type = static_cast<int>(pkInfo.publicKeyType);
+        certPriv->public_key_strength = pkInfo.publicKeyStrength;
+
+        const GooString &certDer = ci->getCertificateDER();
+        certPriv->certificate_der = QByteArray(certDer.c_str(), certDer.getLength());
+
+        certPriv->is_null = false;
+    }
+
+    SignatureValidationInfoPrivate *priv = new SignatureValidationInfoPrivate(CertificateInfo(certPriv));
+    switch (si->getSignatureValStatus()) {
+    case SIGNATURE_VALID:
+        priv->signature_status = SignatureValidationInfo::SignatureValid;
+        break;
+    case SIGNATURE_INVALID:
+        priv->signature_status = SignatureValidationInfo::SignatureInvalid;
+        break;
+    case SIGNATURE_DIGEST_MISMATCH:
+        priv->signature_status = SignatureValidationInfo::SignatureDigestMismatch;
+        break;
+    case SIGNATURE_DECODING_ERROR:
+        priv->signature_status = SignatureValidationInfo::SignatureDecodingError;
+        break;
+    default:
+    case SIGNATURE_GENERIC_ERROR:
+        priv->signature_status = SignatureValidationInfo::SignatureGenericError;
+        break;
+    case SIGNATURE_NOT_FOUND:
+        priv->signature_status = SignatureValidationInfo::SignatureNotFound;
+        break;
+    case SIGNATURE_NOT_VERIFIED:
+        priv->signature_status = SignatureValidationInfo::SignatureNotVerified;
+        break;
+    }
+    switch (si->getCertificateValStatus()) {
+    case CERTIFICATE_TRUSTED:
+        priv->certificate_status = SignatureValidationInfo::CertificateTrusted;
+        break;
+    case CERTIFICATE_UNTRUSTED_ISSUER:
+        priv->certificate_status = SignatureValidationInfo::CertificateUntrustedIssuer;
+        break;
+    case CERTIFICATE_UNKNOWN_ISSUER:
+        priv->certificate_status = SignatureValidationInfo::CertificateUnknownIssuer;
+        break;
+    case CERTIFICATE_REVOKED:
+        priv->certificate_status = SignatureValidationInfo::CertificateRevoked;
+        break;
+    case CERTIFICATE_EXPIRED:
+        priv->certificate_status = SignatureValidationInfo::CertificateExpired;
+        break;
+    default:
+    case CERTIFICATE_GENERIC_ERROR:
+        priv->certificate_status = SignatureValidationInfo::CertificateGenericError;
+        break;
+    case CERTIFICATE_NOT_VERIFIED:
+        priv->certificate_status = SignatureValidationInfo::CertificateNotVerified;
+        break;
+    }
+    priv->signer_name = si->getSignerName();
+    priv->signer_subject_dn = si->getSubjectDN();
+    priv->hash_algorithm = si->getHashAlgorithm();
+    priv->location = si->getLocation();
+    priv->reason = si->getReason();
+
+    priv->signing_time = si->getSigningTime();
+    const std::vector<Goffset> ranges = fws->getSignedRangeBounds();
+    if (!ranges.empty()) {
+        for (Goffset bound : ranges) {
+            priv->range_bounds.append(bound);
+        }
+    }
+    GooString *checkedSignature = fws->getCheckedSignature(&priv->docLength);
+    if (priv->range_bounds.size() == 4 && checkedSignature) {
+        priv->signature = QByteArray::fromHex(checkedSignature->c_str());
+    }
+    delete checkedSignature;
+
+    return SignatureValidationInfo(priv);
+}
+
+}
diff --git a/qt6/src/poppler-form.h b/qt6/src/poppler-form.h
new file mode 100644
index 0000000..cfb72d9
--- /dev/null
+++ b/qt6/src/poppler-form.h
@@ -0,0 +1,755 @@
+/* poppler-form.h: qt interface to poppler
+ * Copyright (C) 2007-2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, 2011, 2016, 2017, 2019, 2020, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2012, Adam Reichold <adamreichold@myopera.com>
+ * Copyright (C) 2016, Hanno Meyer-Thurow <h.mth@web.de>
+ * Copyright (C) 2017, Hans-Ulrich Jüttner <huj@froreich-bioscientia.de>
+ * Copyright (C) 2017, Tobias C. Berner <tcberner@freebsd.org>
+ * Copyright (C) 2018, Andre Heinecke <aheinecke@intevation.de>
+ * Copyright (C) 2018, Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com>
+ * Copyright (C) 2018, Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2019 João Netto <joaonetto901@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.
+ */
+
+#ifndef _POPPLER_QT6_FORM_H_
+#define _POPPLER_QT6_FORM_H_
+
+#include <memory>
+#include <ctime>
+#include <QtCore/QDateTime>
+#include <QtCore/QList>
+#include <QtCore/QRectF>
+#include <QtCore/QStringList>
+#include <QtCore/QSharedPointer>
+#include "poppler-export.h"
+#include "poppler-annotation.h"
+
+class Page;
+class FormWidget;
+class FormWidgetButton;
+class FormWidgetText;
+class FormWidgetChoice;
+class FormWidgetSignature;
+
+namespace Poppler {
+
+class DocumentData;
+class Link;
+
+class FormFieldData;
+class FormFieldIconData;
+
+/**
+     The class containing the appearance information
+ */
+
+class POPPLER_QT6_EXPORT FormFieldIcon
+{
+
+    friend class FormFieldIconData;
+
+public:
+    FormFieldIcon(FormFieldIconData *data);
+    FormFieldIcon(const FormFieldIcon &ffIcon);
+    ~FormFieldIcon();
+
+    FormFieldIcon &operator=(const FormFieldIcon &ffIcon);
+
+private:
+    FormFieldIconData *d_ptr;
+};
+/**
+  The base class representing a form field.
+ */
+class POPPLER_QT6_EXPORT FormField
+{
+
+    friend class FormFieldData;
+
+public:
+    /**
+       The different types of form field.
+    */
+    enum FormType
+    {
+        FormButton, ///< A button field. See \ref Poppler::FormFieldButton::ButtonType "ButtonType"
+        FormText, ///< A text field. See \ref Poppler::FormFieldText::TextType "TextType"
+        FormChoice, ///< A single choice field. See \ref Poppler::FormFieldChoice::ChoiceType "ChoiceType"
+        FormSignature ///< A signature field.
+    };
+
+    virtual ~FormField();
+
+    /**
+      The type of the field.
+     */
+    virtual FormType type() const = 0;
+
+    /**
+       \return The size of the field, in normalized coordinates, i.e.
+       [0..1] with regard to the dimensions (cropbox) of the page
+    */
+    QRectF rect() const;
+
+    /**
+      The ID of the field.
+     */
+    int id() const;
+
+    /**
+      The internal name (T) of the field.
+     */
+    QString name() const;
+
+    /**
+      Sets the internal name (T) of the field.
+     */
+    void setName(const QString &name) const;
+
+    /**
+      The internal fully qualified name of the field.
+     */
+    QString fullyQualifiedName() const;
+
+    /**
+      The name of the field to be used in user interface (eg messages to
+      the user).
+     */
+    QString uiName() const;
+
+    /**
+      Whether this form field is read-only.
+     */
+    bool isReadOnly() const;
+
+    /**
+      Set whether this form field is read-only.
+     */
+    void setReadOnly(bool value);
+
+    /**
+      Whether this form field is visible.
+     */
+    bool isVisible() const;
+
+    /**
+      Set whether this form field is visible.
+     */
+    void setVisible(bool value);
+
+    /**
+      Whether this field is printable.
+     */
+    bool isPrintable() const;
+
+    /**
+      Set whether this field is printable.
+     */
+    void setPrintable(bool value);
+
+    /**
+      The activation action of this form field.
+
+      \note It may be null.
+     */
+    Link *activationAction() const;
+
+    /**
+     * Describes the flags from the form 'AA' dictionary.
+     */
+    enum AdditionalActionType
+    {
+        FieldModified, ///< A JavaScript action to be performed when the user modifies the field
+        FormatField, ///< A JavaScript action to be performed before the field is formatted to display its value
+        ValidateField, ///< A JavaScript action to be performed when the field value changes
+        CalculateField, ///< A JavaScript action to be performed when the field needs to be recalculated
+    };
+    /**
+     * Returns a given form additional action
+     */
+    Link *additionalAction(AdditionalActionType type) const;
+
+    /**
+     * Returns a given widget annotation additional action
+     */
+    Link *additionalAction(Annotation::AdditionalActionType type) const;
+
+protected:
+    /// \cond PRIVATE
+    FormField(std::unique_ptr<FormFieldData> dd);
+
+    std::unique_ptr<FormFieldData> m_formData;
+    /// \endcond
+
+private:
+    Q_DISABLE_COPY(FormField)
+};
+
+/**
+  A form field that represents a "button".
+ */
+class POPPLER_QT6_EXPORT FormFieldButton : public FormField
+{
+public:
+    /**
+     * The types of button field.
+     */
+    enum ButtonType
+    {
+        Push, ///< A simple push button.
+        CheckBox, ///< A check box.
+        Radio ///< A radio button.
+    };
+
+    /// \cond PRIVATE
+    FormFieldButton(DocumentData *doc, ::Page *p, ::FormWidgetButton *w);
+    /// \endcond
+    ~FormFieldButton() override;
+
+    FormType type() const override;
+
+    /**
+      The particular type of the button field.
+     */
+    ButtonType buttonType() const;
+
+    /**
+     * The caption to be used for the button.
+     */
+    QString caption() const;
+
+    /**
+     * Gets the icon used by the button
+     */
+    FormFieldIcon icon() const;
+
+    /**
+     * Sets a new icon for the button, it has to be a icon
+     * returned by FormFieldButton::icon.
+     */
+    void setIcon(const FormFieldIcon &icon);
+
+    /**
+      The state of the button.
+     */
+    bool state() const;
+
+    /**
+      Sets the state of the button to the new \p state .
+     */
+    void setState(bool state);
+
+    /**
+      The list with the IDs of siblings (ie, buttons belonging to the same
+      group as the current one.
+
+      Valid only for \ref Radio buttons, an empty list otherwise.
+     */
+    QList<int> siblings() const;
+
+private:
+    Q_DISABLE_COPY(FormFieldButton)
+};
+
+/**
+  A form field that represents a text input.
+ */
+class POPPLER_QT6_EXPORT FormFieldText : public FormField
+{
+public:
+    /**
+       The particular type of this text field.
+    */
+    enum TextType
+    {
+        Normal, ///< A simple singleline text field.
+        Multiline, ///< A multiline text field.
+        FileSelect ///< An input field to select the path of a file on disk.
+    };
+
+    /// \cond PRIVATE
+    FormFieldText(DocumentData *doc, ::Page *p, ::FormWidgetText *w);
+    /// \endcond
+    ~FormFieldText() override;
+
+    FormType type() const override;
+
+    /**
+      The text type of the text field.
+     */
+    TextType textType() const;
+
+    /**
+      The text associated with the text field.
+     */
+    QString text() const;
+
+    /**
+      Sets the text associated with the text field to the specified
+      \p text.
+     */
+    void setText(const QString &text);
+
+    /**
+      Sets the text inside the Appearance Stream to the specified
+      \p text
+     */
+    void setAppearanceText(const QString &text);
+
+    /**
+      Whether this text field is a password input, eg its text \b must be
+      replaced with asterisks.
+
+      Always false for \ref FileSelect text fields.
+     */
+    bool isPassword() const;
+
+    /**
+      Whether this text field should allow rich text.
+     */
+    bool isRichText() const;
+
+    /**
+      The maximum length for the text of this field, or -1 if not set.
+     */
+    int maximumLength() const;
+
+    /**
+      The horizontal alignment for the text of this text field.
+     */
+    Qt::Alignment textAlignment() const;
+
+    /**
+      Whether the text inserted manually in the field (where possible)
+      can be spell-checked.
+     */
+    bool canBeSpellChecked() const;
+
+    /**
+      The font size of the text in the form field
+     */
+    double getFontSize() const;
+
+    /**
+      Set the font size of the text in the form field (currently only as integer)
+     */
+    void setFontSize(int fontSize);
+
+private:
+    Q_DISABLE_COPY(FormFieldText)
+};
+
+/**
+  A form field that represents a choice field.
+ */
+class POPPLER_QT6_EXPORT FormFieldChoice : public FormField
+{
+public:
+    /**
+       The particular type of this choice field.
+    */
+    enum ChoiceType
+    {
+        ComboBox, ///< A simple singleline text field.
+        ListBox ///< A multiline text field.
+    };
+
+    /// \cond PRIVATE
+    FormFieldChoice(DocumentData *doc, ::Page *p, ::FormWidgetChoice *w);
+    /// \endcond
+    ~FormFieldChoice() override;
+
+    FormType type() const override;
+
+    /**
+      The choice type of the choice field.
+     */
+    ChoiceType choiceType() const;
+
+    /**
+      The possible choices of the choice field.
+     */
+    QStringList choices() const;
+
+    /**
+      The possible choices of the choice field.
+      The first value of the pair is the display name of the choice,
+      The second value is the export value (i.e. for use in javascript, etc) of the choice
+     */
+    QVector<QPair<QString, QString>> choicesWithExportValues() const;
+
+    /**
+      Whether this FormFieldChoice::ComboBox is editable, i.e. the user
+      can type in a custom value.
+
+      Always false for the other types of choices.
+     */
+    bool isEditable() const;
+
+    /**
+      Whether more than one choice of this FormFieldChoice::ListBox
+      can be selected at the same time.
+
+      Always false for the other types of choices.
+     */
+    bool multiSelect() const;
+
+    /**
+      The currently selected choices.
+     */
+    QList<int> currentChoices() const;
+
+    /**
+      Sets the selected choices to \p choice.
+     */
+    void setCurrentChoices(const QList<int> &choice);
+
+    /**
+      The text entered into an editable combo box choice field. Otherwise a null string.
+    */
+    QString editChoice() const;
+
+    /**
+      Sets the text entered into an editable combo box choice field. Otherwise does nothing.
+    */
+    void setEditChoice(const QString &text);
+
+    /**
+      The horizontal alignment for the text of this text field.
+     */
+    Qt::Alignment textAlignment() const;
+
+    /**
+      Whether the text inserted manually in the field (where possible)
+      can be spell-checked.
+
+      Returns false if the field is not an editable text field.
+     */
+    bool canBeSpellChecked() const;
+
+private:
+    Q_DISABLE_COPY(FormFieldChoice)
+};
+
+/**
+  A helper class to store x509 certificate information.
+ */
+class CertificateInfoPrivate;
+class POPPLER_QT6_EXPORT CertificateInfo
+{
+public:
+    /**
+      The algorithm of public key.
+     */
+    enum PublicKeyType
+    {
+        RsaKey,
+        DsaKey,
+        EcKey,
+        OtherKey
+    };
+
+    /**
+      Certificate key usage extensions.
+     */
+    enum KeyUsageExtension
+    {
+        KuDigitalSignature = 0x80,
+        KuNonRepudiation = 0x40,
+        KuKeyEncipherment = 0x20,
+        KuDataEncipherment = 0x10,
+        KuKeyAgreement = 0x08,
+        KuKeyCertSign = 0x04,
+        KuClrSign = 0x02,
+        KuEncipherOnly = 0x01,
+        KuNone = 0x00
+    };
+    Q_DECLARE_FLAGS(KeyUsageExtensions, KeyUsageExtension)
+
+    /**
+      Predefined keys for elements in an entity's distinguished name.
+     */
+    enum EntityInfoKey
+    {
+        CommonName,
+        DistinguishedName,
+        EmailAddress,
+        Organization,
+    };
+
+    CertificateInfo(CertificateInfoPrivate *priv);
+    ~CertificateInfo();
+
+    /**
+      Returns true if certificate has no contents; otherwise returns false
+     */
+    bool isNull() const;
+
+    /**
+      The certificate version string.
+     */
+    int version() const;
+
+    /**
+      The certificate serial number.
+     */
+    QByteArray serialNumber() const;
+
+    /**
+      Information about the issuer.
+     */
+    QString issuerInfo(EntityInfoKey key) const;
+
+    /**
+      Information about the subject
+     */
+    QString subjectInfo(EntityInfoKey key) const;
+
+    /**
+      The date-time when certificate becomes valid.
+     */
+    QDateTime validityStart() const;
+
+    /**
+      The date-time when certificate expires.
+     */
+    QDateTime validityEnd() const;
+
+    /**
+      The uses allowed for the certificate.
+     */
+    KeyUsageExtensions keyUsageExtensions() const;
+
+    /**
+      The public key value.
+     */
+    QByteArray publicKey() const;
+
+    /**
+      The public key type.
+     */
+    PublicKeyType publicKeyType() const;
+
+    /**
+      The strength of public key in bits.
+     */
+    int publicKeyStrength() const;
+
+    /**
+      Returns true if certificate is self-signed otherwise returns false.
+     */
+    bool isSelfSigned() const;
+
+    /**
+      The DER encoded certificate.
+     */
+    QByteArray certificateData() const;
+
+    CertificateInfo(const CertificateInfo &other);
+    CertificateInfo &operator=(const CertificateInfo &other);
+
+private:
+    Q_DECLARE_PRIVATE(CertificateInfo)
+
+    QSharedPointer<CertificateInfoPrivate> d_ptr;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(CertificateInfo::KeyUsageExtensions)
+
+/**
+  A signature validation info helper class.
+ */
+class SignatureValidationInfoPrivate;
+class POPPLER_QT6_EXPORT SignatureValidationInfo
+{
+public:
+    /**
+       The verification result of the signature.
+    */
+    enum SignatureStatus
+    {
+        SignatureValid, ///< The signature is cryptographically valid.
+        SignatureInvalid, ///< The signature is cryptographically invalid.
+        SignatureDigestMismatch, ///< The document content was changed after the signature was applied.
+        SignatureDecodingError, ///< The signature CMS/PKCS7 structure is malformed.
+        SignatureGenericError, ///< The signature could not be verified.
+        SignatureNotFound, ///< The requested signature is not present in the document.
+        SignatureNotVerified ///< The signature is not yet verified.
+    };
+
+    /**
+       The verification result of the certificate.
+    */
+    enum CertificateStatus
+    {
+        CertificateTrusted, ///< The certificate is considered trusted.
+        CertificateUntrustedIssuer, ///< The issuer of this certificate has been marked as untrusted by the user.
+        CertificateUnknownIssuer, ///< The certificate trust chain has not finished in a trusted root certificate.
+        CertificateRevoked, ///< The certificate was revoked by the issuing certificate authority.
+        CertificateExpired, ///< The signing time is outside the validity bounds of this certificate.
+        CertificateGenericError, ///< The certificate could not be verified.
+        CertificateNotVerified ///< The certificate is not yet verified.
+    };
+
+    /**
+        The hash algorithm of the signature
+     */
+    enum HashAlgorithm
+    {
+        HashAlgorithmUnknown,
+        HashAlgorithmMd2,
+        HashAlgorithmMd5,
+        HashAlgorithmSha1,
+        HashAlgorithmSha256,
+        HashAlgorithmSha384,
+        HashAlgorithmSha512,
+        HashAlgorithmSha224
+    };
+
+    /// \cond PRIVATE
+    SignatureValidationInfo(SignatureValidationInfoPrivate *priv);
+    /// \endcond
+    ~SignatureValidationInfo();
+
+    /**
+      The signature status of the signature.
+     */
+    SignatureStatus signatureStatus() const;
+
+    /**
+      The certificate status of the signature.
+     */
+    CertificateStatus certificateStatus() const;
+
+    /**
+      The signer name associated with the signature.
+     */
+    QString signerName() const;
+
+    /**
+      The signer subject distinguished name associated with the signature.
+     */
+    QString signerSubjectDN() const;
+
+    /**
+      Get signing location.
+    */
+    QString location() const;
+
+    /**
+      Get signing reason.
+    */
+    QString reason() const;
+
+    /**
+      The hash algorithm used for the signature.
+     */
+    HashAlgorithm hashAlgorithm() const;
+
+    /**
+      The signing time associated with the signature.
+     */
+    time_t signingTime() const;
+
+    /**
+      Get the signature binary data.
+     */
+    QByteArray signature() const;
+
+    /**
+      Get the bounds of the ranges of the document which are signed.
+     */
+    QList<qint64> signedRangeBounds() const;
+
+    /**
+      Checks whether the signature authenticates the total document
+      except for the signature itself.
+     */
+    bool signsTotalDocument() const;
+
+    /**
+      The signer certificate info.
+    */
+    CertificateInfo certificateInfo() const;
+
+    SignatureValidationInfo(const SignatureValidationInfo &other);
+    SignatureValidationInfo &operator=(const SignatureValidationInfo &other);
+
+private:
+    Q_DECLARE_PRIVATE(SignatureValidationInfo)
+
+    QSharedPointer<SignatureValidationInfoPrivate> d_ptr;
+};
+
+/**
+  A form field that represents a signature.
+ */
+class POPPLER_QT6_EXPORT FormFieldSignature : public FormField
+{
+public:
+    /**
+        The types of signature fields.
+    */
+    enum SignatureType
+    {
+        UnknownSignatureType,
+        AdbePkcs7sha1,
+        AdbePkcs7detached,
+        EtsiCAdESdetached
+    };
+
+    /**
+       The validation options of this signature.
+    */
+    enum ValidateOptions
+    {
+        ValidateVerifyCertificate = 1, ///< Validate the certificate.
+        ValidateForceRevalidation = 2, ///< Force revalidation of the certificate.
+    };
+
+    /// \cond PRIVATE
+    FormFieldSignature(DocumentData *doc, ::Page *p, ::FormWidgetSignature *w);
+    /// \endcond
+    ~FormFieldSignature() override;
+
+    FormType type() const override;
+
+    /**
+        The signature type
+    */
+    SignatureType signatureType() const;
+
+    /**
+      Validate the signature with now as validation time.
+
+      Reset signature validatation info of scoped instance.
+     */
+    SignatureValidationInfo validate(ValidateOptions opt) const;
+
+    /**
+      Validate the signature with @p validationTime as validation time.
+
+      Reset signature validatation info of scoped instance.
+     */
+    SignatureValidationInfo validate(int opt, const QDateTime &validationTime) const;
+
+private:
+    Q_DISABLE_COPY(FormFieldSignature)
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-link-extractor-private.h b/qt6/src/poppler-link-extractor-private.h
new file mode 100644
index 0000000..a3cd7f8
--- /dev/null
+++ b/qt6/src/poppler-link-extractor-private.h
@@ -0,0 +1,56 @@
+/* poppler-link-extractor_p.h: qt interface to poppler
+ * Copyright (C) 2007, 2008, 2011, Pino Toscano <pino@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_LINK_EXTRACTOR_H_
+#define _POPPLER_LINK_EXTRACTOR_H_
+
+#include <Object.h>
+#include <OutputDev.h>
+
+#include <QtCore/QList>
+
+namespace Poppler {
+
+class Link;
+class PageData;
+
+class LinkExtractorOutputDev : public OutputDev
+{
+public:
+    LinkExtractorOutputDev(PageData *data);
+    ~LinkExtractorOutputDev() override;
+
+    // inherited from OutputDev
+    bool upsideDown() override { return false; }
+    bool useDrawChar() override { return false; }
+    bool interpretType3Chars() override { return false; }
+    void processLink(::AnnotLink *link) override;
+
+    // our stuff
+    QList<Link *> links();
+
+private:
+    PageData *m_data;
+    double m_pageCropWidth;
+    double m_pageCropHeight;
+    QList<Link *> m_links;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-link-extractor.cc b/qt6/src/poppler-link-extractor.cc
new file mode 100644
index 0000000..7fe05f2
--- /dev/null
+++ b/qt6/src/poppler-link-extractor.cc
@@ -0,0 +1,81 @@
+/* poppler-link-extractor_p.h: qt interface to poppler
+ * Copyright (C) 2007, 2008, 2011, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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-link-extractor-private.h"
+
+#include <GfxState.h>
+#include <Link.h>
+#include <Object.h>
+#include <Page.h>
+#include <Annot.h>
+
+#include "poppler-qt6.h"
+#include "poppler-page-private.h"
+
+namespace Poppler {
+
+LinkExtractorOutputDev::LinkExtractorOutputDev(PageData *data) : m_data(data)
+{
+    Q_ASSERT(m_data);
+    ::Page *popplerPage = m_data->page;
+    m_pageCropWidth = popplerPage->getCropWidth();
+    m_pageCropHeight = popplerPage->getCropHeight();
+    if (popplerPage->getRotate() == 90 || popplerPage->getRotate() == 270)
+        qSwap(m_pageCropWidth, m_pageCropHeight);
+    GfxState gfxState(72.0, 72.0, popplerPage->getCropBox(), popplerPage->getRotate(), true);
+    setDefaultCTM(gfxState.getCTM());
+}
+
+LinkExtractorOutputDev::~LinkExtractorOutputDev()
+{
+    qDeleteAll(m_links);
+}
+
+void LinkExtractorOutputDev::processLink(::AnnotLink *link)
+{
+    if (!link->isOk())
+        return;
+
+    double left, top, right, bottom;
+    int leftAux, topAux, rightAux, bottomAux;
+    link->getRect(&left, &top, &right, &bottom);
+    QRectF linkArea;
+
+    cvtUserToDev(left, top, &leftAux, &topAux);
+    cvtUserToDev(right, bottom, &rightAux, &bottomAux);
+    linkArea.setLeft((double)leftAux / m_pageCropWidth);
+    linkArea.setTop((double)topAux / m_pageCropHeight);
+    linkArea.setRight((double)rightAux / m_pageCropWidth);
+    linkArea.setBottom((double)bottomAux / m_pageCropHeight);
+
+    Link *popplerLink = m_data->convertLinkActionToLink(link->getAction(), linkArea);
+    if (popplerLink) {
+        m_links.append(popplerLink);
+    }
+    OutputDev::processLink(link);
+}
+
+QList<Link *> LinkExtractorOutputDev::links()
+{
+    QList<Link *> ret = m_links;
+    m_links.clear();
+    return ret;
+}
+
+}
diff --git a/qt6/src/poppler-link-private.h b/qt6/src/poppler-link-private.h
new file mode 100644
index 0000000..603ab36
--- /dev/null
+++ b/qt6/src/poppler-link-private.h
@@ -0,0 +1,70 @@
+/* poppler-link-private.h: qt interface to poppler
+ * Copyright (C) 2016, 2018, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
+ * Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_LINK_PRIVATE_H_
+#define _POPPLER_LINK_PRIVATE_H_
+
+#include <vector>
+
+#include "Link.h"
+
+class LinkOCGState;
+
+namespace Poppler {
+
+class Link;
+
+class LinkPrivate
+{
+public:
+    LinkPrivate(const QRectF &area) : linkArea(area) { }
+
+    virtual ~LinkPrivate() { qDeleteAll(nextLinks); }
+
+    static LinkPrivate *get(Link *link) { return link->d_ptr; }
+
+    LinkPrivate(const LinkPrivate &) = delete;
+    LinkPrivate &operator=(const LinkPrivate &) = delete;
+
+    QRectF linkArea;
+    QVector<Link *> nextLinks;
+};
+
+class LinkOCGStatePrivate : public LinkPrivate
+{
+public:
+    LinkOCGStatePrivate(const QRectF &area, const std::vector<::LinkOCGState::StateList> &sList, bool pRB) : LinkPrivate(area), stateList(sList), preserveRB(pRB) { }
+
+    std::vector<::LinkOCGState::StateList> stateList;
+    bool preserveRB;
+};
+
+class LinkHidePrivate : public LinkPrivate
+{
+public:
+    LinkHidePrivate(const QRectF &area, const QString &tName, bool show) : LinkPrivate(area), targetName(tName), isShow(show) { }
+
+    QString targetName;
+    bool isShow;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-link.cc b/qt6/src/poppler-link.cc
new file mode 100644
index 0000000..cd9f9bb
--- /dev/null
+++ b/qt6/src/poppler-link.cc
@@ -0,0 +1,664 @@
+/* poppler-link.cc: qt interface to poppler
+ * Copyright (C) 2006-2007, 2013, 2016-2019, Albert Astals Cid
+ * Copyright (C) 2007-2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
+ * Copyright (C) 2012, Tobias Koenig <tokoe@kdab.com>
+ * Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
+ * Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
+ * Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Adapting code from
+ *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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 <poppler-link-private.h>
+#include <poppler-private.h>
+#include <poppler-media.h>
+
+#include <QtCore/QStringList>
+
+#include "poppler-annotation-private.h"
+
+#include "Link.h"
+#include "Rendition.h"
+
+namespace Poppler {
+
+class LinkDestinationPrivate : public QSharedData
+{
+public:
+    LinkDestinationPrivate();
+
+    LinkDestination::Kind kind; // destination type
+    QString name;
+    int pageNum; // page number
+    double left, bottom; // position
+    double right, top;
+    double zoom; // zoom factor
+    bool changeLeft : 1, changeTop : 1; // for destXYZ links, which position
+    bool changeZoom : 1; //   components to change
+};
+
+LinkDestinationPrivate::LinkDestinationPrivate()
+{
+    // sane defaults
+    kind = LinkDestination::destXYZ;
+    pageNum = 0;
+    left = 0;
+    bottom = 0;
+    right = 0;
+    top = 0;
+    zoom = 1;
+    changeLeft = true;
+    changeTop = true;
+    changeZoom = false;
+}
+
+class LinkGotoPrivate : public LinkPrivate
+{
+public:
+    LinkGotoPrivate(const QRectF &area, const LinkDestination &dest);
+
+    QString extFileName;
+    LinkDestination destination;
+};
+
+LinkGotoPrivate::LinkGotoPrivate(const QRectF &area, const LinkDestination &dest) : LinkPrivate(area), destination(dest) { }
+
+class LinkExecutePrivate : public LinkPrivate
+{
+public:
+    LinkExecutePrivate(const QRectF &area);
+
+    QString fileName;
+    QString parameters;
+};
+
+LinkExecutePrivate::LinkExecutePrivate(const QRectF &area) : LinkPrivate(area) { }
+
+class LinkBrowsePrivate : public LinkPrivate
+{
+public:
+    LinkBrowsePrivate(const QRectF &area);
+
+    QString url;
+};
+
+LinkBrowsePrivate::LinkBrowsePrivate(const QRectF &area) : LinkPrivate(area) { }
+
+class LinkActionPrivate : public LinkPrivate
+{
+public:
+    LinkActionPrivate(const QRectF &area);
+
+    LinkAction::ActionType type;
+};
+
+LinkActionPrivate::LinkActionPrivate(const QRectF &area) : LinkPrivate(area) { }
+
+class LinkSoundPrivate : public LinkPrivate
+{
+public:
+    LinkSoundPrivate(const QRectF &area);
+    ~LinkSoundPrivate() override;
+
+    double volume;
+    bool sync : 1;
+    bool repeat : 1;
+    bool mix : 1;
+    SoundObject *sound;
+};
+
+LinkSoundPrivate::LinkSoundPrivate(const QRectF &area) : LinkPrivate(area), sound(nullptr) { }
+
+LinkSoundPrivate::~LinkSoundPrivate()
+{
+    delete sound;
+}
+
+class LinkRenditionPrivate : public LinkPrivate
+{
+public:
+    LinkRenditionPrivate(const QRectF &area, ::MediaRendition *rendition, ::LinkRendition::RenditionOperation operation, const QString &script, const Ref ref);
+    ~LinkRenditionPrivate() override;
+
+    MediaRendition *rendition;
+    LinkRendition::RenditionAction action;
+    QString script;
+    Ref annotationReference;
+};
+
+LinkRenditionPrivate::LinkRenditionPrivate(const QRectF &area, ::MediaRendition *r, ::LinkRendition::RenditionOperation operation, const QString &javaScript, const Ref ref)
+    : LinkPrivate(area), rendition(r ? new MediaRendition(r) : nullptr), action(LinkRendition::PlayRendition), script(javaScript), annotationReference(ref)
+{
+    switch (operation) {
+    case ::LinkRendition::NoRendition:
+        action = LinkRendition::NoRendition;
+        break;
+    case ::LinkRendition::PlayRendition:
+        action = LinkRendition::PlayRendition;
+        break;
+    case ::LinkRendition::StopRendition:
+        action = LinkRendition::StopRendition;
+        break;
+    case ::LinkRendition::PauseRendition:
+        action = LinkRendition::PauseRendition;
+        break;
+    case ::LinkRendition::ResumeRendition:
+        action = LinkRendition::ResumeRendition;
+        break;
+    }
+}
+
+LinkRenditionPrivate::~LinkRenditionPrivate()
+{
+    delete rendition;
+}
+
+class LinkJavaScriptPrivate : public LinkPrivate
+{
+public:
+    LinkJavaScriptPrivate(const QRectF &area);
+
+    QString js;
+};
+
+LinkJavaScriptPrivate::LinkJavaScriptPrivate(const QRectF &area) : LinkPrivate(area) { }
+
+class LinkMoviePrivate : public LinkPrivate
+{
+public:
+    LinkMoviePrivate(const QRectF &area, LinkMovie::Operation operation, const QString &title, const Ref reference);
+
+    LinkMovie::Operation operation;
+    QString annotationTitle;
+    Ref annotationReference;
+};
+
+LinkMoviePrivate::LinkMoviePrivate(const QRectF &area, LinkMovie::Operation _operation, const QString &title, const Ref reference) : LinkPrivate(area), operation(_operation), annotationTitle(title), annotationReference(reference) { }
+
+static void cvtUserToDev(::Page *page, double xu, double yu, int *xd, int *yd)
+{
+    double ctm[6];
+
+    page->getDefaultCTM(ctm, 72.0, 72.0, 0, false, true);
+    *xd = (int)(ctm[0] * xu + ctm[2] * yu + ctm[4] + 0.5);
+    *yd = (int)(ctm[1] * xu + ctm[3] * yu + ctm[5] + 0.5);
+}
+
+LinkDestination::LinkDestination(const LinkDestinationData &data) : d(new LinkDestinationPrivate)
+{
+    bool deleteDest = false;
+    const LinkDest *ld = data.ld;
+
+    if (data.namedDest && !ld && !data.externalDest) {
+        deleteDest = true;
+        ld = data.doc->doc->findDest(data.namedDest).release();
+    }
+    // in case this destination was named one, and it was not resolved
+    if (data.namedDest && !ld) {
+        d->name = QString::fromLatin1(data.namedDest->c_str());
+    }
+
+    if (!ld)
+        return;
+
+    if (ld->getKind() == ::destXYZ)
+        d->kind = destXYZ;
+    else if (ld->getKind() == ::destFit)
+        d->kind = destFit;
+    else if (ld->getKind() == ::destFitH)
+        d->kind = destFitH;
+    else if (ld->getKind() == ::destFitV)
+        d->kind = destFitV;
+    else if (ld->getKind() == ::destFitR)
+        d->kind = destFitR;
+    else if (ld->getKind() == ::destFitB)
+        d->kind = destFitB;
+    else if (ld->getKind() == ::destFitBH)
+        d->kind = destFitBH;
+    else if (ld->getKind() == ::destFitBV)
+        d->kind = destFitBV;
+
+    if (!ld->isPageRef())
+        d->pageNum = ld->getPageNum();
+    else {
+        const Ref ref = ld->getPageRef();
+        d->pageNum = data.doc->doc->findPage(ref);
+    }
+    double left = ld->getLeft();
+    double bottom = ld->getBottom();
+    double right = ld->getRight();
+    double top = ld->getTop();
+    d->zoom = ld->getZoom();
+    d->changeLeft = ld->getChangeLeft();
+    d->changeTop = ld->getChangeTop();
+    d->changeZoom = ld->getChangeZoom();
+
+    int leftAux = 0, topAux = 0, rightAux = 0, bottomAux = 0;
+
+    if (!data.externalDest) {
+        ::Page *page;
+        if (d->pageNum > 0 && d->pageNum <= data.doc->doc->getNumPages() && (page = data.doc->doc->getPage(d->pageNum))) {
+            cvtUserToDev(page, left, top, &leftAux, &topAux);
+            cvtUserToDev(page, right, bottom, &rightAux, &bottomAux);
+
+            d->left = leftAux / (double)page->getCropWidth();
+            d->top = topAux / (double)page->getCropHeight();
+            d->right = rightAux / (double)page->getCropWidth();
+            d->bottom = bottomAux / (double)page->getCropHeight();
+        } else
+            d->pageNum = 0;
+    }
+
+    if (deleteDest)
+        delete ld;
+}
+
+LinkDestination::LinkDestination(const QString &description) : d(new LinkDestinationPrivate)
+{
+    const QStringList tokens = description.split(';');
+    if (tokens.size() >= 10) {
+        d->kind = static_cast<Kind>(tokens.at(0).toInt());
+        d->pageNum = tokens.at(1).toInt();
+        d->left = tokens.at(2).toDouble();
+        d->bottom = tokens.at(3).toDouble();
+        d->right = tokens.at(4).toDouble();
+        d->top = tokens.at(5).toDouble();
+        d->zoom = tokens.at(6).toDouble();
+        d->changeLeft = static_cast<bool>(tokens.at(7).toInt());
+        d->changeTop = static_cast<bool>(tokens.at(8).toInt());
+        d->changeZoom = static_cast<bool>(tokens.at(9).toInt());
+    }
+}
+
+LinkDestination::LinkDestination(const LinkDestination &other) : d(other.d) { }
+
+LinkDestination::~LinkDestination() { }
+
+LinkDestination::Kind LinkDestination::kind() const
+{
+    return d->kind;
+}
+
+int LinkDestination::pageNumber() const
+{
+    return d->pageNum;
+}
+
+double LinkDestination::left() const
+{
+    return d->left;
+}
+
+double LinkDestination::bottom() const
+{
+    return d->bottom;
+}
+
+double LinkDestination::right() const
+{
+    return d->right;
+}
+
+double LinkDestination::top() const
+{
+    return d->top;
+}
+
+double LinkDestination::zoom() const
+{
+    return d->zoom;
+}
+
+bool LinkDestination::isChangeLeft() const
+{
+    return d->changeLeft;
+}
+
+bool LinkDestination::isChangeTop() const
+{
+    return d->changeTop;
+}
+
+bool LinkDestination::isChangeZoom() const
+{
+    return d->changeZoom;
+}
+
+QString LinkDestination::toString() const
+{
+    QString s = QString::number((qint8)d->kind);
+    s += ";" + QString::number(d->pageNum);
+    s += ";" + QString::number(d->left);
+    s += ";" + QString::number(d->bottom);
+    s += ";" + QString::number(d->right);
+    s += ";" + QString::number(d->top);
+    s += ";" + QString::number(d->zoom);
+    s += ";" + QString::number((qint8)d->changeLeft);
+    s += ";" + QString::number((qint8)d->changeTop);
+    s += ";" + QString::number((qint8)d->changeZoom);
+    return s;
+}
+
+QString LinkDestination::destinationName() const
+{
+    return d->name;
+}
+
+LinkDestination &LinkDestination::operator=(const LinkDestination &other)
+{
+    if (this == &other)
+        return *this;
+
+    d = other.d;
+    return *this;
+}
+
+// Link
+Link::~Link()
+{
+    delete d_ptr;
+}
+
+Link::Link(const QRectF &linkArea) : d_ptr(new LinkPrivate(linkArea)) { }
+
+Link::Link(LinkPrivate &dd) : d_ptr(&dd) { }
+
+Link::LinkType Link::linkType() const
+{
+    return None;
+}
+
+QRectF Link::linkArea() const
+{
+    Q_D(const Link);
+    return d->linkArea;
+}
+
+QVector<Link *> Link::nextLinks() const
+{
+    return d_ptr->nextLinks;
+}
+
+// LinkGoto
+LinkGoto::LinkGoto(const QRectF &linkArea, const QString &extFileName, const LinkDestination &destination) : Link(*new LinkGotoPrivate(linkArea, destination))
+{
+    Q_D(LinkGoto);
+    d->extFileName = extFileName;
+}
+
+LinkGoto::~LinkGoto() { }
+
+bool LinkGoto::isExternal() const
+{
+    Q_D(const LinkGoto);
+    return !d->extFileName.isEmpty();
+}
+
+QString LinkGoto::fileName() const
+{
+    Q_D(const LinkGoto);
+    return d->extFileName;
+}
+
+LinkDestination LinkGoto::destination() const
+{
+    Q_D(const LinkGoto);
+    return d->destination;
+}
+
+Link::LinkType LinkGoto::linkType() const
+{
+    return Goto;
+}
+
+// LinkExecute
+LinkExecute::LinkExecute(const QRectF &linkArea, const QString &file, const QString &params) : Link(*new LinkExecutePrivate(linkArea))
+{
+    Q_D(LinkExecute);
+    d->fileName = file;
+    d->parameters = params;
+}
+
+LinkExecute::~LinkExecute() { }
+
+QString LinkExecute::fileName() const
+{
+    Q_D(const LinkExecute);
+    return d->fileName;
+}
+QString LinkExecute::parameters() const
+{
+    Q_D(const LinkExecute);
+    return d->parameters;
+}
+
+Link::LinkType LinkExecute::linkType() const
+{
+    return Execute;
+}
+
+// LinkBrowse
+LinkBrowse::LinkBrowse(const QRectF &linkArea, const QString &url) : Link(*new LinkBrowsePrivate(linkArea))
+{
+    Q_D(LinkBrowse);
+    d->url = url;
+}
+
+LinkBrowse::~LinkBrowse() { }
+
+QString LinkBrowse::url() const
+{
+    Q_D(const LinkBrowse);
+    return d->url;
+}
+
+Link::LinkType LinkBrowse::linkType() const
+{
+    return Browse;
+}
+
+// LinkAction
+LinkAction::LinkAction(const QRectF &linkArea, ActionType actionType) : Link(*new LinkActionPrivate(linkArea))
+{
+    Q_D(LinkAction);
+    d->type = actionType;
+}
+
+LinkAction::~LinkAction() { }
+
+LinkAction::ActionType LinkAction::actionType() const
+{
+    Q_D(const LinkAction);
+    return d->type;
+}
+
+Link::LinkType LinkAction::linkType() const
+{
+    return Action;
+}
+
+// LinkSound
+LinkSound::LinkSound(const QRectF &linkArea, double volume, bool sync, bool repeat, bool mix, SoundObject *sound) : Link(*new LinkSoundPrivate(linkArea))
+{
+    Q_D(LinkSound);
+    d->volume = volume;
+    d->sync = sync;
+    d->repeat = repeat;
+    d->mix = mix;
+    d->sound = sound;
+}
+
+LinkSound::~LinkSound() { }
+
+Link::LinkType LinkSound::linkType() const
+{
+    return Sound;
+}
+
+double LinkSound::volume() const
+{
+    Q_D(const LinkSound);
+    return d->volume;
+}
+
+bool LinkSound::synchronous() const
+{
+    Q_D(const LinkSound);
+    return d->sync;
+}
+
+bool LinkSound::repeat() const
+{
+    Q_D(const LinkSound);
+    return d->repeat;
+}
+
+bool LinkSound::mix() const
+{
+    Q_D(const LinkSound);
+    return d->mix;
+}
+
+SoundObject *LinkSound::sound() const
+{
+    Q_D(const LinkSound);
+    return d->sound;
+}
+
+// LinkRendition
+LinkRendition::LinkRendition(const QRectF &linkArea, ::MediaRendition *rendition, int operation, const QString &script, const Ref annotationReference)
+    : Link(*new LinkRenditionPrivate(linkArea, rendition, static_cast<enum ::LinkRendition::RenditionOperation>(operation), script, annotationReference))
+{
+}
+
+LinkRendition::~LinkRendition() { }
+
+Link::LinkType LinkRendition::linkType() const
+{
+    return Rendition;
+}
+
+MediaRendition *LinkRendition::rendition() const
+{
+    Q_D(const LinkRendition);
+    return d->rendition;
+}
+
+LinkRendition::RenditionAction LinkRendition::action() const
+{
+    Q_D(const LinkRendition);
+    return d->action;
+}
+
+QString LinkRendition::script() const
+{
+    Q_D(const LinkRendition);
+    return d->script;
+}
+
+bool LinkRendition::isReferencedAnnotation(const ScreenAnnotation *annotation) const
+{
+    Q_D(const LinkRendition);
+    if (d->annotationReference != Ref::INVALID() && d->annotationReference == annotation->d_ptr->pdfObjectReference()) {
+        return true;
+    }
+
+    return false;
+}
+
+// LinkJavaScript
+LinkJavaScript::LinkJavaScript(const QRectF &linkArea, const QString &js) : Link(*new LinkJavaScriptPrivate(linkArea))
+{
+    Q_D(LinkJavaScript);
+    d->js = js;
+}
+
+LinkJavaScript::~LinkJavaScript() { }
+
+Link::LinkType LinkJavaScript::linkType() const
+{
+    return JavaScript;
+}
+
+QString LinkJavaScript::script() const
+{
+    Q_D(const LinkJavaScript);
+    return d->js;
+}
+
+// LinkMovie
+LinkMovie::LinkMovie(const QRectF &linkArea, Operation operation, const QString &annotationTitle, const Ref annotationReference) : Link(*new LinkMoviePrivate(linkArea, operation, annotationTitle, annotationReference)) { }
+
+LinkMovie::~LinkMovie() { }
+
+Link::LinkType LinkMovie::linkType() const
+{
+    return Movie;
+}
+
+LinkMovie::Operation LinkMovie::operation() const
+{
+    Q_D(const LinkMovie);
+    return d->operation;
+}
+
+bool LinkMovie::isReferencedAnnotation(const MovieAnnotation *annotation) const
+{
+    Q_D(const LinkMovie);
+    if (d->annotationReference != Ref::INVALID() && d->annotationReference == annotation->d_ptr->pdfObjectReference()) {
+        return true;
+    } else if (!d->annotationTitle.isNull()) {
+        return (annotation->movieTitle() == d->annotationTitle);
+    }
+
+    return false;
+}
+
+LinkOCGState::LinkOCGState(LinkOCGStatePrivate *ocgp) : Link(*ocgp) { }
+
+LinkOCGState::~LinkOCGState() { }
+
+Link::LinkType LinkOCGState::linkType() const
+{
+    return OCGState;
+}
+
+// LinkHide
+LinkHide::LinkHide(LinkHidePrivate *lhidep) : Link(*lhidep) { }
+
+LinkHide::~LinkHide() { }
+
+Link::LinkType LinkHide::linkType() const
+{
+    return Hide;
+}
+
+QVector<QString> LinkHide::targets() const
+{
+    Q_D(const LinkHide);
+    return QVector<QString>() << d->targetName;
+}
+
+bool LinkHide::isShowAction() const
+{
+    Q_D(const LinkHide);
+    return d->isShow;
+}
+}
diff --git a/qt6/src/poppler-link.h b/qt6/src/poppler-link.h
new file mode 100644
index 0000000..ae65ce7
--- /dev/null
+++ b/qt6/src/poppler-link.h
@@ -0,0 +1,657 @@
+/* poppler-link.h: qt interface to poppler
+ * Copyright (C) 2006, 2013, 2016, 2018, 2019, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2007-2008, 2010, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2010, 2012, Guillermo Amaral <gamaral@kdab.com>
+ * Copyright (C) 2012, Tobias Koenig <tokoe@kdab.com>
+ * Copyright (C) 2013, Anthony Granger <grangeranthony@gmail.com>
+ * Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
+ * Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Adapting code from
+ *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_LINK_H_
+#define _POPPLER_LINK_H_
+
+#include <QtCore/QString>
+#include <QtCore/QRectF>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QVector>
+#include "poppler-export.h"
+
+struct Ref;
+class MediaRendition;
+
+namespace Poppler {
+
+class LinkPrivate;
+class LinkGotoPrivate;
+class LinkExecutePrivate;
+class LinkBrowsePrivate;
+class LinkActionPrivate;
+class LinkSoundPrivate;
+class LinkJavaScriptPrivate;
+class LinkMoviePrivate;
+class LinkDestinationData;
+class LinkDestinationPrivate;
+class LinkRenditionPrivate;
+class LinkOCGStatePrivate;
+class LinkHidePrivate;
+class MediaRendition;
+class MovieAnnotation;
+class ScreenAnnotation;
+class SoundObject;
+
+/**
+ * \short A destination.
+ *
+ * The LinkDestination class represent a "destination" (in terms of visual
+ * viewport to be displayed) for \link Poppler::LinkGoto GoTo\endlink links,
+ * and items in the table of contents (TOC) of a document.
+ *
+ * Coordinates are in 0..1 range
+ */
+class POPPLER_QT6_EXPORT LinkDestination
+{
+public:
+    /**
+     * The possible kind of "viewport destination".
+     */
+    enum Kind
+    {
+        /**
+         * The new viewport is specified in terms of:
+         * - possible new left coordinate (see isChangeLeft() )
+         * - possible new top coordinate (see isChangeTop() )
+         * - possible new zoom level (see isChangeZoom() )
+         */
+        destXYZ = 1,
+        destFit = 2,
+        destFitH = 3,
+        destFitV = 4,
+        destFitR = 5,
+        destFitB = 6,
+        destFitBH = 7,
+        destFitBV = 8
+    };
+
+    /// \cond PRIVATE
+    LinkDestination(const LinkDestinationData &data);
+    LinkDestination(const QString &description);
+    /// \endcond
+    /**
+     * Copy constructor.
+     */
+    LinkDestination(const LinkDestination &other);
+    /**
+     * Destructor.
+     */
+    ~LinkDestination();
+
+    // Accessors.
+    /**
+     * The kind of destination.
+     */
+    Kind kind() const;
+    /**
+     * Which page is the target of this destination.
+     *
+     * \note this number is 1-based, so for a 5 pages document the
+     *       valid page numbers go from 1 to 5 (both included).
+     */
+    int pageNumber() const;
+    /**
+     * The new left for the viewport of the target page, in case
+     * it is specified to be changed (see isChangeLeft() )
+     */
+    double left() const;
+    double bottom() const;
+    double right() const;
+    /**
+     * The new top for the viewport of the target page, in case
+     * it is specified to be changed (see isChangeTop() )
+     */
+    double top() const;
+    double zoom() const;
+    /**
+     * Whether the left of the viewport on the target page should
+     * be changed.
+     *
+     * \see left()
+     */
+    bool isChangeLeft() const;
+    /**
+     * Whether the top of the viewport on the target page should
+     * be changed.
+     *
+     * \see top()
+     */
+    bool isChangeTop() const;
+    /**
+     * Whether the zoom level should be changed.
+     *
+     * \see zoom()
+     */
+    bool isChangeZoom() const;
+
+    /**
+     * Return a string repesentation of this destination.
+     */
+    QString toString() const;
+
+    /**
+     * Return the name of this destination.
+     */
+    QString destinationName() const;
+
+    /**
+     * Assignment operator.
+     */
+    LinkDestination &operator=(const LinkDestination &other);
+
+private:
+    QSharedDataPointer<LinkDestinationPrivate> d;
+};
+
+/**
+ * \short Encapsulates data that describes a link.
+ *
+ * This is the base class for links. It makes mandatory for inherited
+ * kind of links to reimplement the linkType() method and return the type of
+ * the link described by the reimplemented class.
+ */
+class POPPLER_QT6_EXPORT Link
+{
+public:
+    /// \cond PRIVATE
+    Link(const QRectF &linkArea);
+    /// \endcond
+
+    /**
+     * The possible kinds of link.
+     *
+     * Inherited classes must return an unique identifier
+     */
+    enum LinkType
+    {
+        None, ///< Unknown link
+        Goto, ///< A "Go To" link
+        Execute, ///< A command to be executed
+        Browse, ///< An URL to be browsed (eg "http://poppler.freedesktop.org")
+        Action, ///< A "standard" action to be executed in the viewer
+        Sound, ///< A link representing a sound to be played
+        Movie, ///< An action to be executed on a movie
+        Rendition, ///< A rendition link
+        JavaScript, ///< A JavaScript code to be interpreted
+        OCGState, ///< An Optional Content Group state change
+        Hide, ///< An action to hide a field
+    };
+
+    /**
+     * The type of this link.
+     */
+    virtual LinkType linkType() const;
+
+    /**
+     * Destructor.
+     */
+    virtual ~Link();
+
+    /**
+     * The area of a Page where the link should be active.
+     *
+     * \note this can be a null rect, in this case the link represents
+     * a general action. The area is given in 0..1 range
+     */
+    QRectF linkArea() const;
+
+    /**
+     * Get the next links to be activated / executed after this link.
+     */
+    QVector<Link *> nextLinks() const;
+
+protected:
+    /// \cond PRIVATE
+    Link(LinkPrivate &dd);
+    Q_DECLARE_PRIVATE(Link)
+    LinkPrivate *d_ptr;
+    /// \endcond
+
+private:
+    Q_DISABLE_COPY(Link)
+};
+
+/**
+ * \brief Viewport reaching request.
+ *
+ * With a LinkGoto link, the document requests the specified viewport to be
+ * reached (aka, displayed in a viewer). Furthermore, if a file name is specified,
+ * then the destination refers to that document (and not to the document the
+ * current LinkGoto belongs to).
+ */
+class POPPLER_QT6_EXPORT LinkGoto : public Link
+{
+public:
+    /**
+     * Create a new Goto link.
+     *
+     * \param linkArea the active area of the link
+     * \param extFileName if not empty, the file name to be open
+     * \param destination the destination to be reached
+     */
+    LinkGoto(const QRectF &linkArea, const QString &extFileName, const LinkDestination &destination);
+    /**
+     * Destructor.
+     */
+    ~LinkGoto() override;
+
+    /**
+     * Whether the destination is in an external document
+     * (i.e. not the current document)
+     */
+    bool isExternal() const;
+    // query for goto parameters
+    /**
+     * The file name of the document the destination() refers to,
+     * or an empty string in case it refers to the current document.
+     */
+    QString fileName() const;
+    /**
+     * The destination to reach.
+     */
+    LinkDestination destination() const;
+    LinkType linkType() const override;
+
+private:
+    Q_DECLARE_PRIVATE(LinkGoto)
+    Q_DISABLE_COPY(LinkGoto)
+};
+
+/**
+ * \brief Generic execution request.
+ *
+ * The LinkExecute link represent a "file name" execution request. The result
+ * depends on the \link fileName() file name\endlink:
+ * - if it is a document, then it is requested to be open
+ * - otherwise, it represents an executable to be run with the specified parameters
+ */
+class POPPLER_QT6_EXPORT LinkExecute : public Link
+{
+public:
+    /**
+     * The file name to be executed
+     */
+    QString fileName() const;
+    /**
+     * The parameters for the command.
+     */
+    QString parameters() const;
+
+    /**
+     * Create a new Execute link.
+     *
+     * \param linkArea the active area of the link
+     * \param file the file name to be open, or the program to be execute
+     * \param params the parameters for the program to execute
+     */
+    LinkExecute(const QRectF &linkArea, const QString &file, const QString &params);
+    /**
+     * Destructor.
+     */
+    ~LinkExecute() override;
+    LinkType linkType() const override;
+
+private:
+    Q_DECLARE_PRIVATE(LinkExecute)
+    Q_DISABLE_COPY(LinkExecute)
+};
+
+/**
+ * \brief An URL to browse.
+ *
+ * The LinkBrowse link holds a URL (eg 'http://poppler.freedesktop.org',
+ * 'mailto:john@some.org', etc) to be open.
+ *
+ * The format of the URL is specified by RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt)
+ */
+class POPPLER_QT6_EXPORT LinkBrowse : public Link
+{
+public:
+    /**
+     * The URL to open
+     */
+    QString url() const;
+
+    /**
+     * Create a new browse link.
+     *
+     * \param linkArea the active area of the link
+     * \param url the URL to be open
+     */
+    LinkBrowse(const QRectF &linkArea, const QString &url);
+    /**
+     * Destructor.
+     */
+    ~LinkBrowse() override;
+    LinkType linkType() const override;
+
+private:
+    Q_DECLARE_PRIVATE(LinkBrowse)
+    Q_DISABLE_COPY(LinkBrowse)
+};
+
+/**
+ * \brief "Standard" action request.
+ *
+ * The LinkAction class represents a link that request a "standard" action
+ * to be performed by the viewer on the displayed document.
+ */
+class POPPLER_QT6_EXPORT LinkAction : public Link
+{
+public:
+    /**
+     * The possible types of actions
+     */
+    enum ActionType
+    {
+        PageFirst = 1,
+        PagePrev = 2,
+        PageNext = 3,
+        PageLast = 4,
+        HistoryBack = 5,
+        HistoryForward = 6,
+        Quit = 7,
+        Presentation = 8,
+        EndPresentation = 9,
+        Find = 10,
+        GoToPage = 11,
+        Close = 12,
+        Print = 13
+    };
+
+    /**
+     * The action of the current LinkAction
+     */
+    ActionType actionType() const;
+
+    /**
+     * Create a new Action link, that executes a specified action
+     * on the document.
+     *
+     * \param linkArea the active area of the link
+     * \param actionType which action should be executed
+     */
+    LinkAction(const QRectF &linkArea, ActionType actionType);
+    /**
+     * Destructor.
+     */
+    ~LinkAction() override;
+    LinkType linkType() const override;
+
+private:
+    Q_DECLARE_PRIVATE(LinkAction)
+    Q_DISABLE_COPY(LinkAction)
+};
+
+/**
+ * Sound: a sound to be played.
+ */
+class POPPLER_QT6_EXPORT LinkSound : public Link
+{
+public:
+    // create a Link_Sound
+    LinkSound(const QRectF &linkArea, double volume, bool sync, bool repeat, bool mix, SoundObject *sound);
+    /**
+     * Destructor.
+     */
+    ~LinkSound() override;
+
+    LinkType linkType() const override;
+
+    /**
+     * The volume to be used when playing the sound.
+     *
+     * The volume is in the range [ -1, 1 ], where:
+     * - a negative number: no volume (mute)
+     * - 1: full volume
+     */
+    double volume() const;
+    /**
+     * Whether the playback of the sound should be synchronous
+     * (thus blocking, waiting for the end of the sound playback).
+     */
+    bool synchronous() const;
+    /**
+     * Whether the sound should be played continuously (that is,
+     * started again when it ends)
+     */
+    bool repeat() const;
+    /**
+     * Whether the playback of this sound can be mixed with
+     * playbacks with other sounds of the same document.
+     *
+     * \note When false, any other playback must be stopped before
+     *       playing the sound.
+     */
+    bool mix() const;
+    /**
+     * The sound object to be played
+     */
+    SoundObject *sound() const;
+
+private:
+    Q_DECLARE_PRIVATE(LinkSound)
+    Q_DISABLE_COPY(LinkSound)
+};
+
+/**
+ * Rendition: Rendition link.
+ */
+class POPPLER_QT6_EXPORT LinkRendition : public Link
+{
+public:
+    /**
+     * Describes the possible rendition actions.
+     */
+    enum RenditionAction
+    {
+        NoRendition,
+        PlayRendition,
+        StopRendition,
+        PauseRendition,
+        ResumeRendition
+    };
+
+    /**
+     * Create a new rendition link.
+     *
+     * \param linkArea the active area of the link
+     * \param rendition the media rendition object. Ownership is taken
+     * \param operation the numeric operation (action) (@see ::LinkRendition::RenditionOperation)
+     * \param script the java script code
+     * \param annotationReference the object reference of the screen annotation associated with this rendition action
+     */
+    LinkRendition(const QRectF &linkArea, ::MediaRendition *rendition, int operation, const QString &script, const Ref annotationReference);
+
+    /**
+     * Destructor.
+     */
+    ~LinkRendition() override;
+
+    LinkType linkType() const override;
+
+    /**
+     * Returns the media rendition object if the redition provides one, @c 0 otherwise
+     */
+    MediaRendition *rendition() const;
+
+    /**
+     * Returns the action that should be executed if a rendition object is provided.
+     */
+    RenditionAction action() const;
+
+    /**
+     * The JS code that shall be executed or an empty string.
+     */
+    QString script() const;
+
+    /**
+     * Returns whether the given @p annotation is the referenced screen annotation for this rendition @p link.
+     */
+    bool isReferencedAnnotation(const ScreenAnnotation *annotation) const;
+
+private:
+    Q_DECLARE_PRIVATE(LinkRendition)
+    Q_DISABLE_COPY(LinkRendition)
+};
+
+/**
+ * JavaScript: a JavaScript code to be interpreted.
+ */
+class POPPLER_QT6_EXPORT LinkJavaScript : public Link
+{
+public:
+    /**
+     * Create a new JavaScript link.
+     *
+     * \param linkArea the active area of the link
+     * \param js the JS code to be interpreted
+     */
+    LinkJavaScript(const QRectF &linkArea, const QString &js);
+    /**
+     * Destructor.
+     */
+    ~LinkJavaScript() override;
+
+    LinkType linkType() const override;
+
+    /**
+     * The JS code
+     */
+    QString script() const;
+
+private:
+    Q_DECLARE_PRIVATE(LinkJavaScript)
+    Q_DISABLE_COPY(LinkJavaScript)
+};
+
+/**
+ * Movie: a movie to be played.
+ */
+class POPPLER_QT6_EXPORT LinkMovie : public Link
+{
+public:
+    /**
+     * Describes the operation to be performed on the movie.
+     */
+    enum Operation
+    {
+        Play,
+        Stop,
+        Pause,
+        Resume
+    };
+
+    /**
+     * Create a new Movie link.
+     *
+     * \param linkArea the active area of the link
+     * \param operation the operation to be performed on the movie
+     * \param annotationTitle the title of the movie annotation identifying the movie to be played
+     * \param annotationReference the object reference of the movie annotation identifying the movie to be played
+     *
+     * Note: This constructor is supposed to be used by Poppler::Page only.
+     */
+    LinkMovie(const QRectF &linkArea, Operation operation, const QString &annotationTitle, const Ref annotationReference);
+    /**
+     * Destructor.
+     */
+    ~LinkMovie() override;
+    LinkType linkType() const override;
+    /**
+     * Returns the operation to be performed on the movie.
+     */
+    Operation operation() const;
+    /**
+     * Returns whether the given @p annotation is the referenced movie annotation for this movie @p link.
+     */
+    bool isReferencedAnnotation(const MovieAnnotation *annotation) const;
+
+private:
+    Q_DECLARE_PRIVATE(LinkMovie)
+    Q_DISABLE_COPY(LinkMovie)
+};
+
+/**
+ * OCGState: an optional content group state change.
+ */
+class POPPLER_QT6_EXPORT LinkOCGState : public Link
+{
+    friend class OptContentModel;
+
+public:
+    /**
+     * Create a new OCGState link. This is only used by Poppler::Page.
+     */
+    LinkOCGState(LinkOCGStatePrivate *ocgp);
+    /**
+     * Destructor.
+     */
+    ~LinkOCGState() override;
+
+    LinkType linkType() const override;
+
+private:
+    Q_DECLARE_PRIVATE(LinkOCGState)
+    Q_DISABLE_COPY(LinkOCGState)
+};
+
+/**
+ * Hide: an action to show / hide a field.
+ */
+class POPPLER_QT6_EXPORT LinkHide : public Link
+{
+public:
+    /**
+     * Create a new Hide link. This is only used by Poppler::Page.
+     */
+    LinkHide(LinkHidePrivate *lhidep);
+    /**
+     * Destructor.
+     */
+    ~LinkHide() override;
+
+    LinkType linkType() const override;
+
+    /**
+     * The fully qualified target names of the action.
+     */
+    QVector<QString> targets() const;
+
+    /**
+     * Should this action change the visibility of the target to true.
+     */
+    bool isShowAction() const;
+
+private:
+    Q_DECLARE_PRIVATE(LinkHide)
+    Q_DISABLE_COPY(LinkHide)
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-media.cc b/qt6/src/poppler-media.cc
new file mode 100644
index 0000000..b493b8d
--- /dev/null
+++ b/qt6/src/poppler-media.cc
@@ -0,0 +1,154 @@
+/* poppler-media.cc: qt interface to poppler
+ * Copyright (C) 2012 Guillermo A. Amaral B. <gamaral@kde.org>
+ * Copyright (C) 2013, 2018 Albert Astals Cid <aacid@kde.org>
+ *
+ * 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-media.h"
+
+#include "Rendition.h"
+
+#include "poppler-private.h"
+
+#include <QtCore/QBuffer>
+
+#define BUFFER_MAX 4096
+
+namespace Poppler {
+
+class MediaRenditionPrivate
+{
+public:
+    MediaRenditionPrivate(::MediaRendition *renditionA) : rendition(renditionA) { }
+
+    ~MediaRenditionPrivate() { delete rendition; }
+
+    MediaRenditionPrivate(const MediaRenditionPrivate &) = delete;
+    MediaRenditionPrivate &operator=(const MediaRenditionPrivate &) = delete;
+
+    ::MediaRendition *rendition;
+};
+
+MediaRendition::MediaRendition(::MediaRendition *rendition) : d_ptr(new MediaRenditionPrivate(rendition)) { }
+
+MediaRendition::~MediaRendition()
+{
+    delete d_ptr;
+}
+
+bool MediaRendition::isValid() const
+{
+    Q_D(const MediaRendition);
+    return d->rendition && d->rendition->isOk();
+}
+
+QString MediaRendition::contentType() const
+{
+    Q_ASSERT(isValid() && "Invalid media rendition.");
+    Q_D(const MediaRendition);
+    return UnicodeParsedString(d->rendition->getContentType());
+}
+
+QString MediaRendition::fileName() const
+{
+    Q_ASSERT(isValid() && "Invalid media rendition.");
+    Q_D(const MediaRendition);
+    return UnicodeParsedString(d->rendition->getFileName());
+}
+
+bool MediaRendition::isEmbedded() const
+{
+    Q_ASSERT(isValid() && "Invalid media rendition.");
+    Q_D(const MediaRendition);
+    return d->rendition->getIsEmbedded();
+}
+
+QByteArray MediaRendition::data() const
+{
+    Q_ASSERT(isValid() && "Invalid media rendition.");
+    Q_D(const MediaRendition);
+
+    Stream *s = d->rendition->getEmbbededStream();
+    if (!s)
+        return QByteArray();
+
+    QBuffer buffer;
+    unsigned char data[BUFFER_MAX];
+    int bread;
+
+    buffer.open(QIODevice::WriteOnly);
+    s->reset();
+    while ((bread = s->doGetChars(BUFFER_MAX, data)) != 0)
+        buffer.write(reinterpret_cast<const char *>(data), bread);
+    buffer.close();
+
+    return buffer.data();
+}
+
+bool MediaRendition::autoPlay() const
+{
+    Q_D(const MediaRendition);
+    if (d->rendition->getBEParameters()) {
+        return d->rendition->getBEParameters()->autoPlay;
+    } else if (d->rendition->getMHParameters()) {
+        return d->rendition->getMHParameters()->autoPlay;
+    } else
+        qDebug("No BE or MH parameters to reference!");
+    return false;
+}
+
+bool MediaRendition::showControls() const
+{
+    Q_D(const MediaRendition);
+    if (d->rendition->getBEParameters()) {
+        return d->rendition->getBEParameters()->showControls;
+    } else if (d->rendition->getMHParameters()) {
+        return d->rendition->getMHParameters()->showControls;
+    } else
+        qDebug("No BE or MH parameters to reference!");
+    return false;
+}
+
+float MediaRendition::repeatCount() const
+{
+    Q_D(const MediaRendition);
+    if (d->rendition->getBEParameters()) {
+        return d->rendition->getBEParameters()->repeatCount;
+    } else if (d->rendition->getMHParameters()) {
+        return d->rendition->getMHParameters()->repeatCount;
+    } else
+        qDebug("No BE or MH parameters to reference!");
+    return 1.f;
+}
+
+QSize MediaRendition::size() const
+{
+    Q_D(const MediaRendition);
+    const MediaParameters *mp = nullptr;
+
+    if (d->rendition->getBEParameters())
+        mp = d->rendition->getBEParameters();
+    else if (d->rendition->getMHParameters())
+        mp = d->rendition->getMHParameters();
+    else
+        qDebug("No BE or MH parameters to reference!");
+
+    if (mp)
+        return QSize(mp->windowParams.width, mp->windowParams.height);
+    return QSize();
+}
+
+}
diff --git a/qt6/src/poppler-media.h b/qt6/src/poppler-media.h
new file mode 100644
index 0000000..e6c793b
--- /dev/null
+++ b/qt6/src/poppler-media.h
@@ -0,0 +1,98 @@
+/* poppler-media.h: qt interface to poppler
+ * Copyright (C) 2012 Guillermo A. Amaral B. <gamaral@kde.org>
+ * Copyright (C) 2012, 2013 Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef __POPPLER_MEDIARENDITION_H__
+#define __POPPLER_MEDIARENDITION_H__
+
+#include "poppler-export.h"
+
+#include <QtCore/QSize>
+#include <QtCore/QString>
+
+class MediaRendition;
+class QIODevice;
+
+namespace Poppler {
+class MediaRenditionPrivate;
+
+/**
+  Qt wrapper for MediaRendition.
+ */
+class POPPLER_QT6_EXPORT MediaRendition
+{
+public:
+    /**
+      Constructs a MediaRendition. Takes ownership of the passed rendition
+     */
+    MediaRendition(::MediaRendition *rendition);
+    ~MediaRendition();
+
+    /**
+      Check if wrapper is holding a valid rendition object.
+     */
+    bool isValid() const;
+
+    /**
+      Returns content type.
+     */
+    QString contentType() const;
+
+    /**
+      Returns file name.
+     */
+    QString fileName() const;
+
+    /**
+      Returns true if media is embedded.
+     */
+    bool isEmbedded() const;
+
+    /**
+      Returns data buffer.
+     */
+    QByteArray data() const;
+
+    /**
+      Convenience accessor for auto-play parameter.
+     */
+    bool autoPlay() const;
+
+    /**
+      Convenience accessor for show controls parameter.
+     */
+    bool showControls() const;
+
+    /**
+      Convenience accessor for repeat count parameter.
+     */
+    float repeatCount() const;
+
+    /**
+      Convenience accessor for size parameter.
+     */
+    QSize size() const;
+
+private:
+    Q_DECLARE_PRIVATE(MediaRendition)
+    MediaRenditionPrivate *d_ptr;
+    Q_DISABLE_COPY(MediaRendition)
+};
+}
+
+#endif /* __POPPLER_MEDIARENDITION_H__ */
diff --git a/qt6/src/poppler-movie.cc b/qt6/src/poppler-movie.cc
new file mode 100644
index 0000000..8663b20
--- /dev/null
+++ b/qt6/src/poppler-movie.cc
@@ -0,0 +1,106 @@
+/* poppler-sound.cc: qt interface to poppler
+ * Copyright (C) 2008, 2010, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, 2018, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2010, Carlos Garcia Campos <carlosgc@gnome.org>
+ * Copyright (C) 2012, Tobias Koenig <tobias.koenig@kdab.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 "Object.h"
+#include "Annot.h"
+#include "Movie.h"
+
+#include <QtGui/QImage>
+
+namespace Poppler {
+
+class MovieData
+{
+public:
+    MovieData() : m_movieObj(nullptr) { }
+
+    ~MovieData() { delete m_movieObj; }
+
+    MovieData(const MovieData &) = delete;
+    MovieData &operator=(const MovieData &) = delete;
+
+    Movie *m_movieObj;
+    QSize m_size;
+    int m_rotation;
+    QImage m_posterImage;
+    MovieObject::PlayMode m_playMode : 3;
+    bool m_showControls : 1;
+};
+
+MovieObject::MovieObject(AnnotMovie *ann)
+{
+    m_movieData = new MovieData();
+    m_movieData->m_movieObj = ann->getMovie()->copy();
+    // TODO: copy poster image
+
+    const MovieActivationParameters *mp = m_movieData->m_movieObj->getActivationParameters();
+    int width, height;
+    m_movieData->m_movieObj->getFloatingWindowSize(&width, &height);
+    m_movieData->m_size = QSize(width, height);
+    m_movieData->m_rotation = m_movieData->m_movieObj->getRotationAngle();
+    m_movieData->m_showControls = mp->showControls;
+    m_movieData->m_playMode = (MovieObject::PlayMode)mp->repeatMode;
+}
+
+MovieObject::~MovieObject()
+{
+    delete m_movieData;
+}
+
+QString MovieObject::url() const
+{
+    const GooString *goo = m_movieData->m_movieObj->getFileName();
+    return goo ? QString(goo->c_str()) : QString();
+}
+
+QSize MovieObject::size() const
+{
+    return m_movieData->m_size;
+}
+
+int MovieObject::rotation() const
+{
+    return m_movieData->m_rotation;
+}
+
+bool MovieObject::showControls() const
+{
+    return m_movieData->m_showControls;
+}
+
+MovieObject::PlayMode MovieObject::playMode() const
+{
+    return m_movieData->m_playMode;
+}
+
+bool MovieObject::showPosterImage() const
+{
+    return (m_movieData->m_movieObj->getShowPoster() == true);
+}
+
+QImage MovieObject::posterImage() const
+{
+    return m_movieData->m_posterImage;
+}
+
+}
diff --git a/qt6/src/poppler-optcontent-private.h b/qt6/src/poppler-optcontent-private.h
new file mode 100644
index 0000000..550a7bc
--- /dev/null
+++ b/qt6/src/poppler-optcontent-private.h
@@ -0,0 +1,133 @@
+/* poppler-optcontent-private.h: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <bradh@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2016, 2018, 2019, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2017, Hubert Figuière <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.
+ */
+
+#ifndef POPPLER_OPTCONTENT_PRIVATE_H
+#define POPPLER_OPTCONTENT_PRIVATE_H
+
+#include <QtCore/QMap>
+#include <QtCore/QSet>
+#include <QtCore/QString>
+
+class Array;
+class OCGs;
+class OptionalContentGroup;
+
+class QModelIndex;
+
+namespace Poppler {
+class OptContentItem;
+class OptContentModel;
+class OptContentModelPrivate;
+
+class RadioButtonGroup
+{
+public:
+    RadioButtonGroup(OptContentModelPrivate *ocModel, Array *rbarray);
+    ~RadioButtonGroup();
+    QSet<OptContentItem *> setItemOn(OptContentItem *itemToSetOn);
+
+private:
+    QList<OptContentItem *> itemsInGroup;
+};
+
+class OptContentItem
+{
+public:
+    enum ItemState
+    {
+        On,
+        Off,
+        HeadingOnly
+    };
+
+    OptContentItem(OptionalContentGroup *group);
+    OptContentItem(const QString &label);
+    OptContentItem();
+    ~OptContentItem();
+
+    QString name() const { return m_name; }
+    ItemState state() const { return m_stateBackup; }
+    void setState(ItemState state, bool obeyRadioGroups, QSet<OptContentItem *> &changedItems);
+
+    QList<OptContentItem *> childList() { return m_children; }
+
+    void setParent(OptContentItem *parent) { m_parent = parent; }
+    OptContentItem *parent() { return m_parent; }
+
+    void addChild(OptContentItem *child);
+
+    void appendRBGroup(RadioButtonGroup *rbgroup);
+
+    bool isEnabled() const { return m_enabled; }
+
+    QSet<OptContentItem *> recurseListChildren(bool includeMe = false) const;
+
+    OptionalContentGroup *group() const { return m_group; }
+
+private:
+    OptionalContentGroup *m_group;
+    QString m_name;
+    ItemState m_state; // true for ON, false for OFF
+    ItemState m_stateBackup;
+    QList<OptContentItem *> m_children;
+    OptContentItem *m_parent;
+    QList<RadioButtonGroup *> m_rbGroups;
+    bool m_enabled;
+};
+
+class OptContentModelPrivate
+{
+public:
+    OptContentModelPrivate(OptContentModel *qq, OCGs *optContent);
+    ~OptContentModelPrivate();
+
+    OptContentModelPrivate(const OptContentModelPrivate &) = delete;
+    OptContentModelPrivate &operator=(const OptContentModelPrivate &) = delete;
+
+    void parseRBGroupsArray(Array *rBGroupArray);
+    OptContentItem *nodeFromIndex(const QModelIndex &index, bool canBeNull = false) const;
+    QModelIndex indexFromItem(OptContentItem *node, int column) const;
+
+    /**
+       Get the OptContentItem corresponding to a given reference value.
+
+       \param ref the reference number (e.g. from Object.getRefNum()) to look up
+
+       \return the matching optional content item, or null if the reference wasn't found
+    */
+    OptContentItem *itemFromRef(const QString &ref) const;
+    void setRootNode(OptContentItem *node);
+
+    OptContentModel *q;
+
+    QMap<QString, OptContentItem *> m_optContentItems;
+    QList<OptContentItem *> m_headerOptContentItems;
+    QList<RadioButtonGroup *> m_rbgroups;
+    OptContentItem *m_rootNode;
+
+private:
+    void addChild(OptContentItem *parent, OptContentItem *child);
+    void parseOrderArray(OptContentItem *parentNode, Array *orderArray);
+};
+}
+
+#endif
diff --git a/qt6/src/poppler-optcontent.cc b/qt6/src/poppler-optcontent.cc
new file mode 100644
index 0000000..f648119
--- /dev/null
+++ b/qt6/src/poppler-optcontent.cc
@@ -0,0 +1,437 @@
+/* poppler-optcontent.cc: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <bradh@kde.org>
+ * Copyright (C) 2008, 2014, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, Carlos Garcia Campos <carlosgc@gnome.org>
+ * Copyright (C) 2015-2019, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2017, Hubert Figuière <hub@figuiere.net>
+ * 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) 2018 Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ *
+ * 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-optcontent.h"
+
+#include "poppler-optcontent-private.h"
+
+#include "poppler-private.h"
+#include "poppler-link-private.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QtAlgorithms>
+
+#include "poppler/OptionalContent.h"
+#include "poppler/Link.h"
+
+namespace Poppler {
+RadioButtonGroup::RadioButtonGroup(OptContentModelPrivate *ocModel, Array *rbarray)
+{
+    itemsInGroup.reserve(rbarray->getLength());
+    for (int i = 0; i < rbarray->getLength(); ++i) {
+        const Object &ref = rbarray->getNF(i);
+        if (!ref.isRef()) {
+            qDebug() << "expected ref, but got:" << ref.getType();
+        }
+        OptContentItem *item = ocModel->itemFromRef(QString::number(ref.getRefNum()));
+        itemsInGroup.append(item);
+    }
+    for (OptContentItem *item : std::as_const(itemsInGroup)) {
+        item->appendRBGroup(this);
+    }
+}
+
+RadioButtonGroup::~RadioButtonGroup() { }
+
+QSet<OptContentItem *> RadioButtonGroup::setItemOn(OptContentItem *itemToSetOn)
+{
+    QSet<OptContentItem *> changedItems;
+    for (OptContentItem *thisItem : std::as_const(itemsInGroup)) {
+        if (thisItem != itemToSetOn) {
+            QSet<OptContentItem *> newChangedItems;
+            thisItem->setState(OptContentItem::Off, false /*obeyRadioGroups*/, newChangedItems);
+            changedItems += newChangedItems;
+        }
+    }
+    return changedItems;
+}
+
+OptContentItem::OptContentItem(OptionalContentGroup *group)
+{
+    m_group = group;
+    m_parent = nullptr;
+    m_name = UnicodeParsedString(group->getName());
+    if (group->getState() == OptionalContentGroup::On) {
+        m_state = OptContentItem::On;
+    } else {
+        m_state = OptContentItem::Off;
+    }
+    m_stateBackup = m_state;
+    m_enabled = true;
+}
+
+OptContentItem::OptContentItem(const QString &label)
+{
+    m_parent = nullptr;
+    m_name = label;
+    m_group = nullptr;
+    m_state = OptContentItem::HeadingOnly;
+    m_stateBackup = m_state;
+    m_enabled = true;
+}
+
+OptContentItem::OptContentItem() : m_parent(nullptr), m_enabled(true) { }
+
+OptContentItem::~OptContentItem() { }
+
+void OptContentItem::appendRBGroup(RadioButtonGroup *rbgroup)
+{
+    m_rbGroups.append(rbgroup);
+}
+
+void OptContentItem::setState(ItemState state, bool obeyRadioGroups, QSet<OptContentItem *> &changedItems)
+{
+    if (state == m_state)
+        return;
+
+    m_state = state;
+    m_stateBackup = m_state;
+    changedItems.insert(this);
+    QSet<OptContentItem *> empty;
+    Q_FOREACH (OptContentItem *child, m_children) {
+        ItemState oldState = child->m_stateBackup;
+        child->setState(state == OptContentItem::On ? child->m_stateBackup : OptContentItem::Off, true /*obeyRadioGroups*/, empty);
+        child->m_enabled = state == OptContentItem::On;
+        child->m_stateBackup = oldState;
+    }
+    if (!m_group) {
+        return;
+    }
+    if (state == OptContentItem::On) {
+        m_group->setState(OptionalContentGroup::On);
+        if (obeyRadioGroups) {
+            for (RadioButtonGroup *rbgroup : std::as_const(m_rbGroups)) {
+                changedItems += rbgroup->setItemOn(this);
+            }
+        }
+    } else if (state == OptContentItem::Off) {
+        m_group->setState(OptionalContentGroup::Off);
+    }
+}
+
+void OptContentItem::addChild(OptContentItem *child)
+{
+    m_children += child;
+    child->setParent(this);
+}
+
+QSet<OptContentItem *> OptContentItem::recurseListChildren(bool includeMe) const
+{
+    QSet<OptContentItem *> ret;
+    if (includeMe) {
+        ret.insert(const_cast<OptContentItem *>(this));
+    }
+    Q_FOREACH (OptContentItem *child, m_children) {
+        ret += child->recurseListChildren(true);
+    }
+    return ret;
+}
+
+OptContentModelPrivate::OptContentModelPrivate(OptContentModel *qq, OCGs *optContent) : q(qq)
+{
+    m_rootNode = new OptContentItem();
+    const auto &ocgs = optContent->getOCGs();
+
+    for (const auto &ocg : ocgs) {
+        OptContentItem *node = new OptContentItem(ocg.second.get());
+        m_optContentItems.insert(QString::number(ocg.first.num), node);
+    }
+
+    if (optContent->getOrderArray() == nullptr) {
+        // no Order array, so drop them all at the top level
+        QMapIterator<QString, OptContentItem *> i(m_optContentItems);
+        while (i.hasNext()) {
+            i.next();
+            addChild(m_rootNode, i.value());
+        }
+    } else {
+        parseOrderArray(m_rootNode, optContent->getOrderArray());
+    }
+
+    parseRBGroupsArray(optContent->getRBGroupsArray());
+}
+
+OptContentModelPrivate::~OptContentModelPrivate()
+{
+    qDeleteAll(m_optContentItems);
+    qDeleteAll(m_rbgroups);
+    qDeleteAll(m_headerOptContentItems);
+    delete m_rootNode;
+}
+
+void OptContentModelPrivate::parseOrderArray(OptContentItem *parentNode, Array *orderArray)
+{
+    OptContentItem *lastItem = parentNode;
+    for (int i = 0; i < orderArray->getLength(); ++i) {
+        Object orderItem = orderArray->get(i);
+        if (orderItem.isDict()) {
+            const Object &item = orderArray->getNF(i);
+            if (item.isRef()) {
+                OptContentItem *ocItem = m_optContentItems.value(QString::number(item.getRefNum()));
+                if (ocItem) {
+                    addChild(parentNode, ocItem);
+                    lastItem = ocItem;
+                } else {
+                    qDebug() << "could not find group for object" << item.getRefNum();
+                }
+            }
+        } else if ((orderItem.isArray()) && (orderItem.arrayGetLength() > 0)) {
+            parseOrderArray(lastItem, orderItem.getArray());
+        } else if (orderItem.isString()) {
+            const GooString *label = orderItem.getString();
+            OptContentItem *header = new OptContentItem(UnicodeParsedString(label));
+            m_headerOptContentItems.append(header);
+            addChild(parentNode, header);
+            parentNode = header;
+            lastItem = header;
+        } else {
+            qDebug() << "something unexpected";
+        }
+    }
+}
+
+void OptContentModelPrivate::parseRBGroupsArray(Array *rBGroupArray)
+{
+    if (!rBGroupArray) {
+        return;
+    }
+    // This is an array of array(s)
+    for (int i = 0; i < rBGroupArray->getLength(); ++i) {
+        Object rbObj = rBGroupArray->get(i);
+        if (!rbObj.isArray()) {
+            qDebug() << "expected inner array, got:" << rbObj.getType();
+            return;
+        }
+        Array *rbarray = rbObj.getArray();
+        RadioButtonGroup *rbg = new RadioButtonGroup(this, rbarray);
+        m_rbgroups.append(rbg);
+    }
+}
+
+OptContentModel::OptContentModel(OCGs *optContent, QObject *parent) : QAbstractItemModel(parent)
+{
+    d = new OptContentModelPrivate(this, optContent);
+}
+
+OptContentModel::~OptContentModel()
+{
+    delete d;
+}
+
+void OptContentModelPrivate::setRootNode(OptContentItem *node)
+{
+    q->beginResetModel();
+    delete m_rootNode;
+    m_rootNode = node;
+    q->endResetModel();
+}
+
+QModelIndex OptContentModel::index(int row, int column, const QModelIndex &parent) const
+{
+    if (row < 0 || column != 0) {
+        return QModelIndex();
+    }
+
+    OptContentItem *parentNode = d->nodeFromIndex(parent);
+    if (row < parentNode->childList().count()) {
+        return createIndex(row, column, parentNode->childList().at(row));
+    }
+    return QModelIndex();
+}
+
+QModelIndex OptContentModel::parent(const QModelIndex &child) const
+{
+    OptContentItem *childNode = d->nodeFromIndex(child);
+    if (!childNode) {
+        return QModelIndex();
+    }
+    return d->indexFromItem(childNode->parent(), child.column());
+}
+
+QModelIndex OptContentModelPrivate::indexFromItem(OptContentItem *node, int column) const
+{
+    if (!node) {
+        return QModelIndex();
+    }
+    OptContentItem *parentNode = node->parent();
+    if (!parentNode) {
+        return QModelIndex();
+    }
+    const int row = parentNode->childList().indexOf(node);
+    return q->createIndex(row, column, node);
+}
+
+int OptContentModel::rowCount(const QModelIndex &parent) const
+{
+    OptContentItem *parentNode = d->nodeFromIndex(parent);
+    if (!parentNode) {
+        return 0;
+    } else {
+        return parentNode->childList().count();
+    }
+}
+
+int OptContentModel::columnCount(const QModelIndex &parent) const
+{
+    return 1;
+}
+
+QVariant OptContentModel::data(const QModelIndex &index, int role) const
+{
+    OptContentItem *node = d->nodeFromIndex(index, true);
+    if (!node) {
+        return QVariant();
+    }
+
+    switch (role) {
+    case Qt::DisplayRole:
+        return node->name();
+        break;
+    case Qt::EditRole:
+        if (node->state() == OptContentItem::On) {
+            return true;
+        } else if (node->state() == OptContentItem::Off) {
+            return false;
+        }
+        break;
+    case Qt::CheckStateRole:
+        if (node->state() == OptContentItem::On) {
+            return Qt::Checked;
+        } else if (node->state() == OptContentItem::Off) {
+            return Qt::Unchecked;
+        }
+        break;
+    }
+
+    return QVariant();
+}
+
+bool OptContentModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    OptContentItem *node = d->nodeFromIndex(index, true);
+    if (!node) {
+        return false;
+    }
+
+    switch (role) {
+    case Qt::CheckStateRole: {
+        const bool newvalue = value.toBool();
+        QSet<OptContentItem *> changedItems;
+        node->setState(newvalue ? OptContentItem::On : OptContentItem::Off, true /*obeyRadioGroups*/, changedItems);
+
+        if (!changedItems.isEmpty()) {
+            changedItems += node->recurseListChildren(false);
+            QModelIndexList indexes;
+            Q_FOREACH (OptContentItem *item, changedItems) {
+                indexes.append(d->indexFromItem(item, 0));
+            }
+            std::stable_sort(indexes.begin(), indexes.end());
+            Q_FOREACH (const QModelIndex &changedIndex, indexes) {
+                emit dataChanged(changedIndex, changedIndex);
+            }
+            return true;
+        }
+        break;
+    }
+    }
+
+    return false;
+}
+
+Qt::ItemFlags OptContentModel::flags(const QModelIndex &index) const
+{
+    OptContentItem *node = d->nodeFromIndex(index);
+    Qt::ItemFlags itemFlags = Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
+    if (node->isEnabled()) {
+        itemFlags |= Qt::ItemIsEnabled;
+    }
+    return itemFlags;
+}
+
+QVariant OptContentModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    return QAbstractItemModel::headerData(section, orientation, role);
+}
+
+void OptContentModel::applyLink(LinkOCGState *link)
+{
+    LinkOCGStatePrivate *linkPrivate = link->d_func();
+
+    QSet<OptContentItem *> changedItems;
+
+    const std::vector<::LinkOCGState::StateList> &statesList = linkPrivate->stateList;
+    for (const ::LinkOCGState::StateList &stateList : statesList) {
+        const std::vector<Ref> &refsList = stateList.list;
+        for (const Ref &ref : refsList) {
+            OptContentItem *item = d->itemFromRef(QString::number(ref.num));
+
+            if (stateList.st == ::LinkOCGState::On) {
+                item->setState(OptContentItem::On, linkPrivate->preserveRB, changedItems);
+            } else if (stateList.st == ::LinkOCGState::Off) {
+                item->setState(OptContentItem::Off, linkPrivate->preserveRB, changedItems);
+            } else {
+                OptContentItem::ItemState newState = item->state() == OptContentItem::On ? OptContentItem::Off : OptContentItem::On;
+                item->setState(newState, linkPrivate->preserveRB, changedItems);
+            }
+        }
+    }
+
+    if (!changedItems.isEmpty()) {
+        QSet<OptContentItem *> aux;
+        Q_FOREACH (OptContentItem *item, aux) {
+            changedItems += item->recurseListChildren(false);
+        }
+
+        QModelIndexList indexes;
+        Q_FOREACH (OptContentItem *item, changedItems) {
+            indexes.append(d->indexFromItem(item, 0));
+        }
+        std::stable_sort(indexes.begin(), indexes.end());
+        Q_FOREACH (const QModelIndex &changedIndex, indexes) {
+            emit dataChanged(changedIndex, changedIndex);
+        }
+    }
+}
+
+void OptContentModelPrivate::addChild(OptContentItem *parent, OptContentItem *child)
+{
+    parent->addChild(child);
+}
+
+OptContentItem *OptContentModelPrivate::itemFromRef(const QString &ref) const
+{
+    return m_optContentItems.value(ref);
+}
+
+OptContentItem *OptContentModelPrivate::nodeFromIndex(const QModelIndex &index, bool canBeNull) const
+{
+    if (index.isValid()) {
+        return static_cast<OptContentItem *>(index.internalPointer());
+    } else {
+        return canBeNull ? nullptr : m_rootNode;
+    }
+}
+}
diff --git a/qt6/src/poppler-optcontent.h b/qt6/src/poppler-optcontent.h
new file mode 100644
index 0000000..2aaea36
--- /dev/null
+++ b/qt6/src/poppler-optcontent.h
@@ -0,0 +1,81 @@
+/* poppler-optcontent.h: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <bradh@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013, Anthony Granger <grangeranthony@gmail.com>
+ * Copyright (C) 2016, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef POPPLER_OPTCONTENT_H
+#define POPPLER_OPTCONTENT_H
+
+#include <QtCore/QAbstractListModel>
+
+#include "poppler-export.h"
+#include "poppler-link.h"
+
+class OCGs;
+
+namespace Poppler {
+class Document;
+class OptContentModelPrivate;
+
+/**
+ * \brief Model for optional content
+ *
+ * OptContentModel is an item model representing the optional content items
+ * that can be found in PDF documents.
+ *
+ * The model offers a mostly read-only display of the data, allowing to
+ * enable/disable some contents setting the Qt::CheckStateRole data role.
+ */
+class POPPLER_QT6_EXPORT OptContentModel : public QAbstractItemModel
+{
+    friend class Document;
+
+    Q_OBJECT
+
+public:
+    ~OptContentModel() override;
+
+    QModelIndex index(int row, int column, const QModelIndex &parent) const override;
+    QModelIndex parent(const QModelIndex &child) const override;
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    int columnCount(const QModelIndex &parent) const override;
+
+    QVariant data(const QModelIndex &index, int role) const override;
+    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+
+    Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+    /**
+     * Applies the Optional Content Changes specified by that link.
+     */
+    void applyLink(LinkOCGState *link);
+
+private:
+    OptContentModel(OCGs *optContent, QObject *parent = nullptr);
+
+    friend class OptContentModelPrivate;
+    OptContentModelPrivate *d;
+};
+}
+
+#endif
diff --git a/qt6/src/poppler-outline-private.h b/qt6/src/poppler-outline-private.h
new file mode 100644
index 0000000..e7b9080
--- /dev/null
+++ b/qt6/src/poppler-outline-private.h
@@ -0,0 +1,48 @@
+/* poppler-outline-private.h: qt interface to poppler
+ *
+ * Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019 Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_OUTLINE_PRIVATE_H_
+#define _POPPLER_OUTLINE_PRIVATE_H_
+
+#include <QtCore/QSharedPointer>
+#include <QtCore/QString>
+
+class OutlineItem;
+
+namespace Poppler {
+
+class DocumentData;
+class LinkDestination;
+
+struct OutlineItemData
+{
+    OutlineItemData(::OutlineItem *oi, DocumentData *dd) : data { oi }, documentData { dd } { }
+    ::OutlineItem *data;
+    DocumentData *documentData;
+
+    mutable QString name;
+    mutable QSharedPointer<const LinkDestination> destination;
+    mutable QString externalFileName;
+    mutable QString uri;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-outline.cc b/qt6/src/poppler-outline.cc
new file mode 100644
index 0000000..395d92d
--- /dev/null
+++ b/qt6/src/poppler-outline.cc
@@ -0,0 +1,183 @@
+/* poppler-outline.cc: qt interface to poppler
+ *
+ * Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2019 Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 <poppler-link.h>
+
+#include "poppler-private.h"
+#include "poppler-outline-private.h"
+
+#include "Link.h"
+#include "Outline.h"
+
+namespace Poppler {
+
+OutlineItem::OutlineItem() : m_data { new OutlineItemData { nullptr, nullptr } } { }
+
+OutlineItem::OutlineItem(OutlineItemData *data) : m_data { data } { }
+
+OutlineItem::~OutlineItem()
+{
+    delete m_data;
+    m_data = nullptr;
+}
+
+OutlineItem::OutlineItem(const OutlineItem &other) : m_data { new OutlineItemData { *other.m_data } } { }
+
+OutlineItem &OutlineItem::operator=(const OutlineItem &other)
+{
+    if (this == &other)
+        return *this;
+
+    auto *data = new OutlineItemData { *other.m_data };
+    qSwap(m_data, data);
+    delete data;
+
+    return *this;
+}
+
+OutlineItem::OutlineItem(OutlineItem &&other) noexcept : m_data { other.m_data }
+{
+    other.m_data = nullptr;
+}
+
+OutlineItem &OutlineItem::operator=(OutlineItem &&other) noexcept
+{
+    qSwap(m_data, other.m_data);
+
+    return *this;
+}
+
+bool OutlineItem::isNull() const
+{
+    return !m_data->data;
+}
+
+QString OutlineItem::name() const
+{
+    QString &name = m_data->name;
+
+    if (name.isEmpty()) {
+        if (const ::OutlineItem *data = m_data->data) {
+            name = unicodeToQString(data->getTitle(), data->getTitleLength());
+        }
+    }
+
+    return name;
+}
+
+bool OutlineItem::isOpen() const
+{
+    bool isOpen = false;
+
+    if (const ::OutlineItem *data = m_data->data) {
+        isOpen = data->isOpen();
+    }
+
+    return isOpen;
+}
+
+QSharedPointer<const LinkDestination> OutlineItem::destination() const
+{
+    QSharedPointer<const LinkDestination> &destination = m_data->destination;
+
+    if (!destination) {
+        if (const ::OutlineItem *data = m_data->data) {
+            if (const ::LinkAction *action = data->getAction()) {
+                if (action->getKind() == actionGoTo) {
+                    const auto *linkGoTo = static_cast<const LinkGoTo *>(action);
+                    destination.reset(new LinkDestination(LinkDestinationData(linkGoTo->getDest(), linkGoTo->getNamedDest(), m_data->documentData, false)));
+                } else if (action->getKind() == actionGoToR) {
+                    const auto *linkGoToR = static_cast<const LinkGoToR *>(action);
+                    const bool external = linkGoToR->getFileName() != nullptr;
+                    destination.reset(new LinkDestination(LinkDestinationData(linkGoToR->getDest(), linkGoToR->getNamedDest(), m_data->documentData, external)));
+                }
+            }
+        }
+    }
+
+    return destination;
+}
+
+QString OutlineItem::externalFileName() const
+{
+    QString &externalFileName = m_data->externalFileName;
+
+    if (externalFileName.isEmpty()) {
+        if (const ::OutlineItem *data = m_data->data) {
+            if (const ::LinkAction *action = data->getAction()) {
+                if (action->getKind() == actionGoToR) {
+                    if (const GooString *fileName = static_cast<const LinkGoToR *>(action)->getFileName()) {
+                        externalFileName = UnicodeParsedString(fileName);
+                    }
+                }
+            }
+        }
+    }
+
+    return externalFileName;
+}
+
+QString OutlineItem::uri() const
+{
+    QString &uri = m_data->uri;
+
+    if (uri.isEmpty()) {
+        if (const ::OutlineItem *data = m_data->data) {
+            if (const ::LinkAction *action = data->getAction()) {
+                if (action->getKind() == actionURI) {
+                    uri = UnicodeParsedString(static_cast<const LinkURI *>(action)->getURI());
+                }
+            }
+        }
+    }
+
+    return uri;
+}
+
+bool OutlineItem::hasChildren() const
+{
+    bool result = false;
+
+    if (::OutlineItem *data = m_data->data) {
+        result = data->hasKids();
+    }
+
+    return result;
+}
+
+QVector<OutlineItem> OutlineItem::children() const
+{
+    QVector<OutlineItem> result;
+
+    if (::OutlineItem *data = m_data->data) {
+        data->open();
+        if (const std::vector<::OutlineItem *> *kids = data->getKids()) {
+            for (void *kid : *kids) {
+                result.push_back(OutlineItem { new OutlineItemData { static_cast<::OutlineItem *>(kid), m_data->documentData } });
+            }
+        }
+    }
+
+    return result;
+}
+
+}
diff --git a/qt6/src/poppler-page-private.h b/qt6/src/poppler-page-private.h
new file mode 100644
index 0000000..e1312d4
--- /dev/null
+++ b/qt6/src/poppler-page-private.h
@@ -0,0 +1,58 @@
+/* poppler-page.cc: qt interface to poppler
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2007, 2012, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2015 Adam Reichold <adamreichold@myopera.com>
+ * Copyright (C) 2018 Nelson Benítez León <nbenitezl@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.
+ */
+
+#ifndef _POPPLER_PAGE_PRIVATE_H_
+#define _POPPLER_PAGE_PRIVATE_H_
+
+#include "CharTypes.h"
+
+class QRectF;
+
+class LinkAction;
+class Page;
+class TextPage;
+
+namespace Poppler {
+
+class DocumentData;
+class PageTransition;
+
+class PageData
+{
+public:
+    Link *convertLinkActionToLink(::LinkAction *a, const QRectF &linkArea);
+
+    DocumentData *parentDoc;
+    ::Page *page;
+    int index;
+    PageTransition *transition;
+
+    static Link *convertLinkActionToLink(::LinkAction *a, DocumentData *parentDoc, const QRectF &linkArea);
+
+    TextPage *prepareTextSearch(const QString &text, Page::Rotation rotate, QVector<Unicode> *u);
+    bool performSingleTextSearch(TextPage *textPage, QVector<Unicode> &u, double &sLeft, double &sTop, double &sRight, double &sBottom, Page::SearchDirection direction, bool sCase, bool sWords, bool sDiacritics);
+    QList<QRectF> performMultipleTextSearch(TextPage *textPage, QVector<Unicode> &u, bool sCase, bool sWords, bool sDiacritics);
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-page-transition-private.h b/qt6/src/poppler-page-transition-private.h
new file mode 100644
index 0000000..4dabd14
--- /dev/null
+++ b/qt6/src/poppler-page-transition-private.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005, 2019, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2019 Oliver Sander <oliver.sander@tu-dresden.de>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_PAGE_TRANSITION_PRIVATE_H_
+#define _POPPLER_PAGE_TRANSITION_PRIVATE_H_
+
+class Object;
+
+namespace Poppler {
+
+class PageTransitionParams
+{
+public:
+    Object *dictObj;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-page-transition.cc b/qt6/src/poppler-page-transition.cc
new file mode 100644
index 0000000..30f8bee
--- /dev/null
+++ b/qt6/src/poppler-page-transition.cc
@@ -0,0 +1,100 @@
+/* PageTransition.cc
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2015, Arseniy Lartsev <arseniy@alumni.chalmers.se>
+ * Copyright (C) 2018 Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "PageTransition.h"
+#include "poppler-page-transition.h"
+#include "poppler-page-transition-private.h"
+
+namespace Poppler {
+
+class PageTransitionData
+{
+public:
+    PageTransitionData(Object *trans) { pt = new ::PageTransition(trans); }
+
+    PageTransitionData(const PageTransitionData &ptd) { pt = new ::PageTransition(*ptd.pt); }
+
+    ~PageTransitionData() { delete pt; }
+
+    PageTransitionData &operator=(const PageTransitionData &) = delete;
+
+    ::PageTransition *pt;
+};
+
+PageTransition::PageTransition(const PageTransitionParams params)
+{
+    data = new PageTransitionData(params.dictObj);
+}
+
+PageTransition::PageTransition(const PageTransition &pt)
+{
+    data = new PageTransitionData(*pt.data);
+}
+
+PageTransition::~PageTransition()
+{
+    delete data;
+}
+
+PageTransition &PageTransition::operator=(const PageTransition &other)
+{
+    if (this != &other) {
+        delete data;
+        data = new PageTransitionData(*other.data);
+    }
+
+    return *this;
+}
+
+PageTransition::Type PageTransition::type() const
+{
+    return (Poppler::PageTransition::Type)data->pt->getType();
+}
+
+double PageTransition::durationReal() const
+{
+    return data->pt->getDuration();
+}
+
+PageTransition::Alignment PageTransition::alignment() const
+{
+    return (Poppler::PageTransition::Alignment)data->pt->getAlignment();
+}
+
+PageTransition::Direction PageTransition::direction() const
+{
+    return (Poppler::PageTransition::Direction)data->pt->getDirection();
+}
+
+int PageTransition::angle() const
+{
+    return data->pt->getAngle();
+}
+
+double PageTransition::scale() const
+{
+    return data->pt->getScale();
+}
+bool PageTransition::isRectangular() const
+{
+    return data->pt->isRectangular();
+}
+
+}
diff --git a/qt6/src/poppler-page-transition.h b/qt6/src/poppler-page-transition.h
new file mode 100644
index 0000000..e7dac91
--- /dev/null
+++ b/qt6/src/poppler-page-transition.h
@@ -0,0 +1,142 @@
+/* PageTransition.h
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2015, Arseniy Lartsev <arseniy@alumni.chalmers.se>
+ * Copyright (C) 2018 Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef __PAGETRANSITION_X_H__
+#define __PAGETRANSITION_X_H__
+
+#include "poppler-export.h"
+
+#include <QtCore/qglobal.h>
+
+namespace Poppler {
+
+class PageTransitionParams;
+class PageTransitionData;
+
+/**
+   \brief Describes how a PDF file viewer shall perform the transition
+   from one page to another
+
+   In PDF files there is a way to specify if the viewer shall use
+   certain effects to perform the transition from one page to
+   another. This feature can be used, e.g., in a PDF-based beamer
+   presentation.
+
+   This utility class represents the transition effect, and can be
+   used to extract the information from a PDF object.
+*/
+
+class POPPLER_QT6_EXPORT PageTransition
+{
+public:
+    /** \brief transition effect that shall be used
+     */
+    // if changed remember to keep in sync with PageTransition.h enum
+    enum Type
+    {
+        Replace = 0,
+        Split,
+        Blinds,
+        Box,
+        Wipe,
+        Dissolve,
+        Glitter,
+        Fly,
+        Push,
+        Cover,
+        Uncover,
+        Fade
+    };
+
+    /** \brief alignment of the transition effect that shall be used
+     */
+    // if changed remember to keep in sync with PageTransition.h enum
+    enum Alignment
+    {
+        Horizontal = 0,
+        Vertical
+    };
+
+    /** \brief direction of the transition effect that shall be used
+     */
+    // if changed remember to keep in sync with PageTransition.h enum
+    enum Direction
+    {
+        Inward = 0,
+        Outward
+    };
+
+    PageTransition(const PageTransitionParams params);
+
+    /** \brief copy constructor */
+    PageTransition(const PageTransition &pt);
+
+    /** \brief assignment operator */
+    PageTransition &operator=(const PageTransition &other);
+
+    /**
+       Destructor
+    */
+    ~PageTransition();
+
+    /**
+       \brief Get type of the transition.
+    */
+    Type type() const;
+
+    /**
+       \brief Get duration of the transition in seconds
+    */
+    double durationReal() const;
+
+    /**
+       \brief Get dimension in which the transition effect occurs.
+    */
+    Alignment alignment() const;
+
+    /**
+       \brief Get direction of motion of the transition effect.
+    */
+    Direction direction() const;
+
+    /**
+       \brief Get direction in which the transition effect moves.
+    */
+    int angle() const;
+
+    /**
+       \brief Get starting or ending scale.
+    */
+    double scale() const;
+
+    /**
+       \brief Returns true if the area to be flown is rectangular and
+       opaque.
+    */
+    bool isRectangular() const;
+
+private:
+    PageTransitionData *data;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-page.cc b/qt6/src/poppler-page.cc
new file mode 100644
index 0000000..61fd889
--- /dev/null
+++ b/qt6/src/poppler-page.cc
@@ -0,0 +1,882 @@
+/* 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-2020, 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-2020, 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 Nelson Benítez León <nbenitezl@gmail.com>
+ * Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.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 <config.h>
+#include <PDFDoc.h>
+#include <Catalog.h>
+#include <Form.h>
+#include <ErrorCodes.h>
+#include <TextOutputDev.h>
+#include <Annot.h>
+#include <Link.h>
+#include <ArthurOutputDev.h>
+#include <Rendition.h>
+#if defined(HAVE_SPLASH)
+#    include <SplashOutputDev.h>
+#    include <splash/SplashBitmap.h>
+#endif
+
+#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 reverseVideoA, bool ignorePaperColorA, SplashColorPtr paperColorA, bool bitmapTopDownA, SplashThinLineMode thinLineMode, bool overprintPreviewA)
+        : SplashOutputDev(colorModeA, bitmapRowPadA, reverseVideoA, paperColorA, bitmapTopDownA, thinLineMode, overprintPreviewA), ignorePaperColor(ignorePaperColorA)
+    {
+    }
+
+    void dump() override
+    {
+        if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) {
+            partialUpdateCallback(getXBGRImage(false /* takeImageData */), payload);
+        }
+    }
+
+    QImage getXBGRImage(bool takeImageData)
+    {
+        SplashBitmap *b = getBitmap();
+
+        const int bw = b->getWidth();
+        const int bh = b->getHeight();
+        const int brs = b->getRowSize();
+
+        // 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)) {
+            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.
+                return QImage(data, bw, bh, brs, format, gfree, data);
+            } else {
+                return QImage(data, bw, bh, brs, format).copy();
+            }
+        }
+
+        return QImage();
+    }
+
+private:
+    bool ignorePaperColor;
+};
+
+class QImageDumpingArthurOutputDev : public ArthurOutputDev, public OutputDevCallbackHelper
+{
+public:
+    QImageDumpingArthurOutputDev(QPainter *painter, QImage *i) : ArthurOutputDev(painter), image(i) { }
+
+    void dump() override
+    {
+        if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) {
+            partialUpdateCallback(*image, payload);
+        }
+    }
+
+private:
+    QImage *image;
+};
+
+Link *PageData::convertLinkActionToLink(::LinkAction *a, const QRectF &linkArea)
+{
+    return convertLinkActionToLink(a, parentDoc, linkArea);
+}
+
+Link *PageData::convertLinkActionToLink(::LinkAction *a, DocumentData *parentDoc, const QRectF &linkArea)
+{
+    if (!a)
+        return nullptr;
+
+    Link *popplerLink = nullptr;
+    switch (a->getKind()) {
+    case actionGoTo: {
+        LinkGoTo *g = (LinkGoTo *)a;
+        const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, false);
+        // create link: no ext file, namedDest, object pointer
+        popplerLink = new LinkGoto(linkArea, QString(), LinkDestination(ldd));
+    } break;
+
+    case actionGoToR: {
+        LinkGoToR *g = (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 = new LinkGoto(linkArea, fileName, LinkDestination(ldd));
+    } break;
+
+    case actionLaunch: {
+        LinkLaunch *e = (LinkLaunch *)a;
+        const GooString *p = e->getParams();
+        popplerLink = new LinkExecute(linkArea, e->getFileName()->c_str(), p ? p->c_str() : nullptr);
+    } break;
+
+    case actionNamed: {
+        const std::string &name = ((LinkNamed *)a)->getName();
+        if (name == "NextPage")
+            popplerLink = new LinkAction(linkArea, LinkAction::PageNext);
+        else if (name == "PrevPage")
+            popplerLink = new LinkAction(linkArea, LinkAction::PagePrev);
+        else if (name == "FirstPage")
+            popplerLink = new LinkAction(linkArea, LinkAction::PageFirst);
+        else if (name == "LastPage")
+            popplerLink = new LinkAction(linkArea, LinkAction::PageLast);
+        else if (name == "GoBack")
+            popplerLink = new LinkAction(linkArea, LinkAction::HistoryBack);
+        else if (name == "GoForward")
+            popplerLink = new LinkAction(linkArea, LinkAction::HistoryForward);
+        else if (name == "Quit")
+            popplerLink = new LinkAction(linkArea, LinkAction::Quit);
+        else if (name == "GoToPage")
+            popplerLink = new LinkAction(linkArea, LinkAction::GoToPage);
+        else if (name == "Find")
+            popplerLink = new LinkAction(linkArea, LinkAction::Find);
+        else if (name == "FullScreen")
+            popplerLink = new LinkAction(linkArea, LinkAction::Presentation);
+        else if (name == "Print")
+            popplerLink = new LinkAction(linkArea, LinkAction::Print);
+        else if (name == "Close") {
+            // acroread closes the document always, doesnt care whether
+            // its presentation mode or not
+            // popplerLink = new LinkAction( linkArea, LinkAction::EndPresentation );
+            popplerLink = new LinkAction(linkArea, LinkAction::Close);
+        } else {
+            // TODO
+        }
+    } break;
+
+    case actionURI: {
+        popplerLink = new LinkBrowse(linkArea, ((LinkURI *)a)->getURI().c_str());
+    } break;
+
+    case actionSound: {
+        ::LinkSound *ls = (::LinkSound *)a;
+        popplerLink = new LinkSound(linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject(ls->getSound()));
+    } break;
+
+    case actionJavaScript: {
+        ::LinkJavaScript *ljs = (::LinkJavaScript *)a;
+        popplerLink = new LinkJavaScript(linkArea, UnicodeParsedString(ljs->getScript()));
+    } break;
+
+    case actionMovie: {
+        ::LinkMovie *lm = (::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 = new LinkMovie(linkArea, operation, title, reference);
+    } break;
+
+    case actionRendition: {
+        ::LinkRendition *lrn = (::LinkRendition *)a;
+
+        Ref reference = Ref::INVALID();
+        if (lrn->hasScreenAnnot())
+            reference = lrn->getScreenAnnot();
+
+        popplerLink = new LinkRendition(linkArea, lrn->getMedia() ? lrn->getMedia()->copy() : nullptr, lrn->getOperation(), UnicodeParsedString(lrn->getScript()), reference);
+    } break;
+
+    case actionOCGState: {
+        ::LinkOCGState *plocg = (::LinkOCGState *)a;
+
+        LinkOCGStatePrivate *locgp = new LinkOCGStatePrivate(linkArea, plocg->getStateList(), plocg->getPreserveRB());
+        popplerLink = new LinkOCGState(locgp);
+    } break;
+
+    case actionHide: {
+        ::LinkHide *lh = (::LinkHide *)a;
+
+        LinkHidePrivate *lhp = new LinkHidePrivate(linkArea, lh->hasTargetName() ? UnicodeParsedString(lh->getTargetName()) : QString(), lh->isShowAction());
+        popplerLink = new LinkHide(lhp);
+    } break;
+
+    case actionResetForm:
+        // Not handled in Qt6 front-end yet
+        break;
+
+    case actionUnknown:
+        break;
+    }
+
+    if (popplerLink) {
+        QVector<Link *> links;
+        for (const std::unique_ptr<::LinkAction> &nextAction : a->nextActions()) {
+            links << convertLinkActionToLink(nextAction.get(), parentDoc, linkArea);
+        }
+        LinkPrivate::get(popplerLink)->nextLinks = links;
+    }
+
+    return popplerLink;
+}
+
+inline TextPage *PageData::prepareTextSearch(const QString &text, Page::Rotation rotate, QVector<Unicode> *u)
+{
+    *u = text.toUcs4();
+
+    const int rotation = (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);
+    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 = false)
+{
+    if (direction == Page::FromTop)
+        return textPage->findText(u.data(), u.size(), true, true, false, false, sCase, sDiacritics, false, sWords, &sLeft, &sTop, &sRight, &sBottom);
+    else if (direction == Page::NextResult)
+        return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, false, sWords, &sLeft, &sTop, &sRight, &sBottom);
+    else if (direction == Page::PreviousResult)
+        return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, true, sWords, &sLeft, &sTop, &sRight, &sBottom);
+
+    return false;
+}
+
+inline QList<QRectF> PageData::performMultipleTextSearch(TextPage *textPage, QVector<Unicode> &u, bool sCase, bool sWords, bool sDiacritics = false)
+{
+    QList<QRectF> results;
+    double sLeft = 0.0, sTop = 0.0, sRight = 0.0, sBottom = 0.0;
+
+    while (textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, false, sWords, &sLeft, &sTop, &sRight, &sBottom)) {
+        QRectF result;
+
+        result.setLeft(sLeft);
+        result.setTop(sTop);
+        result.setRight(sRight);
+        result.setBottom(sBottom);
+
+        results.append(result);
+    }
+
+    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) {
+    OutputDevCallbackHelper *helper = reinterpret_cast<OutputDevCallbackHelper *>(user_data);
+    return helper->shouldAbortRenderCallback(helper->payload);
+};
+
+static auto shouldAbortExtractionInternalCallback = [](void *user_data) {
+    TextExtractionAbortHelper *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 renderToArthur(QImageDumpingArthurOutputDev *arthur_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);
+
+    arthur_output->startDoc(page->parentDoc->doc);
+
+    const bool hideAnnotations = page->parentDoc->m_hints & Document::HideAnnotations;
+
+    OutputDevCallbackHelper *abortHelper = arthur_output;
+    page->parentDoc->doc->displayPageSlice(arthur_output, page->index + 1, xres, yres, (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 = (int)rotate * 90;
+    QImage img;
+    switch (m_page->parentDoc->m_backend) {
+    case Poppler::Document::SplashBackend: {
+#if defined(HAVE_SPLASH)
+        SplashColor bgColor;
+        const bool overprintPreview = m_page->parentDoc->m_hints & Document::OverprintPreview ? true : false;
+        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, false, ignorePaperColor, ignorePaperColor ? nullptr : bgColor, true, thinLineMode, overprintPreview);
+
+        splash_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload);
+
+        splash_output.setFontAntialias(m_page->parentDoc->m_hints & Document::TextAntialiasing ? true : false);
+        splash_output.setVectorAntialias(m_page->parentDoc->m_hints & Document::Antialiasing ? true : false);
+        splash_output.setFreeTypeHinting(m_page->parentDoc->m_hints & Document::TextHinting ? true : false, m_page->parentDoc->m_hints & Document::TextSlightHinting ? true : false);
+
+#    ifdef USE_CMS
+        splash_output.setDisplayProfile(m_page->parentDoc->m_displayProfile);
+#    endif
+
+        splash_output.startDoc(m_page->parentDoc->doc);
+
+        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 */);
+#endif
+        break;
+    }
+    case Poppler::Document::ArthurBackend: {
+        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);
+        QImageDumpingArthurOutputDev arthur_output(&painter, &tmpimg);
+
+        arthur_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints));
+
+#ifdef USE_CMS
+        arthur_output.setDisplayProfile(m_page->parentDoc->m_displayProfile);
+#endif
+
+        arthur_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload);
+        renderToArthur(&arthur_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::ArthurBackend: {
+        QImageDumpingArthurOutputDev arthur_output(painter, nullptr);
+
+        arthur_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints));
+
+        return renderToArthur(&arthur_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;
+    output_dev = new TextOutputDev(nullptr, false, 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()) {
+        const PDFRectangle *rect = m_page->page->getCropBox();
+        s = output_dev->getText(rect->x1, rect->y1, rect->x2, rect->y2);
+    } else {
+        s = output_dev->getText(r.left(), r.top(), r.right(), r.bottom());
+    }
+
+    result = QString::fromUtf8(s->c_str());
+
+    delete output_dev;
+    delete s;
+    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) ? false : true;
+    const bool sWords = flags.testFlag(WholeWords) ? true : false;
+    const bool sDiacritics = flags.testFlag(IgnoreDiacritics) ? true : false;
+
+    QVector<Unicode> u;
+    TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u);
+
+    const bool found = m_page->performSingleTextSearch(textPage, u, sLeft, sTop, sRight, sBottom, direction, sCase, sWords, sDiacritics);
+
+    textPage->decRefCnt();
+
+    return found;
+}
+
+QList<QRectF> Page::search(const QString &text, SearchFlags flags, Rotation rotate) const
+{
+    const bool sCase = flags.testFlag(IgnoreCase) ? false : true;
+    const bool sWords = flags.testFlag(WholeWords) ? true : false;
+    const bool sDiacritics = flags.testFlag(IgnoreDiacritics) ? true : false;
+
+    QVector<Unicode> u;
+    TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u);
+
+    const QList<QRectF> results = m_page->performMultipleTextSearch(textPage, u, sCase, sWords, sDiacritics);
+
+    textPage->decRefCnt();
+
+    return results;
+}
+
+QList<TextBox *> Page::textList(Rotation rotate) const
+{
+    return textList(rotate, nullptr, QVariant());
+}
+
+QList<TextBox *> Page::textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const
+{
+    TextOutputDev *output_dev;
+
+    QList<TextBox *> output_list;
+
+    output_dev = new TextOutputDev(nullptr, false, 0, false, false);
+
+    int rotation = (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);
+
+    TextWordList *word_list = output_dev->makeWordList();
+
+    if (!word_list || (shouldAbortExtractionCallback && shouldAbortExtractionCallback(closure))) {
+        delete word_list;
+        delete output_dev;
+        return output_list;
+    }
+
+    QHash<const TextWord *, TextBox *> wordBoxMap;
+
+    output_list.reserve(word_list->getLength());
+    for (int i = 0; i < word_list->getLength(); i++) {
+        TextWord *word = word_list->get(i);
+        GooString *gooWord = word->getText();
+        QString string = QString::fromUtf8(gooWord->c_str());
+        delete gooWord;
+        double xMin, yMin, xMax, yMax;
+        word->getBBox(&xMin, &yMin, &xMax, &yMax);
+
+        TextBox *text_box = new TextBox(string, QRectF(xMin, yMin, xMax - xMin, yMax - yMin));
+        text_box->m_data->hasSpaceAfter = word->hasSpaceAfter() == true;
+        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);
+
+        output_list.append(text_box);
+    }
+
+    for (int i = 0; i < word_list->getLength(); i++) {
+        TextWord *word = word_list->get(i);
+        TextBox *text_box = wordBoxMap.value(word);
+        text_box->m_data->nextWord = wordBoxMap.value(word->nextWord());
+    }
+
+    delete word_list;
+    delete output_dev;
+
+    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;
+}
+
+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((char *)key);
+        std::unique_ptr<::LinkAction> lact = ::LinkAction::parseAction(&o2, m_page->parentDoc->doc->getCatalog()->getBaseURI());
+        Link *popplerLink = nullptr;
+        if (lact != nullptr) {
+            popplerLink = m_page->convertLinkActionToLink(lact.get(), QRectF());
+        }
+        return popplerLink;
+    }
+    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());
+    } else {
+        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);
+}
+
+QList<Link *> Page::links() const
+{
+    LinkExtractorOutputDev link_dev(m_page);
+    m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1);
+    QList<Link *> popplerLinks = link_dev.links();
+
+    return popplerLinks;
+}
+
+QList<Annotation *> Page::annotations() const
+{
+    return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, QSet<Annotation::SubType>());
+}
+
+QList<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);
+}
+
+QList<FormField *> Page::formFields() const
+{
+    QList<FormField *> fields;
+    ::Page *p = m_page->page;
+    ::FormPageWidgets *form = p->getFormWidgets();
+    int formcount = form->getNumWidgets();
+    for (int i = 0; i < formcount; ++i) {
+        ::FormWidget *fm = form->getWidget(i);
+        FormField *ff = nullptr;
+        switch (fm->getType()) {
+        case formButton: {
+            ff = new FormFieldButton(m_page->parentDoc, p, static_cast<FormWidgetButton *>(fm));
+        } break;
+
+        case formText: {
+            ff = new FormFieldText(m_page->parentDoc, p, static_cast<FormWidgetText *>(fm));
+        } break;
+
+        case formChoice: {
+            ff = new FormFieldChoice(m_page->parentDoc, p, static_cast<FormWidgetChoice *>(fm));
+        } break;
+
+        case formSignature: {
+            ff = new FormFieldSignature(m_page->parentDoc, p, static_cast<FormWidgetSignature *>(fm));
+        } break;
+
+        default:;
+        }
+
+        if (ff)
+            fields.append(ff);
+    }
+
+    delete form;
+
+    return fields;
+}
+
+double Page::duration() const
+{
+    return m_page->page->getDuration();
+}
+
+QString Page::label() const
+{
+    GooString goo;
+    if (!m_page->parentDoc->doc->getCatalog()->indexToLabel(m_page->index, &goo))
+        return QString();
+
+    return UnicodeParsedString(&goo);
+}
+
+int Page::index() const
+{
+    return m_page->index;
+}
+
+}
diff --git a/qt6/src/poppler-pdf-converter.cc b/qt6/src/poppler-pdf-converter.cc
new file mode 100644
index 0000000..eac6c4d
--- /dev/null
+++ b/qt6/src/poppler-pdf-converter.cc
@@ -0,0 +1,103 @@
+/* poppler-pdf-converter.cc: qt interface to poppler
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, 2009, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "poppler-private.h"
+#include "poppler-converter-private.h"
+#include "poppler-qiodeviceoutstream-private.h"
+
+#include <QtCore/QFile>
+
+#include <ErrorCodes.h>
+
+namespace Poppler {
+
+class PDFConverterPrivate : public BaseConverterPrivate
+{
+public:
+    PDFConverterPrivate();
+
+    PDFConverter::PDFOptions opts;
+};
+
+PDFConverterPrivate::PDFConverterPrivate() : BaseConverterPrivate() { }
+
+PDFConverter::PDFConverter(DocumentData *document) : BaseConverter(*new PDFConverterPrivate())
+{
+    Q_D(PDFConverter);
+    d->document = document;
+}
+
+PDFConverter::~PDFConverter() { }
+
+void PDFConverter::setPDFOptions(PDFConverter::PDFOptions options)
+{
+    Q_D(PDFConverter);
+    d->opts = options;
+}
+
+PDFConverter::PDFOptions PDFConverter::pdfOptions() const
+{
+    Q_D(const PDFConverter);
+    return d->opts;
+}
+
+bool PDFConverter::convert()
+{
+    Q_D(PDFConverter);
+    d->lastError = NoError;
+
+    if (d->document->locked) {
+        d->lastError = FileLockedError;
+        return false;
+    }
+
+    QIODevice *dev = d->openDevice();
+    if (!dev) {
+        d->lastError = OpenOutputError;
+        return false;
+    }
+
+    bool deleteFile = false;
+    if (QFile *file = qobject_cast<QFile *>(dev))
+        deleteFile = !file->exists();
+
+    int errorCode = errNone;
+    QIODeviceOutStream stream(dev);
+    if (d->opts & WithChanges) {
+        errorCode = d->document->doc->saveAs(&stream);
+    } else {
+        errorCode = d->document->doc->saveWithoutChangesAs(&stream);
+    }
+    d->closeDevice();
+    if (errorCode != errNone) {
+        if (deleteFile) {
+            qobject_cast<QFile *>(dev)->remove();
+        }
+        if (errorCode == errOpenFile)
+            d->lastError = OpenOutputError;
+        else
+            d->lastError = NotSupportedInputFileError;
+    }
+
+    return (errorCode == errNone);
+}
+
+}
diff --git a/qt6/src/poppler-private.cc b/qt6/src/poppler-private.cc
new file mode 100644
index 0000000..4d4e1c5
--- /dev/null
+++ b/qt6/src/poppler-private.cc
@@ -0,0 +1,291 @@
+/* poppler-private.cc: qt interface to poppler
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2006, 2011, 2015, 2017-2019 by Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, 2010, 2011, 2014 by Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013 by Thomas Freitag <Thomas.Freitag@alfa.de>
+ * Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
+ * Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
+ * 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) 2018-2020 Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2019 João Netto <joaonetto901@gmail.com>
+ * Inspired on code by
+ * Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es>
+ * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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-private.h"
+#include "poppler-form.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QDebug>
+#include <QtCore/QVariant>
+
+#include <Link.h>
+#include <Outline.h>
+#include <PDFDocEncoding.h>
+#include <UnicodeMap.h>
+
+namespace Poppler {
+
+namespace Debug {
+
+static void qDebugDebugFunction(const QString &message, const QVariant & /*closure*/)
+{
+    qDebug() << message;
+}
+
+PopplerDebugFunc debugFunction = qDebugDebugFunction;
+QVariant debugClosure;
+
+}
+
+void setDebugErrorFunction(PopplerDebugFunc function, const QVariant &closure)
+{
+    Debug::debugFunction = function ? function : Debug::qDebugDebugFunction;
+    Debug::debugClosure = closure;
+}
+
+void qt6ErrorFunction(ErrorCategory /*category*/, Goffset pos, const char *msg)
+{
+    QString emsg;
+
+    if (pos >= 0) {
+        emsg = QStringLiteral("Error (%1): ").arg(pos);
+    } else {
+        emsg = QStringLiteral("Error: ");
+    }
+    emsg += QString::fromLatin1(msg);
+    (*Debug::debugFunction)(emsg, Debug::debugClosure);
+}
+
+QString unicodeToQString(const Unicode *u, int len)
+{
+    const UnicodeMap *utf8Map = globalParams->getUtf8Map();
+
+    // ignore the last character if it is 0x0
+    if ((len > 0) && (u[len - 1] == 0)) {
+        --len;
+    }
+
+    GooString convertedStr;
+    for (int i = 0; i < len; ++i) {
+        char buf[8];
+        const int n = utf8Map->mapUnicode(u[i], buf, sizeof(buf));
+        convertedStr.append(buf, n);
+    }
+
+    return QString::fromUtf8(convertedStr.c_str(), convertedStr.getLength());
+}
+
+QString UnicodeParsedString(const GooString *s1)
+{
+    return (s1) ? UnicodeParsedString(s1->toStr()) : QString();
+}
+
+QString UnicodeParsedString(const std::string &s1)
+{
+    if (s1.empty())
+        return QString();
+
+    if (GooString::hasUnicodeMarker(s1) || GooString::hasUnicodeMarkerLE(s1)) {
+        return QString::fromUtf16(reinterpret_cast<const char16_t *>(s1.c_str()), s1.size() / 2);
+    } else {
+        int stringLength;
+        const char *cString = pdfDocEncodingToUTF16(s1, &stringLength);
+        auto result = QString::fromUtf16(reinterpret_cast<const char16_t *>(cString), stringLength / 2);
+        delete[] cString;
+        return result;
+    }
+}
+
+GooString *QStringToUnicodeGooString(const QString &s)
+{
+    int len = s.length() * 2 + 2;
+    char *cstring = (char *)gmallocn(len, sizeof(char));
+    cstring[0] = (char)0xfe;
+    cstring[1] = (char)0xff;
+    for (int i = 0; i < s.length(); ++i) {
+        cstring[2 + i * 2] = s.at(i).row();
+        cstring[3 + i * 2] = s.at(i).cell();
+    }
+    GooString *ret = new GooString(cstring, len);
+    gfree(cstring);
+    return ret;
+}
+
+GooString *QStringToGooString(const QString &s)
+{
+    int len = s.length();
+    char *cstring = (char *)gmallocn(s.length(), sizeof(char));
+    for (int i = 0; i < len; ++i)
+        cstring[i] = s.at(i).unicode();
+    GooString *ret = new GooString(cstring, len);
+    gfree(cstring);
+    return ret;
+}
+
+GooString *QDateTimeToUnicodeGooString(const QDateTime &dt)
+{
+    if (!dt.isValid()) {
+        return nullptr;
+    }
+
+    return QStringToUnicodeGooString(dt.toUTC().toString(QStringLiteral("yyyyMMddhhmmss+00'00'")));
+}
+
+Annot::AdditionalActionsType toPopplerAdditionalActionType(Annotation::AdditionalActionType type)
+{
+    switch (type) {
+    case Annotation::CursorEnteringAction:
+        return Annot::actionCursorEntering;
+    case Annotation::CursorLeavingAction:
+        return Annot::actionCursorLeaving;
+    case Annotation::MousePressedAction:
+        return Annot::actionMousePressed;
+    case Annotation::MouseReleasedAction:
+        return Annot::actionMouseReleased;
+    case Annotation::FocusInAction:
+        return Annot::actionFocusIn;
+    case Annotation::FocusOutAction:
+        return Annot::actionFocusOut;
+    case Annotation::PageOpeningAction:
+        return Annot::actionPageOpening;
+    case Annotation::PageClosingAction:
+        return Annot::actionPageClosing;
+    case Annotation::PageVisibleAction:
+        return Annot::actionPageVisible;
+    case Annotation::PageInvisibleAction:
+        return Annot::actionPageInvisible;
+    }
+
+    return Annot::actionCursorEntering;
+}
+
+static void linkActionToTocItem(const ::LinkAction *a, DocumentData *doc, QDomElement *e)
+{
+    if (!a || !e)
+        return;
+
+    switch (a->getKind()) {
+    case actionGoTo: {
+        // page number is contained/referenced in a LinkGoTo
+        const LinkGoTo *g = static_cast<const LinkGoTo *>(a);
+        const LinkDest *destination = g->getDest();
+        if (!destination && g->getNamedDest()) {
+            // no 'destination' but an internal 'named reference'. we could
+            // get the destination for the page now, but it's VERY time consuming,
+            // so better storing the reference and provide the viewport on demand
+            const GooString *s = g->getNamedDest();
+            QChar *charArray = new QChar[s->getLength()];
+            for (int i = 0; i < s->getLength(); ++i)
+                charArray[i] = QChar(s->c_str()[i]);
+            QString aux(charArray, s->getLength());
+            e->setAttribute(QStringLiteral("DestinationName"), aux);
+            delete[] charArray;
+        } else if (destination && destination->isOk()) {
+            LinkDestinationData ldd(destination, nullptr, doc, false);
+            e->setAttribute(QStringLiteral("Destination"), LinkDestination(ldd).toString());
+        }
+        break;
+    }
+    case actionGoToR: {
+        // page number is contained/referenced in a LinkGoToR
+        const LinkGoToR *g = static_cast<const LinkGoToR *>(a);
+        const LinkDest *destination = g->getDest();
+        if (!destination && g->getNamedDest()) {
+            // no 'destination' but an internal 'named reference'. we could
+            // get the destination for the page now, but it's VERY time consuming,
+            // so better storing the reference and provide the viewport on demand
+            const GooString *s = g->getNamedDest();
+            QChar *charArray = new QChar[s->getLength()];
+            for (int i = 0; i < s->getLength(); ++i)
+                charArray[i] = QChar(s->c_str()[i]);
+            QString aux(charArray, s->getLength());
+            e->setAttribute(QStringLiteral("DestinationName"), aux);
+            delete[] charArray;
+        } else if (destination && destination->isOk()) {
+            LinkDestinationData ldd(destination, nullptr, doc, g->getFileName() != nullptr);
+            e->setAttribute(QStringLiteral("Destination"), LinkDestination(ldd).toString());
+        }
+        e->setAttribute(QStringLiteral("ExternalFileName"), g->getFileName()->c_str());
+        break;
+    }
+    case actionURI: {
+        const LinkURI *u = static_cast<const LinkURI *>(a);
+        e->setAttribute(QStringLiteral("DestinationURI"), u->getURI().c_str());
+    }
+    default:;
+    }
+}
+
+DocumentData::~DocumentData()
+{
+    qDeleteAll(m_embeddedFiles);
+    delete (OptContentModel *)m_optContentModel;
+    delete doc;
+}
+
+void DocumentData::init()
+{
+    m_backend = Document::SplashBackend;
+    paperColor = Qt::white;
+    m_hints = 0;
+    m_optContentModel = nullptr;
+}
+
+void DocumentData::addTocChildren(QDomDocument *docSyn, QDomNode *parent, const std::vector<::OutlineItem *> *items)
+{
+    for (::OutlineItem *outlineItem : *items) {
+        // iterate over every object in 'items'
+
+        // 1. create element using outlineItem's title as tagName
+        QString name;
+        const Unicode *uniChar = outlineItem->getTitle();
+        int titleLength = outlineItem->getTitleLength();
+        name = unicodeToQString(uniChar, titleLength);
+        if (name.isEmpty())
+            continue;
+
+        QDomElement item = docSyn->createElement(name);
+        parent->appendChild(item);
+
+        // 2. find the page the link refers to
+        const ::LinkAction *a = outlineItem->getAction();
+        linkActionToTocItem(a, this, &item);
+
+        item.setAttribute(QStringLiteral("Open"), QVariant((bool)outlineItem->isOpen()).toString());
+
+        // 3. recursively descend over children
+        outlineItem->open();
+        const std::vector<::OutlineItem *> *children = outlineItem->getKids();
+        if (children)
+            addTocChildren(docSyn, &item, children);
+    }
+}
+
+FormWidget *FormFieldData::getFormWidget(const FormField *f)
+{
+    return f->m_formData->fm;
+}
+
+FormFieldIconData *FormFieldIconData::getData(const FormFieldIcon &f)
+{
+    return f.d_ptr;
+}
+
+}
diff --git a/qt6/src/poppler-private.h b/qt6/src/poppler-private.h
new file mode 100644
index 0000000..3caaf23
--- /dev/null
+++ b/qt6/src/poppler-private.h
@@ -0,0 +1,262 @@
+/* poppler-private.h: qt interface to poppler
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2005, 2008, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2006-2009, 2011, 2012, 2017-2020 by Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2007-2009, 2011, 2014 by Pino Toscano <pino@kde.org>
+ * Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+ * Copyright (C) 2011 Hib Eris <hib@hiberis.nl>
+ * Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+ * Copyright (C) 2013 Anthony Granger <grangeranthony@gmail.com>
+ * Copyright (C) 2014 Bogdan Cristea <cristeab@gmail.com>
+ * Copyright (C) 2014 Aki Koskinen <freedesktop@akikoskinen.info>
+ * Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
+ * Copyright (C) 2017 Christoph Cullmann <cullmann@kde.org>
+ * 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) 2018, 2020 Adam Reichold <adam.reichold@t-online.de>
+ * Copyright (C) 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
+ * Copyright (C) 2019 João Netto <joaonetto901@gmail.com>
+ * Copyright (C) 2019 Jan Grulich <jgrulich@redhat.com>
+ * Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
+ * Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
+ * Inspired on code by
+ * Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es>
+ * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
+ *
+ * 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.
+ */
+
+#ifndef _POPPLER_PRIVATE_H_
+#define _POPPLER_PRIVATE_H_
+
+#include <QtCore/QFile>
+#include <QtCore/QMutex>
+#include <QtCore/QPointer>
+#include <QtCore/QVector>
+
+#include <config.h>
+#include <GfxState.h>
+#include <GlobalParams.h>
+#include <Form.h>
+#include <PDFDoc.h>
+#include <FontInfo.h>
+#include <OutputDev.h>
+#include <Error.h>
+#if defined(HAVE_SPLASH)
+#    include <SplashOutputDev.h>
+#endif
+
+#include "poppler-qt6.h"
+#include "poppler-embeddedfile-private.h"
+#include "poppler-qiodeviceinstream-private.h"
+
+class LinkDest;
+class FormWidget;
+class OutlineItem;
+
+namespace Poppler {
+
+/* borrowed from kpdf */
+POPPLER_QT6_EXPORT QString unicodeToQString(const Unicode *u, int len);
+
+POPPLER_QT6_EXPORT QString UnicodeParsedString(const GooString *s1);
+
+POPPLER_QT6_EXPORT QString UnicodeParsedString(const std::string &s1);
+
+POPPLER_QT6_EXPORT GooString *QStringToUnicodeGooString(const QString &s);
+
+POPPLER_QT6_EXPORT GooString *QStringToGooString(const QString &s);
+
+GooString *QDateTimeToUnicodeGooString(const QDateTime &dt);
+
+void qt6ErrorFunction(ErrorCategory /*category*/, Goffset pos, const char *msg);
+
+Annot::AdditionalActionsType toPopplerAdditionalActionType(Annotation::AdditionalActionType type);
+
+class LinkDestinationData
+{
+public:
+    LinkDestinationData(const LinkDest *l, const GooString *nd, Poppler::DocumentData *pdfdoc, bool external) : ld(l), namedDest(nd), doc(pdfdoc), externalDest(external) { }
+
+    const LinkDest *ld;
+    const GooString *namedDest;
+    Poppler::DocumentData *doc;
+    bool externalDest;
+};
+
+class DocumentData : private GlobalParamsIniter
+{
+public:
+    DocumentData(const QString &filePath, GooString *ownerPassword, GooString *userPassword) : GlobalParamsIniter(qt6ErrorFunction)
+    {
+        init();
+        m_device = nullptr;
+        m_filePath = filePath;
+
+#ifdef _WIN32
+        doc = new PDFDoc((wchar_t *)filePath.utf16(), filePath.length(), ownerPassword, userPassword);
+#else
+        GooString *fileName = new GooString(QFile::encodeName(filePath).constData());
+        doc = new PDFDoc(fileName, ownerPassword, userPassword);
+#endif
+
+        delete ownerPassword;
+        delete userPassword;
+    }
+
+    DocumentData(QIODevice *device, GooString *ownerPassword, GooString *userPassword) : GlobalParamsIniter(qt6ErrorFunction)
+    {
+        m_device = device;
+        QIODeviceInStream *str = new QIODeviceInStream(device, 0, false, device->size(), Object(objNull));
+        init();
+        doc = new PDFDoc(str, ownerPassword, userPassword);
+        delete ownerPassword;
+        delete userPassword;
+    }
+
+    DocumentData(const QByteArray &data, GooString *ownerPassword, GooString *userPassword) : GlobalParamsIniter(qt6ErrorFunction)
+    {
+        m_device = nullptr;
+        fileContents = data;
+        MemStream *str = new MemStream((char *)fileContents.data(), 0, fileContents.length(), Object(objNull));
+        init();
+        doc = new PDFDoc(str, ownerPassword, userPassword);
+        delete ownerPassword;
+        delete userPassword;
+    }
+
+    void init();
+
+    ~DocumentData();
+
+    DocumentData(const DocumentData &) = delete;
+    DocumentData &operator=(const DocumentData &) = delete;
+
+    void addTocChildren(QDomDocument *docSyn, QDomNode *parent, const std::vector<::OutlineItem *> *items);
+
+    void setPaperColor(const QColor &color) { paperColor = color; }
+
+    void fillMembers()
+    {
+        int numEmb = doc->getCatalog()->numEmbeddedFiles();
+        if (!(0 == numEmb)) {
+            // we have some embedded documents, build the list
+            for (int yalv = 0; yalv < numEmb; ++yalv) {
+                FileSpec *fs = doc->getCatalog()->embeddedFile(yalv);
+                m_embeddedFiles.append(new EmbeddedFile(*new EmbeddedFileData(fs)));
+            }
+        }
+    }
+
+    static Document *checkDocument(DocumentData *doc);
+
+    PDFDoc *doc;
+    QString m_filePath;
+    QIODevice *m_device;
+    QByteArray fileContents;
+    bool locked;
+    Document::RenderBackend m_backend;
+    QList<EmbeddedFile *> m_embeddedFiles;
+    QPointer<OptContentModel> m_optContentModel;
+    QColor paperColor;
+    int m_hints;
+#ifdef USE_CMS
+    GfxLCMSProfilePtr m_sRGBProfile;
+    GfxLCMSProfilePtr m_displayProfile;
+#endif
+};
+
+class FontInfoData
+{
+public:
+    FontInfoData()
+    {
+        isEmbedded = false;
+        isSubset = false;
+        type = FontInfo::unknown;
+    }
+
+    FontInfoData(::FontInfo *fi)
+    {
+        if (fi->getName())
+            fontName = fi->getName()->c_str();
+        if (fi->getFile())
+            fontFile = fi->getFile()->c_str();
+        if (fi->getSubstituteName())
+            fontSubstituteName = fi->getSubstituteName()->c_str();
+        isEmbedded = fi->getEmbedded();
+        isSubset = fi->getSubset();
+        type = (Poppler::FontInfo::Type)fi->getType();
+        embRef = fi->getEmbRef();
+    }
+
+    FontInfoData(const FontInfoData &fid) = default;
+    FontInfoData &operator=(const FontInfoData &) = default;
+
+    QString fontName;
+    QString fontSubstituteName;
+    QString fontFile;
+    bool isEmbedded : 1;
+    bool isSubset : 1;
+    FontInfo::Type type;
+    Ref embRef;
+};
+
+class FontIteratorData
+{
+public:
+    FontIteratorData(int startPage, DocumentData *dd) : fontInfoScanner(dd->doc, startPage), totalPages(dd->doc->getNumPages()), currentPage(qMax(startPage, 0) - 1) { }
+
+    ~FontIteratorData() { }
+
+    FontInfoScanner fontInfoScanner;
+    int totalPages;
+    int currentPage;
+};
+
+class TextBoxData
+{
+public:
+    TextBoxData() : nextWord(nullptr), hasSpaceAfter(false) { }
+
+    QString text;
+    QRectF bBox;
+    TextBox *nextWord;
+    QVector<QRectF> charBBoxes; // the boundingRect of each character
+    bool hasSpaceAfter;
+};
+
+class FormFieldData
+{
+public:
+    FormFieldData(DocumentData *_doc, ::Page *p, ::FormWidget *w) : doc(_doc), page(p), fm(w) { }
+
+    DocumentData *doc;
+    ::Page *page; // Note for some signatures it can be null since there's signatures that don't belong to a given page
+    ::FormWidget *fm;
+    QRectF box;
+    static POPPLER_QT6_EXPORT ::FormWidget *getFormWidget(const FormField *f);
+};
+
+class FormFieldIcon;
+class FormFieldIconData
+{
+public:
+    static POPPLER_QT6_EXPORT FormFieldIconData *getData(const FormFieldIcon &f);
+    Dict *icon;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-ps-converter.cc b/qt6/src/poppler-ps-converter.cc
new file mode 100644
index 0000000..5519b0c
--- /dev/null
+++ b/qt6/src/poppler-ps-converter.cc
@@ -0,0 +1,256 @@
+/* poppler-ps-converter.cc: qt interface to poppler
+ * Copyright (C) 2007, 2009, 2010, 2015, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
+ * Copyright (C) 2011 Glad Deschrijver <glad.deschrijver@gmail.com>
+ * Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+ * Copyright (C) 2014 Adrian Johnson <ajohnson@redneon.com>
+ * Copyright (C) 2020 William Bader <williambader@hotmail.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 "poppler-private.h"
+#include "poppler-converter-private.h"
+
+#include "PSOutputDev.h"
+
+static void outputToQIODevice(void *stream, const char *data, int len)
+{
+    static_cast<QIODevice *>(stream)->write(data, len);
+}
+
+namespace Poppler {
+
+class PSConverterPrivate : public BaseConverterPrivate
+{
+public:
+    PSConverterPrivate();
+
+    QList<int> pageList;
+    QString title;
+    double hDPI;
+    double vDPI;
+    int rotate;
+    int paperWidth;
+    int paperHeight;
+    int marginRight;
+    int marginBottom;
+    int marginLeft;
+    int marginTop;
+    PSConverter::PSOptions opts;
+    void (*pageConvertedCallback)(int page, void *payload);
+    void *pageConvertedPayload;
+};
+
+PSConverterPrivate::PSConverterPrivate()
+    : BaseConverterPrivate(),
+      hDPI(72),
+      vDPI(72),
+      rotate(0),
+      paperWidth(-1),
+      paperHeight(-1),
+      marginRight(0),
+      marginBottom(0),
+      marginLeft(0),
+      marginTop(0),
+      opts(PSConverter::Printing),
+      pageConvertedCallback(nullptr),
+      pageConvertedPayload(nullptr)
+{
+}
+
+PSConverter::PSConverter(DocumentData *document) : BaseConverter(*new PSConverterPrivate())
+{
+    Q_D(PSConverter);
+    d->document = document;
+}
+
+PSConverter::~PSConverter() { }
+
+void PSConverter::setPageList(const QList<int> &pageList)
+{
+    Q_D(PSConverter);
+    d->pageList = pageList;
+}
+
+void PSConverter::setTitle(const QString &title)
+{
+    Q_D(PSConverter);
+    d->title = title;
+}
+
+void PSConverter::setHDPI(double hDPI)
+{
+    Q_D(PSConverter);
+    d->hDPI = hDPI;
+}
+
+void PSConverter::setVDPI(double vDPI)
+{
+    Q_D(PSConverter);
+    d->vDPI = vDPI;
+}
+
+void PSConverter::setRotate(int rotate)
+{
+    Q_D(PSConverter);
+    d->rotate = rotate;
+}
+
+void PSConverter::setPaperWidth(int paperWidth)
+{
+    Q_D(PSConverter);
+    d->paperWidth = paperWidth;
+}
+
+void PSConverter::setPaperHeight(int paperHeight)
+{
+    Q_D(PSConverter);
+    d->paperHeight = paperHeight;
+}
+
+void PSConverter::setRightMargin(int marginRight)
+{
+    Q_D(PSConverter);
+    d->marginRight = marginRight;
+}
+
+void PSConverter::setBottomMargin(int marginBottom)
+{
+    Q_D(PSConverter);
+    d->marginBottom = marginBottom;
+}
+
+void PSConverter::setLeftMargin(int marginLeft)
+{
+    Q_D(PSConverter);
+    d->marginLeft = marginLeft;
+}
+
+void PSConverter::setTopMargin(int marginTop)
+{
+    Q_D(PSConverter);
+    d->marginTop = marginTop;
+}
+
+void PSConverter::setStrictMargins(bool strictMargins)
+{
+    Q_D(PSConverter);
+    if (strictMargins)
+        d->opts |= StrictMargins;
+    else
+        d->opts &= ~StrictMargins;
+}
+
+void PSConverter::setForceRasterize(bool forceRasterize)
+{
+    Q_D(PSConverter);
+    if (forceRasterize)
+        d->opts |= ForceRasterization;
+    else
+        d->opts &= ~ForceRasterization;
+}
+
+void PSConverter::setPSOptions(PSConverter::PSOptions options)
+{
+    Q_D(PSConverter);
+    d->opts = options;
+}
+
+PSConverter::PSOptions PSConverter::psOptions() const
+{
+    Q_D(const PSConverter);
+    return d->opts;
+}
+
+void PSConverter::setPageConvertedCallback(void (*callback)(int page, void *payload), void *payload)
+{
+    Q_D(PSConverter);
+    d->pageConvertedCallback = callback;
+    d->pageConvertedPayload = payload;
+}
+
+static bool annotDisplayDecideCbk(Annot *annot, void *user_data)
+{
+    if (annot->getType() == Annot::typeWidget)
+        return true; // Never hide forms
+    else
+        return *(bool *)user_data;
+}
+
+bool PSConverter::convert()
+{
+    Q_D(PSConverter);
+    d->lastError = NoError;
+
+    Q_ASSERT(!d->pageList.isEmpty());
+    Q_ASSERT(d->paperWidth != -1);
+    Q_ASSERT(d->paperHeight != -1);
+
+    if (d->document->locked) {
+        d->lastError = FileLockedError;
+        return false;
+    }
+
+    QIODevice *dev = d->openDevice();
+    if (!dev) {
+        d->lastError = OpenOutputError;
+        return false;
+    }
+
+    QByteArray pstitle8Bit = d->title.toLocal8Bit();
+    char *pstitlechar;
+    if (!d->title.isEmpty())
+        pstitlechar = pstitle8Bit.data();
+    else
+        pstitlechar = nullptr;
+
+    std::vector<int> pages;
+    foreach (int page, d->pageList) {
+        pages.push_back(page);
+    }
+
+    PSOutputDev *psOut = new PSOutputDev(outputToQIODevice, dev, pstitlechar, d->document->doc, pages, (d->opts & PrintToEPS) ? psModeEPS : psModePS, d->paperWidth, d->paperHeight, false, false, d->marginLeft, d->marginBottom,
+                                         d->paperWidth - d->marginRight, d->paperHeight - d->marginTop, (d->opts & ForceRasterization) ? psAlwaysRasterize : psRasterizeWhenNeeded);
+
+    if (d->opts & StrictMargins) {
+        double xScale = ((double)d->paperWidth - (double)d->marginLeft - (double)d->marginRight) / (double)d->paperWidth;
+        double yScale = ((double)d->paperHeight - (double)d->marginBottom - (double)d->marginTop) / (double)d->paperHeight;
+        psOut->setScale(xScale, yScale);
+    }
+
+    if (psOut->isOk()) {
+        bool isPrinting = (d->opts & Printing) ? true : false;
+        bool showAnnotations = (d->opts & HideAnnotations) ? false : true;
+        foreach (int page, d->pageList) {
+            d->document->doc->displayPage(psOut, page, d->hDPI, d->vDPI, d->rotate, false, true, isPrinting, nullptr, nullptr, annotDisplayDecideCbk, &showAnnotations, true);
+            if (d->pageConvertedCallback)
+                (*d->pageConvertedCallback)(page, d->pageConvertedPayload);
+        }
+        delete psOut;
+        d->closeDevice();
+        return true;
+    } else {
+        delete psOut;
+        d->closeDevice();
+        return false;
+    }
+}
+
+}
diff --git a/qt6/src/poppler-qiodeviceinstream-private.h b/qt6/src/poppler-qiodeviceinstream-private.h
new file mode 100644
index 0000000..1f5a450
--- /dev/null
+++ b/qt6/src/poppler-qiodeviceinstream-private.h
@@ -0,0 +1,48 @@
+/* poppler-qiodeviceinstream-private.h: Qt6 interface to poppler
+ * Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
+ *
+ * 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.
+ */
+
+#ifndef POPPLER_QIODEVICEINSTREAM_PRIVATE_H
+#define POPPLER_QIODEVICEINSTREAM_PRIVATE_H
+
+#include "Object.h"
+#include "Stream.h"
+
+class QIODevice;
+
+namespace Poppler {
+
+class QIODeviceInStream : public BaseSeekInputStream
+{
+public:
+    QIODeviceInStream(QIODevice *device, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA);
+    ~QIODeviceInStream() override;
+
+    BaseStream *copy() override;
+    Stream *makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) override;
+
+private:
+    Goffset currentPos() const override;
+    void setCurrentPos(Goffset offset) override;
+    Goffset read(char *buffer, Goffset count) override;
+
+    QIODevice *m_device;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-qiodeviceinstream.cc b/qt6/src/poppler-qiodeviceinstream.cc
new file mode 100644
index 0000000..dea2f9d
--- /dev/null
+++ b/qt6/src/poppler-qiodeviceinstream.cc
@@ -0,0 +1,59 @@
+/* poppler-qiodeviceinstream.cc: Qt6 interface to poppler
+ * Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
+ *
+ * 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-qiodeviceinstream-private.h"
+
+#include <QtCore/QIODevice>
+
+#include <cstdio>
+
+namespace Poppler {
+
+QIODeviceInStream::QIODeviceInStream(QIODevice *device, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) : BaseSeekInputStream(startA, limitedA, lengthA, std::move(dictA)), m_device(device) { }
+
+QIODeviceInStream::~QIODeviceInStream()
+{
+    close();
+}
+
+BaseStream *QIODeviceInStream::copy()
+{
+    return new QIODeviceInStream(m_device, start, limited, length, dict.copy());
+}
+
+Stream *QIODeviceInStream::makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA)
+{
+    return new QIODeviceInStream(m_device, startA, limitedA, lengthA, std::move(dictA));
+}
+
+Goffset QIODeviceInStream::currentPos() const
+{
+    return m_device->pos();
+}
+
+void QIODeviceInStream::setCurrentPos(Goffset offset)
+{
+    m_device->seek(offset);
+}
+
+Goffset QIODeviceInStream::read(char *buffer, Goffset count)
+{
+    return m_device->read(buffer, count);
+}
+
+}
diff --git a/qt6/src/poppler-qiodeviceoutstream-private.h b/qt6/src/poppler-qiodeviceoutstream-private.h
new file mode 100644
index 0000000..a97317f
--- /dev/null
+++ b/qt6/src/poppler-qiodeviceoutstream-private.h
@@ -0,0 +1,47 @@
+/* poppler-qiodevicestream-private.h: Qt6 interface to poppler
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.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.
+ */
+
+#ifndef POPPLER_QIODEVICESTREAM_PRIVATE_H
+#define POPPLER_QIODEVICESTREAM_PRIVATE_H
+
+#include "Object.h"
+#include "Stream.h"
+
+class QIODevice;
+
+namespace Poppler {
+
+class QIODeviceOutStream : public OutStream
+{
+public:
+    QIODeviceOutStream(QIODevice *device);
+    ~QIODeviceOutStream() override;
+
+    void close() override;
+    Goffset getPos() override;
+    void put(char c) override;
+    void printf(const char *format, ...) override;
+
+private:
+    QIODevice *m_device;
+};
+
+}
+
+#endif
diff --git a/qt6/src/poppler-qiodeviceoutstream.cc b/qt6/src/poppler-qiodeviceoutstream.cc
new file mode 100644
index 0000000..e300fe0
--- /dev/null
+++ b/qt6/src/poppler-qiodeviceoutstream.cc
@@ -0,0 +1,57 @@
+/* poppler-qiodevicestream.cc: Qt6 interface to poppler
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.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-qiodeviceoutstream-private.h"
+
+#include <QtCore/QIODevice>
+
+#include <cstdio>
+
+#define QIODeviceOutStreamBufSize 8192
+
+namespace Poppler {
+
+QIODeviceOutStream::QIODeviceOutStream(QIODevice *device) : m_device(device) { }
+
+QIODeviceOutStream::~QIODeviceOutStream() { }
+
+void QIODeviceOutStream::close() { }
+
+Goffset QIODeviceOutStream::getPos()
+{
+    return m_device->pos();
+}
+
+void QIODeviceOutStream::put(char c)
+{
+    m_device->putChar(c);
+}
+
+void QIODeviceOutStream::printf(const char *format, ...)
+{
+    va_list ap;
+    va_start(ap, format);
+    char buf[QIODeviceOutStreamBufSize];
+    size_t bufsize = 0;
+    bufsize = qvsnprintf(buf, QIODeviceOutStreamBufSize - 1, format, ap);
+    va_end(ap);
+    m_device->write(buf, bufsize);
+}
+
+}
diff --git a/qt6/src/poppler-qt6.h b/qt6/src/poppler-qt6.h
new file mode 100644
index 0000000..a4ad188
--- /dev/null
+++ b/qt6/src/poppler-qt6.h
@@ -0,0 +1,2151 @@
+/* poppler-qt.h: qt interface to poppler
+ * Copyright (C) 2005, Net Integration Technologies, Inc.
+ * Copyright (C) 2005, 2007, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2005-2015, 2017-2020, 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) 2009 Shawn Rutledge <shawn.t.rutledge@gmail.com>
+ * Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
+ * Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
+ * Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+ * Copyright (C) 2011 Glad Deschrijver <glad.deschrijver@gmail.com>
+ * Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
+ * Copyright (C) 2012, Fabio D'Urso <fabiodurso@hotmail.it>
+ * Copyright (C) 2012, Tobias Koenig <tobias.koenig@kdab.com>
+ * Copyright (C) 2012, 2014, 2015, 2018, 2019 Adam Reichold <adamreichold@myopera.com>
+ * Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+ * Copyright (C) 2013 Anthony Granger <grangeranthony@gmail.com>
+ * Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
+ * Copyright (C) 2017 Oliver Sander <oliver.sander@tu-dresden.de>
+ * 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 Nelson Benítez León <nbenitezl@gmail.com>
+ * Copyright (C) 2019 Jan Grulich <jgrulich@redhat.com>
+ * Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
+ * Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.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.
+ */
+
+#ifndef __POPPLER_QT_H__
+#define __POPPLER_QT_H__
+
+#include "poppler-annotation.h"
+#include "poppler-link.h"
+#include "poppler-optcontent.h"
+#include "poppler-page-transition.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QDateTime>
+#include <QtCore/QSet>
+#include <QtXml/QDomDocument>
+#include "poppler-export.h"
+
+class EmbFile;
+class Sound;
+class AnnotMovie;
+
+/**
+   The %Poppler Qt6 binding.
+*/
+namespace Poppler {
+
+class Document;
+class DocumentData;
+
+class PageData;
+
+class FormField;
+class FormFieldSignature;
+
+class TextBoxData;
+
+class PDFConverter;
+class PSConverter;
+
+struct OutlineItemData;
+
+/**
+    Debug/error function.
+
+    This function type is used for debugging & error output;
+    the first parameter is the actual message, the second is the unaltered
+    closure argument which was passed to the setDebugErrorFunction call.
+*/
+typedef void (*PopplerDebugFunc)(const QString & /*message*/, const QVariant & /*closure*/);
+
+/**
+    Set a new debug/error output function.
+
+    If not set, by default error and debug messages will be sent to the
+    Qt \p qDebug() function.
+
+    \param debugFunction the new debug function
+    \param closure user data which will be passes as-is to the debug function
+*/
+POPPLER_QT6_EXPORT void setDebugErrorFunction(PopplerDebugFunc debugFunction, const QVariant &closure);
+
+/**
+    Describes the physical location of text on a document page
+
+    This very simple class describes the physical location of text
+    on the page. It consists of
+    - a QString that contains the text
+    - a QRectF that gives a box that describes where on the page
+    the text is found.
+*/
+class POPPLER_QT6_EXPORT TextBox
+{
+    friend class Page;
+
+public:
+    /**
+       The default constructor sets the \p text and the rectangle that
+       contains the text. Coordinates for the \p bBox are in points =
+       1/72 of an inch.
+    */
+    TextBox(const QString &text, const QRectF &bBox);
+    /**
+      Destructor.
+    */
+    ~TextBox();
+
+    /**
+        Returns the text of this text box
+    */
+    QString text() const;
+
+    /**
+        Returns the position of the text, in point, i.e., 1/72 of
+       an inch
+    */
+    QRectF boundingBox() const;
+
+    /**
+        Returns the pointer to the next text box, if there is one.
+
+        Otherwise, it returns a null pointer.
+    */
+    TextBox *nextWord() const;
+
+    /**
+        Returns the bounding box of the \p i -th characted of the word.
+    */
+    QRectF charBoundingBox(int i) const;
+
+    /**
+        Returns whether there is a space character after this text box
+    */
+    bool hasSpaceAfter() const;
+
+private:
+    Q_DISABLE_COPY(TextBox)
+
+    TextBoxData *m_data;
+};
+
+class FontInfoData;
+/**
+   Container class for information about a font within a PDF
+   document
+*/
+class POPPLER_QT6_EXPORT FontInfo
+{
+    friend class Document;
+
+public:
+    /**
+       The type of font.
+    */
+    enum Type
+    {
+        unknown,
+        Type1,
+        Type1C,
+        Type1COT,
+        Type3,
+        TrueType,
+        TrueTypeOT,
+        CIDType0,
+        CIDType0C,
+        CIDType0COT,
+        CIDTrueType,
+        CIDTrueTypeOT
+    };
+
+    /// \cond PRIVATE
+    /**
+       Create a new font information container.
+    */
+    FontInfo();
+
+    /**
+       Create a new font information container.
+    */
+    FontInfo(const FontInfoData &fid);
+    /// \endcond
+
+    /**
+       Copy constructor.
+    */
+    FontInfo(const FontInfo &fi);
+
+    /**
+       Destructor.
+    */
+    ~FontInfo();
+
+    /**
+       The name of the font. Can be a null QString if the font has no name
+    */
+    QString name() const;
+
+    /**
+       The name of the substitute font. Can be a null QString if the font has no substitute font
+    */
+    QString substituteName() const;
+
+    /**
+       The path of the font file used to represent this font on this system,
+       or a null string is the font is embedded
+    */
+    QString file() const;
+
+    /**
+       Whether the font is embedded in the file, or not
+
+       \return true if the font is embedded
+    */
+    bool isEmbedded() const;
+
+    /**
+       Whether the font provided is only a subset of the full
+       font or not. This only has meaning if the font is embedded.
+
+       \return true if the font is only a subset
+    */
+    bool isSubset() const;
+
+    /**
+       The type of font encoding
+
+       \return a enumerated value corresponding to the font encoding used
+
+       \sa typeName for a string equivalent
+    */
+    Type type() const;
+
+    /**
+       The name of the font encoding used
+
+       \note if you are looking for the name of the font (as opposed to the
+       encoding format used), you probably want name().
+
+       \sa type for a enumeration version
+    */
+    QString typeName() const;
+
+    /**
+       Standard assignment operator
+    */
+    FontInfo &operator=(const FontInfo &fi);
+
+private:
+    FontInfoData *m_data;
+};
+
+class FontIteratorData;
+/**
+   Iterator for reading the fonts in a document.
+
+   FontIterator provides a Java-style iterator for reading the fonts in a
+   document.
+
+   You can use it in the following way:
+   \code
+Poppler::FontIterator* it = doc->newFontIterator();
+while (it->hasNext()) {
+QList<Poppler::FontInfo> fonts = it->next();
+// do something with the fonts
+}
+// after doing the job, the iterator must be freed
+delete it;
+   \endcode
+*/
+class POPPLER_QT6_EXPORT FontIterator
+{
+    friend class Document;
+    friend class DocumentData;
+
+public:
+    /**
+       Destructor.
+    */
+    ~FontIterator();
+
+    /**
+       Returns the fonts of the current page and then advances the iterator
+       to the next page.
+    */
+    QList<FontInfo> next();
+
+    /**
+       Checks whether there is at least one more page to iterate, ie returns
+       false when the iterator is beyond the last page.
+    */
+    bool hasNext() const;
+
+    /**
+       Returns the current page where the iterator is.
+    */
+    int currentPage() const;
+
+private:
+    Q_DISABLE_COPY(FontIterator)
+    FontIterator(int, DocumentData *dd);
+
+    FontIteratorData *d;
+};
+
+class EmbeddedFileData;
+/**
+   Container class for an embedded file with a PDF document
+*/
+class POPPLER_QT6_EXPORT EmbeddedFile
+{
+    friend class DocumentData;
+    friend class AnnotationPrivate;
+
+public:
+    /// \cond PRIVATE
+    EmbeddedFile(EmbFile *embfile);
+    /// \endcond
+
+    /**
+       Destructor.
+    */
+    ~EmbeddedFile();
+
+    /**
+       The name associated with the file
+    */
+    QString name() const;
+
+    /**
+       The description associated with the file, if any.
+
+       This will return an empty QString if there is no description element
+    */
+    QString description() const;
+
+    /**
+       The size of the file.
+
+       This will return < 0 if there is no size element
+    */
+    int size() const;
+
+    /**
+       The modification date for the embedded file, if known.
+    */
+    QDateTime modDate() const;
+
+    /**
+       The creation date for the embedded file, if known.
+    */
+    QDateTime createDate() const;
+
+    /**
+       The MD5 checksum of the file.
+
+       This will return an empty QByteArray if there is no checksum element.
+    */
+    QByteArray checksum() const;
+
+    /**
+       The MIME type of the file, if known.
+    */
+    QString mimeType() const;
+
+    /**
+       The data as a byte array
+    */
+    QByteArray data();
+
+    /**
+       Is the embedded file valid?
+    */
+    bool isValid() const;
+
+    /**
+       A QDataStream for the actual data?
+    */
+    // QDataStream dataStream() const;
+
+private:
+    Q_DISABLE_COPY(EmbeddedFile)
+    EmbeddedFile(EmbeddedFileData &dd);
+
+    EmbeddedFileData *m_embeddedFile;
+};
+
+/**
+   \brief A page in a document.
+
+   The Page class represents a single page within a PDF document.
+
+   You cannot construct a Page directly, but you have to use the Document
+   functions that return a new Page out of an index or a label.
+*/
+class POPPLER_QT6_EXPORT Page
+{
+    friend class Document;
+
+public:
+    /**
+       Destructor.
+    */
+    ~Page();
+
+    /**
+       The type of rotation to apply for an operation
+    */
+    enum Rotation
+    {
+        Rotate0 = 0, ///< Do not rotate
+        Rotate90 = 1, ///< Rotate 90 degrees clockwise
+        Rotate180 = 2, ///< Rotate 180 degrees
+        Rotate270 = 3 ///< Rotate 270 degrees clockwise (90 degrees counterclockwise)
+    };
+
+    /**
+       The kinds of page actions
+    */
+    enum PageAction
+    {
+        Opening, ///< The action when a page is "opened"
+        Closing ///< The action when a page is "closed"
+    };
+
+    /**
+       How the text is going to be returned
+    */
+    enum TextLayout
+    {
+        PhysicalLayout, ///< The text is layouted to resemble the real page layout
+        RawOrderLayout ///< The text is returned without any type of processing
+    };
+
+    /**
+       Additional flags for the renderToPainter method
+    */
+    enum PainterFlag
+    {
+        NoPainterFlags = 0x00000000,
+        /**
+           Do not save/restore the caller-owned painter.
+
+           renderToPainter() by default preserves, using save() + restore(),
+           the state of the painter specified; if this is not needed, this
+           flag can avoid this job
+         */
+        DontSaveAndRestore = 0x00000001
+    };
+    Q_DECLARE_FLAGS(PainterFlags, PainterFlag)
+
+    /**
+       Render the page to a QImage using the current
+       \link Document::renderBackend() Document renderer\endlink.
+
+       If \p x = \p y = \p w = \p h = -1, the method will automatically
+       compute the size of the image from the horizontal and vertical
+       resolutions specified in \p xres and \p yres. Otherwise, the
+       method renders only a part of the page, specified by the
+       parameters (\p x, \p y, \p w, \p h) in pixel coordinates. The returned
+       QImage then has size (\p w, \p h), independent of the page
+       size.
+
+       \param x specifies the left x-coordinate of the box, in
+       pixels.
+
+       \param y specifies the top y-coordinate of the box, in
+       pixels.
+
+       \param w specifies the width of the box, in pixels.
+
+       \param h specifies the height of the box, in pixels.
+
+       \param xres horizontal resolution of the graphics device,
+       in dots per inch
+
+       \param yres vertical resolution of the graphics device, in
+       dots per inch
+
+       \param rotate how to rotate the page
+
+       \warning The parameter (\p x, \p y, \p w, \p h) are not
+       well-tested. Unusual or meaningless parameters may lead to
+       rather unexpected results.
+
+       \returns a QImage of the page, or a null image on failure.
+    */
+    QImage renderToImage(double xres = 72.0, double yres = 72.0, int x = -1, int y = -1, int w = -1, int h = -1, Rotation rotate = Rotate0) const;
+
+    /**
+        Partial Update renderToImage callback.
+
+        This function type is used for doing partial rendering updates;
+        the first parameter is the image as rendered up to now, the second is the unaltered
+        closure argument which was passed to the renderToImage call.
+    */
+    typedef void (*RenderToImagePartialUpdateFunc)(const QImage & /*image*/, const QVariant & /*closure*/);
+
+    /**
+        Partial Update query renderToImage callback.
+
+        This function type is used for query if the partial rendering update should happen;
+        the parameter is the unaltered closure argument which was passed to the renderToImage call.
+    */
+    typedef bool (*ShouldRenderToImagePartialQueryFunc)(const QVariant & /*closure*/);
+
+    /**
+       Render the page to a QImage using the current
+       \link Document::renderBackend() Document renderer\endlink.
+
+       If \p x = \p y = \p w = \p h = -1, the method will automatically
+       compute the size of the image from the horizontal and vertical
+       resolutions specified in \p xres and \p yres. Otherwise, the
+       method renders only a part of the page, specified by the
+       parameters (\p x, \p y, \p w, \p h) in pixel coordinates. The returned
+       QImage then has size (\p w, \p h), independent of the page
+       size.
+
+       \param x specifies the left x-coordinate of the box, in
+       pixels.
+
+       \param y specifies the top y-coordinate of the box, in
+       pixels.
+
+       \param w specifies the width of the box, in pixels.
+
+       \param h specifies the height of the box, in pixels.
+
+       \param xres horizontal resolution of the graphics device,
+       in dots per inch
+
+       \param yres vertical resolution of the graphics device, in
+       dots per inch
+
+       \param rotate how to rotate the page
+
+       \param partialUpdateCallback callback that will be called to
+       report a partial rendering update
+
+       \param shouldDoPartialUpdateCallback callback that will be called
+       to ask if a partial rendering update is wanted. This exists
+       because doing a partial rendering update needs to copy the image
+       buffer so if it is not wanted it is better skipped early.
+
+       \param payload opaque structure that will be passed
+       back to partialUpdateCallback and shouldDoPartialUpdateCallback.
+
+       \warning The parameter (\p x, \p y, \p w, \p h) are not
+       well-tested. Unusual or meaningless parameters may lead to
+       rather unexpected results.
+
+       \returns a QImage of the page, or a null image on failure.
+    */
+    QImage renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,
+                         const QVariant &payload) const;
+
+    /**
+        Abort query function callback.
+
+        This function type is used for query if the current rendering/text extraction should be cancelled.
+    */
+    typedef bool (*ShouldAbortQueryFunc)(const QVariant & /*closure*/);
+
+    /**
+Render the page to a QImage using the current
+\link Document::renderBackend() Document renderer\endlink.
+
+If \p x = \p y = \p w = \p h = -1, the method will automatically
+compute the size of the image from the horizontal and vertical
+resolutions specified in \p xres and \p yres. Otherwise, the
+method renders only a part of the page, specified by the
+parameters (\p x, \p y, \p w, \p h) in pixel coordinates. The returned
+QImage then has size (\p w, \p h), independent of the page
+size.
+
+\param x specifies the left x-coordinate of the box, in
+pixels.
+
+\param y specifies the top y-coordinate of the box, in
+pixels.
+
+\param w specifies the width of the box, in pixels.
+
+\param h specifies the height of the box, in pixels.
+
+\param xres horizontal resolution of the graphics device,
+in dots per inch
+
+\param yres vertical resolution of the graphics device, in
+dots per inch
+
+\param rotate how to rotate the page
+
+\param partialUpdateCallback callback that will be called to
+report a partial rendering update
+
+\param shouldDoPartialUpdateCallback callback that will be called
+to ask if a partial rendering update is wanted. This exists
+because doing a partial rendering update needs to copy the image
+buffer so if it is not wanted it is better skipped early.
+
+\param shouldAbortRenderCallback callback that will be called
+to ask if the rendering should be cancelled.
+
+\param payload opaque structure that will be passed
+back to partialUpdateCallback, shouldDoPartialUpdateCallback
+and shouldAbortRenderCallback.
+
+\warning The parameter (\p x, \p y, \p w, \p h) are not
+well-tested. Unusual or meaningless parameters may lead to
+rather unexpected results.
+
+\returns a QImage of the page, or a null image on failure.
+*/
+    QImage renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,
+                         ShouldAbortQueryFunc shouldAbortRenderCallback, const QVariant &payload) const;
+
+    /**
+       Render the page to the specified QPainter using the current
+       \link Document::renderBackend() Document renderer\endlink.
+
+       If \p x = \p y = \p w = \p h = -1, the method will automatically
+       compute the size of the page area from the horizontal and vertical
+       resolutions specified in \p xres and \p yres. Otherwise, the
+       method renders only a part of the page, specified by the
+       parameters (\p x, \p y, \p w, \p h) in pixel coordinates.
+
+       \param painter the painter to paint on
+
+       \param x specifies the left x-coordinate of the box, in
+       pixels.
+
+       \param y specifies the top y-coordinate of the box, in
+       pixels.
+
+       \param w specifies the width of the box, in pixels.
+
+       \param h specifies the height of the box, in pixels.
+
+       \param xres horizontal resolution of the graphics device,
+       in dots per inch
+
+       \param yres vertical resolution of the graphics device, in
+       dots per inch
+
+       \param rotate how to rotate the page
+
+       \param flags additional painter flags
+
+       \warning The parameter (\p x, \p y, \p w, \p h) are not
+       well-tested. Unusual or meaningless parameters may lead to
+       rather unexpected results.
+
+       \returns whether the painting succeeded
+
+       \note This method is only supported for Arthur
+    */
+    bool renderToPainter(QPainter *painter, double xres = 72.0, double yres = 72.0, int x = -1, int y = -1, int w = -1, int h = -1, Rotation rotate = Rotate0, PainterFlags flags = NoPainterFlags) const;
+
+    /**
+       Get the page thumbnail if it exists.
+
+       \return a QImage of the thumbnail, or a null image
+       if the PDF does not contain one for this page
+    */
+    QImage thumbnail() const;
+
+    /**
+       Returns the text that is inside a specified rectangle
+
+       \param rect the rectangle specifying the area of interest,
+       with coordinates given in points, i.e., 1/72th of an inch.
+       If rect is null, all text on the page is given
+    **/
+    QString text(const QRectF &rect, TextLayout textLayout) const;
+
+    /**
+       Returns the text that is inside a specified rectangle.
+       The text is returned using the physical layout of the page
+
+       \param rect the rectangle specifying the area of interest,
+       with coordinates given in points, i.e., 1/72th of an inch.
+       If rect is null, all text on the page is given
+    **/
+    QString text(const QRectF &rect) const;
+
+    /**
+       The starting point for a search
+    */
+    enum SearchDirection
+    {
+        FromTop, ///< Start sorting at the top of the document
+        NextResult, ///< Find the next result, moving "down the page"
+        PreviousResult ///< Find the previous result, moving "up the page"
+    };
+
+    /**
+       The type of search to perform
+    */
+    enum SearchMode
+    {
+        CaseSensitive, ///< Case differences cause no match in searching
+        CaseInsensitive ///< Case differences are ignored in matching
+    };
+
+    /**
+       Flags to modify the search behaviour
+    */
+    enum SearchFlag
+    {
+        NoSearchFlags = 0x00000000,
+        IgnoreCase = 0x00000001, ///< Case differences are ignored
+        WholeWords = 0x00000002, ///< Only whole words are matched
+        IgnoreDiacritics = 0x00000004 ///< Diacritic differences (eg. accents, umlauts, diaeresis) are ignored.
+                                      ///< This option will have no effect if the search term contains characters which
+                                      ///< are not pure ascii.
+    };
+    Q_DECLARE_FLAGS(SearchFlags, SearchFlag)
+
+    /**
+       Returns true if the specified text was found.
+
+       \param text the text the search
+       \param rectXXX in all directions is used to return where the text was found, for NextResult and PreviousResult
+                   indicates where to continue searching for
+       \param direction in which direction do the search
+       \param flags the flags to consider during matching
+       \param rotate the rotation to apply for the search order
+    **/
+    bool search(const QString &text, double &sLeft, double &sTop, double &sRight, double &sBottom, SearchDirection direction, SearchFlags flags = NoSearchFlags, Rotation rotate = Rotate0) const;
+
+    /**
+       Returns a list of all occurrences of the specified text on the page.
+
+       \param text the text to search
+       \param flags the flags to consider during matching
+       \param rotate the rotation to apply for the search order
+
+       \warning Do not use the returned QRectF as arguments of another search call because of truncation issues if qreal is defined as float.
+    **/
+    QList<QRectF> search(const QString &text, SearchFlags flags = NoSearchFlags, Rotation rotate = Rotate0) const;
+
+    /**
+       Returns a list of text of the page
+
+       This method returns a QList of TextBoxes that contain all
+       the text of the page, with roughly one text word of text
+       per TextBox item.
+
+       For text written in western languages (left-to-right and
+       up-to-down), the QList contains the text in the proper
+       order.
+
+       \note The caller owns the text boxes and they should
+             be deleted when no longer required.
+
+       \warning This method is not tested with Asian scripts
+    */
+    QList<TextBox *> textList(Rotation rotate = Rotate0) const;
+
+    /**
+       Returns a list of text of the page
+
+       This method returns a QList of TextBoxes that contain all
+       the text of the page, with roughly one text word of text
+       per TextBox item.
+
+       For text written in western languages (left-to-right and
+       up-to-down), the QList contains the text in the proper
+       order.
+
+       \param shouldAbortExtractionCallback callback that will be called
+       to ask if the text extraction should be cancelled.
+
+       \param closure opaque structure that will be passed
+       back to shouldAbortExtractionCallback.
+
+       \note The caller owns the text boxes and they should
+             be deleted when no longer required.
+
+       \warning This method is not tested with Asian scripts
+    */
+    QList<TextBox *> textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const;
+
+    /**
+       \return The dimensions (cropbox) of the page, in points (i.e. 1/72th of an inch)
+    */
+    QSizeF pageSizeF() const;
+
+    /**
+       \return The dimensions (cropbox) of the page, in points (i.e. 1/72th of an inch)
+    */
+    QSize pageSize() const;
+
+    /**
+      Returns the transition of this page
+
+      \returns a pointer to a PageTransition structure that
+      defines how transition to this page shall be performed.
+
+      \note The PageTransition structure is owned by this page, and will
+      automatically be destroyed when this page class is
+      destroyed.
+    **/
+    PageTransition *transition() const;
+
+    /**
+      Gets the page action specified, or NULL if there is no action.
+    **/
+    Link *action(PageAction act) const;
+
+    /**
+       Types of orientations that are possible
+    */
+    enum Orientation
+    {
+        Landscape, ///< Landscape orientation (portrait, with 90 degrees clockwise rotation )
+        Portrait, ///< Normal portrait orientation
+        Seascape, ///< Seascape orientation (portrait, with 270 degrees clockwise rotation)
+        UpsideDown ///< Upside down orientation (portrait, with 180 degrees rotation)
+    };
+
+    /**
+       The orientation of the page
+    */
+    Orientation orientation() const;
+
+    /**
+      The default CTM
+    */
+    void defaultCTM(double *CTM, double dpiX, double dpiY, int rotate, bool upsideDown);
+
+    /**
+      Gets the links of the page
+    */
+    QList<Link *> links() const;
+
+    /**
+     Returns the annotations of the page
+
+     \note If you call this method twice, you get different objects
+           pointing to the same annotations (see Annotation).
+           The caller owns the returned objects and they should be deleted
+           when no longer required.
+    */
+    QList<Annotation *> annotations() const;
+
+    /**
+            Returns the annotations of the page
+
+            \param subtypes the subtypes of annotations you are interested in
+
+            \note If you call this method twice, you get different objects
+                  pointing to the same annotations (see Annotation).
+                  The caller owns the returned objects and they should be deleted
+                  when no longer required.
+    */
+    QList<Annotation *> annotations(const QSet<Annotation::SubType> &subtypes) const;
+
+    /**
+     Adds an annotation to the page
+
+     \note Ownership of the annotation object stays with the caller, who can
+           delete it at any time.
+    */
+    void addAnnotation(const Annotation *ann);
+
+    /**
+     Removes an annotation from the page and destroys the annotation object
+
+     \note There mustn't be other Annotation objects pointing this annotation
+    */
+    void removeAnnotation(const Annotation *ann);
+
+    /**
+     Returns the form fields on the page
+     The caller gets the ownership of the returned objects.
+    */
+    QList<FormField *> formFields() const;
+
+    /**
+     Returns the page duration. That is the time, in seconds, that the page
+     should be displayed before the presentation automatically advances to the next page.
+     Returns < 0 if duration is not set.
+    */
+    double duration() const;
+
+    /**
+       Returns the label of the page, or a null string is the page has no label.
+    **/
+    QString label() const;
+
+    /**
+       Returns the index of the page.
+    **/
+    int index() const;
+
+private:
+    Q_DISABLE_COPY(Page)
+
+    Page(DocumentData *doc, int index);
+    PageData *m_page;
+};
+
+/**
+   \brief Item in the outline of a PDF document
+
+   Represents an item in the outline of PDF document, i.e. a name, an internal or external link and a set of child items.
+**/
+class POPPLER_QT6_EXPORT OutlineItem
+{
+    friend class Document;
+
+public:
+    /**
+       Constructs a null item, i.e. one that does not represent a valid item in the outline of some PDF document.
+    **/
+    OutlineItem();
+    ~OutlineItem();
+
+    OutlineItem(const OutlineItem &other);
+    OutlineItem &operator=(const OutlineItem &other);
+
+    OutlineItem(OutlineItem &&other) noexcept;
+    OutlineItem &operator=(OutlineItem &&other) noexcept;
+
+    /**
+       Indicates whether an item is null, i.e. whether it does not represent a valid item in the outline of some PDF document.
+    **/
+    bool isNull() const;
+
+    /**
+       The name of the item which should be displayed to the user.
+    **/
+    QString name() const;
+
+    /**
+       Indicates whether the item should initially be display in an expanded or collapsed state.
+    **/
+    bool isOpen() const;
+
+    /**
+       The destination referred to by this item.
+
+       \returns a shared pointer to an immutable link destination
+    **/
+    QSharedPointer<const LinkDestination> destination() const;
+
+    /**
+       The external file name of the document to which the \see destination refers
+
+       \returns a string with the external file name or an empty string if there is none
+     */
+    QString externalFileName() const;
+
+    /**
+       The URI to which the item links
+
+       \returns a string with the URI which this item links or an empty string if there is none
+    **/
+    QString uri() const;
+
+    /**
+       Determines if this item has any child items
+
+       \returns true if there are any child items
+    **/
+    bool hasChildren() const;
+
+    /**
+       Gets the child items of this item
+
+       \returns a vector outline items, empty if there are none
+    **/
+    QVector<OutlineItem> children() const;
+
+private:
+    OutlineItem(OutlineItemData *data);
+    OutlineItemData *m_data;
+};
+
+/**
+   \brief PDF document.
+
+   The Document class represents a PDF document: its pages, and all the global
+   properties, metadata, etc.
+
+   \section ownership Ownership of the returned objects
+
+   All the functions that returns class pointers create new object, and the
+   responsibility of those is given to the callee.
+
+   The only exception is \link Poppler::Page::transition() Page::transition()\endlink.
+
+   \section document-loading Loading
+
+   To get a Document, you have to load it via the load() & loadFromData()
+   functions.
+
+   In all the functions that have passwords as arguments, they \b must be Latin1
+   encoded. If you have a password that is a UTF-8 string, you need to use
+   QString::toLatin1() (or similar) to convert the password first.
+   If you have a UTF-8 character array, consider converting it to a QString first
+   (QString::fromUtf8(), or similar) before converting to Latin1 encoding.
+
+   \section document-rendering Rendering
+
+   To render pages of a document, you have different Document functions to set
+   various options.
+
+   \subsection document-rendering-backend Backends
+
+   %Poppler offers a different backends for rendering the pages. Currently
+   there are two backends (see #RenderBackend), but only the Splash engine works
+   well and has been tested.
+
+   The available rendering backends can be discovered via availableRenderBackends().
+   The current rendering backend can be changed using setRenderBackend().
+   Please note that setting a backend not listed in the available ones
+   will always result in null QImage's.
+
+   \section document-cms Color management support
+
+   %Poppler, if compiled with this support, provides functions to handle color
+   profiles.
+
+   To know whether the %Poppler version you are using has support for color
+   management, you can query Poppler::isCmsAvailable(). In case it is not
+   available, all the color management-related functions will either do nothing
+   or return null.
+*/
+class POPPLER_QT6_EXPORT Document
+{
+    friend class Page;
+    friend class DocumentData;
+
+public:
+    /**
+       The page mode
+    */
+    enum PageMode
+    {
+        UseNone, ///< No mode - neither document outline nor thumbnail images are visible
+        UseOutlines, ///< Document outline visible
+        UseThumbs, ///< Thumbnail images visible
+        FullScreen, ///< Fullscreen mode (no menubar, windows controls etc)
+        UseOC, ///< Optional content group panel visible
+        UseAttach ///< Attachments panel visible
+    };
+
+    /**
+       The page layout
+    */
+    enum PageLayout
+    {
+        NoLayout, ///< Layout not specified
+        SinglePage, ///< Display a single page
+        OneColumn, ///< Display a single column of pages
+        TwoColumnLeft, ///< Display the pages in two columns, with odd-numbered pages on the left
+        TwoColumnRight, ///< Display the pages in two columns, with odd-numbered pages on the right
+        TwoPageLeft, ///< Display the pages two at a time, with odd-numbered pages on the left
+        TwoPageRight ///< Display the pages two at a time, with odd-numbered pages on the right
+    };
+
+    /**
+       The render backends available
+    */
+    enum RenderBackend
+    {
+        SplashBackend, ///< Splash backend
+        ArthurBackend ///< Arthur (Qt) backend
+    };
+
+    /**
+       The render hints available
+    */
+    enum RenderHint
+    {
+        Antialiasing = 0x00000001, ///< Antialiasing for graphics
+        TextAntialiasing = 0x00000002, ///< Antialiasing for text
+        TextHinting = 0x00000004, ///< Hinting for text
+        TextSlightHinting = 0x00000008, ///< Lighter hinting for text when combined with TextHinting
+        OverprintPreview = 0x00000010, ///< Overprint preview
+        ThinLineSolid = 0x00000020, ///< Enhance thin lines solid
+        ThinLineShape = 0x00000040, ///< Enhance thin lines shape. Wins over ThinLineSolid
+        IgnorePaperColor = 0x00000080, ///< Do not compose with the paper color
+        HideAnnotations = 0x00000100 ///< Do not render annotations
+    };
+    Q_DECLARE_FLAGS(RenderHints, RenderHint)
+
+    /**
+       Form types
+    */
+    enum FormType
+    {
+        NoForm, ///< Document doesn't contain forms
+        AcroForm, ///< AcroForm
+        XfaForm ///< Adobe XML Forms Architecture (XFA), currently unsupported
+    };
+
+    /**
+      Set a color display profile for the current document.
+
+      \param outputProfileA is a \c cmsHPROFILE of the LCMS library.
+
+      \note This should be called before any rendering happens.
+
+      \note It is assumed that poppler takes over the owernship of the corresponding cmsHPROFILE. In particular,
+      it is no longer the caller's responsibility to close the profile after use.
+    */
+    void setColorDisplayProfile(void *outputProfileA);
+    /**
+      Set a color display profile for the current document.
+
+      \param name is the name of the display profile to set.
+
+      \note This should be called before any rendering happens.
+    */
+    void setColorDisplayProfileName(const QString &name);
+    /**
+      Return the current RGB profile.
+
+      \return a \c cmsHPROFILE of the LCMS library.
+
+      \note The returned profile stays a property of poppler and shall NOT be closed by the user. It's
+      existence is guaranteed for as long as this instance of the Document class is not deleted.
+    */
+    void *colorRgbProfile() const;
+    /**
+      Return the current display profile.
+
+      \return a \c cmsHPROFILE of the LCMS library.
+
+      \note The returned profile stays a property of poppler and shall NOT be closed by the user. It's
+      existence is guaranteed for as long as this instance of the Document class is not deleted.
+    */
+    void *colorDisplayProfile() const;
+
+    /**
+       Load the document from a file on disk
+
+       \param filePath the name (and path, if required) of the file to load
+       \param ownerPassword the Latin1-encoded owner password to use in
+       loading the file
+       \param userPassword the Latin1-encoded user ("open") password
+       to use in loading the file
+
+       \return the loaded document, or NULL on error
+
+       \note The caller owns the pointer to Document, and this should
+       be deleted when no longer required.
+
+       \warning The returning document may be locked if a password is required
+       to open the file, and one is not provided (as the userPassword).
+    */
+    static Document *load(const QString &filePath, const QByteArray &ownerPassword = QByteArray(), const QByteArray &userPassword = QByteArray());
+
+    /**
+       Load the document from a device
+
+       \param device the device of the data to load
+       \param ownerPassword the Latin1-encoded owner password to use in
+       loading the file
+       \param userPassword the Latin1-encoded user ("open") password
+       to use in loading the file
+
+       \return the loaded document, or NULL on error
+
+       \note The caller owns the pointer to Document, and this should
+       be deleted when no longer required.
+
+       \note The ownership of the device stays with the caller.
+
+       \note if the file is on disk it is recommended to use the other load overload
+       since it is less resource intensive
+
+       \warning The returning document may be locked if a password is required
+       to open the file, and one is not provided (as the userPassword).
+    */
+    static Document *load(QIODevice *device, const QByteArray &ownerPassword = QByteArray(), const QByteArray &userPassword = QByteArray());
+
+    /**
+       Load the document from memory
+
+       \param fileContents the file contents. They are copied so there is no need
+                           to keep the byte array around for the full life time of
+                           the document.
+       \param ownerPassword the Latin1-encoded owner password to use in
+       loading the file
+       \param userPassword the Latin1-encoded user ("open") password
+       to use in loading the file
+
+       \return the loaded document, or NULL on error
+
+       \note The caller owns the pointer to Document, and this should
+       be deleted when no longer required.
+
+       \warning The returning document may be locked if a password is required
+       to open the file, and one is not provided (as the userPassword).
+    */
+    static Document *loadFromData(const QByteArray &fileContents, const QByteArray &ownerPassword = QByteArray(), const QByteArray &userPassword = QByteArray());
+
+    /**
+       Get a specified Page
+
+       Note that this follows the PDF standard of being zero based - if you
+       want the first page, then you need an index of zero.
+
+       The caller gets the ownership of the returned object.
+
+       This function can return nullptr if for some reason the page can't be properly parsed.
+
+       \param index the page number index
+    */
+    Page *page(int index) const;
+
+    /**
+       \overload
+
+
+       The intent is that you can pass in a label like \c "ix" and
+       get the page with that label (which might be in the table of
+       contents), or pass in \c "1" and get the page that the user
+       expects (which might not be the first page, if there is a
+       title page and a table of contents).
+
+       \param label the page label
+    */
+    Page *page(const QString &label) const;
+
+    /**
+       The number of pages in the document
+    */
+    int numPages() const;
+
+    /**
+       The type of mode that should be used by the application
+       when the document is opened. Note that while this is
+       called page mode, it is really viewer application mode.
+    */
+    PageMode pageMode() const;
+
+    /**
+       The layout that pages should be shown in when the document
+       is first opened.  This basically describes how pages are
+       shown relative to each other.
+    */
+    PageLayout pageLayout() const;
+
+    /**
+       The predominant reading order for text as supplied by
+       the document's viewer preferences.
+    */
+    Qt::LayoutDirection textDirection() const;
+
+    /**
+       Provide the passwords required to unlock the document
+
+       \param ownerPassword the Latin1-encoded owner password to use in
+       loading the file
+       \param userPassword the Latin1-encoded user ("open") password
+       to use in loading the file
+    */
+    bool unlock(const QByteArray &ownerPassword, const QByteArray &userPassword);
+
+    /**
+       Determine if the document is locked
+    */
+    bool isLocked() const;
+
+    /**
+       The date associated with the document
+
+       You would use this method with something like:
+       \code
+QDateTime created = m_doc->date("CreationDate");
+QDateTime modified = m_doc->date("ModDate");
+       \endcode
+
+       The available dates are:
+       - CreationDate: the date of creation of the document
+       - ModDate: the date of the last change in the document
+
+       \param type the type of date that is required
+    */
+    QDateTime date(const QString &type) const;
+
+    /**
+       Set the Info dict date entry specified by \param key to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setDate(const QString &key, const QDateTime &val);
+
+    /**
+       The date of the creation of the document
+    */
+    QDateTime creationDate() const;
+
+    /**
+       Set the creation date of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setCreationDate(const QDateTime &val);
+
+    /**
+       The date of the last change in the document
+    */
+    QDateTime modificationDate() const;
+
+    /**
+       Set the modification date of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setModificationDate(const QDateTime &val);
+
+    /**
+       Get specified information associated with the document
+
+       You would use this method with something like:
+       \code
+QString title = m_doc->info("Title");
+QString subject = m_doc->info("Subject");
+       \endcode
+
+       In addition to \c Title and \c Subject, other information that may
+       be available include \c Author, \c Keywords, \c Creator and \c Producer.
+
+       \param type the information that is required
+
+       \sa infoKeys() to get a list of the available keys
+    */
+    QString info(const QString &type) const;
+
+    /**
+       Set the value of the document's Info dictionary entry specified by \param key to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setInfo(const QString &key, const QString &val);
+
+    /**
+       The title of the document
+    */
+    QString title() const;
+
+    /**
+       Set the title of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setTitle(const QString &val);
+
+    /**
+       The author of the document
+    */
+    QString author() const;
+
+    /**
+       Set the author of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setAuthor(const QString &val);
+
+    /**
+       The subject of the document
+    */
+    QString subject() const;
+
+    /**
+       Set the subject of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setSubject(const QString &val);
+
+    /**
+       The keywords of the document
+    */
+    QString keywords() const;
+
+    /**
+       Set the keywords of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setKeywords(const QString &val);
+
+    /**
+       The creator of the document
+    */
+    QString creator() const;
+
+    /**
+       Set the creator of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setCreator(const QString &val);
+
+    /**
+       The producer of the document
+    */
+    QString producer() const;
+
+    /**
+       Set the producer of the document to \param val
+
+       \returns true on success, false on failure
+    */
+    bool setProducer(const QString &val);
+
+    /**
+       Remove the document's Info dictionary
+
+       \returns true on success, false on failure
+    */
+    bool removeInfo();
+
+    /**
+       Obtain a list of the available string information keys.
+    */
+    QStringList infoKeys() const;
+
+    /**
+       Test if the document is encrypted
+    */
+    bool isEncrypted() const;
+
+    /**
+       Test if the document is linearised
+
+       In some cases, this is called "fast web view", since it
+       is mostly an optimisation for viewing over the Web.
+    */
+    bool isLinearized() const;
+
+    /**
+       Test if the permissions on the document allow it to be
+       printed
+    */
+    bool okToPrint() const;
+
+    /**
+       Test if the permissions on the document allow it to be
+       printed at high resolution
+    */
+    bool okToPrintHighRes() const;
+
+    /**
+       Test if the permissions on the document allow it to be
+       changed.
+
+       \note depending on the type of change, it may be more
+       appropriate to check other properties as well.
+    */
+    bool okToChange() const;
+
+    /**
+       Test if the permissions on the document allow the
+       contents to be copied / extracted
+    */
+    bool okToCopy() const;
+
+    /**
+       Test if the permissions on the document allow annotations
+       to be added or modified, and interactive form fields (including
+       signature fields) to be completed.
+    */
+    bool okToAddNotes() const;
+
+    /**
+       Test if the permissions on the document allow interactive
+       form fields (including signature fields) to be completed.
+
+       \note this can be true even if okToAddNotes() is false - this
+       means that only form completion is permitted.
+    */
+    bool okToFillForm() const;
+
+    /**
+       Test if the permissions on the document allow interactive
+       form fields (including signature fields) to be set, created and
+       modified
+    */
+    bool okToCreateFormFields() const;
+
+    /**
+       Test if the permissions on the document allow content extraction
+       (text and perhaps other content) for accessibility usage (eg for
+       a screen reader)
+    */
+    bool okToExtractForAccessibility() const;
+
+    /**
+       Test if the permissions on the document allow it to be
+       "assembled" - insertion, rotation and deletion of pages;
+       or creation of bookmarks and thumbnail images.
+
+       \note this can be true even if okToChange() is false
+    */
+    bool okToAssemble() const;
+
+    /**
+       The version of the PDF specification that the document
+       conforms to
+
+       \param major an optional pointer to a variable where store the
+       "major" number of the version
+       \param minor an optional pointer to a variable where store the
+       "minor" number of the version
+    */
+    void getPdfVersion(int *major, int *minor) const;
+
+    /**
+       The fonts within the PDF document.
+
+       This is a shorthand for getting all the fonts at once.
+
+       \note this can take a very long time to run with a large
+       document. You may wish to use a FontIterator if you have more
+       than say 20 pages
+
+       \see newFontIterator()
+    */
+    QList<FontInfo> fonts() const;
+
+    /**
+       Creates a new FontIterator object for font scanning.
+
+       The new iterator can be used for reading the font information of the
+       document, reading page by page.
+
+       The caller is responsible for the returned object, ie it should freed
+       it when no more useful.
+
+       \param startPage the initial page from which start reading fonts
+
+       \see fonts()
+    */
+    FontIterator *newFontIterator(int startPage = 0) const;
+
+    /**
+       The font data if the font is an embedded one.
+    */
+    QByteArray fontData(const FontInfo &fi) const;
+
+    /**
+       The documents embedded within the PDF document.
+
+       \note there are two types of embedded document - this call
+       only accesses documents that are embedded at the document level.
+    */
+    QList<EmbeddedFile *> embeddedFiles() const;
+
+    /**
+       Whether there are any documents embedded in this PDF document.
+    */
+    bool hasEmbeddedFiles() const;
+
+    /**
+      Gets the table of contents (TOC) of the Document.
+
+      The caller is responsible for the returned object.
+
+      In the tree the tag name is the 'screen' name of the entry. A tag can have
+      attributes. Here follows the list of tag attributes with meaning:
+      - Destination: A string description of the referred destination
+      - DestinationName: A 'named reference' to the viewport
+      - ExternalFileName: A link to a external filename
+      - Open: A bool value that tells whether the subbranch of the item is open or not
+
+      Resolving the final destination for each item can be done in the following way:
+      - first, checking for 'Destination': if not empty, then a LinkDestination
+        can be constructed straight with it
+      - as second step, if the 'DestinationName' is not empty, then the destination
+        can be resolved using linkDestination()
+
+      Note also that if 'ExternalFileName' is not emtpy, then the destination refers
+      to that document (and not to the current one).
+
+      \returns the TOC, or NULL if the Document does not have one
+    */
+    QDomDocument *toc() const;
+
+    /**
+       Gets the outline of the document
+
+       \returns a vector of outline items, empty if there are none
+    **/
+    QVector<OutlineItem> outline() const;
+
+    /**
+       Tries to resolve the named destination \p name.
+
+       \note this operation starts a search through the whole document
+
+       \returns a new LinkDestination object if the named destination was
+       actually found, or NULL otherwise
+    */
+    LinkDestination *linkDestination(const QString &name);
+
+    /**
+      Sets the paper color
+
+      \param color the new paper color
+     */
+    void setPaperColor(const QColor &color);
+    /**
+      The paper color
+
+      The default color is white.
+     */
+    QColor paperColor() const;
+
+    /**
+     Sets the backend used to render the pages.
+
+     \param backend the new rendering backend
+     */
+    void setRenderBackend(RenderBackend backend);
+    /**
+      The currently set render backend
+
+      The default backend is \ref SplashBackend
+     */
+    RenderBackend renderBackend() const;
+
+    /**
+      The available rendering backends.
+     */
+    static QSet<RenderBackend> availableRenderBackends();
+
+    /**
+     Sets the render \p hint .
+
+     \note some hints may not be supported by some rendering backends.
+
+     \param on whether the flag should be added or removed.
+     */
+    void setRenderHint(RenderHint hint, bool on = true);
+    /**
+      The currently set render hints.
+     */
+    RenderHints renderHints() const;
+
+    /**
+      Gets a new PS converter for this document.
+
+      The caller gets the ownership of the returned converter.
+     */
+    PSConverter *psConverter() const;
+
+    /**
+      Gets a new PDF converter for this document.
+
+      The caller gets the ownership of the returned converter.
+     */
+    PDFConverter *pdfConverter() const;
+
+    /**
+      Gets the metadata stream contents
+    */
+    QString metadata() const;
+
+    /**
+       Test whether this document has "optional content".
+
+       Optional content is used to optionally turn on (display)
+       and turn off (not display) some elements of the document.
+       The most common use of this is for layers in design
+       applications, but it can be used for a range of things,
+       such as not including some content in printing, and
+       displaying content in the appropriate language.
+    */
+    bool hasOptionalContent() const;
+
+    /**
+       Itemviews model for optional content.
+
+       The model is owned by the document.
+    */
+    OptContentModel *optionalContentModel();
+
+    /**
+       Document-level JavaScript scripts.
+
+       Returns the list of document level JavaScript scripts to be always
+       executed before any other script.
+    */
+    QStringList scripts() const;
+
+    /**
+       The PDF identifiers.
+
+       \param permanentId an optional pointer to a variable where store the
+       permanent ID of the document
+       \param updateId an optional pointer to a variable where store the
+       update ID of the document
+
+       \return whether the document has the IDs
+    */
+    bool getPdfId(QByteArray *permanentId, QByteArray *updateId) const;
+
+    /**
+       Returns the type of forms contained in the document
+    */
+    FormType formType() const;
+
+    /**
+       Returns the calculate order for forms (using their id)
+    */
+    QVector<int> formCalculateOrder() const;
+
+    /**
+     Returns the signatures of this document.
+
+     Prefer to use this over getting the signatures for all the pages of the document
+     since there are documents with signatures that don't belong to a given page
+    */
+    QVector<FormFieldSignature *> signatures() const;
+
+    /**
+       Destructor.
+    */
+    ~Document();
+
+private:
+    Q_DISABLE_COPY(Document)
+
+    DocumentData *m_doc;
+
+    Document(DocumentData *dataA);
+};
+
+class BaseConverterPrivate;
+class PSConverterPrivate;
+class PDFConverterPrivate;
+/**
+   \brief Base converter.
+
+   This is the base class for the converters.
+*/
+class POPPLER_QT6_EXPORT BaseConverter
+{
+    friend class Document;
+
+public:
+    /**
+      Destructor.
+    */
+    virtual ~BaseConverter();
+
+    /** Sets the output file name. You must set this or the output device. */
+    void setOutputFileName(const QString &outputFileName);
+
+    /**
+     * Sets the output device. You must set this or the output file name.
+     */
+    void setOutputDevice(QIODevice *device);
+
+    /**
+      Does the conversion.
+
+      \return whether the conversion succeeded
+    */
+    virtual bool convert() = 0;
+
+    enum Error
+    {
+        NoError,
+        FileLockedError,
+        OpenOutputError,
+        NotSupportedInputFileError
+    };
+
+    /**
+      Returns the last error
+    */
+    Error lastError() const;
+
+protected:
+    /// \cond PRIVATE
+    BaseConverter(BaseConverterPrivate &dd);
+    Q_DECLARE_PRIVATE(BaseConverter)
+    BaseConverterPrivate *d_ptr;
+    /// \endcond
+
+private:
+    Q_DISABLE_COPY(BaseConverter)
+};
+
+/**
+   Converts a PDF to PS
+
+   Sizes have to be in Points (1/72 inch)
+
+   If you are using QPrinter you can get paper size by doing:
+   \code
+QPrinter dummy(QPrinter::PrinterResolution);
+dummy.setFullPage(true);
+dummy.setPageSize(myPageSize);
+width = dummy.width();
+height = dummy.height();
+   \endcode
+*/
+class POPPLER_QT6_EXPORT PSConverter : public BaseConverter
+{
+    friend class Document;
+
+public:
+    /**
+      Options for the PS export.
+     */
+    enum PSOption
+    {
+        Printing = 0x00000001, ///< The PS is generated for printing purposes
+        StrictMargins = 0x00000002,
+        ForceRasterization = 0x00000004,
+        PrintToEPS = 0x00000008, ///< Output EPS instead of PS
+        HideAnnotations = 0x00000010 ///< Don't print annotations
+    };
+    Q_DECLARE_FLAGS(PSOptions, PSOption)
+
+    /**
+      Destructor.
+    */
+    ~PSConverter() override;
+
+    /** Sets the list of pages to print. Mandatory. */
+    void setPageList(const QList<int> &pageList);
+
+    /**
+      Sets the title of the PS Document. Optional
+    */
+    void setTitle(const QString &title);
+
+    /**
+      Sets the horizontal DPI. Defaults to 72.0
+    */
+    void setHDPI(double hDPI);
+
+    /**
+      Sets the vertical DPI. Defaults to 72.0
+    */
+    void setVDPI(double vDPI);
+
+    /**
+      Sets the rotate. Defaults to not rotated
+    */
+    void setRotate(int rotate);
+
+    /**
+      Sets the output paper width. Has to be set.
+    */
+    void setPaperWidth(int paperWidth);
+
+    /**
+      Sets the output paper height. Has to be set.
+    */
+    void setPaperHeight(int paperHeight);
+
+    /**
+      Sets the output right margin. Defaults to 0
+    */
+    void setRightMargin(int marginRight);
+
+    /**
+      Sets the output bottom margin. Defaults to 0
+    */
+    void setBottomMargin(int marginBottom);
+
+    /**
+      Sets the output left margin. Defaults to 0
+    */
+    void setLeftMargin(int marginLeft);
+
+    /**
+      Sets the output top margin. Defaults to 0
+    */
+    void setTopMargin(int marginTop);
+
+    /**
+      Defines if margins have to be strictly followed (even if that
+      means changing aspect ratio), or if the margins can be adapted
+      to keep aspect ratio.
+
+      Defaults to false.
+    */
+    void setStrictMargins(bool strictMargins);
+
+    /** Defines if the page will be rasterized to an image before printing. Defaults to false */
+    void setForceRasterize(bool forceRasterize);
+
+    /**
+      Sets the options for the PS export.
+     */
+    void setPSOptions(PSOptions options);
+
+    /**
+      The currently set options for the PS export.
+
+      The default flags are: Printing.
+     */
+    PSOptions psOptions() const;
+
+    /**
+      Sets a function that will be called each time a page is converted.
+
+      The payload belongs to the caller.
+     */
+    void setPageConvertedCallback(void (*callback)(int page, void *payload), void *payload);
+
+    bool convert() override;
+
+private:
+    Q_DECLARE_PRIVATE(PSConverter)
+    Q_DISABLE_COPY(PSConverter)
+
+    PSConverter(DocumentData *document);
+};
+
+/**
+   Converts a PDF to PDF (thus saves a copy of the document).
+*/
+class POPPLER_QT6_EXPORT PDFConverter : public BaseConverter
+{
+    friend class Document;
+
+public:
+    /**
+      Options for the PDF export.
+     */
+    enum PDFOption
+    {
+        WithChanges = 0x00000001 ///< The changes done to the document are saved as well
+    };
+    Q_DECLARE_FLAGS(PDFOptions, PDFOption)
+
+    /**
+      Destructor.
+    */
+    ~PDFConverter() override;
+
+    /**
+      Sets the options for the PDF export.
+     */
+    void setPDFOptions(PDFOptions options);
+    /**
+      The currently set options for the PDF export.
+     */
+    PDFOptions pdfOptions() const;
+
+    bool convert() override;
+
+private:
+    Q_DECLARE_PRIVATE(PDFConverter)
+    Q_DISABLE_COPY(PDFConverter)
+
+    PDFConverter(DocumentData *document);
+};
+
+/**
+   Conversion from PDF date string format to QDateTime
+*/
+POPPLER_QT6_EXPORT QDateTime convertDate(const char *dateString);
+
+/**
+   Whether the color management functions are available.
+*/
+POPPLER_QT6_EXPORT bool isCmsAvailable();
+
+/**
+   Whether the overprint preview functionality is available.
+*/
+POPPLER_QT6_EXPORT bool isOverprintPreviewAvailable();
+
+class SoundData;
+/**
+   Container class for a sound file in a PDF document.
+
+    A sound can be either External (in that case should be loaded the file
+   whose url is represented by url() ), or Embedded, and the player has to
+   play the data contained in data().
+*/
+class POPPLER_QT6_EXPORT SoundObject
+{
+public:
+    /**
+       The type of sound
+    */
+    enum SoundType
+    {
+        External, ///< The real sound file is external
+        Embedded ///< The sound is contained in the data
+    };
+
+    /**
+       The encoding format used for the sound
+    */
+    enum SoundEncoding
+    {
+        Raw, ///< Raw encoding, with unspecified or unsigned values in the range [ 0, 2^B - 1 ]
+        Signed, ///< Twos-complement values
+        muLaw, ///< mu-law-encoded samples
+        ALaw ///< A-law-encoded samples
+    };
+
+    /// \cond PRIVATE
+    SoundObject(Sound *popplersound);
+    /// \endcond
+
+    ~SoundObject();
+
+    /**
+       Is the sound embedded (SoundObject::Embedded) or external (SoundObject::External)?
+    */
+    SoundType soundType() const;
+
+    /**
+       The URL of the sound file to be played, in case of SoundObject::External
+    */
+    QString url() const;
+
+    /**
+       The data of the sound, in case of SoundObject::Embedded
+    */
+    QByteArray data() const;
+
+    /**
+       The sampling rate of the sound
+    */
+    double samplingRate() const;
+
+    /**
+       The number of sound channels to use to play the sound
+    */
+    int channels() const;
+
+    /**
+       The number of bits per sample value per channel
+    */
+    int bitsPerSample() const;
+
+    /**
+       The encoding used for the sound
+    */
+    SoundEncoding soundEncoding() const;
+
+private:
+    Q_DISABLE_COPY(SoundObject)
+
+    SoundData *m_soundData;
+};
+
+class MovieData;
+/**
+   Container class for a movie object in a PDF document.
+*/
+class POPPLER_QT6_EXPORT MovieObject
+{
+    friend class AnnotationPrivate;
+
+public:
+    /**
+       The play mode for playing the movie
+    */
+    enum PlayMode
+    {
+        PlayOnce, ///< Play the movie once, closing the movie controls at the end
+        PlayOpen, ///< Like PlayOnce, but leaving the controls open
+        PlayRepeat, ///< Play continuously until stopped
+        PlayPalindrome ///< Play forward, then backward, then again foward and so on until stopped
+    };
+
+    ~MovieObject();
+
+    /**
+       The URL of the movie to be played
+    */
+    QString url() const;
+
+    /**
+       The size of the movie
+    */
+    QSize size() const;
+
+    /**
+       The rotation (either 0, 90, 180, or 270 degrees clockwise) for the movie,
+    */
+    int rotation() const;
+
+    /**
+       Whether show a bar with movie controls
+    */
+    bool showControls() const;
+
+    /**
+       How to play the movie
+    */
+    PlayMode playMode() const;
+
+    /**
+       Returns whether a poster image should be shown if the movie is not playing.
+    */
+    bool showPosterImage() const;
+
+    /**
+       Returns the poster image that should be shown if the movie is not playing.
+       If the image is null but showImagePoster() returns @c true, the first frame of the movie
+       should be used as poster image.
+    */
+    QImage posterImage() const;
+
+private:
+    /// \cond PRIVATE
+    MovieObject(AnnotMovie *ann);
+    /// \endcond
+
+    Q_DISABLE_COPY(MovieObject)
+
+    MovieData *m_movieData;
+};
+
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Poppler::Page::PainterFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(Poppler::Page::SearchFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(Poppler::Document::RenderHints)
+Q_DECLARE_OPERATORS_FOR_FLAGS(Poppler::PDFConverter::PDFOptions)
+Q_DECLARE_OPERATORS_FOR_FLAGS(Poppler::PSConverter::PSOptions)
+
+#endif
diff --git a/qt6/src/poppler-sound.cc b/qt6/src/poppler-sound.cc
new file mode 100644
index 0000000..eee2a22
--- /dev/null
+++ b/qt6/src/poppler-sound.cc
@@ -0,0 +1,125 @@
+/* poppler-sound.cc: qt interface to poppler
+ * Copyright (C) 2006-2007, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2008, 2018, 2020, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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 "Object.h"
+#include "Stream.h"
+#include "Sound.h"
+
+namespace Poppler {
+
+class SoundData
+{
+public:
+    SoundData() : m_soundObj(nullptr) { }
+
+    ~SoundData() { delete m_soundObj; }
+
+    SoundData(const SoundData &) = delete;
+    SoundData &operator=(const SoundData &) = delete;
+
+    SoundObject::SoundType m_type;
+    Sound *m_soundObj;
+};
+
+SoundObject::SoundObject(Sound *popplersound)
+{
+    m_soundData = new SoundData();
+    switch (popplersound->getSoundKind()) {
+    case soundEmbedded:
+        m_soundData->m_type = SoundObject::Embedded;
+        break;
+    case soundExternal:
+    default:
+        m_soundData->m_type = SoundObject::External;
+        break;
+    }
+
+    m_soundData->m_soundObj = popplersound->copy();
+}
+
+SoundObject::~SoundObject()
+{
+    delete m_soundData;
+}
+
+SoundObject::SoundType SoundObject::soundType() const
+{
+    return m_soundData->m_type;
+}
+
+QString SoundObject::url() const
+{
+    if (m_soundData->m_type != SoundObject::External)
+        return QString();
+
+    return QString(m_soundData->m_soundObj->getFileName().c_str());
+}
+
+QByteArray SoundObject::data() const
+{
+    if (m_soundData->m_type != SoundObject::Embedded)
+        return QByteArray();
+
+    Stream *stream = m_soundData->m_soundObj->getStream();
+    stream->reset();
+    int dataLen = 0;
+    QByteArray fileArray;
+    int i;
+    while ((i = stream->getChar()) != EOF) {
+        fileArray[dataLen] = (char)i;
+        ++dataLen;
+    }
+    fileArray.resize(dataLen);
+
+    return fileArray;
+}
+
+double SoundObject::samplingRate() const
+{
+    return m_soundData->m_soundObj->getSamplingRate();
+}
+
+int SoundObject::channels() const
+{
+    return m_soundData->m_soundObj->getChannels();
+}
+
+int SoundObject::bitsPerSample() const
+{
+    return m_soundData->m_soundObj->getBitsPerSample();
+}
+
+SoundObject::SoundEncoding SoundObject::soundEncoding() const
+{
+    switch (m_soundData->m_soundObj->getEncoding()) {
+    case soundRaw:
+        return SoundObject::Raw;
+    case soundSigned:
+        return SoundObject::Signed;
+    case soundMuLaw:
+        return SoundObject::muLaw;
+    case soundALaw:
+        return SoundObject::ALaw;
+    }
+    return SoundObject::Raw;
+}
+
+}
diff --git a/qt6/src/poppler-textbox.cc b/qt6/src/poppler-textbox.cc
new file mode 100644
index 0000000..98ec004
--- /dev/null
+++ b/qt6/src/poppler-textbox.cc
@@ -0,0 +1,63 @@
+/* poppler-qt.h: qt interface to poppler
+ * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
+ * Copyright (C) 2006-2008, Albert Astals Cid <aacid@kde.org>
+ * Copyright (C) 2008, Pino Toscano <pino@kde.org>
+ *
+ * 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 "poppler-private.h"
+
+namespace Poppler {
+
+TextBox::TextBox(const QString &text, const QRectF &bBox)
+{
+    m_data = new TextBoxData();
+    m_data->text = text;
+    m_data->bBox = bBox;
+}
+
+TextBox::~TextBox()
+{
+    delete m_data;
+}
+
+QString TextBox::text() const
+{
+    return m_data->text;
+}
+
+QRectF TextBox::boundingBox() const
+{
+    return m_data->bBox;
+}
+
+TextBox *TextBox::nextWord() const
+{
+    return m_data->nextWord;
+}
+
+QRectF TextBox::charBoundingBox(int i) const
+{
+    return m_data->charBBoxes.value(i);
+}
+
+bool TextBox::hasSpaceAfter() const
+{
+    return m_data->hasSpaceAfter;
+}
+
+}
diff --git a/qt6/src/poppler-version.cpp b/qt6/src/poppler-version.cpp
new file mode 100644
index 0000000..015267c
--- /dev/null
+++ b/qt6/src/poppler-version.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2009-2010, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2018, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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-version.h"
+
+QString Poppler::Version::string()
+{
+    return QStringLiteral(POPPLER_VERSION);
+}
+
+unsigned int Poppler::Version::major()
+{
+    return POPPLER_VERSION_MAJOR;
+}
+
+unsigned int Poppler::Version::minor()
+{
+    return POPPLER_VERSION_MINOR;
+}
+
+unsigned int Poppler::Version::micro()
+{
+    return POPPLER_VERSION_MICRO;
+}
diff --git a/qt6/src/poppler-version.h.in b/qt6/src/poppler-version.h.in
new file mode 100644
index 0000000..a60a62e
--- /dev/null
+++ b/qt6/src/poppler-version.h.in
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2018, 2019, Albert Astals Cid <aacid@kde.org>
+ *
+ * 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.
+ */
+
+#ifndef POPPLER_VERSION_H
+#define POPPLER_VERSION_H
+
+#include "poppler-export.h"
+
+#include <QString>
+
+// glibc < 2.28 used to include sys/sysmacros.h
+// from sys/types.h and sysmacros.h defines minor and major so
+// undefine them. You may need to undefine them in your code too.
+#undef minor
+#undef major
+
+#define POPPLER_VERSION "@POPPLER_VERSION@"
+#define POPPLER_VERSION_MAJOR @POPPLER_MAJOR_VERSION@
+#define POPPLER_VERSION_MINOR @POPPLER_MINOR_VERSION@
+#define POPPLER_VERSION_MICRO @POPPLER_MICRO_VERSION@
+
+namespace Poppler
+{
+
+namespace Version
+{
+
+/**
+ \returns the version string of the current poppler-qt6 library
+ */
+POPPLER_QT6_EXPORT QString string();
+
+/**
+ \returns the "major" number of the version of the current poppler-qt6 library
+ */
+POPPLER_QT6_EXPORT unsigned int major();
+
+/**
+ \returns the "minor" number of the version of the current poppler-qt6 library
+ */
+POPPLER_QT6_EXPORT unsigned int minor();
+
+/**
+ \returns the "micro" number of the version of the current poppler-qt6 library
+ */
+POPPLER_QT6_EXPORT unsigned int micro();
+
+}
+
+}
+
+#endif
diff --git a/qt6/tests/.gitignore b/qt6/tests/.gitignore
new file mode 100644
index 0000000..014ba9a
--- /dev/null
+++ b/qt6/tests/.gitignore
@@ -0,0 +1,33 @@
+.deps
+.libs
+*.la
+*.lo
+*.moc
+Makefile
+Makefile.in
+stress-poppler-qt6
+stress-poppler-dir
+test-poppler-qt6
+test-password-qt6
+poppler-attachments
+poppler-fonts
+poppler-texts
+poppler-forms
+stress-threads-qt6
+test-render-to-file
+check_actualtext
+check_attachments
+check_dateConversion
+check_fonts
+check_goostring
+check_lexer
+check_links
+check_metadata
+check_optcontent
+check_permissions
+check_pagelayout
+check_pagemode
+check_password
+check_search
+check_strings
+
diff --git a/qt6/tests/CMakeLists.txt b/qt6/tests/CMakeLists.txt
new file mode 100644
index 0000000..96f10e1
--- /dev/null
+++ b/qt6/tests/CMakeLists.txt
@@ -0,0 +1,69 @@
+add_definitions(-DTESTDATADIR=\"${TESTDATADIR}\")
+
+include_directories(
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/../src
+  ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+macro(QT6_ADD_SIMPLETEST exe source)
+  string(REPLACE "-" "" test_name ${exe})
+  set(${test_name}_SOURCES
+    ${source}
+  )
+  poppler_add_test(${exe} BUILD_QT6_TESTS ${${test_name}_SOURCES})
+  target_link_libraries(${exe} poppler-qt6 Qt6::Widgets)
+  if(MSVC)
+    target_link_libraries(${exe} poppler ${poppler_LIBS})
+  endif()
+endmacro()
+
+macro(QT6_ADD_QTEST exe source)
+    string(REPLACE "-" "" test_name ${exe})
+    set(${test_name}_SOURCES
+      ${source}
+    )
+    poppler_add_unittest(${exe} BUILD_QT6_TESTS ${${test_name}_SOURCES})
+    target_link_libraries(${exe} poppler-qt6 Qt6::Widgets Qt6::Test Qt6::Gui)
+    if(MSVC)
+      target_link_libraries(${exe} poppler ${poppler_LIBS})
+    endif()
+endmacro()
+
+
+qt6_add_simpletest(test-poppler-qt6 test-poppler-qt6.cpp)
+qt6_add_simpletest(test-password-qt6 test-password-qt6.cpp)
+qt6_add_simpletest(test-render-to-file-qt6 test-render-to-file.cpp)
+qt6_add_simpletest(poppler-qt6-forms poppler-forms.cpp)
+qt6_add_simpletest(poppler-qt6-fonts poppler-fonts.cpp)
+qt6_add_simpletest(poppler-qt6-attachments poppler-attachments.cpp)
+qt6_add_simpletest(stress-poppler-qt6 stress-poppler-qt6.cpp)
+qt6_add_simpletest(stress-poppler-dir-qt6 stress-poppler-dir.cpp)
+qt6_add_simpletest(stress-threads-qt6 stress-threads-qt6.cpp)
+qt6_add_simpletest(poppler-qt6-texts poppler-texts.cpp)
+qt6_add_simpletest(poppler-qt6-page-labels poppler-page-labels.cpp)
+
+qt6_add_qtest(check_qt6_attachments check_attachments.cpp)
+qt6_add_qtest(check_qt6_dateConversion check_dateConversion.cpp)
+qt6_add_qtest(check_qt6_fonts check_fonts.cpp)
+qt6_add_qtest(check_qt6_links check_links.cpp)
+qt6_add_qtest(check_qt6_annotations check_annotations.cpp)
+qt6_add_qtest(check_qt6_metadata check_metadata.cpp)
+qt6_add_qtest(check_qt6_optcontent check_optcontent.cpp)
+qt6_add_qtest(check_qt6_forms check_forms.cpp)
+qt6_add_qtest(check_qt6_pagelayout check_pagelayout.cpp)
+qt6_add_qtest(check_qt6_pagemode check_pagemode.cpp)
+qt6_add_qtest(check_qt6_password check_password.cpp)
+qt6_add_qtest(check_qt6_permissions check_permissions.cpp)
+qt6_add_qtest(check_qt6_search check_search.cpp)
+qt6_add_qtest(check_qt6_actualtext check_actualtext.cpp)
+qt6_add_qtest(check_qt6_lexer check_lexer.cpp)
+qt6_add_qtest(check_qt6_goostring check_goostring.cpp)
+qt6_add_qtest(check_qt6_object check_object.cpp)
+qt6_add_qtest(check_qt6_stroke_opacity check_stroke_opacity.cpp)
+qt6_add_qtest(check_qt6_utf_conversion check_utf_conversion.cpp)
+qt6_add_qtest(check_qt6_outline check_outline.cpp)
+if (NOT WIN32)
+  qt6_add_qtest(check_qt6_pagelabelinfo check_pagelabelinfo.cpp)
+  qt6_add_qtest(check_qt6_strings check_strings.cpp)
+endif ()
diff --git a/qt6/tests/README.unittest b/qt6/tests/README.unittest
new file mode 100644
index 0000000..4647c7d
--- /dev/null
+++ b/qt6/tests/README.unittest
@@ -0,0 +1,23 @@
+The unittests for the Qt6 bindings rely on the QtTestLib package, and
+will not be built until this is installed. If you do not have it, then
+you can download it from the Trolltech website.
+
+Note that there are a range of ways in which you can run the tests:
+1. "make check" will run all the tests.
+2. You can run a single test by executing the applicable
+executable. For example, you can run the PageMode tests by
+"./check_pagemode"
+3. You can run a single function within a single test by appending the
+name of the function to the executable. For example, if you just want
+to run the FullScreen test within the PageMode tests, you can
+"./check_pagemode checkFullScreen". Run the executable with -functions
+to get a list of all the functions.
+4. You can run a single function  with specific data by appending the
+name of the function, followed by a colon, then the data label to the
+executable. For example, to just do the Author check within the
+metadata checks, you can "./check_metadata checkStrings:Author".
+
+For a full list of options, run a executable with "-help".
+
+Brad Hards
+bradh@frogmouth.net
diff --git a/qt6/tests/check_actualtext.cpp b/qt6/tests/check_actualtext.cpp
new file mode 100644
index 0000000..277db51
--- /dev/null
+++ b/qt6/tests/check_actualtext.cpp
@@ -0,0 +1,57 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+#include <QtCore/QFile>
+
+class TestActualText : public QObject
+{
+    Q_OBJECT
+public:
+    TestActualText(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkActualText1();
+    void checkActualText2();
+
+private:
+    void checkActualText(Poppler::Document *doc);
+};
+
+void TestActualText::checkActualText(Poppler::Document *doc)
+{
+    Poppler::Page *page = doc->page(0);
+    QVERIFY(page);
+
+    QCOMPARE(page->text(QRectF()), QLatin1String("The slow brown fox jumps over the black dog."));
+
+    delete page;
+}
+
+void TestActualText::checkActualText1()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/WithActualText.pdf");
+    QVERIFY(doc);
+
+    checkActualText(doc);
+
+    delete doc;
+}
+
+void TestActualText::checkActualText2()
+{
+    QFile file(TESTDATADIR "/unittestcases/WithActualText.pdf");
+    QVERIFY(file.open(QIODevice::ReadOnly));
+
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(&file);
+    QVERIFY(doc);
+
+    checkActualText(doc);
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestActualText)
+
+#include "check_actualtext.moc"
diff --git a/qt6/tests/check_annotations.cpp b/qt6/tests/check_annotations.cpp
new file mode 100644
index 0000000..68e35a3
--- /dev/null
+++ b/qt6/tests/check_annotations.cpp
@@ -0,0 +1,194 @@
+#include <cmath>
+#include <memory>
+#include <sstream>
+
+#include <QtTest/QtTest>
+#include <QTemporaryFile>
+
+#include <poppler-qt6.h>
+
+#include "poppler/Annot.h"
+#include "goo/GooString.h"
+#include "goo/gstrtod.h"
+
+class TestAnnotations : public QObject
+{
+    Q_OBJECT
+public:
+    TestAnnotations(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkQColorPrecision();
+    void checkFontSizeAndColor();
+    void checkHighlightFromAndToQuads();
+    void checkUTF16LEAnnot();
+    void checkNonMarkupAnnotations();
+    void checkDefaultAppearance();
+};
+
+/* Is .5f sufficient for 16 bit color channel roundtrip trough save and load on all architectures? */
+void TestAnnotations::checkQColorPrecision()
+{
+    bool precisionOk = true;
+    for (int i = std::numeric_limits<uint16_t>::min(); i <= std::numeric_limits<uint16_t>::max(); i++) {
+        double normalized = static_cast<uint16_t>(i) / static_cast<double>(std::numeric_limits<uint16_t>::max());
+        GooString *serialized = GooString::format("{0:.5f}", normalized);
+        double deserialized = gatof(serialized->c_str());
+        delete serialized;
+        uint16_t denormalized = std::round(deserialized * std::numeric_limits<uint16_t>::max());
+        if (static_cast<uint16_t>(i) != denormalized) {
+            precisionOk = false;
+            break;
+        }
+    }
+    QVERIFY(precisionOk);
+}
+
+void TestAnnotations::checkFontSizeAndColor()
+{
+    const QString contents = QStringLiteral("foobar");
+    const std::vector<QColor> testColors { QColor::fromRgb(0xAB, 0xCD, 0xEF), QColor::fromCmyk(0xAB, 0xBC, 0xCD, 0xDE) };
+    const QFont testFont(QStringLiteral("Helvetica"), 20);
+
+    QTemporaryFile tempFile;
+    QVERIFY(tempFile.open());
+    tempFile.close();
+
+    {
+        std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/UseNone.pdf") };
+        QVERIFY(doc.get());
+
+        std::unique_ptr<Poppler::Page> page { doc->page(0) };
+        QVERIFY(page.get());
+
+        for (const auto &color : testColors) {
+            auto annot = std::make_unique<Poppler::TextAnnotation>(Poppler::TextAnnotation::InPlace);
+            annot->setBoundary(QRectF(0.0, 0.0, 1.0, 1.0));
+            annot->setContents(contents);
+            annot->setTextFont(testFont);
+            annot->setTextColor(color);
+            page->addAnnotation(annot.get());
+        }
+
+        std::unique_ptr<Poppler::PDFConverter> conv(doc->pdfConverter());
+        QVERIFY(conv.get());
+        conv->setOutputFileName(tempFile.fileName());
+        conv->setPDFOptions(Poppler::PDFConverter::WithChanges);
+        QVERIFY(conv->convert());
+    }
+
+    {
+        std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(tempFile.fileName()) };
+        QVERIFY(doc.get());
+
+        std::unique_ptr<Poppler::Page> page { doc->page(0) };
+        QVERIFY(page.get());
+
+        auto annots = page->annotations();
+        QCOMPARE(annots.size(), static_cast<int>(testColors.size()));
+
+        auto &&annot = annots.constBegin();
+        for (const auto &color : testColors) {
+            QCOMPARE((*annot)->subType(), Poppler::Annotation::AText);
+            auto textAnnot = static_cast<Poppler::TextAnnotation *>(*annot);
+            QCOMPARE(textAnnot->contents(), contents);
+            QCOMPARE(textAnnot->textFont().pointSize(), testFont.pointSize());
+            QCOMPARE(static_cast<int>(textAnnot->textColor().spec()), static_cast<int>(color.spec()));
+            QCOMPARE(textAnnot->textColor(), color);
+            if (annot != annots.constEnd())
+                ++annot;
+        }
+        qDeleteAll(annots);
+    }
+}
+
+namespace Poppler {
+static bool operator==(const Poppler::HighlightAnnotation::Quad &a, const Poppler::HighlightAnnotation::Quad &b)
+{
+    // FIXME We do not compare capStart, capEnd and feather since AnnotQuadrilaterals doesn't contain that info and thus
+    //       HighlightAnnotationPrivate::fromQuadrilaterals uses default values
+    return a.points[0] == b.points[0] && a.points[1] == b.points[1] && a.points[2] == b.points[2] && a.points[3] == b.points[3];
+}
+}
+
+void TestAnnotations::checkHighlightFromAndToQuads()
+{
+    std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/UseNone.pdf") };
+
+    std::unique_ptr<Poppler::Page> page { doc->page(0) };
+
+    auto ha = std::make_unique<Poppler::HighlightAnnotation>();
+    page->addAnnotation(ha.get());
+
+    const QList<Poppler::HighlightAnnotation::Quad> quads = { { { { 0, 0.1 }, { 0.2, 0.3 }, { 0.4, 0.5 }, { 0.6, 0.7 } }, false, false, 0 }, { { { 0.8, 0.9 }, { 0.1, 0.2 }, { 0.3, 0.4 }, { 0.5, 0.6 } }, true, false, 0.4 } };
+    ha->setHighlightQuads(quads);
+    QCOMPARE(ha->highlightQuads(), quads);
+}
+
+void TestAnnotations::checkUTF16LEAnnot()
+{
+    std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/utf16le-annot.pdf") };
+    QVERIFY(doc.get());
+
+    std::unique_ptr<Poppler::Page> page { doc->page(0) };
+    QVERIFY(page.get());
+
+    auto annots = page->annotations();
+    QCOMPARE(annots.size(), 2);
+
+    auto annot = annots[1];
+    QCOMPARE(annot->contents(), QString::fromUtf8("Únîcödé豰")); // clazy:exclude=qstring-allocations
+}
+
+void TestAnnotations::checkNonMarkupAnnotations()
+{
+    std::unique_ptr<Poppler::Document> doc { Poppler::Document::load(TESTDATADIR "/unittestcases/checkbox_issue_159.pdf") };
+    QVERIFY(doc.get());
+
+    std::unique_ptr<Poppler::Page> page { doc->page(0) };
+    QVERIFY(page.get());
+
+    auto annots = page->annotations();
+    QCOMPARE(annots.size(), 17);
+}
+
+void TestAnnotations::checkDefaultAppearance()
+{
+    std::unique_ptr<GooString> roundtripString;
+    {
+        GooString daString { "/Helv 10 Tf 0.1 0.2 0.3 rg" };
+        const DefaultAppearance da { &daString };
+        QCOMPARE(da.getFontPtSize(), 10.);
+        QVERIFY(da.getFontName().isName());
+        QCOMPARE(da.getFontName().getName(), "Helv");
+        const AnnotColor *color = da.getFontColor();
+        QVERIFY(color);
+        QCOMPARE(color->getSpace(), AnnotColor::colorRGB);
+        QCOMPARE(color->getValues()[0], 0.1);
+        QCOMPARE(color->getValues()[1], 0.2);
+        QCOMPARE(color->getValues()[2], 0.3);
+        roundtripString.reset(da.toAppearanceString());
+    }
+    {
+        /* roundtrip through parse/generate/parse shall preserve values */
+        const DefaultAppearance da { roundtripString.get() };
+        QCOMPARE(da.getFontPtSize(), 10.);
+        QVERIFY(da.getFontName().isName());
+        QCOMPARE(da.getFontName().getName(), "Helv");
+        const AnnotColor *color = da.getFontColor();
+        QVERIFY(color);
+        QCOMPARE(color->getSpace(), AnnotColor::colorRGB);
+        QCOMPARE(color->getValues()[0], 0.1);
+        QCOMPARE(color->getValues()[1], 0.2);
+        QCOMPARE(color->getValues()[2], 0.3);
+    }
+    {
+        /* parsing bad DA strings must not cause crash */
+        GooString daString { "/ % Tf 1 2 rg" };
+        const DefaultAppearance da { &daString };
+        QVERIFY(!da.getFontName().isName());
+    }
+}
+
+QTEST_GUILESS_MAIN(TestAnnotations)
+
+#include "check_annotations.moc"
diff --git a/qt6/tests/check_attachments.cpp b/qt6/tests/check_attachments.cpp
new file mode 100644
index 0000000..077ed5e
--- /dev/null
+++ b/qt6/tests/check_attachments.cpp
@@ -0,0 +1,154 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+#include <QtCore/QFile>
+
+class TestAttachments : public QObject
+{
+    Q_OBJECT
+public:
+    TestAttachments(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkNoAttachments();
+    void checkAttach1();
+    void checkAttach2();
+    void checkAttach3();
+    void checkAttach4();
+};
+
+void TestAttachments::checkNoAttachments()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->hasEmbeddedFiles(), false);
+
+    delete doc;
+}
+
+void TestAttachments::checkAttach1()
+{
+
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/WithAttachments.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasEmbeddedFiles());
+
+    QList<Poppler::EmbeddedFile *> fileList = doc->embeddedFiles();
+    QCOMPARE(fileList.size(), 2);
+
+    Poppler::EmbeddedFile *embfile = fileList.at(0);
+    QCOMPARE(embfile->name(), QLatin1String("kroller.png"));
+    QCOMPARE(embfile->description(), QString());
+    QCOMPARE(embfile->createDate(), QDateTime(QDate(), QTime()));
+    QCOMPARE(embfile->modDate(), QDateTime(QDate(), QTime()));
+    QCOMPARE(embfile->mimeType(), QString());
+
+    QFile file(TESTDATADIR "/unittestcases/kroller.png");
+    QVERIFY(file.open(QIODevice::ReadOnly));
+    QByteArray krollerData = file.readAll();
+    QByteArray embdata = embfile->data();
+    QCOMPARE(krollerData, embdata);
+
+    Poppler::EmbeddedFile *embfile2 = fileList.at(1);
+    QCOMPARE(embfile2->name(), QLatin1String("gnome-64.gif"));
+    QCOMPARE(embfile2->description(), QString());
+    QCOMPARE(embfile2->modDate(), QDateTime(QDate(), QTime()));
+    QCOMPARE(embfile2->createDate(), QDateTime(QDate(), QTime()));
+    QCOMPARE(embfile2->mimeType(), QString());
+
+    QFile file2(TESTDATADIR "/unittestcases/gnome-64.gif");
+    QVERIFY(file2.open(QIODevice::ReadOnly));
+    QByteArray g64Data = file2.readAll();
+    QByteArray emb2data = embfile2->data();
+    QCOMPARE(g64Data, emb2data);
+
+    delete doc;
+}
+
+void TestAttachments::checkAttach2()
+{
+
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/A6EmbeddedFiles.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasEmbeddedFiles());
+
+    QList<Poppler::EmbeddedFile *> fileList;
+    fileList = doc->embeddedFiles();
+    QCOMPARE(fileList.size(), 3);
+
+    Poppler::EmbeddedFile *embfile1 = fileList.at(0);
+    QCOMPARE(embfile1->name(), QLatin1String("Acro7 thoughts"));
+    QCOMPARE(embfile1->description(), QString());
+    QCOMPARE(embfile1->createDate(), QDateTime(QDate(2003, 8, 4), QTime(13, 54, 54), Qt::UTC));
+    QCOMPARE(embfile1->modDate(), QDateTime(QDate(2003, 8, 4), QTime(14, 15, 27), Qt::UTC));
+    QCOMPARE(embfile1->mimeType(), QLatin1String("text/xml"));
+
+    Poppler::EmbeddedFile *embfile2 = fileList.at(1);
+    QCOMPARE(embfile2->name(), QLatin1String("acro transitions 1.xls"));
+    QCOMPARE(embfile2->description(), QString());
+    QCOMPARE(embfile2->createDate(), QDateTime(QDate(2003, 7, 18), QTime(21, 7, 16), Qt::UTC));
+    QCOMPARE(embfile2->modDate(), QDateTime(QDate(2003, 7, 22), QTime(13, 4, 40), Qt::UTC));
+    QCOMPARE(embfile2->mimeType(), QLatin1String("application/excel"));
+
+    Poppler::EmbeddedFile *embfile3 = fileList.at(2);
+    QCOMPARE(embfile3->name(), QLatin1String("apago_pdfe_wide.gif"));
+    QCOMPARE(embfile3->description(), QString());
+    QCOMPARE(embfile3->createDate(), QDateTime(QDate(2003, 1, 31), QTime(15, 54, 29), Qt::UTC));
+    QCOMPARE(embfile3->modDate(), QDateTime(QDate(2003, 1, 31), QTime(15, 52, 58), Qt::UTC));
+    QCOMPARE(embfile3->mimeType(), QString());
+
+    delete doc;
+}
+
+void TestAttachments::checkAttach3()
+{
+
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/shapes+attachments.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasEmbeddedFiles());
+
+    QList<Poppler::EmbeddedFile *> fileList;
+    fileList = doc->embeddedFiles();
+    QCOMPARE(fileList.size(), 1);
+
+    Poppler::EmbeddedFile *embfile = fileList.at(0);
+    QCOMPARE(embfile->name(), QLatin1String("ADEX1.xpdf.pgp"));
+    QCOMPARE(embfile->description(), QString());
+    QCOMPARE(embfile->createDate(), QDateTime(QDate(2004, 3, 29), QTime(19, 37, 16), Qt::UTC));
+    QCOMPARE(embfile->modDate(), QDateTime(QDate(2004, 3, 29), QTime(19, 37, 16), Qt::UTC));
+    QCOMPARE(embfile->mimeType(), QString());
+    delete doc;
+}
+
+void TestAttachments::checkAttach4()
+{
+
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/imageretrieve+attachment.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasEmbeddedFiles());
+
+    QList<Poppler::EmbeddedFile *> fileList;
+    fileList = doc->embeddedFiles();
+    QCOMPARE(fileList.size(), 1);
+
+    Poppler::EmbeddedFile *embfile = fileList.at(0);
+    QCOMPARE(embfile->name(), QLatin1String("export-altona.csv"));
+    QCOMPARE(embfile->description(), QLatin1String("Altona Export"));
+    QCOMPARE(embfile->createDate(), QDateTime(QDate(2005, 8, 30), QTime(20, 49, 35), Qt::UTC));
+    QCOMPARE(embfile->modDate(), QDateTime(QDate(2005, 8, 30), QTime(20, 49, 52), Qt::UTC));
+    QCOMPARE(embfile->mimeType(), QLatin1String("application/vnd.ms-excel"));
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestAttachments)
+#include "check_attachments.moc"
diff --git a/qt6/tests/check_dateConversion.cpp b/qt6/tests/check_dateConversion.cpp
new file mode 100644
index 0000000..69be172
--- /dev/null
+++ b/qt6/tests/check_dateConversion.cpp
@@ -0,0 +1,104 @@
+#include <QtTest/QtTest>
+
+Q_DECLARE_METATYPE(QDate)
+Q_DECLARE_METATYPE(QTime)
+
+#include <poppler-qt6.h>
+
+class TestDateConv : public QObject
+{
+    Q_OBJECT
+public:
+    TestDateConv(QObject *parent = nullptr) : QObject(parent) { }
+
+private slots:
+    void initTestCase();
+    void checkDates_data();
+    void checkDates();
+    void checkInvalidDates_data();
+    void checkInvalidDates();
+};
+
+void TestDateConv::initTestCase()
+{
+    qRegisterMetaType<QDate>("QDate");
+    qRegisterMetaType<QTime>("QTime");
+}
+
+void TestDateConv::checkDates_data()
+{
+    QTest::addColumn<QByteArray>("input");
+    QTest::addColumn<QDate>("day");
+    QTest::addColumn<QTime>("time");
+
+    // This is a typical case - all data provided
+    QTest::newRow("D:20040101121110") << QByteArray("D:20040101121110Z") << QDate(2004, 1, 1) << QTime(12, 11, 10);
+
+    // The D: is strongly recommended, but optional
+    QTest::newRow("20040101121110") << QByteArray("20040101121110Z") << QDate(2004, 1, 1) << QTime(12, 11, 10);
+
+    // Only the year is actually required
+    QTest::newRow("D:2006") << QByteArray("D:2006") << QDate(2006, 1, 1) << QTime(0, 0, 0);
+
+    QTest::newRow("D:200602") << QByteArray("D:200602") << QDate(2006, 2, 1) << QTime(0, 0, 0);
+
+    QTest::newRow("D:20060304") << QByteArray("D:20060304") << QDate(2006, 3, 4) << QTime(0, 0, 0);
+
+    QTest::newRow("D:2006030405") << QByteArray("D:2006030405") << QDate(2006, 3, 4) << QTime(5, 0, 0);
+
+    QTest::newRow("D:200603040512") << QByteArray("D:200603040512") << QDate(2006, 3, 4) << QTime(5, 12, 0);
+
+    // If the timezone isn't specified, I assume UTC
+    QTest::newRow("D:20060304051226") << QByteArray("D:20060304051226") << QDate(2006, 3, 4) << QTime(5, 12, 26);
+
+    // Check for real timezone conversions
+    QTest::newRow("D:20030131115258-04'00'") << QByteArray("D:20030131115258-04'00'") << QDate(2003, 1, 31) << QTime(15, 52, 58);
+
+    QTest::newRow("D:20030131115258+05'00'") << QByteArray("D:20030131115258+05'00'") << QDate(2003, 1, 31) << QTime(6, 52, 58);
+
+    // There are places that have non-hour offsets
+    // Yep, that means you Adelaide.
+    QTest::newRow("D:20030131115258+08'30'") << QByteArray("D:20030131115258+08'30'") << QDate(2003, 1, 31) << QTime(3, 22, 58);
+
+    QTest::newRow("D:20030131115258-08'30'") << QByteArray("D:20030131115258-08'30'") << QDate(2003, 1, 31) << QTime(20, 22, 58);
+}
+
+void TestDateConv::checkDates()
+{
+    QFETCH(QByteArray, input);
+    QFETCH(QDate, day);
+    QFETCH(QTime, time);
+
+    QCOMPARE(Poppler::convertDate(input.constData()), QDateTime(day, time, Qt::UTC));
+}
+
+void TestDateConv::checkInvalidDates_data()
+{
+    QTest::addColumn<QByteArray>("input");
+
+    // Null data
+    QTest::newRow("Null data") << QByteArray();
+
+    // Empty data
+    QTest::newRow("Empty data") << QByteArray("");
+
+    // Empty data
+    QTest::newRow("One character") << QByteArray("D");
+
+    // Empty data
+    QTest::newRow("'D:'") << QByteArray("D:");
+
+    // Empty data
+    QTest::newRow("Not a date") << QByteArray("D:IAmNotAValidDate");
+}
+
+void TestDateConv::checkInvalidDates()
+{
+    QFETCH(QByteArray, input);
+
+    QCOMPARE(Poppler::convertDate(input.constData()), QDateTime());
+}
+
+QTEST_GUILESS_MAIN(TestDateConv)
+
+#include "check_dateConversion.moc"
diff --git a/qt6/tests/check_fonts.cpp b/qt6/tests/check_fonts.cpp
new file mode 100644
index 0000000..f4f747a
--- /dev/null
+++ b/qt6/tests/check_fonts.cpp
@@ -0,0 +1,235 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+#include <memory>
+
+class TestFontsData : public QObject
+{
+    Q_OBJECT
+public:
+    TestFontsData(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkNoFonts();
+    void checkType1();
+    void checkType3();
+    void checkTrueType();
+    void checkFontIterator();
+    void checkSecondDocumentQuery();
+    void checkMultipleIterations();
+    void checkIteratorFonts();
+};
+
+static QList<Poppler::FontInfo> loadFontsViaIterator(Poppler::Document *doc, int from = 0, int count = -1)
+{
+    int num = count == -1 ? doc->numPages() - from : count;
+    QList<Poppler::FontInfo> list;
+    std::unique_ptr<Poppler::FontIterator> it(doc->newFontIterator(from));
+    while (it->hasNext() && num) {
+        list += it->next();
+        --num;
+    }
+    return list;
+}
+
+namespace Poppler {
+static bool operator==(const FontInfo &f1, const FontInfo &f2)
+{
+    if (f1.name() != f2.name())
+        return false;
+    if (f1.file() != f2.file())
+        return false;
+    if (f1.isEmbedded() != f2.isEmbedded())
+        return false;
+    if (f1.isSubset() != f2.isSubset())
+        return false;
+    if (f1.type() != f2.type())
+        return false;
+    if (f1.typeName() != f2.typeName())
+        return false;
+    return true;
+}
+}
+
+void TestFontsData::checkNoFonts()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/image.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = doc->fonts();
+    QCOMPARE(listOfFonts.size(), 0);
+
+    delete doc;
+}
+
+void TestFontsData::checkType1()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/text.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = doc->fonts();
+    QCOMPARE(listOfFonts.size(), 1);
+    QCOMPARE(listOfFonts.at(0).name(), QLatin1String("Helvetica"));
+    QCOMPARE(listOfFonts.at(0).type(), Poppler::FontInfo::Type1);
+    QCOMPARE(listOfFonts.at(0).typeName(), QLatin1String("Type 1"));
+
+    QCOMPARE(listOfFonts.at(0).isEmbedded(), false);
+    QCOMPARE(listOfFonts.at(0).isSubset(), false);
+
+    delete doc;
+}
+
+void TestFontsData::checkType3()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/type3.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = doc->fonts();
+    QCOMPARE(listOfFonts.size(), 2);
+    QCOMPARE(listOfFonts.at(0).name(), QLatin1String("Helvetica"));
+    QCOMPARE(listOfFonts.at(0).type(), Poppler::FontInfo::Type1);
+    QCOMPARE(listOfFonts.at(0).typeName(), QLatin1String("Type 1"));
+
+    QCOMPARE(listOfFonts.at(0).isEmbedded(), false);
+    QCOMPARE(listOfFonts.at(0).isSubset(), false);
+
+    QCOMPARE(listOfFonts.at(1).name(), QString());
+    QCOMPARE(listOfFonts.at(1).type(), Poppler::FontInfo::Type3);
+    QCOMPARE(listOfFonts.at(1).typeName(), QLatin1String("Type 3"));
+
+    QCOMPARE(listOfFonts.at(1).isEmbedded(), true);
+    QCOMPARE(listOfFonts.at(1).isSubset(), false);
+
+    delete doc;
+}
+
+void TestFontsData::checkTrueType()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = doc->fonts();
+    QCOMPARE(listOfFonts.size(), 2);
+    QCOMPARE(listOfFonts.at(0).name(), QLatin1String("Arial-BoldMT"));
+    QCOMPARE(listOfFonts.at(0).type(), Poppler::FontInfo::TrueType);
+    QCOMPARE(listOfFonts.at(0).typeName(), QLatin1String("TrueType"));
+
+    QCOMPARE(listOfFonts.at(0).isEmbedded(), false);
+    QCOMPARE(listOfFonts.at(0).isSubset(), false);
+
+    QCOMPARE(listOfFonts.at(1).name(), QLatin1String("ArialMT"));
+    QCOMPARE(listOfFonts.at(1).type(), Poppler::FontInfo::TrueType);
+    QCOMPARE(listOfFonts.at(1).typeName(), QLatin1String("TrueType"));
+
+    QCOMPARE(listOfFonts.at(1).isEmbedded(), false);
+    QCOMPARE(listOfFonts.at(1).isSubset(), false);
+
+    delete doc;
+}
+
+void TestFontsData::checkFontIterator()
+{
+    // loading a 1-page document
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/type3.pdf");
+    QVERIFY(doc);
+    // loading a 6-pages document
+    Poppler::Document *doc6 = Poppler::Document::load(TESTDATADIR "/tests/cropbox.pdf");
+    QVERIFY(doc6);
+
+    std::unique_ptr<Poppler::FontIterator> it;
+
+    // some tests with the 1-page document:
+    // - check a default iterator
+    it.reset(doc->newFontIterator());
+    QVERIFY(it->hasNext());
+    // - check an iterator for negative pages to behave as 0
+    it.reset(doc->newFontIterator(-1));
+    QVERIFY(it->hasNext());
+    // - check an iterator for pages out of the page limit
+    it.reset(doc->newFontIterator(1));
+    QVERIFY(!it->hasNext());
+    // - check that it reaches the end after 1 iteration
+    it.reset(doc->newFontIterator());
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(!it->hasNext());
+
+    // some tests with the 6-page document:
+    // - check a default iterator
+    it.reset(doc6->newFontIterator());
+    QVERIFY(it->hasNext());
+    // - check an iterator for pages out of the page limit
+    it.reset(doc6->newFontIterator(6));
+    QVERIFY(!it->hasNext());
+    // - check that it reaches the end after 6 iterations
+    it.reset(doc6->newFontIterator());
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(it->hasNext());
+    it->next();
+    QVERIFY(!it->hasNext());
+
+    delete doc;
+    delete doc6;
+}
+
+void TestFontsData::checkSecondDocumentQuery()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/type3.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = doc->fonts();
+    QCOMPARE(listOfFonts.size(), 2);
+    // check we get the very same result when calling fonts() again (#19405)
+    QList<Poppler::FontInfo> listOfFonts2 = doc->fonts();
+    QCOMPARE(listOfFonts, listOfFonts2);
+
+    delete doc;
+}
+
+void TestFontsData::checkMultipleIterations()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/type3.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = loadFontsViaIterator(doc);
+    QCOMPARE(listOfFonts.size(), 2);
+    QList<Poppler::FontInfo> listOfFonts2 = loadFontsViaIterator(doc);
+    QCOMPARE(listOfFonts, listOfFonts2);
+
+    delete doc;
+}
+
+void TestFontsData::checkIteratorFonts()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/tests/fonts.pdf");
+    QVERIFY(doc);
+
+    QList<Poppler::FontInfo> listOfFonts = doc->fonts();
+    QCOMPARE(listOfFonts.size(), 3);
+
+    // check we get the very same result when gatering fonts using the iterator
+    QList<Poppler::FontInfo> listOfFonts2 = loadFontsViaIterator(doc);
+    QCOMPARE(listOfFonts, listOfFonts2);
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestFontsData)
+#include "check_fonts.moc"
diff --git a/qt6/tests/check_forms.cpp b/qt6/tests/check_forms.cpp
new file mode 100644
index 0000000..f5df258
--- /dev/null
+++ b/qt6/tests/check_forms.cpp
@@ -0,0 +1,257 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+#include <poppler-form.h>
+#include <poppler-private.h>
+#include <Form.h>
+
+class TestForms : public QObject
+{
+    Q_OBJECT
+public:
+    TestForms(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void testCheckbox(); // Test for issue #655
+    void testCheckboxIssue159(); // Test for issue #159
+    void testSetIcon(); // Test that setIcon will always be valid.
+    void testSetPrintable();
+    void testSetAppearanceText();
+    void testStandAloneWidgets(); // check for 'de facto' tooltips. Issue #34
+    void testUnicodeFieldAttributes();
+};
+
+void TestForms::testCheckbox()
+{
+    // Test for checkbox issue #655
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/latex-hyperref-checkbox-issue-655.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    QList<Poppler::FormField *> forms = page->formFields();
+    QCOMPARE(forms.size(), 1);
+
+    Poppler::FormField *form = forms.at(0);
+    QCOMPARE(form->type(), Poppler::FormField::FormButton);
+
+    Poppler::FormFieldButton *chkFormFieldButton = static_cast<Poppler::FormFieldButton *>(form);
+
+    // Test this is actually a Checkbox
+    QCOMPARE(chkFormFieldButton->buttonType(), Poppler::FormFieldButton::CheckBox);
+
+    // checkbox comes initially 'unchecked'
+    QCOMPARE(chkFormFieldButton->state(), false);
+    // let's mark it as 'checked'
+    chkFormFieldButton->setState(true);
+    // now test if it was succesfully 'checked'
+    QCOMPARE(chkFormFieldButton->state(), true);
+}
+
+void TestForms::testStandAloneWidgets()
+{
+    // Check for 'de facto' tooltips. Issue #34
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/tooltip.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    QList<Poppler::FormField *> forms = page->formFields();
+
+    QCOMPARE(forms.size(), 3);
+
+    Q_FOREACH (Poppler::FormField *field, forms) {
+        QCOMPARE(field->type(), Poppler::FormField::FormButton);
+
+        Poppler::FormFieldButton *fieldButton = static_cast<Poppler::FormFieldButton *>(field);
+        QCOMPARE(fieldButton->buttonType(), Poppler::FormFieldButton::Push);
+
+        FormField *ff = Poppler::FormFieldData::getFormWidget(fieldButton)->getField();
+        QVERIFY(ff);
+        QCOMPARE(ff->isStandAlone(), true);
+
+        // tooltip.pdf has only these 3 standalone widgets
+        QVERIFY(field->uiName() == QStringLiteral("This is a tooltip!") || // clazy:exclude=qstring-allocations
+                field->uiName() == QStringLiteral("Sulfuric acid") || field->uiName() == QString::fromUtf8("little Gauß"));
+    }
+}
+
+void TestForms::testCheckboxIssue159()
+{
+    // Test for checkbox issue #159
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/checkbox_issue_159.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    Poppler::FormFieldButton *beerFieldButton = nullptr;
+    Poppler::FormFieldButton *wineFieldButton = nullptr;
+
+    QList<Poppler::FormField *> forms = page->formFields();
+
+    // Let's find and assign the "Wine" and "Beer" radio buttons
+    Q_FOREACH (Poppler::FormField *field, forms) {
+        if (field->type() != Poppler::FormField::FormButton)
+            continue;
+
+        Poppler::FormFieldButton *fieldButton = static_cast<Poppler::FormFieldButton *>(field);
+        if (fieldButton->buttonType() != Poppler::FormFieldButton::Radio)
+            continue;
+
+        // printf("%s \n", fieldButton->caption().toLatin1().data());
+        if (fieldButton->caption() == QStringLiteral("Wine")) {
+            wineFieldButton = fieldButton;
+        } else if (fieldButton->caption() == QStringLiteral("Beer")) {
+            beerFieldButton = fieldButton;
+        }
+    }
+
+    // "Beer" and "Wine" radiobuttons belong to the same RadioButton group.
+    // So selecting one should unselect the other.
+    QVERIFY(beerFieldButton);
+    QVERIFY(wineFieldButton);
+
+    // Test that the RadioButton group comes with "Beer" initially selected
+    QCOMPARE(beerFieldButton->state(), true);
+
+    // Now select "Wine". As a result "Beer" should no longer be selected.
+    wineFieldButton->setState(true);
+
+    // Test that "Beer" is indeed not reporting as being selected
+    QCOMPARE(beerFieldButton->state(), false);
+}
+
+void TestForms::testSetIcon()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/form_set_icon.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    QList<Poppler::FormField *> forms = page->formFields();
+
+    Poppler::FormFieldButton *anmButton = nullptr;
+
+    // First we are finding the field which will have its icon changed
+    Q_FOREACH (Poppler::FormField *field, forms) {
+
+        if (field->type() != Poppler::FormField::FormButton)
+            continue;
+
+        Poppler::FormFieldButton *fieldButton = static_cast<Poppler::FormFieldButton *>(field);
+        if (field->name() == QStringLiteral("anm0"))
+            anmButton = fieldButton;
+    }
+
+    QVERIFY(anmButton);
+
+    // Then we set the Icon on this field, for every other field
+    // And verify if it has a valid icon
+    Q_FOREACH (Poppler::FormField *field, forms) {
+
+        if (field->type() != Poppler::FormField::FormButton)
+            continue;
+
+        Poppler::FormFieldButton *fieldButton = static_cast<Poppler::FormFieldButton *>(field);
+        if (field->name() == QStringLiteral("anm0"))
+            continue;
+
+        Poppler::FormFieldIcon newIcon = fieldButton->icon();
+
+        anmButton->setIcon(newIcon);
+
+        Poppler::FormFieldIcon anmIcon = anmButton->icon();
+
+        QVERIFY(Poppler::FormFieldIconData::getData(anmIcon));
+        QVERIFY(Poppler::FormFieldIconData::getData(anmIcon)->icon);
+
+        QCOMPARE(Poppler::FormFieldIconData::getData(anmIcon)->icon->lookupNF("AP").dictLookupNF("N").getRef().num, Poppler::FormFieldIconData::getData(newIcon)->icon->lookupNF("AP").dictLookupNF("N").getRef().num);
+    }
+
+    // Just making sure that setting a invalid icon will still produce a valid icon.
+    anmButton->setIcon(nullptr);
+    Poppler::FormFieldIcon anmIcon = anmButton->icon();
+
+    QVERIFY(Poppler::FormFieldIconData::getData(anmIcon));
+    QVERIFY(Poppler::FormFieldIconData::getData(anmIcon)->icon);
+}
+
+void TestForms::testSetPrintable()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/form_set_icon.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    QList<Poppler::FormField *> forms = page->formFields();
+
+    Q_FOREACH (Poppler::FormField *field, forms) {
+        field->setPrintable(true);
+        QCOMPARE(field->isPrintable(), true);
+
+        field->setPrintable(false);
+        QCOMPARE(field->isPrintable(), false);
+    }
+}
+
+void TestForms::testSetAppearanceText()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/checkbox_issue_159.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    QList<Poppler::FormField *> forms = page->formFields();
+
+    int nTextForms = 0;
+
+    Q_FOREACH (Poppler::FormField *field, forms) {
+
+        if (field->type() != Poppler::FormField::FormText)
+            continue;
+
+        nTextForms++;
+
+        Poppler::FormFieldText *fft = static_cast<Poppler::FormFieldText *>(field);
+
+        const QString textToSet = "HOLA" + fft->name();
+        fft->setAppearanceText(textToSet);
+
+        Dict *dict = Poppler::FormFieldData::getFormWidget(fft)->getObj()->getDict();
+        Object strObject = dict->lookup("AP").dictLookup("N");
+
+        QVERIFY(strObject.isStream());
+
+        GooString s;
+        strObject.getStream()->fillGooString(&s);
+
+        const QString textToFind = QStringLiteral("\n(%1) Tj\n").arg(textToSet);
+        QVERIFY(s.toStr().find(textToFind.toStdString()) != std::string::npos);
+    }
+
+    QCOMPARE(nTextForms, 5);
+}
+
+void TestForms::testUnicodeFieldAttributes()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/fieldWithUtf16Names.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    QList<Poppler::FormField *> forms = page->formFields();
+
+    Poppler::FormField *field = forms.first();
+
+    QCOMPARE(field->name(), QStringLiteral("Tex"));
+    QCOMPARE(field->uiName(), QStringLiteral("Texto de ayuda"));
+}
+
+QTEST_GUILESS_MAIN(TestForms)
+#include "check_forms.moc"
diff --git a/qt6/tests/check_goostring.cpp b/qt6/tests/check_goostring.cpp
new file mode 100644
index 0000000..626e777
--- /dev/null
+++ b/qt6/tests/check_goostring.cpp
@@ -0,0 +1,161 @@
+#include <QtCore/QScopedPointer>
+#include <QtTest/QtTest>
+
+#include "goo/GooString.h"
+
+class TestGooString : public QObject
+{
+    Q_OBJECT
+public:
+    TestGooString(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void testInsertData_data();
+    void testInsertData();
+    void testInsert();
+    void testFormat();
+    void testFromNullptr();
+};
+
+void TestGooString::testInsertData_data()
+{
+    QTest::addColumn<QByteArray>("string");
+    QTest::addColumn<QByteArray>("addition");
+    QTest::addColumn<int>("position");
+    QTest::addColumn<QByteArray>("result");
+
+    QTest::newRow("foo") << QByteArray("foo") << QByteArray("bar") << 0 << QByteArray("barfoo");
+    QTest::newRow("<empty>") << QByteArray() << QByteArray("bar") << 0 << QByteArray("bar");
+    QTest::newRow("foo+bar #1") << QByteArray("f+bar") << QByteArray("oo") << 1 << QByteArray("foo+bar");
+    QTest::newRow("foo+bar #2") << QByteArray("fobar") << QByteArray("o+") << 2 << QByteArray("foo+bar");
+    QTest::newRow("foo+bar #last") << QByteArray("foo+r") << QByteArray("ba") << 4 << QByteArray("foo+bar");
+    QTest::newRow("foo+bar #end") << QByteArray("foo+") << QByteArray("bar") << 4 << QByteArray("foo+bar");
+    QTest::newRow("long #start") << QByteArray("very string") << QByteArray("long long long long long ") << 5 << QByteArray("very long long long long long string");
+}
+
+void TestGooString::testInsertData()
+{
+    QFETCH(QByteArray, string);
+    QFETCH(QByteArray, addition);
+    QFETCH(int, position);
+    QFETCH(QByteArray, result);
+
+    GooString goo(string.constData());
+    QCOMPARE(goo.c_str(), string.constData());
+    goo.insert(position, addition.constData());
+    QCOMPARE(goo.c_str(), result.constData());
+}
+
+void TestGooString::testInsert()
+{
+    {
+        GooString goo;
+        goo.insert(0, ".");
+        goo.insert(0, "This is a very long long test string");
+        QCOMPARE(goo.c_str(), "This is a very long long test string.");
+    }
+    {
+        GooString goo;
+        goo.insert(0, "second-part-third-part");
+        goo.insert(0, "first-part-");
+        QCOMPARE(goo.c_str(), "first-part-second-part-third-part");
+    }
+}
+
+void TestGooString::testFormat()
+{
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{1:x}", 1, 0xF));
+        QCOMPARE(goo->c_str(), "1,f");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{0:x},{0:X},{0:o},{0:b},{0:w}", 0xA));
+        QCOMPARE(goo->c_str(), "10,a,A,12,1010,          ");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{0:x},{0:X},{0:o},{0:b}", -0xA));
+        QCOMPARE(goo->c_str(), "-10,-a,-A,-12,-1010");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:c}{1:c}{2:c}{3:c}", 'T', (char)'E', (short)'S', (int)'T'));
+        QCOMPARE(goo->c_str(), "TEST");
+
+        const QScopedPointer<GooString> goo2(GooString::format("{0:s} {1:t}", "TEST", goo.data()));
+        QCOMPARE(goo2->c_str(), "TEST TEST");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:ud} {1:d} {2:d}", UINT_MAX, INT_MAX, INT_MIN));
+        const QByteArray expected = QStringLiteral("%1 %2 %3").arg(UINT_MAX).arg(INT_MAX).arg(INT_MIN).toLatin1();
+        QCOMPARE(goo->c_str(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:uld} {1:ld} {2:ld}", ULONG_MAX, LONG_MAX, LONG_MIN));
+        const QByteArray expected = QStringLiteral("%1 %2 %3").arg(ULONG_MAX).arg(LONG_MAX).arg(LONG_MIN).toLatin1();
+        QCOMPARE(goo->c_str(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:ulld} {1:lld} {2:lld}", ULLONG_MAX, LLONG_MAX, LLONG_MIN));
+        const QByteArray expected = QStringLiteral("%1 %2 %3").arg(ULLONG_MAX).arg(LLONG_MAX).arg(LLONG_MIN).toLatin1();
+        QCOMPARE(goo->c_str(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> gooD(GooString::format("{0:.1f} {0:.1g} {0:.1gs} | {1:.1f} {1:.1g} {1:.1gs}", 1., .012));
+        const QScopedPointer<GooString> gooF(GooString::format("{0:.1f} {0:.1g} {0:.1gs} | {1:.1f} {1:.1g} {1:.1gs}", 1.f, .012f));
+        QCOMPARE(gooD->c_str(), "1.0 1 1 | 0.0 0 0.01");
+        QCOMPARE(gooF->c_str(), "1.0 1 1 | 0.0 0 0.01");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:.4f} {0:.4g} {0:.4gs}", .012));
+        QCOMPARE(goo->c_str(), "0.0120 0.012 0.012");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{{ SomeText {0:d} }}", 1));
+        QCOMPARE(goo->c_str(), "{ SomeText 1 }");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{{{{ {{ SomeText {0:d}", 2));
+        QCOMPARE(goo->c_str(), "{{ { SomeText 2");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("SomeText {0:d} }} }}}}", 3));
+        QCOMPARE(goo->c_str(), "SomeText 3 } }}");
+    }
+}
+
+void TestGooString::testFromNullptr()
+{
+    {
+        GooString str { static_cast<const GooString *>(nullptr) };
+        QCOMPARE(str.getLength(), 0);
+    }
+
+    {
+        GooString str;
+        str.Set(static_cast<const GooString *>(nullptr));
+        QCOMPARE(str.getLength(), 0);
+    }
+
+    {
+        GooString str { static_cast<const char *>(nullptr) };
+        QCOMPARE(str.getLength(), 0);
+    }
+
+    {
+        GooString str { static_cast<const char *>(nullptr), 0 };
+        QCOMPARE(str.getLength(), 0);
+    }
+
+    {
+        GooString str;
+        str.Set(static_cast<const char *>(nullptr));
+        QCOMPARE(str.getLength(), 0);
+    }
+
+    {
+        GooString str;
+        str.Set(static_cast<const char *>(nullptr), 0);
+        QCOMPARE(str.getLength(), 0);
+    }
+}
+
+QTEST_GUILESS_MAIN(TestGooString)
+#include "check_goostring.moc"
diff --git a/qt6/tests/check_lexer.cpp b/qt6/tests/check_lexer.cpp
new file mode 100644
index 0000000..f0b5e13
--- /dev/null
+++ b/qt6/tests/check_lexer.cpp
@@ -0,0 +1,108 @@
+#include <QtTest/QtTest>
+
+#include "Object.h"
+#include "Lexer.h"
+
+class TestLexer : public QObject
+{
+    Q_OBJECT
+public:
+    TestLexer(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void testNumbers();
+};
+
+void TestLexer::testNumbers()
+{
+    char data[] = "0 1 -1 2147483647 -2147483647 2147483648 -2147483648 4294967297 -2147483649 0.1 1.1 -1.1 2147483647.1 -2147483647.1 2147483648.1 -2147483648.1 4294967297.1 -2147483649.1 9223372036854775807 18446744073709551615";
+    MemStream *stream = new MemStream(data, 0, strlen(data), Object(objNull));
+    Lexer *lexer = new Lexer(nullptr, stream);
+    QVERIFY(lexer);
+
+    Object obj;
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt);
+    QCOMPARE(obj.getInt(), 0);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt);
+    QCOMPARE(obj.getInt(), 1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt);
+    QCOMPARE(obj.getInt(), -1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt);
+    QCOMPARE(obj.getInt(), 2147483647);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt);
+    QCOMPARE(obj.getInt(), -2147483647);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt64);
+    QCOMPARE(obj.getInt64(), 2147483648ll);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt);
+    QCOMPARE(obj.getInt(), -2147483647 - 1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt64);
+    QCOMPARE(obj.getInt64(), 4294967297ll);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt64);
+    QCOMPARE(obj.getInt64(), -2147483649ll);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), 0.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), 1.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), -1.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), 2147483647.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), -2147483647.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), 2147483648.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), -2147483648.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), 4294967297.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), -2147483649.1);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objInt64);
+    QCOMPARE(obj.getInt64(), 9223372036854775807ll);
+
+    obj = lexer->getObj();
+    QCOMPARE(obj.getType(), objReal);
+    QCOMPARE(obj.getReal(), 18446744073709551616.);
+
+    delete lexer;
+}
+
+QTEST_GUILESS_MAIN(TestLexer)
+#include "check_lexer.moc"
diff --git a/qt6/tests/check_links.cpp b/qt6/tests/check_links.cpp
new file mode 100644
index 0000000..27ab711
--- /dev/null
+++ b/qt6/tests/check_links.cpp
@@ -0,0 +1,121 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+#include <memory>
+
+class TestLinks : public QObject
+{
+    Q_OBJECT
+public:
+    TestLinks(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkDocumentWithNoDests();
+    void checkDests_xr01();
+    void checkDests_xr02();
+    void checkDocumentURILink();
+};
+
+static bool isDestinationValid_pageNumber(const Poppler::LinkDestination *dest, const Poppler::Document *doc)
+{
+    return dest->pageNumber() > 0 && dest->pageNumber() <= doc->numPages();
+}
+
+static bool isDestinationValid_name(const Poppler::LinkDestination *dest)
+{
+    return !dest->destinationName().isEmpty();
+}
+
+void TestLinks::checkDocumentWithNoDests()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/WithAttachments.pdf");
+    QVERIFY(doc);
+
+    std::unique_ptr<Poppler::LinkDestination> dest;
+    dest.reset(doc->linkDestination(QStringLiteral("no.dests.in.this.document")));
+    QVERIFY(!isDestinationValid_pageNumber(dest.get(), doc));
+    QVERIFY(isDestinationValid_name(dest.get()));
+
+    delete doc;
+}
+
+void TestLinks::checkDests_xr01()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/xr01.pdf");
+    QVERIFY(doc);
+
+    Poppler::Page *page = doc->page(0);
+    QVERIFY(page);
+
+    QList<Poppler::Link *> links = page->links();
+    QCOMPARE(links.count(), 2);
+
+    {
+        QCOMPARE(links.at(0)->linkType(), Poppler::Link::Goto);
+        Poppler::LinkGoto *link = static_cast<Poppler::LinkGoto *>(links.at(0));
+        const Poppler::LinkDestination dest = link->destination();
+        QVERIFY(!isDestinationValid_pageNumber(&dest, doc));
+        QVERIFY(isDestinationValid_name(&dest));
+        QCOMPARE(dest.destinationName(), QLatin1String("section.1"));
+    }
+
+    {
+        QCOMPARE(links.at(1)->linkType(), Poppler::Link::Goto);
+        Poppler::LinkGoto *link = static_cast<Poppler::LinkGoto *>(links.at(1));
+        const Poppler::LinkDestination dest = link->destination();
+        QVERIFY(!isDestinationValid_pageNumber(&dest, doc));
+        QVERIFY(isDestinationValid_name(&dest));
+        QCOMPARE(dest.destinationName(), QLatin1String("section.2"));
+    }
+
+    qDeleteAll(links);
+    delete page;
+    delete doc;
+}
+
+void TestLinks::checkDests_xr02()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/xr02.pdf");
+    QVERIFY(doc);
+
+    std::unique_ptr<Poppler::LinkDestination> dest;
+    dest.reset(doc->linkDestination(QStringLiteral("section.1")));
+    QVERIFY(isDestinationValid_pageNumber(dest.get(), doc));
+    QVERIFY(!isDestinationValid_name(dest.get()));
+    dest.reset(doc->linkDestination(QStringLiteral("section.2")));
+    QVERIFY(isDestinationValid_pageNumber(dest.get(), doc));
+    QVERIFY(!isDestinationValid_name(dest.get()));
+    dest.reset(doc->linkDestination(QStringLiteral("section.3")));
+    QVERIFY(!isDestinationValid_pageNumber(dest.get(), doc));
+    QVERIFY(isDestinationValid_name(dest.get()));
+
+    delete doc;
+}
+
+void TestLinks::checkDocumentURILink()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/checkbox_issue_159.pdf");
+    QVERIFY(doc);
+
+    Poppler::Page *page = doc->page(0);
+    QVERIFY(page);
+
+    QList<Poppler::Link *> links = page->links();
+    QCOMPARE(links.count(), 1);
+
+    QCOMPARE(links.at(0)->linkType(), Poppler::Link::Browse);
+    Poppler::LinkBrowse *link = static_cast<Poppler::LinkBrowse *>(links.at(0));
+    QCOMPARE(link->url(), QLatin1String("http://www.tcpdf.org"));
+
+    qDeleteAll(links);
+    delete page;
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestLinks)
+
+#include "check_links.moc"
diff --git a/qt6/tests/check_metadata.cpp b/qt6/tests/check_metadata.cpp
new file mode 100644
index 0000000..a97f3c1
--- /dev/null
+++ b/qt6/tests/check_metadata.cpp
@@ -0,0 +1,286 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+class TestMetaData : public QObject
+{
+    Q_OBJECT
+public:
+    TestMetaData(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkStrings_data();
+    void checkStrings();
+    void checkStrings2_data();
+    void checkStrings2();
+    void checkStringKeys();
+    void checkLinearised();
+    void checkNumPages();
+    void checkDate();
+    void checkPageSize();
+    void checkPortraitOrientation();
+    void checkLandscapeOrientation();
+    void checkUpsideDownOrientation();
+    void checkSeascapeOrientation();
+    void checkVersion();
+    void checkPdfId();
+    void checkNoPdfId();
+};
+
+void TestMetaData::checkStrings_data()
+{
+    QTest::addColumn<QString>("key");
+    QTest::addColumn<QString>("value");
+
+    QTest::newRow("Author") << "Author"
+                            << "Brad Hards";
+    QTest::newRow("Title") << "Title"
+                           << "Two pages";
+    QTest::newRow("Subject") << "Subject"
+                             << "A two page layout for poppler testing";
+    QTest::newRow("Keywords") << "Keywords"
+                              << "Qt4 bindings";
+    QTest::newRow("Creator") << "Creator"
+                             << "iText: cgpdftops CUPS filter";
+    QTest::newRow("Producer") << "Producer"
+                              << "Acrobat Distiller 7.0 for Macintosh";
+}
+
+void TestMetaData::checkStrings()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/doublepage.pdf");
+    QVERIFY(doc);
+
+    QFETCH(QString, key);
+    QFETCH(QString, value);
+    QCOMPARE(doc->info(key), value);
+
+    delete doc;
+}
+
+void TestMetaData::checkStrings2_data()
+{
+    QTest::addColumn<QString>("key");
+    QTest::addColumn<QString>("value");
+
+    QTest::newRow("Title") << "Title"
+                           << "Malaga hotels";
+    QTest::newRow("Author") << "Author"
+                            << "Brad Hards";
+    QTest::newRow("Creator") << "Creator"
+                             << "Safari: cgpdftops CUPS filter";
+    QTest::newRow("Producer") << "Producer"
+                              << "Acrobat Distiller 7.0 for Macintosh";
+    QTest::newRow("Keywords") << "Keywords"
+                              << "First\rSecond\rthird";
+    QTest::newRow("Custom1") << "Custom1"
+                             << "CustomValue1";
+    QTest::newRow("Custom2") << "Custom2"
+                             << "CustomValue2";
+}
+
+void TestMetaData::checkStrings2()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+
+    QFETCH(QString, key);
+    QFETCH(QString, value);
+    QCOMPARE(doc->info(key), value);
+
+    delete doc;
+}
+
+void TestMetaData::checkStringKeys()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+
+    QStringList keyList;
+    keyList << QStringLiteral("Title") << QStringLiteral("Author") << QStringLiteral("Creator") << QStringLiteral("Keywords") << QStringLiteral("CreationDate");
+    keyList << QStringLiteral("Producer") << QStringLiteral("ModDate") << QStringLiteral("Custom1") << QStringLiteral("Custom2");
+    keyList.sort();
+    QStringList keysInDoc = doc->infoKeys();
+    keysInDoc.sort();
+    QCOMPARE(keysInDoc, keyList);
+
+    delete doc;
+}
+
+void TestMetaData::checkLinearised()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->isLinearized());
+
+    delete doc;
+
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+    QCOMPARE(doc->isLinearized(), false);
+
+    delete doc;
+}
+
+void TestMetaData::checkPortraitOrientation()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    Poppler::Page *page = doc->page(0);
+    QCOMPARE(page->orientation(), Poppler::Page::Portrait);
+
+    delete page;
+    delete doc;
+}
+
+void TestMetaData::checkNumPages()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/doublepage.pdf");
+    QVERIFY(doc);
+    QCOMPARE(doc->numPages(), 2);
+
+    delete doc;
+
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+    QCOMPARE(doc->numPages(), 1);
+
+    delete doc;
+}
+
+void TestMetaData::checkDate()
+{
+    Poppler::Document *doc;
+
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+    QCOMPARE(doc->date(QStringLiteral("ModDate")), QDateTime(QDate(2005, 12, 5), QTime(9, 44, 46), Qt::UTC));
+    QCOMPARE(doc->date(QStringLiteral("CreationDate")), QDateTime(QDate(2005, 8, 13), QTime(1, 12, 11), Qt::UTC));
+
+    delete doc;
+}
+
+void TestMetaData::checkPageSize()
+{
+    Poppler::Document *doc;
+
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/truetype.pdf");
+    QVERIFY(doc);
+    Poppler::Page *page = doc->page(0);
+    QCOMPARE(page->pageSize(), QSize(595, 842));
+    QCOMPARE(page->pageSizeF(), QSizeF(595.22, 842));
+
+    delete page;
+    delete doc;
+}
+
+void TestMetaData::checkLandscapeOrientation()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    Poppler::Page *page = doc->page(1);
+    QCOMPARE(page->orientation(), Poppler::Page::Landscape);
+
+    delete page;
+    delete doc;
+}
+
+void TestMetaData::checkUpsideDownOrientation()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    Poppler::Page *page = doc->page(2);
+    QCOMPARE(page->orientation(), Poppler::Page::UpsideDown);
+
+    delete page;
+    delete doc;
+}
+
+void TestMetaData::checkSeascapeOrientation()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    Poppler::Page *page = doc->page(3);
+    QCOMPARE(page->orientation(), Poppler::Page::Seascape);
+
+    delete page;
+    delete doc;
+}
+
+void TestMetaData::checkVersion()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/doublepage.pdf");
+    QVERIFY(doc);
+
+    int major = 0, minor = 0;
+    doc->getPdfVersion(&major, &minor);
+    QCOMPARE(major, 1);
+    QCOMPARE(minor, 6);
+
+    delete doc;
+}
+
+void TestMetaData::checkPdfId()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/A6EmbeddedFiles.pdf");
+    QVERIFY(doc);
+
+    const QByteArray referencePermanentId("00C9D5B6D8FB11D7A902003065D630AA");
+    const QByteArray referenceUpdateId("39AECAE6D8FB11D7A902003065D630AA");
+
+    {
+        // no IDs wanted, just existance check
+        QVERIFY(doc->getPdfId(nullptr, nullptr));
+    }
+    {
+        // only permanent ID
+        QByteArray permanentId;
+        QVERIFY(doc->getPdfId(&permanentId, nullptr));
+        QCOMPARE(permanentId.toUpper(), referencePermanentId);
+    }
+    {
+        // only update ID
+        QByteArray updateId;
+        QVERIFY(doc->getPdfId(nullptr, &updateId));
+        QCOMPARE(updateId.toUpper(), referenceUpdateId);
+    }
+    {
+        // both IDs
+        QByteArray permanentId;
+        QByteArray updateId;
+        QVERIFY(doc->getPdfId(&permanentId, &updateId));
+        QCOMPARE(permanentId.toUpper(), referencePermanentId);
+        QCOMPARE(updateId.toUpper(), referenceUpdateId);
+    }
+
+    delete doc;
+}
+
+void TestMetaData::checkNoPdfId()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/WithActualText.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(!doc->getPdfId(nullptr, nullptr));
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestMetaData)
+#include "check_metadata.moc"
diff --git a/qt6/tests/check_object.cpp b/qt6/tests/check_object.cpp
new file mode 100644
index 0000000..e3bc0ee
--- /dev/null
+++ b/qt6/tests/check_object.cpp
@@ -0,0 +1,41 @@
+#include <QtCore/QScopedPointer>
+#include <QtTest/QtTest>
+
+#include "poppler/Object.h"
+
+class TestObject : public QObject
+{
+    Q_OBJECT
+public:
+    TestObject(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void benchDefaultConstructor();
+    void benchMoveConstructor();
+    void benchSetToNull();
+};
+
+void TestObject::benchDefaultConstructor()
+{
+    QBENCHMARK {
+        Object obj;
+    }
+}
+
+void TestObject::benchMoveConstructor()
+{
+    QBENCHMARK {
+        Object src;
+        Object dst { std::move(src) };
+    }
+}
+
+void TestObject::benchSetToNull()
+{
+    Object obj;
+    QBENCHMARK {
+        obj.setToNull();
+    }
+}
+
+QTEST_GUILESS_MAIN(TestObject)
+#include "check_object.moc"
diff --git a/qt6/tests/check_optcontent.cpp b/qt6/tests/check_optcontent.cpp
new file mode 100644
index 0000000..004449c
--- /dev/null
+++ b/qt6/tests/check_optcontent.cpp
@@ -0,0 +1,453 @@
+#include <QtTest/QtTest>
+
+#include "PDFDoc.h"
+#include "GlobalParams.h"
+
+#include <poppler-qt6.h>
+#include <poppler-optcontent-private.h>
+
+class TestOptionalContent : public QObject
+{
+    Q_OBJECT
+public:
+    TestOptionalContent(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkVisPolicy();
+    void checkNestedLayers();
+    void checkNoOptionalContent();
+    void checkIsVisible();
+    void checkVisibilitySetting();
+    void checkRadioButtons();
+};
+
+void TestOptionalContent::checkVisPolicy()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/vis_policy_test.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasOptionalContent());
+
+    Poppler::OptContentModel *optContent = doc->optionalContentModel();
+    QModelIndex index;
+    index = optContent->index(0, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("A"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
+    index = optContent->index(1, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("B"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
+
+    delete doc;
+}
+
+void TestOptionalContent::checkNestedLayers()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/NestedLayers.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasOptionalContent());
+
+    Poppler::OptContentModel *optContent = doc->optionalContentModel();
+    QModelIndex index;
+
+    index = optContent->index(0, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Black Text and Green Snow"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+
+    index = optContent->index(1, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Mountains and Image"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
+
+    // This is a sub-item of "Mountains and Image"
+    QModelIndex subindex = optContent->index(0, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Image"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
+
+    index = optContent->index(2, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Starburst"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
+
+    index = optContent->index(3, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Watermark"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+
+    delete doc;
+}
+
+void TestOptionalContent::checkNoOptionalContent()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->hasOptionalContent(), false);
+
+    delete doc;
+}
+
+void TestOptionalContent::checkIsVisible()
+{
+    GooString *fileName = new GooString(TESTDATADIR "/unittestcases/vis_policy_test.pdf");
+    globalParams = std::make_unique<GlobalParams>();
+    PDFDoc *doc = new PDFDoc(fileName);
+    QVERIFY(doc);
+
+    OCGs *ocgs = doc->getOptContentConfig();
+    QVERIFY(ocgs);
+
+    XRef *xref = doc->getXRef();
+
+    Object obj;
+
+    // In this test, both Ref(21,0) and Ref(2,0) are set to On
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QVERIFY(ocgs->optContentIsVisible(&obj));
+
+    // Same again, looking for any leaks or dubious free()'s
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QVERIFY(ocgs->optContentIsVisible(&obj));
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    obj = xref->fetch(29, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    obj = xref->fetch(36, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    obj = xref->fetch(43, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(50, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(57, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(64, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(71, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    delete doc;
+    globalParams.reset();
+}
+
+void TestOptionalContent::checkVisibilitySetting()
+{
+    globalParams = std::make_unique<GlobalParams>();
+    GooString *fileName = new GooString(TESTDATADIR "/unittestcases/vis_policy_test.pdf");
+    PDFDoc *doc = new PDFDoc(fileName);
+    QVERIFY(doc);
+
+    OCGs *ocgs = doc->getOptContentConfig();
+    QVERIFY(ocgs);
+
+    XRef *xref = doc->getXRef();
+
+    Object obj;
+
+    // In this test, both Ref(21,0) and Ref(28,0) start On,
+    // based on the file settings
+    Object ref21obj({ 21, 0 });
+    Ref ref21 = ref21obj.getRef();
+    OptionalContentGroup *ocgA = ocgs->findOcgByRef(ref21);
+    QVERIFY(ocgA);
+
+    QVERIFY((ocgA->getName()->cmp("A")) == 0);
+    QCOMPARE(ocgA->getState(), OptionalContentGroup::On);
+
+    Object ref28obj({ 28, 0 });
+    Ref ref28 = ref28obj.getRef();
+    OptionalContentGroup *ocgB = ocgs->findOcgByRef(ref28);
+    QVERIFY(ocgB);
+
+    QVERIFY((ocgB->getName()->cmp("B")) == 0);
+    QCOMPARE(ocgB->getState(), OptionalContentGroup::On);
+
+    // turn one Off
+    ocgA->setState(OptionalContentGroup::Off);
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // Same again, looking for any leaks or dubious free()'s
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    obj = xref->fetch(29, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    obj = xref->fetch(36, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    obj = xref->fetch(43, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(50, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(57, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(64, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(71, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // Turn the other one off as well (i.e. both are Off)
+    ocgB->setState(OptionalContentGroup::Off);
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // Same again, looking for any leaks or dubious free()'s
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    obj = xref->fetch(29, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    obj = xref->fetch(36, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    obj = xref->fetch(43, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(50, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(57, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(64, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(71, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // Turn the first one on again (21 is On, 28 is Off)
+    ocgA->setState(OptionalContentGroup::On);
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // Same again, looking for any leaks or dubious free()'s
+    obj = xref->fetch(22, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    obj = xref->fetch(29, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    obj = xref->fetch(36, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    obj = xref->fetch(43, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    obj = xref->fetch(50, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(57, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), true);
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(64, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    obj = xref->fetch(71, 0);
+    QVERIFY(obj.isDict());
+    QCOMPARE(ocgs->optContentIsVisible(&obj), false);
+
+    delete doc;
+    globalParams.reset();
+}
+
+void TestOptionalContent::checkRadioButtons()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/ClarityOCGs.pdf");
+    QVERIFY(doc);
+
+    QVERIFY(doc->hasOptionalContent());
+
+    Poppler::OptContentModel *optContent = doc->optionalContentModel();
+    QModelIndex index;
+
+    index = optContent->index(0, 0, QModelIndex());
+    QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Languages"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+
+    // These are sub-items of the "Languages" label
+    QModelIndex subindex = optContent->index(0, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Checked);
+
+    subindex = optContent->index(1, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+
+    subindex = optContent->index(2, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+
+    // RBGroup of languages, so turning on Japanese should turn off English
+    QVERIFY(optContent->setData(subindex, QVariant(true), Qt::CheckStateRole));
+
+    subindex = optContent->index(0, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    subindex = optContent->index(2, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Checked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::On);
+
+    subindex = optContent->index(1, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    // and turning on French should turn off Japanese
+    QVERIFY(optContent->setData(subindex, QVariant(true), Qt::CheckStateRole));
+
+    subindex = optContent->index(0, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    subindex = optContent->index(2, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    subindex = optContent->index(1, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Checked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::On);
+
+    // and turning off French should leave them all off
+    QVERIFY(optContent->setData(subindex, QVariant(false), Qt::CheckStateRole));
+
+    subindex = optContent->index(0, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    subindex = optContent->index(2, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    subindex = optContent->index(1, 0, index);
+    QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
+    QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
+    QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestOptionalContent)
+
+#include "check_optcontent.moc"
diff --git a/qt6/tests/check_outline.cpp b/qt6/tests/check_outline.cpp
new file mode 100644
index 0000000..dc03a13
--- /dev/null
+++ b/qt6/tests/check_outline.cpp
@@ -0,0 +1,50 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+#include <memory>
+
+class TestOutline : public QObject
+{
+    Q_OBJECT
+public:
+    TestOutline(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkOutline_xr02();
+};
+
+void TestOutline::checkOutline_xr02()
+{
+    std::unique_ptr<Poppler::Document> document { Poppler::Document::load(TESTDATADIR "/unittestcases/xr02.pdf") };
+    QVERIFY(document.get());
+
+    const auto outline = document->outline();
+    QCOMPARE(outline.size(), 2);
+
+    const auto &foo = outline[0];
+    QVERIFY(!foo.isNull());
+    QCOMPARE(foo.name(), QStringLiteral("foo"));
+    QCOMPARE(foo.isOpen(), false);
+    const auto fooDest = foo.destination();
+    QVERIFY(!fooDest.isNull());
+    QCOMPARE(fooDest->pageNumber(), 1);
+    QVERIFY(foo.externalFileName().isEmpty());
+    QVERIFY(foo.uri().isEmpty());
+    QVERIFY(!foo.hasChildren());
+    QVERIFY(foo.children().isEmpty());
+
+    const auto &bar = outline[1];
+    QVERIFY(!bar.isNull());
+    QCOMPARE(bar.name(), QStringLiteral("bar"));
+    QCOMPARE(bar.isOpen(), false);
+    const auto barDest = bar.destination();
+    QVERIFY(!barDest.isNull());
+    QCOMPARE(barDest->pageNumber(), 2);
+    QVERIFY(bar.externalFileName().isEmpty());
+    QVERIFY(bar.uri().isEmpty());
+    QVERIFY(!bar.hasChildren());
+    QVERIFY(bar.children().isEmpty());
+}
+
+QTEST_GUILESS_MAIN(TestOutline)
+#include "check_outline.moc"
diff --git a/qt6/tests/check_pagelabelinfo.cpp b/qt6/tests/check_pagelabelinfo.cpp
new file mode 100644
index 0000000..042f1cf
--- /dev/null
+++ b/qt6/tests/check_pagelabelinfo.cpp
@@ -0,0 +1,72 @@
+#include <QtTest/QtTest>
+
+#include <poppler-private.h>
+
+#include "PageLabelInfo_p.h"
+
+#include "config.h"
+
+class TestPageLabelInfo : public QObject
+{
+    Q_OBJECT
+public:
+    TestPageLabelInfo(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void testFromDecimal();
+    void testFromDecimalUnicode();
+    void testToRoman();
+    void testFromRoman();
+    void testToLatin();
+    void testFromLatin();
+};
+
+void TestPageLabelInfo::testFromDecimal()
+{
+    std::string str { "2342" };
+    const auto res = fromDecimal(str.data(), str.data() + str.size(), false);
+    QCOMPARE(res.first, 2342);
+    QCOMPARE(res.second, true);
+}
+
+void TestPageLabelInfo::testFromDecimalUnicode()
+{
+    std::unique_ptr<GooString> str(Poppler::QStringToUnicodeGooString(QString::fromLocal8Bit("2342")));
+    const auto res = fromDecimal(str->c_str(), str->c_str() + str->getLength(), str->hasUnicodeMarker());
+#ifndef HAVE_CODECVT
+    QEXPECT_FAIL("", "unicode text to index fails without codecvt", Continue);
+#endif
+    QCOMPARE(res.first, 2342);
+#ifndef HAVE_CODECVT
+    QEXPECT_FAIL("", "unicode text to index fails without codecvt", Continue);
+#endif
+    QCOMPARE(res.second, true);
+}
+
+void TestPageLabelInfo::testToRoman()
+{
+    GooString str;
+    toRoman(177, &str, false);
+    QCOMPARE(str.c_str(), "clxxvii");
+}
+
+void TestPageLabelInfo::testFromRoman()
+{
+    GooString roman("clxxvii");
+    QCOMPARE(fromRoman(roman.c_str()), 177);
+}
+
+void TestPageLabelInfo::testToLatin()
+{
+    GooString str;
+    toLatin(54, &str, false);
+    QCOMPARE(str.c_str(), "bbb");
+}
+
+void TestPageLabelInfo::testFromLatin()
+{
+    GooString latin("ddd");
+    QCOMPARE(fromLatin(latin.c_str()), 56);
+}
+
+QTEST_GUILESS_MAIN(TestPageLabelInfo)
+#include "check_pagelabelinfo.moc"
diff --git a/qt6/tests/check_pagelayout.cpp b/qt6/tests/check_pagelayout.cpp
new file mode 100644
index 0000000..01a2ec8
--- /dev/null
+++ b/qt6/tests/check_pagelayout.cpp
@@ -0,0 +1,50 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+class TestPageLayout : public QObject
+{
+    Q_OBJECT
+public:
+    TestPageLayout(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkNone();
+    void checkSingle();
+    void checkFacing();
+};
+
+void TestPageLayout::checkNone()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/UseNone.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageLayout(), Poppler::Document::NoLayout);
+
+    delete doc;
+}
+
+void TestPageLayout::checkSingle()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/FullScreen.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageLayout(), Poppler::Document::SinglePage);
+
+    delete doc;
+}
+
+void TestPageLayout::checkFacing()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/doublepage.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageLayout(), Poppler::Document::TwoPageRight);
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestPageLayout)
+#include "check_pagelayout.moc"
diff --git a/qt6/tests/check_pagemode.cpp b/qt6/tests/check_pagemode.cpp
new file mode 100644
index 0000000..4e830cc
--- /dev/null
+++ b/qt6/tests/check_pagemode.cpp
@@ -0,0 +1,74 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+class TestPageMode : public QObject
+{
+    Q_OBJECT
+public:
+    TestPageMode(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkNone();
+    void checkFullScreen();
+    void checkAttachments();
+    void checkThumbs();
+    void checkOC();
+};
+
+void TestPageMode::checkNone()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/UseNone.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageMode(), Poppler::Document::UseNone);
+
+    delete doc;
+}
+
+void TestPageMode::checkFullScreen()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/FullScreen.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageMode(), Poppler::Document::FullScreen);
+
+    delete doc;
+}
+
+void TestPageMode::checkAttachments()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/UseAttachments.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageMode(), Poppler::Document::UseAttach);
+
+    delete doc;
+}
+
+void TestPageMode::checkThumbs()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/UseThumbs.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageMode(), Poppler::Document::UseThumbs);
+
+    delete doc;
+}
+
+void TestPageMode::checkOC()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/UseOC.pdf");
+    QVERIFY(doc);
+
+    QCOMPARE(doc->pageMode(), Poppler::Document::UseOC);
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestPageMode)
+#include "check_pagemode.moc"
diff --git a/qt6/tests/check_password.cpp b/qt6/tests/check_password.cpp
new file mode 100644
index 0000000..20b2bbd
--- /dev/null
+++ b/qt6/tests/check_password.cpp
@@ -0,0 +1,115 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+class TestPassword : public QObject
+{
+    Q_OBJECT
+public:
+    TestPassword(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void password1();
+    void password1a();
+    void password2();
+    void password2a();
+    void password2b();
+    void password3();
+    void password4();
+    void password4b();
+};
+
+// BUG:4557
+void TestPassword::password1()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/Gday garçon - open.pdf"), "", QString::fromUtf8("garçon").toLatin1()); // clazy:exclude=qstring-allocations
+    QVERIFY(doc);
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+void TestPassword::password1a()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/Gday garçon - open.pdf")); // clazy:exclude=qstring-allocations
+    QVERIFY(doc);
+    QVERIFY(doc->isLocked());
+    QVERIFY(!doc->unlock("", QString::fromUtf8("garçon").toLatin1())); // clazy:exclude=qstring-allocations
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+void TestPassword::password2()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/Gday garçon - owner.pdf"), QString::fromUtf8("garçon").toLatin1(), ""); // clazy:exclude=qstring-allocations
+    QVERIFY(doc);
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+void TestPassword::password2a()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/Gday garçon - owner.pdf"), QString::fromUtf8("garçon").toLatin1()); // clazy:exclude=qstring-allocations
+    QVERIFY(doc);
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+void TestPassword::password2b()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/Gday garçon - owner.pdf"));
+    QVERIFY(doc);
+    QVERIFY(!doc->isLocked());
+    QVERIFY(!doc->unlock(QString::fromUtf8("garçon").toLatin1(), "")); // clazy:exclude=qstring-allocations
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+void TestPassword::password3()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/PasswordEncrypted.pdf"));
+    QVERIFY(doc);
+    QVERIFY(doc->isLocked());
+    QVERIFY(!doc->unlock("", "password"));
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+// issue 690
+void TestPassword::password4()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/encrypted-256.pdf"));
+    QVERIFY(doc);
+    QVERIFY(doc->isLocked());
+    QVERIFY(!doc->unlock("owner-secret", ""));
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+// issue 690
+void TestPassword::password4b()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(QString::fromUtf8(TESTDATADIR "/unittestcases/encrypted-256.pdf"));
+    QVERIFY(doc);
+    QVERIFY(doc->isLocked());
+    QVERIFY(!doc->unlock("", "user-secret"));
+    QVERIFY(!doc->isLocked());
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestPassword)
+#include "check_password.moc"
diff --git a/qt6/tests/check_permissions.cpp b/qt6/tests/check_permissions.cpp
new file mode 100644
index 0000000..87a3355
--- /dev/null
+++ b/qt6/tests/check_permissions.cpp
@@ -0,0 +1,45 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+class TestPermissions : public QObject
+{
+    Q_OBJECT
+public:
+    TestPermissions(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void permissions1();
+};
+
+void TestPermissions::permissions1()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
+    QVERIFY(doc);
+
+    // we are allowed to print
+    QVERIFY(doc->okToPrint());
+
+    // we are not allowed to change
+    QVERIFY(!(doc->okToChange()));
+
+    // we are not allowed to copy or extract content
+    QVERIFY(!(doc->okToCopy()));
+
+    // we are not allowed to print at high resolution
+    QVERIFY(!(doc->okToPrintHighRes()));
+
+    // we are not allowed to fill forms
+    QVERIFY(!(doc->okToFillForm()));
+
+    // we are allowed to extract content for accessibility
+    QVERIFY(doc->okToExtractForAccessibility());
+
+    // we are allowed to assemble this document
+    QVERIFY(doc->okToAssemble());
+
+    delete doc;
+}
+
+QTEST_GUILESS_MAIN(TestPermissions)
+#include "check_permissions.moc"
diff --git a/qt6/tests/check_search.cpp b/qt6/tests/check_search.cpp
new file mode 100644
index 0000000..56e5210
--- /dev/null
+++ b/qt6/tests/check_search.cpp
@@ -0,0 +1,282 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+
+class TestSearch : public QObject
+{
+    Q_OBJECT
+public:
+    TestSearch(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void bug7063();
+    void testNextAndPrevious();
+    void testWholeWordsOnly();
+    void testIgnoreDiacritics();
+    void testRussianSearch(); // Issue #743
+    void testDeseretSearch(); // Issue #853
+};
+
+void TestSearch::bug7063()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/bug7063.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    double rectLeft = 0.0, rectTop = 0.0, rectRight = page->pageSizeF().width(), rectBottom = page->pageSizeF().height();
+
+    QCOMPARE(page->search(QStringLiteral("non-ascii:"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true);
+
+    QCOMPARE(page->search(QStringLiteral("Ascii"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), false);
+    QCOMPARE(page->search(QStringLiteral("Ascii"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop, Poppler::Page::IgnoreCase), true);
+
+    QCOMPARE(page->search(QStringLiteral("latin1:"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), false);
+
+    QCOMPARE(page->search(QString::fromUtf8("é"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("à"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("ç"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("search \"é\", \"à\" or \"ç\""), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("¥µ©"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("¥©"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), false); // clazy:exclude=qstring-allocations
+
+    QCOMPARE(page->search(QStringLiteral("non-ascii:"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true);
+
+    QCOMPARE(page->search(QStringLiteral("Ascii"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), false);
+    QCOMPARE(page->search(QStringLiteral("Ascii"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop, Poppler::Page::IgnoreCase), true);
+
+    QCOMPARE(page->search(QStringLiteral("latin1:"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), false);
+
+    QCOMPARE(page->search(QString::fromUtf8("é"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("à"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("ç"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("search \"é\", \"à\" or \"ç\""), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("¥µ©"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("¥©"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), false); // clazy:exclude=qstring-allocations
+}
+
+void TestSearch::testNextAndPrevious()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/xr01.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    double rectLeft = 0.0, rectTop = 0.0, rectRight = page->pageSizeF().width(), rectBottom = page->pageSizeF().height();
+
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), true);
+    QVERIFY(qAbs(rectLeft - 171.46) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 139.81) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), true);
+    QVERIFY(qAbs(rectLeft - 171.46) < 0.01);
+    QVERIFY(qAbs(rectTop - 139.81) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), false);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 139.81) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), true);
+    QVERIFY(qAbs(rectLeft - 171.46) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), false);
+
+    rectLeft = 0.0, rectTop = 0.0, rectRight = page->pageSizeF().width(), rectBottom = page->pageSizeF().height();
+
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::FromTop), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), true);
+    QVERIFY(qAbs(rectLeft - 171.46) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 139.81) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), true);
+    QVERIFY(qAbs(rectLeft - 171.46) < 0.01);
+    QVERIFY(qAbs(rectTop - 139.81) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::NextResult), false);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 139.81) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), true);
+    QVERIFY(qAbs(rectLeft - 171.46) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), true);
+    QVERIFY(qAbs(rectLeft - 161.44) < 0.01);
+    QVERIFY(qAbs(rectTop - 127.85) < 0.01);
+    QVERIFY(qAbs(rectRight - rectLeft - 6.70) < 0.01);
+    QVERIFY(qAbs(rectBottom - rectTop - 8.85) < 0.01);
+    QCOMPARE(page->search(QStringLiteral("is"), rectLeft, rectTop, rectRight, rectBottom, Poppler::Page::PreviousResult), false);
+}
+
+void TestSearch::testWholeWordsOnly()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/WithActualText.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    const Poppler::Page::SearchDirection direction = Poppler::Page::FromTop;
+
+    const Poppler::Page::SearchFlags mode0;
+    const Poppler::Page::SearchFlags mode1 = Poppler::Page::IgnoreCase;
+    const Poppler::Page::SearchFlags mode2 = Poppler::Page::WholeWords;
+    const Poppler::Page::SearchFlags mode3 = Poppler::Page::IgnoreCase | Poppler::Page::WholeWords;
+
+    double left, top, right, bottom;
+
+    QCOMPARE(page->search(QStringLiteral("brown"), left, top, right, bottom, direction, mode0), true);
+    QCOMPARE(page->search(QStringLiteral("brOwn"), left, top, right, bottom, direction, mode0), false);
+
+    QCOMPARE(page->search(QStringLiteral("brOwn"), left, top, right, bottom, direction, mode1), true);
+    QCOMPARE(page->search(QStringLiteral("brawn"), left, top, right, bottom, direction, mode1), false);
+
+    QCOMPARE(page->search(QStringLiteral("brown"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("own"), left, top, right, bottom, direction, mode2), false);
+
+    QCOMPARE(page->search(QStringLiteral("brOwn"), left, top, right, bottom, direction, mode3), true);
+    QCOMPARE(page->search(QStringLiteral("Own"), left, top, right, bottom, direction, mode3), false);
+}
+
+void TestSearch::testIgnoreDiacritics()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/Issue637.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    const Poppler::Page::SearchDirection direction = Poppler::Page::FromTop;
+
+    const Poppler::Page::SearchFlags mode0;
+    const Poppler::Page::SearchFlags mode1 = Poppler::Page::IgnoreDiacritics;
+    const Poppler::Page::SearchFlags mode2 = Poppler::Page::IgnoreDiacritics | Poppler::Page::IgnoreCase;
+    const Poppler::Page::SearchFlags mode3 = Poppler::Page::IgnoreDiacritics | Poppler::Page::IgnoreCase | Poppler::Page::WholeWords;
+    const Poppler::Page::SearchFlags mode4 = Poppler::Page::IgnoreCase | Poppler::Page::WholeWords;
+
+    double left, top, right, bottom;
+
+    // Test pdf (Issue637.pdf) just contains the following three lines:
+    // La cigüeña voló sobre nuestras cabezas.
+    // La cigogne a survolé nos têtes.
+    // Der Storch flog über unsere Köpfe hinweg.
+
+    QCOMPARE(page->search(QStringLiteral("ciguena"), left, top, right, bottom, direction, mode0), false);
+    QCOMPARE(page->search(QStringLiteral("Ciguena"), left, top, right, bottom, direction, mode1), false);
+    QCOMPARE(page->search(QStringLiteral("ciguena"), left, top, right, bottom, direction, mode1), true);
+    QCOMPARE(page->search(QString::fromUtf8("cigüeña"), left, top, right, bottom, direction, mode1), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("cigüena"), left, top, right, bottom, direction, mode1), false); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("Cigüeña"), left, top, right, bottom, direction, mode1), false); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QStringLiteral("Ciguena"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("ciguena"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("Ciguena"), left, top, right, bottom, direction, mode3), true);
+    QCOMPARE(page->search(QStringLiteral("ciguena"), left, top, right, bottom, direction, mode3), true);
+
+    QCOMPARE(page->search(QString::fromUtf8("cigüeña"), left, top, right, bottom, direction, mode4), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("Cigüeña"), left, top, right, bottom, direction, mode4), true); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QString::fromUtf8("cigüena"), left, top, right, bottom, direction, mode4), false); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(QStringLiteral("Ciguena"), left, top, right, bottom, direction, mode4), false);
+
+    QCOMPARE(page->search(QStringLiteral("kopfe"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("kopfe"), left, top, right, bottom, direction, mode3), true);
+    QCOMPARE(page->search(QStringLiteral("uber"), left, top, right, bottom, direction, mode0), false);
+    QCOMPARE(page->search(QStringLiteral("uber"), left, top, right, bottom, direction, mode1), true);
+    QCOMPARE(page->search(QStringLiteral("uber"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("uber"), left, top, right, bottom, direction, mode3), true);
+
+    QCOMPARE(page->search(QStringLiteral("vole"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("vole"), left, top, right, bottom, direction, mode3), false);
+    QCOMPARE(page->search(QStringLiteral("survole"), left, top, right, bottom, direction, mode3), true);
+    QCOMPARE(page->search(QStringLiteral("tete"), left, top, right, bottom, direction, mode3), false);
+    QCOMPARE(page->search(QStringLiteral("tete"), left, top, right, bottom, direction, mode2), true);
+
+    QCOMPARE(page->search(QStringLiteral("La Ciguena Volo"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("Survole Nos Tetes"), left, top, right, bottom, direction, mode2), true);
+    QCOMPARE(page->search(QStringLiteral("Uber Unsere Kopfe"), left, top, right, bottom, direction, mode2), true);
+}
+
+void TestSearch::testRussianSearch()
+{
+    // Test for issue #743
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/russian.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    const Poppler::Page::SearchDirection direction = Poppler::Page::FromTop;
+
+    const Poppler::Page::SearchFlags mode0 = Poppler::Page::NoSearchFlags;
+    const Poppler::Page::SearchFlags mode1 = Poppler::Page::IgnoreDiacritics;
+    const Poppler::Page::SearchFlags mode2 = Poppler::Page::IgnoreDiacritics | Poppler::Page::IgnoreCase;
+    const Poppler::Page::SearchFlags mode0W = mode0 | Poppler::Page::WholeWords;
+    const Poppler::Page::SearchFlags mode1W = mode1 | Poppler::Page::WholeWords;
+    const Poppler::Page::SearchFlags mode2W = mode2 | Poppler::Page::WholeWords;
+
+    double l, t, r, b; // left, top, right, bottom
+
+    // In the searched page 5, these two words do exist: простой and Простой
+    const QString str = QString::fromUtf8("простой"); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(str, l, t, r, b, direction, mode0), true);
+    QCOMPARE(page->search(str, l, t, r, b, direction, mode1), true);
+    QCOMPARE(page->search(str, l, t, r, b, direction, mode2), true);
+    QCOMPARE(page->search(str, l, t, r, b, direction, mode0W), true);
+    QCOMPARE(page->search(str, l, t, r, b, direction, mode1W), true);
+    QCOMPARE(page->search(str, l, t, r, b, direction, mode2W), true);
+}
+
+void TestSearch::testDeseretSearch()
+{
+    QScopedPointer<Poppler::Document> document(Poppler::Document::load(TESTDATADIR "/unittestcases/deseret.pdf"));
+    QVERIFY(document);
+
+    QScopedPointer<Poppler::Page> page(document->page(0));
+    QVERIFY(page);
+
+    double l, t, r, b; // left, top, right, bottom
+
+    const QString str = QString::fromUtf8("𐐐𐐯𐑊𐐬"); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(str, l, t, r, b, Poppler::Page::FromTop, Poppler::Page::NoSearchFlags), true);
+
+    const QString str2 = QString::fromUtf8("𐐸𐐯𐑊𐐬"); // clazy:exclude=qstring-allocations
+    QCOMPARE(page->search(str2, l, t, r, b, Poppler::Page::FromTop, Poppler::Page::IgnoreCase), true);
+}
+
+QTEST_GUILESS_MAIN(TestSearch)
+#include "check_search.moc"
diff --git a/qt6/tests/check_strings.cpp b/qt6/tests/check_strings.cpp
new file mode 100644
index 0000000..fa7c6ba
--- /dev/null
+++ b/qt6/tests/check_strings.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2010, 2011, Pino Toscano <pino@kde.org>
+ *
+ * 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 <QtTest/QtTest>
+
+#include <poppler-qt6.h>
+#include <poppler-private.h>
+
+#include <GlobalParams.h>
+
+Q_DECLARE_METATYPE(GooString *)
+Q_DECLARE_METATYPE(Unicode *)
+
+class TestStrings : public QObject
+{
+    Q_OBJECT
+
+public:
+    TestStrings(QObject *parent = nullptr) : QObject(parent) { }
+
+private slots:
+    void initTestCase();
+    void cleanupTestCase();
+    void check_unicodeToQString_data();
+    void check_unicodeToQString();
+    void check_UnicodeParsedString_data();
+    void check_UnicodeParsedString();
+    void check_QStringToUnicodeGooString_data();
+    void check_QStringToUnicodeGooString();
+    void check_QStringToGooString_data();
+    void check_QStringToGooString();
+
+private:
+    GooString *newGooString(const char *s);
+    GooString *newGooString(const char *s, int l);
+
+    QVector<GooString *> m_gooStrings;
+};
+
+void TestStrings::initTestCase()
+{
+    qRegisterMetaType<GooString *>("GooString*");
+    qRegisterMetaType<Unicode *>("Unicode*");
+
+    globalParams = std::make_unique<GlobalParams>();
+}
+
+void TestStrings::cleanupTestCase()
+{
+    qDeleteAll(m_gooStrings);
+
+    globalParams.reset();
+}
+
+void TestStrings::check_unicodeToQString_data()
+{
+    QTest::addColumn<Unicode *>("data");
+    QTest::addColumn<int>("length");
+    QTest::addColumn<QString>("result");
+
+    {
+        const int l = 1;
+        Unicode *u = new Unicode[l];
+        u[0] = int('a');
+        QTest::newRow("a") << u << l << QStringLiteral("a");
+    }
+    {
+        const int l = 1;
+        Unicode *u = new Unicode[l];
+        u[0] = 0x0161;
+        QTest::newRow("\u0161") << u << l << QStringLiteral("\u0161");
+    }
+    {
+        const int l = 2;
+        Unicode *u = new Unicode[l];
+        u[0] = int('a');
+        u[1] = int('b');
+        QTest::newRow("ab") << u << l << QStringLiteral("ab");
+    }
+    {
+        const int l = 2;
+        Unicode *u = new Unicode[l];
+        u[0] = int('a');
+        u[1] = 0x0161;
+        QTest::newRow("a\u0161") << u << l << QStringLiteral("a\u0161");
+    }
+    {
+        const int l = 2;
+        Unicode *u = new Unicode[l];
+        u[0] = 0x5c01;
+        u[1] = 0x9762;
+        QTest::newRow("\xe5\xb0\x81\xe9\x9d\xa2") << u << l << QStringLiteral("封面");
+    }
+    {
+        const int l = 3;
+        Unicode *u = new Unicode[l];
+        u[0] = 0x5c01;
+        u[1] = 0x9762;
+        u[2] = 0x0;
+        QTest::newRow("\xe5\xb0\x81\xe9\x9d\xa2 + 0") << u << l << QStringLiteral("封面");
+    }
+}
+
+void TestStrings::check_unicodeToQString()
+{
+    QFETCH(Unicode *, data);
+    QFETCH(int, length);
+    QFETCH(QString, result);
+
+    QCOMPARE(Poppler::unicodeToQString(data, length), result);
+
+    delete[] data;
+}
+
+void TestStrings::check_UnicodeParsedString_data()
+{
+    QTest::addColumn<GooString *>("string");
+    QTest::addColumn<QString>("result");
+
+    // non-unicode strings
+    QTest::newRow("<empty>") << newGooString("") << QString();
+    QTest::newRow("a") << newGooString("a") << QStringLiteral("a");
+    QTest::newRow("ab") << newGooString("ab") << QStringLiteral("ab");
+    QTest::newRow("~") << newGooString("~") << QStringLiteral("~");
+    QTest::newRow("test string") << newGooString("test string") << QStringLiteral("test string");
+
+    // unicode strings
+    QTest::newRow("<unicode marks>") << newGooString("\xFE\xFF") << QString();
+    QTest::newRow("U a") << newGooString("\xFE\xFF\0a", 4) << QStringLiteral("a");
+    QTest::newRow("U ~") << newGooString("\xFE\xFF\0~", 4) << QStringLiteral("~");
+    QTest::newRow("U aa") << newGooString("\xFE\xFF\0a\0a", 6) << QStringLiteral("aa");
+    QTest::newRow("U \xC3\x9F") << newGooString("\xFE\xFF\0\xDF", 4) << QStringLiteral("ß");
+    QTest::newRow("U \xC3\x9F\x61") << newGooString("\xFE\xFF\0\xDF\0\x61", 6) << QStringLiteral("ßa");
+    QTest::newRow("U \xC5\xA1") << newGooString("\xFE\xFF\x01\x61", 4) << QStringLiteral("š");
+    QTest::newRow("U \xC5\xA1\x61") << newGooString("\xFE\xFF\x01\x61\0\x61", 6) << QStringLiteral("ša");
+    QTest::newRow("test string") << newGooString("\xFE\xFF\0t\0e\0s\0t\0 \0s\0t\0r\0i\0n\0g", 24) << QStringLiteral("test string");
+    QTest::newRow("UTF16-LE") << newGooString("\xFF\xFE\xDA\x00\x6E\x00\xEE\x00\x63\x00\xF6\x00\x64\x00\xE9\x00\x51\x75", 18) << QStringLiteral("Únîcödé畑");
+}
+
+void TestStrings::check_UnicodeParsedString()
+{
+    QFETCH(GooString *, string);
+    QFETCH(QString, result);
+
+    QCOMPARE(Poppler::UnicodeParsedString(string), result);
+}
+
+void TestStrings::check_QStringToUnicodeGooString_data()
+{
+    QTest::addColumn<QString>("string");
+    QTest::addColumn<QByteArray>("result");
+
+    QTest::newRow("<null>") << QString() << QByteArray("");
+    QTest::newRow("<empty>") << QString(QLatin1String("")) << QByteArray("");
+    QTest::newRow("a") << QStringLiteral("a") << QByteArray("\0a", 2);
+    QTest::newRow("ab") << QStringLiteral("ab") << QByteArray("\0a\0b", 4);
+    QTest::newRow("test string") << QStringLiteral("test string") << QByteArray("\0t\0e\0s\0t\0 \0s\0t\0r\0i\0n\0g", 22);
+    QTest::newRow("\xC3\x9F") << QStringLiteral("ß") << QByteArray("\0\xDF", 2);
+    QTest::newRow("\xC3\x9F\x61") << QStringLiteral("ßa") << QByteArray("\0\xDF\0\x61", 4);
+}
+
+void TestStrings::check_QStringToUnicodeGooString()
+{
+    QFETCH(QString, string);
+    QFETCH(QByteArray, result);
+
+    GooString *goo = Poppler::QStringToUnicodeGooString(string);
+    QVERIFY(goo->hasUnicodeMarker());
+    QCOMPARE(goo->getLength(), string.length() * 2 + 2);
+    QCOMPARE(result, QByteArray::fromRawData(goo->c_str() + 2, goo->getLength() - 2));
+
+    delete goo;
+}
+
+void TestStrings::check_QStringToGooString_data()
+{
+    QTest::addColumn<QString>("string");
+    QTest::addColumn<GooString *>("result");
+
+    QTest::newRow("<null>") << QString() << newGooString("");
+    QTest::newRow("<empty>") << QString(QLatin1String("")) << newGooString("");
+    QTest::newRow("a") << QStringLiteral("a") << newGooString("a");
+    QTest::newRow("ab") << QStringLiteral("ab") << newGooString("ab");
+}
+
+void TestStrings::check_QStringToGooString()
+{
+    QFETCH(QString, string);
+    QFETCH(GooString *, result);
+
+    GooString *goo = Poppler::QStringToGooString(string);
+    QCOMPARE(goo->c_str(), result->c_str());
+
+    delete goo;
+}
+
+GooString *TestStrings::newGooString(const char *s)
+{
+    GooString *goo = new GooString(s);
+    m_gooStrings.append(goo);
+    return goo;
+}
+
+GooString *TestStrings::newGooString(const char *s, int l)
+{
+    GooString *goo = new GooString(s, l);
+    m_gooStrings.append(goo);
+    return goo;
+}
+
+QTEST_GUILESS_MAIN(TestStrings)
+
+#include "check_strings.moc"
diff --git a/qt6/tests/check_stroke_opacity.cpp b/qt6/tests/check_stroke_opacity.cpp
new file mode 100644
index 0000000..05b7dff
--- /dev/null
+++ b/qt6/tests/check_stroke_opacity.cpp
@@ -0,0 +1,96 @@
+#include <memory>
+
+#include <QtTest/QtTest>
+#include <QtCore/QDebug>
+#include <QImage>
+
+#include <poppler-qt6.h>
+
+// Unit tests for rendering axial shadings without full opacity
+class TestStrokeOpacity : public QObject
+{
+    Q_OBJECT
+public:
+    TestStrokeOpacity(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void checkStrokeOpacity_data();
+    void checkStrokeOpacity();
+};
+
+void TestStrokeOpacity::checkStrokeOpacity_data()
+{
+    QTest::addColumn<int>("backendType");
+
+    QTest::newRow("splash") << (int)Poppler::Document::SplashBackend;
+    QTest::newRow("qpainter") << (int)Poppler::Document::ArthurBackend;
+}
+
+void TestStrokeOpacity::checkStrokeOpacity()
+{
+    QFETCH(int, backendType);
+
+    auto doc = std::unique_ptr<Poppler::Document>(Poppler::Document::load(TESTDATADIR "/unittestcases/stroke-alpha-pattern.pdf"));
+    QVERIFY(doc != nullptr);
+
+    doc->setRenderBackend((Poppler::Document::RenderBackend)backendType);
+
+    // BUG: For some reason splash gets the opacity wrong when antialiasing is switched off
+    if (backendType == (int)Poppler::Document::SplashBackend) {
+        doc->setRenderHint(Poppler::Document::Antialiasing, true);
+    }
+
+    const auto page = std::unique_ptr<Poppler::Page>(doc->page(0));
+    QVERIFY(page != nullptr);
+
+    // Render (at low resolution and with cropped marging)
+    QImage image = page->renderToImage(36, 36, 40, 50, 200, 230);
+
+    // The actual tests start here
+
+    // Allow a tolerance.
+    int tolerance;
+    auto approximatelyEqual = [&tolerance](QRgb c0, const QColor &c1) {
+        return std::abs(qAlpha(c0) - c1.alpha()) <= tolerance && std::abs(qRed(c0) - c1.red()) <= tolerance && std::abs(qGreen(c0) - c1.green()) <= tolerance && std::abs(qBlue(c0) - c1.blue()) <= tolerance;
+    };
+
+    // At the lower left of the test document is a square with an axial shading,
+    // which should be rendered with opacity 0.25.
+    // Check that with a sample pixel
+    auto pixel = image.pixel(70, 160);
+
+    // Splash and QPainter backends implement shadings slightly differently,
+    // hence we cannot expect to get precisely the same colors.
+    tolerance = 2;
+    QVERIFY(approximatelyEqual(pixel, QColor(253, 233, 196, 255)));
+
+    // At the upper left of the test document is a stroked square with an axial shading.
+    // This is implemented by filling a clip region defined by a stroke outline.
+    // Check whether the backend really only renders the stroke, not the region
+    // surrounded by the stroke.
+    auto pixelUpperLeftInterior = image.pixel(70, 70);
+
+    tolerance = 0;
+    QVERIFY(approximatelyEqual(pixelUpperLeftInterior, Qt::white));
+
+    // Now check whether that stroke is semi-transparent.
+    // Bug https://gitlab.freedesktop.org/poppler/poppler/-/issues/178
+    auto pixelUpperLeftOnStroke = image.pixel(70, 20);
+
+    tolerance = 2;
+    QVERIFY(approximatelyEqual(pixelUpperLeftOnStroke, QColor(253, 233, 196, 255)));
+
+    // At the upper right there is a semi-transparent stroked red square
+    // a) Make sure that the color is correct.
+    auto pixelUpperRightOnStroke = image.pixel(130, 20);
+
+    tolerance = 0;
+    QVERIFY(approximatelyEqual(pixelUpperRightOnStroke, QColor(246, 196, 206, 255)));
+
+    // b) Make sure that it is really stroked, not filled
+    auto pixelUpperRightInterior = image.pixel(130, 50);
+    QVERIFY(approximatelyEqual(pixelUpperRightInterior, Qt::white));
+}
+
+QTEST_GUILESS_MAIN(TestStrokeOpacity)
+
+#include "check_stroke_opacity.moc"
diff --git a/qt6/tests/check_utf_conversion.cpp b/qt6/tests/check_utf_conversion.cpp
new file mode 100644
index 0000000..f28829f
--- /dev/null
+++ b/qt6/tests/check_utf_conversion.cpp
@@ -0,0 +1,147 @@
+#include <QtCore/QScopedPointer>
+#include <QtTest/QtTest>
+
+#include <poppler-private.h>
+
+#include <cstring>
+
+#include "GlobalParams.h"
+#include "UnicodeTypeTable.h"
+#include "UTF.h"
+
+class TestUTFConversion : public QObject
+{
+    Q_OBJECT
+public:
+    TestUTFConversion(QObject *parent = nullptr) : QObject(parent) { }
+private slots:
+    void testUTF_data();
+    void testUTF();
+    void testUnicodeToAscii7();
+};
+
+static bool compare(const char *a, const char *b)
+{
+    return strcmp(a, b) == 0;
+}
+
+static bool compare(const uint16_t *a, const uint16_t *b)
+{
+    while (*a && *b) {
+        if (*a++ != *b++)
+            return false;
+    }
+    return *a == *b;
+}
+
+static bool compare(const Unicode *a, const char *b, int len)
+{
+    for (int i = 0; i < len; i++) {
+        if (a[i] != (Unicode)b[i])
+            return false;
+    }
+
+    return *a == (Unicode)*b;
+}
+
+void TestUTFConversion::testUTF_data()
+{
+    QTest::addColumn<QString>("s");
+
+    QTest::newRow("<empty>") << QString(QLatin1String(""));
+    QTest::newRow("a") << QStringLiteral("a");
+    QTest::newRow("abc") << QStringLiteral("abc");
+    QTest::newRow("Latin") << QStringLiteral("Vitrum edere possum; mihi non nocet");
+    QTest::newRow("Greek") << QStringLiteral("Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα");
+    QTest::newRow("Icelandic") << QStringLiteral("Ég get etið gler án þess að meiða mig");
+    QTest::newRow("Russian") << QStringLiteral("Я могу есть стекло, оно мне не вредит.");
+    QTest::newRow("Sanskrit") << QStringLiteral("काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥");
+    QTest::newRow("Arabic") << QStringLiteral("أنا قادر على أكل الزجاج و هذا لا يؤلمني");
+    QTest::newRow("Chinese") << QStringLiteral("我能吞下玻璃而不伤身体。");
+    QTest::newRow("Thai") << QStringLiteral("ฉันกินกระจกได้ แต่มันไม่ทำให้ฉันเจ็บ");
+    QTest::newRow("non BMP") << QStringLiteral("𝓹𝓸𝓹𝓹𝓵𝓮𝓻");
+}
+
+void TestUTFConversion::testUTF()
+{
+    char utf8Buf[1000];
+    char *utf8String;
+    uint16_t utf16Buf[1000];
+    uint16_t *utf16String;
+    int len;
+
+    QFETCH(QString, s);
+    char *str = strdup(s.toUtf8().constData());
+
+    // UTF-8 to UTF-16
+
+    len = utf8CountUtf16CodeUnits(str);
+    QCOMPARE(len, s.size()); // QString size() returns number of code units, not code points
+    Q_ASSERT(len < (int)sizeof(utf16Buf)); // if this fails, make utf16Buf larger
+
+    len = utf8ToUtf16(str, utf16Buf);
+    QVERIFY(compare(utf16Buf, s.utf16()));
+    QCOMPARE(len, s.size());
+
+    utf16String = utf8ToUtf16(str);
+    QVERIFY(compare(utf16String, s.utf16()));
+    free(utf16String);
+
+    // UTF-16 to UTF-8
+
+    len = utf16CountUtf8Bytes(s.utf16());
+    QCOMPARE(len, (int)strlen(str));
+    Q_ASSERT(len < (int)sizeof(utf8Buf)); // if this fails, make utf8Buf larger
+
+    len = utf16ToUtf8(s.utf16(), utf8Buf);
+    QVERIFY(compare(utf8Buf, str));
+    QCOMPARE(len, (int)strlen(str));
+
+    utf8String = utf16ToUtf8(s.utf16());
+    QVERIFY(compare(utf8String, str));
+    free(utf8String);
+
+    free(str);
+}
+
+void TestUTFConversion::testUnicodeToAscii7()
+{
+    globalParams = std::make_unique<GlobalParams>();
+
+    // Test string is one 'Registered' and twenty 'Copyright' chars
+    // so it's long enough to reproduce the bug given that glibc
+    // malloc() always returns 8-byte aligned memory addresses.
+    GooString *goo = Poppler::QStringToUnicodeGooString(QString::fromUtf8("®©©©©©©©©©©©©©©©©©©©©")); // clazy:exclude=qstring-allocations
+
+    Unicode *in;
+    const int in_len = TextStringToUCS4(goo, &in);
+
+    delete goo;
+
+    int in_norm_len;
+    int *in_norm_idx;
+    Unicode *in_norm = unicodeNormalizeNFKC(in, in_len, &in_norm_len, &in_norm_idx, true);
+
+    free(in);
+
+    Unicode *out;
+    int out_len;
+    int *out_ascii_idx;
+
+    unicodeToAscii7(in_norm, in_norm_len, &out, &out_len, in_norm_idx, &out_ascii_idx);
+
+    free(in_norm);
+    free(in_norm_idx);
+
+    // ascii7 conversion: ® -> (R)   © -> (c)
+    const char *expected_ascii = (char *)"(R)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)(c)";
+
+    QCOMPARE(out_len, (int)strlen(expected_ascii));
+    QVERIFY(compare(out, expected_ascii, out_len));
+
+    free(out);
+    free(out_ascii_idx);
+}
+
+QTEST_GUILESS_MAIN(TestUTFConversion)
+#include "check_utf_conversion.moc"
diff --git a/qt6/tests/poppler-attachments.cpp b/qt6/tests/poppler-attachments.cpp
new file mode 100644
index 0000000..367cb97
--- /dev/null
+++ b/qt6/tests/poppler-attachments.cpp
@@ -0,0 +1,36 @@
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+
+#include <iostream>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QCoreApplication a(argc, argv); // QApplication required!
+
+    if (!(argc == 2)) {
+        qWarning() << "usage: poppler-attachments filename";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(argv[1]);
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    if (doc->hasEmbeddedFiles()) {
+        std::cout << "Embedded files: " << std::endl;
+        foreach (Poppler::EmbeddedFile *file, doc->embeddedFiles()) {
+            std::cout << "    " << qPrintable(file->name()) << std::endl;
+            std::cout << "    desc:" << qPrintable(file->description()) << std::endl;
+            QByteArray data = file->data();
+            std::cout << "       data: " << data.constData() << std::endl;
+        }
+
+    } else {
+        std::cout << "There are no embedded document at the top level" << std::endl;
+    }
+    delete doc;
+}
diff --git a/qt6/tests/poppler-fonts.cpp b/qt6/tests/poppler-fonts.cpp
new file mode 100644
index 0000000..140de8d
--- /dev/null
+++ b/qt6/tests/poppler-fonts.cpp
@@ -0,0 +1,87 @@
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+
+#include <iostream>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QCoreApplication a(argc, argv); // QApplication required!
+
+    if (!(argc == 2)) {
+        qWarning() << "usage: poppler-fonts filename";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(argv[1]);
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    std::cout << "name                                 type         emb sub font file";
+    std::cout << std::endl;
+    std::cout << "------------------------------------ ------------ --- --- ---------";
+    std::cout << std::endl;
+
+    foreach (const Poppler::FontInfo &font, doc->fonts()) {
+        if (font.name().isNull()) {
+            std::cout << qPrintable(QStringLiteral("%1").arg(QStringLiteral("[none]"), -37));
+        } else {
+            std::cout << qPrintable(QStringLiteral("%1").arg(font.name(), -37));
+        }
+        switch (font.type()) {
+        case Poppler::FontInfo::unknown:
+            std::cout << "unknown           ";
+            break;
+        case Poppler::FontInfo::Type1:
+            std::cout << "Type 1            ";
+            break;
+        case Poppler::FontInfo::Type1C:
+            std::cout << "Type 1C           ";
+            break;
+        case Poppler::FontInfo::Type3:
+            std::cout << "Type 3            ";
+            break;
+        case Poppler::FontInfo::TrueType:
+            std::cout << "TrueType          ";
+            break;
+        case Poppler::FontInfo::CIDType0:
+            std::cout << "CID Type 0        ";
+            break;
+        case Poppler::FontInfo::CIDType0C:
+            std::cout << "CID Type 0C       ";
+            break;
+        case Poppler::FontInfo::CIDTrueType:
+            std::cout << "CID TrueType      ";
+            break;
+        case Poppler::FontInfo::Type1COT:
+            std::cout << "Type 1C (OT)      ";
+            break;
+        case Poppler::FontInfo::TrueTypeOT:
+            std::cout << "TrueType (OT)     ";
+            break;
+        case Poppler::FontInfo::CIDType0COT:
+            std::cout << "CID Type 0C (OT)  ";
+            break;
+        case Poppler::FontInfo::CIDTrueTypeOT:
+            std::cout << "CID TrueType (OT) ";
+            break;
+        }
+
+        if (font.isEmbedded()) {
+            std::cout << "yes ";
+        } else {
+            std::cout << "no  ";
+        }
+        if (font.isSubset()) {
+            std::cout << "yes ";
+        } else {
+            std::cout << "no  ";
+        }
+        std::cout << qPrintable(font.file());
+        std::cout << std::endl;
+    }
+    delete doc;
+}
diff --git a/qt6/tests/poppler-forms.cpp b/qt6/tests/poppler-forms.cpp
new file mode 100644
index 0000000..57e75d8
--- /dev/null
+++ b/qt6/tests/poppler-forms.cpp
@@ -0,0 +1,272 @@
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDateTime>
+#include <QtCore/QDebug>
+
+#include <iostream>
+
+#include <poppler-qt6.h>
+#include <poppler-form.h>
+
+static std::ostream &operator<<(std::ostream &out, Poppler::FormField::FormType type)
+{
+    switch (type) {
+    case Poppler::FormField::FormButton:
+        out << "Button";
+        break;
+    case Poppler::FormField::FormText:
+        out << "Text";
+        break;
+    case Poppler::FormField::FormChoice:
+        out << "Choice";
+        break;
+    case Poppler::FormField::FormSignature:
+        out << "Signature";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, Poppler::FormFieldButton::ButtonType type)
+{
+    switch (type) {
+    case Poppler::FormFieldButton::Push:
+        out << "Push";
+        break;
+    case Poppler::FormFieldButton::CheckBox:
+        out << "CheckBox";
+        break;
+    case Poppler::FormFieldButton::Radio:
+        out << "Radio";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, Poppler::FormFieldText::TextType type)
+{
+    switch (type) {
+    case Poppler::FormFieldText::Normal:
+        out << "Normal";
+        break;
+    case Poppler::FormFieldText::Multiline:
+        out << "Multiline";
+        break;
+    case Poppler::FormFieldText::FileSelect:
+        out << "FileSelect";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, Poppler::FormFieldChoice::ChoiceType type)
+{
+    switch (type) {
+    case Poppler::FormFieldChoice::ComboBox:
+        out << "ComboBox";
+        break;
+    case Poppler::FormFieldChoice::ListBox:
+        out << "ListBox";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, Poppler::SignatureValidationInfo::SignatureStatus status)
+{
+    switch (status) {
+    case Poppler::SignatureValidationInfo::SignatureValid:
+        out << "Valid";
+        break;
+    case Poppler::SignatureValidationInfo::SignatureInvalid:
+        out << "Invalid";
+        break;
+    case Poppler::SignatureValidationInfo::SignatureDigestMismatch:
+        out << "DigestMismatch";
+        break;
+    case Poppler::SignatureValidationInfo::SignatureDecodingError:
+        out << "DecodingError";
+        break;
+    case Poppler::SignatureValidationInfo::SignatureGenericError:
+        out << "GenericError";
+        break;
+    case Poppler::SignatureValidationInfo::SignatureNotFound:
+        out << "NotFound";
+        break;
+    case Poppler::SignatureValidationInfo::SignatureNotVerified:
+        out << "NotVerifiedYet";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, Poppler::SignatureValidationInfo::CertificateStatus status)
+{
+    switch (status) {
+    case Poppler::SignatureValidationInfo::CertificateTrusted:
+        out << "Trusted";
+        break;
+    case Poppler::SignatureValidationInfo::CertificateUntrustedIssuer:
+        out << "UntrustedIssuer";
+        break;
+    case Poppler::SignatureValidationInfo::CertificateUnknownIssuer:
+        out << "UnknownIssuer";
+        break;
+    case Poppler::SignatureValidationInfo::CertificateRevoked:
+        out << "Revoked";
+        break;
+    case Poppler::SignatureValidationInfo::CertificateExpired:
+        out << "Expired";
+        break;
+    case Poppler::SignatureValidationInfo::CertificateGenericError:
+        out << "GenericError";
+        break;
+    case Poppler::SignatureValidationInfo::CertificateNotVerified:
+        out << "NotVerifiedYet";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, Qt::Alignment alignment)
+{
+    switch (alignment) {
+    case Qt::AlignLeft:
+        out << "Left";
+        break;
+    case Qt::AlignRight:
+        out << "Right";
+        break;
+    case Qt::AlignHCenter:
+        out << "HCenter";
+        break;
+    case Qt::AlignJustify:
+        out << "Justify";
+        break;
+    case Qt::AlignTop:
+        out << "Top";
+        break;
+    case Qt::AlignBottom:
+        out << "Bottom";
+        break;
+    case Qt::AlignVCenter:
+        out << "VCenter";
+        break;
+    case Qt::AlignCenter:
+        out << "Center";
+        break;
+    case Qt::AlignAbsolute:
+        out << "Absolute";
+        break;
+    }
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, const QString &string)
+{
+    out << string.toUtf8().constData();
+    return out;
+}
+
+static std::ostream &operator<<(std::ostream &out, const QRectF &rect)
+{
+    out << QStringLiteral("top: %1 left: %2 width: %3 height: %4").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height());
+    return out;
+}
+
+template<typename T>
+std::ostream &operator<<(std::ostream &out, const QList<T> &elems)
+{
+    bool isFirst = true;
+    for (int i = 0; i < elems.count(); ++i) {
+        if (!isFirst)
+            out << " ";
+        out << elems[i];
+        isFirst = false;
+    }
+    return out;
+}
+
+int main(int argc, char **argv)
+{
+    QCoreApplication a(argc, argv);
+
+    if (!(argc == 2)) {
+        qWarning() << "usage: poppler-forms filename";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(argv[1]);
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    std::cout << "Forms for file " << argv[1] << std::endl;
+    for (int i = 0; i < doc->numPages(); ++i) {
+        Poppler::Page *page = doc->page(i);
+        if (page) {
+            QList<Poppler::FormField *> forms = page->formFields();
+            std::cout << "\tPage " << i + 1 << std::endl;
+            foreach (const Poppler::FormField *form, forms) {
+                std::cout << "\t\tForm" << std::endl;
+                std::cout << "\t\t\tType: " << form->type() << std::endl;
+                std::cout << "\t\t\tRect: " << form->rect() << std::endl;
+                std::cout << "\t\t\tID: " << form->id() << std::endl;
+                std::cout << "\t\t\tName: " << form->name() << std::endl;
+                std::cout << "\t\t\tFullyQualifiedName: " << form->fullyQualifiedName() << std::endl;
+                std::cout << "\t\t\tUIName: " << form->uiName() << std::endl;
+                std::cout << "\t\t\tReadOnly: " << form->isReadOnly() << std::endl;
+                std::cout << "\t\t\tVisible: " << form->isVisible() << std::endl;
+                switch (form->type()) {
+                case Poppler::FormField::FormButton: {
+                    const Poppler::FormFieldButton *buttonForm = static_cast<const Poppler::FormFieldButton *>(form);
+                    std::cout << "\t\t\tButtonType: " << buttonForm->buttonType() << std::endl;
+                    std::cout << "\t\t\tCaption: " << buttonForm->caption() << std::endl;
+                    std::cout << "\t\t\tState: " << buttonForm->state() << std::endl;
+                    std::cout << "\t\t\tSiblings: " << buttonForm->siblings() << std::endl;
+                } break;
+
+                case Poppler::FormField::FormText: {
+                    const Poppler::FormFieldText *textForm = static_cast<const Poppler::FormFieldText *>(form);
+                    std::cout << "\t\t\tTextType: " << textForm->textType() << std::endl;
+                    std::cout << "\t\t\tText: " << textForm->text() << std::endl;
+                    std::cout << "\t\t\tIsPassword: " << textForm->isPassword() << std::endl;
+                    std::cout << "\t\t\tIsRichText: " << textForm->isRichText() << std::endl;
+                    std::cout << "\t\t\tMaximumLength: " << textForm->maximumLength() << std::endl;
+                    std::cout << "\t\t\tTextAlignment: " << textForm->textAlignment() << std::endl;
+                    std::cout << "\t\t\tCanBeSpellChecked: " << textForm->canBeSpellChecked() << std::endl;
+                } break;
+
+                case Poppler::FormField::FormChoice: {
+                    const Poppler::FormFieldChoice *choiceForm = static_cast<const Poppler::FormFieldChoice *>(form);
+                    std::cout << "\t\t\tChoiceType: " << choiceForm->choiceType() << std::endl;
+                    std::cout << "\t\t\tChoices: " << choiceForm->choices() << std::endl;
+                    std::cout << "\t\t\tIsEditable: " << choiceForm->isEditable() << std::endl;
+                    std::cout << "\t\t\tIsMultiSelect: " << choiceForm->multiSelect() << std::endl;
+                    std::cout << "\t\t\tCurrentChoices: " << choiceForm->currentChoices() << std::endl;
+                    std::cout << "\t\t\tEditChoice: " << choiceForm->editChoice() << std::endl;
+                    std::cout << "\t\t\tTextAlignment: " << choiceForm->textAlignment() << std::endl;
+                    std::cout << "\t\t\tCanBeSpellChecked: " << choiceForm->canBeSpellChecked() << std::endl;
+                } break;
+
+                case Poppler::FormField::FormSignature: {
+                    const Poppler::FormFieldSignature *signatureForm = static_cast<const Poppler::FormFieldSignature *>(form);
+                    const Poppler::SignatureValidationInfo svi = signatureForm->validate(Poppler::FormFieldSignature::ValidateVerifyCertificate);
+                    std::cout << "\t\t\tSignatureStatus: " << svi.signatureStatus() << std::endl;
+                    std::cout << "\t\t\tCertificateStatus: " << svi.certificateStatus() << std::endl;
+                    if (svi.signerName().isEmpty() == false)
+                        std::cout << "\t\t\tSignerName: " << svi.signerName() << std::endl;
+                    else
+                        std::cout << "\t\t\tSignerName: "
+                                  << "(null)" << std::endl;
+                    const QDateTime sviTime = QDateTime::fromSecsSinceEpoch(svi.signingTime(), Qt::UTC);
+                    std::cout << "\t\t\tSigningTime: " << sviTime.toString() << std::endl;
+                } break;
+                }
+            }
+            qDeleteAll(forms);
+            delete page;
+        }
+    }
+    delete doc;
+}
diff --git a/qt6/tests/poppler-page-labels.cpp b/qt6/tests/poppler-page-labels.cpp
new file mode 100644
index 0000000..bcb8a85
--- /dev/null
+++ b/qt6/tests/poppler-page-labels.cpp
@@ -0,0 +1,45 @@
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+
+#include <iostream>
+#include <memory>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QCoreApplication a(argc, argv); // QApplication required!
+
+    if (!(argc == 2)) {
+        qWarning() << "usage: poppler-page-labels filename";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(argv[1]);
+    if (!doc || doc->isLocked()) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    for (int i = 0; i < doc->numPages(); i++) {
+        int j = 0;
+        std::cout << "*** Label of Page " << i << std::endl;
+        std::cout << std::flush;
+
+        std::unique_ptr<Poppler::Page> page(doc->page(i));
+
+        if (!page)
+            continue;
+
+        const QByteArray utf8str = page->label().toUtf8();
+        for (j = 0; j < utf8str.size(); j++)
+            std::cout << utf8str[j];
+        std::cout << std::endl;
+
+        std::unique_ptr<Poppler::Page> pageFromPageLabel(doc->page(page->label()));
+        const int indexFromPageLabel = pageFromPageLabel ? pageFromPageLabel->index() : -1;
+        if (indexFromPageLabel != i)
+            std::cout << "WARNING: Page label didn't link back to the same page index " << indexFromPageLabel << " " << i << std::endl;
+    }
+    delete doc;
+}
diff --git a/qt6/tests/poppler-texts.cpp b/qt6/tests/poppler-texts.cpp
new file mode 100644
index 0000000..82c6dc5
--- /dev/null
+++ b/qt6/tests/poppler-texts.cpp
@@ -0,0 +1,37 @@
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+
+#include <iostream>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QCoreApplication a(argc, argv); // QApplication required!
+
+    if (!(argc == 2)) {
+        qWarning() << "usage: poppler-texts filename";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(argv[1]);
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    for (int i = 0; i < doc->numPages(); i++) {
+        int j = 0;
+        std::cout << "*** Page " << i << std::endl;
+        std::cout << std::flush;
+
+        Poppler::Page *page = doc->page(i);
+        const QByteArray utf8str = page->text(QRectF(), Poppler::Page::RawOrderLayout).toUtf8();
+        std::cout << std::flush;
+        for (j = 0; j < utf8str.size(); j++)
+            std::cout << utf8str[j];
+        std::cout << std::endl;
+        delete page;
+    }
+    delete doc;
+}
diff --git a/qt6/tests/stress-poppler-dir.cpp b/qt6/tests/stress-poppler-dir.cpp
new file mode 100644
index 0000000..6f97975
--- /dev/null
+++ b/qt6/tests/stress-poppler-dir.cpp
@@ -0,0 +1,66 @@
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QElapsedTimer>
+#include <QtWidgets/QApplication>
+#include <QtGui/QImage>
+
+#include <iostream>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QApplication a(argc, argv); // QApplication required!
+
+    QElapsedTimer t;
+    t.start();
+
+    QDir directory(argv[1]);
+    foreach (const QString &fileName, directory.entryList()) {
+        if (fileName.endsWith(QStringLiteral("pdf"))) {
+            qDebug() << "Doing" << fileName.toLatin1().data() << ":";
+            Poppler::Document *doc = Poppler::Document::load(directory.canonicalPath() + "/" + fileName);
+            if (!doc) {
+                qWarning() << "doc not loaded";
+            } else if (doc->isLocked()) {
+                if (!doc->unlock("", "password")) {
+                    qWarning() << "couldn't unlock document";
+                    delete doc;
+                }
+            } else {
+                int major = 0, minor = 0;
+                doc->getPdfVersion(&major, &minor);
+                doc->info(QStringLiteral("Title"));
+                doc->info(QStringLiteral("Subject"));
+                doc->info(QStringLiteral("Author"));
+                doc->info(QStringLiteral("Keywords"));
+                doc->info(QStringLiteral("Creator"));
+                doc->info(QStringLiteral("Producer"));
+                doc->date(QStringLiteral("CreationDate")).toString();
+                doc->date(QStringLiteral("ModDate")).toString();
+                doc->numPages();
+                doc->isLinearized();
+                doc->isEncrypted();
+                doc->okToPrint();
+                doc->okToCopy();
+                doc->okToChange();
+                doc->okToAddNotes();
+                doc->pageMode();
+
+                for (int index = 0; index < doc->numPages(); ++index) {
+                    Poppler::Page *page = doc->page(index);
+                    page->renderToImage();
+                    page->pageSize();
+                    page->orientation();
+                    delete page;
+                    std::cout << ".";
+                    std::cout.flush();
+                }
+                std::cout << std::endl;
+                delete doc;
+            }
+        }
+    }
+
+    std::cout << "Elapsed time: " << (t.elapsed() / 1000) << "seconds" << std::endl;
+}
diff --git a/qt6/tests/stress-poppler-qt6.cpp b/qt6/tests/stress-poppler-qt6.cpp
new file mode 100644
index 0000000..010e4e8
--- /dev/null
+++ b/qt6/tests/stress-poppler-qt6.cpp
@@ -0,0 +1,74 @@
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QElapsedTimer>
+#include <QtWidgets/QApplication>
+#include <QtGui/QImage>
+
+#include <iostream>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QApplication a(argc, argv); // QApplication required!
+
+    Q_UNUSED(argc);
+    Q_UNUSED(argv);
+
+    QElapsedTimer t;
+    t.start();
+    QDir dbDir(QStringLiteral("./pdfdb"));
+    if (!dbDir.exists()) {
+        qWarning() << "Database directory does not exist";
+    }
+
+    QStringList excludeSubDirs;
+    excludeSubDirs << QStringLiteral("000048") << QStringLiteral("000607");
+
+    const QStringList dirs = dbDir.entryList(QStringList() << QStringLiteral("0000*"), QDir::Dirs);
+    foreach (const QString &subdir, dirs) {
+        if (excludeSubDirs.contains(subdir)) {
+            // then skip it
+        } else {
+            QString path = "./pdfdb/" + subdir + "/data.pdf";
+            std::cout << "Doing " << path.toLatin1().data() << " :";
+            Poppler::Document *doc = Poppler::Document::load(path);
+            if (!doc) {
+                qWarning() << "doc not loaded";
+            } else {
+                int major = 0, minor = 0;
+                doc->getPdfVersion(&major, &minor);
+                doc->info(QStringLiteral("Title"));
+                doc->info(QStringLiteral("Subject"));
+                doc->info(QStringLiteral("Author"));
+                doc->info(QStringLiteral("Keywords"));
+                doc->info(QStringLiteral("Creator"));
+                doc->info(QStringLiteral("Producer"));
+                doc->date(QStringLiteral("CreationDate")).toString();
+                doc->date(QStringLiteral("ModDate")).toString();
+                doc->numPages();
+                doc->isLinearized();
+                doc->isEncrypted();
+                doc->okToPrint();
+                doc->okToCopy();
+                doc->okToChange();
+                doc->okToAddNotes();
+                doc->pageMode();
+
+                for (int index = 0; index < doc->numPages(); ++index) {
+                    Poppler::Page *page = doc->page(index);
+                    page->renderToImage();
+                    page->pageSize();
+                    page->orientation();
+                    delete page;
+                    std::cout << ".";
+                    std::cout.flush();
+                }
+                std::cout << std::endl;
+                delete doc;
+            }
+        }
+    }
+
+    std::cout << "Elapsed time: " << (t.elapsed() / 1000) << std::endl;
+}
diff --git a/qt6/tests/stress-threads-qt6.cpp b/qt6/tests/stress-threads-qt6.cpp
new file mode 100644
index 0000000..162bb9e
--- /dev/null
+++ b/qt6/tests/stress-threads-qt6.cpp
@@ -0,0 +1,272 @@
+
+#ifndef _WIN32
+#    include <unistd.h>
+#else
+#    include <windows.h>
+#    define sleep Sleep
+#endif
+#include <ctime>
+
+#include <poppler-qt6.h>
+#include <poppler-form.h>
+
+#include <QDebug>
+#include <QFile>
+#include <QImage>
+#include <QMutex>
+#include <QRandomGenerator>
+#include <QThread>
+
+class SillyThread : public QThread
+{
+    Q_OBJECT
+public:
+    SillyThread(Poppler::Document *document, QObject *parent = nullptr);
+
+    void run() override;
+
+private:
+    Poppler::Document *m_document;
+    QVector<Poppler::Page *> m_pages;
+};
+
+class CrazyThread : public QThread
+{
+    Q_OBJECT
+public:
+    CrazyThread(uint seed, Poppler::Document *document, QMutex *annotationMutex, QObject *parent = nullptr);
+
+    void run() override;
+
+private:
+    uint m_seed;
+    Poppler::Document *m_document;
+    QMutex *m_annotationMutex;
+};
+
+static Poppler::Page *loadPage(Poppler::Document *document, int index)
+{
+    Poppler::Page *page = document->page(index);
+
+    if (page == nullptr) {
+        qDebug() << "!Document::page";
+
+        exit(EXIT_FAILURE);
+    }
+
+    return page;
+}
+
+static Poppler::Page *loadRandomPage(Poppler::Document *document)
+{
+    return loadPage(document, QRandomGenerator::global()->bounded(document->numPages()));
+}
+
+SillyThread::SillyThread(Poppler::Document *document, QObject *parent) : QThread(parent), m_document(document), m_pages()
+{
+    m_pages.reserve(m_document->numPages());
+
+    for (int index = 0; index < m_document->numPages(); ++index) {
+        m_pages.append(loadPage(m_document, index));
+    }
+}
+
+void SillyThread::run()
+{
+    forever {
+        foreach (Poppler::Page *page, m_pages) {
+            QImage image = page->renderToImage();
+
+            if (image.isNull()) {
+                qDebug() << "!Page::renderToImage";
+
+                ::exit(EXIT_FAILURE);
+            }
+        }
+    }
+}
+
+CrazyThread::CrazyThread(uint seed, Poppler::Document *document, QMutex *annotationMutex, QObject *parent) : QThread(parent), m_seed(seed), m_document(document), m_annotationMutex(annotationMutex) { }
+
+void CrazyThread::run()
+{
+    typedef QScopedPointer<Poppler::Page> PagePointer;
+
+    forever {
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            qDebug() << "search...";
+
+            PagePointer page(loadRandomPage(m_document));
+
+            page->search(QStringLiteral("c"), Poppler::Page::IgnoreCase);
+            page->search(QStringLiteral("r"));
+            page->search(QStringLiteral("a"), Poppler::Page::IgnoreCase);
+            page->search(QStringLiteral("z"));
+            page->search(QStringLiteral("y"), Poppler::Page::IgnoreCase);
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            qDebug() << "links...";
+
+            PagePointer page(loadRandomPage(m_document));
+
+            QList<Poppler::Link *> links = page->links();
+
+            qDeleteAll(links);
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            qDebug() << "form fields...";
+
+            PagePointer page(loadRandomPage(m_document));
+
+            QList<Poppler::FormField *> formFields = page->formFields();
+
+            qDeleteAll(formFields);
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            qDebug() << "thumbnail...";
+
+            PagePointer page(loadRandomPage(m_document));
+
+            page->thumbnail();
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            qDebug() << "text...";
+
+            PagePointer page(loadRandomPage(m_document));
+
+            page->text(QRectF(QPointF(), page->pageSizeF()));
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            QMutexLocker mutexLocker(m_annotationMutex);
+
+            qDebug() << "add annotation...";
+
+            PagePointer page(loadRandomPage(m_document));
+
+            Poppler::Annotation *annotation = nullptr;
+
+            switch (QRandomGenerator::global()->bounded(3)) {
+            default:
+            case 0:
+                annotation = new Poppler::TextAnnotation(QRandomGenerator::global()->bounded(2) == 0 ? Poppler::TextAnnotation::Linked : Poppler::TextAnnotation::InPlace);
+                break;
+            case 1:
+                annotation = new Poppler::HighlightAnnotation();
+                break;
+            case 2:
+                annotation = new Poppler::InkAnnotation();
+                break;
+            }
+
+            annotation->setBoundary(QRectF(0.0, 0.0, 0.5, 0.5));
+            annotation->setContents(QStringLiteral("crazy"));
+
+            page->addAnnotation(annotation);
+
+            delete annotation;
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            QMutexLocker mutexLocker(m_annotationMutex);
+
+            for (int index = 0; index < m_document->numPages(); ++index) {
+                PagePointer page(loadPage(m_document, index));
+
+                QList<Poppler::Annotation *> annotations = page->annotations();
+
+                if (!annotations.isEmpty()) {
+                    qDebug() << "modify annotation...";
+
+                    annotations.at(QRandomGenerator::global()->bounded(annotations.size()))->setBoundary(QRectF(0.5, 0.5, 0.25, 0.25));
+                    annotations.at(QRandomGenerator::global()->bounded(annotations.size()))->setAuthor(QStringLiteral("foo"));
+                    annotations.at(QRandomGenerator::global()->bounded(annotations.size()))->setContents(QStringLiteral("bar"));
+                    annotations.at(QRandomGenerator::global()->bounded(annotations.size()))->setCreationDate(QDateTime::currentDateTime());
+                    annotations.at(QRandomGenerator::global()->bounded(annotations.size()))->setModificationDate(QDateTime::currentDateTime());
+                }
+
+                qDeleteAll(annotations);
+
+                if (!annotations.isEmpty()) {
+                    break;
+                }
+            }
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            QMutexLocker mutexLocker(m_annotationMutex);
+
+            for (int index = 0; index < m_document->numPages(); ++index) {
+                PagePointer page(loadPage(m_document, index));
+
+                QList<Poppler::Annotation *> annotations = page->annotations();
+
+                if (!annotations.isEmpty()) {
+                    qDebug() << "remove annotation...";
+
+                    page->removeAnnotation(annotations.takeAt(QRandomGenerator::global()->bounded(annotations.size())));
+                }
+
+                qDeleteAll(annotations);
+
+                if (!annotations.isEmpty()) {
+                    break;
+                }
+            }
+        }
+
+        if (QRandomGenerator::global()->bounded(2) == 0) {
+            qDebug() << "fonts...";
+
+            m_document->fonts();
+        }
+    }
+}
+
+int main(int argc, char **argv)
+{
+    if (argc < 5) {
+        qDebug() << "usage: stress-threads-qt duration sillyCount crazyCount file(s)";
+
+        return EXIT_FAILURE;
+    }
+
+    const int duration = atoi(argv[1]);
+    const int sillyCount = atoi(argv[2]);
+    const int crazyCount = atoi(argv[3]);
+
+    for (int argi = 4; argi < argc; ++argi) {
+        const QString file = QFile::decodeName(argv[argi]);
+        Poppler::Document *document = Poppler::Document::load(file);
+
+        if (document == nullptr) {
+            qDebug() << "Could not load" << file;
+            continue;
+        }
+
+        if (document->isLocked()) {
+            qDebug() << file << "is locked";
+            continue;
+        }
+
+        for (int i = 0; i < sillyCount; ++i) {
+            (new SillyThread(document))->start();
+        }
+
+        QMutex *annotationMutex = new QMutex();
+
+        for (int i = 0; i < crazyCount; ++i) {
+            (new CrazyThread(QRandomGenerator::global()->generate(), document, annotationMutex))->start();
+        }
+    }
+
+    sleep(duration);
+
+    return EXIT_SUCCESS;
+}
+
+#include "stress-threads-qt6.moc"
diff --git a/qt6/tests/test-password-qt6.cpp b/qt6/tests/test-password-qt6.cpp
new file mode 100644
index 0000000..80efd47
--- /dev/null
+++ b/qt6/tests/test-password-qt6.cpp
@@ -0,0 +1,132 @@
+#include <QtCore/QDebug>
+#include <QtWidgets/QApplication>
+#include <QtGui/QImage>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtWidgets/QWidget>
+
+#include <poppler-qt6.h>
+
+class PDFDisplay : public QWidget // picture display widget
+{
+    Q_OBJECT
+public:
+    PDFDisplay(Poppler::Document *d, QWidget *parent = nullptr);
+    ~PDFDisplay() override;
+
+protected:
+    void paintEvent(QPaintEvent *) override;
+    void keyPressEvent(QKeyEvent *) override;
+
+private:
+    void display();
+    int m_currentPage;
+    QImage image;
+    Poppler::Document *doc;
+};
+
+PDFDisplay::PDFDisplay(Poppler::Document *d, QWidget *parent) : QWidget(parent)
+{
+    doc = d;
+    m_currentPage = 0;
+    display();
+}
+
+void PDFDisplay::display()
+{
+    if (doc) {
+        Poppler::Page *page = doc->page(m_currentPage);
+        if (page) {
+            qDebug() << "Displaying page: " << m_currentPage;
+            image = page->renderToImage();
+            update();
+            delete page;
+        }
+    } else {
+        qWarning() << "doc not loaded";
+    }
+}
+
+PDFDisplay::~PDFDisplay()
+{
+    delete doc;
+}
+
+void PDFDisplay::paintEvent(QPaintEvent *e)
+{
+    QPainter paint(this); // paint widget
+    if (!image.isNull()) {
+        paint.drawImage(0, 0, image);
+    } else {
+        qWarning() << "null image";
+    }
+}
+
+void PDFDisplay::keyPressEvent(QKeyEvent *e)
+{
+    if (e->key() == Qt::Key_Down) {
+        if (m_currentPage + 1 < doc->numPages()) {
+            m_currentPage++;
+            display();
+        }
+    } else if (e->key() == Qt::Key_Up) {
+        if (m_currentPage > 0) {
+            m_currentPage--;
+            display();
+        }
+    } else if (e->key() == Qt::Key_Q) {
+        exit(0);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    QApplication a(argc, argv); // QApplication required!
+
+    if (argc != 3) {
+        qWarning() << "usage: test-password-qt6 owner-password filename";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(argv[2], argv[1]);
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    // output some meta-data
+    int major = 0, minor = 0;
+    doc->getPdfVersion(&major, &minor);
+    qDebug() << "    PDF Version: " << qPrintable(QStringLiteral("%1.%2").arg(major).arg(minor));
+    qDebug() << "          Title: " << doc->info(QStringLiteral("Title"));
+    qDebug() << "        Subject: " << doc->info(QStringLiteral("Subject"));
+    qDebug() << "         Author: " << doc->info(QStringLiteral("Author"));
+    qDebug() << "      Key words: " << doc->info(QStringLiteral("Keywords"));
+    qDebug() << "        Creator: " << doc->info(QStringLiteral("Creator"));
+    qDebug() << "       Producer: " << doc->info(QStringLiteral("Producer"));
+    qDebug() << "   Date created: " << doc->date(QStringLiteral("CreationDate")).toString();
+    qDebug() << "  Date modified: " << doc->date(QStringLiteral("ModDate")).toString();
+    qDebug() << "Number of pages: " << doc->numPages();
+    qDebug() << "     Linearised: " << doc->isLinearized();
+    qDebug() << "      Encrypted: " << doc->isEncrypted();
+    qDebug() << "    OK to print: " << doc->okToPrint();
+    qDebug() << "     OK to copy: " << doc->okToCopy();
+    qDebug() << "   OK to change: " << doc->okToChange();
+    qDebug() << "OK to add notes: " << doc->okToAddNotes();
+    qDebug() << "      Page mode: " << doc->pageMode();
+    QStringList fontNameList;
+    foreach (const Poppler::FontInfo &font, doc->fonts())
+        fontNameList += font.name();
+    qDebug() << "          Fonts: " << fontNameList.join(QStringLiteral(", "));
+
+    Poppler::Page *page = doc->page(0);
+    qDebug() << "    Page 1 size: " << page->pageSize().width() / 72 << "inches x " << page->pageSize().height() / 72 << "inches";
+
+    PDFDisplay test(doc); // create picture display
+    test.setWindowTitle(QStringLiteral("Poppler-Qt6 Test"));
+    test.show(); // show it
+
+    return a.exec(); // start event loop
+}
+
+#include "test-password-qt6.moc"
diff --git a/qt6/tests/test-poppler-qt6.cpp b/qt6/tests/test-poppler-qt6.cpp
new file mode 100644
index 0000000..b7c0f66
--- /dev/null
+++ b/qt6/tests/test-poppler-qt6.cpp
@@ -0,0 +1,218 @@
+#include <QtCore/QDebug>
+#include <QtCore/QFile>
+#include <QtWidgets/QApplication>
+#include <QtGui/QImage>
+#include <QtWidgets/QLabel>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QWidget>
+
+#include <poppler-qt6.h>
+
+class PDFDisplay : public QWidget // picture display widget
+{
+    Q_OBJECT
+public:
+    PDFDisplay(Poppler::Document *d, bool arthur, QWidget *parent = nullptr);
+    ~PDFDisplay() override;
+    void setShowTextRects(bool show);
+    void display();
+
+protected:
+    void paintEvent(QPaintEvent *) override;
+    void keyPressEvent(QKeyEvent *) override;
+    void mousePressEvent(QMouseEvent *) override;
+
+private:
+    int m_currentPage;
+    QImage image;
+    Poppler::Document *doc;
+    QString backendString;
+    bool showTextRects;
+    QList<Poppler::TextBox *> textRects;
+};
+
+PDFDisplay::PDFDisplay(Poppler::Document *d, bool arthur, QWidget *parent) : QWidget(parent)
+{
+    showTextRects = false;
+    doc = d;
+    m_currentPage = 0;
+    if (arthur) {
+        backendString = QStringLiteral("Arthur");
+        doc->setRenderBackend(Poppler::Document::ArthurBackend);
+    } else {
+        backendString = QStringLiteral("Splash");
+        doc->setRenderBackend(Poppler::Document::SplashBackend);
+    }
+    doc->setRenderHint(Poppler::Document::Antialiasing, true);
+    doc->setRenderHint(Poppler::Document::TextAntialiasing, true);
+}
+
+void PDFDisplay::setShowTextRects(bool show)
+{
+    showTextRects = show;
+}
+
+void PDFDisplay::display()
+{
+    if (doc) {
+        Poppler::Page *page = doc->page(m_currentPage);
+        if (page) {
+            qDebug() << "Displaying page using" << backendString << "backend: " << m_currentPage;
+            QTime t = QTime::currentTime();
+            image = page->renderToImage();
+            qDebug() << "Rendering took" << t.msecsTo(QTime::currentTime()) << "msecs";
+            qDeleteAll(textRects);
+            if (showTextRects) {
+                QPainter painter(&image);
+                painter.setPen(Qt::red);
+                textRects = page->textList();
+                foreach (Poppler::TextBox *tb, textRects) {
+                    painter.drawRect(tb->boundingBox());
+                }
+            } else
+                textRects.clear();
+            update();
+            delete page;
+        }
+    } else {
+        qWarning() << "doc not loaded";
+    }
+}
+
+PDFDisplay::~PDFDisplay()
+{
+    qDeleteAll(textRects);
+    delete doc;
+}
+
+void PDFDisplay::paintEvent(QPaintEvent *e)
+{
+    QPainter paint(this); // paint widget
+    if (!image.isNull()) {
+        paint.drawImage(0, 0, image);
+    } else {
+        qWarning() << "null image";
+    }
+}
+
+void PDFDisplay::keyPressEvent(QKeyEvent *e)
+{
+    if (e->key() == Qt::Key_Down) {
+        if (m_currentPage + 1 < doc->numPages()) {
+            m_currentPage++;
+            display();
+        }
+    } else if (e->key() == Qt::Key_Up) {
+        if (m_currentPage > 0) {
+            m_currentPage--;
+            display();
+        }
+    } else if (e->key() == Qt::Key_Q) {
+        exit(0);
+    }
+}
+
+void PDFDisplay::mousePressEvent(QMouseEvent *e)
+{
+    int i = 0;
+    foreach (Poppler::TextBox *tb, textRects) {
+        if (tb->boundingBox().contains(e->pos())) {
+            const QString tt = QStringLiteral("Text: \"%1\"\nIndex in text list: %2").arg(tb->text()).arg(i);
+            QToolTip::showText(e->globalPosition().toPoint(), tt, this);
+            break;
+        }
+        ++i;
+    }
+}
+
+int main(int argc, char **argv)
+{
+    QApplication a(argc, argv); // QApplication required!
+
+    if (argc < 2 || (argc == 3 && strcmp(argv[2], "-extract") != 0 && strcmp(argv[2], "-arthur") != 0 && strcmp(argv[2], "-textRects") != 0) || argc > 3) {
+        // use argument as file name
+        qWarning() << "usage: test-poppler-qt6 filename [-extract|-arthur|-textRects]";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(QFile::decodeName(argv[1]));
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    if (doc->isLocked()) {
+        qWarning() << "document locked (needs password)";
+        exit(0);
+    }
+
+    // output some meta-data
+    int major = 0, minor = 0;
+    doc->getPdfVersion(&major, &minor);
+    qDebug() << "    PDF Version: " << qPrintable(QStringLiteral("%1.%2").arg(major).arg(minor));
+    qDebug() << "          Title: " << doc->info(QStringLiteral("Title"));
+    qDebug() << "        Subject: " << doc->info(QStringLiteral("Subject"));
+    qDebug() << "         Author: " << doc->info(QStringLiteral("Author"));
+    qDebug() << "      Key words: " << doc->info(QStringLiteral("Keywords"));
+    qDebug() << "        Creator: " << doc->info(QStringLiteral("Creator"));
+    qDebug() << "       Producer: " << doc->info(QStringLiteral("Producer"));
+    qDebug() << "   Date created: " << doc->date(QStringLiteral("CreationDate")).toString();
+    qDebug() << "  Date modified: " << doc->date(QStringLiteral("ModDate")).toString();
+    qDebug() << "Number of pages: " << doc->numPages();
+    qDebug() << "     Linearised: " << doc->isLinearized();
+    qDebug() << "      Encrypted: " << doc->isEncrypted();
+    qDebug() << "    OK to print: " << doc->okToPrint();
+    qDebug() << "     OK to copy: " << doc->okToCopy();
+    qDebug() << "   OK to change: " << doc->okToChange();
+    qDebug() << "OK to add notes: " << doc->okToAddNotes();
+    qDebug() << "      Page mode: " << doc->pageMode();
+    qDebug() << "       Metadata: " << doc->metadata();
+
+    if (doc->hasEmbeddedFiles()) {
+        qDebug() << "Embedded files:";
+        foreach (Poppler::EmbeddedFile *file, doc->embeddedFiles()) {
+            qDebug() << "   " << file->name();
+        }
+        qDebug();
+    } else {
+        qDebug() << "No embedded files";
+    }
+
+    if (doc->numPages() <= 0) {
+        delete doc;
+        qDebug() << "Doc has no pages";
+        return 0;
+    }
+
+    {
+        Poppler::Page *page = doc->page(0);
+        if (page) {
+            qDebug() << "Page 1 size: " << page->pageSize().width() / 72 << "inches x " << page->pageSize().height() / 72 << "inches";
+            delete page;
+        }
+    }
+
+    if (argc == 2 || (argc == 3 && strcmp(argv[2], "-arthur") == 0) || (argc == 3 && strcmp(argv[2], "-textRects") == 0)) {
+        bool useArthur = (argc == 3 && strcmp(argv[2], "-arthur") == 0);
+        PDFDisplay test(doc, useArthur); // create picture display
+        test.setWindowTitle(QStringLiteral("Poppler-Qt6 Test"));
+        test.setShowTextRects(argc == 3 && strcmp(argv[2], "-textRects") == 0);
+        test.display();
+        test.show(); // show it
+
+        return a.exec(); // start event loop
+    } else {
+        Poppler::Page *page = doc->page(0);
+
+        QLabel *l = new QLabel(page->text(QRectF()), nullptr);
+        l->show();
+        delete page;
+        delete doc;
+        return a.exec();
+    }
+}
+
+#include "test-poppler-qt6.moc"
diff --git a/qt6/tests/test-render-to-file.cpp b/qt6/tests/test-render-to-file.cpp
new file mode 100644
index 0000000..b5f73b9
--- /dev/null
+++ b/qt6/tests/test-render-to-file.cpp
@@ -0,0 +1,59 @@
+#include <QtCore/QDebug>
+#include <QtCore/QFile>
+#include <QGuiApplication>
+#include <QImage>
+
+#include <poppler-qt6.h>
+
+int main(int argc, char **argv)
+{
+    QGuiApplication a(argc, argv); // QApplication required!
+
+    if (argc < 2 || (argc == 3 && strcmp(argv[2], "-arthur") != 0) || argc > 3) {
+        // use argument as file name
+        qWarning() << "usage: test-render-to-file-qt6 filename [-arthur]";
+        exit(1);
+    }
+
+    Poppler::Document *doc = Poppler::Document::load(QFile::decodeName(argv[1]));
+    if (!doc) {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    if (doc->isLocked()) {
+        qWarning() << "document locked (needs password)";
+        exit(0);
+    }
+
+    if (doc->numPages() <= 0) {
+        delete doc;
+        qDebug() << "Doc has no pages";
+        return 0;
+    }
+
+    QString backendString;
+    if (argc == 3 && strcmp(argv[2], "-arthur") == 0) {
+        backendString = QStringLiteral("Arthur");
+        doc->setRenderBackend(Poppler::Document::ArthurBackend);
+    } else {
+        backendString = QStringLiteral("Splash");
+        doc->setRenderBackend(Poppler::Document::SplashBackend);
+    }
+    doc->setRenderHint(Poppler::Document::Antialiasing, true);
+    doc->setRenderHint(Poppler::Document::TextAntialiasing, true);
+
+    for (int i = 0; i < doc->numPages(); ++i) {
+        Poppler::Page *page = doc->page(i);
+        if (page) {
+            qDebug() << "Rendering page using" << backendString << "backend: " << i;
+            QTime t = QTime::currentTime();
+            QImage image = page->renderToImage();
+            qDebug() << "Rendering took" << t.msecsTo(QTime::currentTime()) << "msecs";
+            image.save(QStringLiteral("test-render-to-file%1.png").arg(i));
+            delete page;
+        }
+    }
+
+    return 0;
+}