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();
138 const QSize p = q->viewport()->size();
139 const QSize v = m_documentLayout.documentSize;
141 q->horizontalScrollBar()->setRange(0, v.width() - p.width());
142 q->horizontalScrollBar()->setPageStep(p.width());
143 q->verticalScrollBar()->setRange(0, v.height() - p.height());
144 q->verticalScrollBar()->setPageStep(p.height());
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;
353QPdfView::QPdfView(QWidget *parent)
354 : QAbstractScrollArea(parent)
355 , d_ptr(
new QPdfViewPrivate(
this))
361 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged,
this,
362 [d](
int page){ d->currentPageChanged(page); });
363 connect(d->m_pageNavigator, &QPdfPageNavigator::currentZoomChanged,
364 this, &QPdfView::setZoomFactor);
366 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
this,
367 [d](
int pageNumber, QSize imageSize,
const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
368 d->pageRendered(pageNumber, imageSize, image, requestId); });
370 verticalScrollBar()->setSingleStep(20);
371 horizontalScrollBar()->setSingleStep(20);
373 setMouseTracking(
true);
374 d->calculateViewport();
389void QPdfView::setDocument(QPdfDocument *document)
393 if (d->m_document == document)
397 disconnect(d->m_documentStatusChangedConnection);
399 d->m_document = document;
400 emit documentChanged(d->m_document);
403 d->m_documentStatusChangedConnection =
404 connect(d->m_document.data(), &QPdfDocument::statusChanged,
this,
405 [d](QPdfDocument::Status s){ d->documentStatusChanged(s); });
407 d->m_pageRenderer->setDocument(d->m_document);
408 d->m_linkModel.setDocument(d->m_document);
410 d->documentStatusChanged(document->status());
428void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
431 if (d->m_searchModel == searchModel)
434 if (d->m_searchModel)
435 d->m_searchModel->disconnect(
this);
437 d->m_searchModel = searchModel;
438 emit searchModelChanged(searchModel);
441 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
442 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
444 setCurrentSearchResultIndex(-1);
643void QPdfView::paintEvent(QPaintEvent *event)
647 QPainter painter(viewport());
648 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
649 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
651 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
652 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
653 const QRect pageGeometry = it.value().first;
654 if (pageGeometry.intersects(d->m_viewport)) {
655 painter.fillRect(pageGeometry, Qt::white);
657 const int page = it.key();
658 const auto pageIt = d->m_pageCache.constFind(page);
659 if (pageIt != d->m_pageCache.cend()) {
660 const QImage &img = pageIt.value();
661 painter.drawImage(pageGeometry, img);
663 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
666 const QTransform scaleTransform = d->screenScaleTransform(page);
668 const QString fmt = u"page %1 @ %2, %3"_s;
669 d->m_linkModel.setPage(page);
670 const int linkCount = d->m_linkModel.rowCount({});
671 for (
int i = 0; i < linkCount; ++i) {
672 const QRectF linkBounds = scaleTransform.mapRect(
673 d->m_linkModel.data(d->m_linkModel.index(i),
674 int(QPdfLinkModel::Role::Rect)).toRectF())
675 .translated(pageGeometry.topLeft());
676 painter.setPen(Qt::blue);
677 painter.drawRect(linkBounds);
678 painter.setPen(Qt::red);
679 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
680 int(QPdfLinkModel::Role::Location)).toPoint();
682 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
683 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
684 int(QPdfLinkModel::Role::Page)).toInt())
685 .arg(loc.x()).arg(loc.y()));
688 if (d->m_searchModel) {
689 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
690 for (
const QRectF &rect : result.rectangles())
691 painter.fillRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()), SearchResultHighlight);
694 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
695 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
696 if (cur.page() == page) {
697 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
698 for (
const auto &rect : cur.rectangles())
699 painter.drawRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()));
731void QPdfView::mouseMoveEvent(QMouseEvent *event)
734 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
735 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
736 const int page = it.key();
737 const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
738 const QRect pageGeometry = it.value().first;
739 if (pageGeometry.contains(event->position().toPoint())) {
740 QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()
741 + d->m_viewport.topLeft());
742 d->m_linkModel.setPage(page);
743 auto dest = d->m_linkModel.linkAt(posInPoints);
744 setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor);
746 qCDebug(qLcWLink) << event->position() <<
":" << posInPoints <<
"pt ->" << dest;
751void QPdfView::mouseReleaseEvent(QMouseEvent *event)
754 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
755 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
756 const int page = it.key();
757 const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
758 const QRect pageGeometry = it.value().first;
759 if (pageGeometry.contains(event->position().toPoint())) {
760 QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()
761 + d->m_viewport.topLeft());
762 d->m_linkModel.setPage(page);
763 auto dest = d->m_linkModel.linkAt(posInPoints);
764 if (dest.isValid()) {
765 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
766 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());