#include "nglpdfdocument.h" #include "nglpdfpage.h" #include #include #include #include #include #include #include #include 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 (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(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 (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(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 PDFDocument::getPrintableImages(int resolution) const { QVector 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 (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()); } }