/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; static std::ostream& operator<<(std::ostream& rStrm, const Color& rColor) { rStrm << "Color: R:" << static_cast(rColor.GetRed()) << " G:" << static_cast(rColor.GetGreen()) << " B:" << static_cast(rColor.GetBlue()) << " A:" << static_cast(255 - rColor.GetAlpha()); return rStrm; } namespace { /// Tests the PDF export filter. class PdfExportTest : public test::BootstrapFixture, public unotest::MacrosTest { protected: uno::Reference mxComponent; utl::TempFile maTempFile; SvMemoryStream maMemory; utl::MediaDescriptor aMediaDescriptor; std::unique_ptr parseExport(const OString& rPassword = OString()); std::shared_ptr mpPDFium; public: PdfExportTest(); virtual void setUp() override; virtual void tearDown() override; void saveAsPDF(std::u16string_view rFile); void load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument); }; PdfExportTest::PdfExportTest() { maTempFile.EnableKillingFile(); } std::unique_ptr PdfExportTest::parseExport(const OString& rPassword) { SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ); maMemory.WriteStream(aFile); std::shared_ptr pPDFium = vcl::pdf::PDFiumLibrary::get(); std::unique_ptr pPdfDocument = pPDFium->openDocument(maMemory.GetData(), maMemory.GetSize(), rPassword); CPPUNIT_ASSERT(pPdfDocument); return pPdfDocument; } void PdfExportTest::setUp() { test::BootstrapFixture::setUp(); mxDesktop.set(frame::Desktop::create(mxComponentContext)); mpPDFium = vcl::pdf::PDFiumLibrary::get(); } void PdfExportTest::tearDown() { if (mxComponent.is()) mxComponent->dispose(); test::BootstrapFixture::tearDown(); } constexpr OUStringLiteral DATA_DIRECTORY = u"/vcl/qa/cppunit/pdfexport/data/"; void PdfExportTest::saveAsPDF(std::u16string_view rFile) { // Import the bugdoc and export as PDF. OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile; mxComponent = loadFromDesktop(aURL); uno::Reference xStorable(mxComponent, uno::UNO_QUERY); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); } void PdfExportTest::load(std::u16string_view rFile, vcl::filter::PDFDocument& rDocument) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(rFile); // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(rDocument.Read(aStream)); } /// Tests that a pdf image is roundtripped back to PDF as a vector format. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106059) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Explicitly enable the usage of the reference XObject markup. uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "UseReferenceXObject", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"tdf106059.odt"); // Parse the export result. vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // Assert that the XObject in the page resources dictionary is a reference XObject. std::vector aPages = aDocument.GetPages(); // The document has one page. CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); // The page has one image. CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pReferenceXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pReferenceXObject); // The image is a reference XObject. // This dictionary key was missing, so the XObject wasn't a reference one. CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref")); } /// Tests export of PDF images without reference XObjects. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106693) { vcl::filter::PDFDocument aDocument; load(u"tdf106693.odt", aDocument); // Assert that the XObject in the page resources dictionary is a form XObject. std::vector aPages = aDocument.GetPages(); // The document has one page. CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); // The page has one image. CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); // The image is a form XObject. auto pSubtype = dynamic_cast(pXObject->Lookup("Subtype")); CPPUNIT_ASSERT(pSubtype); CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue()); // This failed: UseReferenceXObject was ignored and Ref was always created. CPPUNIT_ASSERT(!pXObject->Lookup("Ref")); // Assert that the form object refers to an inner form object, not a // bitmap. auto pInnerResources = dynamic_cast(pXObject->Lookup("Resources")); CPPUNIT_ASSERT(pInnerResources); auto pInnerXObjects = dynamic_cast( pInnerResources->LookupElement("XObject")); CPPUNIT_ASSERT(pInnerXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pInnerXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pInnerXObject = pInnerXObjects->LookupObject(pInnerXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pInnerXObject); auto pInnerSubtype = dynamic_cast(pInnerXObject->Lookup("Subtype")); CPPUNIT_ASSERT(pInnerSubtype); // This failed: this was Image (bitmap), not Form (vector). CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue()); } /// Tests that text highlight from Impress is not lost. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105461) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf105461.odp"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Make sure there is a filled rectangle inside. int nPageObjectCount = pPdfPage->getObjectCount(); int nYellowPathCount = 0; for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPdfPageObject = pPdfPage->getObject(i); if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path) continue; if (pPdfPageObject->getFillColor() == COL_YELLOW) ++nYellowPathCount; } // This was 0, the page contained no yellow paths. CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107868) { // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export // which is the intent of the test. // FIXME: Why does this fail on macOS? #if !defined MACOSX && !defined _WIN32 // Import the bugdoc and print to PDF. OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf107868.odt"; mxComponent = loadFromDesktop(aURL); uno::Reference xStorable(mxComponent, uno::UNO_QUERY); uno::Reference xPrintable(mxComponent, uno::UNO_QUERY); CPPUNIT_ASSERT(xPrintable.is()); uno::Sequence aOptions(comphelper::InitPropertySequence( { { "FileName", uno::Any(maTempFile.GetURL()) }, { "Wait", uno::Any(true) } })); xPrintable->print(aOptions); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); if (!pPdfDocument) // Printing to PDF failed in a non-interesting way, e.g. CUPS is not // running, there is no printer defined, etc. return; // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Make sure there is no filled rectangle inside. int nPageObjectCount = pPdfPage->getObjectCount(); int nWhitePathCount = 0; for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPdfPageObject = pPdfPage->getObject(i); if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path) continue; if (pPdfPageObject->getFillColor() == COL_WHITE) ++nWhitePathCount; } // This was 4, the page contained 4 white paths at problematic positions. CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount); #endif } /// Tests that embedded video from Impress is not exported as a linked one. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105093) { vcl::filter::PDFDocument aDocument; load(u"tdf105093.odp", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get page annotations. auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots")); CPPUNIT_ASSERT(pAnnots); CPPUNIT_ASSERT_EQUAL(static_cast(1), pAnnots->GetElements().size()); auto pAnnotReference = dynamic_cast(pAnnots->GetElements()[0]); CPPUNIT_ASSERT(pAnnotReference); vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject(); CPPUNIT_ASSERT(pAnnot); CPPUNIT_ASSERT_EQUAL( OString("Annot"), static_cast(pAnnot->Lookup("Type"))->GetValue()); // Get the Action -> Rendition -> MediaClip -> FileSpec. auto pAction = dynamic_cast(pAnnot->Lookup("A")); CPPUNIT_ASSERT(pAction); auto pRendition = dynamic_cast(pAction->LookupElement("R")); CPPUNIT_ASSERT(pRendition); auto pMediaClip = dynamic_cast(pRendition->LookupElement("C")); CPPUNIT_ASSERT(pMediaClip); auto pFileSpec = dynamic_cast(pMediaClip->LookupElement("D")); CPPUNIT_ASSERT(pFileSpec); // Make sure the filespec refers to an embedded file. // This key was missing, the embedded video was handled as a linked one. CPPUNIT_ASSERT(pFileSpec->LookupElement("EF")); } /// Tests export of non-PDF images. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106206) { // Import the bugdoc and export as PDF. vcl::filter::PDFDocument aDocument; load(u"tdf106206.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // The page has a stream. vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); vcl::filter::PDFStreamElement* pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Uncompress it. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); // Make sure there is an image reference there. OString aImage("/Im"); auto pStart = static_cast(aUncompressed.GetData()); const char* pEnd = pStart + aUncompressed.GetSize(); auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength()); CPPUNIT_ASSERT(it != pEnd); // And also that it's not an invalid one. OString aInvalidImage("/Im0"); it = std::search(pStart, pEnd, aInvalidImage.getStr(), aInvalidImage.getStr() + aInvalidImage.getLength()); // This failed, object #0 was referenced. CPPUNIT_ASSERT(bool(it == pEnd)); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf127217) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf127217.odt"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // The page has one annotation. CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount()); std::unique_ptr pAnnot = pPdfPage->getAnnotation(0); // Without the fix in place, this test would have failed here CPPUNIT_ASSERT(!pAnnot->hasKey("DA")); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf109143) { // Import the bugdoc and export as PDF. vcl::filter::PDFDocument aDocument; load(u"tdf109143.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get access to the only image on the only page. vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); // Make sure it's re-compressed. auto pLength = dynamic_cast(pXObject->Lookup("Length")); CPPUNIT_ASSERT(pLength); int nLength = pLength->GetValue(); // This failed: cropped TIFF-in-JPEG wasn't re-compressed, so crop was // lost. Size was 59416, now is 11827. CPPUNIT_ASSERT(nLength < 50000); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972) { // Import the bugdoc and export as PDF. vcl::filter::PDFDocument aDocument; load(u"tdf106972.odt", aDocument); // Get access to the only form object on the only page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); // Get access to the only image inside the form object. auto pFormResources = dynamic_cast(pXObject->Lookup("Resources")); CPPUNIT_ASSERT(pFormResources); auto pImages = dynamic_cast( pFormResources->LookupElement("XObject")); CPPUNIT_ASSERT(pImages); CPPUNIT_ASSERT_EQUAL(static_cast(1), pImages->GetItems().size()); vcl::filter::PDFObjectElement* pImage = pImages->LookupObject(pImages->GetItems().begin()->first); CPPUNIT_ASSERT(pImage); // Assert resources of the image. auto pImageResources = dynamic_cast(pImage->Lookup("Resources")); CPPUNIT_ASSERT(pImageResources); // This failed: the PDF image had no Font resource. CPPUNIT_ASSERT(pImageResources->LookupElement("Font")); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106972Pdf17) { // Import the bugdoc and export as PDF. vcl::filter::PDFDocument aDocument; load(u"tdf106972-pdf17.odt", aDocument); // Get access to the only image on the only page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); // Assert that we now attempt to preserve the original PDF data, even if // the original input was PDF >= 1.4. CPPUNIT_ASSERT(pXObject->Lookup("Resources")); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testSofthyphenPos) { // No need to run it on Windows, since it would use GDI printing, and not trigger PDF export // which is the intent of the test. // FIXME: Why does this fail on macOS? #if !defined MACOSX && !defined _WIN32 // Import the bugdoc and print to PDF. OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "softhyphen_pdf.odt"; mxComponent = loadFromDesktop(aURL); uno::Reference xStorable(mxComponent, uno::UNO_QUERY); uno::Reference xPrintable(mxComponent, uno::UNO_QUERY); CPPUNIT_ASSERT(xPrintable.is()); uno::Sequence aOptions(comphelper::InitPropertySequence( { { "FileName", uno::Any(maTempFile.GetURL()) }, { "Wait", uno::Any(true) } })); xPrintable->print(aOptions); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); if (!pPdfDocument) // Printing to PDF failed in a non-interesting way, e.g. CUPS is not // running, there is no printer defined, etc. return; // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // tdf#96892 incorrect fractional part of font size caused soft-hyphen to // be positioned inside preceding text (incorrect = 11.1, correct = 11.05) // there are 3 texts currently, for line 1, soft-hyphen, line 2 bool haveText(false); int nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPdfPageObject = pPdfPage->getObject(i); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pPdfPageObject->getType()); haveText = true; double const size = pPdfPageObject->getFontSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06); } CPPUNIT_ASSERT(haveText); #endif } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107013) { vcl::filter::PDFDocument aDocument; load(u"tdf107013.odt", aDocument); // Get access to the only image on the only page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); // This failed, the reference to the image was created, but not the image. CPPUNIT_ASSERT(pXObject); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107018) { vcl::filter::PDFDocument aDocument; load(u"tdf107018.odt", aDocument); // Get access to the only image on the only page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); // Get access to the form object inside the image. auto pXObjectResources = dynamic_cast(pXObject->Lookup("Resources")); CPPUNIT_ASSERT(pXObjectResources); auto pXObjectForms = dynamic_cast( pXObjectResources->LookupElement("XObject")); CPPUNIT_ASSERT(pXObjectForms); vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first); CPPUNIT_ASSERT(pForm); // Get access to Resources -> Font -> F1 of the form. auto pFormResources = dynamic_cast(pForm->Lookup("Resources")); CPPUNIT_ASSERT(pFormResources); auto pFonts = dynamic_cast(pFormResources->LookupElement("Font")); CPPUNIT_ASSERT(pFonts); auto pF1Ref = dynamic_cast(pFonts->LookupElement("F1")); CPPUNIT_ASSERT(pF1Ref); vcl::filter::PDFObjectElement* pF1 = pF1Ref->LookupObject(); CPPUNIT_ASSERT(pF1); // Check that Foo -> Bar of the font is of type Pages. auto pFontFoo = dynamic_cast(pF1->Lookup("Foo")); CPPUNIT_ASSERT(pFontFoo); auto pBar = dynamic_cast(pFontFoo->LookupElement("Bar")); CPPUNIT_ASSERT(pBar); vcl::filter::PDFObjectElement* pObject = pBar->LookupObject(); CPPUNIT_ASSERT(pObject); auto pName = dynamic_cast(pObject->Lookup("Type")); CPPUNIT_ASSERT(pName); // This was "XObject", reference in a nested dictionary wasn't updated when // copying the page stream of a PDF image. CPPUNIT_ASSERT_EQUAL(OString("Pages"), pName->GetValue()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf148706) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf148706.odt"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // The page has one annotation. CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount()); std::unique_ptr pAnnot = pPdfPage->getAnnotation(0); CPPUNIT_ASSERT(pAnnot->hasKey("V")); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("V")); OUString aV = pAnnot->getString("V"); // Without the fix in place, this test would have failed with // - Expected: 1821.84 // - Actual : CPPUNIT_ASSERT_EQUAL(OUString("1821.84"), aV); CPPUNIT_ASSERT(pAnnot->hasKey("DV")); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("DV")); OUString aDV = pAnnot->getString("DV"); CPPUNIT_ASSERT_EQUAL(OUString("1821.84"), aDV); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf107089) { vcl::filter::PDFDocument aDocument; load(u"tdf107089.odt", aDocument); // Get access to the only image on the only page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); // Get access to the form object inside the image. auto pXObjectResources = dynamic_cast(pXObject->Lookup("Resources")); CPPUNIT_ASSERT(pXObjectResources); auto pXObjectForms = dynamic_cast( pXObjectResources->LookupElement("XObject")); CPPUNIT_ASSERT(pXObjectForms); vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first); CPPUNIT_ASSERT(pForm); // Make sure 'Hello' is part of the form object's stream. vcl::filter::PDFStreamElement* pStream = pForm->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream aObjectStream; ZCodec aZCodec; aZCodec.BeginCompression(); pStream->GetMemory().Seek(0); aZCodec.Decompress(pStream->GetMemory(), aObjectStream); CPPUNIT_ASSERT(aZCodec.EndCompression()); aObjectStream.Seek(0); OString aHello("Hello"); auto pStart = static_cast(aObjectStream.GetData()); const char* pEnd = pStart + aObjectStream.GetSize(); auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength()); // This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage. CPPUNIT_ASSERT(it != pEnd); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680) { vcl::filter::PDFDocument aDocument; load(u"tdf99680.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // The page 1 has a stream. vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); vcl::filter::PDFStreamElement* pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Uncompress it. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); // tdf#130150 See infos in task - short: tdf#99680 was not the // correct fix, so empty clip regions are valid - allow again in tests // Make sure there are no empty clipping regions. // OString aEmptyRegion("0 0 m h W* n"); // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength()); // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd); // Count save graphic state (q) and restore (Q) operators // and ensure their amount is equal auto pStart = static_cast(aUncompressed.GetData()); const char* pEnd = pStart + aUncompressed.GetSize(); size_t nSaveCount = std::count(pStart, pEnd, 'q'); size_t nRestoreCount = std::count(pStart, pEnd, 'Q'); CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount, nRestoreCount); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf99680_2) { vcl::filter::PDFDocument aDocument; load(u"tdf99680-2.odt", aDocument); // For each document page std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(3), aPages.size()); for (size_t nPageNr = 0; nPageNr < aPages.size(); nPageNr++) { // Get page contents and stream. vcl::filter::PDFObjectElement* pContents = aPages[nPageNr]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); vcl::filter::PDFStreamElement* pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Uncompress the stream. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); // tdf#130150 See infos in task - short: tdf#99680 was not the // correct fix, so empty clip regions are valid - allow again in tests // Make sure there are no empty clipping regions. // OString aEmptyRegion("0 0 m h W* n"); // auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength()); // CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd); // Count save graphic state (q) and restore (Q) operators // and ensure their amount is equal auto pStart = static_cast(aUncompressed.GetData()); const char* pEnd = pStart + aUncompressed.GetSize(); size_t nSaveCount = std::count(pStart, pEnd, 'q'); size_t nRestoreCount = std::count(pStart, pEnd, 'Q'); CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount, nRestoreCount); } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf108963) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf108963.odp"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Test page size (28x15.75 cm, was 1/100th mm off, tdf#112690) // bad: MediaBox[0 0 793.672440944882 446.428346456693] // good: MediaBox[0 0 793.700787401575 446.456692913386] const double aWidth = pPdfPage->getWidth(); CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01); const double aHeight = pPdfPage->getHeight(); CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01); // Make sure there is a filled rectangle inside. int nPageObjectCount = pPdfPage->getObjectCount(); int nYellowPathCount = 0; for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPdfPageObject = pPdfPage->getObject(i); if (pPdfPageObject->getType() != vcl::pdf::PDFPageObjectType::Path) continue; if (pPdfPageObject->getFillColor() == COL_YELLOW) { ++nYellowPathCount; // The path described a yellow rectangle, but it was not rotated. int nSegments = pPdfPageObject->getPathSegmentCount(); CPPUNIT_ASSERT_EQUAL(5, nSegments); std::unique_ptr pSegment = pPdfPageObject->getPathSegment(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Moveto, pSegment->getType()); basegfx::B2DPoint aPoint = pSegment->getPoint(); CPPUNIT_ASSERT_DOUBLES_EQUAL(245.395, aPoint.getX(), 0.0005); CPPUNIT_ASSERT_DOUBLES_EQUAL(244.261, aPoint.getY(), 0.0005); CPPUNIT_ASSERT(!pSegment->isClosed()); pSegment = pPdfPageObject->getPathSegment(1); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType()); aPoint = pSegment->getPoint(); CPPUNIT_ASSERT_DOUBLES_EQUAL(275.102, aPoint.getX(), 0.0005); CPPUNIT_ASSERT_DOUBLES_EQUAL(267.618, aPoint.getY(), 0.0005); CPPUNIT_ASSERT(!pSegment->isClosed()); pSegment = pPdfPageObject->getPathSegment(2); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType()); aPoint = pSegment->getPoint(); CPPUNIT_ASSERT_DOUBLES_EQUAL(287.518, aPoint.getX(), 0.0005); CPPUNIT_ASSERT_DOUBLES_EQUAL(251.829, aPoint.getY(), 0.0005); CPPUNIT_ASSERT(!pSegment->isClosed()); pSegment = pPdfPageObject->getPathSegment(3); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType()); aPoint = pSegment->getPoint(); CPPUNIT_ASSERT_DOUBLES_EQUAL(257.839, aPoint.getX(), 0.0005); CPPUNIT_ASSERT_DOUBLES_EQUAL(228.472, aPoint.getY(), 0.0005); CPPUNIT_ASSERT(!pSegment->isClosed()); pSegment = pPdfPageObject->getPathSegment(4); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFSegmentType::Lineto, pSegment->getType()); aPoint = pSegment->getPoint(); CPPUNIT_ASSERT_DOUBLES_EQUAL(245.395, aPoint.getX(), 0.0005); CPPUNIT_ASSERT_DOUBLES_EQUAL(244.261, aPoint.getY(), 0.0005); CPPUNIT_ASSERT(pSegment->isClosed()); } } CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testAlternativeText) { aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "UseTaggedPDF", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"alternativeText.fodp"); // Parse the export result. vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "StructElem") { auto pS = dynamic_cast(pObject->Lookup("S")); if (pS && pS->GetValue() == "Figure") { CPPUNIT_ASSERT_EQUAL( OUString(u"This is the text alternative - This is the description"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast(pObject->Lookup("Alt")))); } } } // tdf#67866 check that Catalog contains Lang auto* pCatalog = aDocument.GetCatalog(); CPPUNIT_ASSERT(pCatalog); auto* pCatalogDictionary = pCatalog->GetDictionary(); CPPUNIT_ASSERT(pCatalogDictionary); auto pLang = dynamic_cast( pCatalogDictionary->LookupElement("Lang")); CPPUNIT_ASSERT(pLang); CPPUNIT_ASSERT_EQUAL(OString("en-US"), pLang->GetValue()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105972) { vcl::filter::PDFDocument aDocument; load(u"tdf105972.fodt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots")); CPPUNIT_ASSERT(pAnnots); CPPUNIT_ASSERT_EQUAL(static_cast(3), pAnnots->GetElements().size()); sal_uInt32 nTextFieldCount = 0; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("FT")); if (pType && pType->GetValue() == "Tx") { ++nTextFieldCount; auto pT = dynamic_cast(pObject->Lookup("T")); CPPUNIT_ASSERT(pT); auto pAA = dynamic_cast(pObject->Lookup("AA")); CPPUNIT_ASSERT(pAA); CPPUNIT_ASSERT_EQUAL(static_cast(2), pAA->GetItems().size()); auto pF = dynamic_cast(pAA->LookupElement("F")); CPPUNIT_ASSERT(pF); CPPUNIT_ASSERT_EQUAL(static_cast(2), pF->GetItems().size()); if (nTextFieldCount == 1) { CPPUNIT_ASSERT_EQUAL(OString("CurrencyField"), pT->GetValue()); auto pJS = dynamic_cast(pF->LookupElement("JS")); CPPUNIT_ASSERT_EQUAL( OString("AFNumber_Format\\(4, 0, 0, 0, \"\\\\u20ac\",true\\);"), pJS->GetValue()); } else if (nTextFieldCount == 2) { CPPUNIT_ASSERT_EQUAL(OString("TimeField"), pT->GetValue()); auto pJS = dynamic_cast(pF->LookupElement("JS")); CPPUNIT_ASSERT_EQUAL(OString("AFTime_FormatEx\\(\"h:MM:sstt\"\\);"), pJS->GetValue()); } else { CPPUNIT_ASSERT_EQUAL(OString("DateField"), pT->GetValue()); auto pJS = dynamic_cast(pF->LookupElement("JS")); CPPUNIT_ASSERT_EQUAL(OString("AFDate_FormatEx\\(\"yy-mm-dd\"\\);"), pJS->GetValue()); } } } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf148442) { vcl::filter::PDFDocument aDocument; load(u"tdf148442.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots")); CPPUNIT_ASSERT(pAnnots); CPPUNIT_ASSERT_EQUAL(static_cast(3), pAnnots->GetElements().size()); sal_uInt32 nBtnCount = 0; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("FT")); if (pType && pType->GetValue() == "Btn") { ++nBtnCount; auto pT = dynamic_cast(pObject->Lookup("T")); CPPUNIT_ASSERT(pT); auto pAS = dynamic_cast(pObject->Lookup("AS")); CPPUNIT_ASSERT(pAS); auto pAP = dynamic_cast(pObject->Lookup("AP")); CPPUNIT_ASSERT(pAP); auto pN = dynamic_cast(pAP->LookupElement("N")); CPPUNIT_ASSERT(pN); CPPUNIT_ASSERT_EQUAL(static_cast(2), pN->GetItems().size()); if (nBtnCount == 1) { CPPUNIT_ASSERT_EQUAL(OString("Checkbox1"), pT->GetValue()); CPPUNIT_ASSERT_EQUAL(OString("Yes"), pAS->GetValue()); CPPUNIT_ASSERT(!pN->GetItems().count("ref")); CPPUNIT_ASSERT(pN->GetItems().count("Yes")); CPPUNIT_ASSERT(pN->GetItems().count("Off")); } else if (nBtnCount == 2) { CPPUNIT_ASSERT_EQUAL(OString("Checkbox2"), pT->GetValue()); CPPUNIT_ASSERT_EQUAL(OString("Yes"), pAS->GetValue()); // Without the fix in place, this test would have failed here CPPUNIT_ASSERT(pN->GetItems().count("ref")); CPPUNIT_ASSERT(!pN->GetItems().count("Yes")); CPPUNIT_ASSERT(pN->GetItems().count("Off")); } else { CPPUNIT_ASSERT_EQUAL(OString("Checkbox3"), pT->GetValue()); CPPUNIT_ASSERT_EQUAL(OString("Off"), pAS->GetValue()); CPPUNIT_ASSERT(pN->GetItems().count("ref")); CPPUNIT_ASSERT(!pN->GetItems().count("Yes")); // tdf#143612: Without the fix in place, this test would have failed here CPPUNIT_ASSERT(!pN->GetItems().count("Off")); CPPUNIT_ASSERT(pN->GetItems().count("refOff")); } } } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf118244_radioButtonGroup) { vcl::filter::PDFDocument aDocument; load(u"tdf118244_radioButtonGroup.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // There are eight radio buttons. auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots")); CPPUNIT_ASSERT(pAnnots); CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio buttons", static_cast(8), pAnnots->GetElements().size()); sal_uInt32 nRadioGroups = 0; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("FT")); if (pType && pType->GetValue() == "Btn") { auto pKids = dynamic_cast(pObject->Lookup("Kids")); if (pKids) { size_t expectedSize = 2; ++nRadioGroups; if (nRadioGroups == 3) expectedSize = 3; CPPUNIT_ASSERT_EQUAL(expectedSize, pKids->GetElements().size()); } } } CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio groups", sal_uInt32(3), nRadioGroups); } /// Test writing ToUnicode CMAP for LTR ligatures. // This requires Carlito font, if it is missing the test will most likely // fail. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1) { #if HAVE_MORE_FONTS vcl::filter::PDFDocument aDocument; load(u"tdf115117-1.odt", aDocument); vcl::filter::PDFObjectElement* pToUnicode = nullptr; // Get access to ToUnicode of the first font for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "Font") { auto pToUnicodeRef = dynamic_cast(pObject->Lookup("ToUnicode")); CPPUNIT_ASSERT(pToUnicodeRef); pToUnicode = pToUnicodeRef->LookupObject(); break; } } CPPUNIT_ASSERT(pToUnicode); auto pStream = pToUnicode->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream aObjectStream; ZCodec aZCodec; aZCodec.BeginCompression(); pStream->GetMemory().Seek(0); aZCodec.Decompress(pStream->GetMemory(), aObjectStream); CPPUNIT_ASSERT(aZCodec.EndCompression()); aObjectStream.Seek(0); // The first values, <01> <02> etc., are glyph ids, they might change order // if we changed how font subsets are created. // The second values, <00740069> etc., are Unicode code points in hex, // <00740069> is U+0074 and U+0069 i.e. "ti" which is a ligature in // Carlito/Calibri. This test is failing if any of the second values // changed which means we are not detecting ligatures and writing CMAP // entries for them correctly. If glyph order in the subset changes then // the order here will changes and the PDF has to be carefully inspected to // ensure that the new values are correct before updating the string below. OString aCmap("9 beginbfchar\n" "<01> <00740069>\n" "<02> <0020>\n" "<03> <0074>\n" "<04> <0065>\n" "<05> <0073>\n" "<06> <00660069>\n" "<07> <0066006C>\n" "<08> <006600660069>\n" "<09> <00660066006C>\n" "endbfchar"); auto pStart = static_cast(aObjectStream.GetData()); const char* pEnd = pStart + aObjectStream.GetSize(); auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength()); CPPUNIT_ASSERT(it != pEnd); #endif } /// Test writing ToUnicode CMAP for RTL ligatures. // This requires DejaVu Sans font, if it is missing the test will most likely // fail. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2) { #if HAVE_MORE_FONTS // See the comments in testTdf115117_1() for explanation. vcl::filter::PDFDocument aDocument; load(u"tdf115117-2.odt", aDocument); vcl::filter::PDFObjectElement* pToUnicode = nullptr; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "Font") { auto pToUnicodeRef = dynamic_cast(pObject->Lookup("ToUnicode")); CPPUNIT_ASSERT(pToUnicodeRef); pToUnicode = pToUnicodeRef->LookupObject(); break; } } CPPUNIT_ASSERT(pToUnicode); auto pStream = pToUnicode->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream aObjectStream; ZCodec aZCodec; aZCodec.BeginCompression(); pStream->GetMemory().Seek(0); aZCodec.Decompress(pStream->GetMemory(), aObjectStream); CPPUNIT_ASSERT(aZCodec.EndCompression()); aObjectStream.Seek(0); OString aCmap("7 beginbfchar\n" "<01> <06440627>\n" "<02> <0020>\n" "<03> <0641>\n" "<04> <0642>\n" "<05> <0648>\n" "<06> <06440627>\n" "<07> <0628>\n" "endbfchar"); auto pStart = static_cast(aObjectStream.GetData()); const char* pEnd = pStart + aObjectStream.GetSize(); auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength()); CPPUNIT_ASSERT(it != pEnd); #endif } /// Text extracting LTR text with ligatures. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_1a) { #if HAVE_MORE_FONTS // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf115117-1.odt"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); std::unique_ptr pPdfTextPage = pPdfPage->getTextPage(); CPPUNIT_ASSERT(pPdfTextPage); // Extract the text from the page. This pdfium API is a bit higher level // than we want and might apply heuristic that give false positive, but it // is a good approximation in addition to the check in testTdf115117_1(). int nChars = pPdfTextPage->countChars(); CPPUNIT_ASSERT_EQUAL(44, nChars); std::vector aChars(nChars); for (int i = 0; i < nChars; i++) aChars[i] = pPdfTextPage->getUnicode(i); OUString aActualText(aChars.data(), aChars.size()); CPPUNIT_ASSERT_EQUAL(OUString("ti ti test ti\r\nti test fi fl ffi ffl test fi"), aActualText); #endif } /// Test extracting RTL text with ligatures. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115117_2a) { #if HAVE_MORE_FONTS // See the comments in testTdf115117_1a() for explanation. // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf115117-2.odt"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); std::unique_ptr pPdfTextPage = pPdfPage->getTextPage(); CPPUNIT_ASSERT(pPdfTextPage); int nChars = pPdfTextPage->countChars(); CPPUNIT_ASSERT_EQUAL(13, nChars); std::vector aChars(nChars); for (int i = 0; i < nChars; i++) aChars[i] = pPdfTextPage->getUnicode(i); OUString aActualText(aChars.data(), aChars.size()); CPPUNIT_ASSERT_EQUAL( OUString(u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644"), aActualText); #endif } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf145274) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf145274.docx"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); auto pPage = pPdfDocument->openPage(0); CPPUNIT_ASSERT(pPage); int nPageObjectCount = pPage->getObjectCount(); // Without the fix in place, this test would have failed with // - Expected: 6 // - Actual : 4 CPPUNIT_ASSERT_EQUAL(6, nPageObjectCount); auto pTextPage = pPage->getTextPage(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text) continue; CPPUNIT_ASSERT_EQUAL(11.0, pPageObject->getFontSize()); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFTextRenderMode::Fill, pPageObject->getTextRenderMode()); CPPUNIT_ASSERT_EQUAL(COL_RED, pPageObject->getFillColor()); } } /// Test writing ToUnicode CMAP for doubly encoded glyphs. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_1) { #if HAVE_MORE_FONTS // This requires Amiri font, if it is missing the test will fail. vcl::filter::PDFDocument aDocument; load(u"tdf66597-1.odt", aDocument); { // Get access to ToUnicode of the first font vcl::filter::PDFObjectElement* pToUnicode = nullptr; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "Font") { auto pName = dynamic_cast(pObject->Lookup("BaseFont")); auto aName = pName->GetValue().copy(7); // skip the subset id CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("Amiri-Regular"), aName); auto pToUnicodeRef = dynamic_cast(pObject->Lookup("ToUnicode")); CPPUNIT_ASSERT(pToUnicodeRef); pToUnicode = pToUnicodeRef->LookupObject(); break; } } CPPUNIT_ASSERT(pToUnicode); auto pStream = pToUnicode->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream aObjectStream; ZCodec aZCodec; aZCodec.BeginCompression(); pStream->GetMemory().Seek(0); aZCodec.Decompress(pStream->GetMemory(), aObjectStream); CPPUNIT_ASSERT(aZCodec.EndCompression()); aObjectStream.Seek(0); // The <01> is glyph id, <2044> is code point. // The document has two characters <2044><2215><2044>, but the font // reuses the same glyph for U+2044 and U+2215 so we should have a single // CMAP entry for the U+2044, and U+2215 will be handled with ActualText // (tested below). std::string aCmap("1 beginbfchar\n" "<01> <2044>\n" "endbfchar"); std::string aData(static_cast(aObjectStream.GetData()), aObjectStream.GetSize()); auto nPos = aData.find(aCmap); CPPUNIT_ASSERT(nPos != std::string::npos); } { auto aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get page contents and stream. auto pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); auto pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); auto& rObjectStream = pStream->GetMemory(); // Uncompress the stream. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); // Make sure the expected ActualText is present. std::string aData(static_cast(aUncompressed.GetData()), aUncompressed.GetSize()); std::string aActualText("/Span<(1), nCount); aActualText = "/Span<>>"; nPos = aData.find(aActualText); CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos); } #endif } /// Test writing ActualText for RTL many to one glyph to Unicode mapping. // This requires Reem Kufi font, if it is missing the test will fail. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_2) { #if HAVE_MORE_FONTS vcl::filter::PDFDocument aDocument; load(u"tdf66597-2.odt", aDocument); { // Get access to ToUnicode of the first font vcl::filter::PDFObjectElement* pToUnicode = nullptr; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "Font") { auto pName = dynamic_cast(pObject->Lookup("BaseFont")); auto aName = pName->GetValue().copy(7); // skip the subset id CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("ReemKufi-Regular"), aName); auto pToUnicodeRef = dynamic_cast(pObject->Lookup("ToUnicode")); CPPUNIT_ASSERT(pToUnicodeRef); pToUnicode = pToUnicodeRef->LookupObject(); break; } } CPPUNIT_ASSERT(pToUnicode); auto pStream = pToUnicode->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream aObjectStream; ZCodec aZCodec; aZCodec.BeginCompression(); pStream->GetMemory().Seek(0); aZCodec.Decompress(pStream->GetMemory(), aObjectStream); CPPUNIT_ASSERT(aZCodec.EndCompression()); aObjectStream.Seek(0); std::string aCmap("8 beginbfchar\n" "<02> <0632>\n" "<03> <0020>\n" "<04> <0648>\n" "<05> <0647>\n" "<06> <062F>\n" "<08> <062C>\n" "<0A> <0628>\n" "<0C> <0623>\n" "endbfchar"); std::string aData(static_cast(aObjectStream.GetData()), aObjectStream.GetSize()); auto nPos = aData.find(aCmap); CPPUNIT_ASSERT(nPos != std::string::npos); } { auto aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get page contents and stream. auto pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); auto pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); auto& rObjectStream = pStream->GetMemory(); // Uncompress the stream. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); // Make sure the expected ActualText is present. std::string aData(static_cast(aUncompressed.GetData()), aUncompressed.GetSize()); std::vector aCodes({ "0632", "062C", "0628", "0623" }); std::string aActualText("/Span<>>"; nPos = aData.find(aActualText); CPPUNIT_ASSERT_MESSAGE("ActualText not found for " + aCode, nPos != std::string::npos); } } #endif } /// Test writing ActualText for LTR many to one glyph to Unicode mapping. // This requires Gentium Basic font, if it is missing the test will fail. CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf66597_3) { #if HAVE_MORE_FONTS vcl::filter::PDFDocument aDocument; load(u"tdf66597-3.odt", aDocument); { // Get access to ToUnicode of the first font vcl::filter::PDFObjectElement* pToUnicode = nullptr; for (const auto& aElement : aDocument.GetElements()) { auto pObject = dynamic_cast(aElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "Font") { auto pName = dynamic_cast(pObject->Lookup("BaseFont")); auto aName = pName->GetValue().copy(7); // skip the subset id CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("GentiumBasic"), aName); auto pToUnicodeRef = dynamic_cast(pObject->Lookup("ToUnicode")); CPPUNIT_ASSERT(pToUnicodeRef); pToUnicode = pToUnicodeRef->LookupObject(); break; } } CPPUNIT_ASSERT(pToUnicode); auto pStream = pToUnicode->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream aObjectStream; ZCodec aZCodec; aZCodec.BeginCompression(); pStream->GetMemory().Seek(0); aZCodec.Decompress(pStream->GetMemory(), aObjectStream); CPPUNIT_ASSERT(aZCodec.EndCompression()); aObjectStream.Seek(0); std::string aCmap("2 beginbfchar\n" "<01> <1ECB0331030B>\n" "<05> <0020>\n" "endbfchar"); std::string aData(static_cast(aObjectStream.GetData()), aObjectStream.GetSize()); auto nPos = aData.find(aCmap); CPPUNIT_ASSERT(nPos != std::string::npos); } { auto aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get page contents and stream. auto pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); auto pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); auto& rObjectStream = pStream->GetMemory(); // Uncompress the stream. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); // Make sure the expected ActualText is present. std::string aData(static_cast(aUncompressed.GetData()), aUncompressed.GetSize()); std::string aActualText("/Span<>>"); size_t nCount = 0; size_t nPos = 0; while ((nPos = aData.find(aActualText, nPos)) != std::string::npos) { nCount++; nPos += aActualText.length(); } CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", static_cast(4), nCount); } #endif } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf105954) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); uno::Sequence aFilterData(comphelper::InitPropertySequence( { { "ReduceImageResolution", uno::Any(true) }, { "MaxImageResolution", uno::Any(static_cast(300)) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"tdf105954.odt"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // There is a single image on the page. int nPageObjectCount = pPdfPage->getObjectCount(); CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount); // Check width of the image. std::unique_ptr pPageObject = pPdfPage->getObject(/*index=*/0); Size aMeta = pPageObject->getImageSize(*pPdfPage); // This was 2000, i.e. the 'reduce to 300 DPI' request was ignored. // This is now around 238 (228 on macOS). CPPUNIT_ASSERT_LESS(static_cast(250), aMeta.getWidth()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128445) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); saveAsPDF(u"tdf128445.odp"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the fix in place, this test would have failed with // - Expected: 7 // - Actual : 6 CPPUNIT_ASSERT_EQUAL(7, pPdfPage->getObjectCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf128630) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); saveAsPDF(u"tdf128630.odp"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); // Assert the size of the only bitmap on the page. std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); int nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image) continue; std::unique_ptr pBitmap = pPageObject->getImageBitmap(); CPPUNIT_ASSERT(pBitmap); int nWidth = pBitmap->getWidth(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 466 // - Actual : 289 // i.e. the rotated + scaled arrow was more thin than it should be. CPPUNIT_ASSERT_EQUAL(466, nWidth); int nHeight = pBitmap->getHeight(); CPPUNIT_ASSERT_EQUAL(466, nHeight); } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf106702) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf106702.odt"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has two pages. CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); // First page already has the correct image position. std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); int nExpected = 0; int nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image) continue; // Top, but upside down. nExpected = pPageObject->getBounds().getMaxY(); break; } // Second page had an incorrect image position. pPdfPage = pPdfDocument->openPage(/*nIndex=*/1); CPPUNIT_ASSERT(pPdfPage); int nActual = 0; nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image) continue; // Top, but upside down. nActual = pPageObject->getBounds().getMaxY(); break; } // This failed, vertical pos is 818 points, was 1674 (outside visible page // bounds). CPPUNIT_ASSERT_EQUAL(nExpected, nActual); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf113143) { aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); uno::Sequence aFilterData(comphelper::InitPropertySequence({ { "ExportNotesPages", uno::Any(true) }, // ReduceImageResolution is on by default and that hides the bug we // want to test. { "ReduceImageResolution", uno::Any(false) }, // Set a custom PDF version. { "SelectPdfVersion", uno::Any(static_cast(16)) }, })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"tdf113143.odp"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has two pages. CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); // First has the original (larger) image. std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); int nLarger = 0; int nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image) continue; nLarger = pPageObject->getBounds().getWidth(); break; } // Second page has the scaled (smaller) image. pPdfPage = pPdfDocument->openPage(/*nIndex=*/1); CPPUNIT_ASSERT(pPdfPage); int nSmaller = 0; nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image) continue; nSmaller = pPageObject->getBounds().getWidth(); break; } // This failed, both were 319, now nSmaller is 169. CPPUNIT_ASSERT_LESS(nLarger, nSmaller); // The following check used to fail in the past, header was "%PDF-1.5": maMemory.Seek(0); OString aExpectedHeader("%PDF-1.6"); OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength())); CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint71) { // I just care it doesn't crash aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"forcepoint71.key"); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint80) { // printing asserted in SwCellFrame::FindStartEndOfRowSpanCell aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"forcepoint80-1.rtf"); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testForcePoint3) { // printing asserted in SwFrame::GetNextSctLeaf() aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"flowframe_null_ptr_deref.sample"); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf84283) { // Without the fix in place, this test would have crashed aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf84283.doc"); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115262) { aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export"); saveAsPDF(u"tdf115262.ods"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(8, pPdfDocument->getPageCount()); // Get the 6th page. std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/5); CPPUNIT_ASSERT(pPdfPage); // Look up the position of the first image and the 400th row. std::unique_ptr pTextPage = pPdfPage->getTextPage(); int nPageObjectCount = pPdfPage->getObjectCount(); int nFirstImageTop = 0; int nRowTop = 0; for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); // Top, but upside down. float fTop = pPageObject->getBounds().getMaxY(); if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Image) { nFirstImageTop = fTop; } else if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text) { OUString sText = pPageObject->getText(pTextPage); if (sText == "400") nRowTop = fTop; } } // Make sure that the top of the "400" is below the top of the image (in // bottom-right-corner-based PDF coordinates). // This was: expected less than 144, actual is 199. CPPUNIT_ASSERT_LESS(nFirstImageTop, nRowTop); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf121962) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf121962.odt"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); // Get the first page std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); std::unique_ptr pTextPage = pPdfPage->getTextPage(); // Make sure the table sum is displayed as "0", not faulty expression. int nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text) continue; OUString sText = pPageObject->getText(pTextPage); CPPUNIT_ASSERT(sText != "** Expression is faulty **"); } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf115967) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"tdf115967.odt"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); // Get the first page std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); std::unique_ptr pTextPage = pPdfPage->getTextPage(); // Make sure the elements inside a formula in a RTL document are exported // LTR ( m=750abc ) and not RTL ( m=057cba ) int nPageObjectCount = pPdfPage->getObjectCount(); OUString sText; for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Text) continue; OUString sChar = pPageObject->getText(pTextPage); sText += o3tl::trim(sChar); } CPPUNIT_ASSERT_EQUAL(OUString("m=750abc"), sText); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf124272) { // Import the bugdoc and export as PDF. vcl::filter::PDFDocument aDocument; load(u"tdf124272.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // The page has a stream. vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); vcl::filter::PDFStreamElement* pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Uncompress it. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); OString aBitmap("Q q 299.899 782.189 m\n" "55.2 435.889 l 299.899 435.889 l 299.899 782.189 l\n" "h"); auto pStart = static_cast(aUncompressed.GetData()); const char* pEnd = pStart + aUncompressed.GetSize(); auto it = std::search(pStart, pEnd, aBitmap.getStr(), aBitmap.getStr() + aBitmap.getLength()); CPPUNIT_ASSERT(it != pEnd); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf121615) { vcl::filter::PDFDocument aDocument; load(u"tdf121615.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get access to the only image on the only page. vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); vcl::filter::PDFStreamElement* pStream = pXObject->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Load the embedded image. rObjectStream.Seek(0); GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); Graphic aGraphic; sal_uInt16 format; ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"import", rObjectStream, GRFILTER_FORMAT_DONTKNOW, &format); CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult); // The image should be grayscale 8bit JPEG. sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME); CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND); CPPUNIT_ASSERT_EQUAL(jpegFormat, format); BitmapEx aBitmap = aGraphic.GetBitmapEx(); CPPUNIT_ASSERT_EQUAL(tools::Long(200), aBitmap.GetSizePixel().Width()); CPPUNIT_ASSERT_EQUAL(tools::Long(300), aBitmap.GetSizePixel().Height()); CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat()); // tdf#121615 was caused by broken handling of data width with 8bit color, // so the test image has some black in the bottomright corner, check it's there CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 0)); CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 299)); CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(199, 0)); CPPUNIT_ASSERT_EQUAL(COL_BLACK, aBitmap.GetPixelColor(199, 299)); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf141171) { vcl::filter::PDFDocument aDocument; load(u"tdf141171.odt", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get access to the only image on the only page. vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); vcl::filter::PDFStreamElement* pStream = pXObject->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Load the embedded image. rObjectStream.Seek(0); GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); Graphic aGraphic; sal_uInt16 format; ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"import", rObjectStream, GRFILTER_FORMAT_DONTKNOW, &format); CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult); // The image should be grayscale 8bit JPEG. sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME); CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND); CPPUNIT_ASSERT_EQUAL(jpegFormat, format); BitmapEx aBitmap = aGraphic.GetBitmapEx(); Size aSize = aBitmap.GetSizePixel(); CPPUNIT_ASSERT_EQUAL(tools::Long(878), aSize.Width()); CPPUNIT_ASSERT_EQUAL(tools::Long(127), aSize.Height()); CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat()); for (tools::Long nX = 0; nX < aSize.Width(); ++nX) { for (tools::Long nY = 0; nY < aSize.Height(); ++nY) { // Check all pixels in the image are white // Without the fix in place, this test would have failed with // - Expected: Color: R:255 G:255 B:255 A:0 // - Actual : Color: R:0 G:0 B:0 A:0 CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(nX, nY)); } } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf129085) { vcl::filter::PDFDocument aDocument; load(u"tdf129085.docx", aDocument); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); // Get access to the only image on the only page. vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); // Without the fix in place, this test would have failed here CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(1), pXObjects->GetItems().size()); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); CPPUNIT_ASSERT(pXObject); vcl::filter::PDFStreamElement* pStream = pXObject->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Load the embedded image. rObjectStream.Seek(0); GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); Graphic aGraphic; sal_uInt16 format; ErrCode bResult = rFilter.ImportGraphic(aGraphic, u"import", rObjectStream, GRFILTER_FORMAT_DONTKNOW, &format); CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult); sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName(JPG_SHORTNAME); CPPUNIT_ASSERT(jpegFormat != GRFILTER_FORMAT_NOTFOUND); CPPUNIT_ASSERT_EQUAL(jpegFormat, format); BitmapEx aBitmap = aGraphic.GetBitmapEx(); CPPUNIT_ASSERT_EQUAL(tools::Long(884), aBitmap.GetSizePixel().Width()); CPPUNIT_ASSERT_EQUAL(tools::Long(925), aBitmap.GetSizePixel().Height()); CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N24_BPP, aBitmap.getPixelFormat()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTocLink) { // Load the Writer document. OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "toc-link.fodt"; mxComponent = loadFromDesktop(aURL); // Update the ToC. uno::Reference xDocumentIndexesSupplier(mxComponent, uno::UNO_QUERY); CPPUNIT_ASSERT(xDocumentIndexesSupplier.is()); uno::Reference xToc( xDocumentIndexesSupplier->getDocumentIndexes()->getByIndex(0), uno::UNO_QUERY); CPPUNIT_ASSERT(xToc.is()); xToc->refresh(); // Save as PDF. uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Ensure there is a link on the first page (in the ToC). // Without the accompanying fix in place, this test would have failed, as the page contained no // links. CPPUNIT_ASSERT(pPdfPage->hasLinks()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testReduceSmallImage) { // Load the Writer document. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"reduce-small-image.fodt"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount()); std::unique_ptr pPageObject = pPdfPage->getObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pPageObject->getType()); // Make sure we don't scale down a tiny bitmap. std::unique_ptr pBitmap = pPageObject->getImageBitmap(); CPPUNIT_ASSERT(pBitmap); int nWidth = pBitmap->getWidth(); int nHeight = pBitmap->getHeight(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 16 // - Actual : 6 // i.e. the image was scaled down to 300 DPI, even if it had tiny size. CPPUNIT_ASSERT_EQUAL(16, nWidth); CPPUNIT_ASSERT_EQUAL(16, nHeight); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf114256) { aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export"); saveAsPDF(u"tdf114256.ods"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the fix in place, this test would have failed with // - Expected: 13 // - Actual : 0 CPPUNIT_ASSERT_EQUAL(13, pPdfPage->getObjectCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf147027) { // Load the Calc document. aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export"); saveAsPDF(u"tdf147027.ods"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the fix in place, this test would have failed with // - Expected: 778 // - Actual : 40 CPPUNIT_ASSERT_EQUAL(778, pPdfPage->getObjectCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf135346) { // Load the Calc document. aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export"); saveAsPDF(u"tdf135346.ods"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the fix in place, this test would have failed with // - Expected: 56 // - Actual : 0 CPPUNIT_ASSERT_EQUAL(56, pPdfPage->getObjectCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf147164) { aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); saveAsPDF(u"tdf147164.odp"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/1); CPPUNIT_ASSERT(pPdfPage); // Without the fix in place, this test would have failed with // - Expected: 22 // - Actual : 16 CPPUNIT_ASSERT_EQUAL(22, pPdfPage->getObjectCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testReduceImage) { // Load the Writer document. OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "reduce-image.fodt"; mxComponent = loadFromDesktop(aURL); // Save as PDF. uno::Reference xFactory = getMultiServiceFactory(); uno::Reference xFilter( xFactory->createInstance("com.sun.star.document.PDFFilter"), uno::UNO_QUERY); uno::Reference xExporter(xFilter, uno::UNO_QUERY); xExporter->setSourceDocument(mxComponent); SvFileStream aOutputStream(maTempFile.GetURL(), StreamMode::WRITE); uno::Reference xOutputStream(new utl::OStreamWrapper(aOutputStream)); uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "ReduceImageResolution", uno::Any(false) } })); // This is intentionally in an "unlucky" order, output stream comes before filter data. uno::Sequence aDescriptor(comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer_pdf_Export")) }, { "OutputStream", uno::Any(xOutputStream) }, { "FilterData", uno::Any(aFilterData) }, })); xFilter->filter(aDescriptor); aOutputStream.Close(); // Parse the PDF: get the image. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount()); std::unique_ptr pPageObject = pPdfPage->getObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pPageObject->getType()); // Make sure we don't scale down a bitmap. std::unique_ptr pBitmap = pPageObject->getImageBitmap(); CPPUNIT_ASSERT(pBitmap); int nWidth = pBitmap->getWidth(); int nHeight = pBitmap->getHeight(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 160 // - Actual : 6 // i.e. the image was scaled down even with ReduceImageResolution=false. CPPUNIT_ASSERT_EQUAL(160, nWidth); CPPUNIT_ASSERT_EQUAL(160, nHeight); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWrongPage) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); saveAsPDF(u"link-wrong-page.odp"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has 2 pages. CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); // First page should have 1 link (2nd slide, 1st was hidden). std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the accompanying fix in place, this test would have failed, as the link of the first // page went to the second page due to the hidden first slide. CPPUNIT_ASSERT(pPdfPage->hasLinks()); // Second page should have no links (3rd slide). std::unique_ptr pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1); CPPUNIT_ASSERT(pPdfPage2); CPPUNIT_ASSERT(!pPdfPage2->hasLinks()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWrongPagePartial) { // Given a Draw document with 3 pages, a link on the 2nd page: // When exporting that the 2nd and 3rd page to pdf: uno::Sequence aFilterData = { comphelper::makePropertyValue("PageRange", OUString("2-3")), }; aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export"); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"link-wrong-page-partial.odg"); // Then make sure the we have a link on the 1st page, but not on the 2nd one: std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the accompanying fix in place, this test would have failed, as the link was on the // 2nd page instead. CPPUNIT_ASSERT(pPdfPage->hasLinks()); std::unique_ptr pPdfPage2 = pPdfDocument->openPage(/*nIndex=*/1); CPPUNIT_ASSERT(pPdfPage2); CPPUNIT_ASSERT(!pPdfPage2->hasLinks()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testPageRange) { // Given a document with 3 pages: // When exporting that document to PDF, skipping the first page: aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export"); aMediaDescriptor["FilterOptions"] <<= OUString("{\"PageRange\":{\"type\":\"string\",\"value\":\"2-\"}}"); saveAsPDF(u"link-wrong-page-partial.odg"); // Then make sure the resulting PDF has 2 pages: std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // Without the accompanying fix in place, this test would have failed with: // - Expected: 2 // - Actual : 3 // i.e. FilterOptions was ignored. CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testLargePage) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export"); saveAsPDF(u"6m-wide.odg"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has 1 page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); // Check the value (not the unit) of the page size. basegfx::B2DSize aSize = pPdfDocument->getPageSize(0); // Without the accompanying fix in place, this test would have failed with: // - Expected: 8503.94 // - Actual : 17007.875 // i.e. the value for 600 cm was larger than the 14 400 limit set in the spec. CPPUNIT_ASSERT_DOUBLES_EQUAL(8503.94, aSize.getX(), 0.01); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageResourceInlineXObjectRef) { // Create an empty document. mxComponent = loadFromDesktop("private:factory/swriter"); uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); uno::Reference xText = xTextDocument->getText(); uno::Reference xCursor = xText->createTextCursor(); // Insert the PDF image. uno::Reference xFactory(mxComponent, uno::UNO_QUERY); uno::Reference xGraphicObject( xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY); OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-resource-inline-xobject-ref.pdf"; xGraphicObject->setPropertyValue("GraphicURL", uno::Any(aURL)); uno::Reference xShape(xGraphicObject, uno::UNO_QUERY); xShape->setSize(awt::Size(1000, 1000)); uno::Reference xTextContent(xGraphicObject, uno::UNO_QUERY); xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false); // Save as PDF. uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Parse the export result. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); // Make sure that the page -> form -> form has a child image. std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount()); std::unique_ptr pPageObject = pPdfPage->getObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType()); // 2: white background and the actual object. CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount()); std::unique_ptr pFormObject = pPageObject->getFormObject(1); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType()); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1 // - Actual : 0 // i.e. the sub-form was missing its image. CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount()); // Check if the inner form object (original page object in the pdf image) has the correct // rotation. std::unique_ptr pInnerFormObject = pFormObject->getFormObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType()); CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount()); std::unique_ptr pImage = pInnerFormObject->getFormObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType()); basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix(); basegfx::B2DTuple aScale; basegfx::B2DTuple aTranslate; double fRotate = 0; double fShearX = 0; aMat.decompose(aScale, aTranslate, fRotate, fShearX); int nRotateDeg = basegfx::rad2deg(fRotate); // Without the accompanying fix in place, this test would have failed with: // - Expected: -90 // - Actual : 0 // i.e. rotation was lost on pdf export. CPPUNIT_ASSERT_EQUAL(-90, nRotateDeg); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testDefaultVersion) { // Create an empty document. mxComponent = loadFromDesktop("private:factory/swriter"); // Save as PDF. uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Parse the export result. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); int nFileVersion = pPdfDocument->getFileVersion(); CPPUNIT_ASSERT_EQUAL(16, nFileVersion); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testVersion15) { // Create an empty document. mxComponent = loadFromDesktop("private:factory/swriter"); // Save as PDF. uno::Reference xStorable(mxComponent, uno::UNO_QUERY); uno::Sequence aFilterData(comphelper::InitPropertySequence( { { "SelectPdfVersion", uno::Any(static_cast(15)) } })); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); aMediaDescriptor["FilterData"] <<= aFilterData; xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Parse the export result. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); int nFileVersion = pPdfDocument->getFileVersion(); CPPUNIT_ASSERT_EQUAL(15, nFileVersion); } // Check round-trip of importing and exporting the PDF with PDFium filter, // which imports the PDF document as multiple PDFs as graphic object. // Each page in the document has one PDF graphic object which content is // the corresponding page in the PDF. When such a document is exported, // the PDF graphic gets embedded into the exported PDF document (as a // Form XObject). CPPUNIT_TEST_FIXTURE(PdfExportTest, testMultiPagePDF) { // setenv only works on unix based systems #ifndef _WIN32 // We need to enable PDFium import (and make sure to disable after the test) bool bResetEnvVar = false; if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr) { bResetEnvVar = true; setenv("LO_IMPORT_USE_PDFIUM", "1", false); } comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() { if (bResetEnvVar) unsetenv("LO_IMPORT_USE_PDFIUM"); }); // Load the PDF and save as PDF vcl::filter::PDFDocument aDocument; load(u"SimpleMultiPagePDF.pdf", aDocument); std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(3), aPages.size()); vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); CPPUNIT_ASSERT_EQUAL(static_cast(3), pXObjects->GetItems().size()); // 3 PDFs as Form XObjects std::vector rIDs; for (auto const& rPair : pXObjects->GetItems()) { rIDs.push_back(rPair.first); } // Let's check the embedded PDF pages - just make sure the size differs, // which should indicate we don't have 3 times the same page. { // embedded PDF page 1 vcl::filter::PDFObjectElement* pXObject1 = pXObjects->LookupObject(rIDs[0]); CPPUNIT_ASSERT(pXObject1); CPPUNIT_ASSERT_EQUAL(OString("Im19"), rIDs[0]); auto pSubtype1 = dynamic_cast(pXObject1->Lookup("Subtype")); CPPUNIT_ASSERT(pSubtype1); CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype1->GetValue()); auto pXObjectResources = dynamic_cast(pXObject1->Lookup("Resources")); CPPUNIT_ASSERT(pXObjectResources); auto pXObjectForms = dynamic_cast( pXObjectResources->LookupElement("XObject")); CPPUNIT_ASSERT(pXObjectForms); vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first); CPPUNIT_ASSERT(pForm); vcl::filter::PDFStreamElement* pStream = pForm->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); rObjectStream.Seek(STREAM_SEEK_TO_BEGIN); // Just check that the size of the page stream is what is expected. CPPUNIT_ASSERT_EQUAL(sal_uInt64(230), rObjectStream.remainingSize()); } { // embedded PDF page 2 vcl::filter::PDFObjectElement* pXObject2 = pXObjects->LookupObject(rIDs[1]); CPPUNIT_ASSERT(pXObject2); CPPUNIT_ASSERT_EQUAL(OString("Im24"), rIDs[1]); auto pSubtype2 = dynamic_cast(pXObject2->Lookup("Subtype")); CPPUNIT_ASSERT(pSubtype2); CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype2->GetValue()); auto pXObjectResources = dynamic_cast(pXObject2->Lookup("Resources")); CPPUNIT_ASSERT(pXObjectResources); auto pXObjectForms = dynamic_cast( pXObjectResources->LookupElement("XObject")); CPPUNIT_ASSERT(pXObjectForms); vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first); CPPUNIT_ASSERT(pForm); vcl::filter::PDFStreamElement* pStream = pForm->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); rObjectStream.Seek(STREAM_SEEK_TO_BEGIN); // Just check that the size of the page stream is what is expected CPPUNIT_ASSERT_EQUAL(sal_uInt64(309), rObjectStream.remainingSize()); } { // embedded PDF page 3 vcl::filter::PDFObjectElement* pXObject3 = pXObjects->LookupObject(rIDs[2]); CPPUNIT_ASSERT(pXObject3); CPPUNIT_ASSERT_EQUAL(OString("Im4"), rIDs[2]); auto pSubtype3 = dynamic_cast(pXObject3->Lookup("Subtype")); CPPUNIT_ASSERT(pSubtype3); CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype3->GetValue()); auto pXObjectResources = dynamic_cast(pXObject3->Lookup("Resources")); CPPUNIT_ASSERT(pXObjectResources); auto pXObjectForms = dynamic_cast( pXObjectResources->LookupElement("XObject")); CPPUNIT_ASSERT(pXObjectForms); vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first); CPPUNIT_ASSERT(pForm); vcl::filter::PDFStreamElement* pStream = pForm->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); rObjectStream.Seek(STREAM_SEEK_TO_BEGIN); // Just check that the size of the page stream is what is expected CPPUNIT_ASSERT_EQUAL(sal_uInt64(193), rObjectStream.remainingSize()); } #endif } CPPUNIT_TEST_FIXTURE(PdfExportTest, testFormFontName) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); saveAsPDF(u"form-font-name.odt"); // Parse the export result with pdfium. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // The page has one annotation. CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount()); std::unique_ptr pAnnot = pPdfPage->getAnnotation(0); // Examine the default appearance. CPPUNIT_ASSERT(pAnnot->hasKey("DA")); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFObjectType::String, pAnnot->getValueType("DA")); OUString aDA = pAnnot->getString("DA"); // Without the accompanying fix in place, this test would have failed with: // - Expected: 0 0 0 rg /TiRo 12 Tf // - Actual : 0 0 0 rg /F2 12 Tf // i.e. Liberation Serif was exposed as a form font as-is, without picking the closest built-in // font. CPPUNIT_ASSERT_EQUAL(OUString("0 0 0 rg /TiRo 12 Tf"), aDA); } // Check we don't have duplicated objects when we reexport the PDF multiple // times or the size will exponentially increase over time. CPPUNIT_TEST_FIXTURE(PdfExportTest, testReexportPDF) { // setenv only works on unix based systems #ifndef _WIN32 // We need to enable PDFium import (and make sure to disable after the test) bool bResetEnvVar = false; if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr) { bResetEnvVar = true; setenv("LO_IMPORT_USE_PDFIUM", "1", false); } comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() { if (bResetEnvVar) unsetenv("LO_IMPORT_USE_PDFIUM"); }); // Load the PDF and save as PDF vcl::filter::PDFDocument aDocument; load(u"PDFWithImages.pdf", aDocument); // Assert that the XObject in the page resources dictionary is a reference XObject. std::vector aPages = aDocument.GetPages(); // The document has 2 pages. CPPUNIT_ASSERT_EQUAL(size_t(2), aPages.size()); // PAGE 1 { vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); CPPUNIT_ASSERT(pResources); auto pXObjects = dynamic_cast(pResources->Lookup("XObject")); CPPUNIT_ASSERT(pXObjects); std::vector rIDs; for (auto const& rPair : pXObjects->GetItems()) rIDs.push_back(rPair.first); CPPUNIT_ASSERT_EQUAL(size_t(2), rIDs.size()); std::vector aBitmapRefs1; std::vector aBitmapRefs2; { // FORM object 1 OString aID = rIDs[0]; CPPUNIT_ASSERT_EQUAL(OString("Im12"), aID); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(aID); CPPUNIT_ASSERT(pXObject); auto pSubtype = dynamic_cast(pXObject->Lookup("Subtype")); CPPUNIT_ASSERT(pSubtype); CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue()); auto pInnerResources = dynamic_cast(pXObject->Lookup("Resources")); CPPUNIT_ASSERT(pInnerResources); auto pInnerXObjects = dynamic_cast( pInnerResources->LookupElement("XObject")); CPPUNIT_ASSERT(pInnerXObjects); CPPUNIT_ASSERT_EQUAL(size_t(1), pInnerXObjects->GetItems().size()); OString aInnerObjectID = pInnerXObjects->GetItems().begin()->first; CPPUNIT_ASSERT_EQUAL(OString("Im13"), aInnerObjectID); vcl::filter::PDFObjectElement* pInnerXObject = pInnerXObjects->LookupObject(aInnerObjectID); CPPUNIT_ASSERT(pInnerXObject); auto pInnerSubtype = dynamic_cast(pInnerXObject->Lookup("Subtype")); CPPUNIT_ASSERT(pInnerSubtype); CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue()); auto pInnerInnerResources = dynamic_cast( pInnerXObject->Lookup("Resources")); CPPUNIT_ASSERT(pInnerInnerResources); auto pInnerInnerXObjects = dynamic_cast( pInnerInnerResources->LookupElement("XObject")); CPPUNIT_ASSERT(pInnerInnerXObjects); CPPUNIT_ASSERT_EQUAL(size_t(2), pInnerInnerXObjects->GetItems().size()); std::vector aBitmapIDs1; for (auto const& rPair : pInnerInnerXObjects->GetItems()) aBitmapIDs1.push_back(rPair.first); { CPPUNIT_ASSERT_EQUAL(OString("Im11"), aBitmapIDs1[0]); auto pRef = dynamic_cast( pInnerInnerXObjects->LookupElement(aBitmapIDs1[0])); CPPUNIT_ASSERT(pRef); aBitmapRefs1.push_back(pRef->GetObjectValue()); CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue()); vcl::filter::PDFObjectElement* pBitmap = pInnerInnerXObjects->LookupObject(aBitmapIDs1[0]); CPPUNIT_ASSERT(pBitmap); auto pBitmapSubtype = dynamic_cast(pBitmap->Lookup("Subtype")); CPPUNIT_ASSERT(pBitmapSubtype); CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue()); } { CPPUNIT_ASSERT_EQUAL(OString("Im5"), aBitmapIDs1[1]); auto pRef = dynamic_cast( pInnerInnerXObjects->LookupElement(aBitmapIDs1[1])); CPPUNIT_ASSERT(pRef); aBitmapRefs1.push_back(pRef->GetObjectValue()); CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue()); vcl::filter::PDFObjectElement* pBitmap = pInnerInnerXObjects->LookupObject(aBitmapIDs1[1]); CPPUNIT_ASSERT(pBitmap); auto pBitmapSubtype = dynamic_cast(pBitmap->Lookup("Subtype")); CPPUNIT_ASSERT(pBitmapSubtype); CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue()); } } { // FORM object 2 OString aID = rIDs[1]; CPPUNIT_ASSERT_EQUAL(OString("Im4"), aID); vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(aID); CPPUNIT_ASSERT(pXObject); auto pSubtype = dynamic_cast(pXObject->Lookup("Subtype")); CPPUNIT_ASSERT(pSubtype); CPPUNIT_ASSERT_EQUAL(OString("Form"), pSubtype->GetValue()); auto pInnerResources = dynamic_cast(pXObject->Lookup("Resources")); CPPUNIT_ASSERT(pInnerResources); auto pInnerXObjects = dynamic_cast( pInnerResources->LookupElement("XObject")); CPPUNIT_ASSERT(pInnerXObjects); CPPUNIT_ASSERT_EQUAL(size_t(1), pInnerXObjects->GetItems().size()); OString aInnerObjectID = pInnerXObjects->GetItems().begin()->first; CPPUNIT_ASSERT_EQUAL(OString("Im5"), aInnerObjectID); vcl::filter::PDFObjectElement* pInnerXObject = pInnerXObjects->LookupObject(aInnerObjectID); CPPUNIT_ASSERT(pInnerXObject); auto pInnerSubtype = dynamic_cast(pInnerXObject->Lookup("Subtype")); CPPUNIT_ASSERT(pInnerSubtype); CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue()); auto pInnerInnerResources = dynamic_cast( pInnerXObject->Lookup("Resources")); CPPUNIT_ASSERT(pInnerInnerResources); auto pInnerInnerXObjects = dynamic_cast( pInnerInnerResources->LookupElement("XObject")); CPPUNIT_ASSERT(pInnerInnerXObjects); CPPUNIT_ASSERT_EQUAL(size_t(2), pInnerInnerXObjects->GetItems().size()); std::vector aBitmapIDs2; for (auto const& rPair : pInnerInnerXObjects->GetItems()) aBitmapIDs2.push_back(rPair.first); { CPPUNIT_ASSERT_EQUAL(OString("Im11"), aBitmapIDs2[0]); auto pRef = dynamic_cast( pInnerInnerXObjects->LookupElement(aBitmapIDs2[0])); CPPUNIT_ASSERT(pRef); aBitmapRefs2.push_back(pRef->GetObjectValue()); CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue()); vcl::filter::PDFObjectElement* pBitmap = pInnerInnerXObjects->LookupObject(aBitmapIDs2[0]); CPPUNIT_ASSERT(pBitmap); auto pBitmapSubtype = dynamic_cast(pBitmap->Lookup("Subtype")); CPPUNIT_ASSERT(pBitmapSubtype); CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue()); } { CPPUNIT_ASSERT_EQUAL(OString("Im5"), aBitmapIDs2[1]); auto pRef = dynamic_cast( pInnerInnerXObjects->LookupElement(aBitmapIDs2[1])); CPPUNIT_ASSERT(pRef); aBitmapRefs2.push_back(pRef->GetObjectValue()); CPPUNIT_ASSERT_EQUAL(0, pRef->GetGenerationValue()); vcl::filter::PDFObjectElement* pBitmap = pInnerInnerXObjects->LookupObject(aBitmapIDs2[1]); CPPUNIT_ASSERT(pBitmap); auto pBitmapSubtype = dynamic_cast(pBitmap->Lookup("Subtype")); CPPUNIT_ASSERT(pBitmapSubtype); CPPUNIT_ASSERT_EQUAL(OString("Image"), pBitmapSubtype->GetValue()); } } // Ref should point to the same bitmap CPPUNIT_ASSERT_EQUAL(aBitmapRefs1[0], aBitmapRefs2[0]); CPPUNIT_ASSERT_EQUAL(aBitmapRefs1[1], aBitmapRefs2[1]); } #endif } // Check we correctly copy more complex resources (Fonts describing // glyphs in recursive arrays) to the target PDF CPPUNIT_TEST_FIXTURE(PdfExportTest, testReexportDocumentWithComplexResources) { // setenv only works on unix based systems #ifndef _WIN32 // We need to enable PDFium import (and make sure to disable after the test) bool bResetEnvVar = false; if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr) { bResetEnvVar = true; setenv("LO_IMPORT_USE_PDFIUM", "1", false); } comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() { if (bResetEnvVar) unsetenv("LO_IMPORT_USE_PDFIUM"); }); // Load the PDF and save as PDF vcl::filter::PDFDocument aDocument; load(u"ComplexContentDictionary.pdf", aDocument); // Assert that the XObject in the page resources dictionary is a reference XObject. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); // Go directly to the Font object (24 0) (number could change if we change how PDF export works) auto pFont = aDocument.LookupObject(23); CPPUNIT_ASSERT(pFont); // Check it is the Font object (Type = Font) auto pName = dynamic_cast(pFont->GetDictionary()->LookupElement("Type")); CPPUNIT_ASSERT(pName); CPPUNIT_ASSERT_EQUAL(OString("Font"), pName->GetValue()); // Check BaseFont is what we expect auto pBaseFont = dynamic_cast( pFont->GetDictionary()->LookupElement("BaseFont")); CPPUNIT_ASSERT(pBaseFont); CPPUNIT_ASSERT_EQUAL(OString("HOTOMR+Calibri,Italic"), pBaseFont->GetValue()); // Check and get the W array auto pWArray = dynamic_cast(pFont->GetDictionary()->LookupElement("W")); CPPUNIT_ASSERT(pWArray); CPPUNIT_ASSERT_EQUAL(size_t(26), pWArray->GetElements().size()); // Check the content of W array // ObjectCopier didn't copy this array correctly and the document // had glyphs at the wrong places { // first 2 elements auto pNumberAtIndex0 = dynamic_cast(pWArray->GetElement(0)); CPPUNIT_ASSERT(pNumberAtIndex0); CPPUNIT_ASSERT_EQUAL(3.0, pNumberAtIndex0->GetValue()); auto pArrayAtIndex1 = dynamic_cast(pWArray->GetElement(1)); CPPUNIT_ASSERT(pArrayAtIndex1); CPPUNIT_ASSERT_EQUAL(size_t(1), pArrayAtIndex1->GetElements().size()); { auto pNumber = dynamic_cast(pArrayAtIndex1->GetElement(0)); CPPUNIT_ASSERT(pNumber); CPPUNIT_ASSERT_EQUAL(226.0, pNumber->GetValue()); } // last 2 elements auto pNumberAtIndex24 = dynamic_cast(pWArray->GetElement(24)); CPPUNIT_ASSERT(pNumberAtIndex24); CPPUNIT_ASSERT_EQUAL(894.0, pNumberAtIndex24->GetValue()); auto pArrayAtIndex25 = dynamic_cast(pWArray->GetElement(25)); CPPUNIT_ASSERT(pArrayAtIndex25); CPPUNIT_ASSERT_EQUAL(size_t(2), pArrayAtIndex25->GetElements().size()); { auto pNumber1 = dynamic_cast(pArrayAtIndex25->GetElement(0)); CPPUNIT_ASSERT(pNumber1); CPPUNIT_ASSERT_EQUAL(303.0, pNumber1->GetValue()); auto pNumber2 = dynamic_cast(pArrayAtIndex25->GetElement(1)); CPPUNIT_ASSERT(pNumber2); CPPUNIT_ASSERT_EQUAL(303.0, pNumber2->GetValue()); } } #endif } // Tests that at export the PDF has the PDF/UA metadata properly set // when we enable PDF/UA support. CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfUaMetadata) { // Import a basic document (document doesn't really matter) aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable PDF/UA uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"BrownFoxLazyDog.odt"); // Parse the export result. vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); auto* pCatalog = aDocument.GetCatalog(); CPPUNIT_ASSERT(pCatalog); auto* pCatalogDictionary = pCatalog->GetDictionary(); CPPUNIT_ASSERT(pCatalogDictionary); auto* pMetadataObject = pCatalogDictionary->LookupObject("Metadata"); CPPUNIT_ASSERT(pMetadataObject); auto* pMetadataDictionary = pMetadataObject->GetDictionary(); auto* pType = dynamic_cast(pMetadataDictionary->LookupElement("Type")); CPPUNIT_ASSERT(pType); CPPUNIT_ASSERT_EQUAL(OString("Metadata"), pType->GetValue()); auto* pStreamObject = pMetadataObject->GetStream(); CPPUNIT_ASSERT(pStreamObject); auto& rStream = pStreamObject->GetMemory(); rStream.Seek(0); // Search for the PDF/UA marker in the metadata tools::XmlWalker aWalker; CPPUNIT_ASSERT(aWalker.open(&rStream)); CPPUNIT_ASSERT_EQUAL(OString("xmpmeta"), aWalker.name()); bool bPdfUaMarkerFound = false; OString aPdfUaPart; aWalker.children(); while (aWalker.isValid()) { if (aWalker.name() == "RDF" && aWalker.namespaceHref() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#") { aWalker.children(); while (aWalker.isValid()) { if (aWalker.name() == "Description" && aWalker.namespaceHref() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#") { aWalker.children(); while (aWalker.isValid()) { if (aWalker.name() == "part" && aWalker.namespaceHref() == "http://www.aiim.org/pdfua/ns/id/") { aPdfUaPart = aWalker.content(); bPdfUaMarkerFound = true; } aWalker.next(); } aWalker.parent(); } aWalker.next(); } aWalker.parent(); } aWalker.next(); } aWalker.parent(); CPPUNIT_ASSERT(bPdfUaMarkerFound); CPPUNIT_ASSERT_EQUAL(OString("1"), aPdfUaPart); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf139736) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable PDF/UA uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"tdf139736-1.odt"); vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"); CPPUNIT_ASSERT(pContents); vcl::filter::PDFStreamElement* pStream = pContents->GetStream(); CPPUNIT_ASSERT(pStream); SvMemoryStream& rObjectStream = pStream->GetMemory(); // Uncompress it. SvMemoryStream aUncompressed; ZCodec aZCodec; aZCodec.BeginCompression(); rObjectStream.Seek(0); aZCodec.Decompress(rObjectStream, aUncompressed); CPPUNIT_ASSERT(aZCodec.EndCompression()); auto pStart = static_cast(aUncompressed.GetData()); const char* const pEnd = pStart + aUncompressed.GetSize(); enum { Default, Artifact, Tagged } state = Default; auto nLine(0); auto nTagged(0); auto nArtifacts(0); while (true) { ++nLine; auto const pLine = ::std::find(pStart, pEnd, '\n'); if (pLine == pEnd) { break; } std::string_view const line(pStart, pLine - pStart); pStart = pLine + 1; if (!line.empty() && line[0] != '%') { ::std::cerr << nLine << ": " << line << "\n"; if (line == "/Artifact BMC") { CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state); state = Artifact; ++nArtifacts; } else if (line == "/Standard<>BDC") { CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state); state = Tagged; ++nTagged; } else if (line == "EMC") { CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default); state = Default; } else if (nLine > 1) // first line is expected "0.1 w" { CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default); } } } CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state); CPPUNIT_ASSERT_EQUAL(static_cast(1), nTagged); // text in body // 1 image and 1 frame and 1 header text; arbitrary number of aux stuff like borders CPPUNIT_ASSERT(nArtifacts >= 3); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf149140) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable PDF/UA uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"TableTH_test_LibreOfficeWriter7.3.3_HeaderRow-HeadersInTopRow.fodt"); vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); int nTH(0); for (const auto& rDocElement : aDocument.GetElements()) { auto pObject = dynamic_cast(rDocElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "StructElem") { auto pS = dynamic_cast(pObject->Lookup("S")); if (pS && pS->GetValue() == "TH") { int nTable(0); auto pAttrs = dynamic_cast(pObject->Lookup("A")); CPPUNIT_ASSERT(pAttrs != nullptr); for (const auto& rAttrRef : pAttrs->GetElements()) { auto pARef = dynamic_cast(rAttrRef); CPPUNIT_ASSERT(pARef != nullptr); auto pAttr = pARef->LookupObject(); CPPUNIT_ASSERT(pAttr != nullptr); auto pAttrDict = pAttr->GetDictionary(); CPPUNIT_ASSERT(pAttrDict != nullptr); auto pOwner = dynamic_cast(pAttrDict->LookupElement("O")); CPPUNIT_ASSERT(pOwner != nullptr); if (pOwner->GetValue() == "Table") { auto pScope = dynamic_cast( pAttrDict->LookupElement("Scope")); CPPUNIT_ASSERT(pScope != nullptr); CPPUNIT_ASSERT_EQUAL(OString("Column"), pScope->GetValue()); ++nTable; } } CPPUNIT_ASSERT_EQUAL(int(1), nTable); ++nTH; } } } CPPUNIT_ASSERT_EQUAL(int(6), nTH); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf135638) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable PDF/UA uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"image-shape.fodt"); vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); int nFigure(0); for (const auto& rDocElement : aDocument.GetElements()) { auto pObject = dynamic_cast(rDocElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "StructElem") { auto pS = dynamic_cast(pObject->Lookup("S")); if (pS && pS->GetValue() == "Figure") { auto pARef = dynamic_cast(pObject->Lookup("A")); CPPUNIT_ASSERT(pARef != nullptr); auto pAttr = pARef->LookupObject(); CPPUNIT_ASSERT(pAttr != nullptr); auto pAttrDict = pAttr->GetDictionary(); CPPUNIT_ASSERT(pAttrDict != nullptr); auto pOwner = dynamic_cast(pAttrDict->LookupElement("O")); CPPUNIT_ASSERT(pOwner != nullptr); CPPUNIT_ASSERT_EQUAL(OString("Layout"), pOwner->GetValue()); auto pBBox = dynamic_cast(pAttrDict->LookupElement("BBox")); CPPUNIT_ASSERT(pBBox != nullptr); if (nFigure == 0) { CPPUNIT_ASSERT_DOUBLES_EQUAL( 139.5, dynamic_cast(pBBox->GetElements()[0]) ->GetValue(), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL( 480.3, dynamic_cast(pBBox->GetElements()[1]) ->GetValue(), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL( 472.5, dynamic_cast(pBBox->GetElements()[2]) ->GetValue(), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL( 735.3, dynamic_cast(pBBox->GetElements()[3]) ->GetValue(), 0.01); } else { CPPUNIT_ASSERT_DOUBLES_EQUAL( 178.45, dynamic_cast(pBBox->GetElements()[0]) ->GetValue(), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL( 318.65, dynamic_cast(pBBox->GetElements()[1]) ->GetValue(), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL( 326.35, dynamic_cast(pBBox->GetElements()[2]) ->GetValue(), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL( 382.55, dynamic_cast(pBBox->GetElements()[3]) ->GetValue(), 0.01); } ++nFigure; } } } // the first one is a Writer image, 2nd one SdrRectObj CPPUNIT_ASSERT_EQUAL(int(2), nFigure); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf57423) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable PDF/UA uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"Description PDF Export test .odt"); vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); int nFigure(0); int nFormula(0); int nDiv(0); for (const auto& rDocElement : aDocument.GetElements()) { auto pObject = dynamic_cast(rDocElement.get()); if (!pObject) continue; auto pType = dynamic_cast(pObject->Lookup("Type")); if (pType && pType->GetValue() == "StructElem") { auto pS = dynamic_cast(pObject->Lookup("S")); if (pS && pS->GetValue() == "Figure") { switch (nFigure) { case 0: CPPUNIT_ASSERT_EQUAL(OUString(u"QR Code - Tells how to get to Mosegaard"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pObject->Lookup("Alt")))); break; case 1: CPPUNIT_ASSERT_EQUAL(OUString(u"Title: Arrows - Description: Explains the " u"different arrow appearances"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pObject->Lookup("Alt")))); break; case 2: CPPUNIT_ASSERT_EQUAL( OUString(u"My blue triangle - Does not need further description"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pObject->Lookup("Alt")))); break; } ++nFigure; } if (pS && pS->GetValue() == "Formula") { CPPUNIT_ASSERT_EQUAL( OUString(u"Equation 1 - Now we give the full description of eq 1 here"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast(pObject->Lookup("Alt")))); ++nFormula; } if (pS && pS->GetValue() == "Div") { switch (nDiv) { case 0: CPPUNIT_ASSERT_EQUAL(OUString(u"This frame has a description"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pObject->Lookup("Alt")))); break; case 1: // no properties set on this CPPUNIT_ASSERT(!pObject->Lookup("Alt")); break; case 2: CPPUNIT_ASSERT_EQUAL(OUString(u"My textbox - Has a light background"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pObject->Lookup("Alt")))); break; case 3: CPPUNIT_ASSERT_EQUAL(OUString(u"Hey! There is no alternate text for Frame " u"// but maybe not needed?"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pObject->Lookup("Alt")))); break; } ++nDiv; } } } CPPUNIT_ASSERT_EQUAL(int(3), nFigure); CPPUNIT_ASSERT_EQUAL(int(1), nFormula); CPPUNIT_ASSERT_EQUAL(int(4), nDiv); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf135192) { aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable PDF/UA uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"tdf135192-1.fodp"); vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); int nTable(0); for (const auto& rDocElement : aDocument.GetElements()) { auto pObject1 = dynamic_cast(rDocElement.get()); if (!pObject1) continue; auto pType1 = dynamic_cast(pObject1->Lookup("Type")); if (pType1 && pType1->GetValue() == "StructElem") { auto pS1 = dynamic_cast(pObject1->Lookup("S")); if (pS1 && pS1->GetValue() == "Table") { int nTR(0); auto pKids1 = dynamic_cast(pObject1->Lookup("K")); CPPUNIT_ASSERT(pKids1); // there can be additional children, such as MCID ref for (auto pKid1 : pKids1->GetElements()) { auto pRefKid1 = dynamic_cast(pKid1); if (pRefKid1) { auto pObject2 = pRefKid1->LookupObject(); if (pObject2) { auto pType2 = dynamic_cast( pObject2->Lookup("Type")); if (pType2 && pType2->GetValue() == "StructElem") { auto pS2 = dynamic_cast( pObject2->Lookup("S")); if (pS2 && pS2->GetValue() == "TR") { int nTD(0); auto pKids2 = dynamic_cast( pObject2->Lookup("K")); CPPUNIT_ASSERT(pKids2); for (auto pKid2 : pKids2->GetElements()) { auto pRefKid2 = dynamic_cast( pKid2); if (pRefKid2) { auto pObject3 = pRefKid2->LookupObject(); if (pObject3) { auto pType3 = dynamic_cast( pObject3->Lookup("Type")); if (pType3 && pType3->GetValue() == "StructElem") { auto pS3 = dynamic_cast< vcl::filter::PDFNameElement*>( pObject3->Lookup("S")); if (nTR == 0 && pS3 && pS3->GetValue() == "TH") { int nOTable(0); auto pAttrs = dynamic_cast< vcl::filter::PDFArrayElement*>( pObject3->Lookup("A")); CPPUNIT_ASSERT(pAttrs != nullptr); for (const auto& rAttrRef : pAttrs->GetElements()) { auto pARef = dynamic_cast< vcl::filter::PDFReferenceElement*>( rAttrRef); CPPUNIT_ASSERT(pARef != nullptr); auto pAttr = pARef->LookupObject(); CPPUNIT_ASSERT(pAttr != nullptr); auto pAttrDict = pAttr->GetDictionary(); CPPUNIT_ASSERT(pAttrDict != nullptr); auto pOwner = dynamic_cast< vcl::filter::PDFNameElement*>( pAttrDict->LookupElement("O")); CPPUNIT_ASSERT(pOwner != nullptr); if (pOwner->GetValue() == "Table") { auto pScope = dynamic_cast< vcl::filter::PDFNameElement*>( pAttrDict->LookupElement( "Scope")); CPPUNIT_ASSERT(pScope != nullptr); CPPUNIT_ASSERT_EQUAL( OString("Column"), pScope->GetValue()); ++nOTable; } } CPPUNIT_ASSERT_EQUAL(int(1), nOTable); ++nTD; } else if (nTR != 0 && pS3 && pS3->GetValue() == "TD") { ++nTD; } } } } } CPPUNIT_ASSERT_EQUAL(int(3), nTD); ++nTR; } } } } } CPPUNIT_ASSERT_EQUAL(int(2), nTR); ++nTable; } } } CPPUNIT_ASSERT_EQUAL(int(1), nTable); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142129) { OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "master.odm"; mxComponent = loadFromDesktop(aURL); // update linked section dispatchCommand(mxComponent, ".uno:UpdateAllLinks", {}); uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // Enable Outlines export uno::Sequence aFilterData( comphelper::InitPropertySequence({ { "ExportBookmarks", uno::Any(true) } })); aMediaDescriptor["FilterData"] <<= aFilterData; xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Parse the export result. vcl::filter::PDFDocument aDocument; SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); auto* pCatalog = aDocument.GetCatalog(); CPPUNIT_ASSERT(pCatalog); auto* pCatalogDictionary = pCatalog->GetDictionary(); CPPUNIT_ASSERT(pCatalogDictionary); auto* pOutlinesObject = pCatalogDictionary->LookupObject("Outlines"); CPPUNIT_ASSERT(pOutlinesObject); auto* pOutlinesDictionary = pOutlinesObject->GetDictionary(); #if 0 // Type isn't actually written currently auto* pType = dynamic_cast(pOutlinesDictionary->LookupElement("Type")); CPPUNIT_ASSERT(pType); CPPUNIT_ASSERT_EQUAL(OString("Outlines"), pType->GetValue()); #endif auto* pFirst = dynamic_cast( pOutlinesDictionary->LookupElement("First")); CPPUNIT_ASSERT(pFirst); auto* pFirstD = pFirst->LookupObject()->GetDictionary(); CPPUNIT_ASSERT(pFirstD); //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast(pFirstD->LookupElement("Type"))->GetValue()); CPPUNIT_ASSERT_EQUAL(OUString(u"Preface"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast( pFirstD->LookupElement("Title")))); auto* pFirst1 = dynamic_cast(pFirstD->LookupElement("First")); CPPUNIT_ASSERT(pFirst1); auto* pFirst1D = pFirst1->LookupObject()->GetDictionary(); CPPUNIT_ASSERT(pFirst1D); // here is a hidden section with headings "Copyright" etc.; check that // there are no outline entries for it //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast(pFirst1D->LookupElement("Type"))->GetValue()); CPPUNIT_ASSERT_EQUAL( OUString(u"Who is this book for?"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast(pFirst1D->LookupElement("Title")))); auto* pFirst2 = dynamic_cast(pFirst1D->LookupElement("Next")); auto* pFirst2D = pFirst2->LookupObject()->GetDictionary(); CPPUNIT_ASSERT(pFirst2D); //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast(pFirst2D->LookupElement("Type"))->GetValue()); CPPUNIT_ASSERT_EQUAL( OUString(u"What\u2019s in this book?"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast(pFirst2D->LookupElement("Title")))); auto* pFirst3 = dynamic_cast(pFirst2D->LookupElement("Next")); auto* pFirst3D = pFirst3->LookupObject()->GetDictionary(); CPPUNIT_ASSERT(pFirst3D); //CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast(pFirst3D->LookupElement("Type"))->GetValue()); CPPUNIT_ASSERT_EQUAL( OUString(u"Minimum requirements for using LibreOffice"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast(pFirst3D->LookupElement("Title")))); CPPUNIT_ASSERT_EQUAL(static_cast(nullptr), pFirst3D->LookupElement("Next")); CPPUNIT_ASSERT_EQUAL(static_cast(nullptr), pFirstD->LookupElement("Next")); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageRotate180) { // Create an empty document. mxComponent = loadFromDesktop("private:factory/swriter"); uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); uno::Reference xText = xTextDocument->getText(); uno::Reference xCursor = xText->createTextCursor(); // Insert the PDF image. uno::Reference xFactory(mxComponent, uno::UNO_QUERY); uno::Reference xGraphicObject( xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY); OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-rotate-180.pdf"; xGraphicObject->setPropertyValue("GraphicURL", uno::Any(aURL)); uno::Reference xShape(xGraphicObject, uno::UNO_QUERY); xShape->setSize(awt::Size(1000, 1000)); uno::Reference xTextContent(xGraphicObject, uno::UNO_QUERY); xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false); // Save as PDF. uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Parse the export result. std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); // Make sure that the page -> form -> form has a child image. std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount()); std::unique_ptr pPageObject = pPdfPage->getObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType()); // 2: white background and the actual object. CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount()); std::unique_ptr pFormObject = pPageObject->getFormObject(1); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType()); CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount()); // Check if the inner form object (original page object in the pdf image) has the correct // rotation. std::unique_ptr pInnerFormObject = pFormObject->getFormObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType()); CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount()); std::unique_ptr pImage = pInnerFormObject->getFormObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType()); basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix(); basegfx::B2DTuple aScale; basegfx::B2DTuple aTranslate; double fRotate = 0; double fShearX = 0; aMat.decompose(aScale, aTranslate, fRotate, fShearX); // Without the accompanying fix in place, this test would have failed with: // - Expected: -1 // - Actual : 1 // i.e. the 180 degrees rotation didn't happen (via a combination of horizontal + vertical // flip). CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, aScale.getX(), 0.01); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf144222) { // Assume Windows has the font for U+4E2D #ifdef _WIN32 aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export"); saveAsPDF(u"tdf144222.ods"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); std::unique_ptr pTextPage = pPdfPage->getTextPage(); CPPUNIT_ASSERT(pTextPage); int nPageObjectCount = pPdfPage->getObjectCount(); const OUString sChar = u"\u4E2D"; basegfx::B2DRectangle aRect1, aRect2; int nCount = 0; for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPdfPageObject = pPdfPage->getObject(i); if (pPdfPageObject->getType() == vcl::pdf::PDFPageObjectType::Text) { ++nCount; OUString sText = pPdfPageObject->getText(pTextPage); if (sText == sChar) aRect1 = pPdfPageObject->getBounds(); else aRect2 = pPdfPageObject->getBounds(); } } CPPUNIT_ASSERT_EQUAL(2, nCount); CPPUNIT_ASSERT(!aRect1.isEmpty()); CPPUNIT_ASSERT(!aRect2.isEmpty()); CPPUNIT_ASSERT(!aRect1.overlaps(aRect2)); #endif } CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf145873) { // Import the bugdoc and export as PDF. aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); saveAsPDF(u"tdf145873.pptx"); std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); // The document has one page. CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); int nPageObjectCount = pPdfPage->getObjectCount(); // tdf#145873: Without the fix #1 in place, this test would have failed with // - Expected: 66 // - Actual : 3 CPPUNIT_ASSERT_EQUAL(66, nPageObjectCount); auto pObject = pPdfPage->getObject(4); CPPUNIT_ASSERT_MESSAGE("no object", pObject != nullptr); // tdf#145873: Without the fix #2 in place, this test would have failed with // - Expected: 13.23 // - Actual : 3.57... // - Delta : 0.1 CPPUNIT_ASSERT_DOUBLES_EQUAL(13.23, pObject->getBounds().getWidth(), 0.1); // - Expected: 13.49 // - Actual : 3.74... // - Delta : 0.1 CPPUNIT_ASSERT_DOUBLES_EQUAL(13.49, pObject->getBounds().getHeight(), 0.1); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageHyperlink) { // Given a Draw file, containing a PDF image, which has a hyperlink in it: aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export"); // When saving to PDF: saveAsPDF(u"pdf-image-hyperlink.odg"); // Then make sure that link is preserved: std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the accompanying fix in place, this test would have failed, the hyperlink of the PDF // image was lost. CPPUNIT_ASSERT(pPdfPage->hasLinks()); // Also test the precision of the form XObject. // Given a full-page form XObject, page height is 27.94 cm (792 points): // When writing the reciprocal of the object height to PDF: std::unique_ptr pFormObject; for (int i = 0; i < pPdfPage->getObjectCount(); ++i) { std::unique_ptr pObject = pPdfPage->getObject(i); if (pObject->getType() == vcl::pdf::PDFPageObjectType::Form) { pFormObject = std::move(pObject); break; } } CPPUNIT_ASSERT(pFormObject); std::unique_ptr pInnerFormObject; for (int i = 0; i < pFormObject->getFormObjectCount(); ++i) { std::unique_ptr pObject = pFormObject->getFormObject(i); if (pObject->getType() == vcl::pdf::PDFPageObjectType::Form) { pInnerFormObject = std::move(pObject); break; } } CPPUNIT_ASSERT(pInnerFormObject); // Then make sure that enough digits are used, so the point size is unchanged: basegfx::B2DHomMatrix aMatrix = pInnerFormObject->getMatrix(); basegfx::B2DTuple aScale; basegfx::B2DTuple aTranslate; double fRotate{}; double fShearX{}; aMatrix.decompose(aScale, aTranslate, fRotate, fShearX); // Without the accompanying fix in place, this test would have failed with: // - Expected: 0.0012626264 // - Actual : 0.00126 // i.e. the rounded reciprocal was 794 points, not the original 792. // FIXME macOS actual value is 0.0001578282, for unknown reasons. #if !defined MACOSX CPPUNIT_ASSERT_EQUAL(0.0012626264, rtl::math::round(aScale.getY(), 10)); #endif } CPPUNIT_TEST_FIXTURE(PdfExportTest, testURIs) { struct { OUString in; OString out; bool relativeFsys; } URIs[] = { { "http://example.com/", "http://example.com/", true, }, { "file://localfile.odt/", "file://localfile.odt/", true, }, { // tdf 143216 "http://username:password@example.com", "http://username:password@example.com", true, }, { "git://git.example.org/project/example", "git://git.example.org/project/example", true, }, { // The odt/pdf gets substituted due to 'ConvertOOoTargetToPDFTarget' "filebypath.odt", "filebypath.pdf", true, }, { // The odt/pdf gets substituted due to 'ConvertOOoTargetToPDFTarget' // but this time with ExportLinksRelativeFsys off the path is added "filebypath.odt", OUStringToOString(utl::TempFile::GetTempNameBaseDirectory(), RTL_TEXTENCODING_UTF8) + "filebypath.pdf", false, }, { // This also gets made relative due to 'ExportLinksRelativeFsys' utl::TempFile::GetTempNameBaseDirectory() + "fileintempdir.odt", "fileintempdir.pdf", true, } }; // Create an empty document. // Note: The test harness gets very upset if we try and create multiple // documents, or recreate it; so reuse one instance for all the links mxComponent = loadFromDesktop("private:factory/swriter"); uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); uno::Reference xText = xTextDocument->getText(); uno::Reference xCursor = xText->createTextCursor(); xText->insertString(xCursor, "Test pdf", /*bAbsorb=*/false); // Set the name so it can do relative name replacement uno::Reference xModel(mxComponent, uno::UNO_QUERY); xModel->attachResource(maTempFile.GetURL(), xModel->getArgs()); for (unsigned int i = 0; i < (sizeof(URIs) / sizeof(URIs[0])); i++) { // Test the filename rewriting uno::Sequence aFilterData(comphelper::InitPropertySequence({ { "ExportLinksRelativeFsys", uno::Any(URIs[i].relativeFsys) }, { "ConvertOOoTargetToPDFTarget", uno::Any(true) }, })); aMediaDescriptor["FilterData"] <<= aFilterData; // Add a link (based on testNestedHyperlink in rtfexport3) xCursor->gotoStart(/*bExpand=*/false); xCursor->gotoEnd(/*bExpand=*/true); uno::Reference xCursorProps(xCursor, uno::UNO_QUERY); xCursorProps->setPropertyValue("HyperLinkURL", uno::Any(URIs[i].in)); // Save as PDF. uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Use the filter rather than the pdfium route, as per the tdf105093 test, it's // easier to parse the annotations vcl::filter::PDFDocument aDocument; // Parse the export result. SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); // The document has one page. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(static_cast(1), aPages.size()); auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots")); CPPUNIT_ASSERT(pAnnots); // There should be one annotation CPPUNIT_ASSERT_EQUAL(static_cast(1), pAnnots->GetElements().size()); auto pAnnotReference = dynamic_cast(pAnnots->GetElements()[0]); CPPUNIT_ASSERT(pAnnotReference); vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject(); CPPUNIT_ASSERT(pAnnot); // We're expecting something like /Type /Annot /A << /Type /Action /S /URI /URI (path) CPPUNIT_ASSERT_EQUAL( OString("Annot"), static_cast(pAnnot->Lookup("Type"))->GetValue()); CPPUNIT_ASSERT_EQUAL( OString("Link"), static_cast(pAnnot->Lookup("Subtype"))->GetValue()); auto pAction = dynamic_cast(pAnnot->Lookup("A")); CPPUNIT_ASSERT(pAction); auto pURIElem = dynamic_cast(pAction->LookupElement("URI")); CPPUNIT_ASSERT(pURIElem); // Check it matches CPPUNIT_ASSERT_EQUAL(URIs[i].out, pURIElem->GetValue()); // tdf#148934 check a11y CPPUNIT_ASSERT_EQUAL( OUString("Test pdf"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE( *dynamic_cast(pAnnot->Lookup("Contents")))); } } CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageAnnots) { // Given a document with a PDF image that has 2 comments (popup, text) and a hyperlink: aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export"); // When saving to PDF: saveAsPDF(u"pdf-image-annots.odg"); // Then make sure only the hyperlink is kept, since Draw itself has its own comments: std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1 // - Actual : 3 // i.e. not only the hyperlink but also the 2 comments were exported, leading to duplication. CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageEncryption) { // Given an empty document, with an inserted PDF image: mxComponent = loadFromDesktop("private:factory/swriter"); uno::Reference xTextDocument(mxComponent, uno::UNO_QUERY); uno::Reference xText = xTextDocument->getText(); uno::Reference xCursor = xText->createTextCursor(); uno::Reference xFactory(mxComponent, uno::UNO_QUERY); uno::Reference xGraphicObject( xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY); OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "rectangles.pdf"; xGraphicObject->setPropertyValue("GraphicURL", uno::Any(aURL)); uno::Reference xShape(xGraphicObject, uno::UNO_QUERY); xShape->setSize(awt::Size(1000, 1000)); uno::Reference xTextContent(xGraphicObject, uno::UNO_QUERY); xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false); // When saving as encrypted PDF: uno::Reference xStorable(mxComponent, uno::UNO_QUERY); aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); uno::Sequence aFilterData = { comphelper::makePropertyValue("EncryptFile", true), comphelper::makePropertyValue("DocumentOpenPassword", OUString("secret")), }; aMediaDescriptor["FilterData"] <<= aFilterData; xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Then make sure that the image is not lost: std::unique_ptr pPdfDocument = parseExport("secret"); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount()); std::unique_ptr pPageObject = pPdfPage->getObject(0); CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType()); // Without the accompanying fix in place, this test would have failed with: // - Expected: 2 // - Actual : 0 // i.e. instead of the white background and the actual form child, the image was lost due to // missing encryption. CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount()); } CPPUNIT_TEST_FIXTURE(PdfExportTest, testBitmapScaledown) { // Given a document with an upscaled and rotated barcode bitmap in it: aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); // When saving as PDF: saveAsPDF(u"bitmap-scaledown.odt"); // Then verify that the bitmap is not downscaled: std::unique_ptr pPdfDocument = parseExport(); CPPUNIT_ASSERT(pPdfDocument); CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); std::unique_ptr pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); CPPUNIT_ASSERT(pPdfPage); int nPageObjectCount = pPdfPage->getObjectCount(); for (int i = 0; i < nPageObjectCount; ++i) { std::unique_ptr pPageObject = pPdfPage->getObject(i); if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image) continue; std::unique_ptr pBitmap = pPageObject->getImageBitmap(); CPPUNIT_ASSERT(pBitmap); // In-file sizes: good is 2631x380, bad is 1565x14. int nWidth = pBitmap->getWidth(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 2616 // - Actual : 1565 // i.e. the bitmap in the pdf result was small enough to be blurry. CPPUNIT_ASSERT_EQUAL(2616, nWidth); } } } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */