1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <sal/config.h>
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>
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());
59 struct CloseDocument
{
60 void operator ()(FPDF_DOCUMENT doc
) {
62 FPDF_CloseDocument(doc
);
67 using DocumentHolder
=
68 std::unique_ptr
<typename
std::remove_pointer
<FPDF_DOCUMENT
>::type
, CloseDocument
>;
71 void operator ()(FPDF_PAGE page
) {
72 if (page
!= nullptr) {
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
;
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.
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();
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();
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());
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
)
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) }
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
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
)
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
)
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)}
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.
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
)
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
)
438 // This was 4, the page contained 4 white paths at problematic positions.
439 CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount
);
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();
504 SvMemoryStream aUncompressed
;
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)}
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.
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
));
693 double const size(FPDFTextObj_GetFontSize(pPdfPageObject
));
694 CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size
, 1E-06);
697 CPPUNIT_ASSERT(haveText
);
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();
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
;
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();
826 SvMemoryStream aUncompressed
;
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
;
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
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
)
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
)
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
));
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
);
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());
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"));
1011 size_t expectedSize
= 2;
1013 if ( nRadioGroups
== 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
1024 void PdfExportTest::testTdf115117_1()
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());
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();
1048 CPPUNIT_ASSERT(pToUnicode
);
1049 auto pStream
= pToUnicode
->GetStream();
1050 CPPUNIT_ASSERT(pStream
);
1051 SvMemoryStream aObjectStream
;
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"
1075 "<08> <006600660069>\n"
1076 "<09> <00660066006C>\n"
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
);
1085 // This requires DejaVu Sans font, if it is missing the test will most likely
1087 void PdfExportTest::testTdf115117_2()
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());
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();
1112 CPPUNIT_ASSERT(pToUnicode
);
1113 auto pStream
= pToUnicode
->GetStream();
1114 CPPUNIT_ASSERT(pStream
);
1115 SvMemoryStream aObjectStream
;
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"
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
);
1138 void PdfExportTest::testTdf115117_1a()
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
);
1181 void PdfExportTest::testTdf115117_2a()
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
);
1223 void PdfExportTest::testTdf66597_1()
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());
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();
1252 CPPUNIT_ASSERT(pToUnicode
);
1253 auto pStream
= pToUnicode
->GetStream();
1254 CPPUNIT_ASSERT(pStream
);
1255 SvMemoryStream aObjectStream
;
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
1267 std::string
aCmap("1 beginbfchar\n"
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
;
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<");
1299 while ((nPos
= aData
.find(aActualText
, nPos
)) != std::string::npos
)
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
);
1313 // This requires Reem Kufi font, if it is missing the test will fail.
1314 void PdfExportTest::testTdf66597_2()
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());
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();
1342 CPPUNIT_ASSERT(pToUnicode
);
1343 auto pStream
= pToUnicode
->GetStream();
1344 CPPUNIT_ASSERT(pStream
);
1345 SvMemoryStream aObjectStream
;
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"
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
;
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<");
1392 while ((nPos
= aData
.find(aActualText
, nPos
)) != std::string::npos
)
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
);
1409 // This requires Gentium Basic font, if it is missing the test will fail.
1410 void PdfExportTest::testTdf66597_3()
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());
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();
1438 CPPUNIT_ASSERT(pToUnicode
);
1439 auto pStream
= pToUnicode
->GetStream();
1440 CPPUNIT_ASSERT(pStream
);
1441 SvMemoryStream aObjectStream
;
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"
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
;
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>>>");
1481 while ((nPos
= aData
.find(aActualText
, nPos
)) != std::string::npos
)
1484 nPos
+= aActualText
.length();
1486 CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", static_cast<size_t>(4), nCount
);
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
)
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:
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());
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
)
1589 float fLeft
= 0, fBottom
= 0, fRight
= 0, fTop
= 0;
1590 FPDFPageObj_GetBounds(pPageObject
, &fLeft
, &fBottom
, &fRight
, &fTop
);
1595 // Second page had an incorrect image position.
1596 pPdfPage
.reset(FPDF_LoadPage(pPdfDocument
.get(), /*page_index=*/1));
1597 CPPUNIT_ASSERT(pPdfPage
.get());
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
)
1606 float fLeft
= 0, fBottom
= 0, fRight
= 0, fTop
= 0;
1607 FPDFPageObj_GetBounds(pPageObject
, &fLeft
, &fBottom
, &fRight
, &fTop
);
1612 // This failed, vertical pos is 818 points, was 1674 (outside visible page
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
1626 { "ReduceImageResolution", uno::Any(false) },
1627 // Set a custom PDF version.
1628 { "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32
>(16)) },
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());
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
)
1647 float fLeft
= 0, fBottom
= 0, fRight
= 0, fTop
= 0;
1648 FPDFPageObj_GetBounds(pPageObject
, &fLeft
, &fBottom
, &fRight
, &fTop
);
1649 nLarger
= fRight
- fLeft
;
1653 // Second page has the scaled (smaller) image.
1654 pPdfPage
.reset(FPDF_LoadPage(pPdfDocument
.get(), /*page_index=*/1));
1655 CPPUNIT_ASSERT(pPdfPage
.get());
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
)
1664 float fLeft
= 0, fBottom
= 0, fRight
= 0, fTop
= 0;
1665 FPDFPageObj_GetBounds(pPageObject
, &fLeft
, &fBottom
, &fRight
, &fTop
);
1666 nSmaller
= fRight
- fLeft
;
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":
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;
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);
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
)
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());
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
)
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();
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());
1844 uno::Reference
<text::XDocumentIndexesSupplier
> xDocumentIndexesSupplier(mxComponent
,
1846 CPPUNIT_ASSERT(xDocumentIndexesSupplier
.is());
1848 uno::Reference
<util::XRefreshable
> xToc(
1849 xDocumentIndexesSupplier
->getDocumentIndexes()->getByIndex(0), uno::UNO_QUERY
);
1850 CPPUNIT_ASSERT(xToc
.is());
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).
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
)
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.
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
);
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);
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:
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
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
));
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
;
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:
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());
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());
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
2061 void PdfExportTest::testMultiPagePDF()
2063 // setenv only works on unix based systems
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([&]() {
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());
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);
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
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
));
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
;
2249 aMat
.decompose(aScale
, aTranslate
, fRotate
, fShearX
);
2250 // Without the accompanying fix in place, this test would have failed with:
2253 // i.e. the 180 degrees rotation didn't happen (via a combination of horizontal + vertical
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
);
2282 FPDF_LINK pLinkAnnot
= nullptr;
2283 // Without the accompanying fix in place, this test would have failed, the hyperlink of the PDF
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
);
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
);
2311 CPPUNIT_ASSERT(pInnerFormObject
);
2312 // Then make sure that enough digits are used, so the point size is unchanged:
2313 basegfx::B2DHomMatrix aMatrix
;
2315 if (FPDFFormObj_GetMatrix(pInnerFormObject
->getPointer(), &matrix
))
2317 aMatrix
= basegfx::B2DHomMatrix::abcdef(matrix
.a
, matrix
.b
, matrix
.c
, matrix
.d
, matrix
.e
,
2320 basegfx::B2DTuple aScale
;
2321 basegfx::B2DTuple aTranslate
;
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.
2331 CPPUNIT_ASSERT_EQUAL(0.0012626264, rtl::math::round(aScale
.getY(), 10));
2335 CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest
);
2339 CPPUNIT_PLUGIN_IMPLEMENT();
2341 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */