PDF export: improve precision of pdf image sizes
[LibreOffice.git] / vcl / qa / cppunit / pdfexport / pdfexport.cxx
blobea67d55305390c152f994faf46e6ee97a9bd117d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/config.h>
12 #include <memory>
13 #include <type_traits>
15 #include <config_features.h>
17 #include <com/sun/star/frame/Desktop.hpp>
18 #include <com/sun/star/frame/XStorable.hpp>
19 #include <com/sun/star/view/XPrintable.hpp>
20 #include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
21 #include <com/sun/star/util/XRefreshable.hpp>
22 #include <com/sun/star/beans/XPropertySet.hpp>
23 #include <com/sun/star/drawing/XShape.hpp>
24 #include <com/sun/star/text/XTextDocument.hpp>
26 #include <comphelper/scopeguard.hxx>
27 #include <comphelper/processfactory.hxx>
28 #include <comphelper/propertysequence.hxx>
29 #include <test/bootstrapfixture.hxx>
30 #include <unotest/macros_test.hxx>
31 #include <unotools/mediadescriptor.hxx>
32 #include <unotools/tempfile.hxx>
33 #include <vcl/filter/pdfdocument.hxx>
34 #include <tools/zcodec.hxx>
35 #include <fpdf_edit.h>
36 #include <fpdf_text.h>
37 #include <fpdf_doc.h>
38 #include <fpdfview.h>
39 #include <vcl/graphicfilter.hxx>
40 #include <basegfx/matrix/b2dhommatrix.hxx>
41 #include <rtl/math.hxx>
43 #include <vcl/filter/PDFiumLibrary.hxx>
45 using namespace ::com::sun::star;
47 static std::ostream& operator<<(std::ostream& rStrm, const Color& rColor)
49 rStrm << "Color: R:" << static_cast<int>(rColor.GetRed())
50 << " G:" << static_cast<int>(rColor.GetGreen())
51 << " B:" << static_cast<int>(rColor.GetBlue())
52 << " A:" << static_cast<int>(rColor.GetTransparency());
53 return rStrm;
56 namespace
59 struct CloseDocument {
60 void operator ()(FPDF_DOCUMENT doc) {
61 if (doc != nullptr) {
62 FPDF_CloseDocument(doc);
67 using DocumentHolder =
68 std::unique_ptr<typename std::remove_pointer<FPDF_DOCUMENT>::type, CloseDocument>;
70 struct ClosePage {
71 void operator ()(FPDF_PAGE page) {
72 if (page != nullptr) {
73 FPDF_ClosePage(page);
78 using PageHolder =
79 std::unique_ptr<typename std::remove_pointer<FPDF_PAGE>::type, ClosePage>;
81 /// Tests the PDF export filter.
82 class PdfExportTest : public test::BootstrapFixture, public unotest::MacrosTest
84 uno::Reference<uno::XComponentContext> mxComponentContext;
85 uno::Reference<lang::XComponent> mxComponent;
86 utl::TempFile maTempFile;
87 SvMemoryStream maMemory;
88 // Export the document as PDF, then parse it with PDFium.
89 DocumentHolder exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor);
90 std::shared_ptr<vcl::pdf::PDFium> mpPDFium;
92 public:
93 PdfExportTest();
94 virtual void setUp() override;
95 virtual void tearDown() override;
96 void topdf(const OUString& rFile);
97 void load(const OUString& rFile, vcl::filter::PDFDocument& rDocument);
98 /// Tests that a pdf image is roundtripped back to PDF as a vector format.
99 void testTdf106059();
100 /// Tests that text highlight from Impress is not lost.
101 void testTdf105461();
102 void testTdf107868();
103 /// Tests that embedded video from Impress is not exported as a linked one.
104 void testTdf105093();
105 /// Tests export of non-PDF images.
106 void testTdf106206();
107 /// Tests export of PDF images without reference XObjects.
108 void testTdf106693();
109 void testForcePoint71();
110 void testTdf106972();
111 void testTdf106972Pdf17();
112 void testSofthyphenPos();
113 void testTdf107013();
114 void testTdf107018();
115 void testTdf107089();
116 void testTdf99680();
117 void testTdf99680_2();
118 void testTdf108963();
119 void testTdf118244_radioButtonGroup();
120 /// Test writing ToUnicode CMAP for LTR ligatures.
121 void testTdf115117_1();
122 /// Text extracting LTR text with ligatures.
123 void testTdf115117_1a();
124 /// Test writing ToUnicode CMAP for RTL ligatures.
125 void testTdf115117_2();
126 /// Test extracting RTL text with ligatures.
127 void testTdf115117_2a();
128 /// Test writing ToUnicode CMAP for doubly encoded glyphs.
129 void testTdf66597_1();
130 /// Test writing ActualText for RTL many to one glyph to Unicode mapping.
131 void testTdf66597_2();
132 /// Test writing ActualText for LTR many to one glyph to Unicode mapping.
133 void testTdf66597_3();
134 void testTdf109143();
135 void testTdf105954();
136 void testTdf128630();
137 void testTdf106702();
138 void testTdf113143();
139 void testTdf115262();
140 void testTdf121962();
141 void testTdf115967();
142 void testTdf121615();
143 void testTocLink();
144 void testPdfImageResourceInlineXObjectRef();
145 void testLinkWrongPage();
146 void testLargePage();
147 void testVersion15();
148 void testDefaultVersion();
149 void testMultiPagePDF();
150 void testPdfImageRotate180();
151 void testPdfImageHyperlink();
154 CPPUNIT_TEST_SUITE(PdfExportTest);
155 CPPUNIT_TEST(testTdf106059);
156 CPPUNIT_TEST(testTdf105461);
157 CPPUNIT_TEST(testTdf107868);
158 CPPUNIT_TEST(testTdf105093);
159 CPPUNIT_TEST(testTdf106206);
160 CPPUNIT_TEST(testTdf106693);
161 CPPUNIT_TEST(testForcePoint71);
162 CPPUNIT_TEST(testTdf106972);
163 CPPUNIT_TEST(testTdf106972Pdf17);
164 CPPUNIT_TEST(testSofthyphenPos);
165 CPPUNIT_TEST(testTdf107013);
166 CPPUNIT_TEST(testTdf107018);
167 CPPUNIT_TEST(testTdf107089);
168 CPPUNIT_TEST(testTdf99680);
169 CPPUNIT_TEST(testTdf99680_2);
170 CPPUNIT_TEST(testTdf108963);
171 CPPUNIT_TEST(testTdf118244_radioButtonGroup);
172 CPPUNIT_TEST(testTdf115117_1);
173 CPPUNIT_TEST(testTdf115117_1a);
174 CPPUNIT_TEST(testTdf115117_2);
175 CPPUNIT_TEST(testTdf115117_2a);
176 CPPUNIT_TEST(testTdf66597_1);
177 CPPUNIT_TEST(testTdf66597_2);
178 CPPUNIT_TEST(testTdf66597_3);
179 CPPUNIT_TEST(testTdf109143);
180 CPPUNIT_TEST(testTdf105954);
181 CPPUNIT_TEST(testTdf128630);
182 CPPUNIT_TEST(testTdf106702);
183 CPPUNIT_TEST(testTdf113143);
184 CPPUNIT_TEST(testTdf115262);
185 CPPUNIT_TEST(testTdf121962);
186 CPPUNIT_TEST(testTdf115967);
187 CPPUNIT_TEST(testTdf121615);
188 CPPUNIT_TEST(testTocLink);
189 CPPUNIT_TEST(testPdfImageResourceInlineXObjectRef);
190 CPPUNIT_TEST(testLinkWrongPage);
191 CPPUNIT_TEST(testLargePage);
192 CPPUNIT_TEST(testVersion15);
193 CPPUNIT_TEST(testDefaultVersion);
194 CPPUNIT_TEST(testMultiPagePDF);
195 CPPUNIT_TEST(testPdfImageRotate180);
196 CPPUNIT_TEST(testPdfImageHyperlink);
197 CPPUNIT_TEST_SUITE_END();
200 PdfExportTest::PdfExportTest()
202 maTempFile.EnableKillingFile();
205 DocumentHolder PdfExportTest::exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor)
207 // Import the bugdoc and export as PDF.
208 mxComponent = loadFromDesktop(rURL);
209 CPPUNIT_ASSERT(mxComponent.is());
211 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
212 xStorable->storeToURL(maTempFile.GetURL(), rDescriptor.getAsConstPropertyValueList());
214 // Parse the export result with pdfium.
215 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
216 maMemory.WriteStream(aFile);
217 DocumentHolder pPdfDocument(
218 FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
219 CPPUNIT_ASSERT(pPdfDocument.get());
220 return pPdfDocument;
223 void PdfExportTest::setUp()
225 test::BootstrapFixture::setUp();
227 mxComponentContext.set(comphelper::getComponentContext(getMultiServiceFactory()));
228 mxDesktop.set(frame::Desktop::create(mxComponentContext));
230 mpPDFium = vcl::pdf::PDFiumLibrary::get();
233 void PdfExportTest::tearDown()
235 if (mxComponent.is())
236 mxComponent->dispose();
238 test::BootstrapFixture::tearDown();
241 char const DATA_DIRECTORY[] = "/vcl/qa/cppunit/pdfexport/data/";
243 void PdfExportTest::topdf(const OUString& rFile)
245 // Import the bugdoc and export as PDF.
246 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile;
247 mxComponent = loadFromDesktop(aURL);
248 CPPUNIT_ASSERT(mxComponent.is());
250 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
251 utl::MediaDescriptor aMediaDescriptor;
252 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
253 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
256 void PdfExportTest::load(const OUString& rFile, vcl::filter::PDFDocument& rDocument)
258 topdf(rFile);
260 // Parse the export result.
261 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
262 CPPUNIT_ASSERT(rDocument.Read(aStream));
265 void PdfExportTest::testTdf106059()
267 // Import the bugdoc and export as PDF.
268 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106059.odt";
269 mxComponent = loadFromDesktop(aURL);
270 CPPUNIT_ASSERT(mxComponent.is());
272 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
273 utl::MediaDescriptor aMediaDescriptor;
274 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
275 // Explicitly enable the usage of the reference XObject markup.
276 uno::Sequence<beans::PropertyValue> aFilterData( comphelper::InitPropertySequence({
277 {"UseReferenceXObject", uno::Any(true) }
278 }));
279 aMediaDescriptor["FilterData"] <<= aFilterData;
280 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
282 // Parse the export result.
283 vcl::filter::PDFDocument aDocument;
284 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
285 CPPUNIT_ASSERT(aDocument.Read(aStream));
287 // Assert that the XObject in the page resources dictionary is a reference XObject.
288 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
289 // The document has one page.
290 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
291 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
292 CPPUNIT_ASSERT(pResources);
293 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
294 CPPUNIT_ASSERT(pXObjects);
295 // The page has one image.
296 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
297 vcl::filter::PDFObjectElement* pReferenceXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
298 CPPUNIT_ASSERT(pReferenceXObject);
299 // The image is a reference XObject.
300 // This dictionary key was missing, so the XObject wasn't a reference one.
301 CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref"));
304 void PdfExportTest::testTdf106693()
306 vcl::filter::PDFDocument aDocument;
307 load("tdf106693.odt", aDocument);
309 // Assert that the XObject in the page resources dictionary is a form XObject.
310 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
311 // The document has one page.
312 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
313 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
314 CPPUNIT_ASSERT(pResources);
315 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
316 CPPUNIT_ASSERT(pXObjects);
317 // The page has one image.
318 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
319 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
320 CPPUNIT_ASSERT(pXObject);
321 // The image is a form XObject.
322 auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject->Lookup("Subtype"));
323 CPPUNIT_ASSERT(pSubtype);
324 CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue());
325 // This failed: UseReferenceXObject was ignored and Ref was always created.
326 CPPUNIT_ASSERT(!pXObject->Lookup("Ref"));
328 // Assert that the form object refers to an inner form object, not a
329 // bitmap.
330 auto pInnerResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
331 CPPUNIT_ASSERT(pInnerResources);
332 auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pInnerResources->LookupElement("XObject"));
333 CPPUNIT_ASSERT(pInnerXObjects);
334 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pInnerXObjects->GetItems().size());
335 vcl::filter::PDFObjectElement* pInnerXObject = pInnerXObjects->LookupObject(pInnerXObjects->GetItems().begin()->first);
336 CPPUNIT_ASSERT(pInnerXObject);
337 auto pInnerSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype"));
338 CPPUNIT_ASSERT(pInnerSubtype);
339 // This failed: this was Image (bitmap), not Form (vector).
340 CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue());
343 void PdfExportTest::testTdf105461()
345 // Import the bugdoc and export as PDF.
346 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105461.odp";
347 mxComponent = loadFromDesktop(aURL);
348 CPPUNIT_ASSERT(mxComponent.is());
350 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
351 utl::MediaDescriptor aMediaDescriptor;
352 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
353 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
355 // Parse the export result with pdfium.
356 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
357 SvMemoryStream aMemory;
358 aMemory.WriteStream(aFile);
359 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
360 CPPUNIT_ASSERT(pPdfDocument.get());
362 // The document has one page.
363 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
364 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
365 CPPUNIT_ASSERT(pPdfPage.get());
367 // Make sure there is a filled rectangle inside.
368 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
369 int nYellowPathCount = 0;
370 for (int i = 0; i < nPageObjectCount; ++i)
372 FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
373 if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
374 continue;
376 unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
377 FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
378 if (Color(nRed, nGreen, nBlue) == COL_YELLOW)
379 ++nYellowPathCount;
382 // This was 0, the page contained no yellow paths.
383 CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
386 void PdfExportTest::testTdf107868()
388 // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
389 // which is the intent of the test.
390 // FIXME: Why does this fail on macOS?
391 #if !defined MACOSX && !defined _WIN32
393 // Import the bugdoc and print to PDF.
394 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf107868.odt";
395 mxComponent = loadFromDesktop(aURL);
396 CPPUNIT_ASSERT(mxComponent.is());
398 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
399 uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
400 CPPUNIT_ASSERT(xPrintable.is());
401 uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
403 {"FileName", uno::makeAny(maTempFile.GetURL())},
404 {"Wait", uno::makeAny(true)}
405 }));
406 xPrintable->print(aOptions);
408 // Parse the export result with pdfium.
409 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
410 SvMemoryStream aMemory;
411 aMemory.WriteStream(aFile);
412 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
413 if (!pPdfDocument.get())
414 // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
415 // running, there is no printer defined, etc.
416 return;
418 // The document has one page.
419 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
420 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
421 CPPUNIT_ASSERT(pPdfPage.get());
423 // Make sure there is no filled rectangle inside.
424 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
425 int nWhitePathCount = 0;
426 for (int i = 0; i < nPageObjectCount; ++i)
428 FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
429 if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
430 continue;
432 unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
433 FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
434 if (Color(nRed, nGreen, nBlue) == COL_WHITE)
435 ++nWhitePathCount;
438 // This was 4, the page contained 4 white paths at problematic positions.
439 CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount);
440 #endif
443 void PdfExportTest::testTdf105093()
445 vcl::filter::PDFDocument aDocument;
446 load("tdf105093.odp", aDocument);
448 // The document has one page.
449 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
450 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
452 // Get page annotations.
453 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
454 CPPUNIT_ASSERT(pAnnots);
455 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
456 auto pAnnotReference = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
457 CPPUNIT_ASSERT(pAnnotReference);
458 vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
459 CPPUNIT_ASSERT(pAnnot);
460 CPPUNIT_ASSERT_EQUAL(OString("Annot"), static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"))->GetValue());
462 // Get the Action -> Rendition -> MediaClip -> FileSpec.
463 auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"));
464 CPPUNIT_ASSERT(pAction);
465 auto pRendition = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAction->LookupElement("R"));
466 CPPUNIT_ASSERT(pRendition);
467 auto pMediaClip = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pRendition->LookupElement("C"));
468 CPPUNIT_ASSERT(pMediaClip);
469 auto pFileSpec = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pMediaClip->LookupElement("D"));
470 CPPUNIT_ASSERT(pFileSpec);
471 // Make sure the filespec refers to an embedded file.
472 // This key was missing, the embedded video was handled as a linked one.
473 CPPUNIT_ASSERT(pFileSpec->LookupElement("EF"));
476 void PdfExportTest::testTdf106206()
478 // Import the bugdoc and export as PDF.
479 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106206.odt";
480 mxComponent = loadFromDesktop(aURL);
481 CPPUNIT_ASSERT(mxComponent.is());
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());
488 // Parse the export result.
489 vcl::filter::PDFDocument aDocument;
490 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
491 CPPUNIT_ASSERT(aDocument.Read(aStream));
493 // The document has one page.
494 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
495 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
497 // The page has a stream.
498 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
499 CPPUNIT_ASSERT(pContents);
500 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
501 CPPUNIT_ASSERT(pStream);
502 SvMemoryStream& rObjectStream = pStream->GetMemory();
503 // Uncompress it.
504 SvMemoryStream aUncompressed;
505 ZCodec aZCodec;
506 aZCodec.BeginCompression();
507 rObjectStream.Seek(0);
508 aZCodec.Decompress(rObjectStream, aUncompressed);
509 CPPUNIT_ASSERT(aZCodec.EndCompression());
511 // Make sure there is an image reference there.
512 OString aImage("/Im");
513 auto pStart = static_cast<const char*>(aUncompressed.GetData());
514 const char* pEnd = pStart + aUncompressed.GetSize();
515 auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
516 CPPUNIT_ASSERT(it != pEnd);
518 // And also that it's not an invalid one.
519 OString aInvalidImage("/Im0");
520 it = std::search(pStart, pEnd, aInvalidImage.getStr(), aInvalidImage.getStr() + aInvalidImage.getLength());
521 // This failed, object #0 was referenced.
522 CPPUNIT_ASSERT(bool(it == pEnd));
525 void PdfExportTest::testTdf109143()
527 // Import the bugdoc and export as PDF.
528 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf109143.odt";
529 mxComponent = loadFromDesktop(aURL);
530 CPPUNIT_ASSERT(mxComponent.is());
532 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
533 utl::MediaDescriptor aMediaDescriptor;
534 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
535 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
537 // Parse the export result.
538 vcl::filter::PDFDocument aDocument;
539 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
540 CPPUNIT_ASSERT(aDocument.Read(aStream));
542 // The document has one page.
543 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
544 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
546 // Get access to the only image on the only page.
547 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
548 CPPUNIT_ASSERT(pResources);
549 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
550 CPPUNIT_ASSERT(pXObjects);
551 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
552 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
553 CPPUNIT_ASSERT(pXObject);
555 // Make sure it's re-compressed.
556 auto pLength = dynamic_cast<vcl::filter::PDFNumberElement*>(pXObject->Lookup("Length"));
557 CPPUNIT_ASSERT(pLength);
558 int nLength = pLength->GetValue();
559 // This failed: cropped TIFF-in-JPEG wasn't re-compressed, so crop was
560 // lost. Size was 59416, now is 11827.
561 CPPUNIT_ASSERT(nLength < 50000);
564 void PdfExportTest::testTdf106972()
566 // Import the bugdoc and export as PDF.
567 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972.odt";
568 mxComponent = loadFromDesktop(aURL);
569 CPPUNIT_ASSERT(mxComponent.is());
571 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
572 utl::MediaDescriptor aMediaDescriptor;
573 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
574 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
576 // Parse the export result.
577 vcl::filter::PDFDocument aDocument;
578 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
579 CPPUNIT_ASSERT(aDocument.Read(aStream));
581 // Get access to the only form object on the only page.
582 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
583 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
584 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
585 CPPUNIT_ASSERT(pResources);
586 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
587 CPPUNIT_ASSERT(pXObjects);
588 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
589 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
590 CPPUNIT_ASSERT(pXObject);
592 // Get access to the only image inside the form object.
593 auto pFormResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
594 CPPUNIT_ASSERT(pFormResources);
595 auto pImages = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("XObject"));
596 CPPUNIT_ASSERT(pImages);
597 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pImages->GetItems().size());
598 vcl::filter::PDFObjectElement* pImage = pImages->LookupObject(pImages->GetItems().begin()->first);
599 CPPUNIT_ASSERT(pImage);
601 // Assert resources of the image.
602 auto pImageResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pImage->Lookup("Resources"));
603 CPPUNIT_ASSERT(pImageResources);
604 // This failed: the PDF image had no Font resource.
605 CPPUNIT_ASSERT(pImageResources->LookupElement("Font"));
608 void PdfExportTest::testTdf106972Pdf17()
610 // Import the bugdoc and export as PDF.
611 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972-pdf17.odt";
612 mxComponent = loadFromDesktop(aURL);
613 CPPUNIT_ASSERT(mxComponent.is());
615 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
616 utl::MediaDescriptor aMediaDescriptor;
617 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
618 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
620 // Parse the export result.
621 vcl::filter::PDFDocument aDocument;
622 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
623 CPPUNIT_ASSERT(aDocument.Read(aStream));
625 // Get access to the only image on the only page.
626 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
627 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
628 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
629 CPPUNIT_ASSERT(pResources);
630 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
631 CPPUNIT_ASSERT(pXObjects);
632 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
633 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
634 CPPUNIT_ASSERT(pXObject);
636 // Assert that we now attempt to preserve the original PDF data, even if
637 // the original input was PDF >= 1.4.
638 CPPUNIT_ASSERT(pXObject->Lookup("Resources"));
641 void PdfExportTest::testSofthyphenPos()
643 // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
644 // which is the intent of the test.
645 // FIXME: Why does this fail on macOS?
646 #if !defined MACOSX && !defined _WIN32
648 // Import the bugdoc and print to PDF.
649 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "softhyphen_pdf.odt";
650 mxComponent = loadFromDesktop(aURL);
651 CPPUNIT_ASSERT(mxComponent.is());
653 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
654 uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
655 CPPUNIT_ASSERT(xPrintable.is());
656 uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
658 {"FileName", uno::makeAny(maTempFile.GetURL())},
659 {"Wait", uno::makeAny(true)}
660 }));
661 xPrintable->print(aOptions);
663 // Parse the export result with pdfium.
664 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
665 SvMemoryStream aMemory;
666 aMemory.WriteStream(aFile);
667 if (aFile.bad() || !aMemory.GetSize())
669 // Printing to PDF failed in a non-interesting way, e.g. CUPS is not
670 // running, there is no printer defined, etc.
671 return;
673 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
674 CPPUNIT_ASSERT(pPdfDocument);
676 // The document has one page.
677 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
678 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
679 CPPUNIT_ASSERT(pPdfPage.get());
681 // tdf#96892 incorrect fractional part of font size caused soft-hyphen to
682 // be positioned inside preceding text (incorrect = 11.1, correct = 11.05)
684 // there are 3 texts currently, for line 1, soft-hyphen, line 2
685 bool haveText(false);
687 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
688 for (int i = 0; i < nPageObjectCount; ++i)
690 FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
691 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(pPdfPageObject));
692 haveText = true;
693 double const size(FPDFTextObj_GetFontSize(pPdfPageObject));
694 CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06);
697 CPPUNIT_ASSERT(haveText);
698 #endif
701 void PdfExportTest::testTdf107013()
703 vcl::filter::PDFDocument aDocument;
704 load("tdf107013.odt", aDocument);
706 // Get access to the only image on the only page.
707 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
708 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
709 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
710 CPPUNIT_ASSERT(pResources);
711 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
712 CPPUNIT_ASSERT(pXObjects);
713 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
714 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
715 // This failed, the reference to the image was created, but not the image.
716 CPPUNIT_ASSERT(pXObject);
719 void PdfExportTest::testTdf107018()
721 vcl::filter::PDFDocument aDocument;
722 load("tdf107018.odt", aDocument);
724 // Get access to the only image on the only page.
725 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
726 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
727 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
728 CPPUNIT_ASSERT(pResources);
729 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
730 CPPUNIT_ASSERT(pXObjects);
731 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
732 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
733 CPPUNIT_ASSERT(pXObject);
735 // Get access to the form object inside the image.
736 auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
737 CPPUNIT_ASSERT(pXObjectResources);
738 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
739 CPPUNIT_ASSERT(pXObjectForms);
740 vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
741 CPPUNIT_ASSERT(pForm);
743 // Get access to Resources -> Font -> F1 of the form.
744 auto pFormResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pForm->Lookup("Resources"));
745 CPPUNIT_ASSERT(pFormResources);
746 auto pFonts = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("Font"));
747 CPPUNIT_ASSERT(pFonts);
748 auto pF1Ref = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFonts->LookupElement("F1"));
749 CPPUNIT_ASSERT(pF1Ref);
750 vcl::filter::PDFObjectElement* pF1 = pF1Ref->LookupObject();
751 CPPUNIT_ASSERT(pF1);
753 // Check that Foo -> Bar of the font is of type Pages.
754 auto pFontFoo = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pF1->Lookup("Foo"));
755 CPPUNIT_ASSERT(pFontFoo);
756 auto pBar = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFontFoo->LookupElement("Bar"));
757 CPPUNIT_ASSERT(pBar);
758 vcl::filter::PDFObjectElement* pObject = pBar->LookupObject();
759 CPPUNIT_ASSERT(pObject);
760 auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
761 CPPUNIT_ASSERT(pName);
762 // This was "XObject", reference in a nested dictionary wasn't updated when
763 // copying the page stream of a PDF image.
764 CPPUNIT_ASSERT_EQUAL(OString("Pages"), pName->GetValue());
767 void PdfExportTest::testTdf107089()
769 vcl::filter::PDFDocument aDocument;
770 load("tdf107089.odt", aDocument);
772 // Get access to the only image on the only page.
773 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
774 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
775 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
776 CPPUNIT_ASSERT(pResources);
777 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
778 CPPUNIT_ASSERT(pXObjects);
779 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
780 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
781 CPPUNIT_ASSERT(pXObject);
783 // Get access to the form object inside the image.
784 auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
785 CPPUNIT_ASSERT(pXObjectResources);
786 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
787 CPPUNIT_ASSERT(pXObjectForms);
788 vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
789 CPPUNIT_ASSERT(pForm);
791 // Make sure 'Hello' is part of the form object's stream.
792 vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
793 CPPUNIT_ASSERT(pStream);
794 SvMemoryStream aObjectStream;
795 ZCodec aZCodec;
796 aZCodec.BeginCompression();
797 pStream->GetMemory().Seek(0);
798 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
799 CPPUNIT_ASSERT(aZCodec.EndCompression());
800 aObjectStream.Seek(0);
801 OString aHello("Hello");
802 auto pStart = static_cast<const char*>(aObjectStream.GetData());
803 const char* pEnd = pStart + aObjectStream.GetSize();
804 auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength());
805 // This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage.
806 CPPUNIT_ASSERT(it != pEnd);
809 void PdfExportTest::testTdf99680()
811 vcl::filter::PDFDocument aDocument;
812 load("tdf99680.odt", aDocument);
814 // The document has one page.
815 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
816 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
818 // The page 1 has a stream.
819 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
820 CPPUNIT_ASSERT(pContents);
821 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
822 CPPUNIT_ASSERT(pStream);
823 SvMemoryStream& rObjectStream = pStream->GetMemory();
825 // Uncompress it.
826 SvMemoryStream aUncompressed;
827 ZCodec aZCodec;
828 aZCodec.BeginCompression();
829 rObjectStream.Seek(0);
830 aZCodec.Decompress(rObjectStream, aUncompressed);
831 CPPUNIT_ASSERT(aZCodec.EndCompression());
833 // tdf#130150 See infos in task - short: tdf#99680 was not the
834 // correct fix, so empty clip regions are valid - allow again in tests
835 // Make sure there are no empty clipping regions.
836 // OString aEmptyRegion("0 0 m h W* n");
837 // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
838 // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
840 // Count save graphic state (q) and restore (Q) operators
841 // and ensure their amount is equal
842 auto pStart = static_cast<const char*>(aUncompressed.GetData());
843 const char* pEnd = pStart + aUncompressed.GetSize();
844 size_t nSaveCount = std::count(pStart, pEnd, 'q');
845 size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
846 CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount, nRestoreCount);
849 void PdfExportTest::testTdf99680_2()
851 vcl::filter::PDFDocument aDocument;
852 load("tdf99680-2.odt", aDocument);
854 // For each document page
855 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
856 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
857 for (size_t nPageNr = 0; nPageNr < aPages.size(); nPageNr++)
859 // Get page contents and stream.
860 vcl::filter::PDFObjectElement* pContents = aPages[nPageNr]->LookupObject("Contents");
861 CPPUNIT_ASSERT(pContents);
862 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
863 CPPUNIT_ASSERT(pStream);
864 SvMemoryStream& rObjectStream = pStream->GetMemory();
866 // Uncompress the stream.
867 SvMemoryStream aUncompressed;
868 ZCodec aZCodec;
869 aZCodec.BeginCompression();
870 rObjectStream.Seek(0);
871 aZCodec.Decompress(rObjectStream, aUncompressed);
872 CPPUNIT_ASSERT(aZCodec.EndCompression());
874 // tdf#130150 See infos in task - short: tdf#99680 was not the
875 // correct fix, so empty clip regions are valid - allow again in tests
876 // Make sure there are no empty clipping regions.
877 // OString aEmptyRegion("0 0 m h W* n");
878 // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
879 // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
881 // Count save graphic state (q) and restore (Q) operators
882 // and ensure their amount is equal
883 auto pStart = static_cast<const char*>(aUncompressed.GetData());
884 const char* pEnd = pStart + aUncompressed.GetSize();
885 size_t nSaveCount = std::count(pStart, pEnd, 'q');
886 size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
887 CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount, nRestoreCount);
891 void PdfExportTest::testTdf108963()
893 // Import the bugdoc and export as PDF.
894 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf108963.odp";
895 mxComponent = loadFromDesktop(aURL);
896 CPPUNIT_ASSERT(mxComponent.is());
898 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
899 utl::MediaDescriptor aMediaDescriptor;
900 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
901 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
903 // Parse the export result with pdfium.
904 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
905 SvMemoryStream aMemory;
906 aMemory.WriteStream(aFile);
907 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
908 CPPUNIT_ASSERT(pPdfDocument.get());
910 // The document has one page.
911 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
912 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
913 CPPUNIT_ASSERT(pPdfPage.get());
915 // FIXME: strangely this fails on some Win systems after a pdfium update, expected: 793.7; actual: 793
916 #if !defined _WIN32
917 // Test page size (28x15.75 cm, was 1/100th mm off, tdf#112690)
918 // bad: MediaBox[0 0 793.672440944882 446.428346456693]
919 // good: MediaBox[0 0 793.700787401575 446.456692913386]
920 const double aWidth = FPDF_GetPageWidth(pPdfPage.get());
921 CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01);
922 const double aHeight = FPDF_GetPageHeight(pPdfPage.get());
923 CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01);
925 // Make sure there is a filled rectangle inside.
926 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
927 int nYellowPathCount = 0;
928 for (int i = 0; i < nPageObjectCount; ++i)
930 FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
931 if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
932 continue;
934 unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
935 FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
936 if (Color(nRed, nGreen, nBlue) == COL_YELLOW)
938 ++nYellowPathCount;
939 // The path described a yellow rectangle, but it was not rotated.
940 int nSegments = FPDFPath_CountSegments(pPdfPageObject);
941 CPPUNIT_ASSERT_EQUAL(5, nSegments);
942 FPDF_PATHSEGMENT pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 0);
943 CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(pSegment));
944 float fX = 0;
945 float fY = 0;
946 FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
947 CPPUNIT_ASSERT_EQUAL(245395, static_cast<int>(round(fX * 1000)));
948 CPPUNIT_ASSERT_EQUAL(244261, static_cast<int>(round(fY * 1000)));
949 CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
951 pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 1);
952 CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
953 FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
954 CPPUNIT_ASSERT_EQUAL(275102, static_cast<int>(round(fX * 1000)));
955 CPPUNIT_ASSERT_EQUAL(267618, static_cast<int>(round(fY * 1000)));
956 CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
958 pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 2);
959 CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
960 FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
961 CPPUNIT_ASSERT_EQUAL(287518, static_cast<int>(round(fX * 1000)));
962 CPPUNIT_ASSERT_EQUAL(251829, static_cast<int>(round(fY * 1000)));
963 CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
965 pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 3);
966 CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
967 FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
968 CPPUNIT_ASSERT_EQUAL(257839, static_cast<int>(round(fX * 1000)));
969 CPPUNIT_ASSERT_EQUAL(228472, static_cast<int>(round(fY * 1000)));
970 CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
972 pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 4);
973 CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
974 FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
975 CPPUNIT_ASSERT_EQUAL(245395, static_cast<int>(round(fX * 1000)));
976 CPPUNIT_ASSERT_EQUAL(244261, static_cast<int>(round(fY * 1000)));
977 CPPUNIT_ASSERT(FPDFPathSegment_GetClose(pSegment));
981 CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
982 #endif
985 void PdfExportTest::testTdf118244_radioButtonGroup()
987 vcl::filter::PDFDocument aDocument;
988 load("tdf118244_radioButtonGroup.odt", aDocument);
990 // The document has one page.
991 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
992 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
994 // There are eight radio buttons.
995 auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
996 CPPUNIT_ASSERT(pAnnots);
997 CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio buttons",static_cast<size_t>(8), pAnnots->GetElements().size());
999 sal_uInt32 nRadioGroups = 0;
1000 for ( const auto& aElement : aDocument.GetElements() )
1002 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1003 if ( !pObject )
1004 continue;
1005 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"));
1006 if ( pType && pType->GetValue() == "Btn" )
1008 auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("Kids"));
1009 if ( pKids )
1011 size_t expectedSize = 2;
1012 ++nRadioGroups;
1013 if ( nRadioGroups == 3 )
1014 expectedSize = 3;
1015 CPPUNIT_ASSERT_EQUAL(expectedSize, pKids->GetElements().size());
1019 CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio groups", sal_uInt32(3), nRadioGroups);
1022 // This requires Carlito font, if it is missing the test will most likely
1023 // fail.
1024 void PdfExportTest::testTdf115117_1()
1026 #if HAVE_MORE_FONTS
1027 vcl::filter::PDFDocument aDocument;
1028 load("tdf115117-1.odt", aDocument);
1030 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1032 // Get access to ToUnicode of the first font
1033 for (const auto& aElement : aDocument.GetElements())
1035 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1036 if (!pObject)
1037 continue;
1038 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1039 if (pType && pType->GetValue() == "Font")
1041 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1042 CPPUNIT_ASSERT(pToUnicodeRef);
1043 pToUnicode = pToUnicodeRef->LookupObject();
1044 break;
1048 CPPUNIT_ASSERT(pToUnicode);
1049 auto pStream = pToUnicode->GetStream();
1050 CPPUNIT_ASSERT(pStream);
1051 SvMemoryStream aObjectStream;
1052 ZCodec aZCodec;
1053 aZCodec.BeginCompression();
1054 pStream->GetMemory().Seek(0);
1055 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1056 CPPUNIT_ASSERT(aZCodec.EndCompression());
1057 aObjectStream.Seek(0);
1058 // The first values, <01> <02> etc., are glyph ids, they might change order
1059 // if we changed how font subsets are created.
1060 // The second values, <00740069> etc., are Unicode code points in hex,
1061 // <00740069> is U+0074 and U+0069 i.e. "ti" which is a ligature in
1062 // Carlito/Calibri. This test is failing if any of the second values
1063 // changed which means we are not detecting ligatures and writing CMAP
1064 // entries for them correctly. If glyph order in the subset changes then
1065 // the order here will changes and the PDF has to be carefully inspected to
1066 // ensure that the new values are correct before updating the string below.
1067 OString aCmap("9 beginbfchar\n"
1068 "<01> <00740069>\n"
1069 "<02> <0020>\n"
1070 "<03> <0074>\n"
1071 "<04> <0065>\n"
1072 "<05> <0073>\n"
1073 "<06> <00660069>\n"
1074 "<07> <0066006C>\n"
1075 "<08> <006600660069>\n"
1076 "<09> <00660066006C>\n"
1077 "endbfchar");
1078 auto pStart = static_cast<const char*>(aObjectStream.GetData());
1079 const char* pEnd = pStart + aObjectStream.GetSize();
1080 auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
1081 CPPUNIT_ASSERT(it != pEnd);
1082 #endif
1085 // This requires DejaVu Sans font, if it is missing the test will most likely
1086 // fail.
1087 void PdfExportTest::testTdf115117_2()
1089 #if HAVE_MORE_FONTS
1090 // See the comments in testTdf115117_1() for explanation.
1092 vcl::filter::PDFDocument aDocument;
1093 load("tdf115117-2.odt", aDocument);
1095 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1097 for (const auto& aElement : aDocument.GetElements())
1099 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1100 if (!pObject)
1101 continue;
1102 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1103 if (pType && pType->GetValue() == "Font")
1105 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1106 CPPUNIT_ASSERT(pToUnicodeRef);
1107 pToUnicode = pToUnicodeRef->LookupObject();
1108 break;
1112 CPPUNIT_ASSERT(pToUnicode);
1113 auto pStream = pToUnicode->GetStream();
1114 CPPUNIT_ASSERT(pStream);
1115 SvMemoryStream aObjectStream;
1116 ZCodec aZCodec;
1117 aZCodec.BeginCompression();
1118 pStream->GetMemory().Seek(0);
1119 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1120 CPPUNIT_ASSERT(aZCodec.EndCompression());
1121 aObjectStream.Seek(0);
1122 OString aCmap("7 beginbfchar\n"
1123 "<01> <06440627>\n"
1124 "<02> <0020>\n"
1125 "<03> <0641>\n"
1126 "<04> <0642>\n"
1127 "<05> <0648>\n"
1128 "<06> <06440627>\n"
1129 "<07> <0628>\n"
1130 "endbfchar");
1131 auto pStart = static_cast<const char*>(aObjectStream.GetData());
1132 const char* pEnd = pStart + aObjectStream.GetSize();
1133 auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
1134 CPPUNIT_ASSERT(it != pEnd);
1135 #endif
1138 void PdfExportTest::testTdf115117_1a()
1140 #if HAVE_MORE_FONTS
1141 // Import the bugdoc and export as PDF.
1142 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-1.odt";
1143 mxComponent = loadFromDesktop(aURL);
1144 CPPUNIT_ASSERT(mxComponent.is());
1146 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1147 utl::MediaDescriptor aMediaDescriptor;
1148 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1149 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1151 // Parse the export result with pdfium.
1152 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1153 SvMemoryStream aMemory;
1154 aMemory.WriteStream(aFile);
1155 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
1156 CPPUNIT_ASSERT(pPdfDocument.get());
1158 // The document has one page.
1159 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1160 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1161 CPPUNIT_ASSERT(pPdfPage.get());
1163 auto pPdfTextPage = FPDFText_LoadPage(pPdfPage.get());
1164 CPPUNIT_ASSERT(pPdfTextPage);
1166 // Extract the text from the page. This pdfium API is a bit higher level
1167 // than we want and might apply heuristic that give false positive, but it
1168 // is a good approximation in addition to the check in testTdf115117_1().
1169 int nChars = FPDFText_CountChars(pPdfTextPage);
1170 CPPUNIT_ASSERT_EQUAL(44, nChars);
1172 OUString aExpectedText = "ti ti test ti\r\nti test fi fl ffi ffl test fi";
1173 std::vector<sal_uInt32> aChars(nChars);
1174 for (int i = 0; i < nChars; i++)
1175 aChars[i] = FPDFText_GetUnicode(pPdfTextPage, i);
1176 OUString aActualText(aChars.data(), aChars.size());
1177 CPPUNIT_ASSERT_EQUAL(aExpectedText, aActualText);
1178 #endif
1181 void PdfExportTest::testTdf115117_2a()
1183 #if HAVE_MORE_FONTS
1184 // See the comments in testTdf115117_1a() for explanation.
1186 // Import the bugdoc and export as PDF.
1187 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-2.odt";
1188 mxComponent = loadFromDesktop(aURL);
1189 CPPUNIT_ASSERT(mxComponent.is());
1191 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1192 utl::MediaDescriptor aMediaDescriptor;
1193 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1194 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1196 // Parse the export result with pdfium.
1197 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1198 SvMemoryStream aMemory;
1199 aMemory.WriteStream(aFile);
1200 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
1201 CPPUNIT_ASSERT(pPdfDocument.get());
1203 // The document has one page.
1204 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1205 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1206 CPPUNIT_ASSERT(pPdfPage.get());
1208 auto pPdfTextPage = FPDFText_LoadPage(pPdfPage.get());
1209 CPPUNIT_ASSERT(pPdfTextPage);
1211 int nChars = FPDFText_CountChars(pPdfTextPage);
1212 CPPUNIT_ASSERT_EQUAL(13, nChars);
1214 OUString aExpectedText = u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644";
1215 std::vector<sal_uInt32> aChars(nChars);
1216 for (int i = 0; i < nChars; i++)
1217 aChars[i] = FPDFText_GetUnicode(pPdfTextPage, i);
1218 OUString aActualText(aChars.data(), aChars.size());
1219 CPPUNIT_ASSERT_EQUAL(aExpectedText, aActualText);
1220 #endif
1223 void PdfExportTest::testTdf66597_1()
1225 #if HAVE_MORE_FONTS
1226 // This requires Amiri font, if it is missing the test will fail.
1227 vcl::filter::PDFDocument aDocument;
1228 load("tdf66597-1.odt", aDocument);
1231 // Get access to ToUnicode of the first font
1232 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1233 for (const auto& aElement : aDocument.GetElements())
1235 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1236 if (!pObject)
1237 continue;
1238 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1239 if (pType && pType->GetValue() == "Font")
1241 auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
1242 auto aName = pName->GetValue().copy(7); // skip the subset id
1243 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("Amiri-Regular"), aName);
1245 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1246 CPPUNIT_ASSERT(pToUnicodeRef);
1247 pToUnicode = pToUnicodeRef->LookupObject();
1248 break;
1252 CPPUNIT_ASSERT(pToUnicode);
1253 auto pStream = pToUnicode->GetStream();
1254 CPPUNIT_ASSERT(pStream);
1255 SvMemoryStream aObjectStream;
1256 ZCodec aZCodec;
1257 aZCodec.BeginCompression();
1258 pStream->GetMemory().Seek(0);
1259 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1260 CPPUNIT_ASSERT(aZCodec.EndCompression());
1261 aObjectStream.Seek(0);
1262 // The <01> is glyph id, <0020> is code point.
1263 // The document has three characters <space><nbspace><space>, but the font
1264 // reuses the same glyph for space and nbspace so we should have a single
1265 // CMAP entry for the space, and nbspace will be handled with ActualText
1266 // (tested above).
1267 std::string aCmap("1 beginbfchar\n"
1268 "<01> <0020>\n"
1269 "endbfchar");
1270 std::string aData(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
1271 auto nPos = aData.find(aCmap);
1272 CPPUNIT_ASSERT(nPos != std::string::npos);
1276 auto aPages = aDocument.GetPages();
1277 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1278 // Get page contents and stream.
1279 auto pContents = aPages[0]->LookupObject("Contents");
1280 CPPUNIT_ASSERT(pContents);
1281 auto pStream = pContents->GetStream();
1282 CPPUNIT_ASSERT(pStream);
1283 auto& rObjectStream = pStream->GetMemory();
1285 // Uncompress the stream.
1286 SvMemoryStream aUncompressed;
1287 ZCodec aZCodec;
1288 aZCodec.BeginCompression();
1289 rObjectStream.Seek(0);
1290 aZCodec.Decompress(rObjectStream, aUncompressed);
1291 CPPUNIT_ASSERT(aZCodec.EndCompression());
1293 // Make sure the expected ActualText is present.
1294 std::string aData(static_cast<const char*>(aUncompressed.GetData()), aUncompressed.GetSize());
1296 std::string aActualText("/Span<</ActualText<");
1297 size_t nCount = 0;
1298 size_t nPos = 0;
1299 while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1301 nCount++;
1302 nPos += aActualText.length();
1304 CPPUNIT_ASSERT_EQUAL_MESSAGE("The should be one ActualText entry!", static_cast<size_t>(1), nCount);
1306 aActualText = "/Span<</ActualText<FEFF00A0>>>";
1307 nPos = aData.find(aActualText);
1308 CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos);
1310 #endif
1313 // This requires Reem Kufi font, if it is missing the test will fail.
1314 void PdfExportTest::testTdf66597_2()
1316 #if HAVE_MORE_FONTS
1317 vcl::filter::PDFDocument aDocument;
1318 load("tdf66597-2.odt", aDocument);
1321 // Get access to ToUnicode of the first font
1322 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1323 for (const auto& aElement : aDocument.GetElements())
1325 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1326 if (!pObject)
1327 continue;
1328 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1329 if (pType && pType->GetValue() == "Font")
1331 auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
1332 auto aName = pName->GetValue().copy(7); // skip the subset id
1333 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("ReemKufi-Regular"), aName);
1335 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1336 CPPUNIT_ASSERT(pToUnicodeRef);
1337 pToUnicode = pToUnicodeRef->LookupObject();
1338 break;
1342 CPPUNIT_ASSERT(pToUnicode);
1343 auto pStream = pToUnicode->GetStream();
1344 CPPUNIT_ASSERT(pStream);
1345 SvMemoryStream aObjectStream;
1346 ZCodec aZCodec;
1347 aZCodec.BeginCompression();
1348 pStream->GetMemory().Seek(0);
1349 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1350 CPPUNIT_ASSERT(aZCodec.EndCompression());
1351 aObjectStream.Seek(0);
1352 std::string aCmap("8 beginbfchar\n"
1353 "<02> <0632>\n"
1354 "<03> <0020>\n"
1355 "<04> <0648>\n"
1356 "<05> <0647>\n"
1357 "<06> <062F>\n"
1358 "<08> <062C>\n"
1359 "<09> <0628>\n"
1360 "<0B> <0623>\n"
1361 "endbfchar");
1362 std::string aData(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
1363 auto nPos = aData.find(aCmap);
1364 CPPUNIT_ASSERT(nPos != std::string::npos);
1368 auto aPages = aDocument.GetPages();
1369 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1370 // Get page contents and stream.
1371 auto pContents = aPages[0]->LookupObject("Contents");
1372 CPPUNIT_ASSERT(pContents);
1373 auto pStream = pContents->GetStream();
1374 CPPUNIT_ASSERT(pStream);
1375 auto& rObjectStream = pStream->GetMemory();
1377 // Uncompress the stream.
1378 SvMemoryStream aUncompressed;
1379 ZCodec aZCodec;
1380 aZCodec.BeginCompression();
1381 rObjectStream.Seek(0);
1382 aZCodec.Decompress(rObjectStream, aUncompressed);
1383 CPPUNIT_ASSERT(aZCodec.EndCompression());
1385 // Make sure the expected ActualText is present.
1386 std::string aData(static_cast<const char*>(aUncompressed.GetData()), aUncompressed.GetSize());
1388 std::vector<std::string> aCodes({ "0632", "062C", "0628", "0623" });
1389 std::string aActualText("/Span<</ActualText<");
1390 size_t nCount = 0;
1391 size_t nPos = 0;
1392 while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1394 nCount++;
1395 nPos += aActualText.length();
1397 CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", aCodes.size(), nCount);
1399 for (const auto& aCode : aCodes)
1401 aActualText = "/Span<</ActualText<FEFF" + aCode + ">>>";
1402 nPos = aData.find(aActualText);
1403 CPPUNIT_ASSERT_MESSAGE("ActualText not found for " + aCode, nPos != std::string::npos);
1406 #endif
1409 // This requires Gentium Basic font, if it is missing the test will fail.
1410 void PdfExportTest::testTdf66597_3()
1412 #if HAVE_MORE_FONTS
1413 vcl::filter::PDFDocument aDocument;
1414 load("tdf66597-3.odt", aDocument);
1417 // Get access to ToUnicode of the first font
1418 vcl::filter::PDFObjectElement* pToUnicode = nullptr;
1419 for (const auto& aElement : aDocument.GetElements())
1421 auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
1422 if (!pObject)
1423 continue;
1424 auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
1425 if (pType && pType->GetValue() == "Font")
1427 auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
1428 auto aName = pName->GetValue().copy(7); // skip the subset id
1429 CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("GentiumBasic"), aName);
1431 auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
1432 CPPUNIT_ASSERT(pToUnicodeRef);
1433 pToUnicode = pToUnicodeRef->LookupObject();
1434 break;
1438 CPPUNIT_ASSERT(pToUnicode);
1439 auto pStream = pToUnicode->GetStream();
1440 CPPUNIT_ASSERT(pStream);
1441 SvMemoryStream aObjectStream;
1442 ZCodec aZCodec;
1443 aZCodec.BeginCompression();
1444 pStream->GetMemory().Seek(0);
1445 aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
1446 CPPUNIT_ASSERT(aZCodec.EndCompression());
1447 aObjectStream.Seek(0);
1448 std::string aCmap("2 beginbfchar\n"
1449 "<01> <1ECB0331030B>\n"
1450 "<05> <0020>\n"
1451 "endbfchar");
1452 std::string aData(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
1453 auto nPos = aData.find(aCmap);
1454 CPPUNIT_ASSERT(nPos != std::string::npos);
1458 auto aPages = aDocument.GetPages();
1459 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1460 // Get page contents and stream.
1461 auto pContents = aPages[0]->LookupObject("Contents");
1462 CPPUNIT_ASSERT(pContents);
1463 auto pStream = pContents->GetStream();
1464 CPPUNIT_ASSERT(pStream);
1465 auto& rObjectStream = pStream->GetMemory();
1467 // Uncompress the stream.
1468 SvMemoryStream aUncompressed;
1469 ZCodec aZCodec;
1470 aZCodec.BeginCompression();
1471 rObjectStream.Seek(0);
1472 aZCodec.Decompress(rObjectStream, aUncompressed);
1473 CPPUNIT_ASSERT(aZCodec.EndCompression());
1475 // Make sure the expected ActualText is present.
1476 std::string aData(static_cast<const char*>(aUncompressed.GetData()), aUncompressed.GetSize());
1478 std::string aActualText("/Span<</ActualText<FEFF1ECB0331030B>>>");
1479 size_t nCount = 0;
1480 size_t nPos = 0;
1481 while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
1483 nCount++;
1484 nPos += aActualText.length();
1486 CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", static_cast<size_t>(4), nCount);
1488 #endif
1491 void PdfExportTest::testTdf105954()
1493 // Import the bugdoc and export as PDF.
1494 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105954.odt";
1495 mxComponent = loadFromDesktop(aURL);
1496 CPPUNIT_ASSERT(mxComponent.is());
1498 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1499 utl::MediaDescriptor aMediaDescriptor;
1500 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1501 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
1502 { { "ReduceImageResolution", uno::Any(true) },
1503 { "MaxImageResolution", uno::Any(static_cast<sal_Int32>(300)) } }));
1504 aMediaDescriptor["FilterData"] <<= aFilterData;
1505 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1507 // Parse the export result with pdfium.
1508 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1509 SvMemoryStream aMemory;
1510 aMemory.WriteStream(aFile);
1511 DocumentHolder pPdfDocument(
1512 FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
1513 CPPUNIT_ASSERT(pPdfDocument.get());
1515 // The document has one page.
1516 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1517 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1518 CPPUNIT_ASSERT(pPdfPage.get());
1520 // There is a single image on the page.
1521 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1522 CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount);
1524 // Check width of the image.
1525 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), /*index=*/0);
1526 FPDF_IMAGEOBJ_METADATA aMeta;
1527 CPPUNIT_ASSERT(FPDFImageObj_GetImageMetadata(pPageObject, pPdfPage.get(), &aMeta));
1528 // This was 2000, i.e. the 'reduce to 300 DPI' request was ignored.
1529 // This is now around 238 (228 on macOS).
1530 CPPUNIT_ASSERT_LESS(static_cast<unsigned int>(250), aMeta.width);
1533 void PdfExportTest::testTdf128630()
1535 // Import the bugdoc and export as PDF.
1536 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf128630.odp";
1537 utl::MediaDescriptor aMediaDescriptor;
1538 aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
1539 DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1541 // The document has one page.
1542 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1544 // Assert the aspect ratio of the only bitmap on the page.
1545 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1546 CPPUNIT_ASSERT(pPdfPage.get());
1547 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1548 for (int i = 0; i < nPageObjectCount; ++i)
1550 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1551 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
1552 continue;
1554 FPDF_BITMAP pBitmap = FPDFImageObj_GetBitmap(pPageObject);
1555 CPPUNIT_ASSERT(pBitmap);
1556 int nWidth = FPDFBitmap_GetWidth(pBitmap);
1557 int nHeight = FPDFBitmap_GetHeight(pBitmap);
1558 FPDFBitmap_Destroy(pBitmap);
1559 // Without the accompanying fix in place, this test would have failed with:
1560 // assertion failed
1561 // - Expression: nWidth != nHeight
1562 // i.e. the bitmap lost its custom aspect ratio during export.
1563 CPPUNIT_ASSERT(nWidth != nHeight);
1567 void PdfExportTest::testTdf106702()
1569 // Import the bugdoc and export as PDF.
1570 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106702.odt";
1571 utl::MediaDescriptor aMediaDescriptor;
1572 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1573 auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1575 // The document has two pages.
1576 CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
1578 // First page already has the correct image position.
1579 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1580 CPPUNIT_ASSERT(pPdfPage.get());
1581 int nExpected = 0;
1582 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1583 for (int i = 0; i < nPageObjectCount; ++i)
1585 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1586 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
1587 continue;
1589 float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
1590 FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
1591 nExpected = fTop;
1592 break;
1595 // Second page had an incorrect image position.
1596 pPdfPage.reset(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
1597 CPPUNIT_ASSERT(pPdfPage.get());
1598 int nActual = 0;
1599 nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1600 for (int i = 0; i < nPageObjectCount; ++i)
1602 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1603 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
1604 continue;
1606 float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
1607 FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
1608 nActual = fTop;
1609 break;
1612 // This failed, vertical pos is 818 points, was 1674 (outside visible page
1613 // bounds).
1614 CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
1617 void PdfExportTest::testTdf113143()
1619 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf113143.odp";
1620 utl::MediaDescriptor aMediaDescriptor;
1621 aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
1622 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
1623 { "ExportNotesPages", uno::Any(true) },
1624 // ReduceImageResolution is on by default and that hides the bug we
1625 // want to test.
1626 { "ReduceImageResolution", uno::Any(false) },
1627 // Set a custom PDF version.
1628 { "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32>(16)) },
1629 }));
1630 aMediaDescriptor["FilterData"] <<= aFilterData;
1631 auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1633 // The document has two pages.
1634 CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
1636 // First has the original (larger) image.
1637 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1638 CPPUNIT_ASSERT(pPdfPage.get());
1639 int nLarger = 0;
1640 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1641 for (int i = 0; i < nPageObjectCount; ++i)
1643 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1644 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
1645 continue;
1647 float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
1648 FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
1649 nLarger = fRight - fLeft;
1650 break;
1653 // Second page has the scaled (smaller) image.
1654 pPdfPage.reset(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
1655 CPPUNIT_ASSERT(pPdfPage.get());
1656 int nSmaller = 0;
1657 nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1658 for (int i = 0; i < nPageObjectCount; ++i)
1660 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1661 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
1662 continue;
1664 float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
1665 FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
1666 nSmaller = fRight - fLeft;
1667 break;
1670 // This failed, both were 319, now nSmaller is 169.
1671 CPPUNIT_ASSERT_LESS(nLarger, nSmaller);
1673 // The following check used to fail in the past, header was "%PDF-1.5":
1674 maMemory.Seek(0);
1675 OString aExpectedHeader("%PDF-1.6");
1676 OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength()));
1677 CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader);
1680 void PdfExportTest::testForcePoint71()
1682 // I just care it doesn't crash
1683 topdf("forcepoint71.key");
1686 void PdfExportTest::testTdf115262()
1688 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115262.ods";
1689 utl::MediaDescriptor aMediaDescriptor;
1690 aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
1691 auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1692 CPPUNIT_ASSERT_EQUAL(8, FPDF_GetPageCount(pPdfDocument.get()));
1694 // Get the 6th page.
1695 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/5));
1696 CPPUNIT_ASSERT(pPdfPage.get());
1698 // Look up the position of the first image and the 400th row.
1699 FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
1700 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1701 int nFirstImageTop = 0;
1702 int nRowTop = 0;
1703 for (int i = 0; i < nPageObjectCount; ++i)
1705 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1706 float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
1707 FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
1709 if (FPDFPageObj_GetType(pPageObject) == FPDF_PAGEOBJ_IMAGE)
1711 nFirstImageTop = fTop;
1713 else if (FPDFPageObj_GetType(pPageObject) == FPDF_PAGEOBJ_TEXT)
1715 unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0);
1716 std::vector<sal_Unicode> aText(nTextSize);
1717 FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
1718 OUString sText(aText.data(), nTextSize / 2 - 1);
1719 if (sText == "400")
1720 nRowTop = fTop;
1723 // Make sure that the top of the "400" is below the top of the image (in
1724 // bottom-right-corner-based PDF coordinates).
1725 // This was: expected less than 144, actual is 199.
1726 CPPUNIT_ASSERT_LESS(nFirstImageTop, nRowTop);
1727 FPDFText_ClosePage(pTextPage);
1730 void PdfExportTest::testTdf121962()
1732 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf121962.odt";
1733 utl::MediaDescriptor aMediaDescriptor;
1734 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1735 auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1736 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1738 // Get the first page
1739 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1740 CPPUNIT_ASSERT(pPdfPage.get());
1741 FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
1743 // Make sure the table sum is displayed as "0", not faulty expression.
1744 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1745 for (int i = 0; i < nPageObjectCount; ++i)
1747 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1748 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_TEXT)
1749 continue;
1750 unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0);
1751 std::vector<sal_Unicode> aText(nTextSize);
1752 FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
1753 OUString sText(aText.data(), nTextSize / 2 - 1);
1754 CPPUNIT_ASSERT(sText != "** Expression is faulty **");
1759 void PdfExportTest::testTdf115967()
1761 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115967.odt";
1762 utl::MediaDescriptor aMediaDescriptor;
1763 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1764 auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1765 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1767 // Get the first page
1768 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1769 CPPUNIT_ASSERT(pPdfPage.get());
1770 FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
1772 // Make sure the elements inside a formula in a RTL document are exported
1773 // LTR ( m=750abc ) and not RTL ( m=057cba )
1774 int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
1775 OUString sText;
1776 for (int i = 0; i < nPageObjectCount; ++i)
1778 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
1779 if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_TEXT)
1780 continue;
1781 unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 2);
1782 std::vector<sal_Unicode> aText(nTextSize);
1783 FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
1784 OUString sChar(aText.data(), nTextSize / 2 - 1);
1785 sText += sChar.trim();
1787 CPPUNIT_ASSERT_EQUAL(OUString("m=750abc"), sText);
1790 void PdfExportTest::testTdf121615()
1792 vcl::filter::PDFDocument aDocument;
1793 load("tdf121615.odt", aDocument);
1795 // The document has one page.
1796 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1797 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1799 // Get access to the only image on the only page.
1800 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
1801 CPPUNIT_ASSERT(pResources);
1802 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
1803 CPPUNIT_ASSERT(pXObjects);
1804 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
1805 vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
1806 CPPUNIT_ASSERT(pXObject);
1807 vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
1808 CPPUNIT_ASSERT(pStream);
1809 SvMemoryStream& rObjectStream = pStream->GetMemory();
1811 // Load the embedded image.
1812 rObjectStream.Seek( 0 );
1813 GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
1814 Graphic aGraphic;
1815 sal_uInt16 format;
1816 ErrCode bResult = rFilter.ImportGraphic(aGraphic, OUString( "import" ), rObjectStream,
1817 GRFILTER_FORMAT_DONTKNOW, &format);
1818 CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
1820 // The image should be grayscale 8bit JPEG.
1821 sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName( JPG_SHORTNAME );
1822 CPPUNIT_ASSERT( jpegFormat != GRFILTER_FORMAT_NOTFOUND );
1823 CPPUNIT_ASSERT_EQUAL( jpegFormat, format );
1824 BitmapEx aBitmap = aGraphic.GetBitmapEx();
1825 CPPUNIT_ASSERT_EQUAL( 200L, aBitmap.GetSizePixel().Width());
1826 CPPUNIT_ASSERT_EQUAL( 300L, aBitmap.GetSizePixel().Height());
1827 CPPUNIT_ASSERT_EQUAL( 8, int(aBitmap.GetBitCount()));
1828 // tdf#121615 was caused by broken handling of data width with 8bit color,
1829 // so the test image has some black in the bottomright corner, check it's there
1830 CPPUNIT_ASSERT_EQUAL( COL_WHITE, aBitmap.GetPixelColor( 0, 0 ));
1831 CPPUNIT_ASSERT_EQUAL( COL_WHITE, aBitmap.GetPixelColor( 0, 299 ));
1832 CPPUNIT_ASSERT_EQUAL( COL_WHITE, aBitmap.GetPixelColor( 199, 0 ));
1833 CPPUNIT_ASSERT_EQUAL( COL_BLACK, aBitmap.GetPixelColor( 199, 299 ));
1836 void PdfExportTest::testTocLink()
1838 // Load the Writer document.
1839 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "toc-link.fodt";
1840 mxComponent = loadFromDesktop(aURL);
1841 CPPUNIT_ASSERT(mxComponent.is());
1843 // Update the ToC.
1844 uno::Reference<text::XDocumentIndexesSupplier> xDocumentIndexesSupplier(mxComponent,
1845 uno::UNO_QUERY);
1846 CPPUNIT_ASSERT(xDocumentIndexesSupplier.is());
1848 uno::Reference<util::XRefreshable> xToc(
1849 xDocumentIndexesSupplier->getDocumentIndexes()->getByIndex(0), uno::UNO_QUERY);
1850 CPPUNIT_ASSERT(xToc.is());
1852 xToc->refresh();
1854 // Save as PDF.
1855 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1856 utl::MediaDescriptor aMediaDescriptor;
1857 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1858 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1860 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1861 maMemory.WriteStream(aFile);
1862 DocumentHolder pPdfDocument(
1863 FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
1864 CPPUNIT_ASSERT(pPdfDocument.get());
1865 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1867 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1868 CPPUNIT_ASSERT(pPdfPage.get());
1870 // Ensure there is a link on the first page (in the ToC).
1871 int nStartPos = 0;
1872 FPDF_LINK pLinkAnnot = nullptr;
1873 // Without the accompanying fix in place, this test would have failed, as FPDFLink_Enumerate()
1874 // returned false, as the page contained no links.
1875 CPPUNIT_ASSERT(FPDFLink_Enumerate(pPdfPage.get(), &nStartPos, &pLinkAnnot));
1878 bool HasLinksOnPage(PageHolder& pPdfPage)
1880 int nStartPos = 0;
1881 FPDF_LINK pLinkAnnot = nullptr;
1882 return FPDFLink_Enumerate(pPdfPage.get(), &nStartPos, &pLinkAnnot);
1885 void PdfExportTest::testLinkWrongPage()
1887 // Import the bugdoc and export as PDF.
1888 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "link-wrong-page.odp";
1889 utl::MediaDescriptor aMediaDescriptor;
1890 aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
1891 DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1893 // The document has 2 pages.
1894 CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
1896 // First page should have 1 link (2nd slide, 1st was hidden).
1897 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1898 CPPUNIT_ASSERT(pPdfPage.get());
1900 // Without the accompanying fix in place, this test would have failed, as the link of the first
1901 // page went to the second page due to the hidden first slide.
1902 CPPUNIT_ASSERT(HasLinksOnPage(pPdfPage));
1904 // Second page should have no links (3rd slide).
1905 PageHolder pPdfPage2(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
1906 CPPUNIT_ASSERT(pPdfPage2.get());
1907 CPPUNIT_ASSERT(!HasLinksOnPage(pPdfPage2));
1910 void PdfExportTest::testLargePage()
1912 // Import the bugdoc and export as PDF.
1913 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "6m-wide.odg";
1914 utl::MediaDescriptor aMediaDescriptor;
1915 aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
1916 DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
1918 // The document has 1 page.
1919 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1921 // Check the value (not the unit) of the page size.
1922 double fWidth = 0;
1923 double fHeight = 0;
1924 FPDF_GetPageSizeByIndex(pPdfDocument.get(), 0, &fWidth, &fHeight);
1925 // Without the accompanying fix in place, this test would have failed with:
1926 // - Expected: 8503.94
1927 // - Actual : 17007.875
1928 // i.e. the value for 600 cm was larger than the 14 400 limit set in the spec.
1929 CPPUNIT_ASSERT_DOUBLES_EQUAL(8503.94, fWidth, 0.01);
1932 void PdfExportTest::testPdfImageResourceInlineXObjectRef()
1934 // Create an empty document.
1935 mxComponent = loadFromDesktop("private:factory/swriter");
1936 CPPUNIT_ASSERT(mxComponent.is());
1937 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
1938 uno::Reference<text::XText> xText = xTextDocument->getText();
1939 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
1941 // Insert the PDF image.
1942 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
1943 uno::Reference<beans::XPropertySet> xGraphicObject(
1944 xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
1945 OUString aURL
1946 = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-resource-inline-xobject-ref.pdf";
1947 xGraphicObject->setPropertyValue("GraphicURL", uno::makeAny(aURL));
1948 uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
1949 xShape->setSize(awt::Size(1000, 1000));
1950 uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
1951 xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
1953 // Save as PDF.
1954 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
1955 utl::MediaDescriptor aMediaDescriptor;
1956 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1957 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1959 // Parse the export result.
1960 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
1961 maMemory.WriteStream(aFile);
1962 DocumentHolder pPdfDocument(
1963 FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
1964 CPPUNIT_ASSERT(pPdfDocument.get());
1965 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
1967 // Make sure that the page -> form -> form has a child image.
1968 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
1969 CPPUNIT_ASSERT(pPdfPage.get());
1970 CPPUNIT_ASSERT_EQUAL(1, FPDFPage_CountObjects(pPdfPage.get()));
1971 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), 0);
1972 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pPageObject));
1973 // 2: white background and the actual object.
1974 CPPUNIT_ASSERT_EQUAL(2, FPDFFormObj_CountObjects(pPageObject));
1975 FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetObject(pPageObject, 1);
1976 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pFormObject));
1977 // Without the accompanying fix in place, this test would have failed with:
1978 // - Expected: 1
1979 // - Actual : 0
1980 // i.e. the sub-form was missing its image.
1981 CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pFormObject));
1983 // Check if the inner form object (original page object in the pdf image) has the correct
1984 // rotation.
1985 FPDF_PAGEOBJECT pInnerFormObject = FPDFFormObj_GetObject(pFormObject, 0);
1986 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pInnerFormObject));
1987 CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pInnerFormObject));
1988 FPDF_PAGEOBJECT pImage = FPDFFormObj_GetObject(pInnerFormObject, 0);
1989 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(pImage));
1990 FS_MATRIX aMatrix;
1991 FPDFFormObj_GetMatrix(pInnerFormObject, &aMatrix);
1992 basegfx::B2DHomMatrix aMat{ aMatrix.a, aMatrix.c, aMatrix.e, aMatrix.b, aMatrix.d, aMatrix.f };
1993 basegfx::B2DTuple aScale;
1994 basegfx::B2DTuple aTranslate;
1995 double fRotate = 0;
1996 double fShearX = 0;
1997 aMat.decompose(aScale, aTranslate, fRotate, fShearX);
1998 int nRotateDeg = basegfx::rad2deg(fRotate);
1999 // Without the accompanying fix in place, this test would have failed with:
2000 // - Expected: -90
2001 // - Actual : 0
2002 // i.e. rotation was lost on pdf export.
2003 CPPUNIT_ASSERT_EQUAL(-90, nRotateDeg);
2006 void PdfExportTest::testDefaultVersion()
2008 // Create an empty document.
2009 mxComponent = loadFromDesktop("private:factory/swriter");
2010 CPPUNIT_ASSERT(mxComponent.is());
2012 // Save as PDF.
2013 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2014 utl::MediaDescriptor aMediaDescriptor;
2015 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2016 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2018 // Parse the export result.
2019 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2020 maMemory.WriteStream(aFile);
2021 DocumentHolder pPdfDocument(
2022 FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
2023 CPPUNIT_ASSERT(pPdfDocument.get());
2024 int nFileVersion = 0;
2025 FPDF_GetFileVersion(pPdfDocument.get(), &nFileVersion);
2026 CPPUNIT_ASSERT_EQUAL(16, nFileVersion);
2029 void PdfExportTest::testVersion15()
2031 // Create an empty document.
2032 mxComponent = loadFromDesktop("private:factory/swriter");
2033 CPPUNIT_ASSERT(mxComponent.is());
2035 // Save as PDF.
2036 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2037 uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
2038 { { "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32>(15)) } }));
2039 utl::MediaDescriptor aMediaDescriptor;
2040 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2041 aMediaDescriptor["FilterData"] <<= aFilterData;
2042 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2044 // Parse the export result.
2045 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2046 maMemory.WriteStream(aFile);
2047 DocumentHolder pPdfDocument(
2048 FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
2049 CPPUNIT_ASSERT(pPdfDocument.get());
2050 int nFileVersion = 0;
2051 FPDF_GetFileVersion(pPdfDocument.get(), &nFileVersion);
2052 CPPUNIT_ASSERT_EQUAL(15, nFileVersion);
2055 // Check round-trip of importing and exporting the PDF with PDFium filter,
2056 // which imports the PDF document as multiple PDFs as graphic object.
2057 // Each page in the document has one PDF graphic object which content is
2058 // the correcponding page in the PDF. When such a document is exported,
2059 // the PDF graphic gets embedded into the exported PDF document (as a
2060 // Form XObject).
2061 void PdfExportTest::testMultiPagePDF()
2063 // setenv only works on unix based systems
2064 #ifndef _WIN32
2065 // We need to enable PDFium import (and make sure to disable after the test)
2066 bool bResetEnvVar = false;
2067 if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr)
2069 bResetEnvVar = true;
2070 setenv("LO_IMPORT_USE_PDFIUM", "1", false);
2072 comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() {
2073 if (bResetEnvVar)
2074 unsetenv("LO_IMPORT_USE_PDFIUM");
2077 // Load the PDF and save as PDF
2078 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "SimpleMultiPagePDF.pdf";
2079 mxComponent = loadFromDesktop(aURL);
2080 CPPUNIT_ASSERT(mxComponent.is());
2082 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2083 utl::MediaDescriptor aMediaDescriptor;
2084 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2085 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2087 // Parse the export result.
2088 vcl::filter::PDFDocument aDocument;
2089 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
2090 CPPUNIT_ASSERT(aDocument.Read(aStream));
2092 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
2093 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
2095 vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
2096 CPPUNIT_ASSERT(pResources);
2098 auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
2099 CPPUNIT_ASSERT(pXObjects);
2101 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), pXObjects->GetItems().size()); // 3 PDFs as Form XObjects
2103 std::vector<OString> rIDs;
2104 for (auto const & rPair : pXObjects->GetItems()) {
2105 rIDs.push_back(rPair.first);
2108 // Let's check the embedded PDF pages - just make sure the size differs,
2109 // which should indicate we don't have 3 times the same page.
2111 { // embedded PDF page 1
2112 vcl::filter::PDFObjectElement* pXObject1 = pXObjects->LookupObject(rIDs[0]);
2113 CPPUNIT_ASSERT(pXObject1);
2114 CPPUNIT_ASSERT_EQUAL(OString("Im19"), rIDs[0]);
2116 auto pSubtype1 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject1->Lookup("Subtype"));
2117 CPPUNIT_ASSERT(pSubtype1);
2118 CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype1->GetValue());
2120 auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject1->Lookup("Resources"));
2121 CPPUNIT_ASSERT(pXObjectResources);
2122 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
2123 CPPUNIT_ASSERT(pXObjectForms);
2124 vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
2125 CPPUNIT_ASSERT(pForm);
2127 vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
2128 CPPUNIT_ASSERT(pStream);
2129 SvMemoryStream& rObjectStream = pStream->GetMemory();
2130 rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
2132 // Just check that the size of the page stream is what is expected.
2133 CPPUNIT_ASSERT_EQUAL(sal_uInt64(230), rObjectStream.remainingSize());
2136 { // embedded PDF page 2
2137 vcl::filter::PDFObjectElement* pXObject2 = pXObjects->LookupObject(rIDs[1]);
2138 CPPUNIT_ASSERT(pXObject2);
2139 CPPUNIT_ASSERT_EQUAL(OString("Im24"), rIDs[1]);
2141 auto pSubtype2 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject2->Lookup("Subtype"));
2142 CPPUNIT_ASSERT(pSubtype2);
2143 CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype2->GetValue());
2145 auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject2->Lookup("Resources"));
2146 CPPUNIT_ASSERT(pXObjectResources);
2147 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
2148 CPPUNIT_ASSERT(pXObjectForms);
2149 vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
2150 CPPUNIT_ASSERT(pForm);
2152 vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
2153 CPPUNIT_ASSERT(pStream);
2154 SvMemoryStream& rObjectStream = pStream->GetMemory();
2155 rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
2157 // Just check that the size of the page stream is what is expected
2158 CPPUNIT_ASSERT_EQUAL(sal_uInt64(309), rObjectStream.remainingSize());
2161 { // embedded PDF page 3
2162 vcl::filter::PDFObjectElement* pXObject3 = pXObjects->LookupObject(rIDs[2]);
2163 CPPUNIT_ASSERT(pXObject3);
2164 CPPUNIT_ASSERT_EQUAL(OString("Im4"), rIDs[2]);
2166 auto pSubtype3 = dynamic_cast<vcl::filter::PDFNameElement*>(pXObject3->Lookup("Subtype"));
2167 CPPUNIT_ASSERT(pSubtype3);
2168 CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype3->GetValue());
2170 auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject3->Lookup("Resources"));
2171 CPPUNIT_ASSERT(pXObjectResources);
2172 auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
2173 CPPUNIT_ASSERT(pXObjectForms);
2174 vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
2175 CPPUNIT_ASSERT(pForm);
2177 vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
2178 CPPUNIT_ASSERT(pStream);
2179 SvMemoryStream& rObjectStream = pStream->GetMemory();
2180 rObjectStream.Seek(STREAM_SEEK_TO_BEGIN);
2182 // Just check that the size of the page stream is what is expected
2183 CPPUNIT_ASSERT_EQUAL(sal_uInt64(193), rObjectStream.remainingSize());
2185 #endif
2188 void PdfExportTest::testPdfImageRotate180()
2190 // Create an empty document.
2191 uno::Reference<lang::XComponent> xComponent = loadFromDesktop("private:factory/swriter");
2192 uno::Reference<text::XTextDocument> xTextDocument(xComponent, uno::UNO_QUERY);
2193 uno::Reference<text::XText> xText = xTextDocument->getText();
2194 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
2196 // Insert the PDF image.
2197 uno::Reference<lang::XMultiServiceFactory> xFactory(xComponent, uno::UNO_QUERY);
2198 uno::Reference<beans::XPropertySet> xGraphicObject(
2199 xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
2200 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-rotate-180.pdf";
2201 xGraphicObject->setPropertyValue("GraphicURL", uno::makeAny(aURL));
2202 uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
2203 xShape->setSize(awt::Size(1000, 1000));
2204 uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
2205 xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
2207 // Save as PDF.
2208 uno::Reference<frame::XStorable> xStorable(xComponent, uno::UNO_QUERY);
2209 utl::MediaDescriptor aMediaDescriptor;
2210 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
2211 utl::TempFile aTempFile;
2212 aTempFile.EnableKillingFile();
2213 xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2214 xComponent->dispose();
2216 // Parse the export result.
2217 SvFileStream aFile(aTempFile.GetURL(), StreamMode::READ);
2218 SvMemoryStream aMemory;
2219 aMemory.WriteStream(aFile);
2220 DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
2221 CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
2223 // Make sure that the page -> form -> form has a child image.
2224 PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
2225 CPPUNIT_ASSERT(pPdfPage.get());
2226 CPPUNIT_ASSERT_EQUAL(1, FPDFPage_CountObjects(pPdfPage.get()));
2227 FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), 0);
2228 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pPageObject));
2229 // 2: white background and the actual object.
2230 CPPUNIT_ASSERT_EQUAL(2, FPDFFormObj_CountObjects(pPageObject));
2231 FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetObject(pPageObject, 1);
2232 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pFormObject));
2233 CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pFormObject));
2235 // Check if the inner form object (original page object in the pdf image) has the correct
2236 // rotation.
2237 FPDF_PAGEOBJECT pInnerFormObject = FPDFFormObj_GetObject(pFormObject, 0);
2238 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pInnerFormObject));
2239 CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pInnerFormObject));
2240 FPDF_PAGEOBJECT pImage = FPDFFormObj_GetObject(pInnerFormObject, 0);
2241 CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(pImage));
2242 FS_MATRIX aMatrix;
2243 FPDFFormObj_GetMatrix(pInnerFormObject, &aMatrix);
2244 basegfx::B2DHomMatrix aMat{ aMatrix.a, aMatrix.c, aMatrix.e, aMatrix.b, aMatrix.d, aMatrix.f };
2245 basegfx::B2DTuple aScale;
2246 basegfx::B2DTuple aTranslate;
2247 double fRotate = 0;
2248 double fShearX = 0;
2249 aMat.decompose(aScale, aTranslate, fRotate, fShearX);
2250 // Without the accompanying fix in place, this test would have failed with:
2251 // - Expected: -1
2252 // - Actual : 1
2253 // i.e. the 180 degrees rotation didn't happen (via a combination of horizontal + vertical
2254 // flip).
2255 CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, aScale.getX(), 0.01);
2258 void PdfExportTest::testPdfImageHyperlink()
2260 // Given a Draw file, containing a PDF image, which has a hyperlink in it:
2261 OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-hyperlink.odg";
2262 mxComponent = loadFromDesktop(aURL);
2263 CPPUNIT_ASSERT(mxComponent.is());
2265 // When saving to PDF:
2266 uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
2267 utl::MediaDescriptor aMediaDescriptor;
2268 aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
2269 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
2271 // Then make sure that link is preserved:
2272 SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
2273 maMemory.WriteStream(aFile);
2274 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
2275 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
2276 = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize());
2277 CPPUNIT_ASSERT(pPdfDocument);
2278 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
2279 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
2280 CPPUNIT_ASSERT(pPdfPage);
2281 int nStartPos = 0;
2282 FPDF_LINK pLinkAnnot = nullptr;
2283 // Without the accompanying fix in place, this test would have failed, the hyperlink of the PDF
2284 // image was lost.
2285 CPPUNIT_ASSERT(FPDFLink_Enumerate(pPdfPage->getPointer(), &nStartPos, &pLinkAnnot));
2287 // Also test the precision of the form XObject.
2288 // Given a full-page form XObject, page height is 27.94 cm (792 points):
2289 // When writing the reciprocal of the object height to PDF:
2290 std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject;
2291 for (int i = 0; i < pPdfPage->getObjectCount(); ++i)
2293 std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pPdfPage->getObject(i);
2294 if (FPDFPageObj_GetType(pObject->getPointer()) == FPDF_PAGEOBJ_FORM)
2296 pFormObject = std::move(pObject);
2297 break;
2300 CPPUNIT_ASSERT(pFormObject);
2301 std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject;
2302 for (int i = 0; i < pFormObject->getFormObjectCount(); ++i)
2304 std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pFormObject->getFormObject(i);
2305 if (FPDFPageObj_GetType(pObject->getPointer()) == FPDF_PAGEOBJ_FORM)
2307 pInnerFormObject = std::move(pObject);
2308 break;
2311 CPPUNIT_ASSERT(pInnerFormObject);
2312 // Then make sure that enough digits are used, so the point size is unchanged:
2313 basegfx::B2DHomMatrix aMatrix;
2314 FS_MATRIX matrix;
2315 if (FPDFFormObj_GetMatrix(pInnerFormObject->getPointer(), &matrix))
2317 aMatrix = basegfx::B2DHomMatrix::abcdef(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e,
2318 matrix.f);
2320 basegfx::B2DTuple aScale;
2321 basegfx::B2DTuple aTranslate;
2322 double fRotate{};
2323 double fShearX{};
2324 aMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2325 // Without the accompanying fix in place, this test would have failed with:
2326 // - Expected: 0.0012626264
2327 // - Actual : 0.00126
2328 // i.e. the rounded reciprocal was 794 points, not the original 792.
2329 // FIXME macOS actual value is 0.0001578282, for unknown reasons.
2330 #if !defined MACOSX
2331 CPPUNIT_ASSERT_EQUAL(0.0012626264, rtl::math::round(aScale.getY(), 10));
2332 #endif
2335 CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
2339 CPPUNIT_PLUGIN_IMPLEMENT();
2341 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */