1#include <QtTest/QTest>
2#include <QtCore/QTemporaryFile>
3
4#include "Outline.h"
5#include "PDFDoc.h"
6#include "PDFDocFactory.h"
7
8class TestInternalOutline : public QObject
9{
10 Q_OBJECT
11public:
12 explicit TestInternalOutline(QObject *parent = nullptr) : QObject(parent) { }
13private slots:
14 void testCreateOutline();
15 void testSetOutline();
16 void testInsertChild();
17 void testRemoveChild();
18 void testSetTitleAndSetPageDest();
19};
20
21void TestInternalOutline::testCreateOutline()
22{
23 QTemporaryFile tempFile;
24 QVERIFY(tempFile.open());
25 tempFile.close();
26
27 const std::string tempFileName = tempFile.fileName().toStdString();
28 const GooString gooTempFileName { tempFileName };
29
30 std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(uri: GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
31 QVERIFY(doc.get());
32
33 // ensure the file has no existing outline
34 Outline *outline = doc->getOutline();
35 QVERIFY(outline != nullptr);
36 auto *outlineItems = outline->getItems();
37 QVERIFY(outlineItems == nullptr);
38
39 // create an empty outline and save the file
40 outline->setOutline({});
41 outlineItems = outline->getItems();
42 // no items will result in a nullptr rather than a 0 length list
43 QVERIFY(outlineItems == nullptr);
44 doc->saveAs(name: gooTempFileName);
45
46 /******************************************************/
47
48 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName);
49 QVERIFY(doc.get());
50
51 // ensure the re-opened file has an outline with no items
52 outline = doc->getOutline();
53 QVERIFY(outline != nullptr);
54 outlineItems = outline->getItems();
55 QVERIFY(outlineItems == nullptr);
56}
57
58static std::string getTitle(const OutlineItem *item)
59{
60 const std::vector<Unicode> &u = item->getTitle();
61 std::string s;
62 for (const auto &c : u) {
63 s.append(n: 1, c: (char)(c));
64 }
65 return s;
66}
67
68void TestInternalOutline::testSetOutline()
69{
70 QTemporaryFile tempFile;
71 QVERIFY(tempFile.open());
72 tempFile.close();
73
74 const std::string tempFileName = tempFile.fileName().toStdString();
75 const GooString gooTempFileName { tempFileName };
76
77 std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(uri: GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
78 QVERIFY(doc.get());
79
80 // ensure the file has no existing outline
81 Outline *outline = doc->getOutline();
82 QVERIFY(outline != nullptr);
83 auto *outlineItems = outline->getItems();
84 QVERIFY(outlineItems == nullptr);
85
86 // create an outline and save the file
87 outline->setOutline(
88 { { .title: "1", .destPageNum: 1, .children: { { .title: "1.1", .destPageNum: 1, .children: {} }, { .title: "1.2", .destPageNum: 2, .children: {} }, { .title: "1.3", .destPageNum: 3, .children: { { .title: "1.3.1", .destPageNum: 1, .children: {} }, { .title: "1.3.2", .destPageNum: 2, .children: {} }, { .title: "1.3.3", .destPageNum: 3, .children: {} }, { .title: "1.3.4", .destPageNum: 4, .children: {} } } }, { .title: "1.4", .destPageNum: 4, .children: {} } } }, { .title: "2", .destPageNum: 2, .children: {} }, { .title: "3", .destPageNum: 3, .children: {} }, { .title: "4", .destPageNum: 4, .children: {} } });
89 outlineItems = outline->getItems();
90 QVERIFY(outlineItems != nullptr);
91 doc->saveAs(name: gooTempFileName);
92 outline = nullptr;
93
94 /******************************************************/
95
96 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName);
97 QVERIFY(doc.get());
98
99 // ensure the re-opened file has an outline
100 outline = doc->getOutline();
101 QVERIFY(outline != nullptr);
102 outlineItems = outline->getItems();
103
104 QVERIFY(outlineItems != nullptr);
105 QVERIFY(outlineItems->size() == 4);
106
107 OutlineItem *item = outlineItems->at(n: 0);
108 QVERIFY(item != nullptr);
109
110 // c_str() is used so QCOMPARE prints string correctly on disagree
111 QCOMPARE(getTitle(item).c_str(), "1");
112 item = outlineItems->at(n: 1);
113 QVERIFY(item != nullptr);
114 QCOMPARE(getTitle(item).c_str(), "2");
115 item = outlineItems->at(n: 2);
116 QVERIFY(item != nullptr);
117 QCOMPARE(getTitle(item).c_str(), "3");
118 item = outlineItems->at(n: 3);
119 QVERIFY(item != nullptr);
120 QCOMPARE(getTitle(item).c_str(), "4");
121
122 outlineItems = outlineItems->at(n: 0)->getKids();
123 QVERIFY(outlineItems != nullptr);
124 item = outlineItems->at(n: 0);
125 QVERIFY(item != nullptr);
126 QCOMPARE(getTitle(item).c_str(), "1.1");
127 item = outlineItems->at(n: 1);
128 QVERIFY(item != nullptr);
129 QCOMPARE(getTitle(item).c_str(), "1.2");
130 item = outlineItems->at(n: 2);
131 QVERIFY(item != nullptr);
132 QCOMPARE(getTitle(item).c_str(), "1.3");
133 item = outlineItems->at(n: 3);
134 QVERIFY(item != nullptr);
135 QCOMPARE(getTitle(item).c_str(), "1.4");
136
137 outlineItems = outlineItems->at(n: 2)->getKids();
138 QVERIFY(outlineItems != nullptr);
139
140 item = outlineItems->at(n: 0);
141 QVERIFY(item != nullptr);
142 QCOMPARE(getTitle(item).c_str(), "1.3.1");
143 item = outlineItems->at(n: 1);
144 QVERIFY(item != nullptr);
145 QCOMPARE(getTitle(item).c_str(), "1.3.2");
146 item = outlineItems->at(n: 2);
147 QVERIFY(item != nullptr);
148 QCOMPARE(getTitle(item).c_str(), "1.3.3");
149 item = outlineItems->at(n: 3);
150 QVERIFY(item != nullptr);
151 QCOMPARE(getTitle(item).c_str(), "1.3.4");
152}
153
154void TestInternalOutline::testInsertChild()
155{
156 QTemporaryFile tempFile;
157 QVERIFY(tempFile.open());
158 tempFile.close();
159 QTemporaryFile tempFile2;
160 QVERIFY(tempFile2.open());
161 tempFile2.close();
162
163 const std::string tempFileName = tempFile.fileName().toStdString();
164 const GooString gooTempFileName { tempFileName };
165 const std::string tempFileName2 = tempFile2.fileName().toStdString();
166 const GooString gooTempFileName2 { tempFileName2 };
167
168 std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(uri: GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
169 QVERIFY(doc.get());
170
171 // ensure the file has no existing outline
172 Outline *outline = doc->getOutline();
173 QVERIFY(outline != nullptr);
174 auto *outlineItems = outline->getItems();
175 QVERIFY(outlineItems == nullptr);
176
177 // create an outline and save the file
178 outline->setOutline({});
179 doc->saveAs(name: gooTempFileName);
180 outline = nullptr;
181
182 /******************************************************/
183
184 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName);
185 QVERIFY(doc.get());
186
187 // ensure the re-opened file has an outline with no items
188 outline = doc->getOutline();
189 QVERIFY(outline != nullptr);
190 // nullptr for 0-length
191 QVERIFY(outline->getItems() == nullptr);
192
193 // insert first one to empty
194 outline->insertChild(itemTitle: "2", destPageNum: 1, pos: 0);
195 // insert at the end
196 outline->insertChild(itemTitle: "3", destPageNum: 1, pos: 1);
197 // insert at the start
198 outline->insertChild(itemTitle: "1", destPageNum: 1, pos: 0);
199
200 // add an item to "2"
201 outlineItems = outline->getItems();
202 QVERIFY(outlineItems != nullptr);
203 QVERIFY(outlineItems->at(1));
204 outlineItems->at(n: 1)->insertChild(itemTitle: "2.1", destPageNum: 2, pos: 0);
205 outlineItems->at(n: 1)->insertChild(itemTitle: "2.2", destPageNum: 2, pos: 1);
206 outlineItems->at(n: 1)->insertChild(itemTitle: "2.4", destPageNum: 2, pos: 2);
207
208 outlineItems->at(n: 1)->insertChild(itemTitle: "2.3", destPageNum: 2, pos: 2);
209
210 // save the file
211 doc->saveAs(name: gooTempFileName2);
212 outline = nullptr;
213
214 /******************************************************/
215
216 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName2);
217 QVERIFY(doc.get());
218
219 // ensure the re-opened file has an outline
220 outline = doc->getOutline();
221 QVERIFY(outline != nullptr);
222
223 outlineItems = outline->getItems();
224
225 QVERIFY(outlineItems != nullptr);
226 QVERIFY(outlineItems->size() == 3);
227
228 OutlineItem *item = outlineItems->at(n: 0);
229 QVERIFY(item != nullptr);
230
231 // c_str() is used so QCOMPARE prints string correctly on disagree
232 QCOMPARE(getTitle(item).c_str(), "1");
233 item = outlineItems->at(n: 1);
234 QVERIFY(item != nullptr);
235 QCOMPARE(getTitle(item).c_str(), "2");
236 item = outlineItems->at(n: 2);
237 QVERIFY(item != nullptr);
238 QCOMPARE(getTitle(item).c_str(), "3");
239
240 outlineItems = outlineItems->at(n: 1)->getKids();
241 item = outlineItems->at(n: 0);
242 QVERIFY(item != nullptr);
243 QCOMPARE(getTitle(item).c_str(), "2.1");
244 item = outlineItems->at(n: 1);
245 QVERIFY(item != nullptr);
246 QCOMPARE(getTitle(item).c_str(), "2.2");
247 item = outlineItems->at(n: 2);
248 QVERIFY(item != nullptr);
249 QCOMPARE(getTitle(item).c_str(), "2.3");
250 item = outlineItems->at(n: 3);
251 QVERIFY(item != nullptr);
252 QCOMPARE(getTitle(item).c_str(), "2.4");
253}
254
255void TestInternalOutline::testRemoveChild()
256{
257 QTemporaryFile tempFile;
258 QVERIFY(tempFile.open());
259 tempFile.close();
260
261 QTemporaryFile tempFile2;
262 QVERIFY(tempFile2.open());
263 tempFile2.close();
264
265 const std::string tempFileName = tempFile.fileName().toStdString();
266 const GooString gooTempFileName { tempFileName };
267 const std::string tempFileName2 = tempFile2.fileName().toStdString();
268 const GooString gooTempFileName2 { tempFileName2 };
269
270 std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(uri: GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
271 QVERIFY(doc.get());
272
273 // ensure the file has no existing outline
274 Outline *outline = doc->getOutline();
275 QVERIFY(outline != nullptr);
276 auto *outlineItems = outline->getItems();
277 QVERIFY(outlineItems == nullptr);
278
279 // create an outline and save the file
280 outline->setOutline({ { .title: "1", .destPageNum: 1, .children: { { .title: "1.1", .destPageNum: 1, .children: {} }, { .title: "1.2", .destPageNum: 2, .children: {} }, { .title: "1.3", .destPageNum: 3, .children: { { .title: "1.3.1", .destPageNum: 1, .children: {} }, { .title: "1.3.2", .destPageNum: 2, .children: {} }, { .title: "1.3.3", .destPageNum: 3, .children: {} }, { .title: "1.3.4", .destPageNum: 4, .children: {} } } }, { .title: "1.4", .destPageNum: 4, .children: {} } } },
281 { .title: "2", .destPageNum: 2, .children: { { .title: "2.1", .destPageNum: 1, .children: {} } } },
282 { .title: "3", .destPageNum: 3, .children: { { .title: "3.1", .destPageNum: 1, .children: {} }, { .title: "3.2", .destPageNum: 2, .children: { { .title: "3.2.1", .destPageNum: 1, .children: {} } } } } },
283 { .title: "4", .destPageNum: 4, .children: {} } });
284 outlineItems = outline->getItems();
285 QVERIFY(outlineItems != nullptr);
286 doc->saveAs(name: gooTempFileName);
287 outline = nullptr;
288
289 /******************************************************/
290
291 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName);
292 QVERIFY(doc.get());
293
294 outline = doc->getOutline();
295 QVERIFY(outline != nullptr);
296
297 // remove "3"
298 outline->removeChild(pos: 2);
299 // remove "1.3.1"
300 outline->getItems()->at(n: 0)->getKids()->at(n: 2)->removeChild(pos: 0);
301 // remove "1.3.4"
302 outline->getItems()->at(n: 0)->getKids()->at(n: 2)->removeChild(pos: 2);
303 // remove "2.1"
304 outline->getItems()->at(n: 1)->removeChild(pos: 0);
305
306 // save the file
307 doc->saveAs(name: gooTempFileName2);
308 outline = nullptr;
309
310 /******************************************************/
311
312 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName2);
313 QVERIFY(doc.get());
314
315 // ensure the re-opened file has an outline
316 outline = doc->getOutline();
317 QVERIFY(outline != nullptr);
318
319 outlineItems = outline->getItems();
320
321 QVERIFY(outlineItems != nullptr);
322 QVERIFY(outlineItems->size() == 3);
323
324 OutlineItem *item = outlineItems->at(n: 0);
325 QVERIFY(item != nullptr);
326
327 // c_str() is used so QCOMPARE prints string correctly on disagree
328 QCOMPARE(getTitle(item).c_str(), "1");
329 item = outlineItems->at(n: 1);
330 QVERIFY(item != nullptr);
331 QCOMPARE(getTitle(item).c_str(), "2");
332 item = outlineItems->at(n: 2);
333 QVERIFY(item != nullptr);
334 QCOMPARE(getTitle(item).c_str(), "4");
335
336 outlineItems = outlineItems->at(n: 0)->getKids();
337 outlineItems = outlineItems->at(n: 2)->getKids();
338 item = outlineItems->at(n: 0);
339 QVERIFY(item != nullptr);
340 QCOMPARE(getTitle(item).c_str(), "1.3.2");
341 item = outlineItems->at(n: 1);
342 QVERIFY(item != nullptr);
343 QCOMPARE(getTitle(item).c_str(), "1.3.3");
344
345 // verify "2.1" is removed, lst length 0 is returned as a nullptr
346 QVERIFY(outline->getItems()->at(1)->getKids() == nullptr);
347}
348
349void TestInternalOutline::testSetTitleAndSetPageDest()
350{
351 QTemporaryFile tempFile;
352 QVERIFY(tempFile.open());
353 tempFile.close();
354
355 QTemporaryFile tempFile2;
356 QVERIFY(tempFile2.open());
357 tempFile2.close();
358
359 const std::string tempFileName = tempFile.fileName().toStdString();
360 const GooString gooTempFileName { tempFileName };
361 const std::string tempFileName2 = tempFile2.fileName().toStdString();
362 const GooString gooTempFileName2 { tempFileName2 };
363
364 std::unique_ptr<PDFDoc> doc = PDFDocFactory().createPDFDoc(uri: GooString(TESTDATADIR "/unittestcases/truetype.pdf"));
365 QVERIFY(doc.get());
366
367 // ensure the file has no existing outline
368 Outline *outline = doc->getOutline();
369 QVERIFY(outline != nullptr);
370 auto *outlineItems = outline->getItems();
371 QVERIFY(outlineItems == nullptr);
372
373 // create an outline and save the file
374 outline->setOutline({ { .title: "1", .destPageNum: 1, .children: { { .title: "1.1", .destPageNum: 1, .children: {} }, { .title: "1.2", .destPageNum: 2, .children: {} }, { .title: "1.3", .destPageNum: 3, .children: { { .title: "1.3.1", .destPageNum: 1, .children: {} }, { .title: "1.3.2", .destPageNum: 2, .children: {} }, { .title: "1.3.3", .destPageNum: 3, .children: {} }, { .title: "1.3.4", .destPageNum: 4, .children: {} } } }, { .title: "1.4", .destPageNum: 4, .children: {} } } },
375 { .title: "2", .destPageNum: 2, .children: { { .title: "2.1", .destPageNum: 1, .children: {} } } },
376 { .title: "3", .destPageNum: 3, .children: { { .title: "3.1", .destPageNum: 1, .children: {} }, { .title: "3.2", .destPageNum: 2, .children: { { .title: "3.2.1", .destPageNum: 1, .children: {} } } } } },
377 { .title: "4", .destPageNum: 4, .children: {} } });
378 outlineItems = outline->getItems();
379 QVERIFY(outlineItems != nullptr);
380 doc->saveAs(name: gooTempFileName);
381
382 outline = nullptr;
383
384 /******************************************************/
385
386 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName);
387 QVERIFY(doc.get());
388
389 outline = doc->getOutline();
390 QVERIFY(outline != nullptr);
391
392 // change "1.3.1"
393 OutlineItem *item = outline->getItems()->at(n: 0)->getKids()->at(n: 2)->getKids()->at(n: 0);
394 QCOMPARE(getTitle(item).c_str(), "1.3.1");
395
396 item->setTitle("Changed to a different title");
397
398 item = outline->getItems()->at(n: 2);
399 {
400 const LinkAction *action = item->getAction();
401 QVERIFY(action->getKind() == actionGoTo);
402 const LinkGoTo *gotoAction = dynamic_cast<const LinkGoTo *>(action);
403 const LinkDest *dest = gotoAction->getDest();
404 QVERIFY(dest->isPageRef() == false);
405 QCOMPARE(dest->getPageNum(), 3);
406
407 item->setPageDest(1);
408 }
409
410 // save the file
411 doc->saveAs(name: gooTempFileName2);
412 outline = nullptr;
413 item = nullptr;
414
415 /******************************************************/
416
417 doc = PDFDocFactory().createPDFDoc(uri: gooTempFileName2);
418 QVERIFY(doc.get());
419
420 outline = doc->getOutline();
421 QVERIFY(outline != nullptr);
422
423 item = outline->getItems()->at(n: 0)->getKids()->at(n: 2)->getKids()->at(n: 0);
424 QCOMPARE(getTitle(item).c_str(), "Changed to a different title");
425 {
426 item = outline->getItems()->at(n: 2);
427 const LinkAction *action = item->getAction();
428 QVERIFY(action->getKind() == actionGoTo);
429 const LinkGoTo *gotoAction = dynamic_cast<const LinkGoTo *>(action);
430 const LinkDest *dest = gotoAction->getDest();
431 QVERIFY(dest->isPageRef() == false);
432 QCOMPARE(dest->getPageNum(), 1);
433 }
434}
435
436QTEST_GUILESS_MAIN(TestInternalOutline)
437#include "check_internal_outline.moc"
438

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