1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #include <sal/config.h>
11 
12 #include <memory>
13 #include <string_view>
14 #include <type_traits>
15 
16 #include <config_features.h>
17 
18 #include <com/sun/star/frame/Desktop.hpp>
19 #include <com/sun/star/frame/XStorable.hpp>
20 #include <com/sun/star/view/XPrintable.hpp>
21 #include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
22 #include <com/sun/star/util/XRefreshable.hpp>
23 #include <com/sun/star/beans/XPropertySet.hpp>
24 #include <com/sun/star/drawing/XShape.hpp>
25 #include <com/sun/star/text/XTextDocument.hpp>
26 #include <com/sun/star/document/XFilter.hpp>
27 #include <com/sun/star/document/XExporter.hpp>
28 #include <com/sun/star/io/XOutputStream.hpp>
29 
30 #include <comphelper/scopeguard.hxx>
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/propertysequence.hxx>
33 #include <test/bootstrapfixture.hxx>
34 #include <unotest/macros_test.hxx>
35 #include <unotools/mediadescriptor.hxx>
36 #include <unotools/tempfile.hxx>
37 #include <vcl/filter/pdfdocument.hxx>
38 #include <tools/zcodec.hxx>
39 #include <tools/XmlWalker.hxx>
40 #include <vcl/graphicfilter.hxx>
41 #include <basegfx/matrix/b2dhommatrix.hxx>
42 #include <unotools/streamwrap.hxx>
43 
44 #include <vcl/filter/PDFiumLibrary.hxx>
45 #include <comphelper/propertyvalue.hxx>
46 
47 using namespace ::com::sun::star;
48 
operator <<(std::ostream & rStrm,const Color & rColor)49 static std::ostream& operator<<(std::ostream& rStrm, const Color& rColor)
50 {
51     rStrm << "Color: R:" << static_cast<int>(rColor.GetRed())
52           << " G:" << static_cast<int>(rColor.GetGreen())
53           << " B:" << static_cast<int>(rColor.GetBlue())
54           << " A:" << static_cast<int>(255 - rColor.GetAlpha());
55     return rStrm;
56 }
57 
58 namespace
59 {
60 /// Tests the PDF export filter.
61 class PdfExportTest : public test::BootstrapFixture, public unotest::MacrosTest
62 {
63 protected:
64     uno::Reference<lang::XComponent> mxComponent;
65     utl::TempFile maTempFile;
66     SvMemoryStream maMemory;
67     // Export the document as PDF, then parse it with PDFium.
68     std::unique_ptr<vcl::pdf::PDFiumDocument>
69     exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor);
70     std::shared_ptr<vcl::pdf::PDFium> mpPDFium;
71 
72 public:
73     PdfExportTest();
74     virtual void setUp() override;
75     virtual void tearDown() override;
76     void saveAsPDF(std::u16string_view rFile);
77     void load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument);
78 };
79 
PdfExportTest()80 PdfExportTest::PdfExportTest() { maTempFile.EnableKillingFile(); }
81 
82 std::unique_ptr<vcl::pdf::PDFiumDocument>
exportAndParse(const OUString & rURL,const utl::MediaDescriptor & rDescriptor)83 PdfExportTest::exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor)
84 {
85     // Import the bugdoc and export as PDF.
86     mxComponent = loadFromDesktop(rURL);
87     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
88     xStorable->storeToURL(maTempFile.GetURL(), rDescriptor.getAsConstPropertyValueList());
89 
90     // Parse the export result with pdfium.
91     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
92     maMemory.WriteStream(aFile);
93     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
94     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
95         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
96     CPPUNIT_ASSERT(pPdfDocument);
97     return pPdfDocument;
98 }
99 
setUp()100 void PdfExportTest::setUp()
101 {
102     test::BootstrapFixture::setUp();
103 
104     mxDesktop.set(frame::Desktop::create(mxComponentContext));
105 
106     mpPDFium = vcl::pdf::PDFiumLibrary::get();
107 }
108 
tearDown()109 void PdfExportTest::tearDown()
110 {
111     if (mxComponent.is())
112         mxComponent->dispose();
113 
114     test::BootstrapFixture::tearDown();
115 }
116 
117 constexpr OUStringLiteral DATA_DIRECTORY = u"/vcl/qa/cppunit/pdfexport/data/";
118 
saveAsPDF(std::u16string_view rFile)119 void PdfExportTest::saveAsPDF(std::u16string_view rFile)
120 {
121     // Import the bugdoc and export as PDF.
122     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile;
123     mxComponent = loadFromDesktop(aURL);
124     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
125     utl::MediaDescriptor aMediaDescriptor;
126     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
127     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
128 }
129 
load(std::u16string_view rFile,vcl::filter::PDFDocument & rDocument)130 void PdfExportTest::load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument)
131 {
132     saveAsPDF(rFile);
133 
134     // Parse the export result.
135     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
136     CPPUNIT_ASSERT(rDocument.Read(aStream));
137 }
138 
139 /// Tests that a pdf image is roundtripped back to PDF as a vector format.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf106059)140 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106059)
141 {
142     // Import the bugdoc and export as PDF.
143     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106059.odt";
144     mxComponent = loadFromDesktop(aURL);
145     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
146     utl::MediaDescriptor aMediaDescriptor;
147     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
148     // Explicitly enable the usage of the reference XObject markup.
149     uno::Sequence<beans::PropertyValue> aFilterData(
150         comphelper::InitPropertySequence({ { "UseReferenceXObject", uno::Any(true) } }));
151     aMediaDescriptor["FilterData"] <<= aFilterData;
152     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
153 
154     // Parse the export result.
155     vcl::filter::PDFDocument aDocument;
156     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
157     CPPUNIT_ASSERT(aDocument.Read(aStream));
158 
159     // Assert that the XObject in the page resources dictionary is a reference XObject.
160     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
161     // The document has one page.
162     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
163     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
164     CPPUNIT_ASSERT(pResources);
165     auto pXObjects
166         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
167     CPPUNIT_ASSERT(pXObjects);
168     // The page has one image.
169     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
170     vcl::filter::PDFObjectElement* pReferenceXObject
171         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
172     CPPUNIT_ASSERT(pReferenceXObject);
173     // The image is a reference XObject.
174     // This dictionary key was missing, so the XObject wasn't a reference one.
175     CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref"));
176 }
177 
178 /// Tests export of PDF images without reference XObjects.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf106693)179 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106693)
180 {
181     vcl::filter::PDFDocument aDocument;
182     load(u"tdf106693.odt", aDocument);
183 
184     // Assert that the XObject in the page resources dictionary is a form XObject.
185     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
186     // The document has one page.
187     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
188     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
189     CPPUNIT_ASSERT(pResources);
190     auto pXObjects
191         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
192     CPPUNIT_ASSERT(pXObjects);
193     // The page has one image.
194     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
195     vcl::filter::PDFObjectElement* pXObject
196         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
197     CPPUNIT_ASSERT(pXObject);
198     // The image is a form XObject.
199     auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"));
200     CPPUNIT_ASSERT(pSubtype);
201     CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue());
202     // This failed: UseReferenceXObject was ignored and Ref was always created.
203     CPPUNIT_ASSERT(!pXObject->Lookup("Ref"));
204 
205     // Assert that the form object refers to an inner form object, not a
206     // bitmap.
207     auto pInnerResources
208         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
209     CPPUNIT_ASSERT(pInnerResources);
210     auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
211         pInnerResources->LookupElement("XObject"));
212     CPPUNIT_ASSERT(pInnerXObjects);
213     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pInnerXObjects->GetItems().size());
214     vcl::filter::PDFObjectElement* pInnerXObject
215         = pInnerXObjects->LookupObject(pInnerXObjects->GetItems().begin()->first);
216     CPPUNIT_ASSERT(pInnerXObject);
217     auto pInnerSubtype
218         = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"));
219     CPPUNIT_ASSERT(pInnerSubtype);
220     // This failed: this was Image (bitmap), not Form (vector).
221     CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue());
222 }
223 
224 /// Tests that text highlight from Impress is not lost.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf105461)225 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105461)
226 {
227     // Import the bugdoc and export as PDF.
228     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105461.odp";
229     mxComponent = loadFromDesktop(aURL);
230     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
231     utl::MediaDescriptor aMediaDescriptor;
232     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
233     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
234 
235     // Parse the export result with pdfium.
236     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
237     SvMemoryStream aMemory;
238     aMemory.WriteStream(aFile);
239     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
240     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
241         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
242     CPPUNIT_ASSERT(pPdfDocument);
243 
244     // The document has one page.
245     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
246     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
247     CPPUNIT_ASSERT(pPdfPage);
248 
249     // Make sure there is a filled rectangle inside.
250     int nPageObjectCount = pPdfPage->getObjectCount();
251     int nYellowPathCount = 0;
252     for (int i = 0; i < nPageObjectCount; ++i)
253     {
254         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
255         if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
256             continue;
257 
258         if (pPdfPageObject->getFillColor() == COL_YELLOW)
259             ++nYellowPathCount;
260     }
261 
262     // This was 0, the page contained no yellow paths.
263     CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
264 }
265 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf107868)266 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107868)
267 {
268 // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
269 // which is the intent of the test.
270 // FIXME: Why does this fail on macOS?
271 #if !defined MACOSX && !defined _WIN32
272 
273     // Import the bugdoc and print to PDF.
274     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf107868.odt";
275     mxComponent = loadFromDesktop(aURL);
276     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
277     uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
278     CPPUNIT_ASSERT(xPrintable.is());
279     uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
280         { { "FileName", uno::makeAny(maTempFile.GetURL()) }, { "Wait", uno::makeAny(true) } }));
281     xPrintable->print(aOptions);
282 
283     // Parse the export result with pdfium.
284     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
285     SvMemoryStream aMemory;
286     aMemory.WriteStream(aFile);
287     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
288     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
289         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
290     if (!pPdfDocument)
291         // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
292         // running, there is no printer defined, etc.
293         return;
294 
295     // The document has one page.
296     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
297     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
298     CPPUNIT_ASSERT(pPdfPage);
299 
300     // Make sure there is no filled rectangle inside.
301     int nPageObjectCount = pPdfPage->getObjectCount();
302     int nWhitePathCount = 0;
303     for (int i = 0; i < nPageObjectCount; ++i)
304     {
305         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
306         if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
307             continue;
308 
309         if (pPdfPageObject->getFillColor() == COL_WHITE)
310             ++nWhitePathCount;
311     }
312 
313     // This was 4, the page contained 4 white paths at problematic positions.
314     CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount);
315 #endif
316 }
317 
318 /// Tests that embedded video from Impress is not exported as a linked one.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf105093)319 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105093)
320 {
321     vcl::filter::PDFDocument aDocument;
322     load(u"tdf105093.odp", aDocument);
323 
324     // The document has one page.
325     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
326     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
327 
328     // Get page annotations.
329     auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
330     CPPUNIT_ASSERT(pAnnots);
331     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
332     auto pAnnotReference
333         = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
334     CPPUNIT_ASSERT(pAnnotReference);
335     vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
336     CPPUNIT_ASSERT(pAnnot);
337     CPPUNIT_ASSERT_EQUAL(
338         OString("Annot"),
339         static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"))->GetValue());
340 
341     // Get the Action -> Rendition -> MediaClip -> FileSpec.
342     auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"));
343     CPPUNIT_ASSERT(pAction);
344     auto pRendition = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAction->LookupElement("R"));
345     CPPUNIT_ASSERT(pRendition);
346     auto pMediaClip
347         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pRendition->LookupElement("C"));
348     CPPUNIT_ASSERT(pMediaClip);
349     auto pFileSpec
350         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pMediaClip->LookupElement("D"));
351     CPPUNIT_ASSERT(pFileSpec);
352     // Make sure the filespec refers to an embedded file.
353     // This key was missing, the embedded video was handled as a linked one.
354     CPPUNIT_ASSERT(pFileSpec->LookupElement("EF"));
355 }
356 
357 /// Tests export of non-PDF images.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf106206)358 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106206)
359 {
360     // Import the bugdoc and export as PDF.
361     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106206.odt";
362     mxComponent = loadFromDesktop(aURL);
363     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
364     utl::MediaDescriptor aMediaDescriptor;
365     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
366     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
367 
368     // Parse the export result.
369     vcl::filter::PDFDocument aDocument;
370     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
371     CPPUNIT_ASSERT(aDocument.Read(aStream));
372 
373     // The document has one page.
374     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
375     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
376 
377     // The page has a stream.
378     vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
379     CPPUNIT_ASSERT(pContents);
380     vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
381     CPPUNIT_ASSERT(pStream);
382     SvMemoryStream& rObjectStream = pStream->GetMemory();
383     // Uncompress it.
384     SvMemoryStream aUncompressed;
385     ZCodec aZCodec;
386     aZCodec.BeginCompression();
387     rObjectStream.Seek(0);
388     aZCodec.Decompress(rObjectStream, aUncompressed);
389     CPPUNIT_ASSERT(aZCodec.EndCompression());
390 
391     // Make sure there is an image reference there.
392     OString aImage("/Im");
393     auto pStart = static_cast<const char*>(aUncompressed.GetData());
394     const char* pEnd = pStart + aUncompressed.GetSize();
395     auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
396     CPPUNIT_ASSERT(it != pEnd);
397 
398     // And also that it's not an invalid one.
399     OString aInvalidImage("/Im0");
400     it = std::search(pStart, pEnd, aInvalidImage.getStr(),
401                      aInvalidImage.getStr() + aInvalidImage.getLength());
402     // This failed, object #0 was referenced.
403     CPPUNIT_ASSERT(bool(it == pEnd));
404 }
405 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf127217)406 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf127217)
407 {
408     // Import the bugdoc and export as PDF.
409     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf127217.odt";
410     mxComponent = loadFromDesktop(aURL);
411 
412     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
413     utl::MediaDescriptor aMediaDescriptor;
414     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
415     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
416 
417     // Parse the export result with pdfium.
418     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
419     SvMemoryStream aMemory;
420     aMemory.WriteStream(aFile);
421     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
422     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
423         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
424     CPPUNIT_ASSERT(pPdfDocument);
425 
426     // The document has one page.
427     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
428     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
429     CPPUNIT_ASSERT(pPdfPage);
430 
431     // The page has one annotation.
432     CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
433     std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
434 
435     // Without the fix in place, this test would have failed here
436     CPPUNIT_ASSERT(!pAnnot->hasKey("DA"));
437 }
438 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf109143)439 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf109143)
440 {
441     // Import the bugdoc and export as PDF.
442     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf109143.odt";
443     mxComponent = loadFromDesktop(aURL);
444     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
445     utl::MediaDescriptor aMediaDescriptor;
446     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
447     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
448 
449     // Parse the export result.
450     vcl::filter::PDFDocument aDocument;
451     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
452     CPPUNIT_ASSERT(aDocument.Read(aStream));
453 
454     // The document has one page.
455     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
456     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
457 
458     // Get access to the only image on the only page.
459     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
460     CPPUNIT_ASSERT(pResources);
461     auto pXObjects
462         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
463     CPPUNIT_ASSERT(pXObjects);
464     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
465     vcl::filter::PDFObjectElement* pXObject
466         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
467     CPPUNIT_ASSERT(pXObject);
468 
469     // Make sure it's re-compressed.
470     auto pLength = dynamic_cast<vcl::filter::PDFNumberElement*>(pXObject->Lookup("Length"));
471     CPPUNIT_ASSERT(pLength);
472     int nLength = pLength->GetValue();
473     // This failed: cropped TIFF-in-JPEG wasn't re-compressed, so crop was
474     // lost. Size was 59416, now is 11827.
475     CPPUNIT_ASSERT(nLength < 50000);
476 }
477 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf106972)478 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972)
479 {
480     // Import the bugdoc and export as PDF.
481     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972.odt";
482     mxComponent = loadFromDesktop(aURL);
483     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
484     utl::MediaDescriptor aMediaDescriptor;
485     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
486     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
487 
488     // Parse the export result.
489     vcl::filter::PDFDocument aDocument;
490     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
491     CPPUNIT_ASSERT(aDocument.Read(aStream));
492 
493     // Get access to the only form object on the only page.
494     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
495     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
496     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
497     CPPUNIT_ASSERT(pResources);
498     auto pXObjects
499         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
500     CPPUNIT_ASSERT(pXObjects);
501     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
502     vcl::filter::PDFObjectElement* pXObject
503         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
504     CPPUNIT_ASSERT(pXObject);
505 
506     // Get access to the only image inside the form object.
507     auto pFormResources
508         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
509     CPPUNIT_ASSERT(pFormResources);
510     auto pImages = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
511         pFormResources->LookupElement("XObject"));
512     CPPUNIT_ASSERT(pImages);
513     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pImages->GetItems().size());
514     vcl::filter::PDFObjectElement* pImage
515         = pImages->LookupObject(pImages->GetItems().begin()->first);
516     CPPUNIT_ASSERT(pImage);
517 
518     // Assert resources of the image.
519     auto pImageResources
520         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pImage->Lookup("Resources"));
521     CPPUNIT_ASSERT(pImageResources);
522     // This failed: the PDF image had no Font resource.
523     CPPUNIT_ASSERT(pImageResources->LookupElement("Font"));
524 }
525 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf106972Pdf17)526 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972Pdf17)
527 {
528     // Import the bugdoc and export as PDF.
529     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972-pdf17.odt";
530     mxComponent = loadFromDesktop(aURL);
531     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
532     utl::MediaDescriptor aMediaDescriptor;
533     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
534     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
535 
536     // Parse the export result.
537     vcl::filter::PDFDocument aDocument;
538     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
539     CPPUNIT_ASSERT(aDocument.Read(aStream));
540 
541     // Get access to the only image on the only page.
542     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
543     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
544     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
545     CPPUNIT_ASSERT(pResources);
546     auto pXObjects
547         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
548     CPPUNIT_ASSERT(pXObjects);
549     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
550     vcl::filter::PDFObjectElement* pXObject
551         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
552     CPPUNIT_ASSERT(pXObject);
553 
554     // Assert that we now attempt to preserve the original PDF data, even if
555     // the original input was PDF >= 1.4.
556     CPPUNIT_ASSERT(pXObject->Lookup("Resources"));
557 }
558 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testSofthyphenPos)559 CPPUNIT_TEST_FIXTURE(PdfExportTest, testSofthyphenPos)
560 {
561 // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
562 // which is the intent of the test.
563 // FIXME: Why does this fail on macOS?
564 #if !defined MACOSX && !defined _WIN32
565 
566     // Import the bugdoc and print to PDF.
567     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "softhyphen_pdf.odt";
568     mxComponent = loadFromDesktop(aURL);
569     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
570     uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
571     CPPUNIT_ASSERT(xPrintable.is());
572     uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
573         { { "FileName", uno::makeAny(maTempFile.GetURL()) }, { "Wait", uno::makeAny(true) } }));
574     xPrintable->print(aOptions);
575 
576     // Parse the export result with pdfium.
577     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
578     SvMemoryStream aMemory;
579     aMemory.WriteStream(aFile);
580     if (aFile.bad() || !aMemory.GetSize())
581     {
582         // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
583         // running, there is no printer defined, etc.
584         return;
585     }
586     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
587     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
588         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
589     CPPUNIT_ASSERT(pPdfDocument);
590 
591     // The document has one page.
592     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
593     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
594     CPPUNIT_ASSERT(pPdfPage);
595 
596     // tdf#96892 incorrect fractional part of font size caused soft-hyphen to
597     // be positioned inside preceding text (incorrect = 11.1, correct = 11.05)
598 
599     // there are 3 texts currently, for line 1, soft-hyphen, line 2
600     bool haveText(false);
601 
602     int nPageObjectCount = pPdfPage->getObjectCount();
603     for (int i = 0; i < nPageObjectCount; ++i)
604     {
605         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
606         CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pPdfPageObject->getType());
607         haveText = true;
608         double const size = pPdfPageObject->getFontSize();
609         CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06);
610     }
611 
612     CPPUNIT_ASSERT(haveText);
613 #endif
614 }
615 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf107013)616 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107013)
617 {
618     vcl::filter::PDFDocument aDocument;
619     load(u"tdf107013.odt", aDocument);
620 
621     // Get access to the only image on the only page.
622     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
623     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
624     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
625     CPPUNIT_ASSERT(pResources);
626     auto pXObjects
627         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
628     CPPUNIT_ASSERT(pXObjects);
629     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
630     vcl::filter::PDFObjectElement* pXObject
631         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
632     // This failed, the reference to the image was created, but not the image.
633     CPPUNIT_ASSERT(pXObject);
634 }
635 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf107018)636 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107018)
637 {
638     vcl::filter::PDFDocument aDocument;
639     load(u"tdf107018.odt", aDocument);
640 
641     // Get access to the only image on the only page.
642     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
643     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
644     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
645     CPPUNIT_ASSERT(pResources);
646     auto pXObjects
647         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
648     CPPUNIT_ASSERT(pXObjects);
649     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
650     vcl::filter::PDFObjectElement* pXObject
651         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
652     CPPUNIT_ASSERT(pXObject);
653 
654     // Get access to the form object inside the image.
655     auto pXObjectResources
656         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
657     CPPUNIT_ASSERT(pXObjectResources);
658     auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
659         pXObjectResources->LookupElement("XObject"));
660     CPPUNIT_ASSERT(pXObjectForms);
661     vcl::filter::PDFObjectElement* pForm
662         = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
663     CPPUNIT_ASSERT(pForm);
664 
665     // Get access to Resources -> Font -> F1 of the form.
666     auto pFormResources
667         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pForm->Lookup("Resources"));
668     CPPUNIT_ASSERT(pFormResources);
669     auto pFonts
670         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("Font"));
671     CPPUNIT_ASSERT(pFonts);
672     auto pF1Ref = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFonts->LookupElement("F1"));
673     CPPUNIT_ASSERT(pF1Ref);
674     vcl::filter::PDFObjectElement* pF1 = pF1Ref->LookupObject();
675     CPPUNIT_ASSERT(pF1);
676 
677     // Check that Foo -> Bar of the font is of type Pages.
678     auto pFontFoo = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pF1->Lookup("Foo"));
679     CPPUNIT_ASSERT(pFontFoo);
680     auto pBar = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFontFoo->LookupElement("Bar"));
681     CPPUNIT_ASSERT(pBar);
682     vcl::filter::PDFObjectElement* pObject = pBar->LookupObject();
683     CPPUNIT_ASSERT(pObject);
684     auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
685     CPPUNIT_ASSERT(pName);
686     // This was "XObject", reference in a nested dictionary wasn't updated when
687     // copying the page stream of a PDF image.
688     CPPUNIT_ASSERT_EQUAL(OString("Pages"), pName->GetValue());
689 }
690 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf107089)691 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107089)
692 {
693     vcl::filter::PDFDocument aDocument;
694     load(u"tdf107089.odt", aDocument);
695 
696     // Get access to the only image on the only page.
697     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
698     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
699     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
700     CPPUNIT_ASSERT(pResources);
701     auto pXObjects
702         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
703     CPPUNIT_ASSERT(pXObjects);
704     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
705     vcl::filter::PDFObjectElement* pXObject
706         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
707     CPPUNIT_ASSERT(pXObject);
708 
709     // Get access to the form object inside the image.
710     auto pXObjectResources
711         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
712     CPPUNIT_ASSERT(pXObjectResources);
713     auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
714         pXObjectResources->LookupElement("XObject"));
715     CPPUNIT_ASSERT(pXObjectForms);
716     vcl::filter::PDFObjectElement* pForm
717         = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
718     CPPUNIT_ASSERT(pForm);
719 
720     // Make sure 'Hello' is part of the form object's stream.
721     vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
722     CPPUNIT_ASSERT(pStream);
723     SvMemoryStream aObjectStream;
724     ZCodec aZCodec;
725     aZCodec.BeginCompression();
726     pStream->GetMemory().Seek(0);
727     aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
728     CPPUNIT_ASSERT(aZCodec.EndCompression());
729     aObjectStream.Seek(0);
730     OString aHello("Hello");
731     auto pStart = static_cast<const char*>(aObjectStream.GetData());
732     const char* pEnd = pStart + aObjectStream.GetSize();
733     auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength());
734     // This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage.
735     CPPUNIT_ASSERT(it != pEnd);
736 }
737 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf99680)738 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680)
739 {
740     vcl::filter::PDFDocument aDocument;
741     load(u"tdf99680.odt", aDocument);
742 
743     // The document has one page.
744     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
745     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
746 
747     // The page 1 has a stream.
748     vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
749     CPPUNIT_ASSERT(pContents);
750     vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
751     CPPUNIT_ASSERT(pStream);
752     SvMemoryStream& rObjectStream = pStream->GetMemory();
753 
754     // Uncompress it.
755     SvMemoryStream aUncompressed;
756     ZCodec aZCodec;
757     aZCodec.BeginCompression();
758     rObjectStream.Seek(0);
759     aZCodec.Decompress(rObjectStream, aUncompressed);
760     CPPUNIT_ASSERT(aZCodec.EndCompression());
761 
762     // tdf#130150 See infos in task - short: tdf#99680 was not the
763     // correct fix, so empty clip regions are valid - allow again in tests
764     //      Make sure there are no empty clipping regions.
765     //      OString aEmptyRegion("0 0 m h W* n");
766     //      auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
767     //      CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
768 
769     // Count save graphic state (q) and restore (Q) operators
770     // and ensure their amount is equal
771     auto pStart = static_cast<const char*>(aUncompressed.GetData());
772     const char* pEnd = pStart + aUncompressed.GetSize();
773     size_t nSaveCount = std::count(pStart, pEnd, 'q');
774     size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
775     CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount,
776                                  nRestoreCount);
777 }
778 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf99680_2)779 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680_2)
780 {
781     vcl::filter::PDFDocument aDocument;
782     load(u"tdf99680-2.odt", aDocument);
783 
784     // For each document page
785     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
786     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
787     for (size_t nPageNr = 0; nPageNr < aPages.size(); nPageNr++)
788     {
789         // Get page contents and stream.
790         vcl::filter::PDFObjectElement* pContents = aPages[nPageNr]->LookupObject("Contents");
791         CPPUNIT_ASSERT(pContents);
792         vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
793         CPPUNIT_ASSERT(pStream);
794         SvMemoryStream& rObjectStream = pStream->GetMemory();
795 
796         // Uncompress the stream.
797         SvMemoryStream aUncompressed;
798         ZCodec aZCodec;
799         aZCodec.BeginCompression();
800         rObjectStream.Seek(0);
801         aZCodec.Decompress(rObjectStream, aUncompressed);
802         CPPUNIT_ASSERT(aZCodec.EndCompression());
803 
804         // tdf#130150 See infos in task - short: tdf#99680 was not the
805         // correct fix, so empty clip regions are valid - allow again in tests
806         //      Make sure there are no empty clipping regions.
807         //      OString aEmptyRegion("0 0 m h W* n");
808         //      auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
809         //      CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
810 
811         // Count save graphic state (q) and restore (Q) operators
812         // and ensure their amount is equal
813         auto pStart = static_cast<const char*>(aUncompressed.GetData());
814         const char* pEnd = pStart + aUncompressed.GetSize();
815         size_t nSaveCount = std::count(pStart, pEnd, 'q');
816         size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
817         CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!",
818                                      nSaveCount, nRestoreCount);
819     }
820 }
821 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf108963)822 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf108963)
823 {
824     // Import the bugdoc and export as PDF.
825     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf108963.odp";
826     mxComponent = loadFromDesktop(aURL);
827     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
828     utl::MediaDescriptor aMediaDescriptor;
829     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
830     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
831 
832     // Parse the export result with pdfium.
833     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
834     SvMemoryStream aMemory;
835     aMemory.WriteStream(aFile);
836     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
837     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
838         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
839     CPPUNIT_ASSERT(pPdfDocument);
840 
841     // The document has one page.
842     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
843     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
844     CPPUNIT_ASSERT(pPdfPage);
845 
846     // Test page size (28x15.75 cm, was 1/100th mm off, tdf#112690)
847     // bad: MediaBox[0 0 793.672440944882 446.428346456693]
848     // good: MediaBox[0 0 793.700787401575 446.456692913386]
849     const double aWidth = pPdfPage->getWidth();
850     CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01);
851     const double aHeight = pPdfPage->getHeight();
852     CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01);
853 
854     // Make sure there is a filled rectangle inside.
855     int nPageObjectCount = pPdfPage->getObjectCount();
856     int nYellowPathCount = 0;
857     for (int i = 0; i < nPageObjectCount; ++i)
858     {
859         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
860         if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
861             continue;
862 
863         if (pPdfPageObject->getFillColor() == COL_YELLOW)
864         {
865             ++nYellowPathCount;
866             // The path described a yellow rectangle, but it was not rotated.
867             int nSegments = pPdfPageObject->getPathSegmentCount();
868             CPPUNIT_ASSERT_EQUAL(5, nSegments);
869             std::unique_ptr<vcl::pdf::PDFiumPathSegment> pSegment
870                 = pPdfPageObject->getPathSegment(0);
871             CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Moveto, pSegment->getType());
872             basegfx::B2DPoint aPoint = pSegment->getPoint();
873             CPPUNIT_ASSERT_DOUBLES_EQUAL(245.395, aPoint.getX(), 0.0005);
874             CPPUNIT_ASSERT_DOUBLES_EQUAL(244.261, aPoint.getY(), 0.0005);
875             CPPUNIT_ASSERT(!pSegment->isClosed());
876 
877             pSegment = pPdfPageObject->getPathSegment(1);
878             CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
879             aPoint = pSegment->getPoint();
880             CPPUNIT_ASSERT_DOUBLES_EQUAL(275.102, aPoint.getX(), 0.0005);
881             CPPUNIT_ASSERT_DOUBLES_EQUAL(267.618, aPoint.getY(), 0.0005);
882             CPPUNIT_ASSERT(!pSegment->isClosed());
883 
884             pSegment = pPdfPageObject->getPathSegment(2);
885             CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
886             aPoint = pSegment->getPoint();
887             CPPUNIT_ASSERT_DOUBLES_EQUAL(287.518, aPoint.getX(), 0.0005);
888             CPPUNIT_ASSERT_DOUBLES_EQUAL(251.829, aPoint.getY(), 0.0005);
889             CPPUNIT_ASSERT(!pSegment->isClosed());
890 
891             pSegment = pPdfPageObject->getPathSegment(3);
892             CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
893             aPoint = pSegment->getPoint();
894             CPPUNIT_ASSERT_DOUBLES_EQUAL(257.839, aPoint.getX(), 0.0005);
895             CPPUNIT_ASSERT_DOUBLES_EQUAL(228.472, aPoint.getY(), 0.0005);
896             CPPUNIT_ASSERT(!pSegment->isClosed());
897 
898             pSegment = pPdfPageObject->getPathSegment(4);
899             CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType());
900             aPoint = pSegment->getPoint();
901             CPPUNIT_ASSERT_DOUBLES_EQUAL(245.395, aPoint.getX(), 0.0005);
902             CPPUNIT_ASSERT_DOUBLES_EQUAL(244.261, aPoint.getY(), 0.0005);
903             CPPUNIT_ASSERT(pSegment->isClosed());
904         }
905     }
906 
907     CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
908 }
909 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf118244_radioButtonGroup)910 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf118244_radioButtonGroup)
911 {
912     vcl::filter::PDFDocument aDocument;
913     load(u"tdf118244_radioButtonGroup.odt", aDocument);
914 
915     // The document has one page.
916     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
917     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
918 
919     // There are eight radio buttons.
920     auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
921     CPPUNIT_ASSERT(pAnnots);
922     CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio buttons", static_cast<size_t>(8),
923                                  pAnnots->GetElements().size());
924 
925     sal_uInt32 nRadioGroups = 0;
926     for (const auto& aElement : aDocument.GetElements())
927     {
928         auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
929         if (!pObject)
930             continue;
931         auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"));
932         if (pType && pType->GetValue() == "Btn")
933         {
934             auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("Kids"));
935             if (pKids)
936             {
937                 size_t expectedSize = 2;
938                 ++nRadioGroups;
939                 if (nRadioGroups == 3)
940                     expectedSize = 3;
941                 CPPUNIT_ASSERT_EQUAL(expectedSize, pKids->GetElements().size());
942             }
943         }
944     }
945     CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio groups", sal_uInt32(3), nRadioGroups);
946 }
947 
948 /// Test writing ToUnicode CMAP for LTR ligatures.
949 // This requires Carlito font, if it is missing the test will most likely
950 // fail.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf115117_1)951 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1)
952 {
953 #if HAVE_MORE_FONTS
954     vcl::filter::PDFDocument aDocument;
955     load(u"tdf115117-1.odt", aDocument);
956 
957     vcl::filter::PDFObjectElement* pToUnicode = nullptr;
958 
959     // Get access to ToUnicode of the first font
960     for (const auto& aElement : aDocument.GetElements())
961     {
962         auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
963         if (!pObject)
964             continue;
965         auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
966         if (pType && pType->GetValue() == "Font")
967         {
968             auto pToUnicodeRef
969                 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
970             CPPUNIT_ASSERT(pToUnicodeRef);
971             pToUnicode = pToUnicodeRef->LookupObject();
972             break;
973         }
974     }
975 
976     CPPUNIT_ASSERT(pToUnicode);
977     auto pStream = pToUnicode->GetStream();
978     CPPUNIT_ASSERT(pStream);
979     SvMemoryStream aObjectStream;
980     ZCodec aZCodec;
981     aZCodec.BeginCompression();
982     pStream->GetMemory().Seek(0);
983     aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
984     CPPUNIT_ASSERT(aZCodec.EndCompression());
985     aObjectStream.Seek(0);
986     // The first values, <01> <02> etc., are glyph ids, they might change order
987     // if we changed how font subsets are created.
988     // The second values, <00740069> etc., are Unicode code points in hex,
989     // <00740069> is U+0074 and U+0069 i.e. "ti" which is a ligature in
990     // Carlito/Calibri. This test is failing if any of the second values
991     // changed which means we are not detecting ligatures and writing CMAP
992     // entries for them correctly. If glyph order in the subset changes then
993     // the order here will changes and the PDF has to be carefully inspected to
994     // ensure that the new values are correct before updating the string below.
995     OString aCmap("9 beginbfchar\n"
996                   "<01> <00740069>\n"
997                   "<02> <0020>\n"
998                   "<03> <0074>\n"
999                   "<04> <0065>\n"
1000                   "<05> <0073>\n"
1001                   "<06> <00660069>\n"
1002                   "<07> <0066006C>\n"
1003                   "<08> <006600660069>\n"
1004                   "<09> <00660066006C>\n"
1005                   "endbfchar");
1006     auto pStart = static_cast<const char*>(aObjectStream.GetData());
1007     const char* pEnd = pStart + aObjectStream.GetSize();
1008     auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
1009     CPPUNIT_ASSERT(it != pEnd);
1010 #endif
1011 }
1012 
1013 /// Test writing ToUnicode CMAP for RTL ligatures.
1014 // This requires DejaVu Sans font, if it is missing the test will most likely
1015 // fail.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf115117_2)1016 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2)
1017 {
1018 #if HAVE_MORE_FONTS
1019     // See the comments in testTdf115117_1() for explanation.
1020 
1021     vcl::filter::PDFDocument aDocument;
1022     load(u"tdf115117-2.odt", aDocument);
1023 
1024     vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1025 
1026     for (const auto& aElement : aDocument.GetElements())
1027     {
1028         auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1029         if (!pObject)
1030             continue;
1031         auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1032         if (pType && pType->GetValue() == "Font")
1033         {
1034             auto pToUnicodeRef
1035                 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1036             CPPUNIT_ASSERT(pToUnicodeRef);
1037             pToUnicode = pToUnicodeRef->LookupObject();
1038             break;
1039         }
1040     }
1041 
1042     CPPUNIT_ASSERT(pToUnicode);
1043     auto pStream = pToUnicode->GetStream();
1044     CPPUNIT_ASSERT(pStream);
1045     SvMemoryStream aObjectStream;
1046     ZCodec aZCodec;
1047     aZCodec.BeginCompression();
1048     pStream->GetMemory().Seek(0);
1049     aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1050     CPPUNIT_ASSERT(aZCodec.EndCompression());
1051     aObjectStream.Seek(0);
1052     OString aCmap("7 beginbfchar\n"
1053                   "<01> <06440627>\n"
1054                   "<02> <0020>\n"
1055                   "<03> <0641>\n"
1056                   "<04> <0642>\n"
1057                   "<05> <0648>\n"
1058                   "<06> <06440627>\n"
1059                   "<07> <0628>\n"
1060                   "endbfchar");
1061     auto pStart = static_cast<const char*>(aObjectStream.GetData());
1062     const char* pEnd = pStart + aObjectStream.GetSize();
1063     auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
1064     CPPUNIT_ASSERT(it != pEnd);
1065 #endif
1066 }
1067 
1068 /// Text extracting LTR text with ligatures.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf115117_1a)1069 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1a)
1070 {
1071 #if HAVE_MORE_FONTS
1072     // Import the bugdoc and export as PDF.
1073     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-1.odt";
1074     mxComponent = loadFromDesktop(aURL);
1075     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1076     utl::MediaDescriptor aMediaDescriptor;
1077     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1078     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1079 
1080     // Parse the export result with pdfium.
1081     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1082     SvMemoryStream aMemory;
1083     aMemory.WriteStream(aFile);
1084     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1085     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
1086         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
1087     CPPUNIT_ASSERT(pPdfDocument);
1088 
1089     // The document has one page.
1090     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1091     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1092     CPPUNIT_ASSERT(pPdfPage);
1093 
1094     std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1095     CPPUNIT_ASSERT(pPdfTextPage);
1096 
1097     // Extract the text from the page. This pdfium API is a bit higher level
1098     // than we want and might apply heuristic that give false positive, but it
1099     // is a good approximation in addition to the check in testTdf115117_1().
1100     int nChars = pPdfTextPage->countChars();
1101     CPPUNIT_ASSERT_EQUAL(44, nChars);
1102 
1103     std::vector<sal_uInt32> aChars(nChars);
1104     for (int i = 0; i < nChars; i++)
1105         aChars[i] = pPdfTextPage->getUnicode(i);
1106     OUString aActualText(aChars.data(), aChars.size());
1107     CPPUNIT_ASSERT_EQUAL(OUString("ti ti test ti\r\nti test fi fl ffi ffl test fi"), aActualText);
1108 #endif
1109 }
1110 
1111 /// Test extracting RTL text with ligatures.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf115117_2a)1112 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2a)
1113 {
1114 #if HAVE_MORE_FONTS
1115     // See the comments in testTdf115117_1a() for explanation.
1116 
1117     // Import the bugdoc and export as PDF.
1118     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-2.odt";
1119     mxComponent = loadFromDesktop(aURL);
1120     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1121     utl::MediaDescriptor aMediaDescriptor;
1122     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1123     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1124 
1125     // Parse the export result with pdfium.
1126     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1127     SvMemoryStream aMemory;
1128     aMemory.WriteStream(aFile);
1129     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1130     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
1131         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
1132     CPPUNIT_ASSERT(pPdfDocument);
1133 
1134     // The document has one page.
1135     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1136     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1137     CPPUNIT_ASSERT(pPdfPage);
1138 
1139     std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1140     CPPUNIT_ASSERT(pPdfTextPage);
1141 
1142     int nChars = pPdfTextPage->countChars();
1143     CPPUNIT_ASSERT_EQUAL(13, nChars);
1144 
1145     std::vector<sal_uInt32> aChars(nChars);
1146     for (int i = 0; i < nChars; i++)
1147         aChars[i] = pPdfTextPage->getUnicode(i);
1148     OUString aActualText(aChars.data(), aChars.size());
1149     CPPUNIT_ASSERT_EQUAL(
1150         OUString(u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644"), aActualText);
1151 #endif
1152 }
1153 
1154 /// Test writing ToUnicode CMAP for doubly encoded glyphs.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf66597_1)1155 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_1)
1156 {
1157 #if HAVE_MORE_FONTS
1158     // This requires Amiri font, if it is missing the test will fail.
1159     vcl::filter::PDFDocument aDocument;
1160     load(u"tdf66597-1.odt", aDocument);
1161 
1162     {
1163         // Get access to ToUnicode of the first font
1164         vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1165         for (const auto& aElement : aDocument.GetElements())
1166         {
1167             auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1168             if (!pObject)
1169                 continue;
1170             auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1171             if (pType && pType->GetValue() == "Font")
1172             {
1173                 auto pName
1174                     = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
1175                 auto aName = pName->GetValue().copy(7); // skip the subset id
1176                 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("Amiri-Regular"),
1177                                              aName);
1178 
1179                 auto pToUnicodeRef
1180                     = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1181                 CPPUNIT_ASSERT(pToUnicodeRef);
1182                 pToUnicode = pToUnicodeRef->LookupObject();
1183                 break;
1184             }
1185         }
1186 
1187         CPPUNIT_ASSERT(pToUnicode);
1188         auto pStream = pToUnicode->GetStream();
1189         CPPUNIT_ASSERT(pStream);
1190         SvMemoryStream aObjectStream;
1191         ZCodec aZCodec;
1192         aZCodec.BeginCompression();
1193         pStream->GetMemory().Seek(0);
1194         aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1195         CPPUNIT_ASSERT(aZCodec.EndCompression());
1196         aObjectStream.Seek(0);
1197         // The <01> is glyph id, <0020> is code point.
1198         // The document has three characters <space><nbspace><space>, but the font
1199         // reuses the same glyph for space and nbspace so we should have a single
1200         // CMAP entry for the space, and nbspace will be handled with ActualText
1201         // (tested above).
1202         std::string aCmap("1 beginbfchar\n"
1203                           "<01> <0020>\n"
1204                           "endbfchar");
1205         std::string aData(static_cast<const char*>(aObjectStream.GetData()),
1206                           aObjectStream.GetSize());
1207         auto nPos = aData.find(aCmap);
1208         CPPUNIT_ASSERT(nPos != std::string::npos);
1209     }
1210 
1211     {
1212         auto aPages = aDocument.GetPages();
1213         CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1214         // Get page contents and stream.
1215         auto pContents = aPages[0]->LookupObject("Contents");
1216         CPPUNIT_ASSERT(pContents);
1217         auto pStream = pContents->GetStream();
1218         CPPUNIT_ASSERT(pStream);
1219         auto& rObjectStream = pStream->GetMemory();
1220 
1221         // Uncompress the stream.
1222         SvMemoryStream aUncompressed;
1223         ZCodec aZCodec;
1224         aZCodec.BeginCompression();
1225         rObjectStream.Seek(0);
1226         aZCodec.Decompress(rObjectStream, aUncompressed);
1227         CPPUNIT_ASSERT(aZCodec.EndCompression());
1228 
1229         // Make sure the expected ActualText is present.
1230         std::string aData(static_cast<const char*>(aUncompressed.GetData()),
1231                           aUncompressed.GetSize());
1232 
1233         std::string aActualText("/Span<</ActualText<");
1234         size_t nCount = 0;
1235         size_t nPos = 0;
1236         while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1237         {
1238             nCount++;
1239             nPos += aActualText.length();
1240         }
1241         CPPUNIT_ASSERT_EQUAL_MESSAGE("The should be one ActualText entry!", static_cast<size_t>(1),
1242                                      nCount);
1243 
1244         aActualText = "/Span<</ActualText<FEFF00A0>>>";
1245         nPos = aData.find(aActualText);
1246         CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos);
1247     }
1248 #endif
1249 }
1250 
1251 /// Test writing ActualText for RTL many to one glyph to Unicode mapping.
1252 // This requires Reem Kufi font, if it is missing the test will fail.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf66597_2)1253 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_2)
1254 {
1255 #if HAVE_MORE_FONTS
1256     vcl::filter::PDFDocument aDocument;
1257     load(u"tdf66597-2.odt", aDocument);
1258 
1259     {
1260         // Get access to ToUnicode of the first font
1261         vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1262         for (const auto& aElement : aDocument.GetElements())
1263         {
1264             auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1265             if (!pObject)
1266                 continue;
1267             auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1268             if (pType && pType->GetValue() == "Font")
1269             {
1270                 auto pName
1271                     = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
1272                 auto aName = pName->GetValue().copy(7); // skip the subset id
1273                 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("ReemKufi-Regular"),
1274                                              aName);
1275 
1276                 auto pToUnicodeRef
1277                     = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1278                 CPPUNIT_ASSERT(pToUnicodeRef);
1279                 pToUnicode = pToUnicodeRef->LookupObject();
1280                 break;
1281             }
1282         }
1283 
1284         CPPUNIT_ASSERT(pToUnicode);
1285         auto pStream = pToUnicode->GetStream();
1286         CPPUNIT_ASSERT(pStream);
1287         SvMemoryStream aObjectStream;
1288         ZCodec aZCodec;
1289         aZCodec.BeginCompression();
1290         pStream->GetMemory().Seek(0);
1291         aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1292         CPPUNIT_ASSERT(aZCodec.EndCompression());
1293         aObjectStream.Seek(0);
1294         std::string aCmap("8 beginbfchar\n"
1295                           "<02> <0632>\n"
1296                           "<03> <0020>\n"
1297                           "<04> <0648>\n"
1298                           "<05> <0647>\n"
1299                           "<06> <062F>\n"
1300                           "<08> <062C>\n"
1301                           "<09> <0628>\n"
1302                           "<0B> <0623>\n"
1303                           "endbfchar");
1304         std::string aData(static_cast<const char*>(aObjectStream.GetData()),
1305                           aObjectStream.GetSize());
1306         auto nPos = aData.find(aCmap);
1307         CPPUNIT_ASSERT(nPos != std::string::npos);
1308     }
1309 
1310     {
1311         auto aPages = aDocument.GetPages();
1312         CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1313         // Get page contents and stream.
1314         auto pContents = aPages[0]->LookupObject("Contents");
1315         CPPUNIT_ASSERT(pContents);
1316         auto pStream = pContents->GetStream();
1317         CPPUNIT_ASSERT(pStream);
1318         auto& rObjectStream = pStream->GetMemory();
1319 
1320         // Uncompress the stream.
1321         SvMemoryStream aUncompressed;
1322         ZCodec aZCodec;
1323         aZCodec.BeginCompression();
1324         rObjectStream.Seek(0);
1325         aZCodec.Decompress(rObjectStream, aUncompressed);
1326         CPPUNIT_ASSERT(aZCodec.EndCompression());
1327 
1328         // Make sure the expected ActualText is present.
1329         std::string aData(static_cast<const char*>(aUncompressed.GetData()),
1330                           aUncompressed.GetSize());
1331 
1332         std::vector<std::string> aCodes({ "0632", "062C", "0628", "0623" });
1333         std::string aActualText("/Span<</ActualText<");
1334         size_t nCount = 0;
1335         size_t nPos = 0;
1336         while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1337         {
1338             nCount++;
1339             nPos += aActualText.length();
1340         }
1341         CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", aCodes.size(),
1342                                      nCount);
1343 
1344         for (const auto& aCode : aCodes)
1345         {
1346             aActualText = "/Span<</ActualText<FEFF" + aCode + ">>>";
1347             nPos = aData.find(aActualText);
1348             CPPUNIT_ASSERT_MESSAGE("ActualText not found for " + aCode, nPos != std::string::npos);
1349         }
1350     }
1351 #endif
1352 }
1353 
1354 /// Test writing ActualText for LTR many to one glyph to Unicode mapping.
1355 // This requires Gentium Basic font, if it is missing the test will fail.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf66597_3)1356 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_3)
1357 {
1358 #if HAVE_MORE_FONTS
1359     vcl::filter::PDFDocument aDocument;
1360     load(u"tdf66597-3.odt", aDocument);
1361 
1362     {
1363         // Get access to ToUnicode of the first font
1364         vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1365         for (const auto& aElement : aDocument.GetElements())
1366         {
1367             auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1368             if (!pObject)
1369                 continue;
1370             auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1371             if (pType && pType->GetValue() == "Font")
1372             {
1373                 auto pName
1374                     = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
1375                 auto aName = pName->GetValue().copy(7); // skip the subset id
1376                 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("GentiumBasic"),
1377                                              aName);
1378 
1379                 auto pToUnicodeRef
1380                     = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1381                 CPPUNIT_ASSERT(pToUnicodeRef);
1382                 pToUnicode = pToUnicodeRef->LookupObject();
1383                 break;
1384             }
1385         }
1386 
1387         CPPUNIT_ASSERT(pToUnicode);
1388         auto pStream = pToUnicode->GetStream();
1389         CPPUNIT_ASSERT(pStream);
1390         SvMemoryStream aObjectStream;
1391         ZCodec aZCodec;
1392         aZCodec.BeginCompression();
1393         pStream->GetMemory().Seek(0);
1394         aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1395         CPPUNIT_ASSERT(aZCodec.EndCompression());
1396         aObjectStream.Seek(0);
1397         std::string aCmap("2 beginbfchar\n"
1398                           "<01> <1ECB0331030B>\n"
1399                           "<05> <0020>\n"
1400                           "endbfchar");
1401         std::string aData(static_cast<const char*>(aObjectStream.GetData()),
1402                           aObjectStream.GetSize());
1403         auto nPos = aData.find(aCmap);
1404         CPPUNIT_ASSERT(nPos != std::string::npos);
1405     }
1406 
1407     {
1408         auto aPages = aDocument.GetPages();
1409         CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1410         // Get page contents and stream.
1411         auto pContents = aPages[0]->LookupObject("Contents");
1412         CPPUNIT_ASSERT(pContents);
1413         auto pStream = pContents->GetStream();
1414         CPPUNIT_ASSERT(pStream);
1415         auto& rObjectStream = pStream->GetMemory();
1416 
1417         // Uncompress the stream.
1418         SvMemoryStream aUncompressed;
1419         ZCodec aZCodec;
1420         aZCodec.BeginCompression();
1421         rObjectStream.Seek(0);
1422         aZCodec.Decompress(rObjectStream, aUncompressed);
1423         CPPUNIT_ASSERT(aZCodec.EndCompression());
1424 
1425         // Make sure the expected ActualText is present.
1426         std::string aData(static_cast<const char*>(aUncompressed.GetData()),
1427                           aUncompressed.GetSize());
1428 
1429         std::string aActualText("/Span<</ActualText<FEFF1ECB0331030B>>>");
1430         size_t nCount = 0;
1431         size_t nPos = 0;
1432         while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1433         {
1434             nCount++;
1435             nPos += aActualText.length();
1436         }
1437         CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!",
1438                                      static_cast<size_t>(4), nCount);
1439     }
1440 #endif
1441 }
1442 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf105954)1443 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105954)
1444 {
1445     // Import the bugdoc and export as PDF.
1446     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105954.odt";
1447     mxComponent = loadFromDesktop(aURL);
1448     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1449     utl::MediaDescriptor aMediaDescriptor;
1450     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1451     uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
1452         { { "ReduceImageResolution", uno::Any(true) },
1453           { "MaxImageResolution", uno::Any(static_cast<sal_Int32>(300)) } }));
1454     aMediaDescriptor["FilterData"] <<= aFilterData;
1455     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1456 
1457     // Parse the export result with pdfium.
1458     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1459     SvMemoryStream aMemory;
1460     aMemory.WriteStream(aFile);
1461     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1462     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
1463         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
1464     CPPUNIT_ASSERT(pPdfDocument);
1465 
1466     // The document has one page.
1467     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1468     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1469     CPPUNIT_ASSERT(pPdfPage);
1470 
1471     // There is a single image on the page.
1472     int nPageObjectCount = pPdfPage->getObjectCount();
1473     CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount);
1474 
1475     // Check width of the image.
1476     std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(/*index=*/0);
1477     Size aMeta = pPageObject->getImageSize(*pPdfPage);
1478     // This was 2000, i.e. the 'reduce to 300 DPI' request was ignored.
1479     // This is now around 238 (228 on macOS).
1480     CPPUNIT_ASSERT_LESS(static_cast<tools::Long>(250), aMeta.getWidth());
1481 }
1482 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf128630)1483 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128630)
1484 {
1485     // Import the bugdoc and export as PDF.
1486     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf128630.odp";
1487     utl::MediaDescriptor aMediaDescriptor;
1488     aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
1489     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1490 
1491     // The document has one page.
1492     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1493 
1494     // Assert the aspect ratio of the only bitmap on the page.
1495     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1496     CPPUNIT_ASSERT(pPdfPage);
1497     int nPageObjectCount = pPdfPage->getObjectCount();
1498     for (int i = 0; i < nPageObjectCount; ++i)
1499     {
1500         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1501         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1502             continue;
1503 
1504         std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
1505         CPPUNIT_ASSERT(pBitmap);
1506         int nWidth = pBitmap->getWidth();
1507         int nHeight = pBitmap->getHeight();
1508         // Without the accompanying fix in place, this test would have failed with:
1509         // assertion failed
1510         // - Expression: nWidth != nHeight
1511         // i.e. the bitmap lost its custom aspect ratio during export.
1512         CPPUNIT_ASSERT(nWidth != nHeight);
1513     }
1514 }
1515 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf106702)1516 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106702)
1517 {
1518     // Import the bugdoc and export as PDF.
1519     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106702.odt";
1520     utl::MediaDescriptor aMediaDescriptor;
1521     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1522     auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1523 
1524     // The document has two pages.
1525     CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
1526 
1527     // First page already has the correct image position.
1528     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1529     CPPUNIT_ASSERT(pPdfPage);
1530     int nExpected = 0;
1531     int nPageObjectCount = pPdfPage->getObjectCount();
1532     for (int i = 0; i < nPageObjectCount; ++i)
1533     {
1534         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1535         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1536             continue;
1537 
1538         // Top, but upside down.
1539         nExpected = pPageObject->getBounds().getMaxY();
1540         break;
1541     }
1542 
1543     // Second page had an incorrect image position.
1544     pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
1545     CPPUNIT_ASSERT(pPdfPage);
1546     int nActual = 0;
1547     nPageObjectCount = pPdfPage->getObjectCount();
1548     for (int i = 0; i < nPageObjectCount; ++i)
1549     {
1550         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1551         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1552             continue;
1553 
1554         // Top, but upside down.
1555         nActual = pPageObject->getBounds().getMaxY();
1556         break;
1557     }
1558 
1559     // This failed, vertical pos is 818 points, was 1674 (outside visible page
1560     // bounds).
1561     CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
1562 }
1563 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf113143)1564 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf113143)
1565 {
1566     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf113143.odp";
1567     utl::MediaDescriptor aMediaDescriptor;
1568     aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
1569     uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
1570         { "ExportNotesPages", uno::Any(true) },
1571         // ReduceImageResolution is on by default and that hides the bug we
1572         // want to test.
1573         { "ReduceImageResolution", uno::Any(false) },
1574         // Set a custom PDF version.
1575         { "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32>(16)) },
1576     }));
1577     aMediaDescriptor["FilterData"] <<= aFilterData;
1578     auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1579 
1580     // The document has two pages.
1581     CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
1582 
1583     // First has the original (larger) image.
1584     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1585     CPPUNIT_ASSERT(pPdfPage);
1586     int nLarger = 0;
1587     int nPageObjectCount = pPdfPage->getObjectCount();
1588     for (int i = 0; i < nPageObjectCount; ++i)
1589     {
1590         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1591         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1592             continue;
1593 
1594         nLarger = pPageObject->getBounds().getWidth();
1595         break;
1596     }
1597 
1598     // Second page has the scaled (smaller) image.
1599     pPdfPage = pPdfDocument->openPage(/*nIndex=*/1);
1600     CPPUNIT_ASSERT(pPdfPage);
1601     int nSmaller = 0;
1602     nPageObjectCount = pPdfPage->getObjectCount();
1603     for (int i = 0; i < nPageObjectCount; ++i)
1604     {
1605         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1606         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
1607             continue;
1608 
1609         nSmaller = pPageObject->getBounds().getWidth();
1610         break;
1611     }
1612 
1613     // This failed, both were 319, now nSmaller is 169.
1614     CPPUNIT_ASSERT_LESS(nLarger, nSmaller);
1615 
1616     // The following check used to fail in the past, header was "%PDF-1.5":
1617     maMemory.Seek(0);
1618     OString aExpectedHeader("%PDF-1.6");
1619     OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength()));
1620     CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader);
1621 }
1622 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testForcePoint71)1623 CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint71)
1624 {
1625     // I just care it doesn't crash
1626     saveAsPDF(u"forcepoint71.key");
1627 }
1628 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf115262)1629 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115262)
1630 {
1631     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115262.ods";
1632     utl::MediaDescriptor aMediaDescriptor;
1633     aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
1634     auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1635     CPPUNIT_ASSERT_EQUAL(8, pPdfDocument->getPageCount());
1636 
1637     // Get the 6th page.
1638     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/5);
1639     CPPUNIT_ASSERT(pPdfPage);
1640 
1641     // Look up the position of the first image and the 400th row.
1642     std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
1643     int nPageObjectCount = pPdfPage->getObjectCount();
1644     int nFirstImageTop = 0;
1645     int nRowTop = 0;
1646     for (int i = 0; i < nPageObjectCount; ++i)
1647     {
1648         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1649         // Top, but upside down.
1650         float fTop = pPageObject->getBounds().getMaxY();
1651 
1652         if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Image)
1653         {
1654             nFirstImageTop = fTop;
1655         }
1656         else if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
1657         {
1658             OUString sText = pPageObject->getText(pTextPage);
1659             if (sText == "400")
1660                 nRowTop = fTop;
1661         }
1662     }
1663     // Make sure that the top of the "400" is below the top of the image (in
1664     // bottom-right-corner-based PDF coordinates).
1665     // This was: expected less than 144, actual is 199.
1666     CPPUNIT_ASSERT_LESS(nFirstImageTop, nRowTop);
1667 }
1668 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf121962)1669 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf121962)
1670 {
1671     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf121962.odt";
1672     utl::MediaDescriptor aMediaDescriptor;
1673     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1674     auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1675     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1676 
1677     // Get the first page
1678     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1679     CPPUNIT_ASSERT(pPdfPage);
1680     std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
1681 
1682     // Make sure the table sum is displayed as "0", not faulty expression.
1683     int nPageObjectCount = pPdfPage->getObjectCount();
1684     for (int i = 0; i < nPageObjectCount; ++i)
1685     {
1686         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1687         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
1688             continue;
1689         OUString sText = pPageObject->getText(pTextPage);
1690         CPPUNIT_ASSERT(sText != "** Expression is faulty **");
1691     }
1692 }
1693 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf115967)1694 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115967)
1695 {
1696     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115967.odt";
1697     utl::MediaDescriptor aMediaDescriptor;
1698     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1699     auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1700     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1701 
1702     // Get the first page
1703     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1704     CPPUNIT_ASSERT(pPdfPage);
1705     std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
1706 
1707     // Make sure the elements inside a formula in a RTL document are exported
1708     // LTR ( m=750abc ) and not RTL ( m=057cba )
1709     int nPageObjectCount = pPdfPage->getObjectCount();
1710     OUString sText;
1711     for (int i = 0; i < nPageObjectCount; ++i)
1712     {
1713         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
1714         if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text)
1715             continue;
1716         OUString sChar = pPageObject->getText(pTextPage);
1717         sText += sChar.trim();
1718     }
1719     CPPUNIT_ASSERT_EQUAL(OUString("m=750abc"), sText);
1720 }
1721 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf124272)1722 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf124272)
1723 {
1724     // Import the bugdoc and export as PDF.
1725     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf124272.odt";
1726     mxComponent = loadFromDesktop(aURL);
1727 
1728     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1729     utl::MediaDescriptor aMediaDescriptor;
1730     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1731     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1732 
1733     // Parse the export result.
1734     vcl::filter::PDFDocument aDocument;
1735     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1736     CPPUNIT_ASSERT(aDocument.Read(aStream));
1737 
1738     // The document has one page.
1739     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1740     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1741 
1742     // The page has a stream.
1743     vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
1744     CPPUNIT_ASSERT(pContents);
1745     vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1746     CPPUNIT_ASSERT(pStream);
1747     SvMemoryStream& rObjectStream = pStream->GetMemory();
1748     // Uncompress it.
1749     SvMemoryStream aUncompressed;
1750     ZCodec aZCodec;
1751     aZCodec.BeginCompression();
1752     rObjectStream.Seek(0);
1753     aZCodec.Decompress(rObjectStream, aUncompressed);
1754     CPPUNIT_ASSERT(aZCodec.EndCompression());
1755 
1756     OString aBitmap("Q q 299.899 782.189 m\n"
1757                     "55.2 435.889 l 299.899 435.889 l 299.899 782.189 l\n"
1758                     "h");
1759 
1760     auto pStart = static_cast<const char*>(aUncompressed.GetData());
1761     const char* pEnd = pStart + aUncompressed.GetSize();
1762     auto it = std::search(pStart, pEnd, aBitmap.getStr(), aBitmap.getStr() + aBitmap.getLength());
1763     CPPUNIT_ASSERT(it != pEnd);
1764 }
1765 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf121615)1766 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf121615)
1767 {
1768     vcl::filter::PDFDocument aDocument;
1769     load(u"tdf121615.odt", aDocument);
1770 
1771     // The document has one page.
1772     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1773     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1774 
1775     // Get access to the only image on the only page.
1776     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
1777     CPPUNIT_ASSERT(pResources);
1778     auto pXObjects
1779         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
1780     CPPUNIT_ASSERT(pXObjects);
1781     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
1782     vcl::filter::PDFObjectElement* pXObject
1783         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
1784     CPPUNIT_ASSERT(pXObject);
1785     vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
1786     CPPUNIT_ASSERT(pStream);
1787     SvMemoryStream& rObjectStream = pStream->GetMemory();
1788 
1789     // Load the embedded image.
1790     rObjectStream.Seek(0);
1791     GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
1792     Graphic aGraphic;
1793     sal_uInt16 format;
1794     ErrCode bResult = rFilter.ImportGraphic(aGraphic, OUString("import"), rObjectStream,
1795                                             GRFILTER_FORMAT_DONTKNOW, &format);
1796     CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
1797 
1798     // The image should be grayscale 8bit JPEG.
1799     sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
1800     CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND);
1801     CPPUNIT_ASSERT_EQUAL(jpegFormat, format);
1802     BitmapEx aBitmap = aGraphic.GetBitmapEx();
1803     CPPUNIT_ASSERT_EQUAL(tools::Long(200), aBitmap.GetSizePixel().Width());
1804     CPPUNIT_ASSERT_EQUAL(tools::Long(300), aBitmap.GetSizePixel().Height());
1805     CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
1806     // tdf#121615 was caused by broken handling of data width with 8bit color,
1807     // so the test image has some black in the bottomright corner, check it's there
1808     CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 0));
1809     CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 299));
1810     CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(199, 0));
1811     CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmap.GetPixelColor(199, 299));
1812 }
1813 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf141171)1814 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf141171)
1815 {
1816     vcl::filter::PDFDocument aDocument;
1817     load(u"tdf141171.odt", aDocument);
1818 
1819     // The document has one page.
1820     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1821     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1822 
1823     // Get access to the only image on the only page.
1824     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
1825     CPPUNIT_ASSERT(pResources);
1826     auto pXObjects
1827         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
1828     CPPUNIT_ASSERT(pXObjects);
1829     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
1830     vcl::filter::PDFObjectElement* pXObject
1831         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
1832     CPPUNIT_ASSERT(pXObject);
1833     vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
1834     CPPUNIT_ASSERT(pStream);
1835     SvMemoryStream& rObjectStream = pStream->GetMemory();
1836 
1837     // Load the embedded image.
1838     rObjectStream.Seek(0);
1839     GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
1840     Graphic aGraphic;
1841     sal_uInt16 format;
1842     ErrCode bResult = rFilter.ImportGraphic(aGraphic, OUString("import"), rObjectStream,
1843                                             GRFILTER_FORMAT_DONTKNOW, &format);
1844     CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
1845 
1846     // The image should be grayscale 8bit JPEG.
1847     sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
1848     CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND);
1849     CPPUNIT_ASSERT_EQUAL(jpegFormat, format);
1850     BitmapEx aBitmap = aGraphic.GetBitmapEx();
1851     Size aSize = aBitmap.GetSizePixel();
1852     CPPUNIT_ASSERT_EQUAL(tools::Long(878), aSize.Width());
1853     CPPUNIT_ASSERT_EQUAL(tools::Long(127), aSize.Height());
1854     CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat());
1855 
1856     for (tools::Long nX = 0; nX < aSize.Width(); ++nX)
1857     {
1858         for (tools::Long nY = 0; nY < aSize.Height(); ++nY)
1859         {
1860             // Check all pixels in the image are white
1861             // Without the fix in place, this test would have failed with
1862             // - Expected: Color: R:255 G:255 B:255 A:0
1863             // - Actual  : Color: R:0 G:0 B:0 A:0
1864             CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(nX, nY));
1865         }
1866     }
1867 }
1868 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf129085)1869 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf129085)
1870 {
1871     vcl::filter::PDFDocument aDocument;
1872     load(u"tdf129085.docx", aDocument);
1873 
1874     // The document has one page.
1875     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1876     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1877 
1878     // Get access to the only image on the only page.
1879     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
1880     CPPUNIT_ASSERT(pResources);
1881     auto pXObjects
1882         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
1883 
1884     // Without the fix in place, this test would have failed here
1885     CPPUNIT_ASSERT(pXObjects);
1886     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
1887     vcl::filter::PDFObjectElement* pXObject
1888         = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
1889     CPPUNIT_ASSERT(pXObject);
1890     vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
1891     CPPUNIT_ASSERT(pStream);
1892     SvMemoryStream& rObjectStream = pStream->GetMemory();
1893 
1894     // Load the embedded image.
1895     rObjectStream.Seek(0);
1896     GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
1897     Graphic aGraphic;
1898     sal_uInt16 format;
1899     ErrCode bResult = rFilter.ImportGraphic(aGraphic, OUString("import"), rObjectStream,
1900                                             GRFILTER_FORMAT_DONTKNOW, &format);
1901     CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
1902 
1903     sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME);
1904     CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND);
1905     CPPUNIT_ASSERT_EQUAL(jpegFormat, format);
1906     BitmapEx aBitmap = aGraphic.GetBitmapEx();
1907     CPPUNIT_ASSERT_EQUAL(tools::Long(884), aBitmap.GetSizePixel().Width());
1908     CPPUNIT_ASSERT_EQUAL(tools::Long(925), aBitmap.GetSizePixel().Height());
1909     CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat());
1910 }
1911 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTocLink)1912 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTocLink)
1913 {
1914     // Load the Writer document.
1915     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "toc-link.fodt";
1916     mxComponent = loadFromDesktop(aURL);
1917 
1918     // Update the ToC.
1919     uno::Reference<text::XDocumentIndexesSupplier> xDocumentIndexesSupplier(mxComponent,
1920                                                                             uno::UNO_QUERY);
1921     CPPUNIT_ASSERT(xDocumentIndexesSupplier.is());
1922 
1923     uno::Reference<util::XRefreshable> xToc(
1924         xDocumentIndexesSupplier->getDocumentIndexes()->getByIndex(0), uno::UNO_QUERY);
1925     CPPUNIT_ASSERT(xToc.is());
1926 
1927     xToc->refresh();
1928 
1929     // Save as PDF.
1930     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1931     utl::MediaDescriptor aMediaDescriptor;
1932     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1933     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1934 
1935     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1936     maMemory.WriteStream(aFile);
1937     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1938     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
1939         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
1940     CPPUNIT_ASSERT(pPdfDocument);
1941     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1942 
1943     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1944     CPPUNIT_ASSERT(pPdfPage);
1945 
1946     // Ensure there is a link on the first page (in the ToC).
1947     // Without the accompanying fix in place, this test would have failed, as the page contained no
1948     // links.
1949     CPPUNIT_ASSERT(pPdfPage->hasLinks());
1950 }
1951 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testReduceSmallImage)1952 CPPUNIT_TEST_FIXTURE(PdfExportTest, testReduceSmallImage)
1953 {
1954     // Load the Writer document.
1955     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "reduce-small-image.fodt";
1956     mxComponent = loadFromDesktop(aURL);
1957 
1958     // Save as PDF.
1959     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1960     utl::MediaDescriptor aMediaDescriptor;
1961     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1962     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1963 
1964     // Parse the PDF: get the image.
1965     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1966     maMemory.WriteStream(aFile);
1967     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1968     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
1969         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
1970     CPPUNIT_ASSERT(pPdfDocument);
1971     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1972     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1973     CPPUNIT_ASSERT(pPdfPage);
1974     CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
1975     std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
1976     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pPageObject->getType());
1977 
1978     // Make sure we don't scale down a tiny bitmap.
1979     std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
1980     CPPUNIT_ASSERT(pBitmap);
1981     int nWidth = pBitmap->getWidth();
1982     int nHeight = pBitmap->getHeight();
1983     // Without the accompanying fix in place, this test would have failed with:
1984     // - Expected: 16
1985     // - Actual  : 6
1986     // i.e. the image was scaled down to 300 DPI, even if it had tiny size.
1987     CPPUNIT_ASSERT_EQUAL(16, nWidth);
1988     CPPUNIT_ASSERT_EQUAL(16, nHeight);
1989 }
1990 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testReduceImage)1991 CPPUNIT_TEST_FIXTURE(PdfExportTest, testReduceImage)
1992 {
1993     // Load the Writer document.
1994     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "reduce-image.fodt";
1995     mxComponent = loadFromDesktop(aURL);
1996 
1997     // Save as PDF.
1998     uno::Reference<css::lang::XMultiServiceFactory> xFactory = getMultiServiceFactory();
1999     uno::Reference<document::XFilter> xFilter(
2000         xFactory->createInstance("com.sun.star.document.PDFFilter"), uno::UNO_QUERY);
2001     uno::Reference<document::XExporter> xExporter(xFilter, uno::UNO_QUERY);
2002     xExporter->setSourceDocument(mxComponent);
2003 
2004     SvFileStream aOutputStream(maTempFile.GetURL(), StreamMode::WRITE);
2005     uno::Reference<io::XOutputStream> xOutputStream(new utl::OStreamWrapper(aOutputStream));
2006 
2007     uno::Sequence<beans::PropertyValue> aFilterData(
2008         comphelper::InitPropertySequence({ { "ReduceImageResolution", uno::Any(false) } }));
2009 
2010     // This is intentionally in an "unlucky" order, output stream comes before filter data.
2011     uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence({
2012         { "FilterName", uno::Any(OUString("writer_pdf_Export")) },
2013         { "OutputStream", uno::Any(xOutputStream) },
2014         { "FilterData", uno::Any(aFilterData) },
2015     }));
2016     xFilter->filter(aDescriptor);
2017     aOutputStream.Close();
2018 
2019     // Parse the PDF: get the image.
2020     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2021     maMemory.WriteStream(aFile);
2022     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2023     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2024         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
2025     CPPUNIT_ASSERT(pPdfDocument);
2026     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2027     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2028     CPPUNIT_ASSERT(pPdfPage);
2029     CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
2030     std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
2031     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pPageObject->getType());
2032 
2033     // Make sure we don't scale down a bitmap.
2034     std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
2035     CPPUNIT_ASSERT(pBitmap);
2036     int nWidth = pBitmap->getWidth();
2037     int nHeight = pBitmap->getHeight();
2038     // Without the accompanying fix in place, this test would have failed with:
2039     // - Expected: 160
2040     // - Actual  : 6
2041     // i.e. the image was scaled down even with ReduceImageResolution=false.
2042     CPPUNIT_ASSERT_EQUAL(160, nWidth);
2043     CPPUNIT_ASSERT_EQUAL(160, nHeight);
2044 }
2045 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testLinkWrongPage)2046 CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWrongPage)
2047 {
2048     // Import the bugdoc and export as PDF.
2049     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "link-wrong-page.odp";
2050     utl::MediaDescriptor aMediaDescriptor;
2051     aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
2052     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
2053 
2054     // The document has 2 pages.
2055     CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
2056 
2057     // First page should have 1 link (2nd slide, 1st was hidden).
2058     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2059     CPPUNIT_ASSERT(pPdfPage);
2060 
2061     // Without the accompanying fix in place, this test would have failed, as the link of the first
2062     // page went to the second page due to the hidden first slide.
2063     CPPUNIT_ASSERT(pPdfPage->hasLinks());
2064 
2065     // Second page should have no links (3rd slide).
2066     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1);
2067     CPPUNIT_ASSERT(pPdfPage2);
2068     CPPUNIT_ASSERT(!pPdfPage2->hasLinks());
2069 }
2070 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testLinkWrongPagePartial)2071 CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWrongPagePartial)
2072 {
2073     // Given a Draw document with 3 pages, a link on the 2nd page:
2074     // When exporting that the 2nd and 3rd page to pdf:
2075     uno::Sequence<beans::PropertyValue> aFilterData = {
2076         comphelper::makePropertyValue("PageRange", OUString("2-3")),
2077     };
2078 
2079     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "link-wrong-page-partial.odg";
2080     utl::MediaDescriptor aMediaDescriptor;
2081     aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
2082     aMediaDescriptor["FilterData"] <<= aFilterData;
2083 
2084     // Then make sure the we have a link on the 1st page, but not on the 2nd one:
2085     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
2086     CPPUNIT_ASSERT(pPdfDocument);
2087     CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
2088     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2089     CPPUNIT_ASSERT(pPdfPage);
2090     // Without the accompanying fix in place, this test would have failed, as the link was on the
2091     // 2nd page instead.
2092     CPPUNIT_ASSERT(pPdfPage->hasLinks());
2093     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1);
2094     CPPUNIT_ASSERT(pPdfPage2);
2095     CPPUNIT_ASSERT(!pPdfPage2->hasLinks());
2096 }
2097 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testLargePage)2098 CPPUNIT_TEST_FIXTURE(PdfExportTest, testLargePage)
2099 {
2100     // Import the bugdoc and export as PDF.
2101     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "6m-wide.odg";
2102     utl::MediaDescriptor aMediaDescriptor;
2103     aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
2104     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
2105 
2106     // The document has 1 page.
2107     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2108     // Check the value (not the unit) of the page size.
2109     basegfx::B2DSize aSize = pPdfDocument->getPageSize(0);
2110     // Without the accompanying fix in place, this test would have failed with:
2111     // - Expected: 8503.94
2112     // - Actual  : 17007.875
2113     // i.e. the value for 600 cm was larger than the 14 400 limit set in the spec.
2114     CPPUNIT_ASSERT_DOUBLES_EQUAL(8503.94, aSize.getX(), 0.01);
2115 }
2116 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testPdfImageResourceInlineXObjectRef)2117 CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageResourceInlineXObjectRef)
2118 {
2119     // Create an empty document.
2120     mxComponent = loadFromDesktop("private:factory/swriter");
2121     uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
2122     uno::Reference<text::XText> xText = xTextDocument->getText();
2123     uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
2124 
2125     // Insert the PDF image.
2126     uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
2127     uno::Reference<beans::XPropertySet> xGraphicObject(
2128         xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
2129     OUString aURL
2130         = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-resource-inline-xobject-ref.pdf";
2131     xGraphicObject->setPropertyValue("GraphicURL", uno::makeAny(aURL));
2132     uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
2133     xShape->setSize(awt::Size(1000, 1000));
2134     uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
2135     xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
2136 
2137     // Save as PDF.
2138     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2139     utl::MediaDescriptor aMediaDescriptor;
2140     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2141     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2142 
2143     // Parse the export result.
2144     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2145     maMemory.WriteStream(aFile);
2146     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2147     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2148         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
2149     CPPUNIT_ASSERT(pPdfDocument);
2150     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2151 
2152     // Make sure that the page -> form -> form has a child image.
2153     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2154     CPPUNIT_ASSERT(pPdfPage);
2155     CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
2156     std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
2157     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
2158     // 2: white background and the actual object.
2159     CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
2160     std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject = pPageObject->getFormObject(1);
2161     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType());
2162     // Without the accompanying fix in place, this test would have failed with:
2163     // - Expected: 1
2164     // - Actual  : 0
2165     // i.e. the sub-form was missing its image.
2166     CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount());
2167 
2168     // Check if the inner form object (original page object in the pdf image) has the correct
2169     // rotation.
2170     std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject = pFormObject->getFormObject(0);
2171     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType());
2172     CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount());
2173     std::unique_ptr<vcl::pdf::PDFiumPageObject> pImage = pInnerFormObject->getFormObject(0);
2174     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType());
2175     basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix();
2176     basegfx::B2DTuple aScale;
2177     basegfx::B2DTuple aTranslate;
2178     double fRotate = 0;
2179     double fShearX = 0;
2180     aMat.decompose(aScale, aTranslate, fRotate, fShearX);
2181     int nRotateDeg = basegfx::rad2deg(fRotate);
2182     // Without the accompanying fix in place, this test would have failed with:
2183     // - Expected: -90
2184     // - Actual  : 0
2185     // i.e. rotation was lost on pdf export.
2186     CPPUNIT_ASSERT_EQUAL(-90, nRotateDeg);
2187 }
2188 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testDefaultVersion)2189 CPPUNIT_TEST_FIXTURE(PdfExportTest, testDefaultVersion)
2190 {
2191     // Create an empty document.
2192     mxComponent = loadFromDesktop("private:factory/swriter");
2193 
2194     // Save as PDF.
2195     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2196     utl::MediaDescriptor aMediaDescriptor;
2197     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2198     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2199 
2200     // Parse the export result.
2201     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2202     maMemory.WriteStream(aFile);
2203     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2204     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2205         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
2206     CPPUNIT_ASSERT(pPdfDocument);
2207     int nFileVersion = pPdfDocument->getFileVersion();
2208     CPPUNIT_ASSERT_EQUAL(16, nFileVersion);
2209 }
2210 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testVersion15)2211 CPPUNIT_TEST_FIXTURE(PdfExportTest, testVersion15)
2212 {
2213     // Create an empty document.
2214     mxComponent = loadFromDesktop("private:factory/swriter");
2215 
2216     // Save as PDF.
2217     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2218     uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
2219         { { "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32>(15)) } }));
2220     utl::MediaDescriptor aMediaDescriptor;
2221     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2222     aMediaDescriptor["FilterData"] <<= aFilterData;
2223     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2224 
2225     // Parse the export result.
2226     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2227     maMemory.WriteStream(aFile);
2228     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2229     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2230         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
2231     CPPUNIT_ASSERT(pPdfDocument);
2232     int nFileVersion = pPdfDocument->getFileVersion();
2233     CPPUNIT_ASSERT_EQUAL(15, nFileVersion);
2234 }
2235 
2236 // Check round-trip of importing and exporting the PDF with PDFium filter,
2237 // which imports the PDF document as multiple PDFs as graphic object.
2238 // Each page in the document has one PDF graphic object which content is
2239 // the corresponding page in the PDF. When such a document is exported,
2240 // the PDF graphic gets embedded into the exported PDF document (as a
2241 // Form XObject).
CPPUNIT_TEST_FIXTURE(PdfExportTest,testMultiPagePDF)2242 CPPUNIT_TEST_FIXTURE(PdfExportTest, testMultiPagePDF)
2243 {
2244 // setenv only works on unix based systems
2245 #ifndef _WIN32
2246     // We need to enable PDFium import (and make sure to disable after the test)
2247     bool bResetEnvVar = false;
2248     if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
2249     {
2250         bResetEnvVar = true;
2251         setenv("LO_IMPORT_USE_PDFIUM", "1", false);
2252     }
2253     comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
2254         if (bResetEnvVar)
2255             unsetenv("LO_IMPORT_USE_PDFIUM");
2256     });
2257 
2258     // Load the PDF and save as PDF
2259     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "SimpleMultiPagePDF.pdf";
2260     mxComponent = loadFromDesktop(aURL);
2261     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2262     utl::MediaDescriptor aMediaDescriptor;
2263     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2264     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2265 
2266     // Parse the export result.
2267     vcl::filter::PDFDocument aDocument;
2268     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
2269     CPPUNIT_ASSERT(aDocument.Read(aStream));
2270 
2271     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2272     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
2273 
2274     vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
2275     CPPUNIT_ASSERT(pResources);
2276 
2277     auto pXObjects
2278         = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
2279     CPPUNIT_ASSERT(pXObjects);
2280 
2281     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3),
2282                          pXObjects->GetItems().size()); // 3 PDFs as Form XObjects
2283 
2284     std::vector<OString> rIDs;
2285     for (auto const& rPair : pXObjects->GetItems())
2286     {
2287         rIDs.push_back(rPair.first);
2288     }
2289 
2290     // Let's check the embedded PDF pages - just make sure the size differs,
2291     // which should indicate we don't have 3 times the same page.
2292 
2293     { // embedded PDF page 1
2294         vcl::filter::PDFObjectElement* pXObject1 = pXObjects->LookupObject(rIDs[0]);
2295         CPPUNIT_ASSERT(pXObject1);
2296         CPPUNIT_ASSERT_EQUAL(OString("Im19"), rIDs[0]);
2297 
2298         auto pSubtype1 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject1->Lookup("Subtype"));
2299         CPPUNIT_ASSERT(pSubtype1);
2300         CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype1->GetValue());
2301 
2302         auto pXObjectResources
2303             = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject1->Lookup("Resources"));
2304         CPPUNIT_ASSERT(pXObjectResources);
2305         auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2306             pXObjectResources->LookupElement("XObject"));
2307         CPPUNIT_ASSERT(pXObjectForms);
2308         vcl::filter::PDFObjectElement* pForm
2309             = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
2310         CPPUNIT_ASSERT(pForm);
2311 
2312         vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
2313         CPPUNIT_ASSERT(pStream);
2314         SvMemoryStream& rObjectStream = pStream->GetMemory();
2315         rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
2316 
2317         // Just check that the size of the page stream is what is expected.
2318         CPPUNIT_ASSERT_EQUAL(sal_uInt64(230), rObjectStream.remainingSize());
2319     }
2320 
2321     { // embedded PDF page 2
2322         vcl::filter::PDFObjectElement* pXObject2 = pXObjects->LookupObject(rIDs[1]);
2323         CPPUNIT_ASSERT(pXObject2);
2324         CPPUNIT_ASSERT_EQUAL(OString("Im24"), rIDs[1]);
2325 
2326         auto pSubtype2 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject2->Lookup("Subtype"));
2327         CPPUNIT_ASSERT(pSubtype2);
2328         CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype2->GetValue());
2329 
2330         auto pXObjectResources
2331             = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject2->Lookup("Resources"));
2332         CPPUNIT_ASSERT(pXObjectResources);
2333         auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2334             pXObjectResources->LookupElement("XObject"));
2335         CPPUNIT_ASSERT(pXObjectForms);
2336         vcl::filter::PDFObjectElement* pForm
2337             = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
2338         CPPUNIT_ASSERT(pForm);
2339 
2340         vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
2341         CPPUNIT_ASSERT(pStream);
2342         SvMemoryStream& rObjectStream = pStream->GetMemory();
2343         rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
2344 
2345         // Just check that the size of the page stream is what is expected
2346         CPPUNIT_ASSERT_EQUAL(sal_uInt64(309), rObjectStream.remainingSize());
2347     }
2348 
2349     { // embedded PDF page 3
2350         vcl::filter::PDFObjectElement* pXObject3 = pXObjects->LookupObject(rIDs[2]);
2351         CPPUNIT_ASSERT(pXObject3);
2352         CPPUNIT_ASSERT_EQUAL(OString("Im4"), rIDs[2]);
2353 
2354         auto pSubtype3 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject3->Lookup("Subtype"));
2355         CPPUNIT_ASSERT(pSubtype3);
2356         CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype3->GetValue());
2357 
2358         auto pXObjectResources
2359             = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject3->Lookup("Resources"));
2360         CPPUNIT_ASSERT(pXObjectResources);
2361         auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2362             pXObjectResources->LookupElement("XObject"));
2363         CPPUNIT_ASSERT(pXObjectForms);
2364         vcl::filter::PDFObjectElement* pForm
2365             = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
2366         CPPUNIT_ASSERT(pForm);
2367 
2368         vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
2369         CPPUNIT_ASSERT(pStream);
2370         SvMemoryStream& rObjectStream = pStream->GetMemory();
2371         rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
2372 
2373         // Just check that the size of the page stream is what is expected
2374         CPPUNIT_ASSERT_EQUAL(sal_uInt64(193), rObjectStream.remainingSize());
2375     }
2376 #endif
2377 }
2378 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testFormFontName)2379 CPPUNIT_TEST_FIXTURE(PdfExportTest, testFormFontName)
2380 {
2381     // Import the bugdoc and export as PDF.
2382     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "form-font-name.odt";
2383     mxComponent = loadFromDesktop(aURL);
2384 
2385     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2386     utl::MediaDescriptor aMediaDescriptor;
2387     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2388     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2389 
2390     // Parse the export result with pdfium.
2391     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2392     SvMemoryStream aMemory;
2393     aMemory.WriteStream(aFile);
2394     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2395     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2396         = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize());
2397     CPPUNIT_ASSERT(pPdfDocument);
2398 
2399     // The document has one page.
2400     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2401     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2402     CPPUNIT_ASSERT(pPdfPage);
2403 
2404     // The page has one annotation.
2405     CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
2406     std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnot = pPdfPage->getAnnotation(0);
2407 
2408     // Examine the default appearance.
2409     CPPUNIT_ASSERT(pAnnot->hasKey("DA"));
2410     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("DA"));
2411     OUString aDA = pAnnot->getString("DA");
2412 
2413     // Without the accompanying fix in place, this test would have failed with:
2414     // - Expected: 0 0 0 rg /TiRo 12 Tf
2415     // - Actual  : 0 0 0 rg /F2 12 Tf
2416     // i.e. Liberation Serif was exposed as a form font as-is, without picking the closest built-in
2417     // font.
2418     CPPUNIT_ASSERT_EQUAL(OUString("0 0 0 rg /TiRo 12 Tf"), aDA);
2419 }
2420 
2421 // Check we don't have duplicated objects when we reexport the PDF multiple
2422 // times or the size will exponentially increase over time.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testReexportPDF)2423 CPPUNIT_TEST_FIXTURE(PdfExportTest, testReexportPDF)
2424 {
2425 // setenv only works on unix based systems
2426 #ifndef _WIN32
2427     // We need to enable PDFium import (and make sure to disable after the test)
2428     bool bResetEnvVar = false;
2429     if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
2430     {
2431         bResetEnvVar = true;
2432         setenv("LO_IMPORT_USE_PDFIUM", "1", false);
2433     }
2434     comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
2435         if (bResetEnvVar)
2436             unsetenv("LO_IMPORT_USE_PDFIUM");
2437     });
2438 
2439     // Load the PDF and save as PDF
2440     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "PDFWithImages.pdf";
2441     mxComponent = loadFromDesktop(aURL);
2442     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2443     utl::MediaDescriptor aMediaDescriptor;
2444     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2445     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2446 
2447     // Parse the export result.
2448     vcl::filter::PDFDocument aDocument;
2449     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
2450     CPPUNIT_ASSERT(aDocument.Read(aStream));
2451 
2452     // Assert that the XObject in the page resources dictionary is a reference XObject.
2453     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2454 
2455     // The document has 2 pages.
2456     CPPUNIT_ASSERT_EQUAL(size_t(2), aPages.size());
2457 
2458     // PAGE 1
2459     {
2460         vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
2461         CPPUNIT_ASSERT(pResources);
2462 
2463         auto pXObjects
2464             = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
2465         CPPUNIT_ASSERT(pXObjects);
2466 
2467         std::vector<OString> rIDs;
2468         for (auto const& rPair : pXObjects->GetItems())
2469             rIDs.push_back(rPair.first);
2470 
2471         CPPUNIT_ASSERT_EQUAL(size_t(2), rIDs.size());
2472 
2473         std::vector<int> aBitmapRefs1;
2474         std::vector<int> aBitmapRefs2;
2475 
2476         {
2477             // FORM object 1
2478             OString aID = rIDs[0];
2479             CPPUNIT_ASSERT_EQUAL(OString("Im12"), aID);
2480             vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(aID);
2481             CPPUNIT_ASSERT(pXObject);
2482 
2483             auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"));
2484             CPPUNIT_ASSERT(pSubtype);
2485             CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue());
2486 
2487             auto pInnerResources
2488                 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
2489             CPPUNIT_ASSERT(pInnerResources);
2490             auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2491                 pInnerResources->LookupElement("XObject"));
2492             CPPUNIT_ASSERT(pInnerXObjects);
2493             CPPUNIT_ASSERT_EQUAL(size_t(1), pInnerXObjects->GetItems().size());
2494             OString aInnerObjectID = pInnerXObjects->GetItems().begin()->first;
2495             CPPUNIT_ASSERT_EQUAL(OString("Im13"), aInnerObjectID);
2496 
2497             vcl::filter::PDFObjectElement* pInnerXObject
2498                 = pInnerXObjects->LookupObject(aInnerObjectID);
2499             CPPUNIT_ASSERT(pInnerXObject);
2500 
2501             auto pInnerSubtype
2502                 = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"));
2503             CPPUNIT_ASSERT(pInnerSubtype);
2504             CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue());
2505 
2506             auto pInnerInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2507                 pInnerXObject->Lookup("Resources"));
2508             CPPUNIT_ASSERT(pInnerInnerResources);
2509             auto pInnerInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2510                 pInnerInnerResources->LookupElement("XObject"));
2511             CPPUNIT_ASSERT(pInnerInnerXObjects);
2512             CPPUNIT_ASSERT_EQUAL(size_t(2), pInnerInnerXObjects->GetItems().size());
2513 
2514             std::vector<OString> aBitmapIDs1;
2515             for (auto const& rPair : pInnerInnerXObjects->GetItems())
2516                 aBitmapIDs1.push_back(rPair.first);
2517 
2518             {
2519                 CPPUNIT_ASSERT_EQUAL(OString("Im11"), aBitmapIDs1[0]);
2520                 auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2521                     pInnerInnerXObjects->LookupElement(aBitmapIDs1[0]));
2522                 CPPUNIT_ASSERT(pRef);
2523                 aBitmapRefs1.push_back(pRef->GetObjectValue());
2524                 CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
2525 
2526                 vcl::filter::PDFObjectElement* pBitmap
2527                     = pInnerInnerXObjects->LookupObject(aBitmapIDs1[0]);
2528                 CPPUNIT_ASSERT(pBitmap);
2529                 auto pBitmapSubtype
2530                     = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"));
2531                 CPPUNIT_ASSERT(pBitmapSubtype);
2532                 CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue());
2533             }
2534             {
2535                 CPPUNIT_ASSERT_EQUAL(OString("Im5"), aBitmapIDs1[1]);
2536                 auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2537                     pInnerInnerXObjects->LookupElement(aBitmapIDs1[1]));
2538                 CPPUNIT_ASSERT(pRef);
2539                 aBitmapRefs1.push_back(pRef->GetObjectValue());
2540                 CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
2541 
2542                 vcl::filter::PDFObjectElement* pBitmap
2543                     = pInnerInnerXObjects->LookupObject(aBitmapIDs1[1]);
2544                 CPPUNIT_ASSERT(pBitmap);
2545                 auto pBitmapSubtype
2546                     = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"));
2547                 CPPUNIT_ASSERT(pBitmapSubtype);
2548                 CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue());
2549             }
2550         }
2551 
2552         {
2553             // FORM object 2
2554             OString aID = rIDs[1];
2555             CPPUNIT_ASSERT_EQUAL(OString("Im4"), aID);
2556             vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(aID);
2557             CPPUNIT_ASSERT(pXObject);
2558 
2559             auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"));
2560             CPPUNIT_ASSERT(pSubtype);
2561             CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue());
2562 
2563             auto pInnerResources
2564                 = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
2565             CPPUNIT_ASSERT(pInnerResources);
2566             auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2567                 pInnerResources->LookupElement("XObject"));
2568             CPPUNIT_ASSERT(pInnerXObjects);
2569             CPPUNIT_ASSERT_EQUAL(size_t(1), pInnerXObjects->GetItems().size());
2570             OString aInnerObjectID = pInnerXObjects->GetItems().begin()->first;
2571             CPPUNIT_ASSERT_EQUAL(OString("Im5"), aInnerObjectID);
2572 
2573             vcl::filter::PDFObjectElement* pInnerXObject
2574                 = pInnerXObjects->LookupObject(aInnerObjectID);
2575             CPPUNIT_ASSERT(pInnerXObject);
2576 
2577             auto pInnerSubtype
2578                 = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"));
2579             CPPUNIT_ASSERT(pInnerSubtype);
2580             CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue());
2581 
2582             auto pInnerInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2583                 pInnerXObject->Lookup("Resources"));
2584             CPPUNIT_ASSERT(pInnerInnerResources);
2585             auto pInnerInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
2586                 pInnerInnerResources->LookupElement("XObject"));
2587             CPPUNIT_ASSERT(pInnerInnerXObjects);
2588             CPPUNIT_ASSERT_EQUAL(size_t(2), pInnerInnerXObjects->GetItems().size());
2589 
2590             std::vector<OString> aBitmapIDs2;
2591             for (auto const& rPair : pInnerInnerXObjects->GetItems())
2592                 aBitmapIDs2.push_back(rPair.first);
2593 
2594             {
2595                 CPPUNIT_ASSERT_EQUAL(OString("Im11"), aBitmapIDs2[0]);
2596                 auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2597                     pInnerInnerXObjects->LookupElement(aBitmapIDs2[0]));
2598                 CPPUNIT_ASSERT(pRef);
2599                 aBitmapRefs2.push_back(pRef->GetObjectValue());
2600                 CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
2601 
2602                 vcl::filter::PDFObjectElement* pBitmap
2603                     = pInnerInnerXObjects->LookupObject(aBitmapIDs2[0]);
2604                 CPPUNIT_ASSERT(pBitmap);
2605                 auto pBitmapSubtype
2606                     = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"));
2607                 CPPUNIT_ASSERT(pBitmapSubtype);
2608                 CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue());
2609             }
2610             {
2611                 CPPUNIT_ASSERT_EQUAL(OString("Im5"), aBitmapIDs2[1]);
2612                 auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2613                     pInnerInnerXObjects->LookupElement(aBitmapIDs2[1]));
2614                 CPPUNIT_ASSERT(pRef);
2615                 aBitmapRefs2.push_back(pRef->GetObjectValue());
2616                 CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue());
2617 
2618                 vcl::filter::PDFObjectElement* pBitmap
2619                     = pInnerInnerXObjects->LookupObject(aBitmapIDs2[1]);
2620                 CPPUNIT_ASSERT(pBitmap);
2621                 auto pBitmapSubtype
2622                     = dynamic_cast<vcl::filter::PDFNameElement*>(pBitmap->Lookup("Subtype"));
2623                 CPPUNIT_ASSERT(pBitmapSubtype);
2624                 CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue());
2625             }
2626         }
2627         // Ref should point to the same bitmap
2628         CPPUNIT_ASSERT_EQUAL(aBitmapRefs1[0], aBitmapRefs2[0]);
2629         CPPUNIT_ASSERT_EQUAL(aBitmapRefs1[1], aBitmapRefs2[1]);
2630     }
2631 
2632 #endif
2633 }
2634 
2635 // Check we correctly copy more complex resources (Fonts describing
2636 // glyphs in recursive arrays) to the target PDF
CPPUNIT_TEST_FIXTURE(PdfExportTest,testReexportDocumentWithComplexResources)2637 CPPUNIT_TEST_FIXTURE(PdfExportTest, testReexportDocumentWithComplexResources)
2638 {
2639 // setenv only works on unix based systems
2640 #ifndef _WIN32
2641     // We need to enable PDFium import (and make sure to disable after the test)
2642     bool bResetEnvVar = false;
2643     if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
2644     {
2645         bResetEnvVar = true;
2646         setenv("LO_IMPORT_USE_PDFIUM", "1", false);
2647     }
2648     comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
2649         if (bResetEnvVar)
2650             unsetenv("LO_IMPORT_USE_PDFIUM");
2651     });
2652 
2653     // Load the PDF and save as PDF
2654     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "ComplexContentDictionary.pdf";
2655     mxComponent = loadFromDesktop(aURL);
2656     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2657     utl::MediaDescriptor aMediaDescriptor;
2658     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2659     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2660 
2661     // Parse the export result.
2662     vcl::filter::PDFDocument aDocument;
2663     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
2664     CPPUNIT_ASSERT(aDocument.Read(aStream));
2665 
2666     // Assert that the XObject in the page resources dictionary is a reference XObject.
2667     std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2668     CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size());
2669 
2670     // Go directly to the Font object (24 0) (number could change if we change how PDF export works)
2671     auto pFont = aDocument.LookupObject(23);
2672     CPPUNIT_ASSERT(pFont);
2673 
2674     // Check it is the Font object (Type = Font)
2675     auto pName
2676         = dynamic_cast<vcl::filter::PDFNameElement*>(pFont->GetDictionary()->LookupElement("Type"));
2677     CPPUNIT_ASSERT(pName);
2678     CPPUNIT_ASSERT_EQUAL(OString("Font"), pName->GetValue());
2679 
2680     // Check BaseFont is what we expect
2681     auto pBaseFont = dynamic_cast<vcl::filter::PDFNameElement*>(
2682         pFont->GetDictionary()->LookupElement("BaseFont"));
2683     CPPUNIT_ASSERT(pBaseFont);
2684     CPPUNIT_ASSERT_EQUAL(OString("HOTOMR+Calibri,Italic"), pBaseFont->GetValue());
2685 
2686     // Check and get the W array
2687     auto pWArray
2688         = dynamic_cast<vcl::filter::PDFArrayElement*>(pFont->GetDictionary()->LookupElement("W"));
2689     CPPUNIT_ASSERT(pWArray);
2690     CPPUNIT_ASSERT_EQUAL(size_t(26), pWArray->GetElements().size());
2691 
2692     // Check the content of W array
2693     // ObjectCopier didn't copy this array correctly and the document
2694     // had glyphs at the wrong places
2695     {
2696         // first 2 elements
2697         auto pNumberAtIndex0 = dynamic_cast<vcl::filter::PDFNumberElement*>(pWArray->GetElement(0));
2698         CPPUNIT_ASSERT(pNumberAtIndex0);
2699         CPPUNIT_ASSERT_EQUAL(3.0, pNumberAtIndex0->GetValue());
2700 
2701         auto pArrayAtIndex1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pWArray->GetElement(1));
2702         CPPUNIT_ASSERT(pArrayAtIndex1);
2703         CPPUNIT_ASSERT_EQUAL(size_t(1), pArrayAtIndex1->GetElements().size());
2704 
2705         {
2706             auto pNumber
2707                 = dynamic_cast<vcl::filter::PDFNumberElement*>(pArrayAtIndex1->GetElement(0));
2708             CPPUNIT_ASSERT(pNumber);
2709             CPPUNIT_ASSERT_EQUAL(226.0, pNumber->GetValue());
2710         }
2711 
2712         // last 2 elements
2713         auto pNumberAtIndex24
2714             = dynamic_cast<vcl::filter::PDFNumberElement*>(pWArray->GetElement(24));
2715         CPPUNIT_ASSERT(pNumberAtIndex24);
2716         CPPUNIT_ASSERT_EQUAL(894.0, pNumberAtIndex24->GetValue());
2717 
2718         auto pArrayAtIndex25 = dynamic_cast<vcl::filter::PDFArrayElement*>(pWArray->GetElement(25));
2719         CPPUNIT_ASSERT(pArrayAtIndex25);
2720         CPPUNIT_ASSERT_EQUAL(size_t(2), pArrayAtIndex25->GetElements().size());
2721 
2722         {
2723             auto pNumber1
2724                 = dynamic_cast<vcl::filter::PDFNumberElement*>(pArrayAtIndex25->GetElement(0));
2725             CPPUNIT_ASSERT(pNumber1);
2726             CPPUNIT_ASSERT_EQUAL(303.0, pNumber1->GetValue());
2727 
2728             auto pNumber2
2729                 = dynamic_cast<vcl::filter::PDFNumberElement*>(pArrayAtIndex25->GetElement(1));
2730             CPPUNIT_ASSERT(pNumber2);
2731             CPPUNIT_ASSERT_EQUAL(303.0, pNumber2->GetValue());
2732         }
2733     }
2734 #endif
2735 }
2736 
2737 // Tests that at export the PDF has the PDF/UA metadata properly set
2738 // when we enable PDF/UA support.
CPPUNIT_TEST_FIXTURE(PdfExportTest,testPdfUaMetadata)2739 CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfUaMetadata)
2740 {
2741     // Import a basic document (document doesn't really matter)
2742     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "BrownFoxLazyDog.odt";
2743     mxComponent = loadFromDesktop(aURL);
2744     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2745     utl::MediaDescriptor aMediaDescriptor;
2746     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2747 
2748     // Enable PDF/UA
2749     uno::Sequence<beans::PropertyValue> aFilterData(
2750         comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
2751     aMediaDescriptor["FilterData"] <<= aFilterData;
2752     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2753 
2754     // Parse the export result.
2755     vcl::filter::PDFDocument aDocument;
2756     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
2757     CPPUNIT_ASSERT(aDocument.Read(aStream));
2758 
2759     auto* pCatalog = aDocument.GetCatalog();
2760     CPPUNIT_ASSERT(pCatalog);
2761     auto* pCatalogDictionary = pCatalog->GetDictionary();
2762     CPPUNIT_ASSERT(pCatalogDictionary);
2763     auto* pMetadataObject = pCatalogDictionary->LookupObject("Metadata");
2764     CPPUNIT_ASSERT(pMetadataObject);
2765     auto* pMetadataDictionary = pMetadataObject->GetDictionary();
2766     auto* pType
2767         = dynamic_cast<vcl::filter::PDFNameElement*>(pMetadataDictionary->LookupElement("Type"));
2768     CPPUNIT_ASSERT(pType);
2769     CPPUNIT_ASSERT_EQUAL(OString("Metadata"), pType->GetValue());
2770 
2771     auto* pStreamObject = pMetadataObject->GetStream();
2772     CPPUNIT_ASSERT(pStreamObject);
2773     auto& rStream = pStreamObject->GetMemory();
2774     rStream.Seek(0);
2775 
2776     // Search for the PDF/UA marker in the metadata
2777 
2778     tools::XmlWalker aWalker;
2779     CPPUNIT_ASSERT(aWalker.open(&rStream));
2780     CPPUNIT_ASSERT_EQUAL(OString("xmpmeta"), aWalker.name());
2781 
2782     bool bPdfUaMarkerFound = false;
2783     OString aPdfUaPart;
2784 
2785     aWalker.children();
2786     while (aWalker.isValid())
2787     {
2788         if (aWalker.name() == "RDF"
2789             && aWalker.namespaceHref() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
2790         {
2791             aWalker.children();
2792             while (aWalker.isValid())
2793             {
2794                 if (aWalker.name() == "Description"
2795                     && aWalker.namespaceHref() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
2796                 {
2797                     aWalker.children();
2798                     while (aWalker.isValid())
2799                     {
2800                         if (aWalker.name() == "part"
2801                             && aWalker.namespaceHref() == "http://www.aiim.org/pdfua/ns/id/")
2802                         {
2803                             aPdfUaPart = aWalker.content();
2804                             bPdfUaMarkerFound = true;
2805                         }
2806                         aWalker.next();
2807                     }
2808                     aWalker.parent();
2809                 }
2810                 aWalker.next();
2811             }
2812             aWalker.parent();
2813         }
2814         aWalker.next();
2815     }
2816     aWalker.parent();
2817 
2818     CPPUNIT_ASSERT(bPdfUaMarkerFound);
2819     CPPUNIT_ASSERT_EQUAL(OString("1"), aPdfUaPart);
2820 }
2821 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf142129)2822 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142129)
2823 {
2824     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "master.odm";
2825     mxComponent = loadFromDesktop(aURL);
2826 
2827     // update linked section
2828     dispatchCommand(mxComponent, ".uno:UpdateAllLinks", {});
2829 
2830     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2831     utl::MediaDescriptor aMediaDescriptor;
2832     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2833 
2834     // Enable Outlines export
2835     uno::Sequence<beans::PropertyValue> aFilterData(
2836         comphelper::InitPropertySequence({ { "ExportBookmarks", uno::Any(true) } }));
2837     aMediaDescriptor["FilterData"] <<= aFilterData;
2838     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2839 
2840     // Parse the export result.
2841     vcl::filter::PDFDocument aDocument;
2842     SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
2843     CPPUNIT_ASSERT(aDocument.Read(aStream));
2844 
2845     auto* pCatalog = aDocument.GetCatalog();
2846     CPPUNIT_ASSERT(pCatalog);
2847     auto* pCatalogDictionary = pCatalog->GetDictionary();
2848     CPPUNIT_ASSERT(pCatalogDictionary);
2849     auto* pOutlinesObject = pCatalogDictionary->LookupObject("Outlines");
2850     CPPUNIT_ASSERT(pOutlinesObject);
2851     auto* pOutlinesDictionary = pOutlinesObject->GetDictionary();
2852 #if 0
2853     // Type isn't actually written currently
2854     auto* pType
2855         = dynamic_cast<vcl::filter::PDFNameElement*>(pOutlinesDictionary->LookupElement("Type"));
2856     CPPUNIT_ASSERT(pType);
2857     CPPUNIT_ASSERT_EQUAL(OString("Outlines"), pType->GetValue());
2858 #endif
2859 
2860     auto* pFirst = dynamic_cast<vcl::filter::PDFReferenceElement*>(
2861         pOutlinesDictionary->LookupElement("First"));
2862     CPPUNIT_ASSERT(pFirst);
2863     auto* pFirstD = pFirst->LookupObject()->GetDictionary();
2864     CPPUNIT_ASSERT(pFirstD);
2865     //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirstD->LookupElement("Type"))->GetValue());
2866     CPPUNIT_ASSERT_EQUAL(OUString(u"Preface"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
2867                                                    *dynamic_cast<vcl::filter::PDFHexStringElement*>(
2868                                                        pFirstD->LookupElement("Title"))));
2869 
2870     auto* pFirst1
2871         = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirstD->LookupElement("First"));
2872     CPPUNIT_ASSERT(pFirst1);
2873     auto* pFirst1D = pFirst1->LookupObject()->GetDictionary();
2874     CPPUNIT_ASSERT(pFirst1D);
2875     // here is a hidden section with headings "Copyright" etc.; check that
2876     // there are no outline entries for it
2877     //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst1D->LookupElement("Type"))->GetValue());
2878     CPPUNIT_ASSERT_EQUAL(
2879         OUString(u"Who is this book for?"),
2880         ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
2881             *dynamic_cast<vcl::filter::PDFHexStringElement*>(pFirst1D->LookupElement("Title"))));
2882 
2883     auto* pFirst2
2884         = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst1D->LookupElement("Next"));
2885     auto* pFirst2D = pFirst2->LookupObject()->GetDictionary();
2886     CPPUNIT_ASSERT(pFirst2D);
2887     //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst2D->LookupElement("Type"))->GetValue());
2888     CPPUNIT_ASSERT_EQUAL(
2889         OUString(u"What\u2019s in this book?"),
2890         ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
2891             *dynamic_cast<vcl::filter::PDFHexStringElement*>(pFirst2D->LookupElement("Title"))));
2892 
2893     auto* pFirst3
2894         = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst2D->LookupElement("Next"));
2895     auto* pFirst3D = pFirst3->LookupObject()->GetDictionary();
2896     CPPUNIT_ASSERT(pFirst3D);
2897     //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst3D->LookupElement("Type"))->GetValue());
2898     CPPUNIT_ASSERT_EQUAL(
2899         OUString(u"Minimum requirements for using LibreOffice"),
2900         ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
2901             *dynamic_cast<vcl::filter::PDFHexStringElement*>(pFirst3D->LookupElement("Title"))));
2902 
2903     CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
2904                          pFirst3D->LookupElement("Next"));
2905     CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
2906                          pFirstD->LookupElement("Next"));
2907 }
2908 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testPdfImageRotate180)2909 CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageRotate180)
2910 {
2911     // Create an empty document.
2912     mxComponent = loadFromDesktop("private:factory/swriter");
2913     uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
2914     uno::Reference<text::XText> xText = xTextDocument->getText();
2915     uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
2916 
2917     // Insert the PDF image.
2918     uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
2919     uno::Reference<beans::XPropertySet> xGraphicObject(
2920         xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
2921     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-rotate-180.pdf";
2922     xGraphicObject->setPropertyValue("GraphicURL", uno::makeAny(aURL));
2923     uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
2924     xShape->setSize(awt::Size(1000, 1000));
2925     uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
2926     xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
2927 
2928     // Save as PDF.
2929     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2930     utl::MediaDescriptor aMediaDescriptor;
2931     aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2932     xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2933 
2934     // Parse the export result.
2935     SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2936     maMemory.WriteStream(aFile);
2937     std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2938     std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2939         = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
2940     CPPUNIT_ASSERT(pPdfDocument);
2941     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2942 
2943     // Make sure that the page -> form -> form has a child image.
2944     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2945     CPPUNIT_ASSERT(pPdfPage);
2946     CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
2947     std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
2948     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
2949     // 2: white background and the actual object.
2950     CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
2951     std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject = pPageObject->getFormObject(1);
2952     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType());
2953     CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount());
2954 
2955     // Check if the inner form object (original page object in the pdf image) has the correct
2956     // rotation.
2957     std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject = pFormObject->getFormObject(0);
2958     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType());
2959     CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount());
2960     std::unique_ptr<vcl::pdf::PDFiumPageObject> pImage = pInnerFormObject->getFormObject(0);
2961     CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType());
2962     basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix();
2963     basegfx::B2DTuple aScale;
2964     basegfx::B2DTuple aTranslate;
2965     double fRotate = 0;
2966     double fShearX = 0;
2967     aMat.decompose(aScale, aTranslate, fRotate, fShearX);
2968     // Without the accompanying fix in place, this test would have failed with:
2969     // - Expected: -1
2970     // - Actual  : 1
2971     // i.e. the 180 degrees rotation didn't happen (via a combination of horizontal + vertical
2972     // flip).
2973     CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, aScale.getX(), 0.01);
2974 }
2975 
CPPUNIT_TEST_FIXTURE(PdfExportTest,testTdf144222)2976 CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf144222)
2977 {
2978 // Assume Windows has the font for U+4E2D
2979 #ifdef _WIN32
2980     OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf144222.ods";
2981     utl::MediaDescriptor aMediaDescriptor;
2982     aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
2983     auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
2984 
2985     // The document has one page.
2986     CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2987     std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2988     CPPUNIT_ASSERT(pPdfPage);
2989     std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
2990     CPPUNIT_ASSERT(pTextPage);
2991 
2992     int nPageObjectCount = pPdfPage->getObjectCount();
2993     const OUString sChar = u"\u4E2D";
2994     basegfx::B2DRectangle aRect1, aRect2;
2995     int nCount = 0;
2996 
2997     for (int i = 0; i < nPageObjectCount; ++i)
2998     {
2999         std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
3000         if (pPdfPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
3001         {
3002             ++nCount;
3003             OUString sText = pPdfPageObject->getText(pTextPage);
3004             if (sText == sChar)
3005                 aRect1 = pPdfPageObject->getBounds();
3006             else
3007                 aRect2 = pPdfPageObject->getBounds();
3008         }
3009     }
3010 
3011     CPPUNIT_ASSERT_EQUAL(2, nCount);
3012     CPPUNIT_ASSERT(!aRect1.isEmpty());
3013     CPPUNIT_ASSERT(!aRect2.isEmpty());
3014     CPPUNIT_ASSERT(!aRect1.overlaps(aRect2));
3015 #endif
3016 }
3017 
3018 } // end anonymous namespace
3019 
3020 CPPUNIT_PLUGIN_IMPLEMENT();
3021 
3022 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3023