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 const auto docStatus = document ? document->status() : QPdfDocument::Status::Null;
429 d->documentStatusChanged(docStatus);
447void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
450 if (d->m_searchModel == searchModel)
453 if (d->m_searchModel)
454 d->m_searchModel->disconnect(
this);
456 d->m_searchModel = searchModel;
457 emit searchModelChanged(searchModel);
460 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
461 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
463 setCurrentSearchResultIndex(-1);
662void QPdfView::paintEvent(QPaintEvent *event)
666 QPainter painter(viewport());
667 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
668 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
670 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
671 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
672 const QRect pageGeometry = it.value().first;
673 if (pageGeometry.intersects(d->m_viewport)) {
674 painter.fillRect(pageGeometry, Qt::white);
676 const int page = it.key();
677 const auto pageIt = d->m_pageCache.constFind(page);
678 if (pageIt != d->m_pageCache.cend()) {
679 const QImage &img = pageIt.value();
680 painter.drawImage(pageGeometry, img);
682 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
685 const QTransform scaleTransform = d->screenScaleTransform(page);
687 const QString fmt = u"page %1 @ %2, %3"_s;
688 d->m_linkModel.setPage(page);
689 const int linkCount = d->m_linkModel.rowCount({});
690 for (
int i = 0; i < linkCount; ++i) {
691 const QRectF linkBounds = scaleTransform.mapRect(
692 d->m_linkModel.data(d->m_linkModel.index(i),
693 int(QPdfLinkModel::Role::Rect)).toRectF())
694 .translated(pageGeometry.topLeft());
695 painter.setPen(Qt::blue);
696 painter.drawRect(linkBounds);
697 painter.setPen(Qt::red);
698 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
699 int(QPdfLinkModel::Role::Location)).toPoint();
701 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
702 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
703 int(QPdfLinkModel::Role::Page)).toInt())
704 .arg(loc.x()).arg(loc.y()));
707 if (d->m_searchModel) {
709 const auto w = CurrentSearchResultWidth / 2;
710 QMargins highlightRectOffset(w, w, w, w);
712 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
713 for (
const QRectF &rect : result.rectangles()) {
714 const auto r = rect.marginsAdded(highlightRectOffset);
715 painter.fillRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()), SearchResultHighlight);
719 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
720 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
721 if (cur.page() == page) {
722 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
723 for (
const auto &rect : cur.rectangles()) {
724 const auto r = rect.marginsAdded(highlightRectOffset);
725 painter.drawRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()));
771void QPdfView::mouseReleaseEvent(QMouseEvent *event)
774 auto pagePos = d->pagePosition(event->position());
775 if (pagePos.isValid()) {
776 d->m_linkModel.setPage(pagePos.page());
777 auto dest = d->m_linkModel.linkAt(pagePos.location());
778 if (dest.isValid()) {
779 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
780 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
786void QPdfView::wheelEvent(QWheelEvent* event)
788 if (event->modifiers() & Qt::ControlModifier) {
789 int delta = event->angleDelta().y();
797 auto calculateZoomStep = [](qreal currentZoomFactor,
int sign) {
798 constexpr qreal baseStep = 0.1;
799 constexpr qreal ceilStep = 0.9;
801 const auto factor = std::ceil(currentZoomFactor + sign * baseStep);
802 return qMin(baseStep * factor, ceilStep);
804 qreal currentZoom = zoomFactor();
805 qreal stepSize = calculateZoomStep(currentZoom, (delta > 0) ? +1 : -1);
807 qreal zoomChange = (delta > 0) ? stepSize : -stepSize;
808 qreal newZoomFactor = qBound(0.1, currentZoom + zoomChange, 10.0);
809 const auto cursorPageAndPos = d->pagePosition(event->position());
811 setZoomFactor(newZoomFactor);
814 if (cursorPageAndPos.isValid()) {
815 const QRect newPageGeometry = d->m_documentLayout.pageGeometryAndScale.value(cursorPageAndPos.page()).first;
816 const QPointF newPosInDoc = newPageGeometry.topLeft() +
817 d->screenScaleTransform(cursorPageAndPos.page()).map(cursorPageAndPos.location());
818 horizontalScrollBar()->setValue(qRound(newPosInDoc.x() - event->position().x()));
819 verticalScrollBar()->setValue(qRound(newPosInDoc.y() - event->position().y()));
824 QAbstractScrollArea::wheelEvent(event);