diff options
Diffstat (limited to 'src/pdf/qpdfdocument.cpp')
-rw-r--r-- | src/pdf/qpdfdocument.cpp | 179 |
1 files changed, 129 insertions, 50 deletions
diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp index 26ca90e30..17fdb29b9 100644 --- a/src/pdf/qpdfdocument.cpp +++ b/src/pdf/qpdfdocument.cpp @@ -15,8 +15,11 @@ #include <QLoggingCategory> #include <QMetaEnum> #include <QMutex> +#include <QPixmap> #include <QVector2D> +#include <QtCore/private/qtools_p.h> + QT_BEGIN_NAMESPACE Q_GLOBAL_STATIC(QRecursiveMutex, pdfMutex) @@ -37,9 +40,9 @@ public: { m_roleNames = QAbstractItemModel::roleNames(); QMetaEnum rolesMetaEnum = doc->metaObject()->enumerator(doc->metaObject()->indexOfEnumerator("PageModelRole")); - for (int r = Qt::UserRole; r < int(QPdfDocument::PageModelRole::_Count); ++r) { + for (int r = Qt::UserRole; r < int(QPdfDocument::PageModelRole::NRoles); ++r) { auto name = QByteArray(rolesMetaEnum.valueToKey(r)); - name[0] = tolower(name[0]); + name[0] = QtMiscUtils::toAsciiLower(name[0]); m_roleNames.insert(r, name); } connect(doc, &QPdfDocument::statusChanged, this, [this](QPdfDocument::Status s) { @@ -54,14 +57,23 @@ public: { if (!index.isValid()) return QVariant(); + switch (QPdfDocument::PageModelRole(role)) { case QPdfDocument::PageModelRole::Label: return document()->pageLabel(index.row()); case QPdfDocument::PageModelRole::PointSize: return document()->pagePointSize(index.row()); - case QPdfDocument::PageModelRole::_Count: + case QPdfDocument::PageModelRole::NRoles: break; } + + switch (role) { + case Qt::DecorationRole: + return pageThumbnail(index.row()); + case Qt::DisplayRole: + return document()->pageLabel(index.row()); + } + return QVariant(); } @@ -71,8 +83,24 @@ public: private: QPdfDocument *document() const { return static_cast<QPdfDocument *>(parent()); } + QPixmap pageThumbnail(int page) const + { + auto it = m_thumbnails.constFind(page); + if (it == m_thumbnails.constEnd()) { + auto doc = document(); + auto size = doc->pagePointSize(page); + size.scale(128, 128, Qt::KeepAspectRatio); + // TODO use QPdfPageRenderer for threading? + auto image = document()->render(page, size.toSize()); + QPixmap ret = QPixmap::fromImage(image); + m_thumbnails.insert(page, ret); + return ret; + } + return it.value(); + } QHash<int, QByteArray> m_roleNames; + mutable QHash<int, QPixmap> m_thumbnails; }; QPdfDocumentPrivate::QPdfDocumentPrivate() @@ -418,7 +446,7 @@ void QPdfDocumentPrivate::fpdf_AddSegment(_FX_DOWNLOADHINTS *pThis, size_t offse Q_UNUSED(size); } -QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int count) +QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int count) const { QList<ushort> buf(count + 1); // TODO is that enough space in case one unicode character is more than one in utf-16? @@ -427,23 +455,73 @@ QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int return QString::fromUtf16(reinterpret_cast<const char16_t *>(buf.constData()), len - 1); } -QPointF QPdfDocumentPrivate::getCharPosition(FPDF_TEXTPAGE textPage, double pageHeight, int charIndex) +QPointF QPdfDocumentPrivate::getCharPosition(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage, int charIndex) const { double x, y; - int count = FPDFText_CountChars(textPage); - bool ok = FPDFText_GetCharOrigin(textPage, qMin(count - 1, charIndex), &x, &y); - if (!ok) - return QPointF(); - return QPointF(x, pageHeight - y); + const int count = FPDFText_CountChars(textPage); + if (FPDFText_GetCharOrigin(textPage, qMin(count - 1, charIndex), &x, &y)) + return mapPageToView(pdfPage, x, y); + return {}; } -QRectF QPdfDocumentPrivate::getCharBox(FPDF_TEXTPAGE textPage, double pageHeight, int charIndex) +QRectF QPdfDocumentPrivate::getCharBox(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage, int charIndex) const { double l, t, r, b; - bool ok = FPDFText_GetCharBox(textPage, charIndex, &l, &r, &b, &t); - if (!ok) - return QRectF(); - return QRectF(l, pageHeight - t, r - l, t - b); + if (FPDFText_GetCharBox(textPage, charIndex, &l, &r, &b, &t)) + return mapPageToView(pdfPage, l, t, r, b); + return {}; +} + +/*! \internal + Convert the point \a x , \a y to the usual 1x (pixels = points) + 4th-quadrant "view" coordinate system relative to the top-left corner of + the rendered page. Some PDF files have internal transforms that make this + coordinate system different from "page coordinates", so we cannot just + subtract from page height to invert the y coordinates, in general. + */ +QPointF QPdfDocumentPrivate::mapPageToView(FPDF_PAGE pdfPage, double x, double y) const +{ + const auto pageHeight = FPDF_GetPageHeight(pdfPage); + const auto pageWidth = FPDF_GetPageWidth(pdfPage); + int rx, ry; + if (FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, x, y, &rx, &ry)) + return QPointF(rx, ry); + return {}; +} + +/*! \internal + Convert the bounding box defined by \a left \a top \a right and \a bottom + to the usual 1x (pixels = points) 4th-quadrant "view" coordinate system + that we use for rendering things on top of the page image. + Some PDF files have internal transforms that make this coordinate + system different from "page coordinates", so we cannot just + subtract from page height to invert the y coordinates, in general. + */ +QRectF QPdfDocumentPrivate::mapPageToView(FPDF_PAGE pdfPage, double left, double top, double right, double bottom) const +{ + const auto pageHeight = FPDF_GetPageHeight(pdfPage); + const auto pageWidth = FPDF_GetPageWidth(pdfPage); + int xfmLeft, xfmTop, xfmRight, xfmBottom; + if ( FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, left, top, &xfmLeft, &xfmTop) && + FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, right, bottom, &xfmRight, &xfmBottom) ) + return QRectF(xfmLeft, xfmTop, xfmRight - xfmLeft, xfmBottom - xfmTop); + return {}; +} + +/*! \internal + Convert the point \a x , \a y \a from the usual 1x (pixels = points) + 4th-quadrant "view" coordinate system relative to the top-left corner of + the rendered page, to "page coordinates" suited to the given \a pdfPage, + which may have arbitrary internal transforms. + */ +QPointF QPdfDocumentPrivate::mapViewToPage(FPDF_PAGE pdfPage, QPointF position) const +{ + const auto pageHeight = FPDF_GetPageHeight(pdfPage); + const auto pageWidth = FPDF_GetPageWidth(pdfPage); + double rx, ry; + if (FPDF_DeviceToPage(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, position.x(), position.y(), &rx, &ry)) + return QPointF(rx, ry); + return {}; } QPdfDocumentPrivate::TextPosition QPdfDocumentPrivate::hitTest(int page, QPointF position) @@ -452,14 +530,14 @@ QPdfDocumentPrivate::TextPosition QPdfDocumentPrivate::hitTest(int page, QPointF TextPosition result; FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page); - double pageHeight = FPDF_GetPageHeight(pdfPage); FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); - int hitIndex = FPDFText_GetCharIndexAtPos(textPage, position.x(), pageHeight - position.y(), + const QPointF pagePos = mapViewToPage(pdfPage, position); + int hitIndex = FPDFText_GetCharIndexAtPos(textPage, pagePos.x(), pagePos.y(), CharacterHitTolerance, CharacterHitTolerance); if (hitIndex >= 0) { - QPointF charPos = getCharPosition(textPage, pageHeight, hitIndex); + QPointF charPos = getCharPosition(pdfPage, textPage, hitIndex); if (!charPos.isNull()) { - QRectF charBox = getCharBox(textPage, pageHeight, hitIndex); + QRectF charBox = getCharBox(pdfPage, textPage, hitIndex); // If the given position is past the end of the line, i.e. if the right edge of the found character's // bounding box is closer to it than the left edge is, we say that we "hit" the next character index after if (qAbs(charBox.right() - position.x()) < qAbs(charPos.x() - position.x())) { @@ -639,7 +717,7 @@ QVariant QPdfDocument::metaData(MetaDataField field) const const unsigned long len = FPDF_GetMetaText(d->doc, fieldName.constData(), nullptr, 0); QList<ushort> buf(len); - FPDF_GetMetaText(d->doc, fieldName.constData(), buf.data(), buf.length()); + FPDF_GetMetaText(d->doc, fieldName.constData(), buf.data(), buf.size()); lock.unlock(); QString text = QString::fromUtf16(reinterpret_cast<const char16_t *>(buf.data())); @@ -750,7 +828,7 @@ QSizeF QPdfDocument::pagePointSize(int page) const \value Label The page number to be used for display purposes (QString). \value PointSize The page size in points (1/72 of an inch) (QSizeF). - \omitvalue _Count + \omitvalue NRoles */ /*! @@ -781,6 +859,8 @@ QAbstractListModel *QPdfDocument::pageModel() If the document does not have custom page numbering, this function returns \c {page + 1}. + + \sa pageIndexForLabel() */ QString QPdfDocument::pageLabel(int page) { @@ -795,6 +875,21 @@ QString QPdfDocument::pageLabel(int page) } /*! + Returns the index of the page that has the \a label, or \c -1 if not found. + + \sa pageLabel() + \since 6.6 +*/ +int QPdfDocument::pageIndexForLabel(const QString &label) +{ + for (int i = 0; i < d->pageCount; ++i) { + if (pageLabel(i) == label) + return i; + } + return -1; +} + +/*! Renders the \a page into a QImage of size \a imageSize according to the provided \a renderOptions. @@ -822,22 +917,6 @@ QImage QPdfDocument::render(int page, QSize imageSize, QPdfDocumentRenderOptions result.fill(Qt::transparent); FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(result.width(), result.height(), FPDFBitmap_BGRA, result.bits(), result.bytesPerLine()); - int rotation = 0; - switch (renderOptions.rotation()) { - case QPdfDocumentRenderOptions::Rotation::Rotate0: - rotation = 0; - break; - case QPdfDocumentRenderOptions::Rotation::Rotate90: - rotation = 1; - break; - case QPdfDocumentRenderOptions::Rotation::Rotate180: - rotation = 2; - break; - case QPdfDocumentRenderOptions::Rotation::Rotate270: - rotation = 3; - break; - } - const QPdfDocumentRenderOptions::RenderFlags renderFlags = renderOptions.renderFlags(); int flags = 0; if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::Annotations) @@ -883,6 +962,7 @@ QImage QPdfDocument::render(int page, QSize imageSize, QPdfDocumentRenderOptions qCDebug(qLcDoc) << "page" << page << "region" << renderOptions.scaledClipRect() << "size" << imageSize << "took" << timer.elapsed() << "ms"; } else { + const auto rotation = QPdfDocumentPrivate::toFPDFRotation(renderOptions.rotation()); FPDF_RenderPageBitmap(bitmap, pdfPage, 0, 0, result.width(), result.height(), rotation, flags); qCDebug(qLcDoc) << "page" << page << "size" << imageSize << "took" << timer.elapsed() << "ms"; } @@ -901,11 +981,12 @@ QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) { const QPdfMutexLocker lock; FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); - double pageHeight = FPDF_GetPageHeight(pdfPage); + const QPointF pageStart = d->mapViewToPage(pdfPage, start); + const QPointF pageEnd = d->mapViewToPage(pdfPage, end); FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); - int startIndex = FPDFText_GetCharIndexAtPos(textPage, start.x(), pageHeight - start.y(), + int startIndex = FPDFText_GetCharIndexAtPos(textPage, pageStart.x(), pageStart.y(), CharacterHitTolerance, CharacterHitTolerance); - int endIndex = FPDFText_GetCharIndexAtPos(textPage, end.x(), pageHeight - end.y(), + int endIndex = FPDFText_GetCharIndexAtPos(textPage, pageEnd.x(), pageEnd.y(), CharacterHitTolerance, CharacterHitTolerance); QPdfSelection result; @@ -916,7 +997,7 @@ QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) // If the given end position is past the end of the line, i.e. if the right edge of the last character's // bounding box is closer to it than the left edge is, then extend the char range by one - QRectF endCharBox = d->getCharBox(textPage, pageHeight, endIndex); + QRectF endCharBox = d->getCharBox(pdfPage, textPage, endIndex); if (qAbs(endCharBox.right() - end.x()) < qAbs(endCharBox.x() - end.x())) ++endIndex; @@ -928,7 +1009,7 @@ QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) for (int i = 0; i < rectCount; ++i) { double l, r, b, t; FPDFText_GetRect(textPage, i, &l, &t, &r, &b); - QRectF rect(l, pageHeight - t, r - l, t - b); + const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b); if (hull.isNull()) hull = rect; else @@ -958,7 +1039,6 @@ QPdfSelection QPdfDocument::getSelectionAtIndex(int page, int startIndex, int ma return {}; const QPdfMutexLocker lock; FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); - double pageHeight = FPDF_GetPageHeight(pdfPage); FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); int pageCount = FPDFText_CountChars(textPage); if (startIndex >= pageCount) @@ -969,11 +1049,11 @@ QPdfSelection QPdfDocument::getSelectionAtIndex(int page, int startIndex, int ma QString text; if (maxLength > 0) { text = d->getText(textPage, startIndex, maxLength); - rectCount = FPDFText_CountRects(textPage, startIndex, text.length()); + rectCount = FPDFText_CountRects(textPage, startIndex, text.size()); for (int i = 0; i < rectCount; ++i) { double l, r, b, t; FPDFText_GetRect(textPage, i, &l, &t, &r, &b); - QRectF rect(l, pageHeight - t, r - l, t - b); + const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b); if (hull.isNull()) hull = rect; else @@ -982,14 +1062,14 @@ QPdfSelection QPdfDocument::getSelectionAtIndex(int page, int startIndex, int ma } } if (bounds.isEmpty()) - hull = QRectF(d->getCharPosition(textPage, pageHeight, startIndex), QSizeF()); + hull = QRectF(d->getCharPosition(pdfPage, textPage, startIndex), QSizeF()); qCDebug(qLcDoc) << "on page" << page << "at index" << startIndex << "maxLength" << maxLength - << "got" << text.length() << "chars," << rectCount << "rects within" << hull; + << "got" << text.size() << "chars," << rectCount << "rects within" << hull; FPDFText_ClosePage(textPage); FPDF_ClosePage(pdfPage); - return QPdfSelection(text, bounds, hull, startIndex, startIndex + text.length()); + return QPdfSelection(text, bounds, hull, startIndex, startIndex + text.size()); } /*! @@ -999,7 +1079,6 @@ QPdfSelection QPdfDocument::getAllText(int page) { const QPdfMutexLocker lock; FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); - double pageHeight = FPDF_GetPageHeight(pdfPage); FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); int count = FPDFText_CountChars(textPage); if (count < 1) @@ -1011,7 +1090,7 @@ QPdfSelection QPdfDocument::getAllText(int page) for (int i = 0; i < rectCount; ++i) { double l, r, b, t; FPDFText_GetRect(textPage, i, &l, &t, &r, &b); - QRectF rect(l, pageHeight - t, r - l, t - b); + const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b); if (hull.isNull()) hull = rect; else |