96 if (m_viewport == viewport)
99 const QSize oldSize = m_viewport.size();
101 m_viewport = viewport;
103 if (oldSize != m_viewport.size()) {
106 if (m_zoomMode != QPdfView::ZoomMode::Custom) {
111 if (m_pageMode == QPdfView::PageMode::MultiPage) {
115 const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
118 for (
auto it = m_documentLayout.pageGeometryAndScale.cbegin();
119 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
120 const QRect pageGeometry = it.value().first;
121 if (pageGeometry.intersects(currentPageLine)) {
122 currentPage = it.key();
159 const QRect subRect = link.location().isNull() ? link.rectangles().constFirst().toRect()
160 : QRect(link.location().toPoint(), QSize(1, 1));
161 const auto &pageGeometryAndScale = m_documentLayout.pageGeometryAndScale.value(link.page());
162 const QRect scaledSubRect(subRect.topLeft() * pageGeometryAndScale.second,
163 subRect.size() * pageGeometryAndScale.second);
164 const QRect oldViewport = m_viewport;
167 if (m_viewport.contains(scaledSubRect))
174 if (scaledSubRect.left() < m_viewport.left())
175 sx = scaledSubRect.left();
176 else if (scaledSubRect.right() > m_viewport.right())
177 sx = scaledSubRect.right();
178 if (scaledSubRect.top() < m_viewport.top())
179 sy = scaledSubRect.top();
180 else if (scaledSubRect.bottom() > m_viewport.bottom())
181 sy = scaledSubRect.bottom();
186 if (m_pageMode == QPdfView::PageMode::MultiPage) {
187 sx = m_viewport.x() + (sx >= 0 ? sx : 0);
188 sy = m_viewport.y() + (sy >= 0 ? sy : 0);
192 q->horizontalScrollBar()->setValue(sx);
194 q->verticalScrollBar()->setValue(sy);
196 qCDebug(qLcWLink) <<
"scrolled to page" << link.page() <<
"@" << link.location()
197 <<
"scaled target region" << scaledSubRect <<
"scrollbars" << sx << sy
198 <<
"viewport" << oldViewport <<
"->" << m_viewport;
208 if (!m_cachedPagesLRU.contains(pageNumber)) {
209 if (m_cachedPagesLRU.size() > m_pageCacheLimit)
210 m_pageCache.remove(m_cachedPagesLRU.takeFirst());
212 m_cachedPagesLRU.append(pageNumber);
215 m_pageCache.insert(pageNumber, image);
217 q->viewport()->update();
244 if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
245 return documentLayout;
247 QHash<
int, QPair<QRect, qreal>> pageGeometryAndScale;
249 const int pageCount = m_document->pageCount();
253 const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
254 const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
257 for (
int page = startPage; page < endPage; ++page) {
259 qreal pageScale = m_zoomFactor;
260 if (m_zoomMode == QPdfView::ZoomMode::Custom) {
261 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
262 }
else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
263 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
264 pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
265 qreal(pageSize.width()));
266 pageSize *= pageScale;
267 }
else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
268 const QSize viewportSize(m_viewport.size() +
269 QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
271 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
272 QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
274 pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
275 pageSize = scaledSize;
278 totalWidth = qMax(totalWidth, pageSize.width());
280 pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
283 totalWidth += m_documentMargins.left() + m_documentMargins.right();
285 int pageY = m_documentMargins.top();
288 for (
int page = startPage; page < endPage; ++page) {
289 const QSize pageSize = pageGeometryAndScale[page].first.size();
292 const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
294 pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
299 pageY += m_documentMargins.bottom();
301 documentLayout.pageGeometryAndScale = pageGeometryAndScale;
304 documentLayout.documentSize = QSize(totalWidth, pageY);
306 return documentLayout;
371QPdfView::QPdfView(QWidget *parent)
372 : QAbstractScrollArea(parent)
373 , d_ptr(
new QPdfViewPrivate(
this))
379 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged,
this,
380 [d](
int page){ d->currentPageChanged(page); });
381 connect(d->m_pageNavigator, &QPdfPageNavigator::currentZoomChanged,
382 this, &QPdfView::setZoomFactor);
384 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
this,
385 [d](
int pageNumber, QSize imageSize,
const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
386 d->pageRendered(pageNumber, imageSize, image, requestId); });
388 verticalScrollBar()->setSingleStep(20);
389 horizontalScrollBar()->setSingleStep(20);
391 setMouseTracking(
true);
392 d->calculateViewport();
407void QPdfView::setDocument(QPdfDocument *document)
411 if (d->m_document == document)
415 disconnect(d->m_documentStatusChangedConnection);
417 d->m_document = document;
418 emit documentChanged(d->m_document);
421 d->m_documentStatusChangedConnection =
422 connect(d->m_document.data(), &QPdfDocument::statusChanged,
this,
423 [d](QPdfDocument::Status s){ d->documentStatusChanged(s); });
425 d->m_pageRenderer->setDocument(d->m_document);
426 d->m_linkModel.setDocument(d->m_document);
428 d->documentStatusChanged(document->status());
446void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
449 if (d->m_searchModel == searchModel)
452 if (d->m_searchModel)
453 d->m_searchModel->disconnect(
this);
455 d->m_searchModel = searchModel;
456 emit searchModelChanged(searchModel);
459 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
460 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
462 setCurrentSearchResultIndex(-1);
661void QPdfView::paintEvent(QPaintEvent *event)
665 QPainter painter(viewport());
666 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
667 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
669 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
670 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
671 const QRect pageGeometry = it.value().first;
672 if (pageGeometry.intersects(d->m_viewport)) {
673 painter.fillRect(pageGeometry, Qt::white);
675 const int page = it.key();
676 const auto pageIt = d->m_pageCache.constFind(page);
677 if (pageIt != d->m_pageCache.cend()) {
678 const QImage &img = pageIt.value();
679 painter.drawImage(pageGeometry, img);
681 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
684 const QTransform scaleTransform = d->screenScaleTransform(page);
686 const QString fmt = u"page %1 @ %2, %3"_s;
687 d->m_linkModel.setPage(page);
688 const int linkCount = d->m_linkModel.rowCount({});
689 for (
int i = 0; i < linkCount; ++i) {
690 const QRectF linkBounds = scaleTransform.mapRect(
691 d->m_linkModel.data(d->m_linkModel.index(i),
692 int(QPdfLinkModel::Role::Rect)).toRectF())
693 .translated(pageGeometry.topLeft());
694 painter.setPen(Qt::blue);
695 painter.drawRect(linkBounds);
696 painter.setPen(Qt::red);
697 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
698 int(QPdfLinkModel::Role::Location)).toPoint();
700 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
701 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
702 int(QPdfLinkModel::Role::Page)).toInt())
703 .arg(loc.x()).arg(loc.y()));
706 if (d->m_searchModel) {
708 const auto w = CurrentSearchResultWidth / 2;
709 QMargins highlightRectOffset(w, w, w, w);
711 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
712 for (
const QRectF &rect : result.rectangles()) {
713 const auto r = rect.marginsAdded(highlightRectOffset);
714 painter.fillRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()), SearchResultHighlight);
718 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
719 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
720 if (cur.page() == page) {
721 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
722 for (
const auto &rect : cur.rectangles()) {
723 const auto r = rect.marginsAdded(highlightRectOffset);
724 painter.drawRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()));
770void QPdfView::mouseReleaseEvent(QMouseEvent *event)
773 auto pagePos = d->pagePosition(event->position());
774 if (pagePos.isValid()) {
775 d->m_linkModel.setPage(pagePos.page());
776 auto dest = d->m_linkModel.linkAt(pagePos.location());
777 if (dest.isValid()) {
778 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
779 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
785void QPdfView::wheelEvent(QWheelEvent* event)
787 if (event->modifiers() & Qt::ControlModifier) {
788 int delta = event->angleDelta().y();
796 auto calculateZoomStep = [](qreal currentZoomFactor,
int sign) {
797 constexpr qreal baseStep = 0.1;
798 constexpr qreal ceilStep = 0.9;
800 const auto factor = std::ceil(currentZoomFactor + sign * baseStep);
801 return qMin(baseStep * factor, ceilStep);
803 qreal currentZoom = zoomFactor();
804 qreal stepSize = calculateZoomStep(currentZoom, (delta > 0) ? +1 : -1);
806 qreal zoomChange = (delta > 0) ? stepSize : -stepSize;
807 qreal newZoomFactor = qBound(0.1, currentZoom + zoomChange, 10.0);
808 const auto cursorPageAndPos = d->pagePosition(event->position());
810 setZoomFactor(newZoomFactor);
813 if (cursorPageAndPos.isValid()) {
814 const QRect newPageGeometry = d->m_documentLayout.pageGeometryAndScale.value(cursorPageAndPos.page()).first;
815 const QPointF newPosInDoc = newPageGeometry.topLeft() +
816 d->screenScaleTransform(cursorPageAndPos.page()).map(cursorPageAndPos.location());
817 horizontalScrollBar()->setValue(qRound(newPosInDoc.x() - event->position().x()));
818 verticalScrollBar()->setValue(qRound(newPosInDoc.y() - event->position().y()));
823 QAbstractScrollArea::wheelEvent(event);