Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
47 : QAbstractListModel(parent),
48 d_ptr{std::make_unique<QPdfLinkModelPrivate>(this)}
49{
50 Q_D(QPdfLinkModel);
51 QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role"));
52 for (int r = Qt::UserRole; r < int(Role::NRoles); ++r)
53 d->roleNames.insert(r, QByteArray(rolesMetaEnum.valueToKey(r)).toLower());
54}
55
60
61QHash<int, QByteArray> QPdfLinkModel::roleNames() const
62{
63 Q_D(const QPdfLinkModel);
64 return d->roleNames;
65}
66
70int QPdfLinkModel::rowCount(const QModelIndex &parent) const
71{
72 Q_D(const QPdfLinkModel);
74 return d->links.size();
75}
76
81{
82 Q_D(const QPdfLinkModel);
83 const auto &link = d->links.at(index.row());
84 switch (Role(role)) {
85 case Role::Link:
86 return QVariant::fromValue(link);
87 case Role::Rectangle:
88 return link.rectangles().empty() ? QVariant() : link.rectangles().constFirst();
89 case Role::Url:
90 return link.url();
91 case Role::Page:
92 return link.page();
93 case Role::Location:
94 return link.location();
95 case Role::Zoom:
96 return link.zoom();
97 case Role::NRoles:
98 break;
99 }
100 if (role == Qt::DisplayRole)
101 return link.toString();
102 return QVariant();
103}
104
110{
111 Q_D(const QPdfLinkModel);
112 return d->document;
113}
114
116{
117 Q_D(QPdfLinkModel);
118 if (d->document == document)
119 return;
120 if (d->document)
121 disconnect(d->document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged);
122 connect(document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged);
123 d->document = document;
125 if (page())
126 setPage(0);
127 else
128 d->update();
129}
130
136{
137 Q_D(const QPdfLinkModel);
138 return d->page;
139}
140
142{
143 Q_D(QPdfLinkModel);
144 if (d->page == page)
145 return;
146
147 d->page = page;
149 d->update();
150}
151
159{
160 Q_D(const QPdfLinkModel);
161 for (const auto &link : std::as_const(d->links)) {
162 for (const auto &rect : link.rectangles()) {
163 if (rect.contains(point))
164 return link;
165 }
166 }
167 return {};
168}
169
171{
172 Q_Q(QPdfLinkModel);
173 if (!document || !document->d->doc)
174 return;
175 auto doc = document->d->doc;
176 const QPdfMutexLocker lock;
177 FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page);
178 if (!pdfPage) {
179 qCWarning(qLcLink) << "failed to load page" << page;
180 return;
181 }
182 q->beginResetModel();
183 links.clear();
184
185 // Iterate the ordinary links
186 int linkStart = 0;
187 bool hasNext = true;
188 while (hasNext) {
189 FPDF_LINK linkAnnot;
190 hasNext = FPDFLink_Enumerate(pdfPage, &linkStart, &linkAnnot);
191 if (!hasNext)
192 break;
193 FS_RECTF rect;
194 bool ok = FPDFLink_GetAnnotRect(linkAnnot, &rect);
195 if (!ok) {
196 qCWarning(qLcLink) << "skipping link with invalid bounding box";
197 continue; // while enumerating links
198 }
199 // In case horizontal/vertical coordinates are flipped, swap them.
200 if (rect.right < rect.left)
201 std::swap(rect.right, rect.left);
202 if (rect.bottom > rect.top)
203 std::swap(rect.bottom, rect.top);
204
205 QPdfLink linkData;
206 // Use quad points if present; otherwise use the rect.
207 if (int quadPointsCount = FPDFLink_CountQuadPoints(linkAnnot) > 0) {
208 for (int i = 0; i < quadPointsCount; ++i) {
209 FS_QUADPOINTSF point;
210 if (FPDFLink_GetQuadPoints(linkAnnot, i, &point)) {
211 // Quadpoints are counter clockwise from bottom left (x1, y1)
212 QPolygonF poly;
213 poly << QPointF(point.x1, point.y1);
214 poly << QPointF(point.x2, point.y2);
215 poly << QPointF(point.x3, point.y3);
216 poly << QPointF(point.x4, point.y4);
217 QRectF bounds = poly.boundingRect();
218 bounds = document->d->mapPageToView(pdfPage, bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
219 qCDebug(qLcLink) << "quadpoints" << i << "of" << quadPointsCount << ":" << poly << "mapped bounds" << bounds;
220 linkData.d->rects << bounds;
221 // QPdfLink could store polygons rather than rects, to get the benefit of quadpoints;
222 // so far we didn't bother. It would be an API change, and we'd need to use Shapes in PdfLinkDelegate.qml
223 }
224 }
225 } else {
226 linkData.d->rects << document->d->mapPageToView(pdfPage, rect.left, rect.top, rect.right, rect.bottom);
227 }
228 FPDF_DEST dest = FPDFLink_GetDest(doc, linkAnnot);
229 FPDF_ACTION action = FPDFLink_GetAction(linkAnnot);
230 switch (FPDFAction_GetType(action)) {
231 case PDFACTION_UNSUPPORTED: // this happens with valid links in some PDFs
232 case PDFACTION_GOTO: {
233 linkData.d->page = FPDFDest_GetDestPageIndex(doc, dest);
234 if (linkData.d->page < 0) {
235 qCWarning(qLcLink) << "skipping link with invalid page number" << linkData.d->page;
236 continue; // while enumerating links
237 }
238 FPDF_BOOL hasX, hasY, hasZoom;
239 FS_FLOAT x, y, zoom;
240 ok = FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom);
241 if (!ok) {
242 qCWarning(qLcLink) << "link with invalid location and/or zoom @" << linkData.d->rects;
243 break; // at least we got a page number, so the link will jump there
244 }
245 if (hasX && hasY)
246 linkData.d->location = document->d->mapPageToView(pdfPage, x, y);
247 if (hasZoom)
248 linkData.d->zoom = zoom;
249 break;
250 }
251 case PDFACTION_URI: {
252 unsigned long len = FPDFAction_GetURIPath(doc, action, nullptr, 0);
253 if (len < 1) {
254 qCWarning(qLcLink) << "skipping link with empty URI @" << linkData.d->rects;
255 continue; // while enumerating links
256 } else {
257 QByteArray buf(len, 0);
258 unsigned long got = FPDFAction_GetURIPath(doc, action, buf.data(), len);
259 Q_ASSERT(got == len);
260 linkData.d->url = QString::fromLatin1(buf.data(), got - 1);
261 }
262 break;
263 }
264 case PDFACTION_LAUNCH:
265 case PDFACTION_REMOTEGOTO: {
266 unsigned long len = FPDFAction_GetFilePath(action, nullptr, 0);
267 if (len < 1) {
268 qCWarning(qLcLink) << "skipping link with empty file path @" << linkData.d->rects;
269 continue; // while enumerating links
270 } else {
271 QByteArray buf(len, 0);
272 unsigned long got = FPDFAction_GetFilePath(action, buf.data(), len);
273 Q_ASSERT(got == len);
274 linkData.d->url = QUrl::fromLocalFile(QString::fromLatin1(buf.data(), got - 1)).toString();
275
276 // Unfortunately, according to comments in fpdf_doc.h, if it's PDFACTION_REMOTEGOTO,
277 // we can't get the page and location without first opening the linked document
278 // and then calling FPDFAction_GetDest() again.
279 }
280 break;
281 }
282 }
283 links << linkData;
284 }
285
286 // Iterate the web links
287 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
288 if (textPage) {
289 FPDF_PAGELINK webLinks = FPDFLink_LoadWebLinks(textPage);
290 if (webLinks) {
291 int count = FPDFLink_CountWebLinks(webLinks);
292 for (int i = 0; i < count; ++i) {
293 QPdfLink linkData;
294 int len = FPDFLink_GetURL(webLinks, i, nullptr, 0);
295 if (len < 1) {
296 qCWarning(qLcLink) << "skipping link" << i << "with empty URL";
297 } else {
298 QList<unsigned short> buf(len);
299 int got = FPDFLink_GetURL(webLinks, i, buf.data(), len);
300 Q_ASSERT(got == len);
301 linkData.d->url = QString::fromUtf16(
302 reinterpret_cast<const char16_t *>(buf.data()), got - 1);
303 }
304 len = FPDFLink_CountRects(webLinks, i);
305 for (int r = 0; r < len; ++r) {
306 double left, top, right, bottom;
307 bool success = FPDFLink_GetRect(webLinks, i, r, &left, &top, &right, &bottom);
308 if (success) {
309 linkData.d->rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
310 links << linkData;
311 }
312 }
313 }
314 FPDFLink_CloseWebLinks(webLinks);
315 }
316 FPDFText_ClosePage(textPage);
317 }
318
319 // All done
320 FPDF_ClosePage(pdfPage);
321 if (Q_UNLIKELY(qLcLink().isDebugEnabled())) {
322 for (const auto &l : links)
323 qCDebug(qLcLink) << l;
324 }
325 q->endResetModel();
326}
327
328void QPdfLinkModel::onStatusChanged(QPdfDocument::Status status)
329{
330 Q_D(QPdfLinkModel);
331 qCDebug(qLcLink) << "sees document statusChanged" << status;
332 if (status == QPdfDocument::Status::Ready)
333 d->update();
334}
335
337
338#include "moc_qpdflinkmodel_p.cpp"
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
\inmodule QtCore
Definition qbytearray.h:57
QByteArray toLower() const &
Definition qbytearray.h:254
void clear()
Definition qlist.h:434
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QPointF mapPageToView(FPDF_PAGE pdfPage, double x, double y) const
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.
~QPdfLinkModel() override
Destroys the model.
QVariant data(const QModelIndex &index, int role) const override
\reimp
QPdfDocument * document
The document to load links from.
QPdfLink linkAt(QPointF point) const
Returns a \l {QPdfLink::isValid()}{valid} link if found under the point (given in units of points,...
Role
\value Link A QPdfLink object.
void setDocument(QPdfDocument *document)
void documentChanged()
int rowCount(const QModelIndex &parent) const override
\reimp
\inmodule QtCore\reentrant
Definition qpoint.h:217
The QPolygonF class provides a list of points using floating point precision.
Definition qpolygon.h:96
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:500
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:497
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:498
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:499
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition qstring.cpp:6045
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
Definition qurl.cpp:3368
\inmodule QtCore
Definition qvariant.h:65
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:536
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
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLenum GLsizei len
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static bool hasNext(const Symbols &symbols, int i)
Definition main.cpp:78
#define emit
#define Q_UNUSED(x)
obj metaObject() -> className()
myObject disconnect()
[26]
QByteArray page
[45]
QReadWriteLock lock
[0]