summaryrefslogtreecommitdiff
path: root/vcl
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.co.uk>2017-04-07 12:27:40 +0200
committerMiklos Vajna <vmiklos@collabora.co.uk>2017-04-07 13:00:15 +0000
commit4db4b9f016256fc8d2b637ed7a8f2b097aaa864b (patch)
treee59cca792bb0f896366cb24f3f3f66f054264ca2 /vcl
parent209dc36408dd5e1775db2c54b08c3e674158fd2f (diff)
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@collabora.co.uk> Tested-by: Jenkins <ci@libreoffice.org>
Diffstat (limited to 'vcl')
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf107013.odtbin0 -> 19823 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/pdfexport.cxx89
-rw-r--r--vcl/source/filter/ipdf/pdfdocument.cxx46
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx153
4 files changed, 165 insertions, 123 deletions
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
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf107013.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index 566495f38edf..31d0dfb384f2 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -39,6 +39,7 @@ public:
virtual void setUp() override;
virtual void tearDown() override;
#if HAVE_FEATURE_PDFIUM
+ void load(const OUString& rFile, vcl::filter::PDFDocument& rDocument);
/// Tests that a pdf image is roundtripped back to PDF as a vector format.
void testTdf106059();
/// Tests that text highlight from Impress is not lost.
@@ -51,6 +52,7 @@ public:
void testTdf106693();
void testTdf106972();
void testTdf106972Pdf17();
+ void testTdf107013();
#endif
CPPUNIT_TEST_SUITE(PdfExportTest);
@@ -62,6 +64,7 @@ public:
CPPUNIT_TEST(testTdf106693);
CPPUNIT_TEST(testTdf106972);
CPPUNIT_TEST(testTdf106972Pdf17);
+ CPPUNIT_TEST(testTdf107013);
#endif
CPPUNIT_TEST_SUITE_END();
};
@@ -83,6 +86,26 @@ void PdfExportTest::tearDown()
}
#if HAVE_FEATURE_PDFIUM
+
+void PdfExportTest::load(const OUString& rFile, vcl::filter::PDFDocument& rDocument)
+{
+ // Import the bugdoc and export as PDF.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + rFile;
+ mxComponent = loadFromDesktop(aURL);
+ CPPUNIT_ASSERT(mxComponent.is());
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Parse the export result.
+ SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
+ CPPUNIT_ASSERT(rDocument.Read(aStream));
+}
+
void PdfExportTest::testTdf106059()
{
// Import the bugdoc and export as PDF.
@@ -127,22 +150,8 @@ void PdfExportTest::testTdf106059()
void PdfExportTest::testTdf106693()
{
- // Import the bugdoc and export as PDF.
- OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106693.odt";
- mxComponent = loadFromDesktop(aURL);
- CPPUNIT_ASSERT(mxComponent.is());
-
- uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
- utl::TempFile aTempFile;
- aTempFile.EnableKillingFile();
- utl::MediaDescriptor aMediaDescriptor;
- aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
- xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
-
- // Parse the export result.
vcl::filter::PDFDocument aDocument;
- SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
- CPPUNIT_ASSERT(aDocument.Read(aStream));
+ load("tdf106693.odt", aDocument);
// Assert that the XObject in the page resources dictionary is a form XObject.
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
@@ -180,22 +189,8 @@ void PdfExportTest::testTdf106693()
void PdfExportTest::testTdf105461()
{
- // Import the bugdoc and export as PDF.
- OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105461.odp";
- mxComponent = loadFromDesktop(aURL);
- CPPUNIT_ASSERT(mxComponent.is());
-
- uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
- utl::TempFile aTempFile;
- aTempFile.EnableKillingFile();
- utl::MediaDescriptor aMediaDescriptor;
- aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
- xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
-
- // Parse the export result.
vcl::filter::PDFDocument aDocument;
- SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
- CPPUNIT_ASSERT(aDocument.Read(aStream));
+ load("tdf105461.odp", aDocument);
// The document has one page.
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
@@ -226,22 +221,8 @@ void PdfExportTest::testTdf105461()
void PdfExportTest::testTdf105093()
{
- // Import the bugdoc and export as PDF.
- OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105093.odp";
- mxComponent = loadFromDesktop(aURL);
- CPPUNIT_ASSERT(mxComponent.is());
-
- uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
- utl::TempFile aTempFile;
- aTempFile.EnableKillingFile();
- utl::MediaDescriptor aMediaDescriptor;
- aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
- xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
-
- // Parse the export result.
vcl::filter::PDFDocument aDocument;
- SvFileStream aStream(aTempFile.GetURL(), StreamMode::READ);
- CPPUNIT_ASSERT(aDocument.Read(aStream));
+ load("tdf105093.odp", aDocument);
// The document has one page.
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
@@ -403,6 +384,24 @@ void PdfExportTest::testTdf106972Pdf17()
// output is PDF 1.4, and this bugdoc has PDF 1.7 data.
CPPUNIT_ASSERT(!pXObject->Lookup("Resources"));
}
+
+void PdfExportTest::testTdf107013()
+{
+ vcl::filter::PDFDocument aDocument;
+ load("tdf107013.odt", aDocument);
+
+ // Get access to the only image on the only page.
+ std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
+ vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
+ CPPUNIT_ASSERT(pResources);
+ auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
+ CPPUNIT_ASSERT(pXObjects);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
+ vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
+ // This failed, the reference to the image was created, but not the image.
+ CPPUNIT_ASSERT(pXObject);
+}
#endif
CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
index 444ec9239d20..43d4248cc8ad 100644
--- a/vcl/source/filter/ipdf/pdfdocument.cxx
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -1749,6 +1749,36 @@ const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements()
return m_aElements;
}
+/// Visits the page tree recursively, looking for page objects.
+static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>& rRet)
+{
+ auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
+ if (!pKids)
+ {
+ SAL_WARN("vcl.filter", "visitPages: pages has no kids");
+ return;
+ }
+
+ for (const auto& pKid : pKids->GetElements())
+ {
+ auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
+ if (!pReference)
+ continue;
+
+ PDFObjectElement* pKidObject = pReference->LookupObject();
+ if (!pKidObject)
+ continue;
+
+ auto pName = dynamic_cast<PDFNameElement*>(pKidObject->Lookup("Type"));
+ if (pName && pName->GetValue() == "Pages")
+ // Pages inside pages: recurse.
+ visitPages(pKidObject, rRet);
+ else
+ // Found an actual page.
+ rRet.push_back(pKidObject);
+ }
+}
+
std::vector<PDFObjectElement*> PDFDocument::GetPages()
{
std::vector<PDFObjectElement*> aRet;
@@ -1779,21 +1809,7 @@ std::vector<PDFObjectElement*> PDFDocument::GetPages()
return aRet;
}
- auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
- if (!pKids)
- {
- SAL_WARN("vcl.filter", "PDFDocument::GetPages: pages has no kids");
- return aRet;
- }
-
- for (const auto& pKid : pKids->GetElements())
- {
- auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
- if (!pReference)
- continue;
-
- aRet.push_back(pReference->LookupObject());
- }
+ visitPages(pPages, aRet);
return aRet;
}
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 490f5d3b4768..65c62166aca4 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -11109,7 +11109,7 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
double fScaleX = 1.0 / aSize.Width();
double fScaleY = 1.0 / aSize.Height();
- sal_Int32 nWrappedFormObject = 0;
+ std::vector<sal_Int32> aWrappedFormObjects;
if (!m_aContext.UseReferenceXObject)
{
// Parse the PDF data, we need that to write the PDF dictionary of our
@@ -11137,68 +11137,84 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
return;
}
- filter::PDFObjectElement* pPageContents = pPage->LookupObject("Contents");
- if (!pPageContents)
+ std::vector<filter::PDFObjectElement*> aContentStreams;
+ if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
+ aContentStreams.push_back(pContentStream);
+ else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
{
- SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: page has no contents");
- return;
- }
+ for (const auto pElement : pArray->GetElements())
+ {
+ auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
+ if (!pReference)
+ continue;
- nWrappedFormObject = createObject();
- // Write the form XObject wrapped below. This is a separate object from
- // the wrapper, this way there is no need to alter the stream contents.
+ filter::PDFObjectElement* pObject = pReference->LookupObject();
+ if (!pObject)
+ continue;
+
+ aContentStreams.push_back(pObject);
+ }
+ }
- OStringBuffer aLine;
- aLine.append(nWrappedFormObject);
- aLine.append(" 0 obj\n");
- aLine.append("<< /Type /XObject");
- aLine.append(" /Subtype /Form");
- aLine.append(" /Resources <<");
- static const std::initializer_list<OString> aKeys =
- {
- "ColorSpace",
- "ExtGState",
- "Font",
- "XObject"
- };
// Maps from source object id (PDF image) to target object id (export result).
std::map<sal_Int32, sal_Int32> aCopiedResources;
- for (const auto& rKey : aKeys)
- aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
- aLine.append(">>");
- aLine.append(" /BBox [ 0 0 ");
- aLine.append(aSize.Width());
- aLine.append(" ");
- aLine.append(aSize.Height());
- aLine.append(" ]");
-
- auto pFilter = dynamic_cast<filter::PDFNameElement*>(pPageContents->Lookup("Filter"));
- if (pFilter)
+ for (auto pContent : aContentStreams)
{
- aLine.append(" /Filter /");
- aLine.append(pFilter->GetValue());
- }
+ aWrappedFormObjects.push_back(createObject());
+ // Write the form XObject wrapped below. This is a separate object from
+ // the wrapper, this way there is no need to alter the stream contents.
- aLine.append(" /Length ");
+ OStringBuffer aLine;
+ aLine.append(aWrappedFormObjects.back());
+ aLine.append(" 0 obj\n");
+ aLine.append("<< /Type /XObject");
+ aLine.append(" /Subtype /Form");
+ aLine.append(" /Resources <<");
+ static const std::initializer_list<OString> aKeys =
+ {
+ "ColorSpace",
+ "ExtGState",
+ "Font",
+ "XObject"
+ };
+ for (const auto& rKey : aKeys)
+ aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
+ aLine.append(">>");
+ aLine.append(" /BBox [ 0 0 ");
+ aLine.append(aSize.Width());
+ aLine.append(" ");
+ aLine.append(aSize.Height());
+ aLine.append(" ]");
+
+ auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
+ if (pFilter)
+ {
+ aLine.append(" /Filter /");
+ aLine.append(pFilter->GetValue());
+ }
- filter::PDFStreamElement* pPageStream = pPageContents->GetStream();
- if (!pPageStream)
- {
- SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
- return;
- }
+ aLine.append(" /Length ");
- SvMemoryStream& rPageStream = pPageStream->GetMemory();
+ filter::PDFStreamElement* pPageStream = pContent->GetStream();
+ if (!pPageStream)
+ {
+ SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
+ continue;
+ }
- aLine.append(static_cast<sal_Int32>(rPageStream.GetSize()));
+ SvMemoryStream& rPageStream = pPageStream->GetMemory();
- aLine.append(">>\nstream\n");
- // Copy the original page stream to the form XObject stream.
- aLine.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
- aLine.append("\nendstream\nendobj\n\n");
- if (!updateObject(nWrappedFormObject))
- return;
- CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
+ aLine.append(static_cast<sal_Int32>(rPageStream.GetSize()));
+
+ aLine.append(">>\nstream\n");
+ // Copy the original page stream to the form XObject stream.
+ aLine.append(static_cast<const sal_Char*>(rPageStream.GetData()), rPageStream.GetSize());
+ aLine.append("\nendstream\nendobj\n\n");
+ if (!updateObject(aWrappedFormObjects.back()))
+ continue;
+ if (!writeBuffer(aLine.getStr(), aLine.getLength()))
+ continue;
+ }
}
OStringBuffer aLine;
@@ -11210,12 +11226,20 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
aLine.append(" 0 obj\n");
aLine.append("<< /Type /XObject");
aLine.append(" /Subtype /Form");
- aLine.append(" /Resources << /XObject<</Im");
- sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
- aLine.append(nObject);
- aLine.append(" ");
- aLine.append(nObject);
- aLine.append(" 0 R>> >>");
+ aLine.append(" /Resources << /XObject<<");
+ for (const auto nWrappedFormObject : aWrappedFormObjects)
+ {
+ sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
+ aLine.append(" /Im");
+ aLine.append(nObject);
+ aLine.append(" ");
+ aLine.append(nObject);
+ aLine.append(" 0 R");
+
+ if (m_aContext.UseReferenceXObject)
+ break;
+ }
+ aLine.append(">> >>");
aLine.append(" /Matrix [ ");
appendDouble(fScaleX, aLine);
aLine.append(" 0 0 ");
@@ -11255,11 +11279,14 @@ void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
{
// Reset line width to the default.
aStream.append(" 1 w\n");
- // No reference XObject, draw the form XObject containing the original
- // page stream.
- aStream.append("/Im");
- aStream.append(nWrappedFormObject);
- aStream.append(" Do\n");
+ for (const auto nWrappedFormObject : aWrappedFormObjects)
+ {
+ // No reference XObject, draw the form XObject containing the original
+ // page stream.
+ aStream.append("/Im");
+ aStream.append(nWrappedFormObject);
+ aStream.append(" Do\n");
+ }
}
aStream.append("Q");
aLine.append(aStream.getLength());