include/vcl/filter/pdfdocument.hxx | 5 + vcl/qa/cppunit/pdfexport/data/xnview-colorspace.pdf |binary vcl/qa/cppunit/pdfexport/pdfexport.cxx | 65 ++++++++++++++++++++ vcl/source/filter/ipdf/pdfdocument.cxx | 16 ++++ vcl/source/gdi/pdfobjectcopier.cxx | 15 ++++ 5 files changed, 101 insertions(+)
New commits: commit 4d16d3e2040ee59e78949b145ce9fcfc822dc91b Author: Caolán McNamara <caolan.mcnam...@collabora.com> AuthorDate: Tue Jul 23 15:39:24 2024 +0100 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Wed Jul 24 22:35:42 2024 +0200 Resolves: tdf#162161 reexport of specific pdf appears blank the contents of the referenced colorspace obj disappears so it gets rendered blank Change-Id: Iaa76d355b5903c695e74edadc329043156bad3b6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/170907 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index fbe0be89cdc6..b808ef555ee6 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -44,6 +44,7 @@ class PDFDocument; class PDFDictionaryElement; class PDFArrayElement; class PDFStreamElement; +class PDFNameElement; class PDFNumberElement; /// A byte range in a PDF file. @@ -73,6 +74,8 @@ class VCL_DLLPUBLIC PDFObjectElement final : public PDFElement double m_fGenerationValue; /// If set, the object contains this number element (outside any dictionary/array). PDFNumberElement* m_pNumberElement; + /// If set, the object contains this name element (outside any dictionary/array). + PDFNameElement* m_pNameElement; /// Position after the '<<' token. sal_uInt64 m_nDictionaryOffset; /// Length of the dictionary buffer till (before) the '>>' token. @@ -114,6 +117,8 @@ public: void SetDictionary(PDFDictionaryElement* pDictionaryElement); void SetNumberElement(PDFNumberElement* pNumberElement); PDFNumberElement* GetNumberElement() const; + void SetNameElement(PDFNameElement* pNameElement); + PDFNameElement* GetNameElement() const; /// Get access to the parsed key-value items from the object dictionary. const std::map<OString, PDFElement*>& GetDictionaryItems(); const std::vector<PDFReferenceElement*>& GetDictionaryReferences() const; diff --git a/vcl/qa/cppunit/pdfexport/data/xnview-colorspace.pdf b/vcl/qa/cppunit/pdfexport/data/xnview-colorspace.pdf new file mode 100644 index 000000000000..0370c2bbd2e6 Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/xnview-colorspace.pdf differ diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 42c0638f36ca..8e8d720694ef 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -5046,6 +5046,71 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf156528) bounds.getHeight(), 1); } +// tdf#162161 reexport appears to have blank image +CPPUNIT_TEST_FIXTURE(PdfExportTest, testRexportXnViewColorspace) +{ + // 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; + osl_setEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData, OUString("1").pData); + } + comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() { + if (bResetEnvVar) + osl_clearEnvironment(OUString("LO_IMPORT_USE_PDFIUM").pData); + }); + + // Load the PDF and save as PDF + vcl::filter::PDFDocument aDocument; + load(u"xnview-colorspace.pdf", aDocument); + + std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(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<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject")); + CPPUNIT_ASSERT(pXObjects); + CPPUNIT_ASSERT_EQUAL(size_t(1), pXObjects->GetItems().size()); + vcl::filter::PDFObjectElement* pXObject + = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); + CPPUNIT_ASSERT(pXObject); + + auto pSubResources + = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources")); + CPPUNIT_ASSERT(pSubResources); + pXObjects + = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pSubResources->LookupElement("XObject")); + CPPUNIT_ASSERT(pXObjects); + CPPUNIT_ASSERT_EQUAL(size_t(1), pXObjects->GetItems().size()); + pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); + CPPUNIT_ASSERT(pXObject); + + pSubResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources")); + CPPUNIT_ASSERT(pSubResources); + pXObjects + = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pSubResources->LookupElement("XObject")); + CPPUNIT_ASSERT(pXObjects); + CPPUNIT_ASSERT_EQUAL(size_t(1), pXObjects->GetItems().size()); + pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first); + CPPUNIT_ASSERT(pXObject); + + // Dig all the way down to this element which is originally + // 8 0 obj + // /DeviceRGB + // endobj + // and appeared blank when we lost the /DeviceRGB line + auto pColorspace = pXObject->LookupObject("ColorSpace"); + CPPUNIT_ASSERT(pColorspace); + auto pColorSpaceElement = pColorspace->GetNameElement(); + CPPUNIT_ASSERT(pColorSpaceElement); + CPPUNIT_ASSERT_EQUAL(OString("DeviceRGB"), pColorSpaceElement->GetValue()); +} + } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 5ff50d3b55ac..315c9f25d3a5 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -1107,6 +1107,14 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, pObjectStream = pObject; else pObjectKey = pNameElement; + + if (bInObject && !nDepth && pObject) + { + // Name element inside an object, but outside a + // dictionary / array: remember it. + pObject->SetNameElement(pNameElement); + } + break; } case '(': @@ -2295,6 +2303,7 @@ PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, doubl , m_fObjectValue(fObjectValue) , m_fGenerationValue(fGenerationValue) , m_pNumberElement(nullptr) + , m_pNameElement(nullptr) , m_nDictionaryOffset(0) , m_nDictionaryLength(0) , m_pDictionaryElement(nullptr) @@ -2466,6 +2475,13 @@ void PDFObjectElement::SetNumberElement(PDFNumberElement* pNumberElement) PDFNumberElement* PDFObjectElement::GetNumberElement() const { return m_pNumberElement; } +void PDFObjectElement::SetNameElement(PDFNameElement* pNameElement) +{ + m_pNameElement = pNameElement; +} + +PDFNameElement* PDFObjectElement::GetNameElement() const { return m_pNameElement; } + const std::vector<PDFReferenceElement*>& PDFObjectElement::GetDictionaryReferences() const { return m_aDictionaryReferences; diff --git a/vcl/source/gdi/pdfobjectcopier.cxx b/vcl/source/gdi/pdfobjectcopier.cxx index 2f32cdc27ce1..3bace2246cc7 100644 --- a/vcl/source/gdi/pdfobjectcopier.cxx +++ b/vcl/source/gdi/pdfobjectcopier.cxx @@ -143,6 +143,21 @@ sal_Int32 PDFObjectCopier::copyExternalResource(SvMemoryStream& rDocBuffer, pNumber->writeString(aLine); aLine.append(" "); } + // If the object has a name element outside a dictionary or array, copy that. + else if (filter::PDFNameElement* pName = rObject.GetNameElement()) + { + // currently just handle the exact case seen in the real world + if (pName->GetValue() == "DeviceRGB") + { + pName->writeString(aLine); + aLine.append(" "); + } + else + { + SAL_INFO("vcl.pdfwriter", + "PDFObjectCopier::copyExternalResource: skipping: " << pName->GetValue()); + } + } // We have the whole object, now write it to the output. if (!m_rContainer.updateObject(nObject))