[Libreoffice-commits] core.git: vcl/qa vcl/source

Miklos Vajna vmiklos at collabora.co.uk
Fri Apr 7 13:00:43 UTC 2017


 vcl/qa/cppunit/pdfexport/data/tdf107013.odt |binary
 vcl/qa/cppunit/pdfexport/pdfexport.cxx      |   89 ++++++++--------
 vcl/source/filter/ipdf/pdfdocument.cxx      |   46 +++++---
 vcl/source/gdi/pdfwriter_impl.cxx           |  153 ++++++++++++++++------------
 4 files changed, 165 insertions(+), 123 deletions(-)

New commits:
commit 4db4b9f016256fc8d2b637ed7a8f2b097aaa864b
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date:   Fri Apr 7 12:27:40 2017 +0200

    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 at collabora.co.uk>
    Tested-by: Jenkins <ci at libreoffice.org>

diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107013.odt b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt
new file mode 100644
index 000000000000..644e65c6ded8
Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt differ
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());


More information about the Libreoffice-commits mailing list