/**************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtPDF module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qpdflinkmodel_p.h" #include "qpdflinkmodel_p_p.h" #include "qpdfdocument_p.h" #include "third_party/pdfium/public/fpdf_doc.h" #include "third_party/pdfium/public/fpdf_text.h" #include #include QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcLink, "qt.pdf.links") QPdfLinkModel::QPdfLinkModel(QObject *parent) : QAbstractListModel(*(new QPdfLinkModelPrivate()), parent) { QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role")); for (int r = Qt::UserRole; r < int(Role::_Count); ++r) m_roleNames.insert(r, QByteArray(rolesMetaEnum.valueToKey(r)).toLower()); } QPdfLinkModel::~QPdfLinkModel() {} QHash QPdfLinkModel::roleNames() const { return m_roleNames; } int QPdfLinkModel::rowCount(const QModelIndex &parent) const { Q_D(const QPdfLinkModel); Q_UNUSED(parent) return d->links.count(); } QVariant QPdfLinkModel::data(const QModelIndex &index, int role) const { Q_D(const QPdfLinkModel); const QPdfLinkModelPrivate::Link &link = d->links.at(index.row()); switch (Role(role)) { case Role::Rect: return link.rect; case Role::Url: return link.url; case Role::Page: return link.page; case Role::Location: return link.location; case Role::Zoom: return link.zoom; case Role::_Count: break; } if (role == Qt::DisplayRole) return link.toString(); return QVariant(); } QPdfDocument *QPdfLinkModel::document() const { Q_D(const QPdfLinkModel); return d->document; } void QPdfLinkModel::setDocument(QPdfDocument *document) { Q_D(QPdfLinkModel); if (d->document == document) return; disconnect(d->document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged); connect(document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged); d->document = document; emit documentChanged(); if (page()) setPage(0); else d->update(); } int QPdfLinkModel::page() const { Q_D(const QPdfLinkModel); return d->page; } void QPdfLinkModel::setPage(int page) { Q_D(QPdfLinkModel); if (d->page == page) return; d->page = page; emit pageChanged(page); d->update(); } QPdfLinkModelPrivate::QPdfLinkModelPrivate() : QAbstractItemModelPrivate() { } void QPdfLinkModelPrivate::update() { Q_Q(QPdfLinkModel); if (!document || !document->d->doc) return; auto doc = document->d->doc; const QPdfMutexLocker lock; FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page); if (!pdfPage) { qWarning() << "failed to load page" << page; return; } double pageHeight = FPDF_GetPageHeight(pdfPage); q->beginResetModel(); links.clear(); // Iterate the ordinary links int linkStart = 0; bool ok = true; while (ok) { FPDF_LINK linkAnnot; ok = FPDFLink_Enumerate(pdfPage, &linkStart, &linkAnnot); if (!ok) break; FS_RECTF rect; ok = FPDFLink_GetAnnotRect(linkAnnot, &rect); if (!ok) break; Link linkData; linkData.rect = QRectF(rect.left, pageHeight - rect.top, rect.right - rect.left, rect.top - rect.bottom); FPDF_DEST dest = FPDFLink_GetDest(doc, linkAnnot); FPDF_ACTION action = FPDFLink_GetAction(linkAnnot); if (FPDFAction_GetType(action) != PDFACTION_GOTO) { qWarning() << "link action type" << FPDFAction_GetType(action) << "is not yet supported"; continue; } linkData.page = FPDFDest_GetDestPageIndex(doc, dest); FPDF_BOOL hasX, hasY, hasZoom; FS_FLOAT x, y, zoom; ok = FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom); if (!ok) break; if (hasX && hasY) linkData.location = QPointF(x, pageHeight - y); if (hasZoom) linkData.zoom = zoom; links << linkData; } // Iterate the web links FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); if (textPage) { FPDF_PAGELINK webLinks = FPDFLink_LoadWebLinks(textPage); if (webLinks) { int count = FPDFLink_CountWebLinks(webLinks); for (int i = 0; i < count; ++i) { Link linkData; int len = FPDFLink_GetURL(webLinks, i, nullptr, 0); if (len < 1) { qWarning() << "URL" << i << "has length" << len; } else { QVector buf(len); int got = FPDFLink_GetURL(webLinks, i, buf.data(), len); Q_ASSERT(got == len); linkData.url = QString::fromUtf16(buf.data(), got - 1); } FPDFLink_GetTextRange(webLinks, i, &linkData.textStart, &linkData.textCharCount); len = FPDFLink_CountRects(webLinks, i); for (int r = 0; r < len; ++r) { double left, top, right, bottom; bool success = FPDFLink_GetRect(webLinks, i, r, &left, &top, &right, &bottom); if (success) { linkData.rect = QRectF(left, pageHeight - top, right - left, top - bottom); links << linkData; } } } FPDFLink_CloseWebLinks(webLinks); } FPDFText_ClosePage(textPage); } // All done FPDF_ClosePage(pdfPage); if (Q_UNLIKELY(qLcLink().isDebugEnabled())) { for (const Link &l : links) qCDebug(qLcLink) << l.rect << l.toString(); } q->endResetModel(); } void QPdfLinkModel::onStatusChanged(QPdfDocument::Status status) { Q_D(QPdfLinkModel); qCDebug(qLcLink) << "sees document statusChanged" << status; if (status == QPdfDocument::Ready) d->update(); } QString QPdfLinkModelPrivate::Link::toString() const { QString ret; if (page >= 0) return QLatin1String("page ") + QString::number(page) + QLatin1String(" location ") + QString::number(location.x()) + QLatin1Char(',') + QString::number(location.y()) + QLatin1String(" zoom ") + QString::number(zoom); else return url.toString(); } QT_END_NAMESPACE #include "moc_qpdflinkmodel_p.cpp"