diff options
author | Miklos Vajna <vmiklos@collabora.co.uk> | 2017-04-07 12:27:40 +0200 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.co.uk> | 2017-04-07 13:00:15 +0000 |
commit | 4db4b9f016256fc8d2b637ed7a8f2b097aaa864b (patch) | |
tree | e59cca792bb0f896366cb24f3f3f66f054264ca2 /vcl | |
parent | 209dc36408dd5e1775db2c54b08c3e674158fd2f (diff) |
tdf#107013 PDF export of PDF images: handle page tree and content streams
Handle when the page objects are not contained in a single list, but a
tree of "pages" objects.
Also handle when the page object has multiple content streams.
Change-Id: I7c5b0949314768af5915d37830a45e843e629446
Reviewed-on: https://gerrit.libreoffice.org/36256
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
Tested-by: Jenkins <ci@libreoffice.org>
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/qa/cppunit/pdfexport/data/tdf107013.odt | bin | 0 -> 19823 bytes | |||
-rw-r--r-- | vcl/qa/cppunit/pdfexport/pdfexport.cxx | 89 | ||||
-rw-r--r-- | vcl/source/filter/ipdf/pdfdocument.cxx | 46 | ||||
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 153 |
4 files changed, 165 insertions, 123 deletions
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107013.odt b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt Binary files differnew file mode 100644 index 000000000000..644e65c6ded8 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 566495f38edf..31d0dfb384f2 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -39,6 +39,7 @@ public: virtual void setUp() override; virtual void tearDown() override; #if HAVE_FEATURE_PDFIUM + void load(const OUString& rFile, vcl::filter::PDFDocument& rDocument); /// Tests that a pdf image is roundtripped back to PDF as a vector format. void testTdf106059(); /// Tests that text highlight from Impress is not lost. @@ -51,6 +52,7 @@ public: void testTdf106693(); void testTdf106972(); void testTdf106972Pdf17(); + void testTdf107013(); #endif CPPUNIT_TEST_SUITE(PdfExportTest); @@ -62,6 +64,7 @@ public: CPPUNIT_TEST(testTdf106693); CPPUNIT_TEST(testTdf106972); CPPUNIT_TEST(testTdf106972Pdf17); + CPPUNIT_TEST(testTdf107013); #endif CPPUNIT_TEST_SUITE_END(); }; @@ -83,6 +86,26 @@ void PdfExportTest::tearDown() } #if HAVE_FEATURE_PDFIUM + +void PdfExportTest::load(const OUString& rFile, vcl::filter::PDFDocument& rDocument) +{ + // Import the bugdoc and export as PDF. + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile; + mxComponent = loadFromDesktop(aURL); + CPPUNIT_ASSERT(mxComponent.is()); + + uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); + xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); + + // Parse the export result. + SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(rDocument.Read(aStream)); +} + void PdfExportTest::testTdf106059() { // Import the bugdoc and export as PDF. @@ -127,22 +150,8 @@ void PdfExportTest::testTdf106059() void PdfExportTest::testTdf106693() { - // Import the bugdoc and export as PDF. - OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106693.odt"; - mxComponent = loadFromDesktop(aURL); - CPPUNIT_ASSERT(mxComponent.is()); - - uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); - utl::TempFile aTempFile; - aTempFile.EnableKillingFile(); - utl::MediaDescriptor aMediaDescriptor; - aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); - xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); - - // Parse the export result. vcl::filter::PDFDocument aDocument; - SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ); - CPPUNIT_ASSERT(aDocument.Read(aStream)); + load("tdf106693.odt", aDocument); // Assert that the XObject in the page resources dictionary is a form XObject. std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); @@ -180,22 +189,8 @@ void PdfExportTest::testTdf106693() void PdfExportTest::testTdf105461() { - // Import the bugdoc and export as PDF. - OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105461.odp"; - mxComponent = loadFromDesktop(aURL); - CPPUNIT_ASSERT(mxComponent.is()); - - uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); - utl::TempFile aTempFile; - aTempFile.EnableKillingFile(); - utl::MediaDescriptor aMediaDescriptor; - aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); - xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); - - // Parse the export result. vcl::filter::PDFDocument aDocument; - SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ); - CPPUNIT_ASSERT(aDocument.Read(aStream)); + load("tdf105461.odp", aDocument); // The document has one page. std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); @@ -226,22 +221,8 @@ void PdfExportTest::testTdf105461() void PdfExportTest::testTdf105093() { - // Import the bugdoc and export as PDF. - OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105093.odp"; - mxComponent = loadFromDesktop(aURL); - CPPUNIT_ASSERT(mxComponent.is()); - - uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); - utl::TempFile aTempFile; - aTempFile.EnableKillingFile(); - utl::MediaDescriptor aMediaDescriptor; - aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export"); - xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); - - // Parse the export result. vcl::filter::PDFDocument aDocument; - SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ); - CPPUNIT_ASSERT(aDocument.Read(aStream)); + load("tdf105093.odp", aDocument); // The document has one page. std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); @@ -403,6 +384,24 @@ void PdfExportTest::testTdf106972Pdf17() // output is PDF 1.4, and this bugdoc has PDF 1.7 data. CPPUNIT_ASSERT(!pXObject->Lookup("Resources")); } + +void PdfExportTest::testTdf107013() +{ + vcl::filter::PDFDocument aDocument; + load("tdf107013.odt", aDocument); + + // Get access to the only image on the only page. + std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size()); + vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"); + CPPUNIT_ASSERT(pResources); + auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject")); + CPPUNIT_ASSERT(pXObjects); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(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); +} #endif CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest); diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 444ec9239d20..43d4248cc8ad 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -1749,6 +1749,36 @@ const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements() return m_aElements; } +/// Visits the page tree recursively, looking for page objects. +static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>& rRet) +{ + auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids")); + if (!pKids) + { + SAL_WARN("vcl.filter", "visitPages: pages has no kids"); + return; + } + + for (const auto& pKid : pKids->GetElements()) + { + auto pReference = dynamic_cast<PDFReferenceElement*>(pKid); + if (!pReference) + continue; + + PDFObjectElement* pKidObject = pReference->LookupObject(); + if (!pKidObject) + continue; + + auto pName = dynamic_cast<PDFNameElement*>(pKidObject->Lookup("Type")); + if (pName && pName->GetValue() == "Pages") + // Pages inside pages: recurse. + visitPages(pKidObject, rRet); + else + // Found an actual page. + rRet.push_back(pKidObject); + } +} + std::vector<PDFObjectElement*> PDFDocument::GetPages() { std::vector<PDFObjectElement*> aRet; @@ -1779,21 +1809,7 @@ std::vector<PDFObjectElement*> PDFDocument::GetPages() return aRet; } - auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids")); - if (!pKids) - { - SAL_WARN("vcl.filter", "PDFDocument::GetPages: pages has no kids"); - return aRet; - } - - for (const auto& pKid : pKids->GetElements()) - { - auto pReference = dynamic_cast<PDFReferenceElement*>(pKid); - if (!pReference) - continue; - - aRet.push_back(pReference->LookupObject()); - } + visitPages(pPages, aRet); return aRet; } diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 490f5d3b4768..65c62166aca4 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11109,7 +11109,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) double fScaleX = 1.0 / aSize.Width(); double fScaleY = 1.0 / aSize.Height(); - sal_Int32 nWrappedFormObject = 0; + std::vector<sal_Int32> aWrappedFormObjects; if (!m_aContext.UseReferenceXObject) { // Parse the PDF data, we need that to write the PDF dictionary of our @@ -11137,68 +11137,84 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) return; } - filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents"); - if (!pPageContents) + std::vector<filter::PDFObjectElement*> aContentStreams; + if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents")) + aContentStreams.push_back(pContentStream); + else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"))) { - SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents"); - return; - } + for (const auto pElement : pArray->GetElements()) + { + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); + if (!pReference) + continue; - nWrappedFormObject = createObject(); - // Write the form XObject wrapped below. This is a separate object from - // the wrapper, this way there is no need to alter the stream contents. + filter::PDFObjectElement* pObject = pReference->LookupObject(); + if (!pObject) + continue; + + aContentStreams.push_back(pObject); + } + } - OStringBuffer aLine; - aLine.append(nWrappedFormObject); - aLine.append(" 0 obj\n"); - aLine.append("<< /Type /XObject"); - aLine.append(" /Subtype /Form"); - aLine.append(" /Resources <<"); - static const std::initializer_list<OString> aKeys = - { - "ColorSpace", - "ExtGState", - "Font", - "XObject" - }; // Maps from source object id (PDF image) to target object id (export result). std::map<sal_Int32, sal_Int32> aCopiedResources; - for (const auto& rKey : aKeys) - aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources)); - aLine.append(">>"); - aLine.append(" /BBox [ 0 0 "); - aLine.append(aSize.Width()); - aLine.append(" "); - aLine.append(aSize.Height()); - aLine.append(" ]"); - - auto pFilter = dynamic_cast<filter::PDFNameElement*>(pPageContents->Lookup("Filter")); - if (pFilter) + for (auto pContent : aContentStreams) { - aLine.append(" /Filter /"); - aLine.append(pFilter->GetValue()); - } + aWrappedFormObjects.push_back(createObject()); + // Write the form XObject wrapped below. This is a separate object from + // the wrapper, this way there is no need to alter the stream contents. - aLine.append(" /Length "); + OStringBuffer aLine; + aLine.append(aWrappedFormObjects.back()); + aLine.append(" 0 obj\n"); + aLine.append("<< /Type /XObject"); + aLine.append(" /Subtype /Form"); + aLine.append(" /Resources <<"); + static const std::initializer_list<OString> aKeys = + { + "ColorSpace", + "ExtGState", + "Font", + "XObject" + }; + for (const auto& rKey : aKeys) + aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources)); + aLine.append(">>"); + aLine.append(" /BBox [ 0 0 "); + aLine.append(aSize.Width()); + aLine.append(" "); + aLine.append(aSize.Height()); + aLine.append(" ]"); + + auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter")); + if (pFilter) + { + aLine.append(" /Filter /"); + aLine.append(pFilter->GetValue()); + } - filter::PDFStreamElement* pPageStream = pPageContents->GetStream(); - if (!pPageStream) - { - SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream"); - return; - } + aLine.append(" /Length "); - SvMemoryStream& rPageStream = pPageStream->GetMemory(); + filter::PDFStreamElement* pPageStream = pContent->GetStream(); + if (!pPageStream) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream"); + continue; + } - aLine.append(static_cast<sal_Int32>(rPageStream.GetSize())); + SvMemoryStream& rPageStream = pPageStream->GetMemory(); - aLine.append(">>\nstream\n"); - // Copy the original page stream to the form XObject stream. - aLine.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize()); - aLine.append("\nendstream\nendobj\n\n"); - if (!updateObject(nWrappedFormObject)) - return; - CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength())); + aLine.append(static_cast<sal_Int32>(rPageStream.GetSize())); + + aLine.append(">>\nstream\n"); + // Copy the original page stream to the form XObject stream. + aLine.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize()); + aLine.append("\nendstream\nendobj\n\n"); + if (!updateObject(aWrappedFormObjects.back())) + continue; + if (!writeBuffer(aLine.getStr(), aLine.getLength())) + continue; + } } OStringBuffer aLine; @@ -11210,12 +11226,20 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) aLine.append(" 0 obj\n"); aLine.append("<< /Type /XObject"); aLine.append(" /Subtype /Form"); - aLine.append(" /Resources << /XObject<</Im"); - sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject; - aLine.append(nObject); - aLine.append(" "); - aLine.append(nObject); - aLine.append(" 0 R>> >>"); + aLine.append(" /Resources << /XObject<<"); + for (const auto nWrappedFormObject : aWrappedFormObjects) + { + sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject; + aLine.append(" /Im"); + aLine.append(nObject); + aLine.append(" "); + aLine.append(nObject); + aLine.append(" 0 R"); + + if (m_aContext.UseReferenceXObject) + break; + } + aLine.append(">> >>"); aLine.append(" /Matrix [ "); appendDouble(fScaleX, aLine); aLine.append(" 0 0 "); @@ -11255,11 +11279,14 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) { // Reset line width to the default. aStream.append(" 1 w\n"); - // No reference XObject, draw the form XObject containing the original - // page stream. - aStream.append("/Im"); - aStream.append(nWrappedFormObject); - aStream.append(" Do\n"); + for (const auto nWrappedFormObject : aWrappedFormObjects) + { + // No reference XObject, draw the form XObject containing the original + // page stream. + aStream.append("/Im"); + aStream.append(nWrappedFormObject); + aStream.append(" Do\n"); + } } aStream.append("Q"); aLine.append(aStream.getLength()); |