1 | #include <QtTest/QTest> |
2 | #include <QtCore/QTemporaryFile> |
3 | |
4 | #include "Outline.h" |
5 | #include "PDFDoc.h" |
6 | #include "PDFDocFactory.h" |
7 | |
8 | class TestInternalOutline : public QObject |
9 | { |
10 | Q_OBJECT |
11 | public: |
12 | explicit TestInternalOutline(QObject *parent = nullptr) : QObject(parent) { } |
13 | private slots: |
14 | void testCreateOutline(); |
15 | void testSetOutline(); |
16 | void testInsertChild(); |
17 | void testRemoveChild(); |
18 | void testSetTitleAndSetPageDest(); |
19 | }; |
20 | |
21 | void 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 | |
58 | static 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 | |
68 | void 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 | |
154 | void 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 | |
255 | void 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 | |
349 | void 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 | |
436 | QTEST_GUILESS_MAIN(TestInternalOutline) |
437 | #include "check_internal_outline.moc" |
438 | |