87 if (m_viewport == viewport)
90 const QSize oldSize = m_viewport.size();
92 m_viewport = viewport;
94 if (oldSize != m_viewport.size()) {
97 if (m_zoomMode != QPdfView::ZoomMode::Custom) {
102 if (m_pageMode == QPdfView::PageMode::MultiPage) {
106 const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
109 for (
auto it = m_documentLayout.pageGeometryAndScale.cbegin();
110 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
111 const QRect pageGeometry = it.value().first;
112 if (pageGeometry.intersects(currentPageLine)) {
113 currentPage = it.key();
121 m_pageNavigator->jump(currentPage, {}, m_zoomFactor);
131 const QSize p = q->viewport()->size();
132 const QSize v = m_documentLayout.documentSize;
134 q->horizontalScrollBar()->setRange(0, v.width() - p.width());
135 q->horizontalScrollBar()->setPageStep(p.width());
136 q->verticalScrollBar()->setRange(0, v.height() - p.height());
137 q->verticalScrollBar()->setPageStep(p.height());
147 if (!m_cachedPagesLRU.contains(pageNumber)) {
148 if (m_cachedPagesLRU.size() > m_pageCacheLimit)
149 m_pageCache.remove(m_cachedPagesLRU.takeFirst());
151 m_cachedPagesLRU.append(pageNumber);
154 m_pageCache.insert(pageNumber, image);
156 q->viewport()->update();
183 if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
184 return documentLayout;
186 QHash<
int, QPair<QRect, qreal>> pageGeometryAndScale;
188 const int pageCount = m_document->pageCount();
192 const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
193 const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
196 for (
int page = startPage; page < endPage; ++page) {
198 qreal pageScale = m_zoomFactor;
199 if (m_zoomMode == QPdfView::ZoomMode::Custom) {
200 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
201 }
else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
202 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
203 pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
204 qreal(pageSize.width()));
205 pageSize *= pageScale;
206 }
else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
207 const QSize viewportSize(m_viewport.size() +
208 QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
210 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
211 QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
213 pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
214 pageSize = scaledSize;
217 totalWidth = qMax(totalWidth, pageSize.width());
219 pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
222 totalWidth += m_documentMargins.left() + m_documentMargins.right();
224 int pageY = m_documentMargins.top();
227 for (
int page = startPage; page < endPage; ++page) {
228 const QSize pageSize = pageGeometryAndScale[page].first.size();
231 const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
233 pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
238 pageY += m_documentMargins.bottom();
240 documentLayout.pageGeometryAndScale = pageGeometryAndScale;
243 documentLayout.documentSize = QSize(totalWidth, pageY);
245 return documentLayout;
294QPdfView::QPdfView(QWidget *parent)
295 : QAbstractScrollArea(parent)
296 , d_ptr(
new QPdfViewPrivate(
this))
302 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged,
this,
303 [d](
int page){ d->currentPageChanged(page); });
305 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
this,
306 [d](
int pageNumber, QSize imageSize,
const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
307 d->pageRendered(pageNumber, imageSize, image, requestId); });
309 verticalScrollBar()->setSingleStep(20);
310 horizontalScrollBar()->setSingleStep(20);
312 setMouseTracking(
true);
313 d->calculateViewport();
328void QPdfView::setDocument(QPdfDocument *document)
332 if (d->m_document == document)
336 disconnect(d->m_documentStatusChangedConnection);
338 d->m_document = document;
339 emit documentChanged(d->m_document);
342 d->m_documentStatusChangedConnection =
343 connect(d->m_document.data(), &QPdfDocument::statusChanged,
this,
344 [d](){ d->documentStatusChanged(); });
346 d->m_pageRenderer->setDocument(d->m_document);
347 d->m_linkModel.setDocument(d->m_document);
349 d->documentStatusChanged();
367void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
370 if (d->m_searchModel == searchModel)
373 if (d->m_searchModel)
374 d->m_searchModel->disconnect(
this);
376 d->m_searchModel = searchModel;
377 emit searchModelChanged(searchModel);
380 connect(searchModel, &QPdfSearchModel::dataChanged,
this,
381 [
this](
const QModelIndex &,
const QModelIndex &,
const QList<
int> &) { update(); });
383 setCurrentSearchResultIndex(-1);
582void QPdfView::paintEvent(QPaintEvent *event)
586 QPainter painter(viewport());
587 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
588 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
590 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
591 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
592 const QRect pageGeometry = it.value().first;
593 if (pageGeometry.intersects(d->m_viewport)) {
594 painter.fillRect(pageGeometry, Qt::white);
596 const int page = it.key();
597 const auto pageIt = d->m_pageCache.constFind(page);
598 if (pageIt != d->m_pageCache.cend()) {
599 const QImage &img = pageIt.value();
600 painter.drawImage(pageGeometry, img);
602 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
605 const QTransform scaleTransform = d->screenScaleTransform(page);
607 const QString fmt = u"page %1 @ %2, %3"_s;
608 d->m_linkModel.setPage(page);
609 const int linkCount = d->m_linkModel.rowCount({});
610 for (
int i = 0; i < linkCount; ++i) {
611 const QRectF linkBounds = scaleTransform.mapRect(
612 d->m_linkModel.data(d->m_linkModel.index(i),
613 int(QPdfLinkModel::Role::Rect)).toRectF())
614 .translated(pageGeometry.topLeft());
615 painter.setPen(Qt::blue);
616 painter.drawRect(linkBounds);
617 painter.setPen(Qt::red);
618 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
619 int(QPdfLinkModel::Role::Location)).toPoint();
621 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
622 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
623 int(QPdfLinkModel::Role::Page)).toInt())
624 .arg(loc.x()).arg(loc.y()));
627 if (d->m_searchModel) {
628 for (
const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
629 for (
const QRectF &rect : result.rectangles())
630 painter.fillRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()), SearchResultHighlight);
633 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
634 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
635 if (cur.page() == page) {
636 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
637 for (
const auto &rect : cur.rectangles())
638 painter.drawRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()));
670void QPdfView::mouseMoveEvent(QMouseEvent *event)
673 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
674 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
675 const int page = it.key();
676 const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
677 const QRect pageGeometry = it.value().first;
678 if (pageGeometry.contains(event->position().toPoint())) {
679 QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()
680 + d->m_viewport.topLeft());
681 d->m_linkModel.setPage(page);
682 auto dest = d->m_linkModel.linkAt(posInPoints);
683 setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor);
685 qCDebug(qLcWLink) << event->position() <<
":" << posInPoints <<
"pt ->" << dest;
690void QPdfView::mouseReleaseEvent(QMouseEvent *event)
693 for (
auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
694 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
695 const int page = it.key();
696 const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
697 const QRect pageGeometry = it.value().first;
698 if (pageGeometry.contains(event->position().toPoint())) {
699 QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft()
700 + d->m_viewport.topLeft());
701 d->m_linkModel.setPage(page);
702 auto dest = d->m_linkModel.linkAt(posInPoints);
703 if (dest.isValid()) {
704 qCDebug(qLcWLink) << event <<
": jumping to" << dest;
705 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());