1#include <QtTest/QTest>
2
3#include "PDFDoc.h"
4#include "GlobalParams.h"
5
6#include <poppler-qt6.h>
7#include <poppler-optcontent-private.h>
8
9class TestOptionalContent : public QObject
10{
11 Q_OBJECT
12public:
13 explicit TestOptionalContent(QObject *parent = nullptr) : QObject(parent) { }
14private slots:
15 void checkVisPolicy();
16 void checkNestedLayers();
17 void checkNoOptionalContent();
18 void checkIsVisible();
19 void checkVisibilitySetting();
20 void checkRadioButtons();
21};
22
23void TestOptionalContent::checkVisPolicy()
24{
25 std::unique_ptr<Poppler::Document> doc = Poppler::Document::load(TESTDATADIR "/unittestcases/vis_policy_test.pdf");
26 QVERIFY(doc);
27
28 QVERIFY(doc->hasOptionalContent());
29
30 Poppler::OptContentModel *optContent = doc->optionalContentModel();
31 QModelIndex index;
32 index = optContent->index(row: 0, column: 0, parent: QModelIndex());
33 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("A"));
34 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
35 index = optContent->index(row: 1, column: 0, parent: QModelIndex());
36 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("B"));
37 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
38}
39
40void TestOptionalContent::checkNestedLayers()
41{
42 std::unique_ptr<Poppler::Document> doc = Poppler::Document::load(TESTDATADIR "/unittestcases/NestedLayers.pdf");
43 QVERIFY(doc);
44
45 QVERIFY(doc->hasOptionalContent());
46
47 Poppler::OptContentModel *optContent = doc->optionalContentModel();
48 QModelIndex index;
49
50 index = optContent->index(row: 0, column: 0, parent: QModelIndex());
51 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Black Text and Green Snow"));
52 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Unchecked);
53
54 index = optContent->index(row: 1, column: 0, parent: QModelIndex());
55 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Mountains and Image"));
56 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
57
58 // This is a sub-item of "Mountains and Image"
59 QModelIndex subindex = optContent->index(row: 0, column: 0, parent: index);
60 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Image"));
61 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
62
63 index = optContent->index(row: 2, column: 0, parent: QModelIndex());
64 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Starburst"));
65 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Checked);
66
67 index = optContent->index(row: 3, column: 0, parent: QModelIndex());
68 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Watermark"));
69 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Unchecked);
70}
71
72void TestOptionalContent::checkNoOptionalContent()
73{
74 std::unique_ptr<Poppler::Document> doc = Poppler::Document::load(TESTDATADIR "/unittestcases/orientation.pdf");
75 QVERIFY(doc);
76
77 QCOMPARE(doc->hasOptionalContent(), false);
78}
79
80void TestOptionalContent::checkIsVisible()
81{
82 globalParams = std::make_unique<GlobalParams>();
83 PDFDoc *doc = new PDFDoc(std::make_unique<GooString>(TESTDATADIR "/unittestcases/vis_policy_test.pdf"));
84 QVERIFY(doc);
85
86 OCGs *ocgs = doc->getOptContentConfig();
87 QVERIFY(ocgs);
88
89 XRef *xref = doc->getXRef();
90
91 Object obj;
92
93 // In this test, both Ref(21,0) and Ref(2,0) are set to On
94
95 // AnyOn, one element array:
96 // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
97 obj = xref->fetch(num: 22, gen: 0);
98 QVERIFY(obj.isDict());
99 QVERIFY(ocgs->optContentIsVisible(&obj));
100
101 // Same again, looking for any leaks or dubious free()'s
102 obj = xref->fetch(num: 22, gen: 0);
103 QVERIFY(obj.isDict());
104 QVERIFY(ocgs->optContentIsVisible(&obj));
105
106 // AnyOff, one element array:
107 // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
108 obj = xref->fetch(num: 29, gen: 0);
109 QVERIFY(obj.isDict());
110 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
111
112 // AllOn, one element array:
113 // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
114 obj = xref->fetch(num: 36, gen: 0);
115 QVERIFY(obj.isDict());
116 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
117
118 // AllOff, one element array:
119 // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
120 obj = xref->fetch(num: 43, gen: 0);
121 QVERIFY(obj.isDict());
122 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
123
124 // AnyOn, multi-element array:
125 // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
126 obj = xref->fetch(num: 50, gen: 0);
127 QVERIFY(obj.isDict());
128 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
129
130 // AnyOff, multi-element array:
131 // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
132 obj = xref->fetch(num: 57, gen: 0);
133 QVERIFY(obj.isDict());
134 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
135
136 // AllOn, multi-element array:
137 // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
138 obj = xref->fetch(num: 64, gen: 0);
139 QVERIFY(obj.isDict());
140 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
141
142 // AllOff, multi-element array:
143 // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
144 obj = xref->fetch(num: 71, gen: 0);
145 QVERIFY(obj.isDict());
146 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
147
148 delete doc;
149 globalParams.reset();
150}
151
152void TestOptionalContent::checkVisibilitySetting()
153{
154 globalParams = std::make_unique<GlobalParams>();
155 PDFDoc *doc = new PDFDoc(std::make_unique<GooString>(TESTDATADIR "/unittestcases/vis_policy_test.pdf"));
156 QVERIFY(doc);
157
158 OCGs *ocgs = doc->getOptContentConfig();
159 QVERIFY(ocgs);
160
161 XRef *xref = doc->getXRef();
162
163 Object obj;
164
165 // In this test, both Ref(21,0) and Ref(28,0) start On,
166 // based on the file settings
167 Object ref21obj(Ref { .num: 21, .gen: 0 });
168 Ref ref21 = ref21obj.getRef();
169 OptionalContentGroup *ocgA = ocgs->findOcgByRef(ref: ref21);
170 QVERIFY(ocgA);
171
172 QVERIFY((ocgA->getName()->cmp("A")) == 0);
173 QCOMPARE(ocgA->getState(), OptionalContentGroup::On);
174
175 Object ref28obj(Ref { .num: 28, .gen: 0 });
176 Ref ref28 = ref28obj.getRef();
177 OptionalContentGroup *ocgB = ocgs->findOcgByRef(ref: ref28);
178 QVERIFY(ocgB);
179
180 QVERIFY((ocgB->getName()->cmp("B")) == 0);
181 QCOMPARE(ocgB->getState(), OptionalContentGroup::On);
182
183 // turn one Off
184 ocgA->setState(OptionalContentGroup::Off);
185
186 // AnyOn, one element array:
187 // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
188 obj = xref->fetch(num: 22, gen: 0);
189 QVERIFY(obj.isDict());
190 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
191
192 // Same again, looking for any leaks or dubious free()'s
193 obj = xref->fetch(num: 22, gen: 0);
194 QVERIFY(obj.isDict());
195 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
196
197 // AnyOff, one element array:
198 // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
199 obj = xref->fetch(num: 29, gen: 0);
200 QVERIFY(obj.isDict());
201 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
202
203 // AllOn, one element array:
204 // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
205 obj = xref->fetch(num: 36, gen: 0);
206 QVERIFY(obj.isDict());
207 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
208
209 // AllOff, one element array:
210 // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
211 obj = xref->fetch(num: 43, gen: 0);
212 QVERIFY(obj.isDict());
213 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
214
215 // AnyOn, multi-element array:
216 // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
217 obj = xref->fetch(num: 50, gen: 0);
218 QVERIFY(obj.isDict());
219 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
220
221 // AnyOff, multi-element array:
222 // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
223 obj = xref->fetch(num: 57, gen: 0);
224 QVERIFY(obj.isDict());
225 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
226
227 // AllOn, multi-element array:
228 // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
229 obj = xref->fetch(num: 64, gen: 0);
230 QVERIFY(obj.isDict());
231 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
232
233 // AllOff, multi-element array:
234 // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
235 obj = xref->fetch(num: 71, gen: 0);
236 QVERIFY(obj.isDict());
237 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
238
239 // Turn the other one off as well (i.e. both are Off)
240 ocgB->setState(OptionalContentGroup::Off);
241
242 // AnyOn, one element array:
243 // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
244 obj = xref->fetch(num: 22, gen: 0);
245 QVERIFY(obj.isDict());
246 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
247
248 // Same again, looking for any leaks or dubious free()'s
249 obj = xref->fetch(num: 22, gen: 0);
250 QVERIFY(obj.isDict());
251 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
252
253 // AnyOff, one element array:
254 // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
255 obj = xref->fetch(num: 29, gen: 0);
256 QVERIFY(obj.isDict());
257 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
258
259 // AllOn, one element array:
260 // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
261 obj = xref->fetch(num: 36, gen: 0);
262 QVERIFY(obj.isDict());
263 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
264
265 // AllOff, one element array:
266 // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
267 obj = xref->fetch(num: 43, gen: 0);
268 QVERIFY(obj.isDict());
269 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
270
271 // AnyOn, multi-element array:
272 // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
273 obj = xref->fetch(num: 50, gen: 0);
274 QVERIFY(obj.isDict());
275 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
276
277 // AnyOff, multi-element array:
278 // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
279 obj = xref->fetch(num: 57, gen: 0);
280 QVERIFY(obj.isDict());
281 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
282
283 // AllOn, multi-element array:
284 // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
285 obj = xref->fetch(num: 64, gen: 0);
286 QVERIFY(obj.isDict());
287 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
288
289 // AllOff, multi-element array:
290 // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
291 obj = xref->fetch(num: 71, gen: 0);
292 QVERIFY(obj.isDict());
293 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
294
295 // Turn the first one on again (21 is On, 28 is Off)
296 ocgA->setState(OptionalContentGroup::On);
297
298 // AnyOn, one element array:
299 // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
300 obj = xref->fetch(num: 22, gen: 0);
301 QVERIFY(obj.isDict());
302 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
303
304 // Same again, looking for any leaks or dubious free()'s
305 obj = xref->fetch(num: 22, gen: 0);
306 QVERIFY(obj.isDict());
307 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
308
309 // AnyOff, one element array:
310 // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
311 obj = xref->fetch(num: 29, gen: 0);
312 QVERIFY(obj.isDict());
313 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
314
315 // AllOn, one element array:
316 // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
317 obj = xref->fetch(num: 36, gen: 0);
318 QVERIFY(obj.isDict());
319 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
320
321 // AllOff, one element array:
322 // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
323 obj = xref->fetch(num: 43, gen: 0);
324 QVERIFY(obj.isDict());
325 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
326
327 // AnyOn, multi-element array:
328 // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
329 obj = xref->fetch(num: 50, gen: 0);
330 QVERIFY(obj.isDict());
331 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
332
333 // AnyOff, multi-element array:
334 // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
335 obj = xref->fetch(num: 57, gen: 0);
336 QVERIFY(obj.isDict());
337 QCOMPARE(ocgs->optContentIsVisible(&obj), true);
338
339 // AllOn, multi-element array:
340 // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
341 obj = xref->fetch(num: 64, gen: 0);
342 QVERIFY(obj.isDict());
343 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
344
345 // AllOff, multi-element array:
346 // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
347 obj = xref->fetch(num: 71, gen: 0);
348 QVERIFY(obj.isDict());
349 QCOMPARE(ocgs->optContentIsVisible(&obj), false);
350
351 delete doc;
352 globalParams.reset();
353}
354
355void TestOptionalContent::checkRadioButtons()
356{
357 std::unique_ptr<Poppler::Document> doc = Poppler::Document::load(TESTDATADIR "/unittestcases/ClarityOCGs.pdf");
358 QVERIFY(doc);
359
360 QVERIFY(doc->hasOptionalContent());
361
362 Poppler::OptContentModel *optContent = doc->optionalContentModel();
363 QModelIndex index;
364
365 index = optContent->index(row: 0, column: 0, parent: QModelIndex());
366 QCOMPARE(optContent->data(index, Qt::DisplayRole).toString(), QLatin1String("Languages"));
367 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(index, Qt::CheckStateRole).toInt()), Qt::Unchecked);
368
369 // These are sub-items of the "Languages" label
370 QModelIndex subindex = optContent->index(row: 0, column: 0, parent: index);
371 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
372 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Checked);
373
374 subindex = optContent->index(row: 1, column: 0, parent: index);
375 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
376 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
377
378 subindex = optContent->index(row: 2, column: 0, parent: index);
379 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
380 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
381
382 // RBGroup of languages, so turning on Japanese should turn off English
383 QVERIFY(optContent->setData(subindex, QVariant(true), Qt::CheckStateRole));
384
385 subindex = optContent->index(row: 0, column: 0, parent: index);
386 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
387 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
388 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
389
390 subindex = optContent->index(row: 2, column: 0, parent: index);
391 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
392 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Checked);
393 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::On);
394
395 subindex = optContent->index(row: 1, column: 0, parent: index);
396 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
397 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
398 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
399
400 // and turning on French should turn off Japanese
401 QVERIFY(optContent->setData(subindex, QVariant(true), Qt::CheckStateRole));
402
403 subindex = optContent->index(row: 0, column: 0, parent: index);
404 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
405 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
406 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
407
408 subindex = optContent->index(row: 2, column: 0, parent: index);
409 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
410 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
411 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
412
413 subindex = optContent->index(row: 1, column: 0, parent: index);
414 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
415 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Checked);
416 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::On);
417
418 // and turning off French should leave them all off
419 QVERIFY(optContent->setData(subindex, QVariant(false), Qt::CheckStateRole));
420
421 subindex = optContent->index(row: 0, column: 0, parent: index);
422 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("English"));
423 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
424 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
425
426 subindex = optContent->index(row: 2, column: 0, parent: index);
427 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("Japanese"));
428 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
429 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
430
431 subindex = optContent->index(row: 1, column: 0, parent: index);
432 QCOMPARE(optContent->data(subindex, Qt::DisplayRole).toString(), QLatin1String("French"));
433 QCOMPARE(static_cast<Qt::CheckState>(optContent->data(subindex, Qt::CheckStateRole).toInt()), Qt::Unchecked);
434 QCOMPARE(static_cast<Poppler::OptContentItem *>(subindex.internalPointer())->group()->getState(), OptionalContentGroup::Off);
435}
436
437QTEST_GUILESS_MAIN(TestOptionalContent)
438
439#include "check_optcontent.moc"
440

source code of poppler/qt6/tests/check_optcontent.cpp