tdf#142129 vcl: add unit test

Reportedly bug was fixed by commit
5fc6a601d7a1978db291fd0f7dcec638a7c25651

Change-Id: Iee32cacff0c939bdf498e9dc8102eb2d22a5a1c9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121776
Tested-by: Michael Stahl <michael.stahl@allotropia.de>
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
(cherry picked from commit 992f878e8c3424f7ce96a78ccf3a316b29ffd9ec)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121763
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx
index 2686654..4b5d7326 100644
--- a/include/vcl/filter/pdfdocument.hxx
+++ b/include/vcl/filter/pdfdocument.hxx
@@ -546,6 +546,7 @@
    //@{
    /// Decode a hex dump.
    static std::vector<unsigned char> DecodeHexString(PDFHexStringElement const* pElement);
    static OUString DecodeHexStringUTF16BE(PDFHexStringElement const& rElement);
    static OString ReadKeyword(SvStream& rStream);
    static size_t FindStartXRef(SvStream& rStream);
    void ReadXRef(SvStream& rStream);
diff --git a/vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odt b/vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odt
new file mode 100644
index 0000000..05e2bb1
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/WG7100-Preface.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/master.odm b/vcl/qa/cppunit/pdfexport/data/master.odm
new file mode 100644
index 0000000..7401635
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/master.odm
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
index bb20360..0da9bed 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx
@@ -2791,6 +2791,93 @@
    CPPUNIT_ASSERT_EQUAL(OString("1"), aPdfUaPart);
}

CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142129)
{
    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "master.odm";
    mxComponent = loadFromDesktop(aURL);

    // update linked section
    dispatchCommand(mxComponent, ".uno:UpdateAllLinks", {});

    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
    utl::MediaDescriptor aMediaDescriptor;
    aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");

    // Enable Outlines export
    uno::Sequence<beans::PropertyValue> aFilterData(
        comphelper::InitPropertySequence({ { "ExportBookmarks", uno::Any(true) } }));
    aMediaDescriptor["FilterData"] <<= aFilterData;
    xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());

    // Parse the export result.
    vcl::filter::PDFDocument aDocument;
    SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
    CPPUNIT_ASSERT(aDocument.Read(aStream));

    auto* pCatalog = aDocument.GetCatalog();
    CPPUNIT_ASSERT(pCatalog);
    auto* pCatalogDictionary = pCatalog->GetDictionary();
    CPPUNIT_ASSERT(pCatalogDictionary);
    auto* pOutlinesObject = pCatalogDictionary->LookupObject("Outlines");
    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"));
    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(OUString(u"Preface"), ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
                                                   *dynamic_cast<vcl::filter::PDFHexStringElement*>(
                                                       pFirstD->LookupElement("Title"))));

    auto* pFirst1
        = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirstD->LookupElement("First"));
    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(
        OUString(u"Who is this book for?"),
        ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
            *dynamic_cast<vcl::filter::PDFHexStringElement*>(pFirst1D->LookupElement("Title"))));

    auto* pFirst2
        = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst1D->LookupElement("Next"));
    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(
        OUString(u"What\u2019s in this book?"),
        ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
            *dynamic_cast<vcl::filter::PDFHexStringElement*>(pFirst2D->LookupElement("Title"))));

    auto* pFirst3
        = dynamic_cast<vcl::filter::PDFReferenceElement*>(pFirst2D->LookupElement("Next"));
    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(
        OUString(u"Minimum requirements for using LibreOffice"),
        ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(
            *dynamic_cast<vcl::filter::PDFHexStringElement*>(pFirst3D->LookupElement("Title"))));

    CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
                         pFirst3D->LookupElement("Next"));
    CPPUNIT_ASSERT_EQUAL(static_cast<vcl::filter::PDFElement*>(nullptr),
                         pFirstD->LookupElement("Next"));
}

CPPUNIT_TEST_FIXTURE(PdfExportTest, testPdfImageRotate180)
{
    // Create an empty document.
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
index e8bca35..f5fc63b 100644
--- a/vcl/source/filter/ipdf/pdfdocument.cxx
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -2052,6 +2052,23 @@
    return svl::crypto::DecodeHexString(pElement->GetValue());
}

OUString PDFDocument::DecodeHexStringUTF16BE(PDFHexStringElement const& rElement)
{
    std::vector<unsigned char> const encoded(DecodeHexString(&rElement));
    // Text strings can be PDF-DocEncoding or UTF-16BE with mandatory BOM;
    // only the latter supported is here
    if (encoded.size() < 2 || encoded[0] != 0xFE || encoded[1] != 0xFF || (encoded.size() & 1) != 0)
    {
        return OUString();
    }
    OUStringBuffer buf(static_cast<unsigned int>(encoded.size() - 2));
    for (size_t i = 2; i < encoded.size(); i += 2)
    {
        buf.append(sal_Unicode((static_cast<sal_uInt16>(encoded[i]) << 8) | encoded[i + 1]));
    }
    return buf.makeStringAndClear();
}

PDFCommentElement::PDFCommentElement(PDFDocument& rDoc)
    : m_rDoc(rDoc)
{