100 if (m_viewport == viewport)
103 const QSize oldSize = m_viewport.size();
105 m_viewport = viewport;
107 if (oldSize != m_viewport.size()) {
110 if (m_zoomMode != QPdfView::ZoomMode::Custom) {
115 if (m_pageMode == QPdfView::PageMode::MultiPage) {
119 const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
122 for (
auto it = m_documentLayout.pageGeometryAndScale.cbegin();
123 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
124 const QRect pageGeometry = it.value().first;
125 if (pageGeometry.intersects(currentPageLine)) {
126 currentPage = it.key();
166 const QList<QRectF> rects = link.rectangles();
168 if (!rects.isEmpty() && rects.constFirst().topLeft() == link.location())
169 onPage = rects.constFirst();
170 else if (!link.location().isNull())
171 onPage = QRectF(link.location(), QSizeF(1, 1));
172 else if (!rects.isEmpty())
173 onPage = rects.constFirst();
178 const QRect pageGeometry = m_documentLayout.pageGeometryAndScale.value(link.page()).first;
179 const QRect target = screenScaleTransform(link.page()).mapRect(onPage)
180 .translated(pageGeometry.topLeft()).toAlignedRect();
181 const QRect oldViewport = m_viewport;
184 qMin(m_viewport.width(), m_viewport.height()) / 4);
185 const QRect wanted = target.marginsAdded(QMargins(margin, margin, margin, margin));
187 if (m_viewport.contains(wanted))
193 int sx = m_viewport.x();
194 if (wanted.left() < m_viewport.left())
196 else if (wanted.right() > m_viewport.right())
197 sx = qMin(wanted.left(), wanted.right() - m_viewport.width() + 1);
198 int sy = m_viewport.y();
199 if (wanted.top() < m_viewport.top())
201 else if (wanted.bottom() > m_viewport.bottom())
202 sy = qMin(wanted.top(), wanted.bottom() - m_viewport.height() + 1);
204 q->horizontalScrollBar()->setValue(sx);
205 q->verticalScrollBar()->setValue(sy);
207 qCDebug(qLcWLink) <<
"scrolled to page" << link.page() <<
"@" << link.location()
208 <<
"target region" << target <<
"scrollbars" << sx << sy
209 <<
"viewport" << oldViewport <<
"->" << m_viewport;
228 if (!m_cachedPagesLRU.contains(pageNumber)) {
229 if (m_cachedPagesLRU.size() > m_pageCacheLimit)
230 m_pageCache.remove(m_cachedPagesLRU.takeFirst());
232 m_cachedPagesLRU.append(pageNumber);
235 m_pageCache.insert(pageNumber, image);
237 q->viewport()->update();
264 if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
265 return documentLayout;
267 QHash<
int, QPair<QRect, qreal>> pageGeometryAndScale;
269 const int pageCount = m_document->pageCount();
273 const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
274 const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
277 for (
int page = startPage; page < endPage; ++page) {
279 qreal pageScale = m_zoomFactor;
280 if (m_zoomMode == QPdfView::ZoomMode::Custom) {
281 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
282 }
else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
283 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
284 pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
285 qreal(pageSize.width()));
286 pageSize *= pageScale;
287 }
else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
288 const QSize viewportSize(m_viewport.size() +
289 QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
291 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
292 QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
294 pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
295 pageSize = scaledSize;
298 totalWidth = qMax(totalWidth, pageSize.width());
300 pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
303 totalWidth += m_documentMargins.left() + m_documentMargins.right();
305 int pageY = m_documentMargins.top();
308 for (
int page = startPage; page < endPage; ++page) {
309 const QSize pageSize = pageGeometryAndScale[page].first.size();
312 const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
314 pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
319 pageY += m_documentMargins.bottom();
321 documentLayout.pageGeometryAndScale = pageGeometryAndScale;
324 documentLayout.documentSize = QSize(totalWidth, pageY);
326 return documentLayout;
391QPdfView::QPdfView(QWidget *parent)
392 : QAbstractScrollArea(parent)
393 , d_ptr(
new QPdfViewPrivate(
this))
399 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged,
this,
400 [d](
int page){ d->currentPageChanged(page); });
401 connect(d->m_pageNavigator, &QPdfPageNavigator::jumped,
this,
402 [d](
const QPdfLink &link){ d->jumped(link); });
403 connect(d->m_pageNavigator, &QPdfPageNavigator::currentZoomChanged,
this,
404 [d](qreal zoom){ d->currentZoomChanged(zoom); });
406 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
this,
407 [d](
int pageNumber, QSize imageSize,
const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
408 d->pageRendered(pageNumber, imageSize, image, requestId); });
410 verticalScrollBar()->setSingleStep(20);
411 horizontalScrollBar()->setSingleStep(20);
413 setMouseTracking(
true);
414 d->calculateViewport();
432void QPdfView::setDocument(QPdfDocument *document)
436 if (d->m_document == document)
440 disconnect(d->m_documentStatusChangedConnection);
442 d->m_document = document;
443 emit documentChanged(d->m_document);
446 d->m_documentStatusChangedConnection =
447 connect(d->m_document.data(), &QPdfDocument::statusChanged,
this,
448 [d](QPdfDocument::Status s){ d->documentStatusChanged(s); });
450 d->m_pageRenderer->setDocument(d->m_document);
451 d->m_linkModel.setDocument(d->m_document);
453 const auto docStatus = document ? document->status() : QPdfDocument::Status::Null;
454 d->documentStatusChanged(docStatus);
472void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
475 if (d->m_searchModel == searchModel)
478 if (d->m_searchModel)
479 d->m_searchModel->disconnect(
this);
481 d->m_searchModel = searchModel;
482 emit searchModelChanged(searchModel);
485 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
486 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
488 setCurrentSearchResultIndex(-1);
687void QPdfView::paintEvent(QPaintEvent *event)
691 QPainter painter(viewport());
692 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
693 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
695 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
696 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
697 const QRect pageGeometry = it.value().first;
698 if (pageGeometry.intersects(d->m_viewport)) {
699 painter.fillRect(pageGeometry, Qt::white);
701 const int page = it.key();
702 const auto pageIt = d->m_pageCache.constFind(page);
703 if (pageIt != d->m_pageCache.cend()) {
704 const QImage &img = pageIt.value();
705 painter.drawImage(pageGeometry, img);
707 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
710 const QTransform scaleTransform = d->screenScaleTransform(page);
712 const QString fmt = u"page %1 @ %2, %3"_s;
713 d->m_linkModel.setPage(page);
714 const int linkCount = d->m_linkModel.rowCount({});
715 for (
int i = 0; i < linkCount; ++i) {
716 const QRectF linkBounds = scaleTransform.mapRect(
717 d->m_linkModel.data(d->m_linkModel.index(i),
718 int(QPdfLinkModel::Role::Rect)).toRectF())
719 .translated(pageGeometry.topLeft());
720 painter.setPen(Qt::blue);
721 painter.drawRect(linkBounds);
722 painter.setPen(Qt::red);
723 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
724 int(QPdfLinkModel::Role::Location)).toPoint();
726 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
727 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
728 int(QPdfLinkModel::Role::Page)).toInt())
729 .arg(loc.x()).arg(loc.y()));
732 if (d->m_searchModel) {
734 const auto w = CurrentSearchResultWidth / 2;
735 QMargins highlightRectOffset(w, w, w, w);
737 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
738 for (
const QRectF &rect : result.rectangles()) {
739 const auto r = rect.marginsAdded(highlightRectOffset);
740 painter.fillRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()), SearchResultHighlight);
744 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
745 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
746 if (cur.page() == page) {
747 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
748 for (
const auto &rect : cur.rectangles()) {
749 const auto r = rect.marginsAdded(highlightRectOffset);
750 painter.drawRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()));
796void QPdfView::mouseReleaseEvent(QMouseEvent *event)
799 auto pagePos = d->pagePosition(event->position());
800 if (pagePos.isValid()) {
801 d->m_linkModel.setPage(pagePos.page());
802 auto dest = d->m_linkModel.linkAt(pagePos.location());
803 if (dest.isValid()) {
804 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
805 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
810void QPdfView::wheelEvent(QWheelEvent* event)
812 if (event->modifiers() & Qt::ControlModifier) {
813 int delta = event->angleDelta().y();
821 auto calculateZoomStep = [](qreal currentZoomFactor,
int sign) {
822 constexpr qreal baseStep = 0.1;
823 constexpr qreal ceilStep = 0.9;
825 const auto factor = std::ceil(currentZoomFactor + sign * baseStep);
826 return qMin(baseStep * factor, ceilStep);
828 qreal currentZoom = zoomFactor();
829 qreal stepSize = calculateZoomStep(currentZoom, (delta > 0) ? +1 : -1);
831 qreal zoomChange = (delta > 0) ? stepSize : -stepSize;
832 qreal newZoomFactor = qBound(0.1, currentZoom + zoomChange, 10.0);
833 const auto cursorPageAndPos = d->pagePosition(event->position());
835 setZoomFactor(newZoomFactor);
838 if (cursorPageAndPos.isValid()) {
839 const QRect newPageGeometry = d->m_documentLayout.pageGeometryAndScale.value(cursorPageAndPos.page()).first;
840 const QPointF newPosInDoc = newPageGeometry.topLeft() +
841 d->screenScaleTransform(cursorPageAndPos.page()).map(cursorPageAndPos.location());
842 horizontalScrollBar()->setValue(qRound(newPosInDoc.x() - event->position().x()));
843 verticalScrollBar()->setValue(qRound(newPosInDoc.y() - event->position().y()));
848 QAbstractScrollArea::wheelEvent(event);