filter/source/pdf/impdialog.cxx | 5 filter/source/pdf/impdialog.hxx | 2 filter/source/pdf/pdfexport.cxx | 4 filter/source/pdf/pdfexport.hxx | 1 filter/uiconfig/ui/pdfgeneralpage.ui | 17 include/vcl/filter/pdfdocument.hxx | 378 ++ include/vcl/pdfwriter.hxx | 5 include/xmlsecurity/pdfio/pdfdocument.hxx | 299 - sw/qa/extras/rtfimport/data/tdf106694.rtf | 10 sw/qa/extras/rtfimport/rtfimport.cxx | 8 vcl/CppunitTest_vcl_pdfexport.mk | 2 vcl/Library_vcl.mk | 1 vcl/qa/cppunit/pdfexport/data/tdf106693.odt |binary vcl/qa/cppunit/pdfexport/pdfexport.cxx | 111 vcl/source/filter/ipdf/pdfdocument.cxx | 3005 ++++++++++++++++ vcl/source/gdi/pdfwriter_impl.cxx | 365 + vcl/source/gdi/pdfwriter_impl.hxx | 13 writerfilter/source/rtftok/rtfsprm.cxx | 20 xmlsecurity/CppunitTest_xmlsecurity_pdfsigning.mk | 1 xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx | 22 xmlsecurity/source/helper/pdfsignaturehelper.cxx | 10 xmlsecurity/source/pdfio/pdfdocument.cxx | 4044 +++------------------- xmlsecurity/workben/pdfverify.cxx | 8 23 files changed, 4500 insertions(+), 3831 deletions(-)
New commits: commit 3a8ba7cf23ebc89711a249a5105110ea8fdab713 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Thu Mar 30 17:04:19 2017 +0200 vcl PDF export, norefxobj: add test for this Assert two important properties: - the pdf image is described using the form xobject markup (not the reference xobject one) - the form xobject refers to a vector image, not to a bitmap one Change-Id: I94b88976c1e5392758d56254143fbeeeeba51412 Reviewed-on: https://gerrit.libreoffice.org/35901 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 932f6a8f37fbd99fc2ed16aa37966658d388c975) diff --git a/vcl/qa/cppunit/pdfexport/data/tdf106693.odt b/vcl/qa/cppunit/pdfexport/data/tdf106693.odt new file mode 100644 index 000000000000..a2c18037833e Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/tdf106693.odt differ diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index fb0bd8b3c597..ede50077b62c 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -47,6 +47,8 @@ public: void testTdf105093(); /// Tests export of non-PDF images. void testTdf106206(); + /// Tests export of PDF images without reference XObjects. + void testTdf106693(); #endif CPPUNIT_TEST_SUITE(PdfExportTest); @@ -55,6 +57,7 @@ public: CPPUNIT_TEST(testTdf105461); CPPUNIT_TEST(testTdf105093); CPPUNIT_TEST(testTdf106206); + CPPUNIT_TEST(testTdf106693); #endif CPPUNIT_TEST_SUITE_END(); }; @@ -118,6 +121,59 @@ void PdfExportTest::testTdf106059() CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref")); } +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)); + + // Assert that the XObject in the page resources dictionary is a form XObject. + std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); + // The document has one page. + 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); + // The page has one image. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(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<vcl::filter::PDFNameElement*>(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<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources")); + CPPUNIT_ASSERT(pInnerResources); + auto pInnerXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pInnerResources->LookupElement("XObject")); + CPPUNIT_ASSERT(pInnerXObjects); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pInnerXObjects->GetItems().size()); + vcl::filter::PDFObjectElement* pInnerXObject = pInnerXObjects->LookupObject(pInnerXObjects->GetItems().begin()->first); + CPPUNIT_ASSERT(pInnerXObject); + auto pInnerSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerXObject->Lookup("Subtype")); + CPPUNIT_ASSERT(pInnerSubtype); + // This failed: this was Image (bitmap), not Form (vector). + CPPUNIT_ASSERT_EQUAL(OString("Form"), pInnerSubtype->GetValue()); +} + void PdfExportTest::testTdf105461() { // Import the bugdoc and export as PDF. commit d6397d5b1bdfddf3724d0c3e2621f7960674322a Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Wed Mar 29 17:25:55 2017 +0200 vcl PDF export, norefxobj: copy each object only once Even if they are referenced multiple times. This is especially important as objects can refer to each other, creating a cyclic graph. But it also makes the output a tiny bit smaller. Change-Id: I561ac319683a19a797282fe259cc68f3a4c50c3e Reviewed-on: https://gerrit.libreoffice.org/35855 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 92ddc0409c8d3276183afdee543d28e1c307c2c7) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index d132b3ff887e..51eed7262bbe 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11697,9 +11697,16 @@ void PDFWriterImpl::writeJPG( JPGEmit& rObject ) writeReferenceXObject(rObject.m_aReferenceXObject); } -sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject) +sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources) { + auto it = rCopiedResources.find(rObject.GetObjectValue()); + if (it != rCopiedResources.end()) + // This resource was already copied once, nothing to do. + return it->second; + sal_Int32 nObject = createObject(); + // Remember what is the ID of this object in our output. + rCopiedResources[rObject.GetObjectValue()] = nObject; SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject); OStringBuffer aLine; @@ -11722,7 +11729,7 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter if (pReferenced) { // Copy the referenced object. - sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); + sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources); sal_uInt64 nReferenceStart = pDictionary->GetKeyOffset(rItem.first) + rItem.first.getLength(); sal_uInt64 nReferenceEnd = pDictionary->GetKeyOffset(rItem.first) + pDictionary->GetKeyValueLength(rItem.first); @@ -11784,7 +11791,7 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter if (pReferenced) { // Copy the referenced object. - sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); + sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources); sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation(); sal_uInt64 nReferenceEnd = pReference->GetOffset(); @@ -11833,7 +11840,7 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter return nObject; } -OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind) +OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources) { // A name - object ID map, IDs as they appear in our output, not the // original ones. @@ -11863,7 +11870,7 @@ OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, co continue; // Then copying over an object copy its dictionary and its stream. - sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue); + sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources); aRet[rItem.first] = nObject; } @@ -11947,8 +11954,10 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) "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)); + aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources)); aLine.append(">>"); aLine.append(" /BBox [ 0 0 "); aLine.append(aSize.Width()); diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx index 2329cbdec0bb..e1750ab8b73b 100644 --- a/vcl/source/gdi/pdfwriter_impl.hxx +++ b/vcl/source/gdi/pdfwriter_impl.hxx @@ -861,10 +861,10 @@ i12626 void writeReferenceXObject(ReferenceXObjectEmit& rEmit); /// Copies resources of a given kind from an external page to the output, /// returning what has to be included in the new resource dictionary. - OString copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind); + OString copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources); /// Copies a single resource from an external document, returns the new /// object ID in our document. - sal_Int32 copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject); + sal_Int32 copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources); /* tries to find the bitmap by its id and returns its emit data if exists, else creates a new emit data block */ commit 10ec3d39c381547bbdf03f7ec3eebe7005ffdfe7 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Wed Mar 29 14:24:02 2017 +0200 vcl PDF export, norefxobj: avoid replacement bitmap The whole point of "no reference XObjects" is knowing this vector markup is supported everywhere, so no need to provide a fallback bitmap. It was already unreferenced, but now it's not even written to the file, making the PDF export result smaller. Change-Id: Idf766c8eeded4235ebea49d13698a13c6b60f014 Reviewed-on: https://gerrit.libreoffice.org/35841 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 30102ded91b9ecfea172ffc6443154230ee37cbd) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 7096e197296e..d132b3ff887e 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11619,6 +11619,12 @@ bool PDFWriterImpl::writeGradientFunction( GradientEmit& rObject ) void PDFWriterImpl::writeJPG( JPGEmit& rObject ) { + if (rObject.m_aReferenceXObject.m_aPDFData.hasElements() && !m_aContext.UseReferenceXObject) + { + writeReferenceXObject(rObject.m_aReferenceXObject); + return; + } + CHECK_RETURN2( rObject.m_pStream ); CHECK_RETURN2( updateObject( rObject.m_nObject ) ); @@ -12080,6 +12086,12 @@ namespace bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask ) { + if (rObject.m_aReferenceXObject.m_aPDFData.hasElements() && !m_aContext.UseReferenceXObject) + { + writeReferenceXObject(rObject.m_aReferenceXObject); + return true; + } + CHECK_RETURN( updateObject( rObject.m_nObject ) ); Bitmap aBitmap; @@ -12552,7 +12564,8 @@ const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx m_aBitmaps.push_front( BitmapEmit() ); m_aBitmaps.front().m_aID = aID; m_aBitmaps.front().m_aBitmap = aBitmap; - m_aBitmaps.front().m_nObject = createObject(); + if (!rGraphic.getPdfData().hasElements() || m_aContext.UseReferenceXObject) + m_aBitmaps.front().m_nObject = createObject(); createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject); it = m_aBitmaps.begin(); } commit a1b536a3b2bc327e2c8e7817a34d067412713142 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Wed Mar 29 09:47:09 2017 +0200 vcl PDF export, norefxobj: add UI for this Disable the "use reference XObjects" (old behavior) by default, but keep it as an option in case someone wants it. Summary till the help is updated: the old way is simpler code, so it's always correct, but really only Acrobat supports that markup. The new way is supported by all readers, but more complex, so it's more likely it goes wrong. Change-Id: I4769474f29d98412be496a0aa4e8254ae4f0919e Reviewed-on: https://gerrit.libreoffice.org/35826 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 9c944b0d1bff9a0ab1b7e8454c9ac5e7194aa533) diff --git a/filter/source/pdf/impdialog.cxx b/filter/source/pdf/impdialog.cxx index 52bae8de8f12..5a76a2178190 100644 --- a/filter/source/pdf/impdialog.cxx +++ b/filter/source/pdf/impdialog.cxx @@ -82,6 +82,7 @@ ImpPDFTabDialog::ImpPDFTabDialog(vcl::Window* pParent, Sequence< PropertyValue > mbUseTaggedPDF( false ), mbExportNotes( true ), mbViewPDF( false ), + mbUseReferenceXObject( false ), mbExportNotesPages( false ), mbExportOnlyNotesPages( false ), mbUseTransitionEffects( false ), @@ -469,6 +470,7 @@ Sequence< PropertyValue > ImpPDFTabDialog::GetFilterData() aRet.push_back(comphelper::makePropertyValue("SignaturePassword", msSignPassword)); aRet.push_back(comphelper::makePropertyValue("SignatureCertificate", maSignCertificate)); aRet.push_back(comphelper::makePropertyValue("SignatureTSA", msSignTSA)); + aRet.push_back(comphelper::makePropertyValue("UseReferenceXObject", mbUseReferenceXObject)); return comphelper::concatSequences(maConfigItem.GetFilterData(), comphelper::containerToSequence(aRet)); } @@ -511,6 +513,7 @@ ImpPDFTabGeneralPage::ImpPDFTabGeneralPage(vcl::Window* pParent, const SfxItemSe get(mpCbExportEmptyPages, "emptypages"); get(mpCbExportPlaceholders, "exportplaceholders" ); get(mpCbViewPDF, "viewpdf"); + get(mpCbUseReferenceXObject, "usereferencexobject"); get(mpCbWatermark, "watermark"); get(mpFtWatermark, "watermarklabel"); @@ -546,6 +549,7 @@ void ImpPDFTabGeneralPage::dispose() mpCbExportHiddenSlides.clear(); mpCbExportNotes.clear(); mpCbViewPDF.clear(); + mpCbUseReferenceXObject.clear(); mpCbExportNotesPages.clear(); mpCbExportOnlyNotesPages.clear(); mpCbExportEmptyPages.clear(); @@ -676,6 +680,7 @@ void ImpPDFTabGeneralPage::GetFilterConfigItem( ImpPDFTabDialog* paParent ) paParent->mnMaxImageResolution = mpCoReduceImageResolution->GetText().toInt32(); paParent->mbExportNotes = mpCbExportNotes->IsChecked(); paParent->mbViewPDF = mpCbViewPDF->IsChecked(); + paParent->mbUseReferenceXObject = mpCbUseReferenceXObject->IsChecked(); if ( mbIsPresentation ) { paParent->mbExportNotesPages = mpCbExportNotesPages->IsChecked(); diff --git a/filter/source/pdf/impdialog.hxx b/filter/source/pdf/impdialog.hxx index c2ae742744db..29d4b43b0c85 100644 --- a/filter/source/pdf/impdialog.hxx +++ b/filter/source/pdf/impdialog.hxx @@ -104,6 +104,7 @@ protected: sal_Int32 mnPDFTypeSelection; bool mbExportNotes; bool mbViewPDF; + bool mbUseReferenceXObject; bool mbExportNotesPages; bool mbExportOnlyNotesPages; bool mbUseTransitionEffects; @@ -220,6 +221,7 @@ class ImpPDFTabGeneralPage : public SfxTabPage VclPtr<CheckBox> mpCbExportHiddenSlides; VclPtr<CheckBox> mpCbExportNotes; VclPtr<CheckBox> mpCbViewPDF; + VclPtr<CheckBox> mpCbUseReferenceXObject; VclPtr<CheckBox> mpCbExportNotesPages; VclPtr<CheckBox> mpCbExportOnlyNotesPages; diff --git a/filter/source/pdf/pdfexport.cxx b/filter/source/pdf/pdfexport.cxx index 8d4e61ccf6f4..e62fc108c86c 100644 --- a/filter/source/pdf/pdfexport.cxx +++ b/filter/source/pdf/pdfexport.cxx @@ -47,7 +47,6 @@ #include <cppuhelper/exc_hlp.hxx> #include <cppuhelper/compbase.hxx> #include <cppuhelper/basemutex.hxx> -#include <officecfg/Office/Common.hxx> #include "pdfexport.hxx" #include "impdialog.hxx" @@ -96,6 +95,7 @@ PDFExport::PDFExport( const Reference< XComponent >& rxSrcDoc, mbExportNotes ( true ), mbExportPlaceholders ( false ), mbViewPDF ( true ), + mbUseReferenceXObject ( false ), mbExportNotesPages ( false ), mbExportOnlyNotesPages ( false ), mbUseTransitionEffects ( true ), @@ -560,6 +560,8 @@ bool PDFExport::Export( const OUString& rFile, const Sequence< PropertyValue >& rFilterData[ nData ].Value >>= msSignTSA; else if ( rFilterData[ nData ].Name == "ExportPlaceholders" ) rFilterData[ nData ].Value >>= mbExportPlaceholders; + else if ( rFilterData[ nData ].Name == "UseReferenceXObject" ) + rFilterData[ nData ].Value >>= mbUseReferenceXObject; } aContext.URL = aURL.GetMainURL(INetURLObject::DECODE_TO_IURI); @@ -783,8 +785,7 @@ bool PDFExport::Export( const OUString& rFile, const Sequence< PropertyValue >& aContext.SignPassword = msSignPassword; aContext.SignCertificate = maSignCertificate; aContext.SignTSA = msSignTSA; - // Not using reference XObjects is experimental for now. - aContext.UseReferenceXObject = !officecfg::Office::Common::Misc::ExperimentalMode::get(); + aContext.UseReferenceXObject = mbUseReferenceXObject; // all context data set, time to create the printing device std::unique_ptr<vcl::PDFWriter> pPDFWriter(new vcl::PDFWriter( aContext, xEnc )); diff --git a/filter/source/pdf/pdfexport.hxx b/filter/source/pdf/pdfexport.hxx index 5b9391ad60e9..4051cd88ff21 100644 --- a/filter/source/pdf/pdfexport.hxx +++ b/filter/source/pdf/pdfexport.hxx @@ -46,6 +46,7 @@ private: bool mbExportNotes; bool mbExportPlaceholders; bool mbViewPDF; + bool mbUseReferenceXObject; bool mbExportNotesPages; bool mbExportOnlyNotesPages; bool mbUseTransitionEffects; diff --git a/filter/uiconfig/ui/pdfgeneralpage.ui b/filter/uiconfig/ui/pdfgeneralpage.ui index 836fd2504ffd..8301c7c1af81 100644 --- a/filter/uiconfig/ui/pdfgeneralpage.ui +++ b/filter/uiconfig/ui/pdfgeneralpage.ui @@ -427,7 +427,7 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="top_padding">6</property> - <property name="left_padding">12</property> + <property name="left_padding">13</property> <child> <object class="GtkGrid" id="grid10"> <property name="visible">True</property> @@ -638,6 +638,21 @@ </packing> </child> <child> + <object class="GtkCheckButton" id="usereferencexobject"> + <property name="label" translatable="yes">Use reference XObjects</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">13</property> + </packing> + </child> + <child> <object class="GtkCheckButton" id="hiddenpages"> <property name="label" translatable="yes">Export _hidden pages</property> <property name="visible">True</property> diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 4e80f08d9e45..fb0bd8b3c597 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -13,6 +13,7 @@ #include <com/sun/star/frame/XStorable.hpp> #include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> #include <cppuhelper/implbase.hxx> #include <test/bootstrapfixture.hxx> #include <unotest/macros_test.hxx> @@ -87,6 +88,12 @@ void PdfExportTest::testTdf106059() aTempFile.EnableKillingFile(); utl::MediaDescriptor aMediaDescriptor; aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); + // Explicitly enable the usage of the reference XObject markup. + uno::Sequence<beans::PropertyValue> aFilterData = + { + comphelper::makePropertyValue("UseReferenceXObject", true) + }; + aMediaDescriptor["FilterData"] <<= aFilterData; xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); // Parse the export result. commit 2d298ce44645861db5510480b45e1ef9e439fa50 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Tue Mar 28 17:55:06 2017 +0200 vcl PDF export, norefxobj: have the list of keys to copy at one place To avoid repeting ourselves. Change-Id: I39667620b9cf391251327c8f66ad8b9649ead36f Reviewed-on: https://gerrit.libreoffice.org/35810 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit f86c3fd8e95f378061d57b77d1c700e076996086) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 40c41235351c..7096e197296e 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11917,11 +11917,6 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) return; } - OString sColorSpaces = copyExternalResources(*pPage, "ColorSpace"); - OString sExtGStates = copyExternalResources(*pPage, "ExtGState"); - OString sFonts = copyExternalResources(*pPage, "Font"); - OString sXObjects = copyExternalResources(*pPage, "XObject"); - filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents"); if (!pPageContents) { @@ -11932,8 +11927,6 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) 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. - if (!updateObject(nWrappedFormObject)) - return; OStringBuffer aLine; aLine.append(nWrappedFormObject); @@ -11941,10 +11934,15 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) aLine.append("<< /Type /XObject"); aLine.append(" /Subtype /Form"); aLine.append(" /Resources <<"); - aLine.append(sColorSpaces); - aLine.append(sExtGStates); - aLine.append(sFonts); - aLine.append(sXObjects); + static const std::initializer_list<OString> aKeys = + { + "ColorSpace", + "ExtGState", + "Font", + "XObject" + }; + for (const auto& rKey : aKeys) + aLine.append(copyExternalResources(*pPage, rKey)); aLine.append(">>"); aLine.append(" /BBox [ 0 0 "); aLine.append(aSize.Width()); @@ -11976,6 +11974,8 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) // 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())); } commit 7bf1b20b8e890b66ae878a56661b04d77801b17d Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Tue Mar 28 16:14:11 2017 +0200 tdf#106693 vcl PDF export, norefxobj: handle multiple refs in copied arrays Also fix confusion about dictionaries in arrays and arrays in dictionaries. Change-Id: I0d71d5796b1eb4f89e3fd9a5b1f807d2a7340a35 Reviewed-on: https://gerrit.libreoffice.org/35806 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 30608c66374f8effa9d534f7f9a22d41daa9770f) diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index d6b44e88d027..4bed3c32737a 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -109,8 +109,10 @@ class VCL_DLLPUBLIC PDFArrayElement : public PDFElement /// Location after the '[' token. sal_uInt64 m_nOffset = 0; std::vector<PDFElement*> m_aElements; + /// The object that contains this array. + PDFObjectElement* m_pObject; public: - PDFArrayElement(); + PDFArrayElement(PDFObjectElement* pObject); bool Read(SvStream& rStream) override; void PushBack(PDFElement* pElement); const std::vector<PDFElement*>& GetElements(); diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 3e59ac1f63dd..b50105565e3c 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -938,7 +938,7 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s } case '[': { - auto pArr = new PDFArrayElement(); + auto pArr = new PDFArrayElement(pObject); rElements.push_back(std::unique_ptr<PDFElement>(pArr)); if (nDictionaryDepth == 0 && nArrayDepth == 0) { @@ -962,9 +962,10 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s case ']': { rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement())); - pArray = nullptr; - rStream.SeekRel(-1); --nArrayDepth; + if (nArrayDepth == 0) + pArray = nullptr; + rStream.SeekRel(-1); if (nDictionaryDepth == 0 && nArrayDepth == 0) { if (pObject) @@ -2124,18 +2125,25 @@ size_t PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement PDFArrayElement* pArray = nullptr; sal_uInt64 nDictionaryOffset = 0; int nDictionaryDepth = 0; + // Toplevel dictionary found (not inside an array). + bool bDictionaryFound = false; + // Toplevel array found (not inside a dictionary). + bool bArrayFound = false; for (size_t i = nIndex; i < rElements.size(); ++i) { // Dictionary tokens can be nested, track enter/leave. if (auto pDictionary = dynamic_cast<PDFDictionaryElement*>(rElements[i].get())) { + bDictionaryFound = true; if (++nDictionaryDepth == 1) { // First dictionary start, track start offset. nDictionaryOffset = pDictionary->m_nLocation; if (pThisObject) { - pThisObject->SetDictionary(pDictionary); + if (!bArrayFound) + // The the toplevel dictionary of the object. + pThisObject->SetDictionary(pDictionary); pThisDictionary = pDictionary; pThisObject->SetDictionaryOffset(nDictionaryOffset); } @@ -2186,7 +2194,11 @@ size_t PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement else { if (pArray) - pArray->PushBack(pName); + { + if (bDictionaryFound) + // Array inside dictionary. + pArray->PushBack(pName); + } else { // Name-name key-value. @@ -2205,6 +2217,7 @@ size_t PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement auto pArr = dynamic_cast<PDFArrayElement*>(rElements[i].get()); if (pArr) { + bArrayFound = true; pArray = pArr; continue; } @@ -2245,7 +2258,9 @@ size_t PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement } else { - pArray->PushBack(pReference); + if (bDictionaryFound) + // Array inside dictionary. + pArray->PushBack(pReference); } aNumbers.clear(); continue; @@ -2928,7 +2943,10 @@ bool PDFEndObjectElement::Read(SvStream& /*rStream*/) return true; } -PDFArrayElement::PDFArrayElement() = default; +PDFArrayElement::PDFArrayElement(PDFObjectElement* pObject) + : m_pObject(pObject) +{ +} bool PDFArrayElement::Read(SvStream& rStream) { @@ -2948,6 +2966,8 @@ bool PDFArrayElement::Read(SvStream& rStream) void PDFArrayElement::PushBack(PDFElement* pElement) { + if (m_pObject) + SAL_INFO("vcl.filter", "PDFArrayElement::PushBack: object is " << m_pObject->GetObjectValue()); m_aElements.push_back(pElement); } diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 0d1eee6f753a..40c41235351c 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11694,6 +11694,7 @@ void PDFWriterImpl::writeJPG( JPGEmit& rObject ) sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject) { sal_Int32 nObject = createObject(); + SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject); OStringBuffer aLine; aLine.append(nObject); @@ -11766,7 +11767,8 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter const std::vector<filter::PDFElement*>& rElements = pArray->GetElements(); bool bDone = false; - // Complex case: can't copy the array byte array as is, as it contains a reference. + // Complex case: can't copy the array byte array as is, as it may contain references. + sal_uInt64 nCopyStart = 0; for (const auto pElement : rElements) { auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); @@ -11778,28 +11780,37 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter // Copy the referenced object. sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); - sal_uInt64 nArrStart = rObject.GetArrayOffset(); sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation(); sal_uInt64 nReferenceEnd = pReference->GetOffset(); - sal_uInt64 nArrEnd = nArrStart + rObject.GetArrayLength(); + sal_uInt64 nOffset = 0; + if (nCopyStart == 0) + // Array start -> reference start. + nOffset = rObject.GetArrayOffset(); + else + // Previous reference end -> reference start. + nOffset = nCopyStart; + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset); - // Array start -> reference start. - aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nArrStart, nReferenceStart - nArrStart); // Write the updated reference. aLine.append(" "); aLine.append(nRef); aLine.append(" 0 R"); - // Reference end -> array end. - aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nReferenceEnd, nArrEnd - nReferenceEnd); + // Start copying here next time. + nCopyStart = nReferenceEnd; bDone = true; - break; } } } - // Can copy it as-is. - if (!bDone) + if (bDone) + { + // Copy the last part here, in the complex case. + sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength(); + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nArrEnd - nCopyStart); + } + else + // Can copy it as-is. aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength()); aLine.append("]\n"); commit a4c24675a14d324e9103f36300c099f25c2fdc21 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Tue Mar 28 12:10:03 2017 +0200 tdf#106693 vcl PDF export, norefxobj: copy nested arrays correctly When copying an array we're only interested in the start/end position of the outermost array, otherwise only part of the array is copied. Change-Id: I9f5cb5e3ed395142fd82db34e1153ddfdf9f0eb3 Reviewed-on: https://gerrit.libreoffice.org/35797 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 3ea5e3401e567bfe956817fd5abd17530da664f5) diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 6b2bfae3e989..3e59ac1f63dd 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -875,6 +875,8 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s bool bInStartXRef = false; // Dictionary depth, so we know when we're outside any dictionaries. int nDictionaryDepth = 0; + // Array depth, only the offset/length of the toplevel array is tracked. + int nArrayDepth = 0; // Last seen array token that's outside any dictionaries. PDFArrayElement* pArray = nullptr; while (true) @@ -938,7 +940,7 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s { auto pArr = new PDFArrayElement(); rElements.push_back(std::unique_ptr<PDFElement>(pArr)); - if (nDictionaryDepth == 0) + if (nDictionaryDepth == 0 && nArrayDepth == 0) { // The array is attached directly, inform the object. pArray = pArr; @@ -948,6 +950,7 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s pObject->SetArrayOffset(rStream.Tell()); } } + ++nArrayDepth; rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) { @@ -961,7 +964,8 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement())); pArray = nullptr; rStream.SeekRel(-1); - if (nDictionaryDepth == 0) + --nArrayDepth; + if (nDictionaryDepth == 0 && nArrayDepth == 0) { if (pObject) { diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 45655260a27e..0d1eee6f753a 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11906,6 +11906,8 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) return; } + OString sColorSpaces = copyExternalResources(*pPage, "ColorSpace"); + OString sExtGStates = copyExternalResources(*pPage, "ExtGState"); OString sFonts = copyExternalResources(*pPage, "Font"); OString sXObjects = copyExternalResources(*pPage, "XObject"); @@ -11928,6 +11930,8 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) aLine.append("<< /Type /XObject"); aLine.append(" /Subtype /Form"); aLine.append(" /Resources <<"); + aLine.append(sColorSpaces); + aLine.append(sExtGStates); aLine.append(sFonts); aLine.append(sXObjects); aLine.append(">>"); commit 2c98939545d80198d37e2df23d5342105b50a557 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Tue Mar 28 09:06:09 2017 +0200 tdf#106694 RTF import: fix missing paragraph tab position The problem here was that while in general paragraph style / direct formatting deduplication is supposed to happen in the tokenizer, paragraph tab positions is an exception, and dmapper expects to see the duplicated tokens. Fix the problem by introducing a blacklist that contains tokens not to deduplicate. Change-Id: I1cca53e99cfdb082df389ff295f3447cc8f9d3b8 Reviewed-on: https://gerrit.libreoffice.org/35790 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit fea174753b1c6b0882aebb044bf1a1eef6fa50e0) diff --git a/sw/qa/extras/rtfimport/data/tdf106694.rtf b/sw/qa/extras/rtfimport/data/tdf106694.rtf new file mode 100644 index 000000000000..9abcb205bbeb --- /dev/null +++ b/sw/qa/extras/rtfimport/data/tdf106694.rtf @@ -0,0 +1,10 @@ +{\rtf1\ansi\deflang3081\ftnbj\uc1\deff0 +{\colortbl ;\red255\green255\blue255 ;\red0\green0\blue0 ;\red54\green95\blue145 ;\red79\green129\blue188 ;\red255\green0\blue0 ;\red255\green255\blue128 ;\red128\green0\blue0 ;\red127\green127\blue127 ;\red35\green62\blue95 ;\red63\green63\blue63 ;\red95\green95\blue95 ;\red47\green47\blue47 ;\red0\green64\blue128 ;\red79\green79\blue79 ;\red111\green111\blue111 ;\red0\green0\blue255 ;\red239\green239\blue239 ;\red192\green1\blue1 ;} +{\stylesheet +{\f0\fs24 Normal;} +{\s22\snext0\f1\fs18\b\tqr\tldot\tx8280\fi0\li0\ri720\sb120\sa40\sl0 TOC 1 +;} +} +\pard\ssparaaux0\s22\tqr\tldot\tx8280\ri720\sb120\sa40\ql\outlinelevel0\plain\f0\fs24\plain\f2\fs18\hich\f2\dbch\f2\loch\f2\fs18\b +Model Detail\tab 2\par +} diff --git a/sw/qa/extras/rtfimport/rtfimport.cxx b/sw/qa/extras/rtfimport/rtfimport.cxx index 79f82d72dedb..8fee2ab2fcd1 100644 --- a/sw/qa/extras/rtfimport/rtfimport.cxx +++ b/sw/qa/extras/rtfimport/rtfimport.cxx @@ -2752,6 +2752,14 @@ DECLARE_RTFIMPORT_TEST(testTdf105729, "tdf105729.rtf") CPPUNIT_ASSERT_EQUAL(style::ParagraphAdjust_CENTER, static_cast<style::ParagraphAdjust>(getProperty<sal_Int16>(getParagraph(1), "ParaAdjust"))); } +DECLARE_RTFIMPORT_TEST(testTdf106694, "tdf106694.rtf") +{ + auto aTabs = getProperty< uno::Sequence<style::TabStop> >(getParagraph(1), "ParaTabStops"); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aTabs.getLength()); + // This was 0, tab position was incorrect, looked like it was missing. + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(14605), aTabs[0].Position); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/rtftok/rtfsprm.cxx b/writerfilter/source/rtftok/rtfsprm.cxx index da7bc815c6ca..7afee14a4189 100644 --- a/writerfilter/source/rtftok/rtfsprm.cxx +++ b/writerfilter/source/rtftok/rtfsprm.cxx @@ -153,6 +153,23 @@ static RTFValue::Pointer_t getDefaultSPRM(Id const id) } } +/// Is it problematic to deduplicate this SPRM? +static bool isSPRMDeduplicateBlacklist(Id nId) +{ + switch (nId) + { + case NS_ooxml::LN_CT_TabStop_val: + case NS_ooxml::LN_CT_TabStop_leader: + case NS_ooxml::LN_CT_TabStop_pos: + // See the NS_ooxml::LN_CT_PPrBase_tabs handler in DomainMapper, + // deduplication is explicitly not wanted for these tokens. + return true; + + default: + return false; + } +} + /// Does the clone / deduplication of a single sprm. static void cloneAndDeduplicateSprm(std::pair<Id, RTFValue::Pointer_t>& rSprm, RTFSprms& ret) { @@ -161,7 +178,8 @@ static void cloneAndDeduplicateSprm(std::pair<Id, RTFValue::Pointer_t>& rSprm, R { if (rSprm.second->equals(*pValue)) { - ret.erase(rSprm.first); // duplicate to style + if (!isSPRMDeduplicateBlacklist(rSprm.first)) + ret.erase(rSprm.first); // duplicate to style } else if (!rSprm.second->getSprms().empty() || !rSprm.second->getAttributes().empty()) { commit 5a71e1e80def5bac7931e580ffc2203659802947 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Mon Mar 27 15:39:04 2017 +0200 tdf#106693 vcl PDF export, norefxobj: handle multiple refs in copied dicts When copying font definitions the dictionary has multiple values where the type is a reference. Improve PDFWriterImpl::copyExternalResource(), so that multiple references are copied correctly as well. With this the bugdoc (from comment 5) text appears in the output. Reviewed-on: https://gerrit.libreoffice.org/35760 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 2ba9d58d5978c94352c4c6cf9c47aa3de79d05fe) Change-Id: I2343e616d8b36e3cdcbd4e713bd3f7fa7bce6d3b diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index 9ccbb43d0225..d6b44e88d027 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -80,10 +80,12 @@ public: sal_uInt64 GetDictionaryOffset(); void SetDictionaryLength(sal_uInt64 nDictionaryLength); sal_uInt64 GetDictionaryLength(); - PDFDictionaryElement* GetDictionary() const; + PDFDictionaryElement* GetDictionary(); void SetDictionary(PDFDictionaryElement* pDictionaryElement); /// Get access to the parsed key-value items from the object dictionary. const std::map<OString, PDFElement*>& GetDictionaryItems() const; + /// Same as GetDictionaryItems(), but entries are sorted by file offset. + std::vector< std::pair<OString, PDFElement*> > GetDictionaryItemsByOffset(); void SetArray(PDFArrayElement* pArrayElement); void SetStream(PDFStreamElement* pStreamElement); /// Access to the stream of the object, if it has any. diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 4b5c8b18ea45..6b2bfae3e989 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -2443,8 +2443,10 @@ sal_uInt64 PDFObjectElement::GetArrayLength() return m_nArrayLength; } -PDFDictionaryElement* PDFObjectElement::GetDictionary() const +PDFDictionaryElement* PDFObjectElement::GetDictionary() { + if (m_aDictionary.empty()) + PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary); return m_pDictionaryElement; } @@ -2453,6 +2455,25 @@ void PDFObjectElement::SetDictionary(PDFDictionaryElement* pDictionaryElement) m_pDictionaryElement = pDictionaryElement; } +std::vector< std::pair<OString, PDFElement*> > PDFObjectElement::GetDictionaryItemsByOffset() +{ + std::vector< std::pair<OString, PDFElement*> > aRet; + + for (const auto& rItem : m_aDictionary) + aRet.push_back(rItem); + + PDFDictionaryElement* pDictionary = GetDictionary(); + if (!pDictionary) + return aRet; + + std::sort(aRet.begin(), aRet.end(), [pDictionary](const std::pair<OString, PDFElement*>& a, const std::pair<OString, PDFElement*>& b) -> bool + { + return pDictionary->GetKeyOffset(a.first) < pDictionary->GetKeyOffset(b.first); + }); + + return aRet; +} + const std::map<OString, PDFElement*>& PDFObjectElement::GetDictionaryItems() const { return m_aDictionary; diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 794c79595bbb..45655260a27e 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11702,10 +11702,11 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter { aLine.append("<<"); - // Complex case: can't copy the dictionary byte array as is, as it contains a reference. + // Complex case: can't copy the dictionary byte array as is, as it may contain references. bool bDone = false; - const std::map<OString, filter::PDFElement*>& rItems = rObject.GetDictionaryItems(); - for (const auto& rItem : rItems) + std::vector< std::pair<OString, filter::PDFElement*> > aItems = rObject.GetDictionaryItemsByOffset(); + sal_uInt64 nCopyStart = 0; + for (const auto& rItem : aItems) { auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second); if (pReference) @@ -11716,27 +11717,36 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter // Copy the referenced object. sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); - sal_uInt64 nDictStart = rObject.GetDictionaryOffset(); sal_uInt64 nReferenceStart = pDictionary->GetKeyOffset(rItem.first) + rItem.first.getLength(); sal_uInt64 nReferenceEnd = pDictionary->GetKeyOffset(rItem.first) + pDictionary->GetKeyValueLength(rItem.first); - sal_uInt64 nDictEnd = nDictStart + rObject.GetDictionaryLength(); - // Dict start -> reference start. - aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nDictStart, nReferenceStart - nDictStart); + sal_uInt64 nOffset = 0; + if (nCopyStart == 0) + // Dict start -> reference start. + nOffset = rObject.GetDictionaryOffset(); + else + // Previous reference end -> reference start. + nOffset = nCopyStart; + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset); // Write the updated reference. aLine.append(" "); aLine.append(nRef); aLine.append(" 0 R"); - // Reference end -> dict end. - aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nReferenceEnd, nDictEnd - nReferenceEnd); + // Start copying here next time. + nCopyStart = nReferenceEnd; bDone = true; - break; } } } - // Can copy it as-is. - if (!bDone) + if (bDone) + { + // Copy the last part here, in the complex case. + sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength(); + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nDictEnd - nCopyStart); + } + else + // Can copy it as-is. aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength()); aLine.append(">>\n"); @@ -11896,6 +11906,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) return; } + OString sFonts = copyExternalResources(*pPage, "Font"); OString sXObjects = copyExternalResources(*pPage, "XObject"); filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents"); @@ -11917,6 +11928,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) aLine.append("<< /Type /XObject"); aLine.append(" /Subtype /Form"); aLine.append(" /Resources <<"); + aLine.append(sFonts); aLine.append(sXObjects); aLine.append(">>"); aLine.append(" /BBox [ 0 0 "); commit d3ebd862bb8e0d398af4fe004c042deff5dc029c Author: Stephan Bergmann <sberg...@redhat.com> Date: Wed Mar 22 10:03:40 2017 +0100 Missing check for rStream.IsEof() ...after a324099538916eae7f7239d32fd98ec8018cbb72 "xmlsecurity PDF signing: only write incremental xref in an incremental update" inserted the 'if' before the 'while (!rStream.IsEof())' Change-Id: Ib527894031f356c3d6df40b70259469ef4c338de (cherry picked from commit e8aaaa52fa5abe4a70224ab6e6eee6265b0d61c8) diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index f2de0bec68f1..4b5c8b18ea45 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -1927,6 +1927,10 @@ bool PDFNumberElement::Read(SvStream& rStream) m_nOffset = rStream.Tell(); char ch; rStream.ReadChar(ch); + if (rStream.IsEof()) + { + return false; + } if (!isdigit(ch) && ch != '-' && ch != '.') { rStream.SeekRel(-1); commit ff2ad09cb3117ff0654875aa30d5320e001bd8be Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Mon Mar 27 14:21:53 2017 +0200 vcl PDF export, norefxobj: improve ref handling in dicts When copying objects referenced from the page stream support references in any item value, not just for one single item key. Also move the dictionary entry generator code to PDFWriterImpl::copyExternalResources(), so other keys can be copied without code duplication. Change-Id: I4004e82014cec915c66a8a9d3aed2155fa2452ef Reviewed-on: https://gerrit.libreoffice.org/35755 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 06d073695c764744d308c74f80c40a317255fc05) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 3fedd1668512..794c79595bbb 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11705,11 +11705,9 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter // Complex case: can't copy the dictionary byte array as is, as it contains a reference. bool bDone = false; const std::map<OString, filter::PDFElement*>& rItems = rObject.GetDictionaryItems(); - OString aReferenceName("ColorSpace"); - auto it = rItems.find(aReferenceName); - if (it != rItems.end()) + for (const auto& rItem : rItems) { - auto pReference = dynamic_cast<filter::PDFReferenceElement*>(it->second); + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second); if (pReference) { filter::PDFObjectElement* pReferenced = pReference->LookupObject(); @@ -11719,8 +11717,8 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); sal_uInt64 nDictStart = rObject.GetDictionaryOffset(); - sal_uInt64 nReferenceStart = pDictionary->GetKeyOffset(aReferenceName) + aReferenceName.getLength(); - sal_uInt64 nReferenceEnd = pDictionary->GetKeyOffset(aReferenceName) + pDictionary->GetKeyValueLength(aReferenceName); + sal_uInt64 nReferenceStart = pDictionary->GetKeyOffset(rItem.first) + rItem.first.getLength(); + sal_uInt64 nReferenceEnd = pDictionary->GetKeyOffset(rItem.first) + pDictionary->GetKeyValueLength(rItem.first); sal_uInt64 nDictEnd = nDictStart + rObject.GetDictionaryLength(); // Dict start -> reference start. aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nDictStart, nReferenceStart - nDictStart); @@ -11732,6 +11730,7 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nReferenceEnd, nDictEnd - nReferenceEnd); bDone = true; + break; } } } @@ -11807,7 +11806,7 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter return nObject; } -std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind) +OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind) { // A name - object ID map, IDs as they appear in our output, not the // original ones. @@ -11816,11 +11815,11 @@ std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObj // Get the rKind subset of the resource dictionary. auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")); if (!pResources) - return aRet; + return OString(); auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pResources->LookupElement(rKind)); if (!pDictionary) - return aRet; + return OString(); SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer(); @@ -11841,7 +11840,15 @@ std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObj aRet[rItem.first] = nObject; } - return aRet; + // Build the dictionary entry string. + OString sRet = "/" + rKind + "<<"; + for (const auto& rPair : aRet) + { + sRet += "/" + rPair.first + " " + OString::number(rPair.second) + " 0 R"; + } + sRet += ">>"; + + return sRet; } void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) @@ -11889,13 +11896,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) return; } - std::map<OString, sal_Int32> aXObjects = copyExternalResources(*pPage, "XObject"); - OString sXObjects = "/XObject<<"; - for (const auto& rPair : aXObjects) - { - sXObjects += "/" + rPair.first + " " + OString::number(rPair.second) + " 0 R"; - } - sXObjects += ">>"; + OString sXObjects = copyExternalResources(*pPage, "XObject"); filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents"); if (!pPageContents) diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx index ea1c0cd2892e..2329cbdec0bb 100644 --- a/vcl/source/gdi/pdfwriter_impl.hxx +++ b/vcl/source/gdi/pdfwriter_impl.hxx @@ -860,8 +860,8 @@ i12626 /// Writes the form XObject proxy for the image. void writeReferenceXObject(ReferenceXObjectEmit& rEmit); /// Copies resources of a given kind from an external page to the output, - /// returning what has beeen copied (name) and where (object ID). - std::map<OString, sal_Int32> copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind); + /// returning what has to be included in the new resource dictionary. + OString copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind); /// Copies a single resource from an external document, returns the new /// object ID in our document. sal_Int32 copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject); commit 034168971a4f542fb89bbe564943297d3e4ba317 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Mon Mar 27 09:17:30 2017 +0200 tdf#106693 vcl PDF export, norefxobj: copy array objects So far only the dictionary and the stream of the object was copied, see if it has an array, and take care of that as well. Also check if the array contains a reference and act accordingly. Change-Id: I7f3bb12ec0bbc6f6e1db4f43625c7768b862c895 Reviewed-on: https://gerrit.libreoffice.org/35744 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 044e8d795276cc495c1f796a14ad36e6a5f9cdb9) diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index fbfb81ed10a2..9ccbb43d0225 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -32,6 +32,7 @@ class PDFDocument; class PDFDictionaryElement; class PDFArrayElement; class PDFStreamElement; +class PDFNumberElement; /// A byte range in a PDF file. class VCL_DLLPUBLIC PDFElement @@ -54,6 +55,10 @@ class VCL_DLLPUBLIC PDFObjectElement : public PDFElement /// Length of the dictionary buffer till (before) the '>>' token. sal_uInt64 m_nDictionaryLength; PDFDictionaryElement* m_pDictionaryElement; + /// Position after the '[' token, if m_pArrayElement is set. + sal_uInt64 m_nArrayOffset; + /// Length of the array buffer till (before) the ']' token. + sal_uInt64 m_nArrayLength; /// The contained direct array, if any. PDFArrayElement* m_pArrayElement; /// The stream of this object, used when this is an object stream. @@ -83,6 +88,10 @@ public: void SetStream(PDFStreamElement* pStreamElement); /// Access to the stream of the object, if it has any. PDFStreamElement* GetStream() const; + void SetArrayOffset(sal_uInt64 nArrayOffset); + sal_uInt64 GetArrayOffset(); + void SetArrayLength(sal_uInt64 nArrayLength); + sal_uInt64 GetArrayLength(); PDFArrayElement* GetArray() const; /// Parse objects stored in this object stream. void ParseStoredObjects(); @@ -113,9 +122,11 @@ class VCL_DLLPUBLIC PDFReferenceElement : public PDFElement int m_fGenerationValue; /// Location after the 'R' token. sal_uInt64 m_nOffset = 0; + /// The element providing the object number. + PDFNumberElement& m_rObject; public: - PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue); + PDFReferenceElement(PDFDocument& rDoc, PDFNumberElement& rObject, PDFNumberElement& rGeneration); bool Read(SvStream& rStream) override; /// Assuming the reference points to a number object, return its value. double LookupNumber(SvStream& rStream) const; @@ -124,6 +135,7 @@ public: int GetObjectValue() const; int GetGenerationValue() const; sal_uInt64 GetOffset() const; + PDFNumberElement& GetObjectElement() const; }; /// Stream object: a byte array with a known length. diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index a364c0a19612..f2de0bec68f1 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -943,7 +943,10 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s // The array is attached directly, inform the object. pArray = pArr; if (pObject) + { pObject->SetArray(pArray); + pObject->SetArrayOffset(rStream.Tell()); + } } rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) @@ -958,6 +961,13 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement())); pArray = nullptr; rStream.SeekRel(-1); + if (nDictionaryDepth == 0) + { + if (pObject) + { + pObject->SetArrayLength(rStream.Tell() - pObject->GetArrayOffset()); + } + } if (!rElements.back()->Read(rStream)) { SAL_WARN("vcl.filter", "PDFDocument::Tokenize: PDFEndArrayElement::Read() failed"); @@ -1048,7 +1058,7 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s } else { - rElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue()))); + rElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, *pObjectNumber, *pGenerationNumber))); if (pArray) // Reference is part of a direct (non-dictionary) array, inform the array. pArray->PushBack(rElements.back().get()); @@ -2061,6 +2071,8 @@ PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, doubl m_nDictionaryOffset(0), m_nDictionaryLength(0), m_pDictionaryElement(nullptr), + m_nArrayOffset(0), + m_nArrayLength(0), m_pArrayElement(nullptr), m_pStreamElement(nullptr) { @@ -2361,6 +2373,16 @@ sal_uInt64 PDFObjectElement::GetDictionaryOffset() return m_nDictionaryOffset; } +void PDFObjectElement::SetArrayOffset(sal_uInt64 nArrayOffset) +{ + m_nArrayOffset = nArrayOffset; +} + +sal_uInt64 PDFObjectElement::GetArrayOffset() +{ + return m_nArrayOffset; +} + void PDFDictionaryElement::SetKeyOffset(const OString& rKey, sal_uInt64 nOffset) { m_aDictionaryKeyOffset[rKey] = nOffset; @@ -2407,6 +2429,16 @@ sal_uInt64 PDFObjectElement::GetDictionaryLength() return m_nDictionaryLength; } +void PDFObjectElement::SetArrayLength(sal_uInt64 nArrayLength) +{ + m_nArrayLength = nArrayLength; +} + +sal_uInt64 PDFObjectElement::GetArrayLength() +{ + return m_nArrayLength; +} + PDFDictionaryElement* PDFObjectElement::GetDictionary() const { return m_pDictionaryElement; @@ -2592,11 +2624,17 @@ PDFDocument& PDFObjectElement::GetDocument() return m_rDoc; } -PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue) +PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, PDFNumberElement& rObject, PDFNumberElement& rGeneration) : m_rDoc(rDoc), - m_fObjectValue(fObjectValue), - m_fGenerationValue(fGenerationValue) + m_fObjectValue(rObject.GetValue()), + m_fGenerationValue(rGeneration.GetValue()), + m_rObject(rObject) +{ +} + +PDFNumberElement& PDFReferenceElement::GetObjectElement() const { + return m_rObject; } bool PDFReferenceElement::Read(SvStream& rStream) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 1d43bbde8dc0..3fedd1668512 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11751,6 +11751,51 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter aLine.append("\nendstream\n"); } + if (filter::PDFArrayElement* pArray = rObject.GetArray()) + { + aLine.append("["); + + const std::vector<filter::PDFElement*>& rElements = pArray->GetElements(); + bool bDone = false; + // Complex case: can't copy the array byte array as is, as it contains a reference. + for (const auto pElement : rElements) + { + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); + if (pReference) + { + filter::PDFObjectElement* pReferenced = pReference->LookupObject(); + if (pReferenced) + { + // Copy the referenced object. + sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); + + sal_uInt64 nArrStart = rObject.GetArrayOffset(); + sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation(); + sal_uInt64 nReferenceEnd = pReference->GetOffset(); + sal_uInt64 nArrEnd = nArrStart + rObject.GetArrayLength(); + + // Array start -> reference start. + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nArrStart, nReferenceStart - nArrStart); + // Write the updated reference. + aLine.append(" "); + aLine.append(nRef); + aLine.append(" 0 R"); + // Reference end -> array end. + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nReferenceEnd, nArrEnd - nReferenceEnd); + + bDone = true; + break; + } + } + } + + // Can copy it as-is. + if (!bDone) + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength()); + + aLine.append("]\n"); + } + aLine.append("endobj\n\n"); // We have the whole object, now write it to the output. commit 4cbe0413408f0214f9a4dc8c0253571e941c8083 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Fri Mar 24 15:16:32 2017 +0100 tdf#106693 vcl PDF export, norefxobj: update XObject refs Start copying referenced objects recursively, and also take care of updating references to the object IDs as they appear in our output. With this, the 4th image referenced from the PDF image has a correctly updated reference in its dictionary's ColorSpace key. Change-Id: I8b49701c1f60bd0ef5a097b24ce59164554c44fa Reviewed-on: https://gerrit.libreoffice.org/35653 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit f135a8bdeba15cf72dd31c7d613d335bbfc7017b) diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index 135d30d8d8bb..fbfb81ed10a2 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -77,6 +77,8 @@ public: sal_uInt64 GetDictionaryLength(); PDFDictionaryElement* GetDictionary() const; void SetDictionary(PDFDictionaryElement* pDictionaryElement); + /// Get access to the parsed key-value items from the object dictionary. + const std::map<OString, PDFElement*>& GetDictionaryItems() const; void SetArray(PDFArrayElement* pArrayElement); void SetStream(PDFStreamElement* pStreamElement); /// Access to the stream of the object, if it has any. @@ -109,6 +111,8 @@ class VCL_DLLPUBLIC PDFReferenceElement : public PDFElement PDFDocument& m_rDoc; int m_fObjectValue; int m_fGenerationValue; + /// Location after the 'R' token. + sal_uInt64 m_nOffset = 0; public: PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue); @@ -119,6 +123,7 @@ public: PDFObjectElement* LookupObject(); int GetObjectValue() const; int GetGenerationValue() const; + sal_uInt64 GetOffset() const; }; /// Stream object: a byte array with a known length. diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index e8a34e14e7a8..a364c0a19612 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -2217,7 +2217,10 @@ size_t PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement { rDictionary[aName] = pReference; if (pThisDictionary) + { pThisDictionary->SetKeyOffset(aName, nNameOffset); + pThisDictionary->SetKeyValueLength(aName, pReference->GetOffset() - nNameOffset); + } aName.clear(); } else @@ -2414,6 +2417,11 @@ void PDFObjectElement::SetDictionary(PDFDictionaryElement* pDictionaryElement) m_pDictionaryElement = pDictionaryElement; } +const std::map<OString, PDFElement*>& PDFObjectElement::GetDictionaryItems() const +{ + return m_aDictionary; +} + void PDFObjectElement::SetArray(PDFArrayElement* pArrayElement) { m_pArrayElement = pArrayElement; @@ -2591,12 +2599,18 @@ PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, in { } -bool PDFReferenceElement::Read(SvStream& /*rStream*/) +bool PDFReferenceElement::Read(SvStream& rStream) { SAL_INFO("vcl.filter", "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R"); + m_nOffset = rStream.Tell(); return true; } +sal_uInt64 PDFReferenceElement::GetOffset() const +{ + return m_nOffset; +} + double PDFReferenceElement::LookupNumber(SvStream& rStream) const { size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue); diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 841f53e6d3be..1d43bbde8dc0 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11691,6 +11691,77 @@ void PDFWriterImpl::writeJPG( JPGEmit& rObject ) writeReferenceXObject(rObject.m_aReferenceXObject); } +sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject) +{ + sal_Int32 nObject = createObject(); + + OStringBuffer aLine; + aLine.append(nObject); + aLine.append(" 0 obj\n"); + if (filter::PDFDictionaryElement* pDictionary = rObject.GetDictionary()) + { + aLine.append("<<"); + + // Complex case: can't copy the dictionary byte array as is, as it contains a reference. + bool bDone = false; + const std::map<OString, filter::PDFElement*>& rItems = rObject.GetDictionaryItems(); + OString aReferenceName("ColorSpace"); + auto it = rItems.find(aReferenceName); + if (it != rItems.end()) + { + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(it->second); + if (pReference) + { + filter::PDFObjectElement* pReferenced = pReference->LookupObject(); + if (pReferenced) + { + // Copy the referenced object. + sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced); + + sal_uInt64 nDictStart = rObject.GetDictionaryOffset(); + sal_uInt64 nReferenceStart = pDictionary->GetKeyOffset(aReferenceName) + aReferenceName.getLength(); + sal_uInt64 nReferenceEnd = pDictionary->GetKeyOffset(aReferenceName) + pDictionary->GetKeyValueLength(aReferenceName); + sal_uInt64 nDictEnd = nDictStart + rObject.GetDictionaryLength(); + // Dict start -> reference start. + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nDictStart, nReferenceStart - nDictStart); + // Write the updated reference. + aLine.append(" "); + aLine.append(nRef); + aLine.append(" 0 R"); + // Reference end -> dict end. + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nReferenceEnd, nDictEnd - nReferenceEnd); + + bDone = true; + } + } + } + + // Can copy it as-is. + if (!bDone) + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength()); + + aLine.append(">>\n"); + } + + if (filter::PDFStreamElement* pStream = rObject.GetStream()) + { + aLine.append("stream\n"); + SvMemoryStream& rStream = pStream->GetMemory(); + aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize()); + aLine.append("\nendstream\n"); + } + + aLine.append("endobj\n\n"); + + // We have the whole object, now write it to the output. + if (!updateObject(nObject)) + return -1; + if (!writeBuffer(aLine.getStr(), aLine.getLength())) + return -1; + + return nObject; +} + std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind) { // A name - object ID map, IDs as they appear in our output, not the @@ -11706,6 +11777,8 @@ std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObj if (!pDictionary) return aRet; + SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer(); + const std::map<OString, filter::PDFElement*>& rItems = pDictionary->GetItems(); for (const auto& rItem : rItems) { @@ -11718,31 +11791,8 @@ std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObj if (!pValue) continue; - sal_Int32 nObject = createObject(); - if (!updateObject(nObject)) - continue; - - SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer(); - - // When copying over an object copy its dictionary and its stream. - OStringBuffer aLine; - aLine.append(nObject); - aLine.append(" 0 obj\n<<"); - aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + pValue->GetDictionaryOffset(), pValue->GetDictionaryLength()); - aLine.append(">>\nstream\n"); - - filter::PDFStreamElement* pStream = pValue->GetStream(); - if (!pStream) - continue; - - SvMemoryStream& rStream = pStream->GetMemory(); - aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize()); - aLine.append("\nendstream\nendobj\n\n"); - - // We have the whole object, now write it to the output. - if (!writeBuffer(aLine.getStr(), aLine.getLength())) - continue; - + // Then copying over an object copy its dictionary and its stream. + sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue); aRet[rItem.first] = nObject; } diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx index cc69f58f1567..ea1c0cd2892e 100644 --- a/vcl/source/gdi/pdfwriter_impl.hxx +++ b/vcl/source/gdi/pdfwriter_impl.hxx @@ -862,6 +862,9 @@ i12626 /// Copies resources of a given kind from an external page to the output, /// returning what has beeen copied (name) and where (object ID). std::map<OString, sal_Int32> copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind); + /// Copies a single resource from an external document, returns the new + /// object ID in our document. + sal_Int32 copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject); /* tries to find the bitmap by its id and returns its emit data if exists, else creates a new emit data block */ commit 095073cf908b01636bfe6a9fe4224040728de976 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Fri Mar 24 09:46:21 2017 +0100 tdf#106693 vcl PDF export, norefxobj: copy XObject references With this the images inside the PDF image show up correctly. Change-Id: I430502fb6ae9de8111dda7e67db33642ff263317 Reviewed-on: https://gerrit.libreoffice.org/35621 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 1f2bccf2d28d4257aa0e325658d35182367b59d9) diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index 8d362e7e339c..135d30d8d8bb 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -44,13 +44,14 @@ public: /// Indirect object: something with a unique ID. class VCL_DLLPUBLIC PDFObjectElement : public PDFElement { + /// The document owning this element. PDFDocument& m_rDoc; double m_fObjectValue; double m_fGenerationValue; std::map<OString, PDFElement*> m_aDictionary; /// Position after the '<<' token. sal_uInt64 m_nDictionaryOffset; - /// Length of the dictionary buffer till (before) the '<<' token. + /// Length of the dictionary buffer till (before) the '>>' token. sal_uInt64 m_nDictionaryLength; PDFDictionaryElement* m_pDictionaryElement; /// The contained direct array, if any. @@ -86,6 +87,7 @@ public: std::vector< std::unique_ptr<PDFElement> >& GetStoredElements(); SvMemoryStream* GetStreamBuffer() const; void SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer); + PDFDocument& GetDocument(); }; /// Array object: a list. diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 0e458e053a24..e8a34e14e7a8 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -2579,6 +2579,11 @@ void PDFObjectElement::SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamB m_pStreamBuffer = std::move(pStreamBuffer); } +PDFDocument& PDFObjectElement::GetDocument() +{ + return m_rDoc; +} + PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue) : m_rDoc(rDoc), m_fObjectValue(fObjectValue), diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 5b949be60c07..841f53e6d3be 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11691,6 +11691,64 @@ void PDFWriterImpl::writeJPG( JPGEmit& rObject ) writeReferenceXObject(rObject.m_aReferenceXObject); } +std::map<OString, sal_Int32> PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind) +{ + // A name - object ID map, IDs as they appear in our output, not the + // original ones. + std::map<OString, sal_Int32> aRet; + + // Get the rKind subset of the resource dictionary. + auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")); + if (!pResources) + return aRet; + + auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pResources->LookupElement(rKind)); + if (!pDictionary) + return aRet; + + const std::map<OString, filter::PDFElement*>& rItems = pDictionary->GetItems(); + for (const auto& rItem : rItems) + { + // For each item copy it over to our output then insert it into aRet. + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second); + if (!pReference) + continue; + + filter::PDFObjectElement* pValue = pReference->LookupObject(); + if (!pValue) + continue; + + sal_Int32 nObject = createObject(); + if (!updateObject(nObject)) + continue; + + SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer(); + + // When copying over an object copy its dictionary and its stream. + OStringBuffer aLine; + aLine.append(nObject); + aLine.append(" 0 obj\n<<"); + aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + pValue->GetDictionaryOffset(), pValue->GetDictionaryLength()); + aLine.append(">>\nstream\n"); + + filter::PDFStreamElement* pStream = pValue->GetStream(); + if (!pStream) + continue; + + SvMemoryStream& rStream = pStream->GetMemory(); + aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize()); + aLine.append("\nendstream\nendobj\n\n"); + + // We have the whole object, now write it to the output. + if (!writeBuffer(aLine.getStr(), aLine.getLength())) + continue; + + aRet[rItem.first] = nObject; + } + + return aRet; +} + void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) { if (rEmit.m_nFormObject <= 0) @@ -11711,12 +11769,6 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) sal_Int32 nWrappedFormObject = 0; if (!m_aContext.UseReferenceXObject) { - 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. - if (!updateObject(nWrappedFormObject)) - return; - // Parse the PDF data, we need that to write the PDF dictionary of our // object. SvMemoryStream aPDFStream; @@ -11729,24 +11781,48 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) return; } std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages(); - if (aPages.empty() || !aPages[0]) + if (aPages.empty()) { SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); return; } - filter::PDFObjectElement* pPageContents = aPages[0]->LookupObject("Contents"); + filter::PDFObjectElement* pPage = aPages[0]; + if (!pPage) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page"); + return; + } + + std::map<OString, sal_Int32> aXObjects = copyExternalResources(*pPage, "XObject"); + OString sXObjects = "/XObject<<"; + for (const auto& rPair : aXObjects) + { + sXObjects += "/" + rPair.first + " " + OString::number(rPair.second) + " 0 R"; + } + sXObjects += ">>"; + + filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents"); if (!pPageContents) { SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents"); return; } + 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. + if (!updateObject(nWrappedFormObject)) + return; + OStringBuffer aLine; aLine.append(nWrappedFormObject); aLine.append(" 0 obj\n"); aLine.append("<< /Type /XObject"); aLine.append(" /Subtype /Form"); + aLine.append(" /Resources <<"); + aLine.append(sXObjects); + aLine.append(">>"); aLine.append(" /BBox [ 0 0 "); aLine.append(aSize.Width()); aLine.append(" "); diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx index 2bffdf275e4d..cc69f58f1567 100644 --- a/vcl/source/gdi/pdfwriter_impl.hxx +++ b/vcl/source/gdi/pdfwriter_impl.hxx @@ -72,6 +72,11 @@ class PDFStreamIf; class Matrix3; class PdfBuiltinFontFace; +namespace filter +{ +class PDFObjectElement; +} + class PDFWriterImpl { friend class PDFStreamIf; @@ -854,6 +859,9 @@ i12626 void writeJPG( JPGEmit& rEmit ); /// Writes the form XObject proxy for the image. void writeReferenceXObject(ReferenceXObjectEmit& rEmit); + /// Copies resources of a given kind from an external page to the output, + /// returning what has beeen copied (name) and where (object ID). + std::map<OString, sal_Int32> copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind); /* tries to find the bitmap by its id and returns its emit data if exists, else creates a new emit data block */ commit cef6c73ad4b83d17ee3750a37e3134366dfb6687 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Thu Mar 23 14:26:42 2017 +0100 tdf#106693 vcl PDF export, norefxobj: handle compressed page stream Since we want to avoid re-compressing the page stream create two form XObjects: one that resets the graphic state to the default (e.g. line width) and an other one that contains the original page stream as-is. With this PDF images where the page stream is compressed are handled correctly. Change-Id: Ib44dae2e167e4d5604a0a3a3cf91e09795137343 (cherry picked from commit d0c24cbb027130f3781bfc3475dd225190afd560) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 1133cb9b16ff..5b949be60c07 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11696,10 +11696,6 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) if (rEmit.m_nFormObject <= 0) return; - OStringBuffer aLine; - if (!updateObject(rEmit.m_nFormObject)) - return; - // Count /Matrix and /BBox. // vcl::ImportPDF() works with 96 DPI so use the same values here, too. sal_Int32 nOldDPIX = getReferenceDevice()->GetDPIX(); @@ -11712,15 +11708,92 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) double fScaleX = 1.0 / aSize.Width(); double fScaleY = 1.0 / aSize.Height(); + sal_Int32 nWrappedFormObject = 0; + if (!m_aContext.UseReferenceXObject) + { + 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. + if (!updateObject(nWrappedFormObject)) + return; + + // Parse the PDF data, we need that to write the PDF dictionary of our + // object. + SvMemoryStream aPDFStream; + aPDFStream.WriteBytes(rEmit.m_aPDFData.getArray(), rEmit.m_aPDFData.getLength()); + aPDFStream.Seek(0); + filter::PDFDocument aPDFDocument; + if (!aPDFDocument.Read(aPDFStream)) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed"); + return; + } + std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages(); + if (aPages.empty() || !aPages[0]) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); + return; + } + + filter::PDFObjectElement* pPageContents = aPages[0]->LookupObject("Contents"); + if (!pPageContents) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents"); + return; + } + + OStringBuffer aLine; + aLine.append(nWrappedFormObject); + aLine.append(" 0 obj\n"); + aLine.append("<< /Type /XObject"); + aLine.append(" /Subtype /Form"); + 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) + { + aLine.append(" /Filter /"); + aLine.append(pFilter->GetValue()); + } + + aLine.append(" /Length "); + + filter::PDFStreamElement* pPageStream = pPageContents->GetStream(); + if (!pPageStream) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream"); + return; + } + + SvMemoryStream& rPageStream = pPageStream->GetMemory(); + + 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"); + CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength())); + } + + OStringBuffer aLine; + if (!updateObject(rEmit.m_nFormObject)) + return; + // Now have all the info to write the form XObject. aLine.append(rEmit.m_nFormObject); aLine.append(" 0 obj\n"); aLine.append("<< /Type /XObject"); aLine.append(" /Subtype /Form"); aLine.append(" /Resources << /XObject<</Im"); - aLine.append(rEmit.m_nBitmapObject); + sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject; + aLine.append(nObject); aLine.append(" "); - aLine.append(rEmit.m_nBitmapObject); + aLine.append(nObject); aLine.append(" 0 R>> >>"); aLine.append(" /Matrix [ "); appendDouble(fScaleX, aLine); @@ -11759,44 +11832,13 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) } else { - // No reference XObject, include the original page stream. // Reset line width to the default. aStream.append(" 1 w\n"); - SvMemoryStream aPDFStream; - aPDFStream.WriteBytes(rEmit.m_aPDFData.getArray(), rEmit.m_aPDFData.getLength()); - aPDFStream.Seek(0); - filter::PDFDocument aPDFDocument; - if (!aPDFDocument.Read(aPDFStream)) - { - SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed"); - return; - } - std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages(); - if (aPages.empty() || !aPages[0]) - { - SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); - return; - } - - filter::PDFObjectElement* pPageContents = aPages[0]->LookupObject("Contents"); - if (!pPageContents) - { - SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents"); - return; - } - - filter::PDFStreamElement* pPageStream = pPageContents->GetStream(); - if (!pPageStream) - { - SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream"); - return; - } - - SvMemoryStream& rPageStream = pPageStream->GetMemory(); - - // Copy the original page stream to the end of the form XObject stream. - aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize()); - aStream.append("\n"); + // 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()); commit 4ec52adb338c32060adef0314b3816bf0dd4f7db Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Thu Mar 23 10:35:12 2017 +0100 tdf#106693 vcl PDF export, norefxobj: copy page stream This gives correct result in very simple cases when the page stream is not compressed and it references no other objects from the original file. Change-Id: I11ed50180a256bdb5c587fd8927d21c925100a4d Reviewed-on: https://gerrit.libreoffice.org/35580 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit d27818c1f34b01190bf419c34d6a174f3cad7894) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index b47efb7decaf..1133cb9b16ff 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -60,6 +60,7 @@ #include <vcl/strhelper.hxx> #include <vcl/svapp.hxx> #include <vcl/virdev.hxx> +#include <vcl/filter/pdfdocument.hxx> #include "fontsubset.hxx" #include "outdev.h" @@ -11744,13 +11745,59 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) OStringBuffer aStream; aStream.append("q "); - aStream.append(aSize.Width()); - aStream.append(" 0 0 "); - aStream.append(aSize.Height()); - aStream.append(" 0 0 cm\n"); - aStream.append("/Im"); - aStream.append(rEmit.m_nBitmapObject); - aStream.append(" Do\n"); + if (m_aContext.UseReferenceXObject) + { + // Reference XObject markup is used, just refer to the fallback bitmap + // here. + aStream.append(aSize.Width()); + aStream.append(" 0 0 "); + aStream.append(aSize.Height()); + aStream.append(" 0 0 cm\n"); + aStream.append("/Im"); + aStream.append(rEmit.m_nBitmapObject); + aStream.append(" Do\n"); + } + else + { + // No reference XObject, include the original page stream. + // Reset line width to the default. + aStream.append(" 1 w\n"); + SvMemoryStream aPDFStream; + aPDFStream.WriteBytes(rEmit.m_aPDFData.getArray(), rEmit.m_aPDFData.getLength()); + aPDFStream.Seek(0); + filter::PDFDocument aPDFDocument; + if (!aPDFDocument.Read(aPDFStream)) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed"); + return; + } + std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages(); + if (aPages.empty() || !aPages[0]) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); + return; + } + + filter::PDFObjectElement* pPageContents = aPages[0]->LookupObject("Contents"); + if (!pPageContents) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents"); + return; + } + + filter::PDFStreamElement* pPageStream = pPageContents->GetStream(); + if (!pPageStream) + { + SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream"); + return; + } + + SvMemoryStream& rPageStream = pPageStream->GetMemory(); + + // Copy the original page stream to the end of the form XObject stream. + aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize()); + aStream.append("\n"); + } aStream.append("Q"); aLine.append(aStream.getLength()); @@ -12096,6 +12143,8 @@ void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObject rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject; } + else + rEmit.m_aPDFData = rGraphic.getPdfData(); rEmit.m_nFormObject = createObject(); rEmit.m_aPixelSize = rGraphic.GetBitmap().GetPrefSize(); diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx index ff2165a2f5bb..2bffdf275e4d 100644 --- a/vcl/source/gdi/pdfwriter_impl.hxx +++ b/vcl/source/gdi/pdfwriter_impl.hxx @@ -201,6 +201,8 @@ public: sal_Int32 m_nBitmapObject; /// Size of the bitmap replacement, in pixels. Size m_aPixelSize; + /// PDF data from the graphic object, if not writing a reference XObject. + css::uno::Sequence<sal_Int8> m_aPDFData; ReferenceXObjectEmit() : m_nFormObject(0), commit 7de3c19ce685f9719a18859c9240d48e27b55dec Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Wed Mar 22 16:30:35 2017 +0100 tdf#106693 vcl PDF export: initial UseReferenceXObject option It's still on, but in experimental mode start work towards the ability to not use that markup. Change-Id: Idf11c0e0a3c61ad93af331346ec7107304f6dc0f Reviewed-on: https://gerrit.libreoffice.org/35538 Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> Tested-by: Jenkins <c...@libreoffice.org> (cherry picked from commit 5f4826d89bfa1398b16fc85cf593ff58ce5e36a9) diff --git a/filter/source/pdf/pdfexport.cxx b/filter/source/pdf/pdfexport.cxx index 307d39eff691..8d4e61ccf6f4 100644 --- a/filter/source/pdf/pdfexport.cxx +++ b/filter/source/pdf/pdfexport.cxx @@ -47,6 +47,7 @@ #include <cppuhelper/exc_hlp.hxx> #include <cppuhelper/compbase.hxx> #include <cppuhelper/basemutex.hxx> +#include <officecfg/Office/Common.hxx> #include "pdfexport.hxx" #include "impdialog.hxx" @@ -782,6 +783,8 @@ bool PDFExport::Export( const OUString& rFile, const Sequence< PropertyValue >& aContext.SignPassword = msSignPassword; aContext.SignCertificate = maSignCertificate; aContext.SignTSA = msSignTSA; + // Not using reference XObjects is experimental for now. + aContext.UseReferenceXObject = !officecfg::Office::Common::Misc::ExperimentalMode::get(); // all context data set, time to create the printing device std::unique_ptr<vcl::PDFWriter> pPDFWriter(new vcl::PDFWriter( aContext, xEnc )); diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index b54ce53891b2..f4dba18efc84 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -637,6 +637,8 @@ The following structure describes the permissions used in PDF security PDFWriter::ColorMode ColorMode; css::uno::Reference< css::security::XCertificate> SignCertificate; OUString SignTSA; + /// Use reference XObject markup for PDF images. + bool UseReferenceXObject; PDFWriterContext() : RelFsys( false ), //i56629, i49415?, i64585? @@ -666,7 +668,8 @@ The following structure describes the permissions used in PDF security DPIx( 0 ), DPIy( 0 ), ColorMode( PDFWriter::DrawColor ), - SignCertificate( nullptr ) + SignCertificate( nullptr ), + UseReferenceXObject( false ) {} }; diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index b5b371e3b577..b47efb7decaf 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -11692,7 +11692,7 @@ void PDFWriterImpl::writeJPG( JPGEmit& rObject ) void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) { - if (rEmit.m_nFormObject <= 0 || rEmit.m_nEmbeddedObject <= 0) + if (rEmit.m_nFormObject <= 0) return; OStringBuffer aLine; @@ -11732,10 +11732,13 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit) aLine.append(aSize.Height()); aLine.append(" ]\n"); - // Write the reference dictionary. - aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F "); - aLine.append(rEmit.m_nEmbeddedObject); - aLine.append(" 0 R >> >> /Page 0 >>\n"); + if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0) + { + // Write the reference dictionary. + aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F "); + aLine.append(rEmit.m_nEmbeddedObject); + aLine.append(" 0 R >> >> /Page 0 >>\n"); + } aLine.append("/Length "); @@ -12084,13 +12087,17 @@ void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObject if (!rGraphic.getPdfData().hasElements()) return; ... etc. - the rest is truncated _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits