[Libreoffice-commits] core.git: Branch 'distro/collabora/cp-5.3' - 7 commits - include/vcl svx/source vcl/qa vcl/source
Miklos Vajna
vmiklos at collabora.co.uk
Wed Apr 12 08:54:41 UTC 2017
include/vcl/filter/pdfdocument.hxx | 7 -
svx/source/xml/xmlgrhlp.cxx | 15 ++
vcl/qa/cppunit/pdfexport/data/tdf107013.odt |binary
vcl/qa/cppunit/pdfexport/data/tdf107018.odt |binary
vcl/qa/cppunit/pdfexport/data/tdf107089.odt |binary
vcl/qa/cppunit/pdfexport/pdfexport.cxx | 190 ++++++++++++++++++++--------
vcl/source/filter/ipdf/pdfdocument.cxx | 73 ++++++----
vcl/source/filter/ipdf/pdfread.cxx | 107 +++++++++++++++
vcl/source/gdi/pdfwriter_impl.cxx | 144 ++++++++++++---------
9 files changed, 388 insertions(+), 148 deletions(-)
New commits:
commit eae84fb805f8ba1bce72a64e78b8ef6f8eb22439
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Tue Apr 11 17:39:10 2017 +0200
PDF export of PDF images: compress page stream if requested
compressStream() automatically takes care of
VCL_DEBUG_DISABLE_PDFCOMPRESSION, so this also simplifies code.
Change-Id: I7661123e6ba73f8f064ec0543a03e2ec15fd2468
Reviewed-on: https://gerrit.libreoffice.org/36415
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
Tested-by: Jenkins <ci at libreoffice.org>
(cherry picked from commit 54a4121f2040bd11f3d6056767f2d7ad6c7745ac)
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index 76cad0f2a9af..36b5134bdaf3 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -481,10 +481,16 @@ void PdfExportTest::testTdf107089()
// Make sure 'Hello' is part of the form object's stream.
vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
CPPUNIT_ASSERT(pStream);
- SvMemoryStream& rObjectStream = pStream->GetMemory();
+ SvMemoryStream aObjectStream;
+ ZCodec aZCodec;
+ aZCodec.BeginCompression();
+ pStream->GetMemory().Seek(0);
+ aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
+ CPPUNIT_ASSERT(aZCodec.EndCompression());
+ aObjectStream.Seek(0);
OString aHello("Hello");
- auto pStart = static_cast<const char*>(rObjectStream.GetData());
- const char* pEnd = pStart + rObjectStream.GetSize();
+ auto pStart = static_cast<const char*>(aObjectStream.GetData());
+ const char* pEnd = pStart + aObjectStream.GetSize();
auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength());
// This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage.
CPPUNIT_ASSERT(it != pEnd);
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 2c94de56c183..a6ced4d62952 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -12003,10 +12003,11 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
aLine.append(aSize.Height());
aLine.append(" ]");
+ if (!g_bDebugDisableCompression)
+ aLine.append(" /Filter/FlateDecode");
aLine.append(" /Length ");
- sal_Int32 nLength = 0;
- OStringBuffer aStream;
+ SvMemoryStream aStream;
for (auto pContent : aContentStreams)
{
filter::PDFStreamElement* pPageStream = pContent->GetStream();
@@ -12035,21 +12036,19 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
continue;
}
- nLength += aMemoryStream.GetSize();
- aStream.append(static_cast<const sal_Char*>(aMemoryStream.GetData()), aMemoryStream.GetSize());
+ aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
}
else
- {
- nLength += rPageStream.GetSize();
- aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
- }
+ aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
}
+ compressStream(&aStream);
+ sal_Int32 nLength = aStream.Tell();
aLine.append(nLength);
aLine.append(">>\nstream\n");
// Copy the original page streams to the form XObject stream.
- aLine.append(aStream.makeStringAndClear());
+ aLine.append(static_cast<const sal_Char*>(aStream.GetData()), aStream.GetSize());
aLine.append("\nendstream\nendobj\n\n");
if (!updateObject(nWrappedFormObject))
return;
commit 198c3a5929e183ed710c8838844148bd0c1a533a
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Tue Apr 11 16:50:53 2017 +0200
Related: tdf#106972 vcl PDF import: downgrade PDF >= 1.5
There are two problems with these newer PDF versions:
- the current PDF export code doesn't know how to roundtrip such PDF
images (needs work on both the import and export side)
- upgrading the default PDF export version would upset readers who can't
parse PDF >= 1.5
So instead of raising the default PDF export version, for now just be
conservative and depend on pdfium to downgrade the PDF image version to
1.4 if it would be higher.
Given that this modifies the input of the graphic filter this also needs
changes in the ODF export, so that the filter result will contain that
downgraded data, not the original one.
Reviewed-on: https://gerrit.libreoffice.org/36413
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
Tested-by: Jenkins <ci at libreoffice.org>
(cherry picked from commit 37bdf1659ddb11d8706289511623cc7c8b0d264b)
Conflicts:
vcl/source/filter/ipdf/pdfread.cxx
Change-Id: I1efa97af8110e9a6ee3e8a7339bcc7d70457cfb0
diff --git a/svx/source/xml/xmlgrhlp.cxx b/svx/source/xml/xmlgrhlp.cxx
index 6ac0f44d5701..de29f96df181 100644
--- a/svx/source/xml/xmlgrhlp.cxx
+++ b/svx/source/xml/xmlgrhlp.cxx
@@ -564,7 +564,20 @@ bool SvXMLGraphicHelper::ImplWriteGraphic( const OUString& rPictureStorageName,
std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( aStream.xStream ));
if( bUseGfxLink && aGfxLink.GetDataSize() && aGfxLink.GetData() )
- pStream->WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize());
+ {
+ const uno::Sequence<sal_Int8>& rPdfData = aGraphic.getPdfData();
+ if (rPdfData.hasElements())
+ {
+ // The graphic has PDF data attached to it, use that.
+ // vcl::ImportPDF() possibly downgraded the PDF data from a
+ // higher PDF version, while aGfxLink still contains the
+ // original data provided by the user.
+ pStream->WriteBytes(rPdfData.getConstArray(), rPdfData.getLength());
+ bRet = (pStream->GetError() == 0);
+ }
+ else
+ pStream->WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize());
+ }
else
{
if( aGraphic.GetType() == GraphicType::Bitmap )
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index 1b9eaf153d2c..76cad0f2a9af 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -383,10 +383,9 @@ void PdfExportTest::testTdf106972Pdf17()
vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
CPPUNIT_ASSERT(pXObject);
- // This failed, the "image" had resources; that typically means we tried to
- // preserve the original PDF markup here; which is not OK till our default
- // output is PDF 1.4, and this bugdoc has PDF 1.7 data.
- CPPUNIT_ASSERT(!pXObject->Lookup("Resources"));
+ // Assert that we now attempt to preserve the original PDF data, even if
+ // the original input was PDF >= 1.4.
+ CPPUNIT_ASSERT(pXObject->Lookup("Resources"));
}
void PdfExportTest::testTdf107013()
diff --git a/vcl/source/filter/ipdf/pdfread.cxx b/vcl/source/filter/ipdf/pdfread.cxx
index f11f55a0b0f7..f8754405d0b7 100644
--- a/vcl/source/filter/ipdf/pdfread.cxx
+++ b/vcl/source/filter/ipdf/pdfread.cxx
@@ -17,6 +17,7 @@
#endif
#include <fpdfview.h>
#include <fpdf_edit.h>
+#include <fpdf_save.h>
#ifdef WNT
#include <postwin.h>
#endif
@@ -31,6 +32,29 @@ namespace
#if HAVE_FEATURE_PDFIUM
+/// Callback class to be used with FPDF_SaveWithVersion().
+struct CompatibleWriter : public FPDF_FILEWRITE
+{
+public:
+ CompatibleWriter();
+ static int WriteBlockCallback(FPDF_FILEWRITE* pFileWrite, const void* pData, unsigned long nSize);
+
+ SvMemoryStream m_aStream;
+};
+
+CompatibleWriter::CompatibleWriter()
+{
+ FPDF_FILEWRITE::version = 1;
+ FPDF_FILEWRITE::WriteBlock = CompatibleWriter::WriteBlockCallback;
+}
+
+int CompatibleWriter::WriteBlockCallback(FPDF_FILEWRITE* pFileWrite, const void* pData, unsigned long nSize)
+{
+ auto pImpl = static_cast<CompatibleWriter*>(pFileWrite);
+ pImpl->m_aStream.WriteBytes(pData, nSize);
+ return 1;
+}
+
/// Convert to inch, then assume 96 DPI.
double pointToPixel(double fPoint)
{
@@ -94,6 +118,70 @@ bool generatePreview(SvStream& rStream, Graphic& rGraphic)
return true;
}
+
+/// Decide if PDF data is old enough to be compatible.
+bool isCompatible(SvStream& rInStream)
+{
+ // %PDF-x.y
+ sal_uInt8 aFirstBytes[8];
+ rInStream.Seek(STREAM_SEEK_TO_BEGIN);
+ sal_uLong nRead = rInStream.ReadBytes(aFirstBytes, 8);
+ if (nRead < 8)
+ return false;
+
+ if ((aFirstBytes[0] != '%' || aFirstBytes[1] != 'P' || aFirstBytes[2] != 'D' || aFirstBytes[3] != 'F' || aFirstBytes[4] != '-'))
+ return false;
+
+ sal_Int32 nMajor = OString(aFirstBytes[5]).toInt32();
+ sal_Int32 nMinor = OString(aFirstBytes[7]).toInt32();
+ if (nMajor > 1 || (nMajor == 1 && nMinor > 4))
+ return false;
+
+ return true;
+}
+
+/// Takes care of transparently downgrading the version of the PDF stream in
+/// case it's too new for our PDF export.
+bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
+{
+ bool bCompatible = isCompatible(rInStream);
+ rInStream.Seek(STREAM_SEEK_TO_BEGIN);
+ if (bCompatible)
+ // Not converting.
+ rOutStream.WriteStream(rInStream);
+ else
+ {
+ // Downconvert to PDF-1.4.
+ FPDF_LIBRARY_CONFIG aConfig;
+ aConfig.version = 2;
+ aConfig.m_pUserFontPaths = nullptr;
+ aConfig.m_pIsolate = nullptr;
+ aConfig.m_v8EmbedderSlot = 0;
+ FPDF_InitLibraryWithConfig(&aConfig);
+
+ // Read input into a buffer.
+ SvMemoryStream aInBuffer;
+ aInBuffer.WriteStream(rInStream);
+
+ // Load the buffer using pdfium.
+ FPDF_DOCUMENT pPdfDocument = FPDF_LoadMemDocument(aInBuffer.GetData(), aInBuffer.GetSize(), /*password=*/nullptr);
+ if (!pPdfDocument)
+ return false;
+
+ CompatibleWriter aWriter;
+ // 14 means PDF-1.4.
+ if (!FPDF_SaveWithVersion(pPdfDocument, &aWriter, 0, 14))
+ return false;
+
+ FPDF_CloseDocument(pPdfDocument);
+ FPDF_DestroyLibrary();
+
+ aWriter.m_aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ rOutStream.WriteStream(aWriter.m_aStream);
+ }
+
+ return rOutStream.good();
+}
#else
bool generatePreview(SvStream& rStream, Graphic& rGraphic)
{
@@ -102,6 +190,13 @@ bool generatePreview(SvStream& rStream, Graphic& rGraphic)
return true;
}
+
+bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
+{
+ rInStream.Seek(STREAM_SEEK_TO_BEGIN);
+ rOutStream.WriteStream(rInStream);
+ return rOutStream.good();
+}
#endif // HAVE_FEATURE_PDFIUM
}
@@ -116,10 +211,14 @@ bool ImportPDF(SvStream& rStream, Graphic& rGraphic)
return false;
// Save the original PDF stream for later use.
- rStream.Seek(STREAM_SEEK_TO_END);
- uno::Sequence<sal_Int8> aPdfData(rStream.Tell());
- rStream.Seek(STREAM_SEEK_TO_BEGIN);
- rStream.ReadBytes(aPdfData.getArray(), aPdfData.getLength());
+ SvMemoryStream aMemoryStream;
+ if (!getCompatibleStream(rStream, aMemoryStream))
+ return false;
+
+ aMemoryStream.Seek(STREAM_SEEK_TO_END);
+ uno::Sequence<sal_Int8> aPdfData(aMemoryStream.Tell());
+ aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aMemoryStream.ReadBytes(aPdfData.getArray(), aPdfData.getLength());
rGraphic.setPdfData(aPdfData);
return true;
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 2a68cbf683a3..2c94de56c183 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -665,32 +665,6 @@ static void appendDestinationName( const OUString& rString, OStringBuffer& rBuff
}
//<--- i56629
-/// Decide if rGraphic has PDF data that is possible to embed in our output.
-static bool hasPdfData(const Graphic& rGraphic, bool bUseReferenceXObject)
-{
- const css::uno::Sequence<sal_Int8>& rData = rGraphic.getPdfData();
-
- if (rData.getLength() < 8)
- return false;
-
- if (rData[0] != '%' || rData[1] != 'P' || rData[2] != 'D' || rData[3] != 'F' || rData[4] != '-')
- // Unexpected header.
- return false;
-
- if (bUseReferenceXObject)
- // This is possible for all versions.
- return true;
-
- sal_Int32 nMajor = OString(rData[5]).toInt32();
- sal_Int32 nMinor = OString(rData[7]).toInt32();
-
- if (nMajor > 1 || (nMajor == 1 && nMinor > 4))
- // This is PDF-1.5 or newer, can't embed into PDF-1.4.
- return false;
-
- return true;
-}
-
void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
{
rBuffer.append( "FEFF" );
@@ -12487,7 +12461,7 @@ void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObject
// no pdf data.
rEmit.m_nBitmapObject = nBitmapObject;
- if (!hasPdfData(rGraphic, m_aContext.UseReferenceXObject))
+ if (!rGraphic.getPdfData().hasElements())
return;
if (m_aContext.UseReferenceXObject)
@@ -12556,7 +12530,7 @@ void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const
{
m_aJPGs.emplace( m_aJPGs.begin() );
JPGEmit& rEmit = m_aJPGs.front();
- if (!hasPdfData(rGraphic, m_aContext.UseReferenceXObject) || m_aContext.UseReferenceXObject)
+ if (!rGraphic.getPdfData().hasElements() || m_aContext.UseReferenceXObject)
rEmit.m_nObject = createObject();
rEmit.m_aID = aID;
rEmit.m_pStream.reset( pStream );
@@ -12668,7 +12642,7 @@ const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx
m_aBitmaps.push_front( BitmapEmit() );
m_aBitmaps.front().m_aID = aID;
m_aBitmaps.front().m_aBitmap = aBitmap;
- if (!hasPdfData(rGraphic, m_aContext.UseReferenceXObject) || m_aContext.UseReferenceXObject)
+ 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 4ea13cdfebb24cdb59af37000f216e8201f87b22
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Tue Apr 11 12:42:23 2017 +0200
tdf#107089 PDF export of PDF images: handle mixed filters of page streams
It's allowed to compress different page streams differently, and we must
have a single object stream for our form XObject, so just incompress all
of them to be consistent.
Change-Id: I7a20dc2084a902a37dcefa3420d59a576f120bcd
Reviewed-on: https://gerrit.libreoffice.org/36409
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
Tested-by: Jenkins <ci at libreoffice.org>
(cherry picked from commit 9e8598c42a1a6f2fbd88711aa9bea5961eaf7b4a)
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107089.odt b/vcl/qa/cppunit/pdfexport/data/tdf107089.odt
new file mode 100644
index 000000000000..5aaaab944a98
Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/tdf107089.odt differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index aacf36b2796b..1b9eaf153d2c 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -54,6 +54,7 @@ public:
void testTdf106972Pdf17();
void testTdf107013();
void testTdf107018();
+ void testTdf107089();
#endif
CPPUNIT_TEST_SUITE(PdfExportTest);
@@ -67,6 +68,7 @@ public:
CPPUNIT_TEST(testTdf106972Pdf17);
CPPUNIT_TEST(testTdf107013);
CPPUNIT_TEST(testTdf107018);
+ CPPUNIT_TEST(testTdf107089);
#endif
CPPUNIT_TEST_SUITE_END();
};
@@ -452,6 +454,42 @@ void PdfExportTest::testTdf107018()
// copying the page stream of a PDF image.
CPPUNIT_ASSERT_EQUAL(OString("Pages"), pName->GetValue());
}
+
+void PdfExportTest::testTdf107089()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("tdf107089.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Get access to the form object inside the image.
+ auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ // Make sure 'Hello' is part of the form object's stream.
+ vcl::filter::PDFStreamElement* pStream = pForm->GetStream();
+ CPPUNIT_ASSERT(pStream);
+ SvMemoryStream& rObjectStream = pStream->GetMemory();
+ OString aHello("Hello");
+ auto pStart = static_cast<const char*>(rObjectStream.GetData());
+ const char* pEnd = pStart + rObjectStream.GetSize();
+ auto it = std::search(pStart, pEnd, aHello.getStr(), aHello.getStr() + aHello.getLength());
+ // This failed, 'Hello' was part only a mixed compressed/uncompressed stream, i.e. garbage.
+ CPPUNIT_ASSERT(it != pEnd);
+}
#endif
CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 228a60b0403c..2a68cbf683a3 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -12017,7 +12017,8 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
"ColorSpace",
"ExtGState",
"Font",
- "XObject"
+ "XObject",
+ "Shading"
};
for (const auto& rKey : aKeys)
aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
@@ -12028,14 +12029,6 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
aLine.append(aSize.Height());
aLine.append(" ]");
- // For now assume that all the content streams have the same filter.
- auto pFilter = dynamic_cast<filter::PDFNameElement*>(aContentStreams[0]->Lookup("Filter"));
- if (pFilter)
- {
- aLine.append(" /Filter /");
- aLine.append(pFilter->GetValue());
- }
-
aLine.append(" /Length ");
sal_Int32 nLength = 0;
@@ -12051,8 +12044,31 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
SvMemoryStream& rPageStream = pPageStream->GetMemory();
- nLength += rPageStream.GetSize();
- aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
+ if (pFilter)
+ {
+ if (pFilter->GetValue() != "FlateDecode")
+ continue;
+
+ SvMemoryStream aMemoryStream;
+ ZCodec aZCodec;
+ rPageStream.Seek(0);
+ aZCodec.BeginCompression();
+ aZCodec.Decompress(rPageStream, aMemoryStream);
+ if (!aZCodec.EndCompression())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
+ continue;
+ }
+
+ nLength += aMemoryStream.GetSize();
+ aStream.append(static_cast<const sal_Char*>(aMemoryStream.GetData()), aMemoryStream.GetSize());
+ }
+ else
+ {
+ nLength += rPageStream.GetSize();
+ aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
+ }
}
aLine.append(nLength);
commit 6c01286bdb2a9ae10cb2e7f919c3c8016104c6f1
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Mon Apr 10 16:19:52 2017 +0200
Related: tdf#107013 PDF export of PDF images: improve content streams
It can happen that the list of content streams have an equal number of
"q" and "Q" operators when all of them is parsed. This means it's not
correct to represent these separate streams with separate form objects,
as those require equal number of "q" and "Q" operators by the end of
each object.
Instead concatenate the streams and always write a single form object,
not only in case there is a single content stream.
Change-Id: I62e4ee4c86403376155d10447404416686c84ef9
Reviewed-on: https://gerrit.libreoffice.org/36385
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
Tested-by: Jenkins <ci at libreoffice.org>
(cherry picked from commit a876f982a56c0bcc04667d55a53b05c90a8c3354)
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 4b8a4aa9a4a9..228a60b0403c 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -11946,7 +11946,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
double fScaleX = 1.0 / aSize.Width();
double fScaleY = 1.0 / aSize.Height();
- std::vector<sal_Int32> aWrappedFormObjects;
+ sal_Int32 nWrappedFormObject = 0;
if (!m_aContext.UseReferenceXObject)
{
// Parse the PDF data, we need that to write the PDF dictionary of our
@@ -11993,45 +11993,55 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
}
}
+ if (aContentStreams.empty())
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
+ return;
+ }
+
// Maps from source object id (PDF image) to target object id (export result).
std::map<sal_Int32, sal_Int32> aCopiedResources;
- for (auto pContent : aContentStreams)
- {
- aWrappedFormObjects.push_back(createObject());
- // Write the form XObject wrapped below. This is a separate object from
- // the wrapper, this way there is no need to alter the stream contents.
- OStringBuffer aLine;
- aLine.append(aWrappedFormObjects.back());
- aLine.append(" 0 obj\n");
- aLine.append("<< /Type /XObject");
- aLine.append(" /Subtype /Form");
- aLine.append(" /Resources <<");
- static const std::initializer_list<OString> aKeys =
- {
- "ColorSpace",
- "ExtGState",
- "Font",
- "XObject"
- };
- for (const auto& rKey : aKeys)
- aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
- aLine.append(">>");
- aLine.append(" /BBox [ 0 0 ");
- aLine.append(aSize.Width());
- aLine.append(" ");
- aLine.append(aSize.Height());
- aLine.append(" ]");
-
- auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
- if (pFilter)
- {
- aLine.append(" /Filter /");
- aLine.append(pFilter->GetValue());
- }
+ 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.
- aLine.append(" /Length ");
+ OStringBuffer aLine;
+ aLine.append(nWrappedFormObject);
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+ aLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys =
+ {
+ "ColorSpace",
+ "ExtGState",
+ "Font",
+ "XObject"
+ };
+ for (const auto& rKey : aKeys)
+ aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
+ aLine.append(">>");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(aSize.Width());
+ aLine.append(" ");
+ aLine.append(aSize.Height());
+ aLine.append(" ]");
+
+ // For now assume that all the content streams have the same filter.
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(aContentStreams[0]->Lookup("Filter"));
+ if (pFilter)
+ {
+ aLine.append(" /Filter /");
+ aLine.append(pFilter->GetValue());
+ }
+
+ aLine.append(" /Length ");
+ sal_Int32 nLength = 0;
+ OStringBuffer aStream;
+ for (auto pContent : aContentStreams)
+ {
filter::PDFStreamElement* pPageStream = pContent->GetStream();
if (!pPageStream)
{
@@ -12041,17 +12051,20 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
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");
- if (!updateObject(aWrappedFormObjects.back()))
- continue;
- if (!writeBuffer(aLine.getStr(), aLine.getLength()))
- continue;
+ nLength += rPageStream.GetSize();
+ aStream.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
}
+
+ aLine.append(nLength);
+
+ aLine.append(">>\nstream\n");
+ // Copy the original page streams to the form XObject stream.
+ aLine.append(aStream.makeStringAndClear());
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!updateObject(nWrappedFormObject))
+ return;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ return;
}
OStringBuffer aLine;
@@ -12064,18 +12077,14 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
aLine.append("<< /Type /XObject");
aLine.append(" /Subtype /Form");
aLine.append(" /Resources << /XObject<<");
- for (const auto nWrappedFormObject : aWrappedFormObjects)
- {
- sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
- aLine.append(" /Im");
- aLine.append(nObject);
- aLine.append(" ");
- aLine.append(nObject);
- aLine.append(" 0 R");
- if (m_aContext.UseReferenceXObject)
- break;
- }
+ sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
+ aLine.append(" /Im");
+ aLine.append(nObject);
+ aLine.append(" ");
+ aLine.append(nObject);
+ aLine.append(" 0 R");
+
aLine.append(">> >>");
aLine.append(" /Matrix [ ");
appendDouble(fScaleX, aLine);
@@ -12116,14 +12125,12 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
{
// Reset line width to the default.
aStream.append(" 1 w\n");
- for (const auto nWrappedFormObject : aWrappedFormObjects)
- {
- // No reference XObject, draw the form XObject containing the original
- // page stream.
- aStream.append("/Im");
- aStream.append(nWrappedFormObject);
- aStream.append(" Do\n");
- }
+
+ // No reference XObject, draw the form XObject containing the original
+ // page streams.
+ aStream.append("/Im");
+ aStream.append(nWrappedFormObject);
+ aStream.append(" Do\n");
}
aStream.append("Q");
aLine.append(aStream.getLength());
commit 9c8dfbc5c270f9a02c3a1ce4c6165358d57ad0b1
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Fri Apr 7 18:19:41 2017 +0200
tdf#107018 PDF export of PDF images: handle references in nested dictionaries
Also get rid of the GetKeyOffset() and GetKeyValueLength() calls when
copying dictionaries: the reference already knows its offset and length,
so no need to call them. This makes the dictionary and the array
handling more similar.
Change-Id: I65936acfaf857636a8d83da3a4cec69289eb89d8
Reviewed-on: https://gerrit.libreoffice.org/36282
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
Tested-by: Jenkins <ci at libreoffice.org>
(cherry picked from commit ee73747ab58fbbd5039823767693431223c347d3)
diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx
index 595b4f0fdfd3..d83cb8308f11 100644
--- a/include/vcl/filter/pdfdocument.hxx
+++ b/include/vcl/filter/pdfdocument.hxx
@@ -71,6 +71,9 @@ class VCL_DLLPUBLIC PDFObjectElement : public PDFElement
std::vector< std::unique_ptr<PDFElement> > m_aElements;
/// Uncompressed buffer of an object in an object stream.
std::unique_ptr<SvMemoryStream> m_pStreamBuffer;
+ /// List of all reference elements inside this object's dictionary and
+ /// nested dictionaries.
+ std::vector<PDFReferenceElement*> m_aDictionaryReferences;
public:
PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
@@ -88,8 +91,8 @@ public:
PDFNumberElement* GetNumberElement() const;
/// Get access to the parsed key-value items from the object dictionary.
const std::map<OString, PDFElement*>& GetDictionaryItems();
- /// Same as GetDictionaryItems(), but entries are sorted by file offset.
- std::vector< std::pair<OString, PDFElement*> > GetDictionaryItemsByOffset();
+ const std::vector<PDFReferenceElement*>& GetDictionaryReferences() const;
+ void AddDictionaryReference(PDFReferenceElement* pReference);
void SetArray(PDFArrayElement* pArrayElement);
void SetStream(PDFStreamElement* pStreamElement);
/// Access to the stream of the object, if it has any.
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107018.odt b/vcl/qa/cppunit/pdfexport/data/tdf107018.odt
new file mode 100644
index 000000000000..3bfc7b2d73cb
Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/tdf107018.odt differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index 31d0dfb384f2..aacf36b2796b 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -53,6 +53,7 @@ public:
void testTdf106972();
void testTdf106972Pdf17();
void testTdf107013();
+ void testTdf107018();
#endif
CPPUNIT_TEST_SUITE(PdfExportTest);
@@ -65,6 +66,7 @@ public:
CPPUNIT_TEST(testTdf106972);
CPPUNIT_TEST(testTdf106972Pdf17);
CPPUNIT_TEST(testTdf107013);
+ CPPUNIT_TEST(testTdf107018);
#endif
CPPUNIT_TEST_SUITE_END();
};
@@ -402,6 +404,54 @@ void PdfExportTest::testTdf107013()
// This failed, the reference to the image was created, but not the image.
CPPUNIT_ASSERT(pXObject);
}
+
+void PdfExportTest::testTdf107018()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("tdf107018.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pXObject);
+
+ // Get access to the form object inside the image.
+ auto pXObjectResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
+ CPPUNIT_ASSERT(pXObjectResources);
+ auto pXObjectForms = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObjectResources->LookupElement("XObject"));
+ CPPUNIT_ASSERT(pXObjectForms);
+ vcl::filter::PDFObjectElement* pForm = pXObjectForms->LookupObject(pXObjectForms->GetItems().begin()->first);
+ CPPUNIT_ASSERT(pForm);
+
+ // Get access to Resources -> Font -> F1 of the form.
+ auto pFormResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pForm->Lookup("Resources"));
+ CPPUNIT_ASSERT(pFormResources);
+ auto pFonts = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("Font"));
+ CPPUNIT_ASSERT(pFonts);
+ auto pF1Ref = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFonts->LookupElement("F1"));
+ CPPUNIT_ASSERT(pF1Ref);
+ vcl::filter::PDFObjectElement* pF1 = pF1Ref->LookupObject();
+ CPPUNIT_ASSERT(pF1);
+
+ // Check that Foo -> Bar of the font is of type Pages.
+ auto pFontFoo = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pF1->Lookup("Foo"));
+ CPPUNIT_ASSERT(pFontFoo);
+ auto pBar = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFontFoo->LookupElement("Bar"));
+ CPPUNIT_ASSERT(pBar);
+ vcl::filter::PDFObjectElement* pObject = pBar->LookupObject();
+ CPPUNIT_ASSERT(pObject);
+ auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
+ CPPUNIT_ASSERT(pName);
+ // This was "XObject", reference in a nested dictionary wasn't updated when
+ // copying the page stream of a PDF image.
+ CPPUNIT_ASSERT_EQUAL(OString("Pages"), pName->GetValue());
+}
#endif
CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
index 244379be7005..076eb4b7622f 100644
--- a/vcl/source/filter/ipdf/pdfdocument.cxx
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -1070,10 +1070,14 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< s
}
else
{
- rElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, *pObjectNumber, *pGenerationNumber)));
+ auto pReference = new PDFReferenceElement(*this, *pObjectNumber, *pGenerationNumber);
+ rElements.push_back(std::unique_ptr<PDFElement>(pReference));
if (pArray)
// Reference is part of a direct (non-dictionary) array, inform the array.
pArray->PushBack(rElements.back().get());
+ if (bInObject && nDictionaryDepth > 0 && pObject)
+ // Inform the object about a new in-dictionary reference.
+ pObject->AddDictionaryReference(pReference);
}
if (!rElements.back()->Read(rStream))
{
@@ -2509,23 +2513,14 @@ PDFNumberElement* PDFObjectElement::GetNumberElement() const
return m_pNumberElement;
}
-std::vector< std::pair<OString, PDFElement*> > PDFObjectElement::GetDictionaryItemsByOffset()
+const std::vector<PDFReferenceElement*>& PDFObjectElement::GetDictionaryReferences() const
{
- 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 m_aDictionaryReferences;
+}
- return aRet;
+void PDFObjectElement::AddDictionaryReference(PDFReferenceElement* pReference)
+{
+ m_aDictionaryReferences.push_back(pReference);
}
const std::map<OString, PDFElement*>& PDFObjectElement::GetDictionaryItems()
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index fbedcb28e72f..4b8a4aa9a4a9 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -11738,17 +11738,15 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter
OStringBuffer aLine;
aLine.append(nObject);
aLine.append(" 0 obj\n");
- if (filter::PDFDictionaryElement* pDictionary = rObject.GetDictionary())
+ if (rObject.GetDictionary())
{
aLine.append("<<");
// Complex case: can't copy the dictionary byte array as is, as it may contain references.
bool bDone = false;
- std::vector< std::pair<OString, filter::PDFElement*> > aItems = rObject.GetDictionaryItemsByOffset();
sal_uInt64 nCopyStart = 0;
- for (const auto& rItem : aItems)
+ for (auto pReference : rObject.GetDictionaryReferences())
{
- auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
if (pReference)
{
filter::PDFObjectElement* pReferenced = pReference->LookupObject();
@@ -11757,8 +11755,8 @@ sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter
// Copy the referenced object.
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);
+ sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
+ sal_uInt64 nReferenceEnd = pReference->GetOffset();
sal_uInt64 nOffset = 0;
if (nCopyStart == 0)
// Dict start -> reference start.
commit cd3b5910ada26eb5ffef3be05ba460b3c79fb4d7
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Fri Apr 7 16:25:49 2017 +0200
PDF export of PDF images: avoid invalid offset for not used jpeg bitmaps
I missed the JPEG case in commit
30102ded91b9ecfea172ffc6443154230ee37cbd (vcl PDF export, norefxobj:
avoid replacement bitmap, 2017-03-29).
Change-Id: If1f74c7873d05d5d7da5eb881626d4e5e535a0a1
Reviewed-on: https://gerrit.libreoffice.org/36272
Tested-by: Jenkins <ci at libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
(cherry picked from commit 39038a3544d1b42388a15e6098ccad8398e8ef36)
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 48632f9579b5..fbedcb28e72f 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -12535,7 +12535,8 @@ void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const
{
m_aJPGs.emplace( m_aJPGs.begin() );
JPGEmit& rEmit = m_aJPGs.front();
- rEmit.m_nObject = createObject();
+ if (!hasPdfData(rGraphic, m_aContext.UseReferenceXObject) || m_aContext.UseReferenceXObject)
+ rEmit.m_nObject = createObject();
rEmit.m_aID = aID;
rEmit.m_pStream.reset( pStream );
rEmit.m_bTrueColor = bIsTrueColor;
commit 3e11a0c6630d054458557753652bf4939bb51411
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Fri Apr 7 12:27:40 2017 +0200
tdf#107013 PDF export of PDF images: handle page tree and content streams
Handle when the page objects are not contained in a single list, but a
tree of "pages" objects.
Also handle when the page object has multiple content streams.
Change-Id: I7c5b0949314768af5915d37830a45e843e629446
Reviewed-on: https://gerrit.libreoffice.org/36256
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
Tested-by: Jenkins <ci at libreoffice.org>
(cherry picked from commit 4db4b9f016256fc8d2b637ed7a8f2b097aaa864b)
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf107013.odt b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt
new file mode 100644
index 000000000000..644e65c6ded8
Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index 566495f38edf..31d0dfb384f2 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -39,6 +39,7 @@ public:
virtual void setUp() override;
virtual void tearDown() override;
#if HAVE_FEATURE_PDFIUM
+ void load(const OUString& rFile, vcl::filter::PDFDocument& rDocument);
/// Tests that a pdf image is roundtripped back to PDF as a vector format.
void testTdf106059();
/// Tests that text highlight from Impress is not lost.
@@ -51,6 +52,7 @@ public:
void testTdf106693();
void testTdf106972();
void testTdf106972Pdf17();
+ void testTdf107013();
#endif
CPPUNIT_TEST_SUITE(PdfExportTest);
@@ -62,6 +64,7 @@ public:
CPPUNIT_TEST(testTdf106693);
CPPUNIT_TEST(testTdf106972);
CPPUNIT_TEST(testTdf106972Pdf17);
+ CPPUNIT_TEST(testTdf107013);
#endif
CPPUNIT_TEST_SUITE_END();
};
@@ -83,6 +86,26 @@ void PdfExportTest::tearDown()
}
#if HAVE_FEATURE_PDFIUM
+
+void PdfExportTest::load(const OUString& rFile, vcl::filter::PDFDocument& rDocument)
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile;
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(rDocument.Read(aStream));
+}
+
void PdfExportTest::testTdf106059()
{
// Import the bugdoc and export as PDF.
@@ -127,22 +150,8 @@ void PdfExportTest::testTdf106059()
void PdfExportTest::testTdf106693()
{
- // Import the bugdoc and export as PDF.
- OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106693.odt";
- mxComponent = loadFromDesktop(aURL);
- CPPUNIT_ASSERT(mxComponent.is());
-
- uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
- utl::TempFile aTempFile;
- aTempFile.EnableKillingFile();
- utl::MediaDescriptor aMediaDescriptor;
- aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
- xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
-
- // Parse the export result.
vcl::filter::PDFDocument aDocument;
- SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
- CPPUNIT_ASSERT(aDocument.Read(aStream));
+ load("tdf106693.odt", aDocument);
// Assert that the XObject in the page resources dictionary is a form XObject.
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
@@ -180,22 +189,8 @@ void PdfExportTest::testTdf106693()
void PdfExportTest::testTdf105461()
{
- // Import the bugdoc and export as PDF.
- OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105461.odp";
- mxComponent = loadFromDesktop(aURL);
- CPPUNIT_ASSERT(mxComponent.is());
-
- uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
- utl::TempFile aTempFile;
- aTempFile.EnableKillingFile();
- utl::MediaDescriptor aMediaDescriptor;
- aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
- xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
-
- // Parse the export result.
vcl::filter::PDFDocument aDocument;
- SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
- CPPUNIT_ASSERT(aDocument.Read(aStream));
+ load("tdf105461.odp", aDocument);
// The document has one page.
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
@@ -226,22 +221,8 @@ void PdfExportTest::testTdf105461()
void PdfExportTest::testTdf105093()
{
- // Import the bugdoc and export as PDF.
- OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105093.odp";
- mxComponent = loadFromDesktop(aURL);
- CPPUNIT_ASSERT(mxComponent.is());
-
- uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
- utl::TempFile aTempFile;
- aTempFile.EnableKillingFile();
- utl::MediaDescriptor aMediaDescriptor;
- aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
- xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
-
- // Parse the export result.
vcl::filter::PDFDocument aDocument;
- SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
- CPPUNIT_ASSERT(aDocument.Read(aStream));
+ load("tdf105093.odp", aDocument);
// The document has one page.
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
@@ -403,6 +384,24 @@ void PdfExportTest::testTdf106972Pdf17()
// output is PDF 1.4, and this bugdoc has PDF 1.7 data.
CPPUNIT_ASSERT(!pXObject->Lookup("Resources"));
}
+
+void PdfExportTest::testTdf107013()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("tdf107013.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ // This failed, the reference to the image was created, but not the image.
+ CPPUNIT_ASSERT(pXObject);
+}
#endif
CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
index f6ae66f28041..244379be7005 100644
--- a/vcl/source/filter/ipdf/pdfdocument.cxx
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -1748,6 +1748,36 @@ const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements()
return m_aElements;
}
+/// Visits the page tree recursively, looking for page objects.
+static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>& rRet)
+{
+ auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
+ if (!pKids)
+ {
+ SAL_WARN("vcl.filter", "visitPages: pages has no kids");
+ return;
+ }
+
+ for (const auto& pKid : pKids->GetElements())
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
+ if (!pReference)
+ continue;
+
+ PDFObjectElement* pKidObject = pReference->LookupObject();
+ if (!pKidObject)
+ continue;
+
+ auto pName = dynamic_cast<PDFNameElement*>(pKidObject->Lookup("Type"));
+ if (pName && pName->GetValue() == "Pages")
+ // Pages inside pages: recurse.
+ visitPages(pKidObject, rRet);
+ else
+ // Found an actual page.
+ rRet.push_back(pKidObject);
+ }
+}
+
std::vector<PDFObjectElement*> PDFDocument::GetPages()
{
std::vector<PDFObjectElement*> aRet;
@@ -1778,21 +1808,7 @@ std::vector<PDFObjectElement*> PDFDocument::GetPages()
return aRet;
}
- auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
- if (!pKids)
- {
- SAL_WARN("vcl.filter", "PDFDocument::GetPages: pages has no kids");
- return aRet;
- }
-
- for (const auto& pKid : pKids->GetElements())
- {
- auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
- if (!pReference)
- continue;
-
- aRet.push_back(pReference->LookupObject());
- }
+ visitPages(pPages, aRet);
return aRet;
}
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 2fd12cd0e5fb..48632f9579b5 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -11948,7 +11948,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
double fScaleX = 1.0 / aSize.Width();
double fScaleY = 1.0 / aSize.Height();
- sal_Int32 nWrappedFormObject = 0;
+ std::vector<sal_Int32> aWrappedFormObjects;
if (!m_aContext.UseReferenceXObject)
{
// Parse the PDF data, we need that to write the PDF dictionary of our
@@ -11976,68 +11976,84 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
return;
}
- filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents");
- if (!pPageContents)
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
{
- SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents");
- return;
- }
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ continue;
- nWrappedFormObject = createObject();
- // Write the form XObject wrapped below. This is a separate object from
- // the wrapper, this way there is no need to alter the stream contents.
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ continue;
+
+ aContentStreams.push_back(pObject);
+ }
+ }
- OStringBuffer aLine;
- aLine.append(nWrappedFormObject);
- aLine.append(" 0 obj\n");
- aLine.append("<< /Type /XObject");
- aLine.append(" /Subtype /Form");
- aLine.append(" /Resources <<");
- static const std::initializer_list<OString> aKeys =
- {
- "ColorSpace",
- "ExtGState",
- "Font",
- "XObject"
- };
// Maps from source object id (PDF image) to target object id (export result).
std::map<sal_Int32, sal_Int32> aCopiedResources;
- for (const auto& rKey : aKeys)
- aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
- aLine.append(">>");
- aLine.append(" /BBox [ 0 0 ");
- aLine.append(aSize.Width());
- aLine.append(" ");
- aLine.append(aSize.Height());
- aLine.append(" ]");
-
- auto pFilter = dynamic_cast<filter::PDFNameElement*>(pPageContents->Lookup("Filter"));
- if (pFilter)
+ for (auto pContent : aContentStreams)
{
- aLine.append(" /Filter /");
- aLine.append(pFilter->GetValue());
- }
+ aWrappedFormObjects.push_back(createObject());
+ // Write the form XObject wrapped below. This is a separate object from
+ // the wrapper, this way there is no need to alter the stream contents.
- aLine.append(" /Length ");
+ OStringBuffer aLine;
+ aLine.append(aWrappedFormObjects.back());
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+ aLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys =
+ {
+ "ColorSpace",
+ "ExtGState",
+ "Font",
+ "XObject"
+ };
+ for (const auto& rKey : aKeys)
+ aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
+ aLine.append(">>");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(aSize.Width());
+ aLine.append(" ");
+ aLine.append(aSize.Height());
+ aLine.append(" ]");
+
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
+ if (pFilter)
+ {
+ aLine.append(" /Filter /");
+ aLine.append(pFilter->GetValue());
+ }
- filter::PDFStreamElement* pPageStream = pPageContents->GetStream();
- if (!pPageStream)
- {
- SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
- return;
- }
+ aLine.append(" /Length ");
- SvMemoryStream& rPageStream = pPageStream->GetMemory();
+ filter::PDFStreamElement* pPageStream = pContent->GetStream();
+ if (!pPageStream)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
+ continue;
+ }
- aLine.append(static_cast<sal_Int32>(rPageStream.GetSize()));
+ SvMemoryStream& rPageStream = pPageStream->GetMemory();
- aLine.append(">>\nstream\n");
- // Copy the original page stream to the form XObject stream.
- aLine.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
- aLine.append("\nendstream\nendobj\n\n");
- if (!updateObject(nWrappedFormObject))
- return;
- CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.append(static_cast<sal_Int32>(rPageStream.GetSize()));
+
+ aLine.append(">>\nstream\n");
+ // Copy the original page stream to the form XObject stream.
+ aLine.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!updateObject(aWrappedFormObjects.back()))
+ continue;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ continue;
+ }
}
OStringBuffer aLine;
@@ -12049,12 +12065,20 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
aLine.append(" 0 obj\n");
aLine.append("<< /Type /XObject");
aLine.append(" /Subtype /Form");
- aLine.append(" /Resources << /XObject<</Im");
- sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
- aLine.append(nObject);
- aLine.append(" ");
- aLine.append(nObject);
- aLine.append(" 0 R>> >>");
+ aLine.append(" /Resources << /XObject<<");
+ for (const auto nWrappedFormObject : aWrappedFormObjects)
+ {
+ sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
+ aLine.append(" /Im");
+ aLine.append(nObject);
+ aLine.append(" ");
+ aLine.append(nObject);
+ aLine.append(" 0 R");
+
+ if (m_aContext.UseReferenceXObject)
+ break;
+ }
+ aLine.append(">> >>");
aLine.append(" /Matrix [ ");
appendDouble(fScaleX, aLine);
aLine.append(" 0 0 ");
@@ -12094,11 +12118,14 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
{
// Reset line width to the default.
aStream.append(" 1 w\n");
- // No reference XObject, draw the form XObject containing the original
- // page stream.
- aStream.append("/Im");
- aStream.append(nWrappedFormObject);
- aStream.append(" Do\n");
+ for (const auto nWrappedFormObject : aWrappedFormObjects)
+ {
+ // No reference XObject, draw the form XObject containing the original
+ // page stream.
+ aStream.append("/Im");
+ aStream.append(nWrappedFormObject);
+ aStream.append(" Do\n");
+ }
}
aStream.append("Q");
aLine.append(aStream.getLength());
More information about the Libreoffice-commits
mailing list