tdf#107089 PDF export of PDF images: handle mixed filters of page streams

It's allowed to compress different page streams differently, and we must
have a single object stream for our form XObject, so just incompress all
of them to be consistent.

Change-Id: I7a20dc2084a902a37dcefa3420d59a576f120bcd
Reviewed-on: https://gerrit.libreoffice.org/36409
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
Tested-by: Jenkins <ci@libreoffice.org>
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107089.odt b/vcl/qa/cppunit/pdfexport/data/tdf107089.odt
new file mode 100644
index 0000000..5aaaab9
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf107089.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index aacf36b..1b9eaf1 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -54,6 +54,7 @@ public:
    void testTdf106972Pdf17();
    void testTdf107013();
    void testTdf107018();
    void testTdf107089();
#endif

    CPPUNIT_TEST_SUITE(PdfExportTest);
@@ -67,6 +68,7 @@ public:
    CPPUNIT_TEST(testTdf106972Pdf17);
    CPPUNIT_TEST(testTdf107013);
    CPPUNIT_TEST(testTdf107018);
    CPPUNIT_TEST(testTdf107089);
#endif
    CPPUNIT_TEST_SUITE_END();
};
@@ -452,6 +454,42 @@ void PdfExportTest::testTdf107018()
    // copying the page stream of a PDF image.
    CPPUNIT_ASSERT_EQUAL(OString("Pages"), pName->GetValue());
}

void PdfExportTest::testTdf107089()
{
    vcl::filter::PDFDocument aDocument;
    load("tdf107089.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);
    CPPUNIT_ASSERT(pXObject);

    // Get access to the form object inside the image.
    auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
    CPPUNIT_ASSERT(pXObjectResources);
    auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(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& rObjectStream = pStream->GetMemory();
    OString aHello("Hello");
    auto pStart = static_cast<const char*>(rObjectStream.GetData());
    const char* pEnd = pStart + rObjectStream.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);
}
#endif

CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 218ee88..f05c94e 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -11178,7 +11178,8 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
            "ColorSpace",
            "ExtGState",
            "Font",
            "XObject"
            "XObject",
            "Shading"
        };
        for (const auto& rKey : aKeys)
            aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
@@ -11189,14 +11190,6 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
        aLine.append(aSize.Height());
        aLine.append(" ]");

        // For now assume that all the content streams have the same filter.
        auto pFilter = dynamic_cast<filter::PDFNameElement*>(aContentStreams[0]->Lookup("Filter"));
        if (pFilter)
        {
            aLine.append(" /Filter /");
            aLine.append(pFilter->GetValue());
        }

        aLine.append(" /Length ");

        sal_Int32 nLength = 0;
@@ -11212,8 +11205,31 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)

            SvMemoryStream& rPageStream = pPageStream->GetMemory();

            nLength += rPageStream.GetSize();
            aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
            auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
            if (pFilter)
            {
                if (pFilter->GetValue() != "FlateDecode")
                    continue;

                SvMemoryStream aMemoryStream;
                ZCodec aZCodec;
                rPageStream.Seek(0);
                aZCodec.BeginCompression();
                aZCodec.Decompress(rPageStream, aMemoryStream);
                if (!aZCodec.EndCompression())
                {
                    SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
                    continue;
                }

                nLength += aMemoryStream.GetSize();
                aStream.append(static_cast<const sal_Char*>(aMemoryStream.GetData()), aMemoryStream.GetSize());
            }
            else
            {
                nLength += rPageStream.GetSize();
                aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
            }
        }

        aLine.append(nLength);