88 if (m_viewport == viewport)
91 const QSize oldSize = m_viewport.size();
93 m_viewport = viewport;
95 if (oldSize != m_viewport.size()) {
98 if (m_zoomMode != QPdfView::ZoomMode::Custom) {
103 if (m_pageMode == QPdfView::PageMode::MultiPage) {
107 const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
110 for (
auto it = m_documentLayout.pageGeometryAndScale.cbegin();
111 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
112 const QRect pageGeometry = it.value().first;
113 if (pageGeometry.intersects(currentPageLine)) {
114 currentPage = it.key();
132 const QSize p = q->viewport()->size();
133 const QSize v = m_documentLayout.documentSize;
135 q->horizontalScrollBar()->setRange(0, v.width() - p.width());
136 q->horizontalScrollBar()->setPageStep(p.width());
137 q->verticalScrollBar()->setRange(0, v.height() - p.height());
138 q->verticalScrollBar()->setPageStep(p.height());
148 if (!m_cachedPagesLRU.contains(pageNumber)) {
149 if (m_cachedPagesLRU.size() > m_pageCacheLimit)
150 m_pageCache.remove(m_cachedPagesLRU.takeFirst());
152 m_cachedPagesLRU.append(pageNumber);
155 m_pageCache.insert(pageNumber, image);
157 q->viewport()->update();
184 if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
185 return documentLayout;
187 QHash<
int, QPair<QRect, qreal>> pageGeometryAndScale;
189 const int pageCount = m_document->pageCount();
193 const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
194 const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
197 for (
int page = startPage; page < endPage; ++page) {
199 qreal pageScale = m_zoomFactor;
200 if (m_zoomMode == QPdfView::ZoomMode::Custom) {
201 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
202 }
else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
203 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
204 pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
205 qreal(pageSize.width()));
206 pageSize *= pageScale;
207 }
else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
208 const QSize viewportSize(m_viewport.size() +
209 QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
211 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
212 QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
214 pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
215 pageSize = scaledSize;
218 totalWidth = qMax(totalWidth, pageSize.width());
220 pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
223 totalWidth += m_documentMargins.left() + m_documentMargins.right();
225 int pageY = m_documentMargins.top();
228 for (
int page = startPage; page < endPage; ++page) {
229 const QSize pageSize = pageGeometryAndScale[page].first.size();
232 const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
234 pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
239 pageY += m_documentMargins.bottom();
241 documentLayout.pageGeometryAndScale = pageGeometryAndScale;
244 documentLayout.documentSize = QSize(totalWidth, pageY);
246 return documentLayout;
295QPdfView::QPdfView(QWidget *parent)
296 : QAbstractScrollArea(parent)
297 , d_ptr(
new QPdfViewPrivate(
this))
303 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged,
this,
304 [d](
int page){ d->currentPageChanged(page); });
306 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
this,
307 [d](
int pageNumber, QSize imageSize,
const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
308 d->pageRendered(pageNumber, imageSize, image, requestId); });
310 verticalScrollBar()->setSingleStep(20);
311 horizontalScrollBar()->setSingleStep(20);
313 setMouseTracking(
true);
314 d->calculateViewport();
329void QPdfView::setDocument(QPdfDocument *document)
333 if (d->m_document == document)
337 disconnect(d->m_documentStatusChangedConnection);
339 d->m_document = document;
340 emit documentChanged(d->m_document);
343 d->m_documentStatusChangedConnection =
344 connect(d->m_document.data(), &QPdfDocument::statusChanged,
this,
345 [d](QPdfDocument::Status s){ d->documentStatusChanged(s); });
347 d->m_pageRenderer->setDocument(d->m_document);
348 d->m_linkModel.setDocument(d->m_document);
350 d->documentStatusChanged(document->status());
368void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
371 if (d->m_searchModel == searchModel)
374 if (d->m_searchModel)
375 d->m_searchModel->disconnect(
this);
377 d->m_searchModel = searchModel;
378 emit searchModelChanged(searchModel);
381 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
382 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
384 setCurrentSearchResultIndex(-1);
583void QPdfView::paintEvent(QPaintEvent *event)
587 QPainter painter(viewport());
588 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
589 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
591 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
592 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
593 const QRect pageGeometry = it.value().first;
594 if (pageGeometry.intersects(d->m_viewport)) {
595 painter.fillRect(pageGeometry, Qt::white);
597 const int page = it.key();
598 const auto pageIt = d->m_pageCache.constFind(page);
599 if (pageIt != d->m_pageCache.cend()) {
600 const QImage &img = pageIt.value();
601 painter.drawImage(pageGeometry, img);
603 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
606 const QTransform scaleTransform = d->screenScaleTransform(page);
608 const QString fmt = u"page %1 @ %2, %3"_s;
609 d->m_linkModel.setPage(page);
610 const int linkCount = d->m_linkModel.rowCount({});
611 for (
int i = 0; i < linkCount; ++i) {
612 const QRectF linkBounds = scaleTransform.mapRect(
613 d->m_linkModel.data(d->m_linkModel.index(i),
614 int(QPdfLinkModel::Role::Rect)).toRectF())
615 .translated(pageGeometry.topLeft());
616 painter.setPen(Qt::blue);
617 painter.drawRect(linkBounds);
618 painter.setPen(Qt::red);
619 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
620 int(QPdfLinkModel::Role::Location)).toPoint();
622 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
623 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
624 int(QPdfLinkModel::Role::Page)).toInt())
625 .arg(loc.x()).arg(loc.y()));
628 if (d->m_searchModel) {
629 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
630 for (
const QRectF &rect : result.rectangles())
631 painter.fillRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()), SearchResultHighlight);
634 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
635 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
636 if (cur.page() == page) {
637 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
638 for (
const auto &rect : cur.rectangles())
639 painter.drawRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()));
671void QPdfView::mouseMoveEvent(QMouseEvent *event)
674 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
675 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
676 const int page = it.key();
677 const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
678 const QRect pageGeometry = it.value().first;
679 if (pageGeometry.contains(event->position().toPoint())) {
680 QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()
681 + d->m_viewport.topLeft());
682 d->m_linkModel.setPage(page);
683 auto dest = d->m_linkModel.linkAt(posInPoints);
684 setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor);
686 qCDebug(qLcWLink) << event->position() <<
":" << posInPoints <<
"pt ->" << dest;
691void QPdfView::mouseReleaseEvent(QMouseEvent *event)
694 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
695 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
696 const int page = it.key();
697 const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
698 const QRect pageGeometry = it.value().first;
699 if (pageGeometry.contains(event->position().toPoint())) {
700 QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()
701 + d->m_viewport.topLeft());
702 d->m_linkModel.setPage(page);
703 auto dest = d->m_linkModel.linkAt(posInPoints);
704 if (dest.isValid()) {
705 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
706 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());