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