1
2#ifndef _WIN32
3# include <unistd.h>
4#else
5# include <windows.h>
6# define sleep Sleep
7#endif
8#include <ctime>
9
10#include <poppler-qt6.h>
11#include <poppler-form.h>
12
13#include <QDebug>
14#include <QFile>
15#include <QImage>
16#include <QMutex>
17#include <QRandomGenerator>
18#include <QThread>
19
20class SillyThread : public QThread
21{
22 Q_OBJECT
23public:
24 explicit SillyThread(Poppler::Document *document, QObject *parent = nullptr);
25
26 void run() override;
27
28private:
29 Poppler::Document *m_document;
30 std::vector<std::unique_ptr<Poppler::Page>> m_pages;
31};
32
33class CrazyThread : public QThread
34{
35 Q_OBJECT
36public:
37 CrazyThread(Poppler::Document *document, QMutex *annotationMutex, QObject *parent = nullptr);
38
39 void run() override;
40
41private:
42 Poppler::Document *m_document;
43 QMutex *m_annotationMutex;
44};
45
46static std::unique_ptr<Poppler::Page> loadPage(Poppler::Document *document, int index)
47{
48 std::unique_ptr<Poppler::Page> page = document->page(index);
49
50 if (page == nullptr) {
51 qDebug() << "!Document::page";
52
53 exit(EXIT_FAILURE);
54 }
55
56 return page;
57}
58
59static std::unique_ptr<Poppler::Page> loadRandomPage(Poppler::Document *document)
60{
61 return loadPage(document, index: QRandomGenerator::global()->bounded(highest: document->numPages()));
62}
63
64SillyThread::SillyThread(Poppler::Document *document, QObject *parent) : QThread(parent), m_document(document), m_pages()
65{
66 m_pages.reserve(n: m_document->numPages());
67
68 for (int index = 0; index < m_document->numPages(); ++index) {
69 m_pages.push_back(x: loadPage(document: m_document, index));
70 }
71}
72
73void SillyThread::run()
74{
75 forever {
76 for (std::unique_ptr<Poppler::Page> &page : m_pages) {
77 QImage image = page->renderToImage();
78
79 if (image.isNull()) {
80 qDebug() << "!Page::renderToImage";
81
82 ::exit(EXIT_FAILURE);
83 }
84 }
85 }
86}
87
88CrazyThread::CrazyThread(Poppler::Document *document, QMutex *annotationMutex, QObject *parent) : QThread(parent), m_document(document), m_annotationMutex(annotationMutex) { }
89
90void CrazyThread::run()
91{
92 typedef std::unique_ptr<Poppler::Page> PagePointer;
93
94 forever {
95 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
96 qDebug() << "search...";
97
98 PagePointer page(loadRandomPage(document: m_document));
99
100 page->search(QStringLiteral("c"), flags: Poppler::Page::IgnoreCase);
101 page->search(QStringLiteral("r"));
102 page->search(QStringLiteral("a"), flags: Poppler::Page::IgnoreCase);
103 page->search(QStringLiteral("z"));
104 page->search(QStringLiteral("y"), flags: Poppler::Page::IgnoreCase);
105 }
106
107 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
108 qDebug() << "links...";
109
110 PagePointer page(loadRandomPage(document: m_document));
111
112 std::vector<std::unique_ptr<Poppler::Link>> links = page->links();
113 }
114
115 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
116 qDebug() << "form fields...";
117
118 PagePointer page(loadRandomPage(document: m_document));
119
120 std::vector<std::unique_ptr<Poppler::FormField>> formFields = page->formFields();
121 }
122
123 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
124 qDebug() << "thumbnail...";
125
126 PagePointer page(loadRandomPage(document: m_document));
127
128 page->thumbnail();
129 }
130
131 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
132 qDebug() << "text...";
133
134 PagePointer page(loadRandomPage(document: m_document));
135
136 page->text(rect: QRectF(QPointF(), page->pageSizeF()));
137 }
138
139 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
140 QMutexLocker mutexLocker(m_annotationMutex);
141
142 qDebug() << "add annotation...";
143
144 PagePointer page(loadRandomPage(document: m_document));
145
146 Poppler::Annotation *annotation = nullptr;
147
148 switch (QRandomGenerator::global()->bounded(highest: 3)) {
149 default:
150 case 0:
151 annotation = new Poppler::TextAnnotation(QRandomGenerator::global()->bounded(highest: 2) == 0 ? Poppler::TextAnnotation::Linked : Poppler::TextAnnotation::InPlace);
152 break;
153 case 1:
154 annotation = new Poppler::HighlightAnnotation();
155 break;
156 case 2:
157 annotation = new Poppler::InkAnnotation();
158 break;
159 }
160
161 annotation->setBoundary(QRectF(0.0, 0.0, 0.5, 0.5));
162 annotation->setContents(QStringLiteral("crazy"));
163
164 page->addAnnotation(ann: annotation);
165
166 delete annotation;
167 }
168
169 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
170 QMutexLocker mutexLocker(m_annotationMutex);
171
172 for (int index = 0; index < m_document->numPages(); ++index) {
173 PagePointer page(loadPage(document: m_document, index));
174
175 std::vector<std::unique_ptr<Poppler::Annotation>> annotations = page->annotations();
176
177 if (!annotations.empty()) {
178 qDebug() << "modify annotation...";
179
180 // size is now a qsizetype which confuses bounded(), pretend we will never have that many annotations anyway
181 const quint32 annotationsSize = annotations.size();
182 annotations.at(n: QRandomGenerator::global()->bounded(highest: annotationsSize))->setBoundary(QRectF(0.5, 0.5, 0.25, 0.25));
183 annotations.at(n: QRandomGenerator::global()->bounded(highest: annotationsSize))->setAuthor(QStringLiteral("foo"));
184 annotations.at(n: QRandomGenerator::global()->bounded(highest: annotationsSize))->setContents(QStringLiteral("bar"));
185 annotations.at(n: QRandomGenerator::global()->bounded(highest: annotationsSize))->setCreationDate(QDateTime::currentDateTime());
186 annotations.at(n: QRandomGenerator::global()->bounded(highest: annotationsSize))->setModificationDate(QDateTime::currentDateTime());
187 }
188
189 if (!annotations.empty()) {
190 break;
191 }
192 }
193 }
194
195 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
196 QMutexLocker mutexLocker(m_annotationMutex);
197
198 for (int index = 0; index < m_document->numPages(); ++index) {
199 PagePointer page(loadPage(document: m_document, index));
200
201 std::vector<std::unique_ptr<Poppler::Annotation>> annotations = page->annotations();
202
203 if (!annotations.empty()) {
204 qDebug() << "remove annotation...";
205
206 // size is now a qsizetype which confuses bounded(), pretend we will never have that many annotations anyway
207 const quint32 annotationsSize = annotations.size();
208 page->removeAnnotation(ann: annotations[QRandomGenerator::global()->bounded(highest: annotationsSize)].get());
209 annotations.erase(position: annotations.begin() + QRandomGenerator::global()->bounded(highest: annotationsSize));
210 }
211
212 if (!annotations.empty()) {
213 break;
214 }
215 }
216 }
217
218 if (QRandomGenerator::global()->bounded(highest: 2) == 0) {
219 qDebug() << "fonts...";
220
221 m_document->fonts();
222 }
223 }
224}
225
226int main(int argc, char **argv)
227{
228 if (argc < 5) {
229 qDebug() << "usage: stress-threads-qt duration sillyCount crazyCount file(s)";
230
231 return EXIT_FAILURE;
232 }
233
234 const int duration = atoi(nptr: argv[1]);
235 const int sillyCount = atoi(nptr: argv[2]);
236 const int crazyCount = atoi(nptr: argv[3]);
237
238 for (int argi = 4; argi < argc; ++argi) {
239 const QString file = QFile::decodeName(localFileName: argv[argi]);
240 std::unique_ptr<Poppler::Document> document = Poppler::Document::load(filePath: file);
241
242 if (document == nullptr) {
243 qDebug() << "Could not load" << file;
244 continue;
245 }
246
247 if (document->isLocked()) {
248 qDebug() << file << "is locked";
249 continue;
250 }
251
252 for (int i = 0; i < sillyCount; ++i) {
253 (new SillyThread(document.get()))->start();
254 }
255
256 QMutex *annotationMutex = new QMutex();
257
258 for (int i = 0; i < crazyCount; ++i) {
259 (new CrazyThread(document.get(), annotationMutex))->start();
260 }
261 }
262
263 sleep(seconds: duration);
264
265 return EXIT_SUCCESS;
266}
267
268#include "stress-threads-qt6.moc"
269

source code of poppler/qt6/tests/stress-threads-qt6.cpp