Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qpdflinkmodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpdflink_p.h"
5#include "qpdflinkmodel.h"
6#include "qpdflinkmodel_p.h"
7#include "qpdfdocument_p.h"
8
9#include "third_party/pdfium/public/fpdf_doc.h"
10#include "third_party/pdfium/public/fpdf_text.h"
11
12#include <QLoggingCategory>
13#include <QMetaEnum>
14
16
17Q_LOGGING_CATEGORY(qLcLink, "qt.pdf.links")
18
19
48{
49 Q_D(QPdfLinkModel);
50 QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role"));
51 for (int r = Qt::UserRole; r < int(Role::NRoles); ++r)
52 d->roleNames.insert(r, QByteArray(rolesMetaEnum.valueToKey(r)).toLower());
53}
54
59
61{
62 Q_D(const QPdfLinkModel);
63 return d->roleNames;
64}
65
70{
71 Q_D(const QPdfLinkModel);
73 return d->links.size();
74}
75
80{
81 Q_D(const QPdfLinkModel);
82 const auto &link = d->links.at(index.row());
83 switch (Role(role)) {
84 case Role::Link:
85 return QVariant::fromValue(link);
86 case Role::Rectangle:
87 return link.rectangles().empty() ? QVariant() : link.rectangles().constFirst();
88 case Role::Url:
89 return link.url();
90 case Role::Page:
91 return link.page();
92 case Role::Location:
93 return link.location();
94 case Role::Zoom:
95 return link.zoom();
96 case Role::NRoles:
97 break;
98 }
99 if (role == Qt::DisplayRole)
100 return link.toString();
101 return QVariant();
102}
103
109{
110 Q_D(const QPdfLinkModel);
111 return d->document;
112}
113
115{
116 Q_D(QPdfLinkModel);
117 if (d->document == document)
118 return;
119 if (d->document)
120 disconnect(d->document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged);
121 connect(document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged);
122 d->document = document;
124 if (page())
125 setPage(0);
126 else
127 d->update();
128}
129
135{
136 Q_D(const QPdfLinkModel);
137 return d->page;
138}
139
141{
142 Q_D(QPdfLinkModel);
143 if (d->page == page)
144 return;
145
146 d->page = page;
148 d->update();
149}
150
158{
159 Q_D(const QPdfLinkModel);
160 for (const auto &link : std::as_const(d->links)) {
161 for (const auto &rect : link.rectangles()) {
162 if (rect.contains(point))
163 return link;
164 }
165 }
166 return {};
167}
168
170{
171}
172
174{
175 Q_Q(QPdfLinkModel);
176 if (!document || !document->d->doc)
177 return;
178 auto doc = document->d->doc;
179 const QPdfMutexLocker lock;
180 FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page);
181 if (!pdfPage) {
182 qCWarning(qLcLink) << "failed to load page" << page;
183 return;
184 }
185 double pageHeight = FPDF_GetPageHeight(pdfPage);
186 q->beginResetModel();
187 links.clear();
188
189 // Iterate the ordinary links
190 int linkStart = 0;
191 bool hasNext = true;
192 while (hasNext) {
193 FPDF_LINK linkAnnot;
194 hasNext = FPDFLink_Enumerate(pdfPage, &linkStart, &linkAnnot);
195 if (!hasNext)
196 break;
197 FS_RECTF rect;
198 bool ok = FPDFLink_GetAnnotRect(linkAnnot, &rect);
199 if (!ok) {
200 qCWarning(qLcLink) << "skipping link with invalid bounding box";
201 continue; // while enumerating links
202 }
203 // In case horizontal/vertical coordinates are flipped, swap them.
204 if (rect.right < rect.left)
205 std::swap(rect.right, rect.left);
206 if (rect.bottom > rect.top)
207 std::swap(rect.bottom, rect.top);
208
209 QPdfLink linkData;
210 linkData.d->rects << QRectF(rect.left, pageHeight - rect.top,
211 rect.right - rect.left, rect.top - rect.bottom);
212 FPDF_DEST dest = FPDFLink_GetDest(doc, linkAnnot);
213 FPDF_ACTION action = FPDFLink_GetAction(linkAnnot);
214 switch (FPDFAction_GetType(action)) {
215 case PDFACTION_UNSUPPORTED: // this happens with valid links in some PDFs
216 case PDFACTION_GOTO: {
217 linkData.d->page = FPDFDest_GetDestPageIndex(doc, dest);
218 if (linkData.d->page < 0) {
219 qCWarning(qLcLink) << "skipping link with invalid page number";
220 continue; // while enumerating links
221 }
222 FPDF_BOOL hasX, hasY, hasZoom;
223 FS_FLOAT x, y, zoom;
224 ok = FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom);
225 if (!ok) {
226 qCWarning(qLcLink) << "link with invalid location and/or zoom @" << linkData.d->rects;
227 break; // at least we got a page number, so the link will jump there
228 }
229 if (hasX && hasY)
230 linkData.d->location = QPointF(x, pageHeight - y);
231 if (hasZoom)
232 linkData.d->zoom = zoom;
233 break;
234 }
235 case PDFACTION_URI: {
236 unsigned long len = FPDFAction_GetURIPath(doc, action, nullptr, 0);
237 if (len < 1) {
238 qCWarning(qLcLink) << "skipping link with empty URI @" << linkData.d->rects;
239 continue; // while enumerating links
240 } else {
241 QByteArray buf(len, 0);
242 unsigned long got = FPDFAction_GetURIPath(doc, action, buf.data(), len);
243 Q_ASSERT(got == len);
244 linkData.d->url = QString::fromLatin1(buf.data(), got - 1);
245 }
246 break;
247 }
248 case PDFACTION_LAUNCH:
249 case PDFACTION_REMOTEGOTO: {
250 unsigned long len = FPDFAction_GetFilePath(action, nullptr, 0);
251 if (len < 1) {
252 qCWarning(qLcLink) << "skipping link with empty file path @" << linkData.d->rects;
253 continue; // while enumerating links
254 } else {
255 QByteArray buf(len, 0);
256 unsigned long got = FPDFAction_GetFilePath(action, buf.data(), len);
257 Q_ASSERT(got == len);
258 linkData.d->url = QUrl::fromLocalFile(QString::fromLatin1(buf.data(), got - 1)).toString();
259
260 // Unfortunately, according to comments in fpdf_doc.h, if it's PDFACTION_REMOTEGOTO,
261 // we can't get the page and location without first opening the linked document
262 // and then calling FPDFAction_GetDest() again.
263 }
264 break;
265 }
266 }
267 links << linkData;
268 }
269
270 // Iterate the web links
271 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
272 if (textPage) {
273 FPDF_PAGELINK webLinks = FPDFLink_LoadWebLinks(textPage);
274 if (webLinks) {
275 int count = FPDFLink_CountWebLinks(webLinks);
276 for (int i = 0; i < count; ++i) {
277 QPdfLink linkData;
278 int len = FPDFLink_GetURL(webLinks, i, nullptr, 0);
279 if (len < 1) {
280 qCWarning(qLcLink) << "skipping link" << i << "with empty URL";
281 } else {
283 int got = FPDFLink_GetURL(webLinks, i, buf.data(), len);
284 Q_ASSERT(got == len);
285 linkData.d->url = QString::fromUtf16(
286 reinterpret_cast<const char16_t *>(buf.data()), got - 1);
287 }
288 len = FPDFLink_CountRects(webLinks, i);
289 for (int r = 0; r < len; ++r) {
290 double left, top, right, bottom;
291 bool success = FPDFLink_GetRect(webLinks, i, r, &left, &top, &right, &bottom);
292 if (success) {
293 linkData.d->rects << QRectF(left, pageHeight - top, right - left, top - bottom);
294 links << linkData;
295 }
296 }
297 }
298 FPDFLink_CloseWebLinks(webLinks);
299 }
300 FPDFText_ClosePage(textPage);
301 }
302
303 // All done
304 FPDF_ClosePage(pdfPage);
305 if (Q_UNLIKELY(qLcLink().isDebugEnabled())) {
306 for (const auto &l : links)
307 qCDebug(qLcLink) << l;
308 }
309 q->endResetModel();
310}
311
312void QPdfLinkModel::onStatusChanged(QPdfDocument::Status status)
313{
314 Q_D(QPdfLinkModel);
315 qCDebug(qLcLink) << "sees document statusChanged" << status;
316 if (status == QPdfDocument::Status::Ready)
317 d->update();
318}
319
321
322#include "moc_qpdflinkmodel_p.cpp"
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
\inmodule QtCore
Definition qbytearray.h:57
QByteArray toLower() const &
Definition qbytearray.h:190
\inmodule QtCore
Definition qhash.h:818
Definition qlist.h:74
void clear()
Definition qlist.h:417
\inmodule QtCore
const char * valueToKey(int value) const
Returns the string that is used as the name of the given enumeration value, or \nullptr if value is n...
\inmodule QtCore
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
The QPdfDocument class loads a PDF document and renders pages from it.
Status
This enum describes the current status of the document.
void statusChanged(QPdfDocument::Status status)
QList< QPdfLink > links
QPdfDocument * document
The QPdfLinkModel class holds the geometry and the destination for each link that the specified \l pa...
QHash< int, QByteArray > roleNames() const override
void setPage(int page)
void pageChanged(int page)
int page
The page to load links from.
QVariant data(const QModelIndex &index, int role) const override
\reimp
QPdfDocument * document
The document to load links from.
Role
\value Link A QPdfLink object.
QPdfLink linkAt(QPointF point) const
Returns a \l {QPdfLink::isValid()}{valid} link if found under the point (given in units of points,...
void setDocument(QPdfDocument *document)
void documentChanged()
int rowCount(const QModelIndex &parent) const override
\reimp
~QPdfLinkModel()
Destroys the model.
QList< QRectF > rects
Definition qpdflink_p.h:48
QPointF location
Definition qpdflink_p.h:43
\inmodule QtCore\reentrant
Definition qpoint.h:214
\inmodule QtCore\reentrant
Definition qrect.h:483
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition qstring.cpp:5883
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
Definition qurl.cpp:3354
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2828
\inmodule QtCore
Definition qvariant.h:64
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:531
rect
[4]
Combined button and popup list for selecting options.
@ UserRole
@ DisplayRole
#define Q_UNLIKELY(x)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLint GLint GLint GLint GLint x
[0]
GLuint index
[2]
GLboolean r
[2]
GLdouble GLdouble GLdouble GLdouble top
GLenum GLenum GLsizei count
GLdouble GLdouble right
GLint left
GLint GLint bottom
GLenum GLuint GLenum GLsizei const GLchar * buf
GLint y
GLenum GLsizei len
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static bool hasNext(const Symbols &symbols, int i)
Definition main.cpp:66
#define emit
#define Q_UNUSED(x)
obj metaObject() -> className()
myObject disconnect()
[26]
QByteArray page
[45]
QReadWriteLock lock
[0]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent