335 lines
8.1 KiB
C++
Executable File
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());
|
|
}
|
|
|
|
}
|