Files
odfstudio/vcl/qa/cppunit/pdfexport/pdfexport3.cxx
T
Miklos Vajna fe590de0fe pdfium: update to 8073
Patches:
- external/pdfium/extractpatterns-fill.patch.1 is now upstream
- external/pdfium/extractpatterns-stroke.patch.1 as well
- external/pdfium/fix-windows-build.patch.1 as well
- external/pdfium/system-icu.patch.1 as well
- external/pdfium/system-dragonbox.diff is a new dependency, patch the
  code to use our bundled dragonbox
- update external/pdfium/system-abseil.diff to apply again

Adapted tests:
- CppunitTest_vcl_pdfexport goes back to its previous expected values
  after pdfium.git commit 17cf94b52e01507108da6460ebca6b48995a698e
  (Revert "Fix mixed-direction line extraction", 2026-09-17)
- CppunitTest_vcl_pdfexport2 as well

Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
Change-Id: I09bf5fc9f54b697eed2e144c3f57c3b72f3bbc37
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13508
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 0272de3a9b98d0c3ef4479e0dc12abe8cc040447)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211551
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-30 08:42:24 +02:00

3670 lines
160 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* -*- 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 <string_view>
#include <config_fonts.h>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <comphelper/propertysequence.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <unotools/tempfile.hxx>
#include <vcl/filter/pdfdocument.hxx>
#include <tools/zcodec.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <rtl/math.hxx>
#include <o3tl/string_view.hxx>
#include <IDocumentDeviceAccess.hxx>
#include <printdata.hxx>
#include <unotxdoc.hxx>
#include <doc.hxx>
#include <docsh.hxx>
#include <vcl/filter/PDFiumLibrary.hxx>
#include <vcl/pdf/PDFFormFieldType.hxx>
#include <vcl/pdf/PDFPageObjectType.hxx>
#include <vcl/pdfread.hxx>
#include <comphelper/propertyvalue.hxx>
#include <cmath>
#include <set>
static std::ostream& operator<<(std::ostream& rStream, const std::set<rtl::OString>& rSet);
#include <test/unoapi_test.hxx>
using namespace ::com::sun::star;
static std::ostream& operator<<(std::ostream& rStream, const std::set<OString>& rSet)
{
rStream << "{ ";
for (auto it = rSet.begin(); it != rSet.end(); ++it)
{
if (it != rSet.begin())
rStream << ", ";
rStream << *it;
}
rStream << " }";
return rStream;
}
namespace
{
/// Tests the PDF export filter.
class PdfExportTest3 : public UnoApiTest
{
public:
PdfExportTest3()
: UnoApiTest(u"/vcl/qa/cppunit/pdfexport/data/"_ustr)
{
}
};
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf157397)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
{ "PDFUACompliance", uno::Any(true) },
{ "ExportFormFields", uno::Any(true) },
}));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"PDF_export_with_formcontrol.fodt");
skipValidation();
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
vcl::filter::PDFObjectElement* pDocument(nullptr);
for (auto* pObject1 : aDocument.GetObjects())
{
auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
if (pType1 && pType1->GetValue() == "StructElem")
{
auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
if (pS1 && pS1->GetValue() == "Document")
{
pDocument = pObject1;
}
}
}
CPPUNIT_ASSERT(pDocument);
auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pDocument->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids1);
// assume there are no MCID ref at this level
auto pKids1v = pKids1->GetElements();
auto pRefKid12 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[2]);
CPPUNIT_ASSERT(pRefKid12);
auto pObject12 = pRefKid12->LookupObject();
CPPUNIT_ASSERT(pObject12);
auto pType12 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject12->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType12->GetValue());
auto pS12 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject12->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Text body"_ostr, pS12->GetValue());
auto pKids12 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject12->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids12);
// assume there are no MCID ref at this level
auto pKids12v = pKids12->GetElements();
auto pRefKid120 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids12v[0]);
CPPUNIT_ASSERT(pRefKid120);
auto pObject120 = pRefKid120->LookupObject();
CPPUNIT_ASSERT(pObject120);
auto pType120 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject120->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType120->GetValue());
auto pS120 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject120->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS120->GetValue());
{
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject120->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObjR->LookupElement("Obj"_ostr));
auto pAnnot = pAnnotRef->LookupObject();
auto pAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
auto pASubtype
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
pAnnot->Lookup("Contents"_ostr));
CPPUNIT_ASSERT_EQUAL(
u"wiki-seite"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
auto pAA
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
CPPUNIT_ASSERT(pAA);
auto pAAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Action"_ostr, pAAType->GetValue());
auto pAAS
= dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("S"_ostr));
CPPUNIT_ASSERT_EQUAL("URI"_ostr, pAAS->GetValue());
auto pAAURI = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
pAA->LookupElement("URI"_ostr));
CPPUNIT_ASSERT_EQUAL("https://klexikon.zum.de/wiki/Kl%C3%A4ranlage"_ostr,
pAAURI->GetValue());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
}
auto pRefKid13 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[3]);
CPPUNIT_ASSERT(pRefKid13);
auto pObject13 = pRefKid13->LookupObject();
CPPUNIT_ASSERT(pObject13);
auto pType13 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject13->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType13->GetValue());
auto pS13 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject13->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Text body"_ostr, pS13->GetValue());
auto pKids13 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject13->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids13);
// assume there are no MCID ref at this level
auto pKids13v = pKids13->GetElements();
auto pRefKid130 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids13v[0]);
CPPUNIT_ASSERT(pRefKid130);
auto pObject130 = pRefKid130->LookupObject();
CPPUNIT_ASSERT(pObject130);
auto pType130 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject130->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType130->GetValue());
auto pS130 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject130->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS130->GetValue());
{
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject130->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObjR->LookupElement("Obj"_ostr));
auto pAnnot = pAnnotRef->LookupObject();
auto pAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
auto pASubtype
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
pAnnot->Lookup("Contents"_ostr));
CPPUNIT_ASSERT_EQUAL(
u"https://de.wikipedia.org/wiki/Kl\u00E4ranlage#Mechanische_Vorreinigung"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
auto pAA
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
CPPUNIT_ASSERT(pAA);
auto pAAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Action"_ostr, pAAType->GetValue());
auto pAAS
= dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("S"_ostr));
CPPUNIT_ASSERT_EQUAL("URI"_ostr, pAAS->GetValue());
auto pAAURI = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
pAA->LookupElement("URI"_ostr));
CPPUNIT_ASSERT_EQUAL(
"https://de.wikipedia.org/wiki/Kl%C3%A4ranlage#Mechanische_Vorreinigung"_ostr,
pAAURI->GetValue());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
}
auto pRefKid14 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[4]);
CPPUNIT_ASSERT(pRefKid14);
auto pObject14 = pRefKid14->LookupObject();
CPPUNIT_ASSERT(pObject14);
auto pType14 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject14->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType14->GetValue());
auto pS14 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject14->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Text body"_ostr, pS14->GetValue());
auto pKids14 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject14->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids14);
// assume there are no MCID ref at this level
auto pKids14v = pKids14->GetElements();
auto pRefKid140 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids14v[0]);
CPPUNIT_ASSERT(pRefKid140);
auto pObject140 = pRefKid140->LookupObject();
CPPUNIT_ASSERT(pObject140);
auto pType140 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject140->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType140->GetValue());
auto pS140 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject140->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS140->GetValue());
{
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject140->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObjR->LookupElement("Obj"_ostr));
auto pAnnot = pAnnotRef->LookupObject();
auto pAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
auto pASubtype
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
auto pAContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(
pAnnot->Lookup("Contents"_ostr));
CPPUNIT_ASSERT_EQUAL(
u"tour"_ustr, ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue());
auto pAA
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
CPPUNIT_ASSERT(pAA);
auto pAAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Action"_ostr, pAAType->GetValue());
auto pAAS
= dynamic_cast<vcl::filter::PDFNameElement*>(pAA->LookupElement("S"_ostr));
CPPUNIT_ASSERT_EQUAL("URI"_ostr, pAAS->GetValue());
auto pAAURI = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
pAA->LookupElement("URI"_ostr));
CPPUNIT_ASSERT_EQUAL(
"https://vr-easy.com/tour/usr/220113-virtuellerschulausflug/#pano=24"_ostr,
pAAURI->GetValue());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
}
auto pRefKid16 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1v[6]);
CPPUNIT_ASSERT(pRefKid16);
auto pObject16 = pRefKid16->LookupObject();
CPPUNIT_ASSERT(pObject16);
auto pType16 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject16->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType16->GetValue());
auto pS16 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject16->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Text body"_ostr, pS16->GetValue());
auto pKids16 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject16->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids16);
// assume there are no MCID ref at this level
auto pKids16v = pKids16->GetElements();
auto pRefKid160 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids16v[0]);
CPPUNIT_ASSERT(pRefKid160);
auto pObject160 = pRefKid160->LookupObject();
CPPUNIT_ASSERT(pObject160);
auto pType160 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject160->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType160->GetValue());
auto pS160 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject160->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Form"_ostr, pS160->GetValue());
auto pA160Dict = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject160->Lookup("A"_ostr));
CPPUNIT_ASSERT(pA160Dict);
auto pA160O = dynamic_cast<vcl::filter::PDFNameElement*>(pA160Dict->LookupElement("O"_ostr));
CPPUNIT_ASSERT(pA160O);
CPPUNIT_ASSERT_EQUAL("PrintField"_ostr, pA160O->GetValue());
auto pA160Role
= dynamic_cast<vcl::filter::PDFNameElement*>(pA160Dict->LookupElement("Role"_ostr));
CPPUNIT_ASSERT(pA160Role);
CPPUNIT_ASSERT_EQUAL("tv"_ostr, pA160Role->GetValue());
{
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject160->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObjR->LookupElement("Obj"_ostr));
auto pAnnot = pAnnotRef->LookupObject();
auto pAType
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue());
auto pASubtype
= dynamic_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT_EQUAL("Widget"_ostr, pASubtype->GetValue());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf135192)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"tdf135192-1.fodp");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
int nTable(0);
for (auto* pObject1 : aDocument.GetObjects())
{
auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
if (pType1 && pType1->GetValue() == "StructElem")
{
auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
if (pS1 && pS1->GetValue() == "Table")
{
int nTR(0);
auto pKids1
= dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids1);
// there can be additional children, such as MCID ref
for (auto pKid1 : pKids1->GetElements())
{
auto pRefKid1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid1);
if (pRefKid1)
{
auto pObject2 = pRefKid1->LookupObject();
if (pObject2)
{
auto pType2 = dynamic_cast<vcl::filter::PDFNameElement*>(
pObject2->Lookup("Type"_ostr));
if (pType2 && pType2->GetValue() == "StructElem")
{
auto pS2 = dynamic_cast<vcl::filter::PDFNameElement*>(
pObject2->Lookup("S"_ostr));
if (pS2 && pS2->GetValue() == "TR")
{
int nTD(0);
auto pKids2 = dynamic_cast<vcl::filter::PDFArrayElement*>(
pObject2->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids2);
for (auto pKid2 : pKids2->GetElements())
{
auto pRefKid2
= dynamic_cast<vcl::filter::PDFReferenceElement*>(
pKid2);
if (pRefKid2)
{
auto pObject3 = pRefKid2->LookupObject();
if (pObject3)
{
auto pType3
= dynamic_cast<vcl::filter::PDFNameElement*>(
pObject3->Lookup("Type"_ostr));
if (pType3 && pType3->GetValue() == "StructElem")
{
auto pS3 = dynamic_cast<
vcl::filter::PDFNameElement*>(
pObject3->Lookup("S"_ostr));
if (nTR == 0 && pS3 && pS3->GetValue() == "TH")
{
int nOTable(0);
auto pAttrs = dynamic_cast<
vcl::filter::PDFArrayElement*>(
pObject3->Lookup("A"_ostr));
CPPUNIT_ASSERT(pAttrs != nullptr);
for (const auto& rAttrRef :
pAttrs->GetElements())
{
auto pAttrDict = dynamic_cast<
vcl::filter::PDFDictionaryElement*>(
rAttrRef);
CPPUNIT_ASSERT(pAttrDict != nullptr);
auto pOwner = dynamic_cast<
vcl::filter::PDFNameElement*>(
pAttrDict->LookupElement("O"_ostr));
CPPUNIT_ASSERT(pOwner != nullptr);
if (pOwner->GetValue() == "Table")
{
auto pScope = dynamic_cast<
vcl::filter::PDFNameElement*>(
pAttrDict->LookupElement(
"Scope"_ostr));
CPPUNIT_ASSERT(pScope != nullptr);
CPPUNIT_ASSERT_EQUAL(
"Column"_ostr,
pScope->GetValue());
++nOTable;
}
}
CPPUNIT_ASSERT_EQUAL(1, nOTable);
++nTD;
}
else if (nTR != 0 && pS3
&& pS3->GetValue() == "TD")
{
++nTD;
}
}
}
}
}
CPPUNIT_ASSERT_EQUAL(3, nTD);
++nTR;
}
}
}
}
}
CPPUNIT_ASSERT_EQUAL(2, nTR);
++nTable;
}
}
}
CPPUNIT_ASSERT_EQUAL(1, nTable);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf154955)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"grouped-shape.fodt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
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());
auto pStart = static_cast<const char*>(aUncompressed.GetData());
const char* const pEnd = pStart + aUncompressed.GetSize();
enum
{
Default,
Artifact,
Tagged
} state
= Default;
int nLine(0);
int nTagged(0);
int nArtifacts(0);
while (true)
{
++nLine;
auto const pLine = ::std::find(pStart, pEnd, '\n');
if (pLine == pEnd)
{
break;
}
std::string_view const line(pStart, pLine - pStart);
pStart = pLine + 1;
if (!line.empty() && line[0] != '%')
{
::std::cerr << nLine << ": " << line << "\n";
if (o3tl::starts_with(line, "/Artifact "))
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
state = Artifact;
++nArtifacts;
}
else if (o3tl::starts_with(line, "/Figure<</MCID "))
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
state = Tagged;
++nTagged;
}
else if (line == "EMC")
{
CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
state = Default;
}
else if (nLine > 1) // first line is expected "0.1 w"
{
CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
}
}
}
CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
CPPUNIT_ASSERT_EQUAL(2, nTagged);
CPPUNIT_ASSERT_GREATEREQUAL(1, nArtifacts);
int nFigure(0);
for (auto* pObject : aDocument.GetObjects())
{
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
if (pType && pType->GetValue() == "StructElem")
{
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
if (pS && pS->GetValue() == "Figure")
{
switch (nFigure)
{
case 0:
CPPUNIT_ASSERT_EQUAL(u"Two rectangles - Grouped"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pObject->Lookup("Alt"_ostr))));
break;
case 1:
CPPUNIT_ASSERT_EQUAL(u"these ones are green"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pObject->Lookup("Alt"_ostr))));
break;
}
auto pParentRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject->Lookup("P"_ostr));
CPPUNIT_ASSERT(pParentRef);
auto pParent(pParentRef->LookupObject());
CPPUNIT_ASSERT(pParent);
auto pParentType
= dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pParentType->GetValue());
auto pParentS
= dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pParentS->GetValue());
++nFigure;
}
}
}
// the problem was that there were 4 shapes (the sub-shapes of the 2 groups)
CPPUNIT_ASSERT_EQUAL(2, nFigure);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf155190)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"tdf155190.odt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
int nDiv(0);
int nFigure(0);
for (auto* pObject1 : aDocument.GetObjects())
{
auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
// start with the text box
if (pType1 && pType1->GetValue() == "StructElem" && pS1 && pS1->GetValue() == "Div")
{
++nDiv;
auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids1);
for (auto pKid1 : pKids1->GetElements())
{
auto pRefKid1 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid1);
if (pRefKid1)
{
auto pObject2 = pRefKid1->LookupObject();
CPPUNIT_ASSERT(pObject2);
auto pType2
= dynamic_cast<vcl::filter::PDFNameElement*>(pObject2->Lookup("Type"_ostr));
CPPUNIT_ASSERT(pType2);
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType2->GetValue());
auto pS2
= dynamic_cast<vcl::filter::PDFNameElement*>(pObject2->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("FigureCaption"_ostr, pS2->GetValue());
auto pKids2
= dynamic_cast<vcl::filter::PDFArrayElement*>(pObject2->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids2);
// there are additional children, MCID ref
for (auto pKid2 : pKids2->GetElements())
{
auto pRefKid2 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid2);
if (pRefKid2)
{
auto pObject3 = pRefKid2->LookupObject();
CPPUNIT_ASSERT(pObject3);
auto pType3 = dynamic_cast<vcl::filter::PDFNameElement*>(
pObject3->Lookup("Type"_ostr));
if (pType3 && pType3->GetValue() == "StructElem")
{
auto pS3 = dynamic_cast<vcl::filter::PDFNameElement*>(
pObject3->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pS3->GetValue());
auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(
pObject3->Lookup("Alt"_ostr));
CPPUNIT_ASSERT_EQUAL(
u"Picture of apples"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt));
auto pKids3 = dynamic_cast<vcl::filter::PDFArrayElement*>(
pObject3->Lookup("K"_ostr));
CPPUNIT_ASSERT(pKids3);
// the problem was that this didn't reference an MCID
CPPUNIT_ASSERT(!pKids3->GetElements().empty());
++nFigure;
}
}
}
}
}
}
}
CPPUNIT_ASSERT_EQUAL(1, nDiv);
CPPUNIT_ASSERT_EQUAL(1, nFigure);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testMediaShapeAnnot)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"vid.odt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
CPPUNIT_ASSERT(pAnnots);
// There should be one annotation
CPPUNIT_ASSERT_EQUAL(size_t(1), pAnnots->GetElements().size());
auto pAnnotReference
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
CPPUNIT_ASSERT(pAnnotReference);
// check /Annot - produced by sw
vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
CPPUNIT_ASSERT(pAnnot);
CPPUNIT_ASSERT_EQUAL(
"Annot"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
CPPUNIT_ASSERT_EQUAL(
"Screen"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
auto pA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
CPPUNIT_ASSERT(pA);
auto pR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pA->LookupElement("R"_ostr));
CPPUNIT_ASSERT(pR);
auto pC = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pR->LookupElement("C"_ostr));
CPPUNIT_ASSERT(pC);
auto pCT = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pC->LookupElement("CT"_ostr));
CPPUNIT_ASSERT_EQUAL("video/webm"_ostr, pCT->GetValue());
auto pD = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pC->LookupElement("D"_ostr));
CPPUNIT_ASSERT(pD);
auto pDesc = dynamic_cast<vcl::filter::PDFHexStringElement*>(pD->LookupElement("Desc"_ostr));
CPPUNIT_ASSERT(pDesc);
CPPUNIT_ASSERT_EQUAL(u"alternativloser text\nand some description"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pDesc));
auto pAlt = dynamic_cast<vcl::filter::PDFArrayElement*>(pC->LookupElement("Alt"_ostr));
CPPUNIT_ASSERT(pAlt);
auto pLang = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAlt->GetElement(0));
CPPUNIT_ASSERT_EQUAL(""_ostr, pLang->GetValue());
auto pAltText = dynamic_cast<vcl::filter::PDFHexStringElement*>(pAlt->GetElement(1));
CPPUNIT_ASSERT_EQUAL(u"alternativloser text\nand some description"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAltText));
auto pStructParent
= dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent"_ostr));
CPPUNIT_ASSERT(pStructParent);
vcl::filter::PDFReferenceElement* pStructElemRef(nullptr);
// check ParentTree to find StructElem
int nRoots(0);
for (auto* pObject1 : aDocument.GetObjects())
{
auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
if (pType1 && pType1->GetValue() == "StructTreeRoot")
{
++nRoots;
auto pParentTree = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObject1->Lookup("ParentTree"_ostr));
CPPUNIT_ASSERT(pParentTree);
auto pNumTree = pParentTree->LookupObject();
CPPUNIT_ASSERT(pNumTree);
auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums"_ostr));
CPPUNIT_ASSERT(pNums);
int nFound(0);
for (size_t i = 0; i < pNums->GetElements().size(); i += 2)
{
auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i));
if (pI->GetValue() == pStructParent->GetValue())
{
++nFound;
CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1);
pStructElemRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1));
CPPUNIT_ASSERT(pStructElemRef);
}
}
CPPUNIT_ASSERT_EQUAL(1, nFound);
}
}
CPPUNIT_ASSERT_EQUAL(1, nRoots);
// check /StructElem - produced by drawinglayer
CPPUNIT_ASSERT(pStructElemRef);
auto pStructElem(pStructElemRef->LookupObject());
CPPUNIT_ASSERT(pStructElem);
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pS->GetValue());
auto pSEAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pStructElem->Lookup("Alt"_ostr));
CPPUNIT_ASSERT_EQUAL(u"alternativloser text - and some description"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pSEAlt));
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testFlyFrameHyperlinkAnnot)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"image-hyperlink-alttext.fodt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
CPPUNIT_ASSERT(pAnnots);
// There should be one annotation
CPPUNIT_ASSERT_EQUAL(size_t(1), pAnnots->GetElements().size());
auto pAnnotReference
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
CPPUNIT_ASSERT(pAnnotReference);
// check /Annot - produced by sw
vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
CPPUNIT_ASSERT(pAnnot);
CPPUNIT_ASSERT_EQUAL(
"Annot"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
CPPUNIT_ASSERT_EQUAL(
"Link"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
auto pContents
= dynamic_cast<vcl::filter::PDFHexStringElement*>(pAnnot->Lookup("Contents"_ostr));
CPPUNIT_ASSERT_EQUAL(u"Ship to Bugzilla"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pContents));
auto pStructParent
= dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent"_ostr));
CPPUNIT_ASSERT(pStructParent);
vcl::filter::PDFReferenceElement* pStructElemRef(nullptr);
// check ParentTree to find StructElem
int nRoots(0);
for (auto* pObject1 : aDocument.GetObjects())
{
auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
if (pType1 && pType1->GetValue() == "StructTreeRoot")
{
++nRoots;
auto pParentTree = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObject1->Lookup("ParentTree"_ostr));
CPPUNIT_ASSERT(pParentTree);
auto pNumTree = pParentTree->LookupObject();
CPPUNIT_ASSERT(pNumTree);
auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums"_ostr));
CPPUNIT_ASSERT(pNums);
int nFound(0);
for (size_t i = 0; i < pNums->GetElements().size(); i += 2)
{
auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i));
if (pI->GetValue() == pStructParent->GetValue())
{
++nFound;
CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1);
pStructElemRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1));
CPPUNIT_ASSERT(pStructElemRef);
}
}
CPPUNIT_ASSERT_EQUAL(1, nFound);
}
}
CPPUNIT_ASSERT_EQUAL(1, nRoots);
// check /StructElem - produced by sw painting code
CPPUNIT_ASSERT(pStructElemRef);
auto pStructElem(pStructElemRef->LookupObject());
CPPUNIT_ASSERT(pStructElem);
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS->GetValue());
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
// the Link is inside a Figure
auto pParentRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pStructElem->Lookup("P"_ostr));
CPPUNIT_ASSERT(pParentRef);
auto pParent(pParentRef->LookupObject());
CPPUNIT_ASSERT(pParent);
auto pParentType = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pParentType->GetValue());
auto pParentS = dynamic_cast<vcl::filter::PDFNameElement*>(pParent->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pParentS->GetValue());
auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pParent->Lookup("Alt"_ostr));
CPPUNIT_ASSERT_EQUAL(u"Ship drawing - Very cute"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt));
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testFormControlAnnot)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
{ "PDFUACompliance", uno::Any(true) },
{ "ExportFormFields", uno::Any(true) },
}));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"formcontrol.fodt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
CPPUNIT_ASSERT(pAnnots);
// There should be one annotation
CPPUNIT_ASSERT_EQUAL(size_t(1), pAnnots->GetElements().size());
auto pAnnotReference
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]);
CPPUNIT_ASSERT(pAnnotReference);
// check /Annot
vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
CPPUNIT_ASSERT(pAnnot);
CPPUNIT_ASSERT_EQUAL(
"Annot"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
CPPUNIT_ASSERT_EQUAL(
"Widget"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
auto pT = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAnnot->Lookup("T"_ostr));
CPPUNIT_ASSERT(pT);
CPPUNIT_ASSERT_EQUAL("Check Box 1"_ostr, pT->GetValue());
auto pTU = dynamic_cast<vcl::filter::PDFHexStringElement*>(pAnnot->Lookup("TU"_ostr));
CPPUNIT_ASSERT(pTU);
CPPUNIT_ASSERT_EQUAL(u"helpful text"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pTU));
auto pStructParent
= dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent"_ostr));
CPPUNIT_ASSERT(pStructParent);
vcl::filter::PDFReferenceElement* pStructElemRef(nullptr);
// check ParentTree to find StructElem
int nRoots(0);
for (auto* pObject1 : aDocument.GetObjects())
{
auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
if (pType1 && pType1->GetValue() == "StructTreeRoot")
{
++nRoots;
auto pParentTree = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pObject1->Lookup("ParentTree"_ostr));
CPPUNIT_ASSERT(pParentTree);
auto pNumTree = pParentTree->LookupObject();
CPPUNIT_ASSERT(pNumTree);
auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums"_ostr));
CPPUNIT_ASSERT(pNums);
int nFound(0);
for (size_t i = 0; i < pNums->GetElements().size(); i += 2)
{
auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i));
if (pI->GetValue() == pStructParent->GetValue())
{
++nFound;
CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1);
pStructElemRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1));
CPPUNIT_ASSERT(pStructElemRef);
}
}
CPPUNIT_ASSERT_EQUAL(1, nFound);
}
}
CPPUNIT_ASSERT_EQUAL(1, nRoots);
// check /StructElem
CPPUNIT_ASSERT(pStructElemRef);
auto pStructElem(pStructElemRef->LookupObject());
CPPUNIT_ASSERT(pStructElem);
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue());
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S"_ostr));
CPPUNIT_ASSERT_EQUAL("Form"_ostr, pS->GetValue());
auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pStructElem->Lookup("Alt"_ostr));
CPPUNIT_ASSERT_EQUAL(u"textuelle alternative - a box to check"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt));
auto pA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pStructElem->Lookup("A"_ostr));
CPPUNIT_ASSERT(pA);
auto pO = dynamic_cast<vcl::filter::PDFNameElement*>(pA->LookupElement("O"_ostr));
CPPUNIT_ASSERT(pO);
CPPUNIT_ASSERT_EQUAL("PrintField"_ostr, pO->GetValue());
auto pRole = dynamic_cast<vcl::filter::PDFNameElement*>(pA->LookupElement("Role"_ostr));
CPPUNIT_ASSERT(pRole);
CPPUNIT_ASSERT_EQUAL("cb"_ostr, pRole->GetValue());
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K"_ostr));
int nMCID(0);
int nRef(0);
for (size_t i = 0; i < pKids->GetElements().size(); ++i)
{
auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i));
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pKids->GetElement(i));
if (pNum)
{
++nMCID;
}
if (pObjR)
{
++nRef;
auto pOType
= dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
auto pAnnotRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject());
}
}
CPPUNIT_ASSERT_EQUAL(1, nMCID);
CPPUNIT_ASSERT_EQUAL(1, nRef);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf142129)
{
loadFromFile(u"master.odm");
// update linked section
dispatchCommand(mxComponent, u".uno:UpdateAllLinks"_ustr, {});
// Enable Outlines export
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "ExportBookmarks", uno::Any(true) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
vcl::filter::PDFDocument aDocument;
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
auto* pCatalog = aDocument.GetCatalog();
CPPUNIT_ASSERT(pCatalog);
auto* pCatalogDictionary = pCatalog->GetDictionary();
CPPUNIT_ASSERT(pCatalogDictionary);
auto* pOutlinesObject = pCatalogDictionary->LookupObject("Outlines"_ostr);
CPPUNIT_ASSERT(pOutlinesObject);
auto* pOutlinesDictionary = pOutlinesObject->GetDictionary();
#if 0
// Type isn't actually written currently
auto* pType
= dynamic_cast<vcl::filter::PDFNameElement*>(pOutlinesDictionary->LookupElement("Type"));
CPPUNIT_ASSERT(pType);
CPPUNIT_ASSERT_EQUAL(OString("Outlines"), pType->GetValue());
#endif
auto* pFirst = dynamic_cast<vcl::filter::PDFReferenceElement*>(
pOutlinesDictionary->LookupElement("First"_ostr));
CPPUNIT_ASSERT(pFirst);
auto* pFirstD = pFirst->LookupObject()->GetDictionary();
CPPUNIT_ASSERT(pFirstD);
//CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirstD->LookupElement("Type"))->GetValue());
CPPUNIT_ASSERT_EQUAL(u"Preface"_ustr, ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pFirstD->LookupElement("Title"_ostr))));
auto* pFirst1
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirstD->LookupElement("First"_ostr));
CPPUNIT_ASSERT(pFirst1);
auto* pFirst1D = pFirst1->LookupObject()->GetDictionary();
CPPUNIT_ASSERT(pFirst1D);
// here is a hidden section with headings "Copyright" etc.; check that
// there are no outline entries for it
//CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst1D->LookupElement("Type"))->GetValue());
CPPUNIT_ASSERT_EQUAL(u"Who is this book for?"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pFirst1D->LookupElement("Title"_ostr))));
auto* pFirst2
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst1D->LookupElement("Next"_ostr));
auto* pFirst2D = pFirst2->LookupObject()->GetDictionary();
CPPUNIT_ASSERT(pFirst2D);
//CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst2D->LookupElement("Type"))->GetValue());
CPPUNIT_ASSERT_EQUAL(u"What\u2019s in this book?"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pFirst2D->LookupElement("Title"_ostr))));
auto* pFirst3
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst2D->LookupElement("Next"_ostr));
auto* pFirst3D = pFirst3->LookupObject()->GetDictionary();
CPPUNIT_ASSERT(pFirst3D);
//CPPUNIT_ASSERT_EQUAL(OString("Outlines"), dynamic_cast<vcl::filter::PDFNameElement*>(pFirst3D->LookupElement("Type"))->GetValue());
CPPUNIT_ASSERT_EQUAL(u"Minimum requirements for using LibreOffice"_ustr,
::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pFirst3D->LookupElement("Title"_ostr))));
CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
pFirst3D->LookupElement("Next"_ostr));
CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
pFirstD->LookupElement("Next"_ostr));
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testPdfImageRotate180)
{
// Create an empty document.
loadFromURL(u"private:factory/swriter"_ustr);
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(u"com.sun.star.text.TextGraphicObject"_ustr), uno::UNO_QUERY);
OUString aURL = createFileURL(u"pdf-image-rotate-180.pdf");
xGraphicObject->setPropertyValue(u"GraphicURL"_ustr, uno::Any(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.
save(TestFilter::PDF_WRITER);
// Parse the export result.
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
// Make sure that the page -> form -> form has a child image.
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
// 2: white background and the actual object.
CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject = pPageObject->getFormObject(1);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pFormObject->getType());
CPPUNIT_ASSERT_EQUAL(1, pFormObject->getFormObjectCount());
// Check if the inner form object (original page object in the pdf image) has the correct
// rotation.
std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject = pFormObject->getFormObject(0);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pInnerFormObject->getType());
CPPUNIT_ASSERT_EQUAL(1, pInnerFormObject->getFormObjectCount());
std::unique_ptr<vcl::pdf::PDFiumPageObject> pImage = pInnerFormObject->getFormObject(0);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Image, pImage->getType());
basegfx::B2DHomMatrix aMat = pInnerFormObject->getMatrix();
basegfx::B2DTuple aScale;
basegfx::B2DTuple aTranslate;
double fRotate = 0;
double fShearX = 0;
aMat.decompose(aScale, aTranslate, fRotate, fShearX);
// Without the accompanying fix in place, this test would have failed with:
// - Expected: -1
// - Actual : 1
// i.e. the 180 degrees rotation didn't happen (via a combination of horizontal + vertical
// flip).
CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, aScale.getX(), 0.01);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf144222)
{
// Assume Windows has the font for U+4E2D
#ifdef _WIN32
loadFromFile(u"tdf144222.ods");
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
// The document has one page.
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
const OUString sChar = u"\u4E2D"_ustr;
basegfx::B2DRectangle aRect1, aRect2;
int nCount = 0;
for (int i = 0; i < nPageObjectCount; ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPdfPageObject = pPdfPage->getObject(i);
if (pPdfPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
++nCount;
OUString sText = pPdfPageObject->getText(pTextPage);
if (sText == sChar)
aRect1 = pPdfPageObject->getBounds();
else
aRect2 = pPdfPageObject->getBounds();
}
}
CPPUNIT_ASSERT_EQUAL(2, nCount);
CPPUNIT_ASSERT(!aRect1.isEmpty());
CPPUNIT_ASSERT(!aRect2.isEmpty());
CPPUNIT_ASSERT(!aRect1.overlaps(aRect2));
#endif
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf145873)
{
// Import the bugdoc and export as PDF.
loadFromFile(u"tdf145873.pptx");
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
// The document has one page.
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
int nPageObjectCount = pPdfPage->getObjectCount();
// tdf#145873: Without the fix #1 in place, this test would have failed with
// - Expected: 66
// - Actual : 3
CPPUNIT_ASSERT_EQUAL(66, nPageObjectCount);
auto pObject = pPdfPage->getObject(4);
CPPUNIT_ASSERT_MESSAGE("no object", pObject != nullptr);
// tdf#145873: Without the fix #2 in place, this test would have failed with
// - Expected: 13.40
// - Actual : 3.57...
// - Delta : 0.1
CPPUNIT_ASSERT_DOUBLES_EQUAL(13.40, pObject->getBounds().getWidth(), 0.1);
// - Expected: 13.79
// - Actual : 3.74...
// - Delta : 0.1
CPPUNIT_ASSERT_DOUBLES_EQUAL(13.79, pObject->getBounds().getHeight(), 0.1);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testPdfImageHyperlink)
{
// Given a Draw file, containing a PDF image, which has a hyperlink in it:
// When saving to PDF:
loadFromFile(u"pdf-image-hyperlink.odg");
save(TestFilter::PDF_WRITER);
// Then make sure that link is preserved:
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
// Without the accompanying fix in place, this test would have failed, the hyperlink of the PDF
// image was lost.
CPPUNIT_ASSERT(pPdfPage->hasLinks());
// Also test the precision of the form XObject.
// Given a full-page form XObject, page height is 27.94 cm (792 points):
// When writing the reciprocal of the object height to PDF:
std::unique_ptr<vcl::pdf::PDFiumPageObject> pFormObject;
for (int i = 0; i < pPdfPage->getObjectCount(); ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pPdfPage->getObject(i);
if (pObject->getType() == vcl::pdf::PDFPageObjectType::Form)
{
pFormObject = std::move(pObject);
break;
}
}
CPPUNIT_ASSERT(pFormObject);
std::unique_ptr<vcl::pdf::PDFiumPageObject> pInnerFormObject;
for (int i = 0; i < pFormObject->getFormObjectCount(); ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pFormObject->getFormObject(i);
if (pObject->getType() == vcl::pdf::PDFPageObjectType::Form)
{
pInnerFormObject = std::move(pObject);
break;
}
}
CPPUNIT_ASSERT(pInnerFormObject);
// Then make sure that enough digits are used, so the point size is unchanged:
basegfx::B2DHomMatrix aMatrix = pInnerFormObject->getMatrix();
basegfx::B2DTuple aScale;
basegfx::B2DTuple aTranslate;
double fRotate{};
double fShearX{};
aMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
// Without the accompanying fix in place, this test would have failed with:
// - Expected: 0.0012626264
// - Actual : 0.00126
// i.e. the rounded reciprocal was 794 points, not the original 792.
CPPUNIT_ASSERT_EQUAL(0.001262626, rtl::math::round(aScale.getY(), 9));
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testURIs)
{
struct
{
OUString in;
OString out;
bool relativeFsys;
} URIs[] = { {
u"http://example.com/"_ustr,
"http://example.com/"_ostr,
true,
},
{
u"file://localfile.odt/"_ustr,
"file://localfile.odt/"_ostr,
true,
},
{
// tdf 143216
u"http://username:password@example.com"_ustr,
"http://username:password@example.com"_ostr,
true,
},
{
u"git://git.example.org/project/example"_ustr,
"git://git.example.org/project/example"_ostr,
true,
},
{
// The odt/pdf gets substituted due to 'ConvertOOoTargetToPDFTarget'
u"filebypath.odt"_ustr,
"filebypath.pdf"_ostr,
true,
},
{
// The odt/pdf gets substituted due to 'ConvertOOoTargetToPDFTarget'
// but this time with ExportLinksRelativeFsys off the path is added
u"filebypath.odt"_ustr,
OUStringToOString(utl::GetTempNameBaseDirectory(), RTL_TEXTENCODING_UTF8)
+ "filebypath.pdf",
false,
},
{
// This also gets made relative due to 'ExportLinksRelativeFsys'
utl::GetTempNameBaseDirectory() + "fileintempdir.odt",
"fileintempdir.pdf"_ostr,
true,
} };
// Create an empty document.
// Note: The test harness gets very upset if we try and create multiple
// documents, or recreate it; so reuse one instance for all the links
loadFromURL(u"private:factory/swriter"_ustr);
uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<text::XText> xText = xTextDocument->getText();
uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
xText->insertString(xCursor, u"Test pdf"_ustr, /*bAbsorb=*/false);
// Set the name so it can do relative name replacement
uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
xModel->attachResource(maTempFile.GetURL(), xModel->getArgs());
for (const auto& uri : URIs)
{
// Test the filename rewriting
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
{ "ExportLinksRelativeFsys", uno::Any(uri.relativeFsys) },
{ "ConvertOOoTargetToPDFTarget", uno::Any(true) },
}));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
// Add a link (based on testNestedHyperlink in rtfexport3)
xCursor->gotoStart(/*bExpand=*/false);
xCursor->gotoEnd(/*bExpand=*/true);
uno::Reference<beans::XPropertySet> xCursorProps(xCursor, uno::UNO_QUERY);
xCursorProps->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(uri.in));
xCursorProps->setPropertyValue(u"HyperLinkName"_ustr, uno::Any(u"Testname"_ustr));
// Save as PDF.
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Use the filter rather than the pdfium route, as per the tdf105093 test, it's
// easier to parse the annotations
vcl::filter::PDFDocument aDocument;
// Parse the export result.
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(size_t(1), aPages.size());
auto pAnnots
= dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
CPPUNIT_ASSERT(pAnnots);
// There should be one annotation
CPPUNIT_ASSERT_EQUAL(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);
// We're expecting something like /Type /Annot /A << /Type /Action /S /URI /URI (path)
CPPUNIT_ASSERT_EQUAL(
"Annot"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
CPPUNIT_ASSERT_EQUAL(
"Link"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A"_ostr));
CPPUNIT_ASSERT(pAction);
auto pURIElem = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(
pAction->LookupElement("URI"_ostr));
CPPUNIT_ASSERT(pURIElem);
// Check it matches
CPPUNIT_ASSERT_EQUAL(uri.out, pURIElem->GetValue());
// tdf#148934 check a11y
CPPUNIT_ASSERT_EQUAL(u"Testname"_ustr, ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
*dynamic_cast<vcl::filter::PDFHexStringElement*>(
pAnnot->Lookup("Contents"_ostr))));
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testPdfImageAnnots)
{
// Given a document with a PDF image that has 2 comments (popup, text) and a hyperlink:
// When saving to PDF:
loadFromFile(u"pdf-image-annots.odg");
save(TestFilter::PDF_WRITER);
// Then make sure only the hyperlink is kept, since Draw itself has its own comments:
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
// Without the accompanying fix in place, this test would have failed with:
// - Expected: 1
// - Actual : 3
// i.e. not only the hyperlink but also the 2 comments were exported, leading to duplication.
CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getAnnotationCount());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testPdfImageEncryption)
{
// Given an empty document, with an inserted PDF image:
loadFromURL(u"private:factory/swriter"_ustr);
uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<text::XText> xText = xTextDocument->getText();
uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
uno::Reference<beans::XPropertySet> xGraphicObject(
xFactory->createInstance(u"com.sun.star.text.TextGraphicObject"_ustr), uno::UNO_QUERY);
OUString aURL = createFileURL(u"rectangles.pdf");
xGraphicObject->setPropertyValue(u"GraphicURL"_ustr, uno::Any(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);
// When saving as encrypted PDF:
save(TestFilter::PDF_WRITER, /*rParams*/ {}, /*pPassword*/ "secret");
// Then make sure that the image is not lost:
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get(), "secret"_ostr);
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
// Without the accompanying fix in place, this test would have failed with:
// - Expected: 2
// - Actual : 0
// i.e. instead of the white background and the actual form child, the image was lost due to
// missing encryption.
CPPUNIT_ASSERT_EQUAL(2, pPageObject->getFormObjectCount());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testBitmapScaledown)
{
// FIXME: the DPI check should be removed when either (1) the test is fixed to work with
// non-default DPI; or (2) unit tests on Windows are made to use svp VCL plugin.
if (!IsDefaultDPI())
return;
// Given a document with an upscaled and rotated barcode bitmap in it:
// When saving as PDF:
loadFromFile(u"bitmap-scaledown.odt");
save(TestFilter::PDF_WRITER);
// Then verify that the bitmap is not downscaled:
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
int nPageObjectCount = pPdfPage->getObjectCount();
for (int i = 0; i < nPageObjectCount; ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Image)
continue;
std::unique_ptr<vcl::pdf::PDFiumBitmap> pBitmap = pPageObject->getImageBitmap();
CPPUNIT_ASSERT(pBitmap);
// In-file sizes: good is 2631x380, bad is 1565x14.
int nWidth = pBitmap->getWidth();
// Without the accompanying fix in place, this test would have failed with:
// - Expected: 2616
// - Actual : 1565
// i.e. the bitmap in the pdf result was small enough to be blurry.
CPPUNIT_ASSERT_EQUAL(2616, nWidth);
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf139627)
{
#if HAVE_MORE_FONTS
loadFromFile(u"justified-arabic-kashida.odt");
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
// The document has one page.
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
// 7 objects, 3 text, others are path
int nPageObjectCount = pPdfPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(7, nPageObjectCount);
// 3 text objects
OUString sText[3];
/* With "Noto Sans Arabic" font, these are the X ranges on Linux:
0: ( 61.75 - 415.94)
1: (479.70 - 422.40)
2: (209.40 - 453.2)
*/
basegfx::B2DRectangle aRect[3];
std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject;
int nTextObjectCount = 0;
for (int i = 0; i < nPageObjectCount; ++i)
{
pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
sText[nTextObjectCount] = pPageObject->getText(pTextPage);
aRect[nTextObjectCount] = pPageObject->getBounds();
++nTextObjectCount;
}
}
CPPUNIT_ASSERT_EQUAL(3, nTextObjectCount);
// Text: جِـرم (which means "mass" in Persian)
// Rendered as (left to right): "reh + mim" - "kasreh" - "jeh + tatweel"
int rehmim = 0, kasreh = 1, jehtatweel = 2;
CPPUNIT_ASSERT_EQUAL(u"رم"_ustr, sText[rehmim].trim());
CPPUNIT_ASSERT_EQUAL(OUString::fromUtf8("\xD9\x90\xD8\xAC"), sText[kasreh].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, sText[jehtatweel].trim());
// "Kasreh" should be within "jeh" character
CPPUNIT_ASSERT_GREATER(aRect[jehtatweel].getMinX(), aRect[kasreh].getMinX());
CPPUNIT_ASSERT_LESS(aRect[jehtatweel].getMaxX(), aRect[kasreh].getMaxX());
// "Tatweel" should cover "jeh" and "reh"+"mim" to avoid gap
// Checking right gap
//CPPUNIT_ASSERT_GREATER(aRect[jehtatweel].getMinX(), aRect[tatweel].getMaxX());
// Checking left gap
// Kashida fails to reach to rehmim before the series of patches starting
// with 3901e029bd39575f700e69a73818565d62226a23. The visible symptom is
// a gap in the left of Kashida.
CPPUNIT_ASSERT_LESS(aRect[rehmim].getMaxX(), aRect[jehtatweel].getMinX());
// Overlappings of Kashida and surrounding characters is ~9% of the width
// of the "jeh" character, while using "Noto Arabic Sans" font in this
// specific example.
// We set the hard limit of 10% here.
CPPUNIT_ASSERT_LESS(0.1, fabs(aRect[rehmim].getMaxX() - aRect[jehtatweel].getMinX())
/ aRect[jehtatweel].getWidth());
#endif
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testRexportRefToKids)
{
// We need to enable PDFium import (and make sure to disable after the test)
UsePdfium aGuard;
// Load the PDF and save as PDF
vcl::filter::PDFDocument aDocument;
loadFromFile(u"ref-to-kids.pdf");
save(TestFilter::PDF_WRITER);
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
CPPUNIT_ASSERT(pResources);
auto pXObjects
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
CPPUNIT_ASSERT(pXObjects);
// Without the fix LookupObject for all /Im's will fail.
for (auto const& rPair : pXObjects->GetItems())
{
if (rPair.first.startsWith("Im"))
CPPUNIT_ASSERT(pXObjects->LookupObject(rPair.first));
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testRexportFilterSingletonArray)
{
// We need to enable PDFium import (and make sure to disable after the test)
UsePdfium aGuard;
// the test fails with tagged PDF enabled
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "UseTaggedPDF", uno::Any(false) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
// Load the PDF and save as PDF
vcl::filter::PDFDocument aDocument;
loadFromFile(u"ref-to-kids.pdf");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
// Directly go to the inner XObject Im5 that contains the rectangle drawings.
auto pInnerIm = aDocument.LookupObject(5);
CPPUNIT_ASSERT(pInnerIm);
auto pFilter = dynamic_cast<vcl::filter::PDFNameElement*>(pInnerIm->Lookup("Filter"_ostr));
CPPUNIT_ASSERT(pFilter);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Filter must be FlateDecode", "FlateDecode"_ostr,
pFilter->GetValue());
vcl::filter::PDFStreamElement* pStream = pInnerIm->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());
// Without the fix, the stream is doubly compressed,
// hence one decompression will not yield the "re" expressions.
auto pStart = static_cast<const char*>(aUncompressed.GetData());
const char* pEnd = pStart + aUncompressed.GetSize();
OString aImage = "100 0 30 50 re B*\n70 67 50 30 re B*\n"_ostr;
auto it = std::search(pStart, pEnd, aImage.getStr(), aImage.getStr() + aImage.getLength());
CPPUNIT_ASSERT(it != pEnd);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testRexportMediaBoxOrigin)
{
// We need to enable PDFium import (and make sure to disable after the test)
UsePdfium aGuard;
// Load the PDF and save as PDF
vcl::filter::PDFDocument aDocument;
loadFromFile(u"ref-to-kids.pdf");
save(TestFilter::PDF_WRITER);
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
// Directly go to the inner XObject Im12 that contains the rectangle drawings in page 2.
auto pInnerIm = aDocument.LookupObject(12);
CPPUNIT_ASSERT(pInnerIm);
static constexpr sal_Int32 aOrigin[2] = { -800, -600 };
sal_Int32 aSize[2] = { 0, 0 };
auto pBBox = dynamic_cast<vcl::filter::PDFArrayElement*>(pInnerIm->Lookup("BBox"_ostr));
CPPUNIT_ASSERT(pBBox);
const auto& rElements2 = pBBox->GetElements();
CPPUNIT_ASSERT_EQUAL(size_t(4), rElements2.size());
for (sal_Int32 nIdx = 0; nIdx < 4; ++nIdx)
{
const auto* pNumElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements2[nIdx]);
CPPUNIT_ASSERT(pNumElement);
if (nIdx < 2)
CPPUNIT_ASSERT_EQUAL(aOrigin[nIdx], static_cast<sal_Int32>(pNumElement->GetValue()));
else
aSize[nIdx - 2] = static_cast<sal_Int32>(pNumElement->GetValue()) - aOrigin[nIdx - 2];
}
auto pMatrix = dynamic_cast<vcl::filter::PDFArrayElement*>(pInnerIm->Lookup("Matrix"_ostr));
CPPUNIT_ASSERT(pMatrix);
const auto& rElements = pMatrix->GetElements();
CPPUNIT_ASSERT_EQUAL(size_t(6), rElements.size());
sal_Int32 aMatTranslate[6]
= { // Rotation by $\theta$ $cos(\theta), sin(\theta), -sin(\theta), cos(\theta)$
0, -1, 1, 0,
// Translate x,y
-aOrigin[1] - aSize[1] / vcl::PDF_INSERT_MAGIC_SCALE_FACTOR / 2
+ aSize[0] / vcl::PDF_INSERT_MAGIC_SCALE_FACTOR / 2,
aOrigin[0] + aSize[0] / vcl::PDF_INSERT_MAGIC_SCALE_FACTOR / 2
+ aSize[1] / vcl::PDF_INSERT_MAGIC_SCALE_FACTOR / 2
};
for (sal_Int32 nIdx = 0; nIdx < 6; ++nIdx)
{
const auto* pNumElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[nIdx]);
CPPUNIT_ASSERT(pNumElement);
CPPUNIT_ASSERT_EQUAL(aMatTranslate[nIdx], static_cast<sal_Int32>(pNumElement->GetValue()));
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testRexportResourceItemReference)
{
// We need to enable PDFium import (and make sure to disable after the test)
UsePdfium aGuard;
// Load the PDF and save as PDF
vcl::filter::PDFDocument aDocument;
loadFromFile(u"ref-to-kids.pdf");
save(TestFilter::PDF_WRITER);
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
CPPUNIT_ASSERT_EQUAL(size_t(5), aPages.size());
// Directly go to the inner XObject Im12 that has reference to Font in page 2.
auto pInnerIm = aDocument.LookupObject(12);
CPPUNIT_ASSERT(pInnerIm);
auto pResources
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pInnerIm->Lookup("Resources"_ostr));
CPPUNIT_ASSERT(pResources);
auto pFontsReference
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pResources->LookupElement("Font"_ostr));
CPPUNIT_ASSERT(pFontsReference);
auto pFontsObject = pFontsReference->LookupObject();
CPPUNIT_ASSERT(pFontsObject);
auto pFontDict
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pFontsObject->Lookup("FF132"_ostr));
CPPUNIT_ASSERT(pFontDict);
auto pFontDescriptor = pFontDict->LookupObject("FontDescriptor"_ostr);
CPPUNIT_ASSERT(pFontDescriptor);
auto pFontWidths = pFontDict->LookupObject("Widths"_ostr);
CPPUNIT_ASSERT(pFontWidths);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf152246)
{
// Import the bugdoc and export as PDF.
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
{ "ExportFormFields", uno::Any(true) },
}));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"content-control-rtl.docx");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the export result.
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(size_t(1), aPages.size());
// Position array
static constexpr double aPos[5][4] = { { 55.699, 706.701, 132.401, 722.499 },
{ 197.499, 706.701, 274.201, 722.499 },
{ 302.349, 679.101, 379.051, 694.899 },
{ 479.599, 679.101, 556.301, 694.899 },
{ 55.699, 651.501, 132.401, 667.299 } };
// Get page annotations.
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
CPPUNIT_ASSERT(pAnnots);
CPPUNIT_ASSERT_EQUAL(size_t(5), pAnnots->GetElements().size());
for (sal_Int32 i = 0; i < 5; ++i)
{
auto pAnnotReference
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[i]);
CPPUNIT_ASSERT(pAnnotReference);
vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject();
CPPUNIT_ASSERT(pAnnot);
CPPUNIT_ASSERT_EQUAL(
"Annot"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"_ostr))->GetValue());
CPPUNIT_ASSERT_EQUAL(
"Widget"_ostr,
static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"_ostr))->GetValue());
auto pRect = dynamic_cast<vcl::filter::PDFArrayElement*>(pAnnot->Lookup("Rect"_ostr));
CPPUNIT_ASSERT(pRect);
const auto& rElements = pRect->GetElements();
CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size());
for (sal_Int32 nIdx = 0; nIdx < 4; ++nIdx)
{
const auto* pNumElement = dynamic_cast<vcl::filter::PDFNumberElement*>(rElements[nIdx]);
CPPUNIT_ASSERT(pNumElement);
CPPUNIT_ASSERT_DOUBLES_EQUAL(aPos[i][nIdx], pNumElement->GetValue(), 1e-6);
}
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf155161)
{
vcl::filter::PDFDocument aDocument;
loadFromFile(u"tdf155161.odt");
save(TestFilter::PDF_WRITER);
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
// Check that all fonts in the document are not Type 3 fonts.
std::set<OString> aFontNames;
for (auto* pObject : aDocument.GetObjects())
{
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
if (pType && pType->GetValue() == "Font")
{
auto pSubtype
= dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT(pSubtype);
CPPUNIT_ASSERT("Type3"_ostr != pSubtype->GetValue());
auto pName
= dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("BaseFont"_ostr));
CPPUNIT_ASSERT(pName);
aFontNames.insert(pName->GetValue().copy(7)); // skip the subset id
}
}
// There must be two fonts
std::set<OString> aExpected{ "Cantarell-Regular"_ostr, "Cantarell-Bold"_ostr };
CPPUNIT_ASSERT_EQUAL(aExpected, aFontNames);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTrueTypeCompositeFont)
{
// Subsets of fonts with glyf outlines are embedded as composite fonts, so
// that the subset no longer needs a cmap of our own making.
vcl::filter::PDFDocument aDocument;
loadFromFile(u"SimpleTestDocument.fodt");
save(TestFilter::PDF_WRITER);
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
vcl::filter::PDFObjectElement* pType0 = nullptr;
vcl::filter::PDFObjectElement* pCIDFont = nullptr;
for (auto* pObject : aDocument.GetObjects())
{
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
if (!pType || pType->GetValue() != "Font")
continue;
auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT(pSubtype);
if (pSubtype->GetValue() == "Type0")
pType0 = pObject;
else if (pSubtype->GetValue() == "CIDFontType2")
pCIDFont = pObject;
}
CPPUNIT_ASSERT(pType0);
CPPUNIT_ASSERT(pCIDFont);
// The CIDs are the glyph IDs of the subset
auto pCIDToGIDMap
= dynamic_cast<vcl::filter::PDFNameElement*>(pCIDFont->Lookup("CIDToGIDMap"_ostr));
CPPUNIT_ASSERT(pCIDToGIDMap);
CPPUNIT_ASSERT_EQUAL("Identity"_ostr, pCIDToGIDMap->GetValue());
// and it is not a simple font any more
CPPUNIT_ASSERT(!pType0->Lookup("FirstChar"_ostr));
CPPUNIT_ASSERT(!pType0->Lookup("Widths"_ostr));
// The font program is still a TrueType one
auto pDescriptorRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pCIDFont->Lookup("FontDescriptor"_ostr));
CPPUNIT_ASSERT(pDescriptorRef);
auto pDescriptor = pDescriptorRef->LookupObject();
CPPUNIT_ASSERT(pDescriptor);
CPPUNIT_ASSERT(pDescriptor->Lookup("FontFile2"_ostr));
// and the text can still be extracted
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pPdfTextPage);
int nChars = pPdfTextPage->countChars();
std::vector<sal_uInt32> aChars(nChars);
for (int i = 0; i < nChars; i++)
aChars[i] = pPdfTextPage->getUnicode(i);
CPPUNIT_ASSERT_EQUAL(u"This is a test document."_ustr, OUString(aChars.data(), aChars.size()));
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testPDFA1CIDSet)
{
// PDF/A-1 requires a CIDSet listing the CIDs of the font program.
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence({
{ "SelectPdfVersion", uno::Any(sal_Int32(1)) }, // PDF/A-1b
}));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
vcl::filter::PDFDocument aDocument;
loadFromFile(u"tdf171869.odt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
vcl::filter::PDFObjectElement* pCIDFont = nullptr;
for (auto* pObject : aDocument.GetObjects())
{
auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
if (pSubtype && pSubtype->GetValue() == "CIDFontType0")
pCIDFont = pObject;
}
CPPUNIT_ASSERT(pCIDFont);
auto pDescriptorRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pCIDFont->Lookup("FontDescriptor"_ostr));
CPPUNIT_ASSERT(pDescriptorRef);
auto pDescriptor = pDescriptorRef->LookupObject();
CPPUNIT_ASSERT(pDescriptor);
auto pCIDSetRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pDescriptor->Lookup("CIDSet"_ostr));
CPPUNIT_ASSERT(pCIDSetRef);
auto pCIDSetObject = pCIDSetRef->LookupObject();
CPPUNIT_ASSERT(pCIDSetObject);
auto pCIDSetStream = pCIDSetObject->GetStream();
CPPUNIT_ASSERT(pCIDSetStream);
SvMemoryStream aCIDSet;
ZCodec aCIDSetCodec;
aCIDSetCodec.BeginCompression();
pCIDSetStream->GetMemory().Seek(0);
aCIDSetCodec.Decompress(pCIDSetStream->GetMemory(), aCIDSet);
CPPUNIT_ASSERT(aCIDSetCodec.EndCompression());
// The charset is identity, so the CIDs are those of the three glyphs of the
// subset: one byte with the top three bits set, and nothing else.
CPPUNIT_ASSERT_EQUAL(sal_uInt64(1), aCIDSet.GetSize());
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0xE0), static_cast<const sal_uInt8*>(aCIDSet.GetData())[0]);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testNoCIDSetWithoutPDFA1)
{
// Not PDF/A-1, so no CIDSet.
vcl::filter::PDFDocument aDocument;
loadFromFile(u"tdf171869.odt");
save(TestFilter::PDF_WRITER);
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
bool bSawDescriptor = false;
for (auto* pObject : aDocument.GetObjects())
{
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
if (!pType || pType->GetValue() != "FontDescriptor")
continue;
bSawDescriptor = true;
CPPUNIT_ASSERT(!pObject->Lookup("CIDSet"_ostr));
}
CPPUNIT_ASSERT(bSawDescriptor);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf171869)
{
// Document using an embedded CID-keyed font (a Source Han Sans subset)
vcl::filter::PDFDocument aDocument;
loadFromFile(u"tdf171869.odt");
save(TestFilter::PDF_WRITER);
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
// The only fonts must be a Type 0 wrapper and its CIDFontType0 descendant.
vcl::filter::PDFObjectElement* pType0 = nullptr;
vcl::filter::PDFObjectElement* pCIDFont = nullptr;
int nFonts = 0;
for (auto* pObject : aDocument.GetObjects())
{
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
if (!pType || pType->GetValue() != "Font")
continue;
nFonts++;
auto pSubtype = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT(pSubtype);
if (pSubtype->GetValue() == "Type0")
pType0 = pObject;
else if (pSubtype->GetValue() == "CIDFontType0")
pCIDFont = pObject;
}
CPPUNIT_ASSERT_EQUAL(2, nFonts);
CPPUNIT_ASSERT(pType0);
CPPUNIT_ASSERT(pCIDFont);
auto pBaseFont = dynamic_cast<vcl::filter::PDFNameElement*>(pType0->Lookup("BaseFont"_ostr));
CPPUNIT_ASSERT(pBaseFont);
CPPUNIT_ASSERT_EQUAL("SourceHanSans-Regular"_ostr, pBaseFont->GetValue().copy(7));
// The font program is a bare CFF (CIDFontType0C)
auto pDescriptorRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pCIDFont->Lookup("FontDescriptor"_ostr));
CPPUNIT_ASSERT(pDescriptorRef);
auto pDescriptor = pDescriptorRef->LookupObject();
CPPUNIT_ASSERT(pDescriptor);
auto pFontFileRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pDescriptor->Lookup("FontFile3"_ostr));
CPPUNIT_ASSERT(pFontFileRef);
auto pFontFile = pFontFileRef->LookupObject();
CPPUNIT_ASSERT(pFontFile);
auto pFontFileSubtype
= dynamic_cast<vcl::filter::PDFNameElement*>(pFontFile->Lookup("Subtype"_ostr));
CPPUNIT_ASSERT(pFontFileSubtype);
CPPUNIT_ASSERT_EQUAL("CIDFontType0C"_ostr, pFontFileSubtype->GetValue());
auto pEncodingRef
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pType0->Lookup("Encoding"_ostr));
CPPUNIT_ASSERT(pEncodingRef);
auto pEncoding = pEncodingRef->LookupObject();
CPPUNIT_ASSERT(pEncoding);
auto pEncodingStream = pEncoding->GetStream();
CPPUNIT_ASSERT(pEncodingStream);
SvMemoryStream aObjectStream;
ZCodec aZCodec;
aZCodec.BeginCompression();
pEncodingStream->GetMemory().Seek(0);
aZCodec.Decompress(pEncodingStream->GetMemory(), aObjectStream);
CPPUNIT_ASSERT(aZCodec.EndCompression());
std::string aCMap(static_cast<const char*>(aObjectStream.GetData()), aObjectStream.GetSize());
// The subset always has an identity charset, so the code of each glyph is
// also its CID.
CPPUNIT_ASSERT(aCMap.find("begincidrange\n"
"<00> <02> 0\n"
"endcidrange")
!= std::string::npos);
// Check the text can be extracted (i.e. the ToUnicode CMap works)
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pPdfTextPage);
int nChars = pPdfTextPage->countChars();
CPPUNIT_ASSERT_EQUAL(2, nChars);
std::vector<sal_uInt32> aChars(nChars);
for (int i = 0; i < nChars; i++)
aChars[i] = pPdfTextPage->getUnicode(i);
OUString aActualText(aChars.data(), aChars.size());
CPPUNIT_ASSERT_EQUAL(u"世솅"_ustr, aActualText);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf48707_1)
{
// Import the bugdoc and export as PDF.
loadFromFile(u"tdf48707-1.fodt");
save(TestFilter::PDF_WRITER);
// Parse the export result with pdfium.
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPage);
int nPageObjectCount = pPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(6, nPageObjectCount);
auto pTextPage = pPage->getTextPage();
for (int i = 0; i < nPageObjectCount; ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
// The text and path objects (underline and overline) should all be red.
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, pPageObject->getFillColor());
else
CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, pPageObject->getStrokeColor());
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf48707_2)
{
// Import the bugdoc and export as PDF.
loadFromFile(u"tdf48707-2.fodt");
save(TestFilter::PDF_WRITER);
// Parse the export result with pdfium.
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPage);
int nPageObjectCount = pPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(13, nPageObjectCount);
auto pTextPage = pPage->getTextPage();
for (int i = 0; i < nPageObjectCount; ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(i);
if (pPageObject->getType() != vcl::pdf::PDFPageObjectType::Path)
continue;
// The table-like paths should be red, underline and overline should be black.
if (i >= 8)
CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPageObject->getStrokeColor());
else
CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, pPageObject->getStrokeColor());
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf156528)
{
loadFromFile(u"wide_page1.fodt");
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
// The document has two pages
CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
// 1st page (5100 mm width x 210 mm high, UserUnit = 2)
auto pPdfPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPdfPage);
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(5100.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
pPdfPage->getWidth(), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(210.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
pPdfPage->getHeight(), 1);
// 1 object (rectangle 5060 mm width x 170 mm high, UserUnit = 2)
CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
auto pRect = pPdfPage->getObject(0);
CPPUNIT_ASSERT(pRect);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Path, pRect->getType());
auto bounds = pRect->getBounds();
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(5060.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
bounds.getWidth(), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(170.0 / 2, o3tl::Length::mm, o3tl::Length::pt),
bounds.getHeight(), 1);
// 2nd page (210 mm width x 297 mm high, UserUnit = 1)
pPdfPage = pPdfDocument->openPage(1);
CPPUNIT_ASSERT(pPdfPage);
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(210.0, o3tl::Length::mm, o3tl::Length::pt),
pPdfPage->getWidth(), 1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(297.0, o3tl::Length::mm, o3tl::Length::pt),
pPdfPage->getHeight(), 1);
// 1 object (rectangle 170 mm width x 257 mm high, UserUnit = 1)
CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
pRect = pPdfPage->getObject(0);
CPPUNIT_ASSERT(pRect);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Path, pRect->getType());
bounds = pRect->getBounds();
// Without the fix, this would fail with
// - Expected: 481.889763779528
// - Actual : 241.925001144409
// - Delta : 1
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(170.0, o3tl::Length::mm, o3tl::Length::pt),
bounds.getWidth(), 1);
//
// - Expected: 728.503937007874
// - Actual : 365.25
// - Delta : 1
CPPUNIT_ASSERT_DOUBLES_EQUAL(o3tl::convert(257.0, o3tl::Length::mm, o3tl::Length::pt),
bounds.getHeight(), 1);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf113866)
{
loadFromFile(u"tdf113866.odt");
// Set -- Printer Settings->Options->Print text in Black -- to true
SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
IDocumentDeviceAccess& rDocAccess = pDoc->getIDocumentDeviceAccess();
SwPrintData aDocPrintData = rDocAccess.getPrintData();
aDocPrintData.SetPrintBlackFont(true);
rDocAccess.setPrintData(aDocPrintData);
// Export to pdf
save(TestFilter::PDF_WRITER);
// Parse the export result with pdfium.
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
// Non-NULL pPdfDocument means pdfium is available.
if (pPdfDocument != nullptr)
{
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPdfPage);
int nPageObjectCount = pPdfPage->getObjectCount();
for (int i = 0; i < nPageObjectCount; ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(i);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
// Without the bug fix in place the test will fail with
// - Expected: rgba[008000ff]
// - Actual : rgba[000000ff]
// With the bug fixed, the green text in the test doc will stay green,
// when exported to pdf, while Print Text in Black is true
CPPUNIT_ASSERT_EQUAL(COL_GREEN, pPageObject->getFillColor());
}
}
}
// Form controls coordinates scrambled when exporting to pdf with unchecked form creation in Writer
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf159817)
{
// Enable PDF/UA
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "ExportFormFields", uno::Any(false) } }));
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
loadFromFile(u"tdf159817.fodt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
CPPUNIT_ASSERT(pPdfPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
// So I extracted these values by using SAL_WARN(... << ...getMinimum()), but it appears
// that the C++ stream operators do not output double values with sufficient resolution for me
// to recreate those values in code, sigh, so resort to rounding things.
auto roundPoint = [&pPdfPage](int i) {
auto p = pPdfPage->getObject(i)->getBounds().getMinimum();
return basegfx::B2DPoint(std::floor(p.getX() * 10) / 10.0,
std::floor(p.getY() * 10) / 10.0);
};
// before the fix these co-ordinates would have been way further down the page
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(8.6, 677.3), roundPoint(13));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(9.3, 677.9), roundPoint(14));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(9.8, 678.5), roundPoint(15));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(85.0, 677.3), roundPoint(16));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(85.6, 677.9), roundPoint(17));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(170.1, 677.3), roundPoint(18));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(170.6, 677.9), roundPoint(19));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(0.0, 654.0), roundPoint(20));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(0.6, 654.6), roundPoint(21));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.3, 655.5), roundPoint(22));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.3, 655.5), roundPoint(23));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.2, 655.5), roundPoint(24));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.2, 655.5), roundPoint(25));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.4, 655.5), roundPoint(26));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.4, 655.5), roundPoint(27));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.1, 655.5), roundPoint(28));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.1, 655.5), roundPoint(29));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.5, 655.5), roundPoint(30));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.5, 655.5), roundPoint(31));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.0, 655.5), roundPoint(32));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(1.0, 655.5), roundPoint(33));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(28.3, 641.4), roundPoint(34));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(28.3, 623.7), roundPoint(35));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(28.3, 623.8), roundPoint(36));
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(138.6, 623.7), roundPoint(37));
}
void GetPdfPageTextObjectsAndBounds(std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument,
int nPage, std::vector<OUString>& rOutText,
std::vector<basegfx::B2DRectangle>& rOutRect)
{
CPPUNIT_ASSERT_GREATER(nPage, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(nPage);
CPPUNIT_ASSERT(pPdfPage);
auto pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
rOutText.push_back(pPageObject->getText(pTextPage));
rOutRect.push_back(pPageObject->getBounds());
}
}
}
// Tests that kerning is correctly applied across color changes
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf61444)
{
loadFromFile(u"tdf61444.odt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(4), aText.size());
CPPUNIT_ASSERT_EQUAL(u"Wait"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"W"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"ai"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u"t"_ustr, aText.at(3).trim());
// Both lines should have the same kerning, so should end at approximately the same X coordinate
auto solid_extent = aRect.at(0).getMaxX();
auto color_extent = aRect.at(3).getMaxX();
CPPUNIT_ASSERT_DOUBLES_EQUAL(solid_extent, color_extent, /*delta*/ 0.15);
}
// tdf#124116 - Tests that track-changes inside a grapheme cluster does not break positioning
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf124116TrackUntrack)
{
loadFromFile(u"tdf124116-hebrew-track-untrack.odt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
// The underlying document has 4 lines:
// - שמחַ plain
// - שמחַ tracked
// - שמחַ with patah tracked
// - שמחַ with everything except patah tracked
// ---
// However, due to the way text items are inserted for Hebrew, there will be 10:
// - het with an improperly spaced patah, then שמ for the first 2 lines
// - as above, followed by a blank for the next 2 representing the actual diacritic
// ---
// This test will likely need to be rewritten if tdf#158329 is fixed.
CPPUNIT_ASSERT_EQUAL(size_t(10), aText.size());
// All that matters for this test is that the patah is positioned well under the het
auto het_x0 = aRect.at(4).getMinX();
auto patah_x0 = aRect.at(6).getMinX();
CPPUNIT_ASSERT_GREATER(10.0, patah_x0 - het_x0);
auto het_x1 = aRect.at(7).getMinX();
auto patah_x1 = aRect.at(9).getMinX();
CPPUNIT_ASSERT_GREATER(10.0, patah_x1 - het_x1);
}
// tdf#134226 - Tests that shaping is not broken by invisible spans
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf134226)
{
loadFromFile(u"tdf134226-shadda-in-hidden-span.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(8), aText.size());
CPPUNIT_ASSERT_EQUAL(u"ة"_ustr, aText[0].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[1].trim());
CPPUNIT_ASSERT_EQUAL(u"\u0651ق"_ustr, aText[2].trim());
CPPUNIT_ASSERT_EQUAL(u"ش"_ustr, aText[3].trim());
CPPUNIT_ASSERT_EQUAL(u"\u0651ق"_ustr, aText[4].trim());
CPPUNIT_ASSERT_EQUAL(u"ش"_ustr, aText[5].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[6].trim());
CPPUNIT_ASSERT_EQUAL(u"ة"_ustr, aText[7].trim());
// Verify that the corresponding text segments are positioned roughly equally
auto fnEqualPos
= [](const basegfx::B2DRectangle& stExpected, const basegfx::B2DRectangle& stFound) {
CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMinX(), stFound.getMinX(), /*delta*/ 0.15);
CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMaxX(), stFound.getMaxX(), /*delta*/ 0.15);
};
fnEqualPos(aRect[0], aRect[7]);
fnEqualPos(aRect[1], aRect[6]);
fnEqualPos(aRect[2], aRect[4]);
fnEqualPos(aRect[3], aRect[5]);
}
// tdf#71956 - Tests that glyphs can be individually styled
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf71956)
{
loadFromFile(u"tdf71956-styled-diacritics.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(12), aText.size());
CPPUNIT_ASSERT_EQUAL(u"ه"_ustr, aText[0].trim());
CPPUNIT_ASSERT_EQUAL(u"\u064e\u0651\u0670ل"_ustr, aText[1].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[2].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[3].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[4].trim());
CPPUNIT_ASSERT_EQUAL(u"ل"_ustr, aText[5].trim());
CPPUNIT_ASSERT_EQUAL(u"ل"_ustr, aText[6].trim());
CPPUNIT_ASSERT_EQUAL(u"\u064e\u0651\u0670ل"_ustr, aText[7].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[8].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[9].trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[10].trim());
CPPUNIT_ASSERT_EQUAL(u"ه"_ustr, aText[11].trim());
// Verify that the corresponding text segments are positioned roughly equally
auto fnEqualPos
= [](const basegfx::B2DRectangle& stExpected, const basegfx::B2DRectangle& stFound) {
CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMinX(), stFound.getMinX(), /*delta*/ 0.15);
CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMaxX(), stFound.getMaxX(), /*delta*/ 0.15);
};
fnEqualPos(aRect[0], aRect[11]);
fnEqualPos(aRect[1], aRect[10]);
fnEqualPos(aRect[2], aRect[8]);
fnEqualPos(aRect[3], aRect[9]);
fnEqualPos(aRect[4], aRect[7]);
fnEqualPos(aRect[5], aRect[6]);
}
// tdf#101686 - Verifies that drawinglayer clears RTL flags while drawing Writer text boxes
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf101686)
{
loadFromFile(u"tdf101686.fodt");
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
// Get the first pace
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
CPPUNIT_ASSERT(pPdfPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(3, nPageObjectCount);
std::vector<OUString> aText;
int nTextObjectCount = 0;
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
aText.push_back(pPageObject->getText(pTextPage));
++nTextObjectCount;
}
}
CPPUNIT_ASSERT_EQUAL(3, nTextObjectCount);
CPPUNIT_ASSERT_EQUAL(u"Frame"_ustr, aText[0].trim());
// Without the fix, one of these two will be "xobtxeT"
CPPUNIT_ASSERT_EQUAL(u"Textbox"_ustr, aText[1].trim());
CPPUNIT_ASSERT_EQUAL(u"Textbox"_ustr, aText[2].trim());
}
// tdf#162161 reexport appears to have blank image
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testRexportXnViewColorspace)
{
// We need to enable PDFium import (and make sure to disable after the test)
UsePdfium aGuard;
// Load the PDF and save as PDF
vcl::filter::PDFDocument aDocument;
loadFromFile(u"xnview-colorspace.pdf");
save(TestFilter::PDF_WRITER);
// Parse the export result.
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
CPPUNIT_ASSERT(aDocument.Read(aStream));
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size());
// Get access to the only image on the only page.
vcl::filter::PDFObjectElement* pResources = aPages[0]->LookupObject("Resources"_ostr);
CPPUNIT_ASSERT(pResources);
auto pXObjects
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pResources->Lookup("XObject"_ostr));
CPPUNIT_ASSERT(pXObjects);
CPPUNIT_ASSERT_EQUAL(size_t(1), pXObjects->GetItems().size());
vcl::filter::PDFObjectElement* pXObject
= pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
CPPUNIT_ASSERT(pXObject);
auto pSubResources
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
CPPUNIT_ASSERT(pSubResources);
pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
pSubResources->LookupElement("XObject"_ostr));
CPPUNIT_ASSERT(pXObjects);
CPPUNIT_ASSERT_EQUAL(size_t(1), pXObjects->GetItems().size());
pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
CPPUNIT_ASSERT(pXObject);
pSubResources
= dynamic_cast<vcl::filter::PDFDictionaryElement*>(pXObject->Lookup("Resources"_ostr));
CPPUNIT_ASSERT(pSubResources);
pXObjects = dynamic_cast<vcl::filter::PDFDictionaryElement*>(
pSubResources->LookupElement("XObject"_ostr));
CPPUNIT_ASSERT(pXObjects);
CPPUNIT_ASSERT_EQUAL(size_t(1), pXObjects->GetItems().size());
pXObject = pXObjects->LookupObject(pXObjects->GetItems().begin()->first);
CPPUNIT_ASSERT(pXObject);
// Dig all the way down to this element which is originally
// 8 0 obj
// /DeviceRGB
// endobj
// and appeared blank when we lost the /DeviceRGB line
auto pColorspace = pXObject->LookupObject("ColorSpace"_ostr);
CPPUNIT_ASSERT(pColorspace);
auto pColorSpaceElement = pColorspace->GetNameElement();
CPPUNIT_ASSERT(pColorSpaceElement);
CPPUNIT_ASSERT_EQUAL("DeviceRGB"_ostr, pColorSpaceElement->GetValue());
}
// tdf#157390 - Verifies metrics are correct for PDF export mixing horizontal and vertical CJK
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf157390)
{
loadFromFile(u"tdf157390-overlapping-kanji.fodt");
save(TestFilter::PDF_WRITER);
auto pPdfDocument = parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
CPPUNIT_ASSERT(pPdfPage);
auto pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
// This bug manifests as aberrant character advances in the middle horizontal text.
// Locate the text on the page
auto aStr = u"無い有る有る"_ustr;
int nBaseIndex = 0;
for (int i = 0; i < pTextPage->countChars(); ++i)
{
if (pTextPage->getUnicode(i) == static_cast<unsigned int>(aStr[0]))
{
nBaseIndex = i;
break;
}
}
CPPUNIT_ASSERT(nBaseIndex + 6 <= pTextPage->countChars());
// Extract the character rects
std::vector<basegfx::B2DRectangle> aRects;
for (int i = 0; i < 6; ++i)
{
auto nPageIndex = nBaseIndex + i;
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(aStr[i]), pTextPage->getUnicode(nPageIndex));
aRects.push_back(pTextPage->getCharBox(nPageIndex, /*fPageHeight*/ 1000.0));
}
// Verify glyph advances don't exceed some pessimistic range
double nGuess = aRects.at(0).getMinX();
for (const auto& stRect : aRects)
{
std::cout << stRect << std::endl;
CPPUNIT_ASSERT_GREATER(nGuess - 0.1 * stRect.getWidth(), stRect.getMinX());
CPPUNIT_ASSERT_LESS(nGuess + 0.5 * stRect.getWidth(), stRect.getMinX());
nGuess = stRect.getMaxX();
}
}
// tdf#162205 - Verifies bidi portions on vertical left-to-right pages are rendered correctly
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf162205Ltr)
{
loadFromFile(u"tdf162205-ltr.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(10), aText.size());
CPPUNIT_ASSERT_EQUAL(u"T"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"h"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"e"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"\u0644"_ustr, aText.at(4).trim()); // lam
CPPUNIT_ASSERT_EQUAL(u"\u0627"_ustr, aText.at(5).trim()); // alef
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_EQUAL(u"T"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_EQUAL(u"h"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u"e"_ustr, aText.at(9).trim());
// When the bug occurs, the Arabic portion is rendered far to the left of the English portions.
// Verify that Arabic characters are within range.
auto fnWithinRange = [](const auto& stExpected, const auto& stFound) {
CPPUNIT_ASSERT_LESS(20.0, std::abs(stExpected.getMinX() - stFound.getMinX()));
};
fnWithinRange(aRect.at(0), aRect.at(4));
fnWithinRange(aRect.at(7), aRect.at(5));
}
// tdf#162205 - Verifies bidi portions on vertical left-to-right pages are rendered correctly
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf162205Rtl)
{
loadFromFile(u"tdf162205-rtl.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(10), aText.size());
CPPUNIT_ASSERT_EQUAL(u"T"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"h"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"e"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"\u0644"_ustr, aText.at(4).trim()); // lam
CPPUNIT_ASSERT_EQUAL(u"\u0627"_ustr, aText.at(5).trim()); // alef
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_EQUAL(u"T"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_EQUAL(u"h"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u"e"_ustr, aText.at(9).trim());
// When the bug occurs, the Arabic portion is rendered far to the left of the English portions.
// Verify that Arabic characters are within range.
auto fnWithinRange = [](const auto& stExpected, const auto& stFound) {
CPPUNIT_ASSERT_LESS(20.0, std::abs(stExpected.getMinX() - stFound.getMinX()));
};
fnWithinRange(aRect.at(0), aRect.at(4));
fnWithinRange(aRect.at(7), aRect.at(5));
}
// tdf#162194 - Verifies soft hyphens inside ligatures are rendered correctly.
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf162194SoftHyphen)
{
loadFromFile(u"tdf162194-soft-hyphen.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(4), aText.size());
CPPUNIT_ASSERT_EQUAL(u"Waffle"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"AAA Waf"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"-"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u"fle"_ustr, aText.at(3).trim());
}
// tdf#160786 - Tests that Calc format code with repeat char is measured correctly
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf160786)
{
loadFromFile(u"tdf160786.fods");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(5), aText.size());
CPPUNIT_ASSERT_EQUAL(u"A"_ustr, aText.at(3).trim());
// The currency line is padded with an unknown number of 'f' characters. It doesn't matter how
// many are used, as long as the cell is padded to the expected width. Just verify that this
// text object is the expected one.
CPPUNIT_ASSERT(o3tl::trim(aText.at(4)).starts_with(u"$"));
// The currency cell must not overlap the adjacent cell
CPPUNIT_ASSERT_GREATEREQUAL(aRect.at(3).getMaxX(), aRect.at(4).getMinX());
// The currency cell must be padded to occupy its space reasonably well.
// As a heuristic, ensure the free space is no more than the width of "A"
CPPUNIT_ASSERT_LESS(aRect.at(3).getMaxX() + aRect.at(3).getWidth(), aRect.at(4).getMinX());
}
// tdf#151748 - Textboxes should validate kashida positions
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf151748KashidaSpace)
{
loadFromFile(u"tdf151748.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(17), aText.size());
// Box 1: Not enough room for kashida
CPPUNIT_ASSERT_EQUAL(u"خط تخوردگی"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"وتوسط"_ustr, aText.at(1).trim());
// Box 2: One kashida toward end
CPPUNIT_ASSERT_EQUAL(u"وردگی"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim()); // Kashida
CPPUNIT_ASSERT_EQUAL(u"خط تخ"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_EQUAL(u"وتوسط"_ustr, aText.at(5).trim());
// Box 3: Two kashida
CPPUNIT_ASSERT_EQUAL(u"وردگی"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(7).trim()); // Kashida
CPPUNIT_ASSERT_EQUAL(u"ط تخ"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(9).trim()); // Kashida
CPPUNIT_ASSERT_EQUAL(u"خ"_ustr, aText.at(10).trim());
CPPUNIT_ASSERT_EQUAL(u"وتوسط"_ustr, aText.at(11).trim());
// Box 4: One kashida (text size change)
CPPUNIT_ASSERT_EQUAL(u"خط"_ustr, aText.at(12).trim());
CPPUNIT_ASSERT_EQUAL(u"وردگی"_ustr, aText.at(13).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(14).trim()); // Kashida
CPPUNIT_ASSERT_EQUAL(u"تخ"_ustr, aText.at(15).trim());
CPPUNIT_ASSERT_EQUAL(u"وتوسط"_ustr, aText.at(16).trim());
}
// tdf#163105 - Writer kashida justification should expand spaces
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf163105SwKashidaSpaceExpansion)
{
loadFromFile(u"tdf163105-kashida-spaces.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(5), aText.size());
CPPUNIT_ASSERT_EQUAL(u"یده"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"ه کش"_ustr, aText.at(2).trim()); // This span is whitespace justified
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"نویس"_ustr, aText.at(4).trim());
// Without the fix, this will be less than 25
CPPUNIT_ASSERT_GREATER(150.0, aRect.at(2).getWidth());
}
// tdf#163105 - Writer should use font information when choosing kashida positions
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf163105Writer)
{
loadFromFile(u"tdf163105-writer.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
// The fix allows kashida justification in this document.
// Without the fix, this will be 1.
CPPUNIT_ASSERT_EQUAL(size_t(5), aText.size());
CPPUNIT_ASSERT_EQUAL(u"ارسی"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"تن ف"_ustr, aText.at(2).trim()); // This span is whitespace justified
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"م"_ustr, aText.at(4).trim());
// Without the fix, this will be greater than X
CPPUNIT_ASSERT_LESS(170.0, aRect.at(2).getWidth());
}
// tdf#163105 - Edit Engine should use font information when choosing kashida positions
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf163105Editeng)
{
loadFromFile(u"tdf163105-editeng.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
// The fix allows kashida justification in this document.
// Without the fix, this will be 1.
CPPUNIT_ASSERT_EQUAL(size_t(5), aText.size());
CPPUNIT_ASSERT_EQUAL(u"ارسی"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"تن ف"_ustr, aText.at(2).trim()); // This span is whitespace justified
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"م"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_LESS(170.0, aRect.at(2).getWidth());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf140767SyriacJustification)
{
loadFromFile(u"tdf140767.odt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(11), aText.size());
CPPUNIT_ASSERT_EQUAL(u"ܝ"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"ܺܛ"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"ܰܚ"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_EQUAL(u"ܕ"_ustr, aText.at(5).trim()); // This span is whitespace justified
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_EQUAL(u"ܰܓ"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_EQUAL(u"ܒ"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(9).trim());
CPPUNIT_ASSERT_EQUAL(u"ܰܐ"_ustr, aText.at(10).trim());
// Without kashida justification, this space will be 224.328
CPPUNIT_ASSERT_LESS(90.0, aRect.at(5).getWidth());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf36709FirstLineIndentEm)
{
loadFromFile(u"tdf36709.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(16), aText.size());
// Lines from the Writer portion
CPPUNIT_ASSERT_EQUAL(u"0 em constant size"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(57.256, aRect.at(0).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"1 em constant size"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(69.856, aRect.at(1).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em constant size"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(81.328, aRect.at(2).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"3 em constant size"_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(93.376, aRect.at(3).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(81.328, aRect.at(4).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(89.504, aRect.at(5).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(97.680, aRect.at(6).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(105.856, aRect.at(7).getMinX(), /*delta*/ 2.0);
// Lines from the Edit Engine portion
CPPUNIT_ASSERT_EQUAL(u"0 em constant size"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(62.106, aRect.at(8).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"1 em constant size"_ustr, aText.at(9).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(76.010, aRect.at(9).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em constant size"_ustr, aText.at(10).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(88.778, aRect.at(10).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"3 em constant size"_ustr, aText.at(11).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(102.126, aRect.at(11).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(12).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(88.778, aRect.at(12).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(13).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(97.754, aRect.at(13).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(14).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(106.830, aRect.at(14).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"2 em variable size"_ustr, aText.at(15).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(115.906, aRect.at(15).getMinX(), /*delta*/ 2.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf163913LeftRightMarginEm)
{
loadFromFile(u"tdf163913.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(9), aText.size());
// Lines from the Writer portion
CPPUNIT_ASSERT_EQUAL(u"AAAAAAAAAAA"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.800, aRect.at(0).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"AAAA"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.800, aRect.at(1).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"AAAAAAAA"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(117.400, aRect.at(2).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"AAAAAAA"_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(117.400, aRect.at(3).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.800, aRect.at(4).getMinX(), /*delta*/ 2.0);
// Lines from the Edit Engine portion
CPPUNIT_ASSERT_EQUAL(u"AAAAAAAAAAA"_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.800, aRect.at(5).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"AAAA"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.800, aRect.at(6).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"AAAAAAAA"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(123.750, aRect.at(7).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_EQUAL(u"AAAAAAA"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(123.750, aRect.at(8).getMinX(), /*delta*/ 2.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testFormRoundtrip)
{
// Loads and saves a PDF with filled forms. This checks the forms survive the round-trip.
// We need to enable PDFium import (and make sure to disable after the test)
UsePdfium aGuard;
// Need to properly set the PDF export options
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "UseTaggedPDF", uno::Any(true) } }));
aMediaDescriptor["FilterData"] <<= aFilterData;
loadFromFile(u"FilledUpForm.pdf");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList());
// Parse the round-tripped document with PDFium
auto pPdfDocument = parsePDFExport(vcl::pdf::PDFiumLibrary::get());
// Should be 1 page
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(1);
// 5 annotations means 5 form fields
CPPUNIT_ASSERT_EQUAL(5, pPage->getAnnotationCount());
// Check each form
{
std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::CheckBox,
pAnnotation->getFormFieldType(pPdfDocument.get()));
}
{
std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(1);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::ComboBox,
pAnnotation->getFormFieldType(pPdfDocument.get()));
}
{
std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(2);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField,
pAnnotation->getFormFieldType(pPdfDocument.get()));
}
{
std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(3);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField,
pAnnotation->getFormFieldType(pPdfDocument.get()));
}
{
std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(4);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField,
pAnnotation->getFormFieldType(pPdfDocument.get()));
}
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf162750SmallCapsLigature)
{
loadFromFile(u"tdf162750.fodt");
save(TestFilter::PDF_WRITER);
auto pPdfDocument = parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
CPPUNIT_ASSERT(pPdfPage);
auto pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(3, nPageObjectCount);
std::vector<OUString> aText;
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
aText.push_back(pPageObject->getText(pTextPage));
}
}
CPPUNIT_ASSERT_EQUAL(size_t(3), aText.size());
CPPUNIT_ASSERT_EQUAL(u"ffi"_ustr, aText.at(0).trim());
// Without the fix, this will be "ffi"
CPPUNIT_ASSERT_EQUAL(u"f"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"FI"_ustr, aText.at(2).trim());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf164106SplitReorderedClusters)
{
loadFromFile(u"tdf164106.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(14), aText.size());
auto fnCompareIndices = [&](size_t nSplit, size_t nCombined) {
CPPUNIT_ASSERT_EQUAL(aText.at(nSplit).trim(), aText.at(nCombined).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(aRect.at(nSplit).getMinX(), aRect.at(nCombined).getMinX(),
/*delta*/ 0.2);
CPPUNIT_ASSERT_DOUBLES_EQUAL(aRect.at(nSplit).getMaxX(), aRect.at(nCombined).getMaxX(),
/*delta*/ 0.2);
};
fnCompareIndices(0, 7);
fnCompareIndices(1, 8);
fnCompareIndices(2, 9);
fnCompareIndices(3, 10);
fnCompareIndices(4, 11);
fnCompareIndices(5, 12);
fnCompareIndices(6, 13);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testPDFAttachmentsWithEncryptedFile)
{
// Encrypt the document and use the hybrid mode.
// The original ODF document will be saved to the PDF as an attachment.
uno::Sequence<beans::PropertyValue> aFilterData
= { comphelper::makePropertyValue("IsAddStream", true) };
comphelper::SequenceAsHashMap aMediaDescriptor;
aMediaDescriptor["FilterData"] <<= aFilterData;
loadFromFile(u"SimpleTestDocument.fodt");
save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList(),
/*pPassword*/ "secret");
// Parse the round-tripped document with PDFium
auto pPdfDocument = parsePDFExport(vcl::pdf::PDFiumLibrary::get(), "secret"_ostr);
// Should be 1 page
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
// Should have 1 attachment
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getAttachmentCount());
// Get the attachment
auto pAttachment = pPdfDocument->getAttachment(0);
CPPUNIT_ASSERT(pAttachment);
// Check the filename of the attachment
CPPUNIT_ASSERT_EQUAL(u"Original.odt"_ustr, pAttachment->getName());
// Write the attachment to the buffer
std::vector<sal_uInt8> aBuffer;
CPPUNIT_ASSERT(pAttachment->getFile(aBuffer));
CPPUNIT_ASSERT_GREATER(size_t(0), aBuffer.size());
// Create a temp file and store the content of the attachment
utl::TempFileNamed aTempFile;
aTempFile.EnableKillingFile();
{
SvFileStream aOutputStream(aTempFile.GetURL(), StreamMode::WRITE | StreamMode::TRUNC);
aOutputStream.WriteBytes(aBuffer.data(), aBuffer.size());
}
dispose();
// Load the attached document from the temp file
UnoApiTest::loadFromURL(aTempFile.GetURL());
// Check the content - first paragraph
uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
CPPUNIT_ASSERT(xTextDocument.is());
uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xTextDocument->getText(),
uno::UNO_QUERY);
CPPUNIT_ASSERT(xParagraphEnumAccess.is());
uno::Reference<container::XEnumeration> xParagraphEnum
= xParagraphEnumAccess->createEnumeration();
uno::Reference<text::XTextContent> const xElement(xParagraphEnum->nextElement(),
uno::UNO_QUERY);
CPPUNIT_ASSERT(xElement.is());
uno::Reference<text::XTextRange> const xParagraph(xElement, uno::UNO_QUERY);
CPPUNIT_ASSERT(xParagraph.is());
CPPUNIT_ASSERT_EQUAL(u"This is a test document."_ustr, xParagraph->getString());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTextBoxRuby)
{
// This test exercises a work-in-progress Edit Engine ruby feature.
// It is expected that this test will fail and need to be updated
// as the feature is refined.
loadFromFile(u"textbox-ruby.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(17), aText.size());
// Lines from the Writer portion
CPPUNIT_ASSERT_EQUAL(u"Prototype test for ruby characters in Edit Engine"_ustr,
aText.at(0).trim());
// Lines from the Edit Engine portion
CPPUNIT_ASSERT_EQUAL(u"Left-aligned:"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"top1"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(171.0, aRect.at(2).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(734.0, aRect.at(2).getMaxY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(171.0, aRect.at(3).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(719.0, aRect.at(3).getMinY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"Centered:"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_EQUAL(u"top2"_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(165.0, aRect.at(5).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(715.0, aRect.at(5).getMaxY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(156.0, aRect.at(6).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(701.0, aRect.at(6).getMinY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"Right-aligned:"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_EQUAL(u"top3"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(198.0, aRect.at(8).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(697.0, aRect.at(8).getMaxY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(9).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(178.0, aRect.at(9).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(682.0, aRect.at(9).getMinY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"Below:"_ustr, aText.at(10).trim());
CPPUNIT_ASSERT_EQUAL(u"top4"_ustr, aText.at(11).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(153.0, aRect.at(11).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(667.0, aRect.at(11).getMaxY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"BASE"_ustr, aText.at(12).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(144.0, aRect.at(12).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(668.0, aRect.at(12).getMinY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"Line wrapped: other"_ustr, aText.at(13).trim());
CPPUNIT_ASSERT_EQUAL(u"top5"_ustr, aText.at(14).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(133.0, aRect.at(14).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(650.0, aRect.at(14).getMaxY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"BASE BASE"_ustr, aText.at(15).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(106.0, aRect.at(15).getMinX(), /*delta*/ 5.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(636.0, aRect.at(15).getMinY(), /*delta*/ 5.0);
CPPUNIT_ASSERT_EQUAL(u"other"_ustr, aText.at(16).trim());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf166044ContFootnoteOnlyOnePgNum)
{
loadFromFile(u"tdf166044-cont-footnote-one-pgnum.fodt");
save(TestFilter::PDF_WRITER);
auto pPdfDocument = parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPdfPage);
auto pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(36, nPageObjectCount);
auto pContNoticeObject = pPdfPage->getObject(33);
CPPUNIT_ASSERT(pContNoticeObject);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pContNoticeObject->getType());
// Without the fix, this assert would fail with:
// - Expected: ມະນດ
// - Actual : ມະນດ2
CPPUNIT_ASSERT_EQUAL(u"ມະນດ"_ustr, pContNoticeObject->getText(pTextPage));
auto pPgNumObject = pPdfPage->getObject(34);
CPPUNIT_ASSERT(pPgNumObject);
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Text, pPgNumObject->getType());
CPPUNIT_ASSERT_EQUAL(u"2"_ustr, pPgNumObject->getText(pTextPage));
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf118350StartEndParaAlign)
{
loadFromFile(u"tdf118350-start-end-para-align.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(24), aText.size());
// Lines from the Writer portion
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned left."_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(0).getMinX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned start."_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(1).getMinX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned right."_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(2).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned end."_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(3).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned left"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(5).getMinX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned start"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(6).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned right"_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(9).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(550.0, aRect.at(8).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned end"_ustr, aText.at(10).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(11).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(57.0, aRect.at(11).getMinX(), /*delta*/ 10.0);
// Lines from the Edit Engine portion
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned left."_ustr, aText.at(12).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(12).getMinX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned start."_ustr, aText.at(13).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(13).getMinX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned right."_ustr, aText.at(14).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(14).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is LTR aligned end."_ustr, aText.at(15).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(15).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned left"_ustr, aText.at(16).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(17).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(17).getMinX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned start"_ustr, aText.at(18).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(19).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(18).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned right"_ustr, aText.at(20).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(21).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(537.0, aRect.at(20).getMaxX(), /*delta*/ 10.0);
CPPUNIT_ASSERT_EQUAL(u"This paragraph is RTL aligned end"_ustr, aText.at(22).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(23).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(99.0, aRect.at(23).getMinX(), /*delta*/ 10.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf72640LabelAlignIgnoresDirOff)
{
loadFromFile(u"tdf72640-label-align-ignores-dir-off.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*page*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(10), aText.size());
CPPUNIT_ASSERT_EQUAL(u"1."_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"LTR start"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(80.0, aRect.at(0).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"2"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"RTL start"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(535.0, aRect.at(2).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"1."_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_EQUAL(u"LTR end"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, aRect.at(5).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"2"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u"RTL end"_ustr, aText.at(9).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(585.0, aRect.at(7).getMaxX(), /*delta*/ 1.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf72640LabelAlignIgnoresDirOn)
{
loadFromFile(u"tdf72640-label-align-ignores-dir-on.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*page*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(10), aText.size());
CPPUNIT_ASSERT_EQUAL(u"1."_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"LTR start"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(80.0, aRect.at(0).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"2"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"RTL start"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(585.0, aRect.at(2).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"1."_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_EQUAL(u"LTR end"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, aRect.at(5).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"2"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_EQUAL(u"."_ustr, aText.at(8).trim());
CPPUNIT_ASSERT_EQUAL(u"RTL end"_ustr, aText.at(9).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(535.0, aRect.at(7).getMaxX(), /*delta*/ 1.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, tdf150076BackgroundPdf)
{
loadFromFile(u"tdf150076.odt");
save(TestFilter::PDF_WRITER);
// Parse the export result.
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPdfPage);
CPPUNIT_ASSERT_EQUAL(1, pPdfPage->getObjectCount());
std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPdfPage->getObject(0);
// Without the fix in place, this fails with:
// equality assertion failed
// - Expected: 5
// - Actual : 3
// Where 5 means form (i.e. embedded PDF) and 3 means it's just an image
CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFPageObjectType::Form, pPageObject->getType());
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf155557UnderlineKashidaPortion)
{
loadFromFile(u"tdf155557-underline-kashida-portion.fodt");
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport(vcl::pdf::PDFiumLibrary::get());
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(0);
CPPUNIT_ASSERT(pPdfPage);
std::vector<basegfx::B2DRectangle> aStrokeRect;
int nPageObjectCount = pPdfPage->getObjectCount();
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Path)
{
aStrokeRect.push_back(pPageObject->getBounds());
}
}
CPPUNIT_ASSERT_EQUAL(size_t(2), aStrokeRect.size());
CPPUNIT_ASSERT_DOUBLES_EQUAL(547.8, aStrokeRect.at(0).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(555.9, aStrokeRect.at(0).getMaxX(), /*delta*/ 1.0);
// Without the fix in place, this fails with:
// double equality assertion failed
// - Expected: 56.1
// - Actual : 534.2
// - Delta : 1
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.1, aStrokeRect.at(1).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(548.9, aStrokeRect.at(1).getMaxX(), /*delta*/ 1.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testTdf117941RtlStrikeoutChars)
{
loadFromFile(u"tdf117941-rtl-strike-chars.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*page*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(8), aText.size());
CPPUNIT_ASSERT_EQUAL(u"AAAAAAAA"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.8, aRect.at(0).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(261.2, aRect.at(0).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"//////////////"_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.8, aRect.at(1).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(264.7, aRect.at(1).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"אאאאאאאא"_ustr, aText.at(2).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(354.5, aRect.at(2).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(553.8, aRect.at(2).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"//////////////"_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(353.5, aRect.at(3).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(558.3, aRect.at(3).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"AAAAAAAA"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.8, aRect.at(4).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(261.2, aRect.at(4).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"XXXXXXXXX"_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(56.8, aRect.at(5).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(267.6, aRect.at(5).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"אאאאאאאא"_ustr, aText.at(6).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(354.5, aRect.at(6).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(553.8, aRect.at(6).getMaxX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_EQUAL(u"XXXXXXXXX"_ustr, aText.at(7).trim());
CPPUNIT_ASSERT_DOUBLES_EQUAL(353.1, aRect.at(7).getMinX(), /*delta*/ 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(565.2, aRect.at(7).getMaxX(), /*delta*/ 1.0);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest3, testDropCapPaint)
{
loadFromFile(u"drop_fly_paint.fodt");
save(TestFilter::PDF_WRITER);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
GetPdfPageTextObjectsAndBounds(parsePDFExport(vcl::pdf::PDFiumLibrary::get()), /*nPage*/ 0,
aText, aRect);
CPPUNIT_ASSERT_EQUAL(size_t(7), aText.size());
// Rest of the drop-capped paragraph's 3 forced lines.
CPPUNIT_ASSERT_EQUAL(u"rop Cap Painting Test Document."_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u"Second forced line of the drop cap paragraph."_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"Third forced line of the drop cap paragraph."_ustr, aText.at(2).trim());
// The drop cap glyph itself must be non-whitespace.
CPPUNIT_ASSERT_EQUAL(u"D"_ustr, aText.at(3).trim());
// Must be noticeably taller than a body line (spans 3 lines), not
// just present-but-same-height.
double nDropCapHeight = aRect.at(3).getMaxY() - aRect.at(3).getMinY();
double nBodyLineHeight = aRect.at(2).getMaxY() - aRect.at(2).getMinY();
CPPUNIT_ASSERT(nDropCapHeight > nBodyLineHeight * 2.5);
// Sits left of the wrapped text beside it, not overlapping/underneath.
CPPUNIT_ASSERT(aRect.at(3).getMaxX() < aRect.at(0).getMinX());
CPPUNIT_ASSERT_DOUBLES_EQUAL(61.33, aRect.at(3).getMinX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(94.00, aRect.at(3).getMaxX(), /*delta*/ 2.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(696.45, aRect.at(3).getMinY(), /*delta*/ 2.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(734.43, aRect.at(3).getMaxY(), /*delta*/ 2.0);
// Second paragraph: text wrapping around the anchored fly frame.
CPPUNIT_ASSERT_EQUAL(u"Fly frame paragraph line one."_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_EQUAL(u"Fly frame paragraph line two."_ustr, aText.at(5).trim());
CPPUNIT_ASSERT_EQUAL(u"Fly frame paragraph line three."_ustr, aText.at(6).trim());
// Every line starts well to the right of the drop-cap paragraph's own
// margin, confirming wrap="parallel" is actually pushing text aside.
CPPUNIT_ASSERT(aRect.at(4).getMinX() > aRect.at(0).getMinX() + 10.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(114.53, aRect.at(4).getMinX(), /*delta*/ 2.0);
}
} // end anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */