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