94 if (m_viewport == viewport)
97 const QSize oldSize = m_viewport.size();
99 m_viewport = viewport;
101 if (oldSize != m_viewport.size()) {
104 if (m_zoomMode != QPdfView::ZoomMode::Custom) {
109 if (m_pageMode == QPdfView::PageMode::MultiPage) {
113 const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
116 for (
auto it = m_documentLayout.pageGeometryAndScale.cbegin();
117 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
118 const QRect pageGeometry = it.value().first;
119 if (pageGeometry.intersects(currentPageLine)) {
120 currentPage = it.key();
157 const QRect subRect = link.location().isNull() ? link.rectangles().constFirst().toRect()
158 : QRect(link.location().toPoint(), QSize(1, 1));
159 const auto &pageGeometryAndScale = m_documentLayout.pageGeometryAndScale.value(link.page());
160 const QRect scaledSubRect(subRect.topLeft() * pageGeometryAndScale.second,
161 subRect.size() * pageGeometryAndScale.second);
162 const QRect oldViewport = m_viewport;
165 if (m_viewport.contains(scaledSubRect))
172 if (scaledSubRect.left() < m_viewport.left())
173 sx = scaledSubRect.left();
174 else if (scaledSubRect.right() > m_viewport.right())
175 sx = scaledSubRect.right();
176 if (scaledSubRect.top() < m_viewport.top())
177 sy = scaledSubRect.top();
178 else if (scaledSubRect.bottom() > m_viewport.bottom())
179 sy = scaledSubRect.bottom();
184 if (m_pageMode == QPdfView::PageMode::MultiPage) {
185 sx = m_viewport.x() + (sx >= 0 ? sx : 0);
186 sy = m_viewport.y() + (sy >= 0 ? sy : 0);
190 q->horizontalScrollBar()->setValue(sx);
192 q->verticalScrollBar()->setValue(sy);
194 qCDebug(qLcWLink) <<
"scrolled to page" << link.page() <<
"@" << link.location()
195 <<
"scaled target region" << scaledSubRect <<
"scrollbars" << sx << sy
196 <<
"viewport" << oldViewport <<
"->" << m_viewport;
206 if (!m_cachedPagesLRU.contains(pageNumber)) {
207 if (m_cachedPagesLRU.size() > m_pageCacheLimit)
208 m_pageCache.remove(m_cachedPagesLRU.takeFirst());
210 m_cachedPagesLRU.append(pageNumber);
213 m_pageCache.insert(pageNumber, image);
215 q->viewport()->update();
242 if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
243 return documentLayout;
245 QHash<
int, QPair<QRect, qreal>> pageGeometryAndScale;
247 const int pageCount = m_document->pageCount();
251 const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
252 const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
255 for (
int page = startPage; page < endPage; ++page) {
257 qreal pageScale = m_zoomFactor;
258 if (m_zoomMode == QPdfView::ZoomMode::Custom) {
259 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
260 }
else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
261 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
262 pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
263 qreal(pageSize.width()));
264 pageSize *= pageScale;
265 }
else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
266 const QSize viewportSize(m_viewport.size() +
267 QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
269 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
270 QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
272 pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
273 pageSize = scaledSize;
276 totalWidth = qMax(totalWidth, pageSize.width());
278 pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
281 totalWidth += m_documentMargins.left() + m_documentMargins.right();
283 int pageY = m_documentMargins.top();
286 for (
int page = startPage; page < endPage; ++page) {
287 const QSize pageSize = pageGeometryAndScale[page].first.size();
290 const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
292 pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
297 pageY += m_documentMargins.bottom();
299 documentLayout.pageGeometryAndScale = pageGeometryAndScale;
302 documentLayout.documentSize = QSize(totalWidth, pageY);
304 return documentLayout;
318 for (
auto it = m_documentLayout.pageGeometryAndScale.cbegin();
319 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
320 const int page = it.key();
321 const QTransform screenInvTransform = screenScaleTransform(page).inverted();
322 const QRect pageGeometry = it.value().first;
323 if (pageGeometry.contains(viewPosition.toPoint())) {
324 QPointF posInPoints = screenInvTransform.map(viewPosition - pageGeometry.topLeft()
325 + m_viewport.topLeft());
326 return QPdfLink(page, posInPoints, 1);
369QPdfView::QPdfView(QWidget *parent)
370 : QAbstractScrollArea(parent)
371 , d_ptr(
new QPdfViewPrivate(
this))
377 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged,
this,
378 [d](
int page){ d->currentPageChanged(page); });
379 connect(d->m_pageNavigator, &QPdfPageNavigator::currentZoomChanged,
380 this, &QPdfView::setZoomFactor);
382 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
this,
383 [d](
int pageNumber, QSize imageSize,
const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
384 d->pageRendered(pageNumber, imageSize, image, requestId); });
386 verticalScrollBar()->setSingleStep(20);
387 horizontalScrollBar()->setSingleStep(20);
389 setMouseTracking(
true);
390 d->calculateViewport();
405void QPdfView::setDocument(QPdfDocument *document)
409 if (d->m_document == document)
413 disconnect(d->m_documentStatusChangedConnection);
415 d->m_document = document;
416 emit documentChanged(d->m_document);
419 d->m_documentStatusChangedConnection =
420 connect(d->m_document.data(), &QPdfDocument::statusChanged,
this,
421 [d](QPdfDocument::Status s){ d->documentStatusChanged(s); });
423 d->m_pageRenderer->setDocument(d->m_document);
424 d->m_linkModel.setDocument(d->m_document);
426 d->documentStatusChanged(document->status());
444void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
447 if (d->m_searchModel == searchModel)
450 if (d->m_searchModel)
451 d->m_searchModel->disconnect(
this);
453 d->m_searchModel = searchModel;
454 emit searchModelChanged(searchModel);
457 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
458 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
460 setCurrentSearchResultIndex(-1);
659void QPdfView::paintEvent(QPaintEvent *event)
663 QPainter painter(viewport());
664 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
665 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
667 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
668 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
669 const QRect pageGeometry = it.value().first;
670 if (pageGeometry.intersects(d->m_viewport)) {
671 painter.fillRect(pageGeometry, Qt::white);
673 const int page = it.key();
674 const auto pageIt = d->m_pageCache.constFind(page);
675 if (pageIt != d->m_pageCache.cend()) {
676 const QImage &img = pageIt.value();
677 painter.drawImage(pageGeometry, img);
679 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
682 const QTransform scaleTransform = d->screenScaleTransform(page);
684 const QString fmt = u"page %1 @ %2, %3"_s;
685 d->m_linkModel.setPage(page);
686 const int linkCount = d->m_linkModel.rowCount({});
687 for (
int i = 0; i < linkCount; ++i) {
688 const QRectF linkBounds = scaleTransform.mapRect(
689 d->m_linkModel.data(d->m_linkModel.index(i),
690 int(QPdfLinkModel::Role::Rect)).toRectF())
691 .translated(pageGeometry.topLeft());
692 painter.setPen(Qt::blue);
693 painter.drawRect(linkBounds);
694 painter.setPen(Qt::red);
695 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
696 int(QPdfLinkModel::Role::Location)).toPoint();
698 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
699 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
700 int(QPdfLinkModel::Role::Page)).toInt())
701 .arg(loc.x()).arg(loc.y()));
704 if (d->m_searchModel) {
706 const auto w = CurrentSearchResultWidth / 2;
707 QMargins highlightRectOffset(w, w, w, w);
709 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
710 for (
const QRectF &rect : result.rectangles()) {
711 const auto r = rect.marginsAdded(highlightRectOffset);
712 painter.fillRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()), SearchResultHighlight);
716 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
717 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
718 if (cur.page() == page) {
719 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
720 for (
const auto &rect : cur.rectangles()) {
721 const auto r = rect.marginsAdded(highlightRectOffset);
722 painter.drawRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()));
768void QPdfView::mouseReleaseEvent(QMouseEvent *event)
771 auto pagePos = d->pagePosition(event->position());
772 if (pagePos.isValid()) {
773 d->m_linkModel.setPage(pagePos.page());
774 auto dest = d->m_linkModel.linkAt(pagePos.location());
775 if (dest.isValid()) {
776 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
777 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
783void QPdfView::wheelEvent(QWheelEvent* event)
785 if (event->modifiers() & Qt::ControlModifier) {
786 int delta = event->angleDelta().y();
794 auto calculateZoomStep = [](qreal currentZoomFactor,
int sign) {
795 constexpr qreal baseStep = 0.1;
796 constexpr qreal ceilStep = 0.9;
798 const auto factor = std::ceil(currentZoomFactor + sign * baseStep);
799 return qMin(baseStep * factor, ceilStep);
801 qreal currentZoom = zoomFactor();
802 qreal stepSize = calculateZoomStep(currentZoom, (delta > 0) ? +1 : -1);
804 qreal zoomChange = (delta > 0) ? stepSize : -stepSize;
805 qreal newZoomFactor = qBound(0.1, currentZoom + zoomChange, 10.0);
806 const auto cursorPageAndPos = d->pagePosition(event->position());
808 setZoomFactor(newZoomFactor);
811 if (cursorPageAndPos.isValid()) {
812 const QRect newPageGeometry = d->m_documentLayout.pageGeometryAndScale.value(cursorPageAndPos.page()).first;
813 const QPointF newPosInDoc = newPageGeometry.topLeft() +
814 d->screenScaleTransform(cursorPageAndPos.page()).map(cursorPageAndPos.location());
815 horizontalScrollBar()->setValue(qRound(newPosInDoc.x() - event->position().x()));
816 verticalScrollBar()->setValue(qRound(newPosInDoc.y() - event->position().y()));
821 QAbstractScrollArea::wheelEvent(event);