Regression from commit 68549e00d5
(vcl,
BitmapEx transformed draw: special-case simple rotations, 2019-10-10),
the intention there was to fix an error on the last col/row of a bitmap,
but that was only tested with input where the aspect ratio doesn't
change on scaling.
Fix the problem by going back to the original way in the "aspect ratio
changes" case.
Change-Id: I52bed503ddaadbbbdf64ac6fec2fe268153866f1
Reviewed-on: https://gerrit.libreoffice.org/82467
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
1953 lines
81 KiB
C++
1953 lines
81 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
|
|
#include <memory>
|
|
#include <type_traits>
|
|
|
|
#include <config_features.h>
|
|
|
|
#include <com/sun/star/frame/Desktop.hpp>
|
|
#include <com/sun/star/frame/XStorable.hpp>
|
|
#include <com/sun/star/view/XPrintable.hpp>
|
|
#include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
|
|
#include <com/sun/star/util/XRefreshable.hpp>
|
|
#include <com/sun/star/beans/XPropertySet.hpp>
|
|
#include <com/sun/star/drawing/XShape.hpp>
|
|
#include <com/sun/star/text/XTextDocument.hpp>
|
|
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <comphelper/propertysequence.hxx>
|
|
#include <test/bootstrapfixture.hxx>
|
|
#include <unotest/macros_test.hxx>
|
|
#include <unotools/mediadescriptor.hxx>
|
|
#include <unotools/tempfile.hxx>
|
|
#include <vcl/filter/pdfdocument.hxx>
|
|
#include <tools/zcodec.hxx>
|
|
#include <fpdf_edit.h>
|
|
#include <fpdf_text.h>
|
|
#include <fpdf_doc.h>
|
|
#include <fpdfview.h>
|
|
#include <vcl/graphicfilter.hxx>
|
|
#include <basegfx/matrix/b2dhommatrix.hxx>
|
|
|
|
using namespace ::com::sun::star;
|
|
|
|
static std::ostream& operator<<(std::ostream& rStrm, const Color& rColor)
|
|
{
|
|
rStrm << "Color: R:" << static_cast<int>(rColor.GetRed())
|
|
<< " G:" << static_cast<int>(rColor.GetGreen())
|
|
<< " B:" << static_cast<int>(rColor.GetBlue())
|
|
<< " A:" << static_cast<int>(rColor.GetTransparency());
|
|
return rStrm;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
struct CloseDocument {
|
|
void operator ()(FPDF_DOCUMENT doc) {
|
|
if (doc != nullptr) {
|
|
FPDF_CloseDocument(doc);
|
|
}
|
|
}
|
|
};
|
|
|
|
using DocumentHolder =
|
|
std::unique_ptr<typename std::remove_pointer<FPDF_DOCUMENT>::type, CloseDocument>;
|
|
|
|
struct ClosePage {
|
|
void operator ()(FPDF_PAGE page) {
|
|
if (page != nullptr) {
|
|
FPDF_ClosePage(page);
|
|
}
|
|
}
|
|
};
|
|
|
|
using PageHolder =
|
|
std::unique_ptr<typename std::remove_pointer<FPDF_PAGE>::type, ClosePage>;
|
|
|
|
/// Tests the PDF export filter.
|
|
class PdfExportTest : public test::BootstrapFixture, public unotest::MacrosTest
|
|
{
|
|
uno::Reference<uno::XComponentContext> mxComponentContext;
|
|
uno::Reference<lang::XComponent> mxComponent;
|
|
utl::TempFile maTempFile;
|
|
SvMemoryStream maMemory;
|
|
// Export the document as PDF, then parse it with PDFium.
|
|
DocumentHolder exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor);
|
|
|
|
public:
|
|
PdfExportTest();
|
|
virtual void setUp() override;
|
|
virtual void tearDown() override;
|
|
void topdf(const OUString& rFile);
|
|
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.
|
|
void testTdf105461();
|
|
void testTdf107868();
|
|
/// Tests that embedded video from Impress is not exported as a linked one.
|
|
void testTdf105093();
|
|
/// Tests export of non-PDF images.
|
|
void testTdf106206();
|
|
/// Tests export of PDF images without reference XObjects.
|
|
void testTdf106693();
|
|
void testForcePoint71();
|
|
void testTdf106972();
|
|
void testTdf106972Pdf17();
|
|
void testSofthyphenPos();
|
|
void testTdf107013();
|
|
void testTdf107018();
|
|
void testTdf107089();
|
|
void testTdf99680();
|
|
void testTdf99680_2();
|
|
void testTdf108963();
|
|
void testTdf118244_radioButtonGroup();
|
|
/// Test writing ToUnicode CMAP for LTR ligatures.
|
|
void testTdf115117_1();
|
|
/// Text extracting LTR text with ligatures.
|
|
void testTdf115117_1a();
|
|
/// Test writing ToUnicode CMAP for RTL ligatures.
|
|
void testTdf115117_2();
|
|
/// Test extracting RTL text with ligatures.
|
|
void testTdf115117_2a();
|
|
/// Test writing ToUnicode CMAP for doubly encoded glyphs.
|
|
void testTdf66597_1();
|
|
/// Test writing ActualText for RTL many to one glyph to Unicode mapping.
|
|
void testTdf66597_2();
|
|
/// Test writing ActualText for LTR many to one glyph to Unicode mapping.
|
|
void testTdf66597_3();
|
|
void testTdf109143();
|
|
void testTdf105954();
|
|
void testTdf128630();
|
|
void testTdf106702();
|
|
void testTdf113143();
|
|
void testTdf115262();
|
|
void testTdf121962();
|
|
void testTdf115967();
|
|
void testTdf121615();
|
|
void testTocLink();
|
|
void testPdfImageResourceInlineXObjectRef();
|
|
|
|
CPPUNIT_TEST_SUITE(PdfExportTest);
|
|
CPPUNIT_TEST(testTdf106059);
|
|
CPPUNIT_TEST(testTdf105461);
|
|
CPPUNIT_TEST(testTdf107868);
|
|
CPPUNIT_TEST(testTdf105093);
|
|
CPPUNIT_TEST(testTdf106206);
|
|
CPPUNIT_TEST(testTdf106693);
|
|
CPPUNIT_TEST(testForcePoint71);
|
|
CPPUNIT_TEST(testTdf106972);
|
|
CPPUNIT_TEST(testTdf106972Pdf17);
|
|
CPPUNIT_TEST(testSofthyphenPos);
|
|
CPPUNIT_TEST(testTdf107013);
|
|
CPPUNIT_TEST(testTdf107018);
|
|
CPPUNIT_TEST(testTdf107089);
|
|
CPPUNIT_TEST(testTdf99680);
|
|
CPPUNIT_TEST(testTdf99680_2);
|
|
CPPUNIT_TEST(testTdf108963);
|
|
CPPUNIT_TEST(testTdf118244_radioButtonGroup);
|
|
CPPUNIT_TEST(testTdf115117_1);
|
|
CPPUNIT_TEST(testTdf115117_1a);
|
|
CPPUNIT_TEST(testTdf115117_2);
|
|
CPPUNIT_TEST(testTdf115117_2a);
|
|
CPPUNIT_TEST(testTdf66597_1);
|
|
CPPUNIT_TEST(testTdf66597_2);
|
|
CPPUNIT_TEST(testTdf66597_3);
|
|
CPPUNIT_TEST(testTdf109143);
|
|
CPPUNIT_TEST(testTdf105954);
|
|
CPPUNIT_TEST(testTdf128630);
|
|
CPPUNIT_TEST(testTdf106702);
|
|
CPPUNIT_TEST(testTdf113143);
|
|
CPPUNIT_TEST(testTdf115262);
|
|
CPPUNIT_TEST(testTdf121962);
|
|
CPPUNIT_TEST(testTdf115967);
|
|
CPPUNIT_TEST(testTdf121615);
|
|
CPPUNIT_TEST(testTocLink);
|
|
CPPUNIT_TEST(testPdfImageResourceInlineXObjectRef);
|
|
CPPUNIT_TEST_SUITE_END();
|
|
};
|
|
|
|
PdfExportTest::PdfExportTest()
|
|
{
|
|
maTempFile.EnableKillingFile();
|
|
}
|
|
|
|
DocumentHolder PdfExportTest::exportAndParse(const OUString& rURL, const utl::MediaDescriptor& rDescriptor)
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
mxComponent = loadFromDesktop(rURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
xStorable->storeToURL(maTempFile.GetURL(), rDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
maMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(
|
|
FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
return pPdfDocument;
|
|
}
|
|
|
|
void PdfExportTest::setUp()
|
|
{
|
|
test::BootstrapFixture::setUp();
|
|
|
|
mxComponentContext.set(comphelper::getComponentContext(getMultiServiceFactory()));
|
|
mxDesktop.set(frame::Desktop::create(mxComponentContext));
|
|
|
|
FPDF_LIBRARY_CONFIG config;
|
|
config.version = 2;
|
|
config.m_pUserFontPaths = nullptr;
|
|
config.m_pIsolate = nullptr;
|
|
config.m_v8EmbedderSlot = 0;
|
|
FPDF_InitLibraryWithConfig(&config);
|
|
}
|
|
|
|
void PdfExportTest::tearDown()
|
|
{
|
|
FPDF_DestroyLibrary();
|
|
|
|
if (mxComponent.is())
|
|
mxComponent->dispose();
|
|
|
|
test::BootstrapFixture::tearDown();
|
|
}
|
|
|
|
char const DATA_DIRECTORY[] = "/vcl/qa/cppunit/pdfexport/data/";
|
|
|
|
void PdfExportTest::topdf(const OUString& rFile)
|
|
{
|
|
// 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::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
}
|
|
|
|
void PdfExportTest::load(const OUString& rFile, vcl::filter::PDFDocument& rDocument)
|
|
{
|
|
topdf(rFile);
|
|
|
|
// Parse the export result.
|
|
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
|
CPPUNIT_ASSERT(rDocument.Read(aStream));
|
|
}
|
|
|
|
void PdfExportTest::testTdf106059()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106059.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
// Explicitly enable the usage of the reference XObject markup.
|
|
uno::Sequence<beans::PropertyValue> aFilterData( comphelper::InitPropertySequence({
|
|
{"UseReferenceXObject", uno::Any(true) }
|
|
}));
|
|
aMediaDescriptor["FilterData"] <<= aFilterData;
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result.
|
|
vcl::filter::PDFDocument aDocument;
|
|
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
|
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
|
|
|
// Assert that the XObject in the page resources dictionary is a reference 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* pReferenceXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
|
|
CPPUNIT_ASSERT(pReferenceXObject);
|
|
// The image is a reference XObject.
|
|
// This dictionary key was missing, so the XObject wasn't a reference one.
|
|
CPPUNIT_ASSERT(pReferenceXObject->Lookup("Ref"));
|
|
}
|
|
|
|
void PdfExportTest::testTdf106693()
|
|
{
|
|
vcl::filter::PDFDocument aDocument;
|
|
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();
|
|
// 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.
|
|
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::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// Make sure there is a filled rectangle inside.
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
int nYellowPathCount = 0;
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
|
|
continue;
|
|
|
|
unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
|
|
FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
|
|
if (Color(nRed, nGreen, nBlue) == COL_YELLOW)
|
|
++nYellowPathCount;
|
|
}
|
|
|
|
// This was 0, the page contained no yellow paths.
|
|
CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
|
|
}
|
|
|
|
void PdfExportTest::testTdf107868()
|
|
{
|
|
// No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
|
|
// which is the intent of the test.
|
|
// FIXME: Why does this fail on macOS?
|
|
#if !defined MACOSX && !defined _WIN32
|
|
|
|
// Import the bugdoc and print to PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf107868.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
|
|
CPPUNIT_ASSERT(xPrintable.is());
|
|
uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
|
|
{
|
|
{"FileName", uno::makeAny(maTempFile.GetURL())},
|
|
{"Wait", uno::makeAny(true)}
|
|
}));
|
|
xPrintable->print(aOptions);
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
if (!pPdfDocument.get())
|
|
// Printing to PDF failed in a non-interesting way, e.g. CUPS is not
|
|
// running, there is no printer defined, etc.
|
|
return;
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// Make sure there is no filled rectangle inside.
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
int nWhitePathCount = 0;
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
|
|
continue;
|
|
|
|
unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
|
|
FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
|
|
if (Color(nRed, nGreen, nBlue) == COL_WHITE)
|
|
++nWhitePathCount;
|
|
}
|
|
|
|
// This was 4, the page contained 4 white paths at problematic positions.
|
|
CPPUNIT_ASSERT_EQUAL(0, nWhitePathCount);
|
|
#endif
|
|
}
|
|
|
|
void PdfExportTest::testTdf105093()
|
|
{
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf105093.odp", aDocument);
|
|
|
|
// The document has one page.
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
|
|
// Get page annotations.
|
|
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
|
|
CPPUNIT_ASSERT(pAnnots);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size());
|
|
auto pAnnotReference = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
|
|
CPPUNIT_ASSERT(pAnnotReference);
|
|
vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
|
|
CPPUNIT_ASSERT(pAnnot);
|
|
CPPUNIT_ASSERT_EQUAL(OString("Annot"), static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"))->GetValue());
|
|
|
|
// Get the Action -> Rendition -> MediaClip -> FileSpec.
|
|
auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"));
|
|
CPPUNIT_ASSERT(pAction);
|
|
auto pRendition = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAction->LookupElement("R"));
|
|
CPPUNIT_ASSERT(pRendition);
|
|
auto pMediaClip = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pRendition->LookupElement("C"));
|
|
CPPUNIT_ASSERT(pMediaClip);
|
|
auto pFileSpec = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pMediaClip->LookupElement("D"));
|
|
CPPUNIT_ASSERT(pFileSpec);
|
|
// Make sure the filespec refers to an embedded file.
|
|
// This key was missing, the embedded video was handled as a linked one.
|
|
CPPUNIT_ASSERT(pFileSpec->LookupElement("EF"));
|
|
}
|
|
|
|
void PdfExportTest::testTdf106206()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106206.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result.
|
|
vcl::filter::PDFDocument aDocument;
|
|
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
|
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
|
|
|
// The document has one page.
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
|
|
// The page has a stream.
|
|
vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
|
|
CPPUNIT_ASSERT(pContents);
|
|
vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream& rObjectStream = pStream->GetMemory();
|
|
// Uncompress it.
|
|
SvMemoryStream aUncompressed;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
rObjectStream.Seek(0);
|
|
aZCodec.Decompress(rObjectStream, aUncompressed);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
|
|
// Make sure there is an image reference there.
|
|
OString aImage("/Im");
|
|
auto pStart = static_cast<const char*>(aUncompressed.GetData());
|
|
const char* pEnd = pStart + aUncompressed.GetSize();
|
|
auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
|
|
CPPUNIT_ASSERT(it != pEnd);
|
|
|
|
// And also that it's not an invalid one.
|
|
OString aInvalidImage("/Im0");
|
|
it = std::search(pStart, pEnd, aInvalidImage.getStr(), aInvalidImage.getStr() + aInvalidImage.getLength());
|
|
// This failed, object #0 was referenced.
|
|
CPPUNIT_ASSERT(bool(it == pEnd));
|
|
}
|
|
|
|
void PdfExportTest::testTdf109143()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf109143.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result.
|
|
vcl::filter::PDFDocument aDocument;
|
|
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
|
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
|
|
|
// The document has one page.
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
|
|
// Get access to the only image on the only page.
|
|
vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
|
|
CPPUNIT_ASSERT(pResources);
|
|
auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
|
|
CPPUNIT_ASSERT(pXObjects);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
|
|
vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
|
|
CPPUNIT_ASSERT(pXObject);
|
|
|
|
// Make sure it's re-compressed.
|
|
auto pLength = dynamic_cast<vcl::filter::PDFNumberElement*>(pXObject->Lookup("Length"));
|
|
CPPUNIT_ASSERT(pLength);
|
|
int nLength = pLength->GetValue();
|
|
// This failed: cropped TIFF-in-JPEG wasn't re-compressed, so crop was
|
|
// lost. Size was 59416, now is 11827.
|
|
CPPUNIT_ASSERT(nLength < 50000);
|
|
}
|
|
|
|
void PdfExportTest::testTdf106972()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result.
|
|
vcl::filter::PDFDocument aDocument;
|
|
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
|
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
|
|
|
// Get access to the only form object 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 only image inside the form object.
|
|
auto pFormResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"));
|
|
CPPUNIT_ASSERT(pFormResources);
|
|
auto pImages = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFormResources->LookupElement("XObject"));
|
|
CPPUNIT_ASSERT(pImages);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pImages->GetItems().size());
|
|
vcl::filter::PDFObjectElement* pImage = pImages->LookupObject(pImages->GetItems().begin()->first);
|
|
CPPUNIT_ASSERT(pImage);
|
|
|
|
// Assert resources of the image.
|
|
auto pImageResources = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pImage->Lookup("Resources"));
|
|
CPPUNIT_ASSERT(pImageResources);
|
|
// This failed: the PDF image had no Font resource.
|
|
CPPUNIT_ASSERT(pImageResources->LookupElement("Font"));
|
|
}
|
|
|
|
void PdfExportTest::testTdf106972Pdf17()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106972-pdf17.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result.
|
|
vcl::filter::PDFDocument aDocument;
|
|
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
|
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
|
|
|
// 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);
|
|
|
|
// 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::testSofthyphenPos()
|
|
{
|
|
// No need to run it on Windows, since it would use GDI printing, and not trigger PDF export
|
|
// which is the intent of the test.
|
|
// FIXME: Why does this fail on macOS?
|
|
#if !defined MACOSX && !defined _WIN32
|
|
|
|
// Import the bugdoc and print to PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "softhyphen_pdf.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
uno::Reference<view::XPrintable> xPrintable(mxComponent, uno::UNO_QUERY);
|
|
CPPUNIT_ASSERT(xPrintable.is());
|
|
uno::Sequence<beans::PropertyValue> aOptions(comphelper::InitPropertySequence(
|
|
{
|
|
{"FileName", uno::makeAny(maTempFile.GetURL())},
|
|
{"Wait", uno::makeAny(true)}
|
|
}));
|
|
xPrintable->print(aOptions);
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
if (!pPdfDocument.get())
|
|
{
|
|
// Printing to PDF failed in a non-interesting way, e.g. CUPS is not
|
|
// running, there is no printer defined, etc.
|
|
return;
|
|
}
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// tdf#96892 incorrect fractional part of font size caused soft-hyphen to
|
|
// be positioned inside preceding text (incorrect = 11.1, correct = 11.05)
|
|
|
|
// there are 3 texts currently, for line 1, soft-hyphen, line 2
|
|
bool haveText(false);
|
|
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(pPdfPageObject));
|
|
haveText = true;
|
|
double const size(FPDFTextObj_GetFontSize(pPdfPageObject));
|
|
CPPUNIT_ASSERT_DOUBLES_EQUAL(11.05, size, 1E-06);
|
|
}
|
|
|
|
CPPUNIT_ASSERT(haveText);
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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 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*>(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);
|
|
}
|
|
|
|
void PdfExportTest::testTdf99680()
|
|
{
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf99680.odt", aDocument);
|
|
|
|
// The document has one page.
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
|
|
// The page 1 has a stream.
|
|
vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
|
|
CPPUNIT_ASSERT(pContents);
|
|
vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream& rObjectStream = pStream->GetMemory();
|
|
|
|
// Uncompress it.
|
|
SvMemoryStream aUncompressed;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
rObjectStream.Seek(0);
|
|
aZCodec.Decompress(rObjectStream, aUncompressed);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
|
|
// Make sure there are no empty clipping regions.
|
|
OString aEmptyRegion("0 0 m h W* n");
|
|
auto pStart = static_cast<const char*>(aUncompressed.GetData());
|
|
const char* pEnd = pStart + aUncompressed.GetSize();
|
|
auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
|
|
|
|
// Count save graphic state (q) and restore (Q) operators
|
|
// and ensure their amount is equal
|
|
size_t nSaveCount = std::count(pStart, pEnd, 'q');
|
|
size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount, nRestoreCount);
|
|
}
|
|
|
|
void PdfExportTest::testTdf99680_2()
|
|
{
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf99680-2.odt", aDocument);
|
|
|
|
// For each document page
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aPages.size());
|
|
for (size_t nPageNr = 0; nPageNr < aPages.size(); nPageNr++)
|
|
{
|
|
// Get page contents and stream.
|
|
vcl::filter::PDFObjectElement* pContents = aPages[nPageNr]->LookupObject("Contents");
|
|
CPPUNIT_ASSERT(pContents);
|
|
vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream& rObjectStream = pStream->GetMemory();
|
|
|
|
// Uncompress the stream.
|
|
SvMemoryStream aUncompressed;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
rObjectStream.Seek(0);
|
|
aZCodec.Decompress(rObjectStream, aUncompressed);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
|
|
// Make sure there are no empty clipping regions.
|
|
OString aEmptyRegion("0 0 m h W* n");
|
|
auto pStart = static_cast<const char*>(aUncompressed.GetData());
|
|
const char* pEnd = pStart + aUncompressed.GetSize();
|
|
auto it = std::search(pStart, pEnd, aEmptyRegion.getStr(), aEmptyRegion.getStr() + aEmptyRegion.getLength());
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Empty clipping region detected!", it, pEnd);
|
|
|
|
// Count save graphic state (q) and restore (Q) operators
|
|
// and ensure their amount is equal
|
|
size_t nSaveCount = std::count(pStart, pEnd, 'q');
|
|
size_t nRestoreCount = std::count(pStart, pEnd, 'Q');
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Save/restore graphic state operators count mismatch!", nSaveCount, nRestoreCount);
|
|
}
|
|
}
|
|
|
|
void PdfExportTest::testTdf108963()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf108963.odp";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// FIXME: strangely this fails on some Win systems after a pdfium update, expected: 793.7; actual: 793
|
|
#if !defined _WIN32
|
|
// Test page size (28x15.75 cm, was 1/100th mm off, tdf#112690)
|
|
// bad: MediaBox[0 0 793.672440944882 446.428346456693]
|
|
// good: MediaBox[0 0 793.700787401575 446.456692913386]
|
|
const double aWidth = FPDF_GetPageWidth(pPdfPage.get());
|
|
CPPUNIT_ASSERT_DOUBLES_EQUAL(793.7, aWidth, 0.01);
|
|
const double aHeight = FPDF_GetPageHeight(pPdfPage.get());
|
|
CPPUNIT_ASSERT_DOUBLES_EQUAL(446.46, aHeight, 0.01);
|
|
|
|
// Make sure there is a filled rectangle inside.
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
int nYellowPathCount = 0;
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPdfPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPdfPageObject) != FPDF_PAGEOBJ_PATH)
|
|
continue;
|
|
|
|
unsigned int nRed = 0, nGreen = 0, nBlue = 0, nAlpha = 0;
|
|
FPDFPageObj_GetFillColor(pPdfPageObject, &nRed, &nGreen, &nBlue, &nAlpha);
|
|
if (Color(nRed, nGreen, nBlue) == COL_YELLOW)
|
|
{
|
|
++nYellowPathCount;
|
|
// The path described a yellow rectangle, but it was not rotated.
|
|
int nSegments = FPDFPath_CountSegments(pPdfPageObject);
|
|
CPPUNIT_ASSERT_EQUAL(5, nSegments);
|
|
FPDF_PATHSEGMENT pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 0);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(pSegment));
|
|
float fX = 0;
|
|
float fY = 0;
|
|
FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
|
|
CPPUNIT_ASSERT_EQUAL(245395, static_cast<int>(round(fX * 1000)));
|
|
CPPUNIT_ASSERT_EQUAL(244261, static_cast<int>(round(fY * 1000)));
|
|
CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
|
|
|
|
pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 1);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
|
|
FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
|
|
CPPUNIT_ASSERT_EQUAL(275102, static_cast<int>(round(fX * 1000)));
|
|
CPPUNIT_ASSERT_EQUAL(267618, static_cast<int>(round(fY * 1000)));
|
|
CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
|
|
|
|
pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 2);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
|
|
FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
|
|
CPPUNIT_ASSERT_EQUAL(287518, static_cast<int>(round(fX * 1000)));
|
|
CPPUNIT_ASSERT_EQUAL(251829, static_cast<int>(round(fY * 1000)));
|
|
CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
|
|
|
|
pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 3);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
|
|
FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
|
|
CPPUNIT_ASSERT_EQUAL(257839, static_cast<int>(round(fX * 1000)));
|
|
CPPUNIT_ASSERT_EQUAL(228472, static_cast<int>(round(fY * 1000)));
|
|
CPPUNIT_ASSERT(!FPDFPathSegment_GetClose(pSegment));
|
|
|
|
pSegment = FPDFPath_GetPathSegment(pPdfPageObject, 4);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(pSegment));
|
|
FPDFPathSegment_GetPoint(pSegment, &fX, &fY);
|
|
CPPUNIT_ASSERT_EQUAL(245395, static_cast<int>(round(fX * 1000)));
|
|
CPPUNIT_ASSERT_EQUAL(244261, static_cast<int>(round(fY * 1000)));
|
|
CPPUNIT_ASSERT(FPDFPathSegment_GetClose(pSegment));
|
|
}
|
|
}
|
|
|
|
CPPUNIT_ASSERT_EQUAL(1, nYellowPathCount);
|
|
#endif
|
|
}
|
|
|
|
void PdfExportTest::testTdf118244_radioButtonGroup()
|
|
{
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf118244_radioButtonGroup.odt", aDocument);
|
|
|
|
// The document has one page.
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
|
|
// There are eight radio buttons.
|
|
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"));
|
|
CPPUNIT_ASSERT(pAnnots);
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio buttons",static_cast<size_t>(8), pAnnots->GetElements().size());
|
|
|
|
sal_uInt32 nRadioGroups = 0;
|
|
for ( const auto& aElement : aDocument.GetElements() )
|
|
{
|
|
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
|
|
if ( !pObject )
|
|
continue;
|
|
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("FT"));
|
|
if ( pType && pType->GetValue() == "Btn" )
|
|
{
|
|
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject->Lookup("Kids"));
|
|
if ( pKids )
|
|
{
|
|
size_t expectedSize = 2;
|
|
++nRadioGroups;
|
|
if ( nRadioGroups == 3 )
|
|
expectedSize = 3;
|
|
CPPUNIT_ASSERT_EQUAL(expectedSize, pKids->GetElements().size());
|
|
}
|
|
}
|
|
}
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("# of radio groups", sal_uInt32(3), nRadioGroups);
|
|
}
|
|
|
|
// This requires Carlito font, if it is missing the test will most likely
|
|
// fail.
|
|
void PdfExportTest::testTdf115117_1()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf115117-1.odt", aDocument);
|
|
|
|
vcl::filter::PDFObjectElement* pToUnicode = nullptr;
|
|
|
|
// Get access to ToUnicode of the first font
|
|
for (const auto& aElement : aDocument.GetElements())
|
|
{
|
|
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
|
|
if (!pObject)
|
|
continue;
|
|
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
|
|
if (pType && pType->GetValue() == "Font")
|
|
{
|
|
auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
|
|
CPPUNIT_ASSERT(pToUnicodeRef);
|
|
pToUnicode = pToUnicodeRef->LookupObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CPPUNIT_ASSERT(pToUnicode);
|
|
auto pStream = pToUnicode->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream aObjectStream;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
pStream->GetMemory().Seek(0);
|
|
aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
aObjectStream.Seek(0);
|
|
// The first values, <01> <02> etc., are glyph ids, they might change order
|
|
// if we changed how font subsets are created.
|
|
// The second values, <00740069> etc., are Unicode code points in hex,
|
|
// <00740069> is U+0074 and U+0069 i.e. "ti" which is a ligature in
|
|
// Carlito/Calibri. This test is failing if any of the second values
|
|
// changed which means we are not detecting ligatures and writing CMAP
|
|
// entries for them correctly. If glyph order in the subset changes then
|
|
// the order here will changes and the PDF has to be carefully inspected to
|
|
// ensure that the new values are correct before updating the string below.
|
|
OString aCmap("9 beginbfchar\n"
|
|
"<01> <00740069>\n"
|
|
"<02> <0020>\n"
|
|
"<03> <0074>\n"
|
|
"<04> <0065>\n"
|
|
"<05> <0073>\n"
|
|
"<06> <00660069>\n"
|
|
"<07> <0066006C>\n"
|
|
"<08> <006600660069>\n"
|
|
"<09> <00660066006C>\n"
|
|
"endbfchar");
|
|
auto pStart = static_cast<const char*>(aObjectStream.GetData());
|
|
const char* pEnd = pStart + aObjectStream.GetSize();
|
|
auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
|
|
CPPUNIT_ASSERT(it != pEnd);
|
|
#endif
|
|
}
|
|
|
|
// This requires DejaVu Sans font, if it is missing the test will most likely
|
|
// fail.
|
|
void PdfExportTest::testTdf115117_2()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
// See the comments in testTdf115117_1() for explanation.
|
|
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf115117-2.odt", aDocument);
|
|
|
|
vcl::filter::PDFObjectElement* pToUnicode = nullptr;
|
|
|
|
for (const auto& aElement : aDocument.GetElements())
|
|
{
|
|
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
|
|
if (!pObject)
|
|
continue;
|
|
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
|
|
if (pType && pType->GetValue() == "Font")
|
|
{
|
|
auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
|
|
CPPUNIT_ASSERT(pToUnicodeRef);
|
|
pToUnicode = pToUnicodeRef->LookupObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CPPUNIT_ASSERT(pToUnicode);
|
|
auto pStream = pToUnicode->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream aObjectStream;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
pStream->GetMemory().Seek(0);
|
|
aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
aObjectStream.Seek(0);
|
|
OString aCmap("7 beginbfchar\n"
|
|
"<01> <06440627>\n"
|
|
"<02> <0020>\n"
|
|
"<03> <0641>\n"
|
|
"<04> <0642>\n"
|
|
"<05> <0648>\n"
|
|
"<06> <06440627>\n"
|
|
"<07> <0628>\n"
|
|
"endbfchar");
|
|
auto pStart = static_cast<const char*>(aObjectStream.GetData());
|
|
const char* pEnd = pStart + aObjectStream.GetSize();
|
|
auto it = std::search(pStart, pEnd, aCmap.getStr(), aCmap.getStr() + aCmap.getLength());
|
|
CPPUNIT_ASSERT(it != pEnd);
|
|
#endif
|
|
}
|
|
|
|
void PdfExportTest::testTdf115117_1a()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-1.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
auto pPdfTextPage = FPDFText_LoadPage(pPdfPage.get());
|
|
CPPUNIT_ASSERT(pPdfTextPage);
|
|
|
|
// Extract the text from the page. This pdfium API is a bit higher level
|
|
// than we want and might apply heuristic that give false positive, but it
|
|
// is a good approximation in addition to the check in testTdf115117_1().
|
|
int nChars = FPDFText_CountChars(pPdfTextPage);
|
|
CPPUNIT_ASSERT_EQUAL(44, nChars);
|
|
|
|
OUString aExpectedText = "ti ti test ti\r\nti test fi fl ffi ffl test fi";
|
|
std::vector<sal_uInt32> aChars(nChars);
|
|
for (int i = 0; i < nChars; i++)
|
|
aChars[i] = FPDFText_GetUnicode(pPdfTextPage, i);
|
|
OUString aActualText(aChars.data(), aChars.size());
|
|
CPPUNIT_ASSERT_EQUAL(aExpectedText, aActualText);
|
|
#endif
|
|
}
|
|
|
|
void PdfExportTest::testTdf115117_2a()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
// See the comments in testTdf115117_1a() for explanation.
|
|
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115117-2.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
auto pPdfTextPage = FPDFText_LoadPage(pPdfPage.get());
|
|
CPPUNIT_ASSERT(pPdfTextPage);
|
|
|
|
int nChars = FPDFText_CountChars(pPdfTextPage);
|
|
CPPUNIT_ASSERT_EQUAL(13, nChars);
|
|
|
|
OUString aExpectedText = u"\u0627\u0644 \u0628\u0627\u0644 \u0648\u0642\u0641 \u0627\u0644";
|
|
std::vector<sal_uInt32> aChars(nChars);
|
|
for (int i = 0; i < nChars; i++)
|
|
aChars[i] = FPDFText_GetUnicode(pPdfTextPage, i);
|
|
OUString aActualText(aChars.data(), aChars.size());
|
|
CPPUNIT_ASSERT_EQUAL(aExpectedText, aActualText);
|
|
#endif
|
|
}
|
|
|
|
void PdfExportTest::testTdf66597_1()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
// This requires Amiri font, if it is missing the test will fail.
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf66597-1.odt", aDocument);
|
|
|
|
{
|
|
// Get access to ToUnicode of the first font
|
|
vcl::filter::PDFObjectElement* pToUnicode = nullptr;
|
|
for (const auto& aElement : aDocument.GetElements())
|
|
{
|
|
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
|
|
if (!pObject)
|
|
continue;
|
|
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
|
|
if (pType && pType->GetValue() == "Font")
|
|
{
|
|
auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
|
|
auto aName = pName->GetValue().copy(7); // skip the subset id
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("Amiri-Regular"), aName);
|
|
|
|
auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
|
|
CPPUNIT_ASSERT(pToUnicodeRef);
|
|
pToUnicode = pToUnicodeRef->LookupObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CPPUNIT_ASSERT(pToUnicode);
|
|
auto pStream = pToUnicode->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream aObjectStream;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
pStream->GetMemory().Seek(0);
|
|
aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
aObjectStream.Seek(0);
|
|
// The <01> is glyph id, <0020> is code point.
|
|
// The document has three characters <space><nbspace><space>, but the font
|
|
// reuses the same glyph for space and nbspace so we should have a single
|
|
// CMAP entry for the space, and nbspace will be handled with ActualText
|
|
// (tested above).
|
|
std::string aCmap("1 beginbfchar\n"
|
|
"<01> <0020>\n"
|
|
"endbfchar");
|
|
std::string aData(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
|
|
auto nPos = aData.find(aCmap);
|
|
CPPUNIT_ASSERT(nPos != std::string::npos);
|
|
}
|
|
|
|
{
|
|
auto aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
// Get page contents and stream.
|
|
auto pContents = aPages[0]->LookupObject("Contents");
|
|
CPPUNIT_ASSERT(pContents);
|
|
auto pStream = pContents->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
auto& rObjectStream = pStream->GetMemory();
|
|
|
|
// Uncompress the stream.
|
|
SvMemoryStream aUncompressed;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
rObjectStream.Seek(0);
|
|
aZCodec.Decompress(rObjectStream, aUncompressed);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
|
|
// Make sure the expected ActualText is present.
|
|
std::string aData(static_cast<const char*>(aUncompressed.GetData()), aUncompressed.GetSize());
|
|
|
|
std::string aActualText("/Span<</ActualText<");
|
|
size_t nCount = 0;
|
|
size_t nPos = 0;
|
|
while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
|
|
{
|
|
nCount++;
|
|
nPos += aActualText.length();
|
|
}
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("The should be one ActualText entry!", static_cast<size_t>(1), nCount);
|
|
|
|
aActualText = "/Span<</ActualText<FEFF00A0>>>";
|
|
nPos = aData.find(aActualText);
|
|
CPPUNIT_ASSERT_MESSAGE("ActualText not found!", nPos != std::string::npos);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// This requires Reem Kufi font, if it is missing the test will fail.
|
|
void PdfExportTest::testTdf66597_2()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf66597-2.odt", aDocument);
|
|
|
|
{
|
|
// Get access to ToUnicode of the first font
|
|
vcl::filter::PDFObjectElement* pToUnicode = nullptr;
|
|
for (const auto& aElement : aDocument.GetElements())
|
|
{
|
|
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
|
|
if (!pObject)
|
|
continue;
|
|
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
|
|
if (pType && pType->GetValue() == "Font")
|
|
{
|
|
auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
|
|
auto aName = pName->GetValue().copy(7); // skip the subset id
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("ReemKufi-Regular"), aName);
|
|
|
|
auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
|
|
CPPUNIT_ASSERT(pToUnicodeRef);
|
|
pToUnicode = pToUnicodeRef->LookupObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CPPUNIT_ASSERT(pToUnicode);
|
|
auto pStream = pToUnicode->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream aObjectStream;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
pStream->GetMemory().Seek(0);
|
|
aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
aObjectStream.Seek(0);
|
|
std::string aCmap("8 beginbfchar\n"
|
|
"<02> <0632>\n"
|
|
"<03> <0020>\n"
|
|
"<04> <0648>\n"
|
|
"<05> <0647>\n"
|
|
"<06> <062F>\n"
|
|
"<08> <062C>\n"
|
|
"<09> <0628>\n"
|
|
"<0B> <0623>\n"
|
|
"endbfchar");
|
|
std::string aData(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
|
|
auto nPos = aData.find(aCmap);
|
|
CPPUNIT_ASSERT(nPos != std::string::npos);
|
|
}
|
|
|
|
{
|
|
auto aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
// Get page contents and stream.
|
|
auto pContents = aPages[0]->LookupObject("Contents");
|
|
CPPUNIT_ASSERT(pContents);
|
|
auto pStream = pContents->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
auto& rObjectStream = pStream->GetMemory();
|
|
|
|
// Uncompress the stream.
|
|
SvMemoryStream aUncompressed;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
rObjectStream.Seek(0);
|
|
aZCodec.Decompress(rObjectStream, aUncompressed);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
|
|
// Make sure the expected ActualText is present.
|
|
std::string aData(static_cast<const char*>(aUncompressed.GetData()), aUncompressed.GetSize());
|
|
|
|
std::vector<std::string> aCodes({ "0632", "062C", "0628", "0623" });
|
|
std::string aActualText("/Span<</ActualText<");
|
|
size_t nCount = 0;
|
|
size_t nPos = 0;
|
|
while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
|
|
{
|
|
nCount++;
|
|
nPos += aActualText.length();
|
|
}
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", aCodes.size(), nCount);
|
|
|
|
for (const auto& aCode : aCodes)
|
|
{
|
|
aActualText = "/Span<</ActualText<FEFF" + aCode + ">>>";
|
|
nPos = aData.find(aActualText);
|
|
CPPUNIT_ASSERT_MESSAGE("ActualText not found for " + aCode, nPos != std::string::npos);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// This requires Gentium Basic font, if it is missing the test will fail.
|
|
void PdfExportTest::testTdf66597_3()
|
|
{
|
|
#if HAVE_MORE_FONTS
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf66597-3.odt", aDocument);
|
|
|
|
{
|
|
// Get access to ToUnicode of the first font
|
|
vcl::filter::PDFObjectElement* pToUnicode = nullptr;
|
|
for (const auto& aElement : aDocument.GetElements())
|
|
{
|
|
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(aElement.get());
|
|
if (!pObject)
|
|
continue;
|
|
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"));
|
|
if (pType && pType->GetValue() == "Font")
|
|
{
|
|
auto pName = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"));
|
|
auto aName = pName->GetValue().copy(7); // skip the subset id
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected font name", OString("GentiumBasic"), aName);
|
|
|
|
auto pToUnicodeRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("ToUnicode"));
|
|
CPPUNIT_ASSERT(pToUnicodeRef);
|
|
pToUnicode = pToUnicodeRef->LookupObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CPPUNIT_ASSERT(pToUnicode);
|
|
auto pStream = pToUnicode->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream aObjectStream;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
pStream->GetMemory().Seek(0);
|
|
aZCodec.Decompress(pStream->GetMemory(), aObjectStream);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
aObjectStream.Seek(0);
|
|
std::string aCmap("2 beginbfchar\n"
|
|
"<01> <1ECB0331030B>\n"
|
|
"<05> <0020>\n"
|
|
"endbfchar");
|
|
std::string aData(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
|
|
auto nPos = aData.find(aCmap);
|
|
CPPUNIT_ASSERT(nPos != std::string::npos);
|
|
}
|
|
|
|
{
|
|
auto aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
// Get page contents and stream.
|
|
auto pContents = aPages[0]->LookupObject("Contents");
|
|
CPPUNIT_ASSERT(pContents);
|
|
auto pStream = pContents->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
auto& rObjectStream = pStream->GetMemory();
|
|
|
|
// Uncompress the stream.
|
|
SvMemoryStream aUncompressed;
|
|
ZCodec aZCodec;
|
|
aZCodec.BeginCompression();
|
|
rObjectStream.Seek(0);
|
|
aZCodec.Decompress(rObjectStream, aUncompressed);
|
|
CPPUNIT_ASSERT(aZCodec.EndCompression());
|
|
|
|
// Make sure the expected ActualText is present.
|
|
std::string aData(static_cast<const char*>(aUncompressed.GetData()), aUncompressed.GetSize());
|
|
|
|
std::string aActualText("/Span<</ActualText<FEFF1ECB0331030B>>>");
|
|
size_t nCount = 0;
|
|
size_t nPos = 0;
|
|
while ((nPos = aData.find(aActualText, nPos)) != std::string::npos)
|
|
{
|
|
nCount++;
|
|
nPos += aActualText.length();
|
|
}
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Number of ActualText entries does not match!", static_cast<size_t>(4), nCount);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void PdfExportTest::testTdf105954()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf105954.odt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
|
|
{ { "ReduceImageResolution", uno::Any(true) },
|
|
{ "MaxImageResolution", uno::Any(static_cast<sal_Int32>(300)) } }));
|
|
aMediaDescriptor["FilterData"] <<= aFilterData;
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Parse the export result with pdfium.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
SvMemoryStream aMemory;
|
|
aMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(
|
|
FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// There is a single image on the page.
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
CPPUNIT_ASSERT_EQUAL(1, nPageObjectCount);
|
|
|
|
// Check width of the image.
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), /*index=*/0);
|
|
FPDF_IMAGEOBJ_METADATA aMeta;
|
|
CPPUNIT_ASSERT(FPDFImageObj_GetImageMetadata(pPageObject, pPdfPage.get(), &aMeta));
|
|
// This was 2000, i.e. the 'reduce to 300 DPI' request was ignored.
|
|
// This is now around 238 (228 on macOS).
|
|
CPPUNIT_ASSERT_LESS(static_cast<unsigned int>(250), aMeta.width);
|
|
}
|
|
|
|
void PdfExportTest::testTdf128630()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf128630.odp";
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
|
|
DocumentHolder pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
|
|
|
|
// The document has one page.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// Assert the aspect ratio of the only bitmap on the page.
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
|
|
continue;
|
|
|
|
FPDF_BITMAP pBitmap = FPDFImageObj_GetBitmap(pPageObject);
|
|
CPPUNIT_ASSERT(pBitmap);
|
|
int nWidth = FPDFBitmap_GetWidth(pBitmap);
|
|
int nHeight = FPDFBitmap_GetHeight(pBitmap);
|
|
FPDFBitmap_Destroy(pBitmap);
|
|
// Without the accompanying fix in place, this test would have failed with:
|
|
// assertion failed
|
|
// - Expression: nWidth != nHeight
|
|
// i.e. the bitmap lost its custom aspect ratio during export.
|
|
CPPUNIT_ASSERT(nWidth != nHeight);
|
|
}
|
|
}
|
|
|
|
void PdfExportTest::testTdf106702()
|
|
{
|
|
// Import the bugdoc and export as PDF.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf106702.odt";
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
|
|
|
|
// The document has two pages.
|
|
CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// First page already has the correct image position.
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
int nExpected = 0;
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
|
|
continue;
|
|
|
|
float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
|
|
FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
|
|
nExpected = fTop;
|
|
break;
|
|
}
|
|
|
|
// Second page had an incorrect image position.
|
|
pPdfPage.reset(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
int nActual = 0;
|
|
nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
|
|
continue;
|
|
|
|
float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
|
|
FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
|
|
nActual = fTop;
|
|
break;
|
|
}
|
|
|
|
// This failed, vertical pos is 818 points, was 1674 (outside visible page
|
|
// bounds).
|
|
CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
|
|
}
|
|
|
|
void PdfExportTest::testTdf113143()
|
|
{
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf113143.odp";
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("impress_pdf_Export");
|
|
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
|
|
{ "ExportNotesPages", uno::Any(true) },
|
|
// ReduceImageResolution is on by default and that hides the bug we
|
|
// want to test.
|
|
{ "ReduceImageResolution", uno::Any(false) },
|
|
// Set a custom PDF version.
|
|
{ "SelectPdfVersion", uno::makeAny(static_cast<sal_Int32>(16)) },
|
|
}));
|
|
aMediaDescriptor["FilterData"] <<= aFilterData;
|
|
auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
|
|
|
|
// The document has two pages.
|
|
CPPUNIT_ASSERT_EQUAL(2, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// First has the original (larger) image.
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
int nLarger = 0;
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
|
|
continue;
|
|
|
|
float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
|
|
FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
|
|
nLarger = fRight - fLeft;
|
|
break;
|
|
}
|
|
|
|
// Second page has the scaled (smaller) image.
|
|
pPdfPage.reset(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
int nSmaller = 0;
|
|
nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_IMAGE)
|
|
continue;
|
|
|
|
float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
|
|
FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
|
|
nSmaller = fRight - fLeft;
|
|
break;
|
|
}
|
|
|
|
// This failed, both were 319, now nSmaller is 169.
|
|
CPPUNIT_ASSERT_LESS(nLarger, nSmaller);
|
|
|
|
// The following check used to fail in the past, header was "%PDF-1.5":
|
|
maMemory.Seek(0);
|
|
OString aExpectedHeader("%PDF-1.6");
|
|
OString aHeader(read_uInt8s_ToOString(maMemory, aExpectedHeader.getLength()));
|
|
CPPUNIT_ASSERT_EQUAL(aExpectedHeader, aHeader);
|
|
}
|
|
|
|
void PdfExportTest::testForcePoint71()
|
|
{
|
|
// I just care it doesn't crash
|
|
topdf("forcepoint71.key");
|
|
}
|
|
|
|
void PdfExportTest::testTdf115262()
|
|
{
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115262.ods";
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("calc_pdf_Export");
|
|
auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
|
|
CPPUNIT_ASSERT_EQUAL(8, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// Get the 6th page.
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/5));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// Look up the position of the first image and the 400th row.
|
|
FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
int nFirstImageTop = 0;
|
|
int nRowTop = 0;
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
float fLeft = 0, fBottom = 0, fRight = 0, fTop = 0;
|
|
FPDFPageObj_GetBounds(pPageObject, &fLeft, &fBottom, &fRight, &fTop);
|
|
|
|
if (FPDFPageObj_GetType(pPageObject) == FPDF_PAGEOBJ_IMAGE)
|
|
{
|
|
nFirstImageTop = fTop;
|
|
}
|
|
else if (FPDFPageObj_GetType(pPageObject) == FPDF_PAGEOBJ_TEXT)
|
|
{
|
|
unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0);
|
|
std::vector<sal_Unicode> aText(nTextSize);
|
|
FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
|
|
OUString sText(aText.data(), nTextSize / 2 - 1);
|
|
if (sText == "400")
|
|
nRowTop = fTop;
|
|
}
|
|
}
|
|
// Make sure that the top of the "400" is below the top of the image (in
|
|
// bottom-right-corner-based PDF coordinates).
|
|
// This was: expected less than 144, actual is 199.
|
|
CPPUNIT_ASSERT_LESS(nFirstImageTop, nRowTop);
|
|
FPDFText_ClosePage(pTextPage);
|
|
}
|
|
|
|
void PdfExportTest::testTdf121962()
|
|
{
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf121962.odt";
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// Get the first page
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
|
|
|
|
// Make sure the table sum is displayed as "0", not faulty expression.
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_TEXT)
|
|
continue;
|
|
unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 0);
|
|
std::vector<sal_Unicode> aText(nTextSize);
|
|
FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
|
|
OUString sText(aText.data(), nTextSize / 2 - 1);
|
|
CPPUNIT_ASSERT(sText != "** Expression is faulty **");
|
|
}
|
|
|
|
}
|
|
|
|
void PdfExportTest::testTdf115967()
|
|
{
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf115967.odt";
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
auto pPdfDocument = exportAndParse(aURL, aMediaDescriptor);
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// Get the first page
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
FPDF_TEXTPAGE pTextPage = FPDFText_LoadPage(pPdfPage.get());
|
|
|
|
// Make sure the elements inside a formula in a RTL document are exported
|
|
// LTR ( m=750abc ) and not RTL ( m=057cba )
|
|
int nPageObjectCount = FPDFPage_CountObjects(pPdfPage.get());
|
|
OUString sText;
|
|
for (int i = 0; i < nPageObjectCount; ++i)
|
|
{
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), i);
|
|
if (FPDFPageObj_GetType(pPageObject) != FPDF_PAGEOBJ_TEXT)
|
|
continue;
|
|
unsigned long nTextSize = FPDFTextObj_GetText(pPageObject, pTextPage, nullptr, 2);
|
|
std::vector<sal_Unicode> aText(nTextSize);
|
|
FPDFTextObj_GetText(pPageObject, pTextPage, aText.data(), nTextSize);
|
|
OUString sChar(aText.data(), nTextSize / 2 - 1);
|
|
sText += sChar.trim();
|
|
}
|
|
CPPUNIT_ASSERT_EQUAL(OUString("m=750abc"), sText);
|
|
}
|
|
|
|
void PdfExportTest::testTdf121615()
|
|
{
|
|
vcl::filter::PDFDocument aDocument;
|
|
load("tdf121615.odt", aDocument);
|
|
|
|
// The document has one page.
|
|
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
|
|
|
|
// Get access to the only image on the only page.
|
|
vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources");
|
|
CPPUNIT_ASSERT(pResources);
|
|
auto pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"));
|
|
CPPUNIT_ASSERT(pXObjects);
|
|
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pXObjects->GetItems().size());
|
|
vcl::filter::PDFObjectElement* pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
|
|
CPPUNIT_ASSERT(pXObject);
|
|
vcl::filter::PDFStreamElement* pStream = pXObject->GetStream();
|
|
CPPUNIT_ASSERT(pStream);
|
|
SvMemoryStream& rObjectStream = pStream->GetMemory();
|
|
|
|
// Load the embedded image.
|
|
rObjectStream.Seek( 0 );
|
|
GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
|
|
Graphic aGraphic;
|
|
sal_uInt16 format;
|
|
ErrCode bResult = rFilter.ImportGraphic(aGraphic, OUString( "import" ), rObjectStream,
|
|
GRFILTER_FORMAT_DONTKNOW, &format);
|
|
CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
|
|
|
|
// The image should be grayscale 8bit JPEG.
|
|
sal_uInt16 jpegFormat = rFilter.GetImportFormatNumberForShortName( JPG_SHORTNAME );
|
|
CPPUNIT_ASSERT( jpegFormat != GRFILTER_FORMAT_NOTFOUND );
|
|
CPPUNIT_ASSERT_EQUAL( jpegFormat, format );
|
|
BitmapEx aBitmap = aGraphic.GetBitmapEx();
|
|
CPPUNIT_ASSERT_EQUAL( 200L, aBitmap.GetSizePixel().Width());
|
|
CPPUNIT_ASSERT_EQUAL( 300L, aBitmap.GetSizePixel().Height());
|
|
CPPUNIT_ASSERT_EQUAL( 8, int(aBitmap.GetBitCount()));
|
|
// tdf#121615 was caused by broken handling of data width with 8bit color,
|
|
// so the test image has some black in the bottomright corner, check it's there
|
|
CPPUNIT_ASSERT_EQUAL( COL_WHITE, aBitmap.GetPixelColor( 0, 0 ));
|
|
CPPUNIT_ASSERT_EQUAL( COL_WHITE, aBitmap.GetPixelColor( 0, 299 ));
|
|
CPPUNIT_ASSERT_EQUAL( COL_WHITE, aBitmap.GetPixelColor( 199, 0 ));
|
|
CPPUNIT_ASSERT_EQUAL( COL_BLACK, aBitmap.GetPixelColor( 199, 299 ));
|
|
}
|
|
|
|
void PdfExportTest::testTocLink()
|
|
{
|
|
// Load the Writer document.
|
|
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "toc-link.fodt";
|
|
mxComponent = loadFromDesktop(aURL);
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
|
|
// Update the ToC.
|
|
uno::Reference<text::XDocumentIndexesSupplier> xDocumentIndexesSupplier(mxComponent,
|
|
uno::UNO_QUERY);
|
|
CPPUNIT_ASSERT(xDocumentIndexesSupplier.is());
|
|
|
|
uno::Reference<util::XRefreshable> xToc(
|
|
xDocumentIndexesSupplier->getDocumentIndexes()->getByIndex(0), uno::UNO_QUERY);
|
|
CPPUNIT_ASSERT(xToc.is());
|
|
|
|
xToc->refresh();
|
|
|
|
// Save as PDF.
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
maMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(
|
|
FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
|
|
// Ensure there is a link on the first page (in the ToC).
|
|
int nStartPos = 0;
|
|
FPDF_LINK pLinkAnnot = nullptr;
|
|
// Without the accompanying fix in place, this test would have failed, as FPDFLink_Enumerate()
|
|
// returned false, as the page contained no links.
|
|
CPPUNIT_ASSERT(FPDFLink_Enumerate(pPdfPage.get(), &nStartPos, &pLinkAnnot));
|
|
}
|
|
|
|
void PdfExportTest::testPdfImageResourceInlineXObjectRef()
|
|
{
|
|
// Create an empty document.
|
|
mxComponent = loadFromDesktop("private:factory/swriter");
|
|
CPPUNIT_ASSERT(mxComponent.is());
|
|
uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
|
|
uno::Reference<text::XText> xText = xTextDocument->getText();
|
|
uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
|
|
|
|
// Insert the PDF image.
|
|
uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
|
|
uno::Reference<beans::XPropertySet> xGraphicObject(
|
|
xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
|
|
OUString aURL
|
|
= m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-image-resource-inline-xobject-ref.pdf";
|
|
xGraphicObject->setPropertyValue("GraphicURL", uno::makeAny(aURL));
|
|
uno::Reference<drawing::XShape> xShape(xGraphicObject, uno::UNO_QUERY);
|
|
xShape->setSize(awt::Size(1000, 1000));
|
|
uno::Reference<text::XTextContent> xTextContent(xGraphicObject, uno::UNO_QUERY);
|
|
xText->insertTextContent(xCursor->getStart(), xTextContent, /*bAbsorb=*/false);
|
|
|
|
// Save as PDF.
|
|
uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
|
|
utl::MediaDescriptor aMediaDescriptor;
|
|
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
|
|
xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
|
|
|
|
// Init pdfium, vcl::ImportPDF() calls FPDF_DestroyLibrary after our setUp().
|
|
FPDF_LIBRARY_CONFIG config;
|
|
config.version = 2;
|
|
config.m_pUserFontPaths = nullptr;
|
|
config.m_pIsolate = nullptr;
|
|
config.m_v8EmbedderSlot = 0;
|
|
FPDF_InitLibraryWithConfig(&config);
|
|
|
|
// Parse the export result.
|
|
SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ);
|
|
maMemory.WriteStream(aFile);
|
|
DocumentHolder pPdfDocument(
|
|
FPDF_LoadMemDocument(maMemory.GetData(), maMemory.GetSize(), /*password=*/nullptr));
|
|
CPPUNIT_ASSERT(pPdfDocument.get());
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDF_GetPageCount(pPdfDocument.get()));
|
|
|
|
// Make sure that the page -> form -> form has a child image.
|
|
PageHolder pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/0));
|
|
CPPUNIT_ASSERT(pPdfPage.get());
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDFPage_CountObjects(pPdfPage.get()));
|
|
FPDF_PAGEOBJECT pPageObject = FPDFPage_GetObject(pPdfPage.get(), 0);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pPageObject));
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pPageObject));
|
|
FPDF_PAGEOBJECT pFormObject = FPDFFormObj_GetObject(pPageObject, 0);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pFormObject));
|
|
// Without the accompanying fix in place, this test would have failed with:
|
|
// - Expected: 1
|
|
// - Actual : 0
|
|
// i.e. the sub-form was missing its image.
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pFormObject));
|
|
|
|
// Check if the inner form object (original page object in the pdf image) has the correct
|
|
// rotation.
|
|
FPDF_PAGEOBJECT pInnerFormObject = FPDFFormObj_GetObject(pFormObject, 0);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(pInnerFormObject));
|
|
CPPUNIT_ASSERT_EQUAL(1, FPDFFormObj_CountObjects(pInnerFormObject));
|
|
FPDF_PAGEOBJECT pImage = FPDFFormObj_GetObject(pInnerFormObject, 0);
|
|
CPPUNIT_ASSERT_EQUAL(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(pImage));
|
|
double fA = 0;
|
|
double fB = 0;
|
|
double fC = 0;
|
|
double fD = 0;
|
|
double fE = 0;
|
|
double fF = 0;
|
|
FPDFFormObj_GetMatrix(pInnerFormObject, &fA, &fB, &fC, &fD, &fE, &fF);
|
|
basegfx::B2DHomMatrix aMat{ fA, fC, fE, fB, fD, fF };
|
|
basegfx::B2DTuple aScale;
|
|
basegfx::B2DTuple aTranslate;
|
|
double fRotate = 0;
|
|
double fShearX = 0;
|
|
aMat.decompose(aScale, aTranslate, fRotate, fShearX);
|
|
int nRotateDeg = basegfx::rad2deg(fRotate);
|
|
// Without the accompanying fix in place, this test would have failed with:
|
|
// - Expected: -90
|
|
// - Actual : 0
|
|
// i.e. rotation was lost on pdf export.
|
|
CPPUNIT_ASSERT_EQUAL(-90, nRotateDeg);
|
|
}
|
|
|
|
CPPUNIT_TEST_SUITE_REGISTRATION(PdfExportTest);
|
|
|
|
}
|
|
|
|
CPPUNIT_PLUGIN_IMPLEMENT();
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|