tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / vcl / qa / cppunit / pdfexport / pdfexport.cxx
blobc1f2ea43a6a0e725ecb458e438fd1bc158cf9e60
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/config.h>
12 #include <memory>
13 #include <string_view>
15 #include <config_fonts.h>
17 #include <com/sun/star/view/XPrintable.hpp>
19 #include <comphelper/propertysequence.hxx>
20 #include <test/unoapi_test.hxx>
21 #include <unotools/mediadescriptor.hxx>
22 #include <unotools/tempfile.hxx>
23 #include <vcl/filter/pdfdocument.hxx>
24 #include <tools/zcodec.hxx>
25 #include <o3tl/string_view.hxx>
26 #include <officecfg/Office/Common.hxx>
28 #include <vcl/filter/PDFiumLibrary.hxx>
30 using namespace ::com::sun::star;
32 namespace
34 /// Tests the PDF export filter.
35 class PdfExportTest : public UnoApiTest
37 protected:
38 utl::MediaDescriptor aMediaDescriptor;
40 public:
41 PdfExportTest()
42 : UnoApiTest(u"/vcl/qa/cppunit/pdfexport/data/"_ustr)
46 void saveAsPDF(std::u16string_view rFile);
47 void load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument);
50 void PdfExportTest::saveAsPDF(std::u16string_view rFile)
52 // Import the bugdoc and export as PDF.
53 loadFromFile(rFile);
54 aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_pdf_Export"_ustr;
55 saveWithParams(aMediaDescriptor.getAsConstPropertyValueList());
58 void PdfExportTest::load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument)
60 saveAsPDF(rFile);
62 // Parse the export result.
63 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
64 CPPUNIT_ASSERT(rDocument.Read(aStream));
67 CPPUNIT_TEST_FIXTURE(PdfExportTest, testPopupRectangleSize)
69 // Enable Comment as PDF annotations
70 uno::Sequence<beans::PropertyValue> aFilterData(
71 comphelper::InitPropertySequence({ { "ExportNotes", uno::Any(true) } }));
72 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
73 saveAsPDF(u"tdf162955_comment.odp");
75 // Parse the export result with pdfium.
76 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
77 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
78 CPPUNIT_ASSERT(pPdfPage);
80 // Popup annotation
82 auto pAnnotation = pPdfPage->getAnnotation(1);
83 CPPUNIT_ASSERT(pAnnotation);
84 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pAnnotation->getSubType());
85 CPPUNIT_ASSERT(!pAnnotation->getRectangle().isEmpty());
86 double nWidth = pAnnotation->getRectangle().getWidth();
87 double nHeight = pAnnotation->getRectangle().getHeight();
88 CPPUNIT_ASSERT(nWidth > 0);
89 CPPUNIT_ASSERT(nHeight > 0);
93 CPPUNIT_TEST_FIXTURE(PdfExportTest, testCommentAnnotation)
95 // Enable PDF/UA and Comment as PDF annotations
96 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
97 { { "PDFUACompliance", uno::Any(true) }, { "ExportNotes", uno::Any(true) } }));
98 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
100 vcl::filter::PDFDocument aDocument;
101 load(u"tdf162359.odt", aDocument);
103 // The document has one page.
104 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
105 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
107 vcl::filter::PDFObjectElement* pAnnot(nullptr);
108 for (const auto& aElement : aDocument.GetElements())
110 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
111 if (!pObject)
112 continue;
113 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
114 if (pType && pType->GetValue() == "StructElem")
116 auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
117 if (pS && pS->GetValue() == "Annot")
119 pAnnot = pObject;
123 CPPUNIT_ASSERT(pAnnot);
124 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("K"_ostr));
125 CPPUNIT_ASSERT(pKids);
126 auto pObj = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(0));
127 CPPUNIT_ASSERT(pObj);
128 auto pOType = dynamic_cast<vcl::filter::PDFNameElement*>(pObj->LookupElement("Type"_ostr));
129 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
131 // Parse the export result with pdfium.
132 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
133 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
134 CPPUNIT_ASSERT(pPdfPage);
136 // The page has two annotation.
137 CPPUNIT_ASSERT_EQUAL(2, pPdfPage->getAnnotationCount());
138 // Text annotation
140 auto pAnnotation = pPdfPage->getAnnotation(0);
141 CPPUNIT_ASSERT(pAnnotation);
142 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Text, pAnnotation->getSubType());
143 CPPUNIT_ASSERT(pAnnotation->hasKey("StructParent"_ostr));
146 // Popup annotation
148 auto pAnnotation = pPdfPage->getAnnotation(1);
149 CPPUNIT_ASSERT(pAnnotation);
150 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Popup, pAnnotation->getSubType());
151 CPPUNIT_ASSERT(!pAnnotation->getRectangle().isEmpty());
155 CPPUNIT_TEST_FIXTURE(PdfExportTest, testFigurePlacement)
157 vcl::filter::PDFDocument aDocument;
158 load(u"tdf159900_figurePlacement.odt", aDocument);
160 // The document has one page.
161 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
162 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
164 for (const auto& aElement : aDocument.GetElements())
166 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
167 if (!pObject)
168 continue;
169 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
170 if (pType && pType->GetValue() == "StructElem")
172 auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
173 if (pS && pS->GetValue() == "Figure")
175 auto pAttrDict
176 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("A"_ostr));
177 CPPUNIT_ASSERT(pAttrDict);
178 auto pOwner = dynamic_cast<vcl::filter::PDFNameElement*>(
179 pAttrDict->LookupElement("O"_ostr));
180 CPPUNIT_ASSERT(pOwner);
181 if (pOwner->GetValue() == "Layout")
183 auto pPlacement = dynamic_cast<vcl::filter::PDFNameElement*>(
184 pAttrDict->LookupElement("Placement"_ostr));
185 CPPUNIT_ASSERT(pPlacement);
187 // Without the fix in place, this test would have failed with
188 // Expected: Inline
189 // Actual: Block
190 CPPUNIT_ASSERT_EQUAL("Inline"_ostr, pPlacement->GetValue());
197 /// Tests that a pdf image is roundtripped back to PDF as a vector format.
198 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106059)
200 // Explicitly enable the usage of the reference XObject markup.
201 uno::Sequence<beans::PropertyValue> aFilterData(
202 comphelper::InitPropertySequence({ { "UseReferenceXObject", uno::Any(true) } }));
203 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
205 vcl::filter::PDFDocument aDocument;
206 load(u"tdf106059.odt", aDocument);
208 // Assert that the XObject in the page resources dictionary is a reference XObject.
209 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
210 // The document has one page.
211 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
212 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
213 CPPUNIT_ASSERT(pResources);
214 auto pXObjects
215 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
216 CPPUNIT_ASSERT(pXObjects);
217 // The page has one image.
218 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
219 vcl::filter::PDFObjectElement* pReferenceXObject
220 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
221 CPPUNIT_ASSERT(pReferenceXObject);
222 // The image is a reference XObject.
223 // This dictionary key was missing, so the XObject wasn't a reference one.
224 CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref"_ostr));
227 /// Tests export of PDF images without reference XObjects.
228 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106693)
230 vcl::filter::PDFDocument aDocument;
231 load(u"tdf106693.odt", aDocument);
233 // Assert that the XObject in the page resources dictionary is a form XObject.
234 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
235 // The document has one page.
236 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
237 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
238 CPPUNIT_ASSERT(pResources);
239 auto pXObjects
240 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
241 CPPUNIT_ASSERT(pXObjects);
242 // The page has one image.
243 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
244 vcl::filter::PDFObjectElement* pXObject
245 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
246 CPPUNIT_ASSERT(pXObject);
247 // The image is a form XObject.
248 auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"_ostr));
249 CPPUNIT_ASSERT(pSubtype);
250 CPPUNIT_ASSERT_EQUAL("Form"_ostr, pSubtype->GetValue());
251 // This failed: UseReferenceXObject was ignored and Ref was always created.
252 CPPUNIT_ASSERT(!pXObject->Lookup("Ref"_ostr));
254 // Assert that the form object refers to an inner form object, not a
255 // bitmap.
256 auto pInnerResources
257 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
258 CPPUNIT_ASSERT(pInnerResources);
259 auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
260 pInnerResources->LookupElement("XObject"_ostr));
261 CPPUNIT_ASSERT(pInnerXObjects);
262 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pInnerXObjects->GetItems().size());
263 vcl::filter::PDFObjectElement* pInnerXObject
264 = pInnerXObjects->LookupObject(pInnerXObjects->GetItems().begin()->first);
265 CPPUNIT_ASSERT(pInnerXObject);
266 auto pInnerSubtype
267 = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"_ostr));
268 CPPUNIT_ASSERT(pInnerSubtype);
269 // This failed: this was Image (bitmap), not Form (vector).
270 CPPUNIT_ASSERT_EQUAL("Form"_ostr, pInnerSubtype->GetValue());
273 /// Tests that text highlight from Impress is not lost.
274 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105461)
276 // Import the bugdoc and export as PDF.
277 saveAsPDF(u"tdf105461.odp");
279 // Parse the export result with pdfium.
280 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
282 // The document has one page.
283 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
284 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
285 CPPUNIT_ASSERT(pPdfPage);
287 // Make sure there is a filled rectangle inside.
288 int nPageObjectCount = pPdfPage->getObjectCount();
289 int nYellowPathCount = 0;
290 for (int i = 0; i < nPageObjectCount; ++i)
292 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
293 if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
294 continue;
296 if (pPdfPageObject->getFillColor() == COL_YELLOW)
297 ++nYellowPathCount;
300 // This was 0, the page contained no yellow paths.
301 CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
304 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107868)
306 // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
307 // which is the intent of the test.
308 // FIXME: Why does this fail on macOS?
309 #if !defined MACOSX && !defined _WIN32
311 // Import the bugdoc and print to PDF.
312 loadFromFile(u"tdf107868.odt");
313 uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
314 CPPUNIT_ASSERT(xPrintable.is());
315 uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
316 { { "FileName", uno::Any(maTempFile.GetURL()) }, { "Wait", uno::Any(true) } }));
317 xPrintable->print(aOptions);
319 // Parse the export result with pdfium.
320 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
321 if (!pPdfDocument)
322 // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
323 // running, there is no printer defined, etc.
324 return;
326 // The document has one page.
327 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
328 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
329 CPPUNIT_ASSERT(pPdfPage);
331 // Make sure there is no filled rectangle inside.
332 int nPageObjectCount = pPdfPage->getObjectCount();
333 int nWhitePathCount = 0;
334 for (int i = 0; i < nPageObjectCount; ++i)
336 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
337 if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
338 continue;
340 if (pPdfPageObject->getFillColor() == COL_WHITE)
341 ++nWhitePathCount;
344 // This was 4, the page contained 4 white paths at problematic positions.
345 CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount);
346 #endif
349 /// Tests that embedded video from Impress is not exported as a linked one.
350 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105093)
352 vcl::filter::PDFDocument aDocument;
353 load(u"tdf105093.odp", aDocument);
355 // The document has one page.
356 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
357 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
359 // Get page annotations.
360 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
361 CPPUNIT_ASSERT(pAnnots);
362 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
363 auto pAnnotReference
364 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
365 CPPUNIT_ASSERT(pAnnotReference);
366 vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
367 CPPUNIT_ASSERT(pAnnot);
368 CPPUNIT_ASSERT_EQUAL(
369 "Annot"_ostr,
370 static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
372 // Get the Action -> Rendition -> MediaClip -> FileSpec.
373 auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
374 CPPUNIT_ASSERT(pAction);
375 auto pRendition
376 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAction->LookupElement("R"_ostr));
377 CPPUNIT_ASSERT(pRendition);
378 auto pMediaClip
379 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pRendition->LookupElement("C"_ostr));
380 CPPUNIT_ASSERT(pMediaClip);
381 auto pFileSpec
382 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pMediaClip->LookupElement("D"_ostr));
383 CPPUNIT_ASSERT(pFileSpec);
384 // Make sure the filespec refers to an embedded file.
385 // This key was missing, the embedded video was handled as a linked one.
386 CPPUNIT_ASSERT(pFileSpec->LookupElement("EF"_ostr));
389 /// Tests export of non-PDF images.
390 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106206)
392 // Import the bugdoc and export as PDF.
393 vcl::filter::PDFDocument aDocument;
394 load(u"tdf106206.odt", aDocument);
396 // The document has one page.
397 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
398 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
400 // The page has a stream.
401 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
402 CPPUNIT_ASSERT(pContents);
403 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
404 CPPUNIT_ASSERT(pStream);
405 SvMemoryStream& rObjectStream = pStream->GetMemory();
406 // Uncompress it.
407 SvMemoryStream aUncompressed;
408 ZCodec aZCodec;
409 aZCodec.BeginCompression();
410 rObjectStream.Seek(0);
411 aZCodec.Decompress(rObjectStream, aUncompressed);
412 CPPUNIT_ASSERT(aZCodec.EndCompression());
414 // Make sure there is an image reference there.
415 OString aImage("/Im"_ostr);
416 auto pStart = static_cast<const char*>(aUncompressed.GetData());
417 const char* pEnd = pStart + aUncompressed.GetSize();
418 auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
419 CPPUNIT_ASSERT(it != pEnd);
421 // And also that it's not an invalid one.
422 OString aInvalidImage("/Im0"_ostr);
423 it = std::search(pStart, pEnd, aInvalidImage.getStr(),
424 aInvalidImage.getStr() + aInvalidImage.getLength());
425 // This failed, object #0 was referenced.
426 CPPUNIT_ASSERT(bool(it == pEnd));
429 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf127217)
431 // Import the bugdoc and export as PDF.
432 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
433 { "ExportFormFields", uno::Any(true) },
434 }));
435 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
436 saveAsPDF(u"tdf127217.odt");
438 // Parse the export result with pdfium.
439 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
441 // The document has one page.
442 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
443 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
444 CPPUNIT_ASSERT(pPdfPage);
446 // The page has one annotation.
447 CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
448 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
450 // Without the fix in place, this test would have failed here
451 CPPUNIT_ASSERT(!pAnnot->hasKey("DA"_ostr));
454 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142741)
456 // Import the doc and export as PDF.
457 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
458 { "ExportFormFields", uno::Any(true) },
459 }));
460 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
461 saveAsPDF(u"tdf142741.odt");
463 // Parse the export result with pdfium.
464 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
466 CPPUNIT_ASSERT_EQUAL(2,
467 pPdfDocument->getPageCount()); // To ensure that exported pdf has 2 pages
469 for (int pageNum = 0; pageNum < 2; ++pageNum)
471 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(pageNum);
472 CPPUNIT_ASSERT(pPdfPage);
474 CPPUNIT_ASSERT_EQUAL(1,
475 pPdfPage->getAnnotationCount()); // Expect only one annotation per page
476 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPdfPage->getAnnotation(0);
477 CPPUNIT_ASSERT(pAnnotation);
479 // Check if annotation is a link and has the expected content, here "1" (both in first page content and footnote of second page)
480 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Link, pAnnotation->getSubType());
481 OUString content = pAnnotation->getString(vcl::pdf::constDictionaryKeyContents);
482 CPPUNIT_ASSERT_EQUAL(u"1"_ustr, content);
486 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf109143)
488 // Import the bugdoc and export as PDF.
489 vcl::filter::PDFDocument aDocument;
490 load(u"tdf109143.odt", aDocument);
492 // The document has one page.
493 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
494 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
496 // Get access to the only image on the only page.
497 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
498 CPPUNIT_ASSERT(pResources);
499 auto pXObjects
500 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
501 CPPUNIT_ASSERT(pXObjects);
502 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
503 vcl::filter::PDFObjectElement* pXObject
504 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
505 CPPUNIT_ASSERT(pXObject);
507 // Make sure it's re-compressed.
508 auto pLength = dynamic_cast<vcl::filter::PDFNumberElement*>(pXObject->Lookup("Length"_ostr));
509 CPPUNIT_ASSERT(pLength);
510 int nLength = pLength->GetValue();
511 // This failed: cropped TIFF-in-JPEG wasn't re-compressed, so crop was
512 // lost. Size was 59416, now is 11827.
513 CPPUNIT_ASSERT(nLength < 50000);
516 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972)
518 // Import the bugdoc and export as PDF.
519 vcl::filter::PDFDocument aDocument;
520 load(u"tdf106972.odt", aDocument);
522 // Get access to the only form object on the only page.
523 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
524 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
525 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
526 CPPUNIT_ASSERT(pResources);
527 auto pXObjects
528 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
529 CPPUNIT_ASSERT(pXObjects);
530 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
531 vcl::filter::PDFObjectElement* pXObject
532 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
533 CPPUNIT_ASSERT(pXObject);
535 // Get access to the only image inside the form object.
536 auto pFormResources
537 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
538 CPPUNIT_ASSERT(pFormResources);
539 auto pImages = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
540 pFormResources->LookupElement("XObject"_ostr));
541 CPPUNIT_ASSERT(pImages);
542 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pImages->GetItems().size());
543 vcl::filter::PDFObjectElement* pImage
544 = pImages->LookupObject(pImages->GetItems().begin()->first);
545 CPPUNIT_ASSERT(pImage);
547 // Assert resources of the image.
548 auto pImageResources
549 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pImage->Lookup("Resources"_ostr));
550 CPPUNIT_ASSERT(pImageResources);
551 // This failed: the PDF image had no Font resource.
552 CPPUNIT_ASSERT(pImageResources->LookupElement("Font"_ostr));
555 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972Pdf17)
557 // Import the bugdoc and export as PDF.
558 vcl::filter::PDFDocument aDocument;
559 load(u"tdf106972-pdf17.odt", aDocument);
561 // Get access to the only image on the only page.
562 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
563 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
564 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
565 CPPUNIT_ASSERT(pResources);
566 auto pXObjects
567 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
568 CPPUNIT_ASSERT(pXObjects);
569 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
570 vcl::filter::PDFObjectElement* pXObject
571 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
572 CPPUNIT_ASSERT(pXObject);
574 // Assert that we now attempt to preserve the original PDF data, even if
575 // the original input was PDF >= 1.4.
576 CPPUNIT_ASSERT(pXObject->Lookup("Resources"_ostr));
579 CPPUNIT_TEST_FIXTURE(PdfExportTest, testSofthyphenPos)
581 // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
582 // which is the intent of the test.
583 // FIXME: Why does this fail on macOS?
584 #if !defined MACOSX && !defined _WIN32
586 // Import the bugdoc and print to PDF.
587 loadFromFile(u"softhyphen_pdf.odt");
588 uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
589 CPPUNIT_ASSERT(xPrintable.is());
590 uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
591 { { "FileName", uno::Any(maTempFile.GetURL()) }, { "Wait", uno::Any(true) } }));
592 xPrintable->print(aOptions);
594 // Parse the export result with pdfium.
595 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
596 if (!pPdfDocument)
597 // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
598 // running, there is no printer defined, etc.
599 return;
601 // The document has one page.
602 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
603 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
604 CPPUNIT_ASSERT(pPdfPage);
606 // tdf#96892 incorrect fractional part of font size caused soft-hyphen to
607 // be positioned inside preceding text (incorrect = 11.1, correct = 11.05)
609 // there are 3 texts currently, for line 1, soft-hyphen, line 2
610 bool haveText(false);
612 int nPageObjectCount = pPdfPage->getObjectCount();
613 for (int i = 0; i < nPageObjectCount; ++i)
615 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
616 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pPdfPageObject->getType());
617 haveText = true;
618 double const size = pPdfPageObject->getFontSize();
619 CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06);
622 CPPUNIT_ASSERT(haveText);
623 #endif
626 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107013)
628 vcl::filter::PDFDocument aDocument;
629 load(u"tdf107013.odt", aDocument);
631 // Get access to the only image on the only page.
632 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
633 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
634 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
635 CPPUNIT_ASSERT(pResources);
636 auto pXObjects
637 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
638 CPPUNIT_ASSERT(pXObjects);
639 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
640 vcl::filter::PDFObjectElement* pXObject
641 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
642 // This failed, the reference to the image was created, but not the image.
643 CPPUNIT_ASSERT(pXObject);
646 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107018)
648 vcl::filter::PDFDocument aDocument;
649 load(u"tdf107018.odt", aDocument);
651 // Get access to the only image on the only page.
652 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
653 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
654 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
655 CPPUNIT_ASSERT(pResources);
656 auto pXObjects
657 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
658 CPPUNIT_ASSERT(pXObjects);
659 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
660 vcl::filter::PDFObjectElement* pXObject
661 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
662 CPPUNIT_ASSERT(pXObject);
664 // Get access to the form object inside the image.
665 auto pXObjectResources
666 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
667 CPPUNIT_ASSERT(pXObjectResources);
668 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
669 pXObjectResources->LookupElement("XObject"_ostr));
670 CPPUNIT_ASSERT(pXObjectForms);
671 vcl::filter::PDFObjectElement* pForm
672 = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
673 CPPUNIT_ASSERT(pForm);
675 // Get access to Resources -> Font -> F1 of the form.
676 auto pFormResources
677 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pForm->Lookup("Resources"_ostr));
678 CPPUNIT_ASSERT(pFormResources);
679 auto pFonts = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
680 pFormResources->LookupElement("Font"_ostr));
681 CPPUNIT_ASSERT(pFonts);
682 auto pF1Ref = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFonts->LookupElement("F1"_ostr));
683 CPPUNIT_ASSERT(pF1Ref);
684 vcl::filter::PDFObjectElement* pF1 = pF1Ref->LookupObject();
685 CPPUNIT_ASSERT(pF1);
687 // Check that Foo -> Bar of the font is of type Pages.
688 auto pFontFoo = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pF1->Lookup("Foo"_ostr));
689 CPPUNIT_ASSERT(pFontFoo);
690 auto pBar
691 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFontFoo->LookupElement("Bar"_ostr));
692 CPPUNIT_ASSERT(pBar);
693 vcl::filter::PDFObjectElement* pObject = pBar->LookupObject();
694 CPPUNIT_ASSERT(pObject);
695 auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
696 CPPUNIT_ASSERT(pName);
697 // This was "XObject", reference in a nested dictionary wasn't updated when
698 // copying the page stream of a PDF image.
699 CPPUNIT_ASSERT_EQUAL("Pages"_ostr, pName->GetValue());
702 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf148706)
704 // Import the bugdoc and export as PDF.
705 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
706 { "ExportFormFields", uno::Any(true) },
707 }));
708 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
709 saveAsPDF(u"tdf148706.odt");
711 // Parse the export result with pdfium.
712 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
714 // The document has one page.
715 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
716 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
717 CPPUNIT_ASSERT(pPdfPage);
719 // The page has one annotation.
720 CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
721 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
723 CPPUNIT_ASSERT(pAnnot->hasKey("V"_ostr));
724 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("V"_ostr));
725 OUString aV = pAnnot->getString("V"_ostr);
727 // Without the fix in place, this test would have failed with
728 // - Expected: 1821.84
729 // - Actual :
730 CPPUNIT_ASSERT_EQUAL(u"1821.84"_ustr, aV);
732 CPPUNIT_ASSERT(pAnnot->hasKey("DV"_ostr));
733 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("DV"_ostr));
734 OUString aDV = pAnnot->getString("DV"_ostr);
736 CPPUNIT_ASSERT_EQUAL(u"1821.84"_ustr, aDV);
739 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107089)
741 vcl::filter::PDFDocument aDocument;
742 load(u"tdf107089.odt", aDocument);
744 // Get access to the only image on the only page.
745 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
746 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
747 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
748 CPPUNIT_ASSERT(pResources);
749 auto pXObjects
750 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
751 CPPUNIT_ASSERT(pXObjects);
752 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
753 vcl::filter::PDFObjectElement* pXObject
754 = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
755 CPPUNIT_ASSERT(pXObject);
757 // Get access to the form object inside the image.
758 auto pXObjectResources
759 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
760 CPPUNIT_ASSERT(pXObjectResources);
761 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
762 pXObjectResources->LookupElement("XObject"_ostr));
763 CPPUNIT_ASSERT(pXObjectForms);
764 vcl::filter::PDFObjectElement* pForm
765 = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
766 CPPUNIT_ASSERT(pForm);
768 // Make sure 'Hello' is part of the form object's stream.
769 vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
770 CPPUNIT_ASSERT(pStream);
771 SvMemoryStream aObjectStream;
772 ZCodec aZCodec;
773 aZCodec.BeginCompression();
774 pStream->GetMemory().Seek(0);
775 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
776 CPPUNIT_ASSERT(aZCodec.EndCompression());
777 aObjectStream.Seek(0);
778 OString aHello("Hello"_ostr);
779 auto pStart = static_cast<const char*>(aObjectStream.GetData());
780 const char* pEnd = pStart + aObjectStream.GetSize();
781 auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength());
782 // This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage.
783 CPPUNIT_ASSERT(it != pEnd);
786 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680)
788 vcl::filter::PDFDocument aDocument;
789 load(u"tdf99680.odt", aDocument);
791 // The document has one page.
792 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
793 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
795 // The page 1 has a stream.
796 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
797 CPPUNIT_ASSERT(pContents);
798 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
799 CPPUNIT_ASSERT(pStream);
800 SvMemoryStream& rObjectStream = pStream->GetMemory();
802 // Uncompress it.
803 SvMemoryStream aUncompressed;
804 ZCodec aZCodec;
805 aZCodec.BeginCompression();
806 rObjectStream.Seek(0);
807 aZCodec.Decompress(rObjectStream, aUncompressed);
808 CPPUNIT_ASSERT(aZCodec.EndCompression());
810 // tdf#130150 See infos in task - short: tdf#99680 was not the
811 // correct fix, so empty clip regions are valid - allow again in tests
812 // Make sure there are no empty clipping regions.
813 // OString aEmptyRegion("0 0 m h W* n");
814 // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
815 // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
817 // Count save graphic state (q) and restore (Q) operators
818 // and ensure their amount is equal
819 auto pStart = static_cast<const char*>(aUncompressed.GetData());
820 const char* pEnd = pStart + aUncompressed.GetSize();
821 size_t nSaveCount = std::count(pStart, pEnd, 'q');
822 size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
823 CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount,
824 nRestoreCount);
827 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680_2)
829 vcl::filter::PDFDocument aDocument;
830 load(u"tdf99680-2.odt", aDocument);
832 // For each document page
833 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
834 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
835 for (size_t nPageNr = 0; nPageNr < aPages.size(); nPageNr++)
837 // Get page contents and stream.
838 vcl::filter::PDFObjectElement* pContents = aPages[nPageNr]->LookupObject("Contents"_ostr);
839 CPPUNIT_ASSERT(pContents);
840 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
841 CPPUNIT_ASSERT(pStream);
842 SvMemoryStream& rObjectStream = pStream->GetMemory();
844 // Uncompress the stream.
845 SvMemoryStream aUncompressed;
846 ZCodec aZCodec;
847 aZCodec.BeginCompression();
848 rObjectStream.Seek(0);
849 aZCodec.Decompress(rObjectStream, aUncompressed);
850 CPPUNIT_ASSERT(aZCodec.EndCompression());
852 // tdf#130150 See infos in task - short: tdf#99680 was not the
853 // correct fix, so empty clip regions are valid - allow again in tests
854 // Make sure there are no empty clipping regions.
855 // OString aEmptyRegion("0 0 m h W* n");
856 // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
857 // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
859 // Count save graphic state (q) and restore (Q) operators
860 // and ensure their amount is equal
861 auto pStart = static_cast<const char*>(aUncompressed.GetData());
862 const char* pEnd = pStart + aUncompressed.GetSize();
863 size_t nSaveCount = std::count(pStart, pEnd, 'q');
864 size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
865 CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!",
866 nSaveCount, nRestoreCount);
870 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf108963)
872 // Import the bugdoc and export as PDF.
873 saveAsPDF(u"tdf108963.odp");
875 // Parse the export result with pdfium.
876 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
878 // The document has one page.
879 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
880 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
881 CPPUNIT_ASSERT(pPdfPage);
883 // Test page size (28x15.75 cm, was 1/100th mm off, tdf#112690)
884 // bad: MediaBox[0 0 793.672440944882 446.428346456693]
885 // good: MediaBox[0 0 793.700787401575 446.456692913386]
886 const double aWidth = pPdfPage->getWidth();
887 CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01);
888 const double aHeight = pPdfPage->getHeight();
889 CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01);
891 // Make sure there is a filled rectangle inside.
892 int nPageObjectCount = pPdfPage->getObjectCount();
893 int nYellowPathCount = 0;
894 for (int i = 0; i < nPageObjectCount; ++i)
896 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
897 if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
898 continue;
900 if (pPdfPageObject->getFillColor() == COL_YELLOW)
902 ++nYellowPathCount;
903 // The path described a yellow rectangle, but it was not rotated.
904 int nSegments = pPdfPageObject->getPathSegmentCount();
905 CPPUNIT_ASSERT_EQUAL(5, nSegments);
906 std::unique_ptr<vcl::pdf::PDFiumPathSegment> pSegment
907 = pPdfPageObject->getPathSegment(0);
908 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Moveto, pSegment->getType());
909 basegfx::B2DPoint aPoint = pSegment->getPoint();
910 CPPUNIT_ASSERT_DOUBLES_EQUAL(245, aPoint.getX(), 0.999);
911 CPPUNIT_ASSERT_DOUBLES_EQUAL(244, aPoint.getY(), 0.999);
912 CPPUNIT_ASSERT(!pSegment->isClosed());
914 pSegment = pPdfPageObject->getPathSegment(1);
915 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
916 aPoint = pSegment->getPoint();
917 CPPUNIT_ASSERT_DOUBLES_EQUAL(275, aPoint.getX(), 0.999);
918 CPPUNIT_ASSERT_DOUBLES_EQUAL(267, aPoint.getY(), 0.999);
919 CPPUNIT_ASSERT(!pSegment->isClosed());
921 pSegment = pPdfPageObject->getPathSegment(2);
922 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
923 aPoint = pSegment->getPoint();
924 CPPUNIT_ASSERT_DOUBLES_EQUAL(287, aPoint.getX(), 0.999);
925 CPPUNIT_ASSERT_DOUBLES_EQUAL(251, aPoint.getY(), 0.999);
926 CPPUNIT_ASSERT(!pSegment->isClosed());
928 pSegment = pPdfPageObject->getPathSegment(3);
929 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
930 aPoint = pSegment->getPoint();
931 CPPUNIT_ASSERT_DOUBLES_EQUAL(257, aPoint.getX(), 0.999);
932 CPPUNIT_ASSERT_DOUBLES_EQUAL(228, aPoint.getY(), 0.999);
933 CPPUNIT_ASSERT(!pSegment->isClosed());
935 pSegment = pPdfPageObject->getPathSegment(4);
936 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
937 aPoint = pSegment->getPoint();
938 CPPUNIT_ASSERT_DOUBLES_EQUAL(245, aPoint.getX(), 0.999);
939 CPPUNIT_ASSERT_DOUBLES_EQUAL(244, aPoint.getY(), 0.999);
940 CPPUNIT_ASSERT(pSegment->isClosed());
944 CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
947 CPPUNIT_TEST_FIXTURE(PdfExportTest, testAlternativeText)
949 vcl::filter::PDFDocument aDocument;
950 load(u"alternativeText.fodp", aDocument);
952 // The document has one page.
953 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
954 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
956 for (const auto& aElement : aDocument.GetElements())
958 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
959 if (!pObject)
960 continue;
961 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
962 if (pType && pType->GetValue() == "StructElem")
964 auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
965 if (pS && pS->GetValue() == "Figure")
967 CPPUNIT_ASSERT_EQUAL(u"This is the text alternative - This is the description"_ustr,
968 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
969 *dynamic_cast<vcl::filter::PDFHexStringElement*>(
970 pObject->Lookup("Alt"_ostr))));
975 // tdf#67866 check that Catalog contains Lang
976 auto* pCatalog = aDocument.GetCatalog();
977 CPPUNIT_ASSERT(pCatalog);
978 auto* pCatalogDictionary = pCatalog->GetDictionary();
979 CPPUNIT_ASSERT(pCatalogDictionary);
980 auto pLang = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
981 pCatalogDictionary->LookupElement("Lang"_ostr));
982 CPPUNIT_ASSERT(pLang);
983 CPPUNIT_ASSERT_EQUAL("en-US"_ostr, pLang->GetValue());
986 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105972)
988 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
989 { "ExportFormFields", uno::Any(true) },
990 }));
991 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
992 vcl::filter::PDFDocument aDocument;
993 load(u"tdf105972.fodt", aDocument);
995 // The document has one page.
996 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
997 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
999 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
1000 CPPUNIT_ASSERT(pAnnots);
1001 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pAnnots->GetElements().size());
1003 sal_uInt32 nTextFieldCount = 0;
1004 for (const auto& aElement : aDocument.GetElements())
1006 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1007 if (!pObject)
1008 continue;
1009 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"_ostr));
1010 if (pType && pType->GetValue() == "Tx")
1012 ++nTextFieldCount;
1014 auto pT
1015 = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pObject->Lookup("T"_ostr));
1016 CPPUNIT_ASSERT(pT);
1017 auto pAA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("AA"_ostr));
1018 CPPUNIT_ASSERT(pAA);
1019 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pAA->GetItems().size());
1020 auto pF
1021 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAA->LookupElement("F"_ostr));
1022 CPPUNIT_ASSERT(pF);
1023 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pF->GetItems().size());
1025 if (nTextFieldCount == 1)
1027 CPPUNIT_ASSERT_EQUAL("CurrencyField"_ostr, pT->GetValue());
1029 auto pJS = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
1030 pF->LookupElement("JS"_ostr));
1031 CPPUNIT_ASSERT_EQUAL("AFNumber_Format\\(4, 0, 0, 0, \"\\\\u20ac\",true\\);"_ostr,
1032 pJS->GetValue());
1034 else if (nTextFieldCount == 2)
1036 CPPUNIT_ASSERT_EQUAL("TimeField"_ostr, pT->GetValue());
1038 auto pJS = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
1039 pF->LookupElement("JS"_ostr));
1040 CPPUNIT_ASSERT_EQUAL("AFTime_FormatEx\\(\"h:MM:sstt\"\\);"_ostr, pJS->GetValue());
1042 else
1044 CPPUNIT_ASSERT_EQUAL("DateField"_ostr, pT->GetValue());
1046 auto pJS = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
1047 pF->LookupElement("JS"_ostr));
1048 CPPUNIT_ASSERT_EQUAL("AFDate_FormatEx\\(\"yy-mm-dd\"\\);"_ostr, pJS->GetValue());
1054 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf148442)
1056 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
1057 { "ExportFormFields", uno::Any(true) },
1058 }));
1059 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
1061 vcl::filter::PDFDocument aDocument;
1062 load(u"tdf148442.odt", aDocument);
1064 // The document has one page.
1065 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1066 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1068 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
1069 CPPUNIT_ASSERT(pAnnots);
1070 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pAnnots->GetElements().size());
1072 sal_uInt32 nBtnCount = 0;
1073 for (const auto& aElement : aDocument.GetElements())
1075 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1076 if (!pObject)
1077 continue;
1078 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"_ostr));
1079 if (pType && pType->GetValue() == "Btn")
1081 ++nBtnCount;
1082 auto pT
1083 = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pObject->Lookup("T"_ostr));
1084 CPPUNIT_ASSERT(pT);
1085 auto pAS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("AS"_ostr));
1086 CPPUNIT_ASSERT(pAS);
1088 auto pAP = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("AP"_ostr));
1089 CPPUNIT_ASSERT(pAP);
1090 auto pN
1091 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAP->LookupElement("N"_ostr));
1092 CPPUNIT_ASSERT(pN);
1093 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pN->GetItems().size());
1095 if (nBtnCount == 1)
1097 CPPUNIT_ASSERT_EQUAL("Checkbox1"_ostr, pT->GetValue());
1098 CPPUNIT_ASSERT_EQUAL("Yes"_ostr, pAS->GetValue());
1099 CPPUNIT_ASSERT(!pN->GetItems().count("ref"_ostr));
1100 CPPUNIT_ASSERT(pN->GetItems().count("Yes"_ostr));
1101 CPPUNIT_ASSERT(pN->GetItems().count("Off"_ostr));
1103 else if (nBtnCount == 2)
1105 CPPUNIT_ASSERT_EQUAL("Checkbox2"_ostr, pT->GetValue());
1106 CPPUNIT_ASSERT_EQUAL("Yes"_ostr, pAS->GetValue());
1108 // Without the fix in place, this test would have failed here
1109 CPPUNIT_ASSERT(pN->GetItems().count("ref"_ostr));
1110 CPPUNIT_ASSERT(!pN->GetItems().count("Yes"_ostr));
1111 CPPUNIT_ASSERT(pN->GetItems().count("Off"_ostr));
1113 else
1115 CPPUNIT_ASSERT_EQUAL("Checkbox3"_ostr, pT->GetValue());
1116 CPPUNIT_ASSERT_EQUAL("Off"_ostr, pAS->GetValue());
1117 CPPUNIT_ASSERT(pN->GetItems().count("ref"_ostr));
1118 CPPUNIT_ASSERT(!pN->GetItems().count("Yes"_ostr));
1120 // tdf#143612: Without the fix in place, this test would have failed here
1121 CPPUNIT_ASSERT(!pN->GetItems().count("Off"_ostr));
1122 CPPUNIT_ASSERT(pN->GetItems().count("refOff"_ostr));
1128 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf118244_radioButtonGroup)
1130 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
1131 { "ExportFormFields", uno::Any(true) },
1132 }));
1133 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
1135 vcl::filter::PDFDocument aDocument;
1136 load(u"tdf118244_radioButtonGroup.odt", aDocument);
1138 // The document has one page.
1139 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1140 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1142 // There are eight radio buttons.
1143 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
1144 CPPUNIT_ASSERT(pAnnots);
1145 CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio buttons", static_cast<size_t>(8),
1146 pAnnots->GetElements().size());
1148 sal_uInt32 nRadioGroups = 0;
1149 for (const auto& aElement : aDocument.GetElements())
1151 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1152 if (!pObject)
1153 continue;
1154 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"_ostr));
1155 if (pType && pType->GetValue() == "Btn")
1157 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("Kids"_ostr));
1158 if (pKids)
1160 size_t expectedSize = 2;
1161 ++nRadioGroups;
1162 if (nRadioGroups == 3)
1163 expectedSize = 3;
1164 CPPUNIT_ASSERT_EQUAL(expectedSize, pKids->GetElements().size());
1168 CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio groups", sal_uInt32(3), nRadioGroups);
1171 /// Test writing ToUnicode CMAP for LTR ligatures.
1172 // This requires Carlito font, if it is missing the test will most likely
1173 // fail.
1174 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1)
1176 #if HAVE_MORE_FONTS
1177 vcl::filter::PDFDocument aDocument;
1178 load(u"tdf115117-1.odt", aDocument);
1180 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1182 // Get access to ToUnicode of the first font
1183 for (const auto& aElement : aDocument.GetElements())
1185 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1186 if (!pObject)
1187 continue;
1188 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
1189 if (pType && pType->GetValue() == "Font")
1191 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
1192 pObject->Lookup("ToUnicode"_ostr));
1193 CPPUNIT_ASSERT(pToUnicodeRef);
1194 pToUnicode = pToUnicodeRef->LookupObject();
1195 break;
1199 CPPUNIT_ASSERT(pToUnicode);
1200 auto pStream = pToUnicode->GetStream();
1201 CPPUNIT_ASSERT(pStream);
1202 SvMemoryStream aObjectStream;
1203 ZCodec aZCodec;
1204 aZCodec.BeginCompression();
1205 pStream->GetMemory().Seek(0);
1206 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1207 CPPUNIT_ASSERT(aZCodec.EndCompression());
1208 aObjectStream.Seek(0);
1209 // The first values, <01> <02> etc., are glyph ids, they might change order
1210 // if we changed how font subsets are created.
1211 // The second values, <00740069> etc., are Unicode code points in hex,
1212 // <00740069> is U+0074 and U+0069 i.e. "ti" which is a ligature in
1213 // Carlito/Calibri. This test is failing if any of the second values
1214 // changed which means we are not detecting ligatures and writing CMAP
1215 // entries for them correctly. If glyph order in the subset changes then
1216 // the order here will changes and the PDF has to be carefully inspected to
1217 // ensure that the new values are correct before updating the string below.
1218 OString aCmap("9 beginbfchar\n"
1219 "<01> <00740069>\n"
1220 "<02> <0020>\n"
1221 "<03> <0074>\n"
1222 "<04> <0065>\n"
1223 "<05> <0073>\n"
1224 "<06> <00660069>\n"
1225 "<07> <0066006C>\n"
1226 "<08> <006600660069>\n"
1227 "<09> <00660066006C>\n"
1228 "endbfchar"_ostr);
1229 auto pStart = static_cast<const char*>(aObjectStream.GetData());
1230 const char* pEnd = pStart + aObjectStream.GetSize();
1231 auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
1232 CPPUNIT_ASSERT(it != pEnd);
1233 #endif
1236 /// Test writing ToUnicode CMAP for RTL ligatures.
1237 // This requires DejaVu Sans font, if it is missing the test will most likely
1238 // fail.
1239 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2)
1241 #if HAVE_MORE_FONTS
1242 // See the comments in testTdf115117_1() for explanation.
1244 vcl::filter::PDFDocument aDocument;
1245 load(u"tdf115117-2.odt", aDocument);
1247 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1249 for (const auto& aElement : aDocument.GetElements())
1251 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1252 if (!pObject)
1253 continue;
1254 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
1255 if (pType && pType->GetValue() == "Font")
1257 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
1258 pObject->Lookup("ToUnicode"_ostr));
1259 CPPUNIT_ASSERT(pToUnicodeRef);
1260 pToUnicode = pToUnicodeRef->LookupObject();
1261 break;
1265 CPPUNIT_ASSERT(pToUnicode);
1266 auto pStream = pToUnicode->GetStream();
1267 CPPUNIT_ASSERT(pStream);
1268 SvMemoryStream aObjectStream;
1269 ZCodec aZCodec;
1270 aZCodec.BeginCompression();
1271 pStream->GetMemory().Seek(0);
1272 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1273 CPPUNIT_ASSERT(aZCodec.EndCompression());
1274 aObjectStream.Seek(0);
1275 OString aCmap("7 beginbfchar\n"
1276 "<01> <06440627>\n"
1277 "<02> <0020>\n"
1278 "<03> <0641>\n"
1279 "<04> <0642>\n"
1280 "<05> <0648>\n"
1281 "<06> <06440627>\n"
1282 "<07> <0628>\n"
1283 "endbfchar"_ostr);
1284 auto pStart = static_cast<const char*>(aObjectStream.GetData());
1285 const char* pEnd = pStart + aObjectStream.GetSize();
1286 auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
1287 CPPUNIT_ASSERT(it != pEnd);
1288 #endif
1291 /// Text extracting LTR text with ligatures.
1292 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1a)
1294 #if HAVE_MORE_FONTS
1295 // Import the bugdoc and export as PDF.
1296 saveAsPDF(u"tdf115117-1.odt");
1298 // Parse the export result with pdfium.
1299 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1301 // The document has one page.
1302 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1303 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1304 CPPUNIT_ASSERT(pPdfPage);
1306 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1307 CPPUNIT_ASSERT(pPdfTextPage);
1309 // Extract the text from the page. This pdfium API is a bit higher level
1310 // than we want and might apply heuristic that give false positive, but it
1311 // is a good approximation in addition to the check in testTdf115117_1().
1312 int nChars = pPdfTextPage->countChars();
1313 CPPUNIT_ASSERT_EQUAL(44, nChars);
1315 std::vector<sal_uInt32> aChars(nChars);
1316 for (int i = 0; i < nChars; i++)
1317 aChars[i] = pPdfTextPage->getUnicode(i);
1318 OUString aActualText(aChars.data(), aChars.size());
1319 CPPUNIT_ASSERT_EQUAL(u"ti ti test ti\r\nti test fi fl ffi ffl test fi"_ustr, aActualText);
1320 #endif
1323 /// Test extracting RTL text with ligatures.
1324 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2a)
1326 #if HAVE_MORE_FONTS
1327 // See the comments in testTdf115117_1a() for explanation.
1329 // Import the bugdoc and export as PDF.
1330 saveAsPDF(u"tdf115117-2.odt");
1332 // Parse the export result with pdfium.
1333 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1335 // The document has one page.
1336 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1337 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1338 CPPUNIT_ASSERT(pPdfPage);
1340 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1341 CPPUNIT_ASSERT(pPdfTextPage);
1343 int nChars = pPdfTextPage->countChars();
1344 CPPUNIT_ASSERT_EQUAL(13, nChars);
1346 std::vector<sal_uInt32> aChars(nChars);
1347 for (int i = 0; i < nChars; i++)
1348 aChars[i] = pPdfTextPage->getUnicode(i);
1349 OUString aActualText(aChars.data(), aChars.size());
1350 CPPUNIT_ASSERT_EQUAL(u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644"_ustr,
1351 aActualText);
1352 #endif
1355 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf154549)
1357 // FIXME: On Windows, the number of chars is 4 instead of 3
1358 #ifndef _WIN32
1359 saveAsPDF(u"tdf154549.odt");
1361 // Parse the export result with pdfium.
1362 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1364 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1365 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1366 CPPUNIT_ASSERT(pPdfPage);
1368 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1369 CPPUNIT_ASSERT(pPdfTextPage);
1371 int nChars = pPdfTextPage->countChars();
1373 CPPUNIT_ASSERT_EQUAL(1, nChars);
1375 std::vector<sal_uInt32> aChars(nChars);
1376 for (int i = 0; i < nChars; i++)
1377 aChars[i] = pPdfTextPage->getUnicode(i);
1378 OUString aActualText(aChars.data(), aChars.size());
1380 // Without the fix in place, this test would have failed with
1381 // - Expected: ִبي
1382 // - Actual : بִي
1383 CPPUNIT_ASSERT_EQUAL(u"\u064a"_ustr, aActualText);
1384 #endif
1387 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf150846)
1389 // Without the fix in place, this test would have failed with
1390 // An uncaught exception of type com.sun.star.io.IOException
1391 saveAsPDF(u"tdf150846.txt");
1393 // Parse the export result with pdfium.
1394 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1396 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1397 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1398 CPPUNIT_ASSERT(pPdfPage);
1400 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1401 CPPUNIT_ASSERT(pPdfTextPage);
1403 int nChars = pPdfTextPage->countChars();
1405 CPPUNIT_ASSERT_EQUAL(5, nChars);
1407 std::vector<sal_uInt32> aChars(nChars);
1408 for (int i = 0; i < nChars; i++)
1409 aChars[i] = pPdfTextPage->getUnicode(i);
1410 OUString aActualText(aChars.data(), aChars.size());
1411 CPPUNIT_ASSERT_EQUAL(u"hello"_ustr, aActualText);
1414 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf160401)
1416 saveAsPDF(u"tdf160401.pptx");
1418 // Parse the export result with pdfium.
1419 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1421 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1422 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1423 CPPUNIT_ASSERT(pPdfPage);
1425 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1426 CPPUNIT_ASSERT(pPdfTextPage);
1428 int nChars = pPdfTextPage->countChars();
1430 CPPUNIT_ASSERT_EQUAL(16, nChars);
1432 std::vector<sal_uInt32> aChars(nChars);
1433 for (int i = 0; i < nChars; i++)
1434 aChars[i] = pPdfTextPage->getUnicode(i);
1435 OUString aActualText(aChars.data(), aChars.size());
1437 // Without the fix in place, this test would have failed with
1438 // - Expected: אאא בבב
1439 // אאא בבב
1440 // - Actual : אאא בבב
1441 // בבב אאא
1442 CPPUNIT_ASSERT_EQUAL(u"אאא בבב\r\nאאא בבב"_ustr, aActualText);
1445 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf103492)
1447 // Import the bugdoc and export as PDF.
1448 saveAsPDF(u"tdf103492.odt");
1450 // Parse the export result with pdfium.
1451 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1453 // The document has two page.
1454 CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
1455 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage1 = pPdfDocument->openPage(/*nIndex=*/0);
1456 CPPUNIT_ASSERT(pPdfPage1);
1458 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage1 = pPdfPage1->getTextPage();
1459 CPPUNIT_ASSERT(pPdfTextPage1);
1461 int nChars1 = pPdfTextPage1->countChars();
1463 // Without the fix in place, this test would have failed with
1464 // - Expected: 15
1465 // - Actual : 18
1466 CPPUNIT_ASSERT_EQUAL(15, nChars1);
1468 std::vector<sal_uInt32> aChars1(nChars1);
1469 for (int i = 0; i < nChars1; i++)
1470 aChars1[i] = pPdfTextPage1->getUnicode(i);
1471 OUString aActualText1(aChars1.data(), aChars1.size());
1472 CPPUNIT_ASSERT_EQUAL(u"يوسف My name is"_ustr, aActualText1);
1474 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1);
1475 CPPUNIT_ASSERT(pPdfPage2);
1477 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage2 = pPdfPage2->getTextPage();
1478 CPPUNIT_ASSERT(pPdfTextPage2);
1480 int nChars2 = pPdfTextPage2->countChars();
1482 CPPUNIT_ASSERT_EQUAL(15, nChars2);
1484 std::vector<sal_uInt32> aChars2(nChars2);
1485 for (int i = 0; i < nChars2; i++)
1486 aChars2[i] = pPdfTextPage2->getUnicode(i);
1487 OUString aActualText2(aChars2.data(), aChars2.size());
1488 CPPUNIT_ASSERT_EQUAL(u"My name is يوسف"_ustr, aActualText2);
1491 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf145274)
1493 // Import the bugdoc and export as PDF.
1494 saveAsPDF(u"tdf145274.docx");
1496 // Parse the export result with pdfium.
1497 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1499 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1501 auto pPage = pPdfDocument->openPage(0);
1502 CPPUNIT_ASSERT(pPage);
1504 int nPageObjectCount = pPage->getObjectCount();
1506 // Without the fix in place, this test would have failed with
1507 // - Expected: 6
1508 // - Actual : 4
1509 CPPUNIT_ASSERT_EQUAL(6, nPageObjectCount);
1511 auto pTextPage = pPage->getTextPage();
1513 for (int i = 0; i < nPageObjectCount; ++i)
1515 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
1516 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
1517 continue;
1519 CPPUNIT_ASSERT_EQUAL(11.0, pPageObject->getFontSize());
1520 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFTextRenderMode::Fill, pPageObject->getTextRenderMode());
1521 CPPUNIT_ASSERT_EQUAL(COL_RED, pPageObject->getFillColor());
1525 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf156685)
1527 // Import the bugdoc and export as PDF.
1528 saveAsPDF(u"tdf156685.docx");
1530 // Parse the export result with pdfium.
1531 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1533 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1535 auto pPage = pPdfDocument->openPage(0);
1536 CPPUNIT_ASSERT(pPage);
1538 int nPageObjectCount = pPage->getObjectCount();
1540 CPPUNIT_ASSERT_EQUAL(9, nPageObjectCount);
1542 auto pTextPage = pPage->getTextPage();
1544 for (int i = 0; i < nPageObjectCount; ++i)
1546 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
1547 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
1548 continue;
1550 CPPUNIT_ASSERT_EQUAL(11.0, pPageObject->getFontSize());
1551 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFTextRenderMode::Fill, pPageObject->getTextRenderMode());
1553 // Without the fix in place, this test would have failed with
1554 // - Expected: rgba[000000ff]
1555 // - Actual : rgba[ffffffff]
1556 CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPageObject->getFillColor());
1560 /// Test writing ToUnicode CMAP for doubly encoded glyphs.
1561 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_1)
1563 #if HAVE_MORE_FONTS
1564 // This requires Amiri font, if it is missing the test will fail.
1565 vcl::filter::PDFDocument aDocument;
1566 load(u"tdf66597-1.odt", aDocument);
1569 // Get access to ToUnicode of the first font
1570 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1571 for (const auto& aElement : aDocument.GetElements())
1573 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1574 if (!pObject)
1575 continue;
1576 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
1577 if (pType && pType->GetValue() == "Font")
1579 auto pName
1580 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
1581 auto aName = pName->GetValue().copy(7); // skip the subset id
1582 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", "Amiri-Regular"_ostr, aName);
1584 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
1585 pObject->Lookup("ToUnicode"_ostr));
1586 CPPUNIT_ASSERT(pToUnicodeRef);
1587 pToUnicode = pToUnicodeRef->LookupObject();
1588 break;
1592 CPPUNIT_ASSERT(pToUnicode);
1593 auto pStream = pToUnicode->GetStream();
1594 CPPUNIT_ASSERT(pStream);
1595 SvMemoryStream aObjectStream;
1596 ZCodec aZCodec;
1597 aZCodec.BeginCompression();
1598 pStream->GetMemory().Seek(0);
1599 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1600 CPPUNIT_ASSERT(aZCodec.EndCompression());
1601 aObjectStream.Seek(0);
1602 // The <01> is glyph id, <2044> is code point.
1603 // The document has two characters <2044><2215><2044>, but the font
1604 // reuses the same glyph for U+2044 and U+2215 so we should have a single
1605 // CMAP entry for the U+2044, and U+2215 will be handled with ActualText
1606 // (tested below).
1607 std::string aCmap("1 beginbfchar\n"
1608 "<01> <2044>\n"
1609 "endbfchar");
1610 std::string aData(static_cast<const char*>(aObjectStream.GetData()),
1611 aObjectStream.GetSize());
1612 auto nPos = aData.find(aCmap);
1613 CPPUNIT_ASSERT(nPos != std::string::npos);
1617 auto aPages = aDocument.GetPages();
1618 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1619 // Get page contents and stream.
1620 auto pContents = aPages[0]->LookupObject("Contents"_ostr);
1621 CPPUNIT_ASSERT(pContents);
1622 auto pStream = pContents->GetStream();
1623 CPPUNIT_ASSERT(pStream);
1624 auto& rObjectStream = pStream->GetMemory();
1626 // Uncompress the stream.
1627 SvMemoryStream aUncompressed;
1628 ZCodec aZCodec;
1629 aZCodec.BeginCompression();
1630 rObjectStream.Seek(0);
1631 aZCodec.Decompress(rObjectStream, aUncompressed);
1632 CPPUNIT_ASSERT(aZCodec.EndCompression());
1634 // Make sure the expected ActualText is present.
1635 std::string aData(static_cast<const char*>(aUncompressed.GetData()),
1636 aUncompressed.GetSize());
1638 std::string aActualText("/Span<</ActualText<");
1639 size_t nCount = 0;
1640 size_t nPos = 0;
1641 while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1643 nCount++;
1644 nPos += aActualText.length();
1646 CPPUNIT_ASSERT_EQUAL_MESSAGE("The should be one ActualText entry!", static_cast<size_t>(1),
1647 nCount);
1649 aActualText = "/Span<</ActualText<FEFF2215>>>";
1650 nPos = aData.find(aActualText);
1651 CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos);
1653 #endif
1656 /// Test writing ActualText for RTL many to one glyph to Unicode mapping.
1657 // This requires Reem Kufi font, if it is missing the test will fail.
1658 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_2)
1660 #if HAVE_MORE_FONTS
1661 vcl::filter::PDFDocument aDocument;
1662 load(u"tdf66597-2.odt", aDocument);
1665 // Get access to ToUnicode of the first font
1666 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1667 for (const auto& aElement : aDocument.GetElements())
1669 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1670 if (!pObject)
1671 continue;
1672 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
1673 if (pType && pType->GetValue() == "Font")
1675 auto pName
1676 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
1677 auto aName = pName->GetValue().copy(7); // skip the subset id
1678 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", "ReemKufi-Regular"_ostr,
1679 aName);
1681 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
1682 pObject->Lookup("ToUnicode"_ostr));
1683 CPPUNIT_ASSERT(pToUnicodeRef);
1684 pToUnicode = pToUnicodeRef->LookupObject();
1685 break;
1689 CPPUNIT_ASSERT(pToUnicode);
1690 auto pStream = pToUnicode->GetStream();
1691 CPPUNIT_ASSERT(pStream);
1692 SvMemoryStream aObjectStream;
1693 ZCodec aZCodec;
1694 aZCodec.BeginCompression();
1695 pStream->GetMemory().Seek(0);
1696 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1697 CPPUNIT_ASSERT(aZCodec.EndCompression());
1698 aObjectStream.Seek(0);
1699 std::string aCmap("8 beginbfchar\n"
1700 "<02> <0632>\n"
1701 "<03> <0020>\n"
1702 "<04> <0648>\n"
1703 "<05> <0647>\n"
1704 "<06> <062F>\n"
1705 "<08> <062C>\n"
1706 "<0A> <0628>\n"
1707 "<0C> <0623>\n"
1708 "endbfchar");
1709 std::string aData(static_cast<const char*>(aObjectStream.GetData()),
1710 aObjectStream.GetSize());
1711 auto nPos = aData.find(aCmap);
1712 CPPUNIT_ASSERT(nPos != std::string::npos);
1716 auto aPages = aDocument.GetPages();
1717 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1718 // Get page contents and stream.
1719 auto pContents = aPages[0]->LookupObject("Contents"_ostr);
1720 CPPUNIT_ASSERT(pContents);
1721 auto pStream = pContents->GetStream();
1722 CPPUNIT_ASSERT(pStream);
1723 auto& rObjectStream = pStream->GetMemory();
1725 // Uncompress the stream.
1726 SvMemoryStream aUncompressed;
1727 ZCodec aZCodec;
1728 aZCodec.BeginCompression();
1729 rObjectStream.Seek(0);
1730 aZCodec.Decompress(rObjectStream, aUncompressed);
1731 CPPUNIT_ASSERT(aZCodec.EndCompression());
1733 // Make sure the expected ActualText is present.
1734 std::string aData(static_cast<const char*>(aUncompressed.GetData()),
1735 aUncompressed.GetSize());
1737 std::vector<std::string> aCodes({ "0632", "062C", "0628", "0623" });
1738 std::string aActualText("/Span<</ActualText<");
1739 size_t nCount = 0;
1740 size_t nPos = 0;
1741 while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1743 nCount++;
1744 nPos += aActualText.length();
1746 CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", aCodes.size(),
1747 nCount);
1749 for (const auto& aCode : aCodes)
1751 aActualText = "/Span<</ActualText<FEFF" + aCode + ">>>";
1752 nPos = aData.find(aActualText);
1753 CPPUNIT_ASSERT_MESSAGE("ActualText not found for " + aCode, nPos != std::string::npos);
1756 #endif
1759 /// Test writing ActualText for LTR many to one glyph to Unicode mapping.
1760 // This requires Gentium Basic font, if it is missing the test will fail.
1761 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_3)
1763 #if HAVE_MORE_FONTS
1764 vcl::filter::PDFDocument aDocument;
1765 load(u"tdf66597-3.odt", aDocument);
1768 // Get access to ToUnicode of the first font
1769 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1770 for (const auto& aElement : aDocument.GetElements())
1772 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1773 if (!pObject)
1774 continue;
1775 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
1776 if (pType && pType->GetValue() == "Font")
1778 auto pName
1779 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
1780 auto aName = pName->GetValue().copy(7); // skip the subset id
1781 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", "GentiumBasic"_ostr, aName);
1783 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
1784 pObject->Lookup("ToUnicode"_ostr));
1785 CPPUNIT_ASSERT(pToUnicodeRef);
1786 pToUnicode = pToUnicodeRef->LookupObject();
1787 break;
1791 CPPUNIT_ASSERT(pToUnicode);
1792 auto pStream = pToUnicode->GetStream();
1793 CPPUNIT_ASSERT(pStream);
1794 SvMemoryStream aObjectStream;
1795 ZCodec aZCodec;
1796 aZCodec.BeginCompression();
1797 pStream->GetMemory().Seek(0);
1798 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1799 CPPUNIT_ASSERT(aZCodec.EndCompression());
1800 aObjectStream.Seek(0);
1801 std::string aCmap("2 beginbfchar\n"
1802 "<01> <1ECB0331030B>\n"
1803 "<05> <0020>\n"
1804 "endbfchar");
1805 std::string aData(static_cast<const char*>(aObjectStream.GetData()),
1806 aObjectStream.GetSize());
1807 auto nPos = aData.find(aCmap);
1808 CPPUNIT_ASSERT(nPos != std::string::npos);
1812 auto aPages = aDocument.GetPages();
1813 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1814 // Get page contents and stream.
1815 auto pContents = aPages[0]->LookupObject("Contents"_ostr);
1816 CPPUNIT_ASSERT(pContents);
1817 auto pStream = pContents->GetStream();
1818 CPPUNIT_ASSERT(pStream);
1819 auto& rObjectStream = pStream->GetMemory();
1821 // Uncompress the stream.
1822 SvMemoryStream aUncompressed;
1823 ZCodec aZCodec;
1824 aZCodec.BeginCompression();
1825 rObjectStream.Seek(0);
1826 aZCodec.Decompress(rObjectStream, aUncompressed);
1827 CPPUNIT_ASSERT(aZCodec.EndCompression());
1829 // Make sure the expected ActualText is present.
1830 std::string aData(static_cast<const char*>(aUncompressed.GetData()),
1831 aUncompressed.GetSize());
1833 std::string aActualText("/Span<</ActualText<FEFF1ECB0331030B>>>");
1834 size_t nCount = 0;
1835 size_t nPos = 0;
1836 while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1838 nCount++;
1839 nPos += aActualText.length();
1841 CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!",
1842 static_cast<size_t>(4), nCount);
1844 #endif
1847 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105954)
1849 // Import the bugdoc and export as PDF.
1850 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
1851 { { "ReduceImageResolution", uno::Any(true) },
1852 { "MaxImageResolution", uno::Any(static_cast<sal_Int32>(300)) } }));
1853 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
1854 saveAsPDF(u"tdf105954.odt");
1856 // Parse the export result with pdfium.
1857 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1859 // The document has one page.
1860 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1861 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1862 CPPUNIT_ASSERT(pPdfPage);
1864 // There is a single image on the page.
1865 int nPageObjectCount = pPdfPage->getObjectCount();
1866 CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount);
1868 // Check width of the image.
1869 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(/*index=*/0);
1870 Size aMeta = pPageObject->getImageSize(*pPdfPage);
1871 // This was 2000, i.e. the 'reduce to 300 DPI' request was ignored.
1872 // This is now around 238 (228 on macOS).
1873 CPPUNIT_ASSERT_LESS(static_cast<tools::Long>(250), aMeta.getWidth());
1876 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157679)
1878 // Import the bugdoc and export as PDF.
1879 saveAsPDF(u"tdf157679.pptx");
1880 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1882 // The document has one page.
1883 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1885 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1886 CPPUNIT_ASSERT(pPdfPage);
1888 // Without the fix in place, this test would have failed with
1889 // - Expected: 3
1890 // - Actual : 5
1891 CPPUNIT_ASSERT_EQUAL(3, pPdfPage->getObjectCount());
1893 std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
1894 int nPageObjectCount = pPdfPage->getObjectCount();
1895 for (int i = 0; i < nPageObjectCount; ++i)
1897 // Check there are not Text objects
1898 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1899 CPPUNIT_ASSERT(pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text);
1903 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128445)
1905 // Import the bugdoc and export as PDF.
1906 saveAsPDF(u"tdf128445.odp");
1907 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1909 // The document has one page.
1910 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1912 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1913 CPPUNIT_ASSERT(pPdfPage);
1915 // Without the fix in place, this test would have failed with
1916 // - Expected: 7
1917 // - Actual : 6
1918 CPPUNIT_ASSERT_EQUAL(7, pPdfPage->getObjectCount());
1921 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128630)
1923 // FIXME: the DPI check should be removed when either (1) the test is fixed to work with
1924 // non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
1925 if (!IsDefaultDPI())
1926 return;
1928 // Import the bugdoc and export as PDF.
1929 saveAsPDF(u"tdf128630.odp");
1930 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1932 // The document has one page.
1933 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1935 // Assert the size of the only bitmap on the page.
1936 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1937 CPPUNIT_ASSERT(pPdfPage);
1938 int nPageObjectCount = pPdfPage->getObjectCount();
1939 for (int i = 0; i < nPageObjectCount; ++i)
1941 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1942 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1943 continue;
1945 std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
1946 CPPUNIT_ASSERT(pBitmap);
1947 int nWidth = pBitmap->getWidth();
1948 // Without the accompanying fix in place, this test would have failed with:
1949 // - Expected: 466
1950 // - Actual : 289
1951 // i.e. the rotated + scaled arrow was more thin than it should be.
1952 CPPUNIT_ASSERT_DOUBLES_EQUAL(466, nWidth, 1);
1953 int nHeight = pBitmap->getHeight();
1954 CPPUNIT_ASSERT_EQUAL(nWidth, nHeight);
1958 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106702)
1960 // Import the bugdoc and export as PDF.
1961 saveAsPDF(u"tdf106702.odt");
1963 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1965 // The document has two pages.
1966 CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
1968 // First page already has the correct image position.
1969 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1970 CPPUNIT_ASSERT(pPdfPage);
1971 int nExpected = 0;
1972 int nPageObjectCount = pPdfPage->getObjectCount();
1973 for (int i = 0; i < nPageObjectCount; ++i)
1975 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1976 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1977 continue;
1979 // Top, but upside down.
1980 nExpected = pPageObject->getBounds().getMaxY();
1981 break;
1984 // Second page had an incorrect image position.
1985 pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
1986 CPPUNIT_ASSERT(pPdfPage);
1987 int nActual = 0;
1988 nPageObjectCount = pPdfPage->getObjectCount();
1989 for (int i = 0; i < nPageObjectCount; ++i)
1991 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1992 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1993 continue;
1995 // Top, but upside down.
1996 nActual = pPageObject->getBounds().getMaxY();
1997 break;
2000 // This failed, vertical pos is 818 points, was 1674 (outside visible page
2001 // bounds).
2002 CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
2005 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf113143)
2007 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
2008 { "ExportNotesPages", uno::Any(true) },
2009 // ReduceImageResolution is on by default and that hides the bug we
2010 // want to test.
2011 { "ReduceImageResolution", uno::Any(false) },
2012 // Set a custom PDF version.
2013 { "SelectPdfVersion", uno::Any(static_cast<sal_Int32>(16)) },
2014 }));
2015 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
2016 saveAsPDF(u"tdf113143.odp");
2018 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
2020 // The document has two pages.
2021 CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
2023 // First has the original (larger) image.
2024 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2025 CPPUNIT_ASSERT(pPdfPage);
2026 int nLarger = 0;
2027 int nPageObjectCount = pPdfPage->getObjectCount();
2028 for (int i = 0; i < nPageObjectCount; ++i)
2030 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
2031 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
2032 continue;
2034 nLarger = pPageObject->getBounds().getWidth();
2035 break;
2038 // Second page has the scaled (smaller) image.
2039 pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
2040 CPPUNIT_ASSERT(pPdfPage);
2041 int nSmaller = 0;
2042 nPageObjectCount = pPdfPage->getObjectCount();
2043 for (int i = 0; i < nPageObjectCount; ++i)
2045 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
2046 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
2047 continue;
2049 nSmaller = pPageObject->getBounds().getWidth();
2050 break;
2053 // This failed, both were 319, now nSmaller is 169.
2054 CPPUNIT_ASSERT_LESS(nLarger, nSmaller);
2056 // The following check used to fail in the past, header was "%PDF-1.5":
2057 maMemory.Seek(0);
2058 OString aExpectedHeader("%PDF-1.6"_ostr);
2059 OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength()));
2060 CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader);
2063 CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint71)
2065 // I just care it doesn't crash
2066 // numerous Zip errors are detected now and libetonyek cannot RepairPackage
2067 CPPUNIT_ASSERT_ASSERTION_FAIL(loadFromFile(u"forcepoint71.key"));
2070 CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint80)
2072 // printing asserted in SwCellFrame::FindStartEndOfRowSpanCell
2073 saveAsPDF(u"forcepoint80-1.rtf");
2076 CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint3)
2078 // printing asserted in SwFrame::GetNextSctLeaf()
2079 saveAsPDF(u"flowframe_null_ptr_deref.sample");
2082 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf162586)
2084 // Without the fix in place, this test would have crashed
2085 saveAsPDF(u"tdf162586.odt");
2088 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf84283)
2090 // Without the fix in place, this test would have crashed
2091 saveAsPDF(u"tdf84283.doc");
2094 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115262)
2096 saveAsPDF(u"tdf115262.ods");
2097 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
2098 CPPUNIT_ASSERT_EQUAL(8, pPdfDocument->getPageCount());
2100 // Get the 6th page.
2101 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/5);
2102 CPPUNIT_ASSERT(pPdfPage);
2104 // Look up the position of the first image and the 400th row.
2105 std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
2106 int nPageObjectCount = pPdfPage->getObjectCount();
2107 int nFirstImageTop = 0;
2108 int nRowTop = 0;
2109 for (int i = 0; i < nPageObjectCount; ++i)
2111 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
2112 // Top, but upside down.
2113 float fTop = pPageObject->getBounds().getMaxY();
2115 if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Image)
2117 nFirstImageTop = fTop;
2119 else if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
2121 OUString sText = pPageObject->getText(pTextPage);
2122 if (sText == "400")
2123 nRowTop = fTop;
2126 // Make sure that the top of the "400" is below the top of the image (in
2127 // bottom-right-corner-based PDF coordinates).
2128 // This was: expected less than 144, actual is 199.
2129 CPPUNIT_ASSERT_LESS(nFirstImageTop, nRowTop);
2132 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf121962)
2134 saveAsPDF(u"tdf121962.odt");
2135 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
2136 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2138 // Get the first page
2139 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2140 CPPUNIT_ASSERT(pPdfPage);
2141 std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
2143 // Make sure the table sum is displayed as "0", not faulty expression.
2144 int nPageObjectCount = pPdfPage->getObjectCount();
2145 for (int i = 0; i < nPageObjectCount; ++i)
2147 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
2148 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
2149 continue;
2150 OUString sText = pPageObject->getText(pTextPage);
2151 CPPUNIT_ASSERT(sText != "** Expression is faulty **");
2155 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf139065)
2157 saveAsPDF(u"tdf139065.odt");
2158 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
2159 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2160 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2161 CPPUNIT_ASSERT(pPdfPage);
2163 // Without the fix in place, this test would have failed with
2164 // - Expected: 15
2165 // - Actual : 6
2166 CPPUNIT_ASSERT_EQUAL(15, pPdfPage->getObjectCount());
2169 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816)
2171 // Enable PDF/UA
2172 uno::Sequence<beans::PropertyValue> aFilterData(
2173 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
2174 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
2176 vcl::filter::PDFDocument aDocument;
2177 load(u"tdf157816.fodt", aDocument);
2179 // The document has one page.
2180 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2181 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
2183 vcl::filter::PDFObjectElement* pDocument(nullptr);
2184 for (const auto& rDocElement : aDocument.GetElements())
2186 auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
2187 if (!pObject1)
2188 continue;
2189 auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
2190 if (pType1 && pType1->GetValue() == "StructElem")
2192 auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
2193 if (pS1 && pS1->GetValue() == "Document")
2195 pDocument = pObject1;
2199 CPPUNIT_ASSERT(pDocument);
2201 auto pKidsD = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
2202 CPPUNIT_ASSERT(pKidsD);
2203 // assume there are no MCID ref at this level
2204 auto pKidsDv = pKidsD->GetElements();
2205 auto pRefKidD2 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[2]);
2206 CPPUNIT_ASSERT(pRefKidD2);
2207 auto pObjectD2 = pRefKidD2->LookupObject();
2208 CPPUNIT_ASSERT(pObjectD2);
2209 auto pTypeD2 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD2->Lookup("Type"_ostr));
2210 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD2->GetValue());
2211 auto pSD2 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD2->Lookup("S"_ostr));
2212 CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pSD2->GetValue());
2214 auto pKidsD2 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD2->Lookup("K"_ostr));
2215 CPPUNIT_ASSERT(pKidsD2);
2216 auto pKidsD2v = pKidsD2->GetElements();
2217 auto pRefKidD20 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[0]);
2218 // MCID for text
2219 CPPUNIT_ASSERT(!pRefKidD20);
2220 auto pRefKidD21 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[1]);
2221 // MCID for text
2222 CPPUNIT_ASSERT(!pRefKidD21);
2224 auto pRefKidD22 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[2]);
2225 CPPUNIT_ASSERT(pRefKidD22);
2226 auto pObjectD22 = pRefKidD22->LookupObject();
2227 CPPUNIT_ASSERT(pObjectD22);
2228 auto pTypeD22 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD22->Lookup("Type"_ostr));
2229 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD22->GetValue());
2230 auto pSD22 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD22->Lookup("S"_ostr));
2231 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD22->GetValue());
2233 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD22->Lookup("K"_ostr));
2234 auto nMCID(0);
2235 auto nRef(0);
2236 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2238 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2239 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2240 if (pNum)
2242 ++nMCID;
2244 if (pObjR)
2246 ++nRef;
2247 auto pOType
2248 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2249 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2250 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2251 pObjR->LookupElement("Obj"_ostr));
2252 auto pAnnot = pAnnotRef->LookupObject();
2253 auto pAType
2254 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2255 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2256 auto pASubtype
2257 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2258 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2259 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2260 pAnnot->Lookup("Contents"_ostr));
2261 CPPUNIT_ASSERT_EQUAL(
2262 u"Error: Reference source not found"_ustr,
2263 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2264 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2265 pAnnot->Lookup("StructParent"_ostr));
2266 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2267 auto pARect
2268 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2269 CPPUNIT_ASSERT(pARect);
2270 const auto& rElements = pARect->GetElements();
2271 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2272 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2273 CPPUNIT_ASSERT(pNumL);
2274 CPPUNIT_ASSERT_DOUBLES_EQUAL(95.143, pNumL->GetValue(), 1e-3);
2275 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2276 CPPUNIT_ASSERT(pNumT);
2277 CPPUNIT_ASSERT_DOUBLES_EQUAL(674.589, pNumT->GetValue(), 1e-3);
2278 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2279 CPPUNIT_ASSERT(pNumR);
2280 // this changed to the end of the text, not the start of the fly
2281 CPPUNIT_ASSERT_DOUBLES_EQUAL(187.157, pNumR->GetValue(), 1e-3);
2282 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2283 CPPUNIT_ASSERT(pNumB);
2284 CPPUNIT_ASSERT_DOUBLES_EQUAL(688.389, pNumB->GetValue(), 1e-3);
2287 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2288 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2291 auto pRefKidD23 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[3]);
2292 CPPUNIT_ASSERT(pRefKidD23);
2293 auto pObjectD23 = pRefKidD23->LookupObject();
2294 CPPUNIT_ASSERT(pObjectD23);
2295 auto pTypeD23 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD23->Lookup("Type"_ostr));
2296 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD23->GetValue());
2297 auto pSD23 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD23->Lookup("S"_ostr));
2298 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD23->GetValue());
2300 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD23->Lookup("K"_ostr));
2301 auto nMCID(0);
2302 auto nRef(0);
2303 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2305 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2306 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2307 if (pNum)
2309 ++nMCID;
2311 if (pObjR)
2313 ++nRef;
2314 auto pOType
2315 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2316 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2317 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2318 pObjR->LookupElement("Obj"_ostr));
2319 auto pAnnot = pAnnotRef->LookupObject();
2320 auto pAType
2321 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2322 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2323 auto pASubtype
2324 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2325 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2326 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2327 pAnnot->Lookup("Contents"_ostr));
2328 CPPUNIT_ASSERT_EQUAL(
2329 u"Error: Reference source not found"_ustr,
2330 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2331 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2332 pAnnot->Lookup("StructParent"_ostr));
2333 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2334 auto pARect
2335 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2336 CPPUNIT_ASSERT(pARect);
2337 const auto& rElements = pARect->GetElements();
2338 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2339 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2340 CPPUNIT_ASSERT(pNumL);
2341 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
2342 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2343 CPPUNIT_ASSERT(pNumT);
2344 CPPUNIT_ASSERT_DOUBLES_EQUAL(660.789, pNumT->GetValue(), 1e-3);
2345 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2346 CPPUNIT_ASSERT(pNumR);
2347 CPPUNIT_ASSERT_DOUBLES_EQUAL(146.107, pNumR->GetValue(), 1e-3);
2348 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2349 CPPUNIT_ASSERT(pNumB);
2350 CPPUNIT_ASSERT_DOUBLES_EQUAL(674.589, pNumB->GetValue(), 1e-3);
2353 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2354 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2357 auto pRefKidD24 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[4]);
2358 CPPUNIT_ASSERT(pRefKidD24);
2359 auto pObjectD24 = pRefKidD24->LookupObject();
2360 CPPUNIT_ASSERT(pObjectD24);
2361 auto pTypeD24 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD24->Lookup("Type"_ostr));
2362 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD24->GetValue());
2363 auto pSD24 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD24->Lookup("S"_ostr));
2364 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD24->GetValue());
2366 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD24->Lookup("K"_ostr));
2367 auto nMCID(0);
2368 auto nRef(0);
2369 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2371 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2372 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2373 if (pNum)
2375 ++nMCID;
2377 if (pObjR)
2379 ++nRef;
2380 auto pOType
2381 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2382 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2383 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2384 pObjR->LookupElement("Obj"_ostr));
2385 auto pAnnot = pAnnotRef->LookupObject();
2386 auto pAType
2387 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2388 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2389 auto pASubtype
2390 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2391 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2392 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2393 pAnnot->Lookup("Contents"_ostr));
2394 CPPUNIT_ASSERT_EQUAL(
2395 u"Error: Reference source not found"_ustr,
2396 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2397 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2398 pAnnot->Lookup("StructParent"_ostr));
2399 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2400 auto pARect
2401 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2402 CPPUNIT_ASSERT(pARect);
2403 const auto& rElements = pARect->GetElements();
2404 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2405 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2406 CPPUNIT_ASSERT(pNumL);
2407 CPPUNIT_ASSERT_DOUBLES_EQUAL(146.043, pNumL->GetValue(), 1e-3);
2408 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2409 CPPUNIT_ASSERT(pNumT);
2410 CPPUNIT_ASSERT_DOUBLES_EQUAL(660.789, pNumT->GetValue(), 1e-3);
2411 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2412 CPPUNIT_ASSERT(pNumR);
2413 CPPUNIT_ASSERT_DOUBLES_EQUAL(179.357, pNumR->GetValue(), 1e-3);
2414 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2415 CPPUNIT_ASSERT(pNumB);
2416 CPPUNIT_ASSERT_DOUBLES_EQUAL(674.589, pNumB->GetValue(), 1e-3);
2419 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2420 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2423 auto pRefKidD25 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[5]);
2424 CPPUNIT_ASSERT(pRefKidD25);
2425 auto pObjectD25 = pRefKidD25->LookupObject();
2426 CPPUNIT_ASSERT(pObjectD25);
2427 auto pTypeD25 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD25->Lookup("Type"_ostr));
2428 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD25->GetValue());
2429 auto pSD25 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD25->Lookup("S"_ostr));
2430 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD25->GetValue());
2432 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD25->Lookup("K"_ostr));
2433 auto nMCID(0);
2434 auto nRef(0);
2435 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2437 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2438 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2439 if (pNum)
2441 ++nMCID;
2443 if (pObjR)
2445 ++nRef;
2446 auto pOType
2447 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2448 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2449 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2450 pObjR->LookupElement("Obj"_ostr));
2451 auto pAnnot = pAnnotRef->LookupObject();
2452 auto pAType
2453 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2454 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2455 auto pASubtype
2456 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2457 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2458 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2459 pAnnot->Lookup("Contents"_ostr));
2460 CPPUNIT_ASSERT_EQUAL(
2461 u"Error: Reference source not found"_ustr,
2462 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2463 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2464 pAnnot->Lookup("StructParent"_ostr));
2465 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2466 auto pARect
2467 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2468 CPPUNIT_ASSERT(pARect);
2469 const auto& rElements = pARect->GetElements();
2470 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2471 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2472 CPPUNIT_ASSERT(pNumL);
2473 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
2474 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2475 CPPUNIT_ASSERT(pNumT);
2476 CPPUNIT_ASSERT_DOUBLES_EQUAL(646.989, pNumT->GetValue(), 1e-3);
2477 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2478 CPPUNIT_ASSERT(pNumR);
2479 CPPUNIT_ASSERT_DOUBLES_EQUAL(174.757, pNumR->GetValue(), 1e-3);
2480 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2481 CPPUNIT_ASSERT(pNumB);
2482 CPPUNIT_ASSERT_DOUBLES_EQUAL(660.789, pNumB->GetValue(), 1e-3);
2485 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2486 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2489 auto pRefKidD26 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[6]);
2490 CPPUNIT_ASSERT(pRefKidD26);
2491 auto pObjectD26 = pRefKidD26->LookupObject();
2492 CPPUNIT_ASSERT(pObjectD26);
2493 auto pTypeD26 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD26->Lookup("Type"_ostr));
2494 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD26->GetValue());
2495 auto pSD26 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD26->Lookup("S"_ostr));
2496 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD26->GetValue());
2498 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD26->Lookup("K"_ostr));
2499 auto nMCID(0);
2500 auto nRef(0);
2501 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2503 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2504 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2505 if (pNum)
2507 ++nMCID;
2509 if (pObjR)
2511 ++nRef;
2512 auto pOType
2513 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2514 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2515 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2516 pObjR->LookupElement("Obj"_ostr));
2517 auto pAnnot = pAnnotRef->LookupObject();
2518 auto pAType
2519 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2520 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2521 auto pASubtype
2522 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2523 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2524 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2525 pAnnot->Lookup("Contents"_ostr));
2526 CPPUNIT_ASSERT_EQUAL(
2527 u"Error: Reference source not found"_ustr,
2528 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2529 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2530 pAnnot->Lookup("StructParent"_ostr));
2531 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2532 auto pARect
2533 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2534 CPPUNIT_ASSERT(pARect);
2535 const auto& rElements = pARect->GetElements();
2536 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2537 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2538 CPPUNIT_ASSERT(pNumL);
2539 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
2540 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2541 CPPUNIT_ASSERT(pNumT);
2542 CPPUNIT_ASSERT_DOUBLES_EQUAL(633.189, pNumT->GetValue(), 1e-3);
2543 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2544 CPPUNIT_ASSERT(pNumR);
2545 CPPUNIT_ASSERT_DOUBLES_EQUAL(86.757, pNumR->GetValue(), 1e-3);
2546 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2547 CPPUNIT_ASSERT(pNumB);
2548 CPPUNIT_ASSERT_DOUBLES_EQUAL(646.989, pNumB->GetValue(), 1e-3);
2551 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2552 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2555 auto pRefKidD27 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD2v[7]);
2556 // MCID for text
2557 CPPUNIT_ASSERT(!pRefKidD27);
2559 // the problem was that in addition to the 5 links with SE there were 3 more
2560 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
2561 CPPUNIT_ASSERT(pAnnots);
2562 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), pAnnots->GetElements().size());
2565 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link)
2567 // Enable PDF/UA
2568 uno::Sequence<beans::PropertyValue> aFilterData(
2569 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
2570 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
2572 vcl::filter::PDFDocument aDocument;
2573 load(u"LinkWithFly.fodt", aDocument);
2575 // The document has one page.
2576 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2577 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
2579 vcl::filter::PDFObjectElement* pDocument(nullptr);
2580 for (const auto& rDocElement : aDocument.GetElements())
2582 auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
2583 if (!pObject1)
2584 continue;
2585 auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
2586 if (pType1 && pType1->GetValue() == "StructElem")
2588 auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
2589 if (pS1 && pS1->GetValue() == "Document")
2591 pDocument = pObject1;
2595 CPPUNIT_ASSERT(pDocument);
2597 auto pKidsD = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
2598 CPPUNIT_ASSERT(pKidsD);
2599 // assume there are no MCID ref at this level
2600 auto pKidsDv = pKidsD->GetElements();
2601 auto pRefKidD0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[0]);
2602 CPPUNIT_ASSERT(pRefKidD0);
2603 auto pObjectD0 = pRefKidD0->LookupObject();
2604 CPPUNIT_ASSERT(pObjectD0);
2605 auto pTypeD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("Type"_ostr));
2606 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD0->GetValue());
2607 auto pSD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("S"_ostr));
2608 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pSD0->GetValue());
2610 auto pKidsD0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD0->Lookup("K"_ostr));
2611 CPPUNIT_ASSERT(pKidsD0);
2612 auto pKidsD0v = pKidsD0->GetElements();
2614 auto pRefKidD00 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[0]);
2615 CPPUNIT_ASSERT(pRefKidD00);
2616 auto pObjectD00 = pRefKidD00->LookupObject();
2617 CPPUNIT_ASSERT(pObjectD00);
2618 auto pTypeD00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD00->Lookup("Type"_ostr));
2619 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue());
2620 auto pSD00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD00->Lookup("S"_ostr));
2621 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue());
2623 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD00->Lookup("K"_ostr));
2624 auto nMCID(0);
2625 auto nRef(0);
2626 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2628 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2629 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2630 if (pNum)
2632 ++nMCID;
2634 if (pObjR)
2636 ++nRef;
2637 auto pOType
2638 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2639 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2640 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2641 pObjR->LookupElement("Obj"_ostr));
2642 auto pAnnot = pAnnotRef->LookupObject();
2643 auto pAType
2644 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2645 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2646 auto pASubtype
2647 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2648 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2649 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2650 pAnnot->Lookup("Contents"_ostr));
2651 CPPUNIT_ASSERT_EQUAL(
2652 u"https://www.mozilla.org/en-US/firefox/119.0/releasenotes/"_ustr,
2653 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2654 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2655 pAnnot->Lookup("StructParent"_ostr));
2656 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2657 auto pARect
2658 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2659 CPPUNIT_ASSERT(pARect);
2660 const auto& rElements = pARect->GetElements();
2661 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2662 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2663 CPPUNIT_ASSERT(pNumL);
2664 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
2665 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2666 CPPUNIT_ASSERT(pNumT);
2667 CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3);
2668 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2669 CPPUNIT_ASSERT(pNumR);
2670 // this changed to the end of the text, not the start of the fly
2671 CPPUNIT_ASSERT_DOUBLES_EQUAL(191.657, pNumR->GetValue(), 1e-3);
2672 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2673 CPPUNIT_ASSERT(pNumB);
2674 CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3);
2677 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2678 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2681 auto pRefKidD01 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[1]);
2682 CPPUNIT_ASSERT(pRefKidD01);
2683 auto pObjectD01 = pRefKidD01->LookupObject();
2684 CPPUNIT_ASSERT(pObjectD01);
2685 auto pTypeD01 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD01->Lookup("Type"_ostr));
2686 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD01->GetValue());
2687 auto pSD01 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD01->Lookup("S"_ostr));
2688 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue());
2690 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD01->Lookup("K"_ostr));
2691 auto nMCID(0);
2692 auto nRef(0);
2693 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2695 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2696 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2697 if (pNum)
2699 ++nMCID;
2701 if (pObjR)
2703 ++nRef;
2704 auto pOType
2705 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2706 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2707 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2708 pObjR->LookupElement("Obj"_ostr));
2709 auto pAnnot = pAnnotRef->LookupObject();
2710 auto pAType
2711 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2712 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2713 auto pASubtype
2714 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2715 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2716 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2717 pAnnot->Lookup("Contents"_ostr));
2718 CPPUNIT_ASSERT_EQUAL(
2719 u"https://www.mozilla.org/en-US/firefox/119.0/releasenotes/"_ustr,
2720 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2721 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2722 pAnnot->Lookup("StructParent"_ostr));
2723 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2724 auto pARect
2725 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2726 CPPUNIT_ASSERT(pARect);
2727 const auto& rElements = pARect->GetElements();
2728 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2729 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2730 CPPUNIT_ASSERT(pNumL);
2731 CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3);
2732 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2733 CPPUNIT_ASSERT(pNumT);
2734 CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3);
2735 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2736 CPPUNIT_ASSERT(pNumR);
2737 // this changed to the end of the text, not the start of the fly
2738 CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3);
2739 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2740 CPPUNIT_ASSERT(pNumB);
2741 CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3);
2744 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2745 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2748 auto pRefKidD02 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[2]);
2749 CPPUNIT_ASSERT(pRefKidD02);
2750 auto pObjectD02 = pRefKidD02->LookupObject();
2751 CPPUNIT_ASSERT(pObjectD02);
2752 auto pTypeD02 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD02->Lookup("Type"_ostr));
2753 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue());
2754 auto pSD02 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD02->Lookup("S"_ostr));
2755 CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pSD02->GetValue());
2757 auto pRefKidD1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[1]);
2758 CPPUNIT_ASSERT(pRefKidD1);
2759 auto pObjectD1 = pRefKidD1->LookupObject();
2760 CPPUNIT_ASSERT(pObjectD1);
2761 auto pTypeD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("Type"_ostr));
2762 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD1->GetValue());
2763 auto pSD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("S"_ostr));
2764 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pSD1->GetValue());
2766 auto pKidsD1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD1->Lookup("K"_ostr));
2767 CPPUNIT_ASSERT(pKidsD1);
2768 auto pKidsD1v = pKidsD1->GetElements();
2770 auto pRefKidD10 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[0]);
2771 CPPUNIT_ASSERT(pRefKidD10);
2772 auto pObjectD10 = pRefKidD10->LookupObject();
2773 CPPUNIT_ASSERT(pObjectD10);
2774 auto pTypeD10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD10->Lookup("Type"_ostr));
2775 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD10->GetValue());
2776 auto pSD10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD10->Lookup("S"_ostr));
2777 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue());
2779 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD10->Lookup("K"_ostr));
2780 auto nMCID(0);
2781 auto nRef(0);
2782 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2784 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2785 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2786 if (pNum)
2788 ++nMCID;
2790 if (pObjR)
2792 ++nRef;
2793 auto pOType
2794 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2795 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2796 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2797 pObjR->LookupElement("Obj"_ostr));
2798 auto pAnnot = pAnnotRef->LookupObject();
2799 auto pAType
2800 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2801 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2802 auto pASubtype
2803 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2804 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2805 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2806 pAnnot->Lookup("Contents"_ostr));
2807 CPPUNIT_ASSERT_EQUAL(
2808 u"https://www.mozilla.org/en-US/firefox/118.0/releasenotes/"_ustr,
2809 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2810 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2811 pAnnot->Lookup("StructParent"_ostr));
2812 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2813 auto pARect
2814 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2815 CPPUNIT_ASSERT(pARect);
2816 const auto& rElements = pARect->GetElements();
2817 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2818 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2819 CPPUNIT_ASSERT(pNumL);
2820 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
2821 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2822 CPPUNIT_ASSERT(pNumT);
2823 CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3);
2824 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2825 CPPUNIT_ASSERT(pNumR);
2826 // this changed to the end of the text, not the start of the fly
2827 CPPUNIT_ASSERT_DOUBLES_EQUAL(191.657, pNumR->GetValue(), 1e-3);
2828 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2829 CPPUNIT_ASSERT(pNumB);
2830 CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3);
2833 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2834 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2837 auto pRefKidD11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[1]);
2838 CPPUNIT_ASSERT(pRefKidD11);
2839 auto pObjectD11 = pRefKidD11->LookupObject();
2840 CPPUNIT_ASSERT(pObjectD11);
2841 auto pTypeD11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD11->Lookup("Type"_ostr));
2842 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD11->GetValue());
2843 auto pSD11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD11->Lookup("S"_ostr));
2844 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue());
2846 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD11->Lookup("K"_ostr));
2847 auto nMCID(0);
2848 auto nRef(0);
2849 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
2851 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
2852 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
2853 if (pNum)
2855 ++nMCID;
2857 if (pObjR)
2859 ++nRef;
2860 auto pOType
2861 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
2862 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
2863 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2864 pObjR->LookupElement("Obj"_ostr));
2865 auto pAnnot = pAnnotRef->LookupObject();
2866 auto pAType
2867 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
2868 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
2869 auto pASubtype
2870 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
2871 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
2872 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
2873 pAnnot->Lookup("Contents"_ostr));
2874 CPPUNIT_ASSERT_EQUAL(
2875 u"https://www.mozilla.org/en-US/firefox/118.0/releasenotes/"_ustr,
2876 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
2877 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
2878 pAnnot->Lookup("StructParent"_ostr));
2879 CPPUNIT_ASSERT(pStructParent); // every link must have it!
2880 auto pARect
2881 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
2882 CPPUNIT_ASSERT(pARect);
2883 const auto& rElements = pARect->GetElements();
2884 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
2885 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
2886 CPPUNIT_ASSERT(pNumL);
2887 CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3);
2888 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
2889 CPPUNIT_ASSERT(pNumT);
2890 CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3);
2891 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
2892 CPPUNIT_ASSERT(pNumR);
2893 // this changed to the end of the text, not the start of the fly
2894 CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3);
2895 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
2896 CPPUNIT_ASSERT(pNumB);
2897 CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3);
2900 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
2901 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
2904 // the problem was that in addition to the 4 links with SE there was 1 more
2905 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
2906 CPPUNIT_ASSERT(pAnnots);
2907 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), pAnnots->GetElements().size());
2910 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142133)
2912 vcl::filter::PDFDocument aDocument;
2913 load(u"tdf142133.docx", aDocument);
2915 // The document has one page.
2916 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2917 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
2919 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
2920 CPPUNIT_ASSERT(pAnnots);
2922 // There should be one annotation
2923 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
2924 auto pAnnotReference
2925 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
2926 CPPUNIT_ASSERT(pAnnotReference);
2927 vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
2928 CPPUNIT_ASSERT(pAnnot);
2929 // We're expecting something like /Type /Annot /A << /Type /Action /S /URI /URI (path)
2930 CPPUNIT_ASSERT_EQUAL(
2931 "Annot"_ostr,
2932 static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
2933 CPPUNIT_ASSERT_EQUAL(
2934 "Link"_ostr,
2935 static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
2936 auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
2937 CPPUNIT_ASSERT(pAction);
2938 auto pURIElem
2939 = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAction->LookupElement("URI"_ostr));
2940 CPPUNIT_ASSERT(pURIElem);
2941 // Check it matches
2942 CPPUNIT_ASSERT_EQUAL("https://google.com/"_ostr, pURIElem->GetValue());
2945 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806)
2947 // Enable PDF/UA
2948 uno::Sequence<beans::PropertyValue> aFilterData(
2949 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
2950 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
2952 vcl::filter::PDFDocument aDocument;
2953 load(u"LinkPages.fodt", aDocument);
2955 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2956 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), aPages.size());
2958 vcl::filter::PDFObjectElement* pDocument(nullptr);
2959 for (const auto& rDocElement : aDocument.GetElements())
2961 auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
2962 if (!pObject1)
2963 continue;
2964 auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
2965 if (pType1 && pType1->GetValue() == "StructElem")
2967 auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
2968 if (pS1 && pS1->GetValue() == "Document")
2970 pDocument = pObject1;
2974 CPPUNIT_ASSERT(pDocument);
2976 auto pKidsD = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
2977 CPPUNIT_ASSERT(pKidsD);
2978 // assume there are no MCID ref at this level
2979 auto pKidsDv = pKidsD->GetElements();
2980 auto pRefKidD0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[0]);
2981 CPPUNIT_ASSERT(pRefKidD0);
2982 auto pObjectD0 = pRefKidD0->LookupObject();
2983 CPPUNIT_ASSERT(pObjectD0);
2984 auto pTypeD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("Type"_ostr));
2985 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD0->GetValue());
2986 auto pSD0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD0->Lookup("S"_ostr));
2987 CPPUNIT_ASSERT_EQUAL("H1"_ostr, pSD0->GetValue());
2989 auto pKidsD0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD0->Lookup("K"_ostr));
2990 CPPUNIT_ASSERT(pKidsD0);
2991 auto pKidsD0v = pKidsD0->GetElements();
2993 auto pRefKidD00 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[0]);
2994 CPPUNIT_ASSERT(pRefKidD00);
2995 auto pObjectD00 = pRefKidD00->LookupObject();
2996 CPPUNIT_ASSERT(pObjectD00);
2997 auto pTypeD00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD00->Lookup("Type"_ostr));
2998 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue());
2999 auto pSD00 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD00->Lookup("S"_ostr));
3000 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue());
3002 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD00->Lookup("K"_ostr));
3003 auto nMCID(0);
3004 auto nRef(0);
3005 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3007 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3008 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3009 if (pNum)
3011 ++nMCID;
3013 if (pObjR)
3015 ++nRef;
3016 auto pOType
3017 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3018 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3019 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3020 pObjR->LookupElement("Obj"_ostr));
3021 auto pAnnot = pAnnotRef->LookupObject();
3022 auto pAType
3023 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3024 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3025 auto pASubtype
3026 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3027 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3028 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3029 pAnnot->Lookup("Contents"_ostr));
3030 CPPUNIT_ASSERT_EQUAL(
3031 u"foo foo foo foo"_ustr,
3032 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3033 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3034 pAnnot->Lookup("StructParent"_ostr));
3035 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3036 auto pARect
3037 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3038 CPPUNIT_ASSERT(pARect);
3039 const auto& rElements = pARect->GetElements();
3040 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3041 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3042 CPPUNIT_ASSERT(pNumL);
3043 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
3044 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3045 CPPUNIT_ASSERT(pNumT);
3046 CPPUNIT_ASSERT_DOUBLES_EQUAL(240.455, pNumT->GetValue(), 1e-3);
3047 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3048 CPPUNIT_ASSERT(pNumR);
3049 CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3);
3050 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3051 CPPUNIT_ASSERT(pNumB);
3052 CPPUNIT_ASSERT_DOUBLES_EQUAL(350.855, pNumB->GetValue(), 1e-3);
3055 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3056 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3059 auto pRefKidD01 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[1]);
3060 CPPUNIT_ASSERT(pRefKidD01);
3061 auto pObjectD01 = pRefKidD01->LookupObject();
3062 CPPUNIT_ASSERT(pObjectD01);
3063 auto pTypeD01 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD01->Lookup("Type"_ostr));
3064 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD01->GetValue());
3065 auto pSD01 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD01->Lookup("S"_ostr));
3066 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue());
3068 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD01->Lookup("K"_ostr));
3069 auto nMCID(0);
3070 auto nRef(0);
3071 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3073 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3074 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3075 if (pNum)
3077 ++nMCID;
3079 if (pObjR)
3081 ++nRef;
3082 auto pOType
3083 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3084 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3085 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3086 pObjR->LookupElement("Obj"_ostr));
3087 auto pAnnot = pAnnotRef->LookupObject();
3088 auto pAType
3089 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3090 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3091 auto pASubtype
3092 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3093 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3094 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3095 pAnnot->Lookup("Contents"_ostr));
3096 CPPUNIT_ASSERT_EQUAL(
3097 u"foo foo foo foo"_ustr,
3098 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3099 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3100 pAnnot->Lookup("StructParent"_ostr));
3101 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3102 auto pARect
3103 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3104 CPPUNIT_ASSERT(pARect);
3105 const auto& rElements = pARect->GetElements();
3106 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3107 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3108 CPPUNIT_ASSERT(pNumL);
3109 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3);
3110 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3111 CPPUNIT_ASSERT(pNumT);
3112 CPPUNIT_ASSERT_DOUBLES_EQUAL(130.055, pNumT->GetValue(), 1e-3);
3113 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3114 CPPUNIT_ASSERT(pNumR);
3115 CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3);
3116 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3117 CPPUNIT_ASSERT(pNumB);
3118 CPPUNIT_ASSERT_DOUBLES_EQUAL(240.455, pNumB->GetValue(), 1e-3);
3121 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3122 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3125 auto pRefKidD02 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[2]);
3126 CPPUNIT_ASSERT(pRefKidD02);
3127 auto pObjectD02 = pRefKidD02->LookupObject();
3128 CPPUNIT_ASSERT(pObjectD02);
3129 auto pTypeD02 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD02->Lookup("Type"_ostr));
3130 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue());
3131 auto pSD02 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD02->Lookup("S"_ostr));
3132 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD02->GetValue());
3134 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD02->Lookup("K"_ostr));
3135 auto nMCID(0);
3136 auto nRef(0);
3137 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3139 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3140 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3141 if (pNum)
3143 ++nMCID;
3145 if (pObjR)
3147 ++nRef;
3148 auto pOType
3149 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3150 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3151 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3152 pObjR->LookupElement("Obj"_ostr));
3153 auto pAnnot = pAnnotRef->LookupObject();
3154 auto pAType
3155 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3156 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3157 auto pASubtype
3158 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3159 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3160 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3161 pAnnot->Lookup("Contents"_ostr));
3162 CPPUNIT_ASSERT_EQUAL(
3163 u"foo foo foo foo"_ustr,
3164 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3165 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3166 pAnnot->Lookup("StructParent"_ostr));
3167 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3168 auto pARect
3169 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3170 CPPUNIT_ASSERT(pARect);
3171 const auto& rElements = pARect->GetElements();
3172 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3173 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3174 CPPUNIT_ASSERT(pNumL);
3175 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3);
3176 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3177 CPPUNIT_ASSERT(pNumT);
3178 CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumT->GetValue(), 1e-3);
3179 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3180 CPPUNIT_ASSERT(pNumR);
3181 CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3);
3182 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3183 CPPUNIT_ASSERT(pNumB);
3184 CPPUNIT_ASSERT_DOUBLES_EQUAL(362.855, pNumB->GetValue(), 1e-3);
3187 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3188 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3191 auto pRefKidD03 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD0v[3]);
3192 CPPUNIT_ASSERT(pRefKidD03);
3193 auto pObjectD03 = pRefKidD03->LookupObject();
3194 CPPUNIT_ASSERT(pObjectD03);
3195 auto pTypeD03 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD03->Lookup("Type"_ostr));
3196 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD03->GetValue());
3197 auto pSD03 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD03->Lookup("S"_ostr));
3198 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD03->GetValue());
3200 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD03->Lookup("K"_ostr));
3201 auto nMCID(0);
3202 auto nRef(0);
3203 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3205 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3206 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3207 if (pNum)
3209 ++nMCID;
3211 if (pObjR)
3213 ++nRef;
3214 auto pOType
3215 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3216 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3217 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3218 pObjR->LookupElement("Obj"_ostr));
3219 auto pAnnot = pAnnotRef->LookupObject();
3220 auto pAType
3221 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3222 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3223 auto pASubtype
3224 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3225 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3226 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3227 pAnnot->Lookup("Contents"_ostr));
3228 CPPUNIT_ASSERT_EQUAL(
3229 u"foo foo foo foo"_ustr,
3230 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3231 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3232 pAnnot->Lookup("StructParent"_ostr));
3233 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3234 auto pARect
3235 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3236 CPPUNIT_ASSERT(pARect);
3237 const auto& rElements = pARect->GetElements();
3238 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3239 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3240 CPPUNIT_ASSERT(pNumL);
3241 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3);
3242 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3243 CPPUNIT_ASSERT(pNumT);
3244 CPPUNIT_ASSERT_DOUBLES_EQUAL(142.055, pNumT->GetValue(), 1e-3);
3245 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3246 CPPUNIT_ASSERT(pNumR);
3247 CPPUNIT_ASSERT_DOUBLES_EQUAL(206.007, pNumR->GetValue(), 1e-3);
3248 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3249 CPPUNIT_ASSERT(pNumB);
3250 CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumB->GetValue(), 1e-3);
3253 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3254 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3256 auto pRefKidD1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsDv[1]);
3257 CPPUNIT_ASSERT(pRefKidD1);
3258 auto pObjectD1 = pRefKidD1->LookupObject();
3259 CPPUNIT_ASSERT(pObjectD1);
3260 auto pTypeD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("Type"_ostr));
3261 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD1->GetValue());
3262 auto pSD1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD1->Lookup("S"_ostr));
3263 CPPUNIT_ASSERT_EQUAL("Text#20body"_ostr, pSD1->GetValue());
3265 auto pKidsD1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD1->Lookup("K"_ostr));
3266 CPPUNIT_ASSERT(pKidsD1);
3267 auto pKidsD1v = pKidsD1->GetElements();
3269 auto pRefKidD10 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[0]);
3270 CPPUNIT_ASSERT(pRefKidD10);
3271 auto pObjectD10 = pRefKidD10->LookupObject();
3272 CPPUNIT_ASSERT(pObjectD10);
3273 auto pTypeD10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD10->Lookup("Type"_ostr));
3274 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD10->GetValue());
3275 auto pSD10 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD10->Lookup("S"_ostr));
3276 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue());
3278 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD10->Lookup("K"_ostr));
3279 auto nMCID(0);
3280 auto nRef(0);
3281 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3283 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3284 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3285 if (pNum)
3287 ++nMCID;
3289 if (pObjR)
3291 ++nRef;
3292 auto pOType
3293 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3294 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3295 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3296 pObjR->LookupElement("Obj"_ostr));
3297 auto pAnnot = pAnnotRef->LookupObject();
3298 auto pAType
3299 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3300 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3301 auto pASubtype
3302 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3303 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3304 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3305 pAnnot->Lookup("Contents"_ostr));
3306 CPPUNIT_ASSERT_EQUAL(
3307 u"foo foo foo foo"_ustr,
3308 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3309 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3310 pAnnot->Lookup("StructParent"_ostr));
3311 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3312 auto pARect
3313 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3314 CPPUNIT_ASSERT(pARect);
3315 const auto& rElements = pARect->GetElements();
3316 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3317 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3318 CPPUNIT_ASSERT(pNumL);
3319 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3);
3320 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3321 CPPUNIT_ASSERT(pNumT);
3322 CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumT->GetValue(), 1e-3);
3323 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3324 CPPUNIT_ASSERT(pNumR);
3325 CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3);
3326 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3327 CPPUNIT_ASSERT(pNumB);
3328 CPPUNIT_ASSERT_DOUBLES_EQUAL(362.855, pNumB->GetValue(), 1e-3);
3331 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3332 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3335 auto pRefKidD11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[1]);
3336 CPPUNIT_ASSERT(pRefKidD11);
3337 auto pObjectD11 = pRefKidD11->LookupObject();
3338 CPPUNIT_ASSERT(pObjectD11);
3339 auto pTypeD11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD11->Lookup("Type"_ostr));
3340 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD11->GetValue());
3341 auto pSD11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD11->Lookup("S"_ostr));
3342 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue());
3344 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD11->Lookup("K"_ostr));
3345 auto nMCID(0);
3346 auto nRef(0);
3347 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3349 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3350 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3351 if (pNum)
3353 ++nMCID;
3355 if (pObjR)
3357 ++nRef;
3358 auto pOType
3359 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3360 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3361 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3362 pObjR->LookupElement("Obj"_ostr));
3363 auto pAnnot = pAnnotRef->LookupObject();
3364 auto pAType
3365 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3366 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3367 auto pASubtype
3368 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3369 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3370 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3371 pAnnot->Lookup("Contents"_ostr));
3372 CPPUNIT_ASSERT_EQUAL(
3373 u"foo foo foo foo"_ustr,
3374 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3375 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3376 pAnnot->Lookup("StructParent"_ostr));
3377 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3378 auto pARect
3379 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3380 CPPUNIT_ASSERT(pARect);
3381 const auto& rElements = pARect->GetElements();
3382 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3383 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3384 CPPUNIT_ASSERT(pNumL);
3385 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3);
3386 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3387 CPPUNIT_ASSERT(pNumT);
3388 CPPUNIT_ASSERT_DOUBLES_EQUAL(140.005, pNumT->GetValue(), 1e-3);
3389 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3390 CPPUNIT_ASSERT(pNumR);
3391 CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3);
3392 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3393 CPPUNIT_ASSERT(pNumB);
3394 CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumB->GetValue(), 1e-3);
3397 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3398 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3401 auto pRefKidD12 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[2]);
3402 CPPUNIT_ASSERT(pRefKidD12);
3403 auto pObjectD12 = pRefKidD12->LookupObject();
3404 CPPUNIT_ASSERT(pObjectD12);
3405 auto pTypeD12 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD12->Lookup("Type"_ostr));
3406 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD12->GetValue());
3407 auto pSD12 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD12->Lookup("S"_ostr));
3408 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD12->GetValue());
3410 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD12->Lookup("K"_ostr));
3411 auto nMCID(0);
3412 auto nRef(0);
3413 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3415 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3416 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3417 if (pNum)
3419 ++nMCID;
3421 if (pObjR)
3423 ++nRef;
3424 auto pOType
3425 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3426 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3427 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3428 pObjR->LookupElement("Obj"_ostr));
3429 auto pAnnot = pAnnotRef->LookupObject();
3430 auto pAType
3431 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3432 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3433 auto pASubtype
3434 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3435 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3436 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3437 pAnnot->Lookup("Contents"_ostr));
3438 CPPUNIT_ASSERT_EQUAL(
3439 u"foo foo foo foo"_ustr,
3440 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3441 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3442 pAnnot->Lookup("StructParent"_ostr));
3443 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3444 auto pARect
3445 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3446 CPPUNIT_ASSERT(pARect);
3447 const auto& rElements = pARect->GetElements();
3448 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3449 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3450 CPPUNIT_ASSERT(pNumL);
3451 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3);
3452 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3453 CPPUNIT_ASSERT(pNumT);
3454 CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumT->GetValue(), 1e-3);
3455 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3456 CPPUNIT_ASSERT(pNumR);
3457 CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3);
3458 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3459 CPPUNIT_ASSERT(pNumB);
3460 CPPUNIT_ASSERT_DOUBLES_EQUAL(362.855, pNumB->GetValue(), 1e-3);
3463 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3464 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3467 auto pRefKidD13 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKidsD1v[3]);
3468 CPPUNIT_ASSERT(pRefKidD13);
3469 auto pObjectD13 = pRefKidD13->LookupObject();
3470 CPPUNIT_ASSERT(pObjectD13);
3471 auto pTypeD13 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD13->Lookup("Type"_ostr));
3472 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD13->GetValue());
3473 auto pSD13 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjectD13->Lookup("S"_ostr));
3474 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD13->GetValue());
3476 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObjectD13->Lookup("K"_ostr));
3477 auto nMCID(0);
3478 auto nRef(0);
3479 for (size_t i = 0; i < pKids->GetElements().size(); ++i)
3481 auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
3482 auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
3483 if (pNum)
3485 ++nMCID;
3487 if (pObjR)
3489 ++nRef;
3490 auto pOType
3491 = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
3492 CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
3493 auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
3494 pObjR->LookupElement("Obj"_ostr));
3495 auto pAnnot = pAnnotRef->LookupObject();
3496 auto pAType
3497 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
3498 CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
3499 auto pASubtype
3500 = dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
3501 CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
3502 auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
3503 pAnnot->Lookup("Contents"_ostr));
3504 CPPUNIT_ASSERT_EQUAL(
3505 u"foo foo foo foo"_ustr,
3506 ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
3507 auto pStructParent = dynamic_cast<vcl::filter::PDFNumberElement*>(
3508 pAnnot->Lookup("StructParent"_ostr));
3509 CPPUNIT_ASSERT(pStructParent); // every link must have it!
3510 auto pARect
3511 = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
3512 CPPUNIT_ASSERT(pARect);
3513 const auto& rElements = pARect->GetElements();
3514 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), rElements.size());
3515 const auto* pNumL = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[0]);
3516 CPPUNIT_ASSERT(pNumL);
3517 CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3);
3518 const auto* pNumT = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[1]);
3519 CPPUNIT_ASSERT(pNumT);
3520 CPPUNIT_ASSERT_DOUBLES_EQUAL(140.005, pNumT->GetValue(), 1e-3);
3521 const auto* pNumR = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[2]);
3522 CPPUNIT_ASSERT(pNumR);
3523 CPPUNIT_ASSERT_DOUBLES_EQUAL(184.707, pNumR->GetValue(), 1e-3);
3524 const auto* pNumB = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[3]);
3525 CPPUNIT_ASSERT(pNumB);
3526 CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumB->GetValue(), 1e-3);
3529 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID);
3530 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef);
3533 // the problem was that the links in follow frames were all missing
3534 auto pAnnots0 = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
3535 CPPUNIT_ASSERT(pAnnots0);
3536 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pAnnots0->GetElements().size());
3537 auto pAnnots1 = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[1]->Lookup("Annots"_ostr));
3538 CPPUNIT_ASSERT(pAnnots1);
3539 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pAnnots1->GetElements().size());
3540 auto pAnnots2 = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[2]->Lookup("Annots"_ostr));
3541 CPPUNIT_ASSERT(pAnnots2);
3542 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pAnnots2->GetElements().size());
3543 auto pAnnots3 = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[3]->Lookup("Annots"_ostr));
3544 CPPUNIT_ASSERT(pAnnots3);
3545 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), pAnnots3->GetElements().size());
3548 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115967)
3550 saveAsPDF(u"tdf115967.odt");
3551 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
3552 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
3554 // Get the first page
3555 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
3556 CPPUNIT_ASSERT(pPdfPage);
3557 std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
3559 // Make sure the elements inside a formula in a RTL document are exported
3560 // LTR ( m=750abc ) and not RTL ( m=057cba )
3561 int nPageObjectCount = pPdfPage->getObjectCount();
3562 OUString sText;
3563 for (int i = 0; i < nPageObjectCount; ++i)
3565 std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
3566 if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
3567 continue;
3568 OUString sChar = pPageObject->getText(pTextPage);
3569 sText += o3tl::trim(sChar);
3571 CPPUNIT_ASSERT_EQUAL(u"m=750abc"_ustr, sText);
3574 } // end anonymous namespace
3576 CPPUNIT_PLUGIN_IMPLEMENT();
3578 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */