Files
qt6-migration-pipeline/Z/src/nglwidgets/nglpdfdocument.cpp
T

335 lines
8.1 KiB
C++
Executable File

#include "nglpdfdocument.h"
#include "nglpdfpage.h"
#include <QtCore/QUrl>
#include <QLabel>
#include <QScrollArea>
#include <QScrollBar>
#include <QVBoxLayout>
#include <QTimer>
#include <iostream>
#include <memory>
namespace NGL
{
PDFDocument::PDFDocument(QWidget *parent)
: QWidget(parent)
, m_doc(nullptr)
, m_currentPage(0)
, m_maxCachedPages(20)
, m_scaleFactor(1.0)
, m_widthFactor(1.0)
, m_rotation(Poppler::Page::Rotate0)
{
QVBoxLayout *lay = new QVBoxLayout(this);
lay->setContentsMargins(0,0,0,0);
lay->setSpacing(0);
m_pdfLabel = new QWidget(this);
m_pdfLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QVBoxLayout *l = new QVBoxLayout(m_pdfLabel);
const int innerMargin = 6;
l->setContentsMargins(innerMargin, innerMargin, innerMargin, innerMargin);
l->setSpacing(innerMargin);
l->setSizeConstraint(QLayout::SetFixedSize);
m_scrollArea = new QScrollArea(this);
m_scrollArea->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setWidget(m_pdfLabel);
lay->addWidget(m_scrollArea);
}
PDFDocument::~PDFDocument()
{
qDeleteAll(m_pages);
m_pages.clear();
m_doc.reset();
}
bool PDFDocument::showPDF(const QUrl &url)
{
return showPDF(url.toLocalFile());
}
bool PDFDocument::showPDF(const QString &path)
{
if (path.isEmpty())
return false;
if (path == m_currentPath)
{
m_scaleFactor = 0;
m_widthFactor = 1;
setZoomFactor(1.0);
setRotation(Poppler::Page::Rotate0);
return true;
}
m_doc.reset(Poppler::Document::load(path));
if (!m_doc)
return false;
m_currentPath = path;
return showPDFInternal();
}
bool PDFDocument::showPDF(const QByteArray &pdf)
{
m_currentPath.clear();
m_doc.reset(Poppler::Document::loadFromData(pdf));
if (!m_doc)
return false;
return showPDFInternal();
}
bool PDFDocument::showPDFInternal()
{
disconnect(m_scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this, &PDFDocument::onVSliderValueChanged);
m_doc->setRenderHint(Poppler::Document::Antialiasing);
m_doc->setRenderHint(Poppler::Document::TextAntialiasing);
m_doc->setRenderHint(Poppler::Document::TextHinting);
m_doc->setRenderHint(Poppler::Document::TextSlightHinting);
m_scrollArea->horizontalScrollBar()->setSliderPosition(0);
m_scrollArea->verticalScrollBar()->setSliderPosition(0);
m_renderedPages.clear();
qDeleteAll(m_pages);
m_pages.clear();
m_ranges.clear();
QVBoxLayout *l = qobject_cast<QVBoxLayout *> (m_pdfLabel->layout());
const int cnt = m_doc->numPages();
m_pages.reserve(cnt);
for (int i = 0; i < cnt; ++i)
{
PDFPage *p = new PDFPage(m_pdfLabel);
p->setZoomFactor(m_scaleFactor);
p->setRotation(m_rotation);
auto pagePtr = m_doc->page(i);
p->setPopplerPage(pagePtr.get());
// keep ownership via unique_ptr; PDFPage will not delete the Poppler::Page pointer
connect(p, &PDFPage::pageRendered,
this, &PDFDocument::onPageRendered);
connect(p, &PDFPage::externalLinkClicked,
this, &PDFDocument::linkClicked);
connect(p, &PDFPage::linkClicked,
this, &PDFDocument::onLinkClicked);
m_pages += p;
l->addWidget(p);
}
rebuildRanges();
m_currentPage = std::min(m_currentPage, static_cast<int>(m_ranges.count() - 1));
ensureCurPageVisible();
emit currentPageChanged(m_currentPage + 1, m_ranges.count());
connect(m_scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this, &PDFDocument::onVSliderValueChanged);
return true;
}
void PDFDocument::resizePdfToWidth()
{
double scale = m_scaleFactor;
m_widthFactor = double(m_pdfLabel->size().width()) / size().width() * 1.03;
m_scaleFactor = 0;
setZoomFactor(scale);
}
int PDFDocument::curPage() const
{
return m_currentPage + 1;
}
int PDFDocument::numPages() const
{
return m_doc ? m_doc->numPages() : -1;
}
void PDFDocument::setZoomFactor(double zoom)
{
if (qFuzzyCompare(m_scaleFactor, zoom))
return;
m_scaleFactor = zoom;
for (PDFPage *p : qAsConst(m_pages))
p->setZoomFactor(m_scaleFactor / m_widthFactor);
rebuildRanges();
}
void PDFDocument::setRotation(Poppler::Page::Rotation rot)
{
if (m_rotation == rot)
return;
m_rotation = rot;
if (m_pages.isEmpty())
return;
for (PDFPage *p : qAsConst(m_pages))
p->setRotation(m_rotation);
rebuildRanges();
emit currentPageChanged(m_currentPage+1, m_ranges.count());
emit rotationChanged(m_rotation);
}
bool PDFDocument::setPage(int page)
{
page--;
if (m_currentPage == page)
{
ensureCurPageVisible();
return true;
}
if (m_pages.isEmpty())
{
m_currentPage = page;
return true;
}
if (page < 0 || numPages() < 0 || page >= m_doc->numPages())
return false;
m_currentPage = page;
emit currentPageChanged(m_currentPage + 1, m_ranges.count());
ensureCurPageVisible();
return true;
}
bool PDFDocument::nextPage()
{
if (m_doc == nullptr || m_currentPage >= m_doc->numPages() - 1)
return false;
m_currentPage++;
emit currentPageChanged(m_currentPage + 1, m_ranges.count());
ensureCurPageVisible();
return true;
}
bool PDFDocument::previousPage()
{
if (m_doc == nullptr || m_currentPage < 1)
return false;
m_currentPage--;
emit currentPageChanged(m_currentPage + 1, m_ranges.count());
ensureCurPageVisible();
return true;
}
void PDFDocument::onVSliderValueChanged(int y)
{
y += m_scrollArea->height() / 2;
int curPage = m_ranges.count() - 1;
for (int i = 0; i < m_ranges.count(); ++i)
{
if (m_ranges.at(i) > y)
{
curPage = i - 1;
break;
}
}
if (curPage != m_currentPage && curPage >= 0)
{
m_currentPage = curPage;
emit currentPageChanged(m_currentPage + 1, m_ranges.count());
}
}
void PDFDocument::onPageRendered()
{
PDFPage *p = qobject_cast<PDFPage *> (sender());
if (m_renderedPages.contains(p))
return;
while (m_renderedPages.count() > m_maxCachedPages - 1)
{
p = m_renderedPages.takeFirst();
if (p)
p->clearPixmap();
}
m_renderedPages.push_back(p);
}
void PDFDocument::onLinkClicked(const Poppler::LinkDestination &dest)
{
PDFPage *p = qobject_cast<PDFPage *>(sender());
if (!p)
return;
m_currentPage = dest.pageNumber() - 1;
emit currentPageChanged(m_currentPage + 1, m_ranges.count());
ensureCurPageVisible(qRound(dest.right() * p->width()),
qRound(dest.top() * p->height()));
}
QVector<QImage> PDFDocument::getPrintableImages(int resolution) const
{
QVector<QImage> imageList;
imageList.reserve(m_pages.size());
for (const PDFPage *p : m_pages)
imageList.append(p->getPrintableImage(resolution));
return imageList;
}
QImage PDFDocument::getPrintableImage(int pageNum, int resolution) const
{
if (pageNum < 0 || pageNum >= m_pages.size())
return QImage();
return m_pages[pageNum]->getPrintableImage(resolution);
}
void PDFDocument::ensureCurPageVisible(int xOfs, int yOfs)
{
if (m_currentPage >= m_ranges.size() || m_currentPage < 0)
return;
if (m_scrollArea->verticalScrollBar()->maximum() == 0)
{
QTimer::singleShot(50, this, [this, xOfs, yOfs]() { ensureCurPageVisible(xOfs, yOfs); });
return;
}
int y = m_ranges.at(m_currentPage) + yOfs;
y -= m_pdfLabel->layout()->spacing() * 2;
int x = m_scrollArea->horizontalScrollBar()->value() + xOfs;
m_scrollArea->ensureVisible(x, y, 0, 0);
y += m_scrollArea->height();
m_scrollArea->ensureVisible(x, y, 0, 0);
}
void PDFDocument::rebuildRanges()
{
QVBoxLayout *l = qobject_cast<QVBoxLayout *> (m_pdfLabel->layout());
m_ranges.clear();
int height = l->contentsMargins().top();
for (PDFPage *p : qAsConst(m_pages))
{
m_ranges += height;
height += p->height();
height += l->spacing();
}
m_pdfLabel->resize(m_pdfLabel->sizeHint());
m_pdfLabel->setMinimumSize(m_pdfLabel->sizeHint());
}
}