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