vcl/inc/pdf/pdfwriter_impl.hxx    |    5 ++
 vcl/source/gdi/pdfwriter_impl.cxx |   80 ++++++++++++++++++++++++++++++++------
 2 files changed, 73 insertions(+), 12 deletions(-)

New commits:
commit c5eea0a2ed090dbc243e8330d8d99ca94bacf691
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Fri Mar 10 15:14:28 2023 +0100
Commit:     Caolán McNamara <caol...@redhat.com>
CommitDate: Thu Mar 16 11:44:23 2023 +0000

    tdf#152218 vcl: PDF/UA export: produce ID for footnote/endnote
    
    There is this mysterious requirement:
    
      7.9 Notes and references
      Footnotes and endnotes shall be tagged with a Note tag. Each note
      tag shall have a unique entry in the ID key as described in ISO
      32000-1:2008, 14.7.2, Table 323.
    
    Then also an IDTree is required; PDF 1.7, 10.6.1 Structure Hierarchy:
    
      (Required if any structure elements have element identifiers)
    
    The point of this is entirely unclear to me, it looks like neither
    poppler nor pdfium even have code to read the IDTree, and structure
    elements already always have an object id by which they can be
    referenced, so why add a second ID?
    
    Alas veraPDF complains if it is missing:
    
      Specification: ISO 14289-1:2014, Clause: 7.9, Test number: 1
      Note tag shall have ID entry
    
    Change-Id: Ia8e52c46312c7dbb3bf7ef8635321200e5de24ae
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148645
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    (cherry picked from commit 4250f334de729360567ec9bd88aed06fbd823dc4)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148764
    Reviewed-by: Caolán McNamara <caol...@redhat.com>

diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx
index e8ce5163177f..ab0ff5b4f86e 100644
--- a/vcl/inc/pdf/pdfwriter_impl.hxx
+++ b/vcl/inc/pdf/pdfwriter_impl.hxx
@@ -24,6 +24,7 @@
 #include <map>
 #include <list>
 #include <unordered_map>
+#include <unordered_set>
 #include <memory>
 #include <string_view>
 #include <vector>
@@ -756,6 +757,8 @@ private:
     /* role map of struct tree root */
     std::unordered_map< OString, OString >
                                         m_aRoleMap;
+    /* structure elements (object ids) that should have ID */
+    std::unordered_set<sal_Int32> m_StructElemObjsWithID;
 
     /* contains all widgets used in the PDF
      */
@@ -980,6 +983,8 @@ i12626
     sal_Int32 emitStructure( PDFStructureElement& rEle );
     // writes structure parent tree
     sal_Int32 emitStructParentTree( sal_Int32 nTreeObject );
+    // writes structure IDTree
+    sal_Int32 emitStructIDTree(sal_Int32 nTreeObject);
     // writes page tree and catalog
     bool emitCatalog();
     // writes signature dictionary object
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx 
b/vcl/source/gdi/pdfwriter_impl.cxx
index 268946426990..698e171963a8 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -111,6 +111,18 @@ constexpr sal_Int32 pointToPixel(double pt)
     return sal_Int32(pt * fDivisor);
 }
 
+void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
+{
+    aLine.append(nObjectID);
+    aLine.append(" 0 obj\n");
+}
+
+void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
+{
+    aLine.append(nObjectID);
+    aLine.append(" 0 R ");
+}
+
 void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
 {
     static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
@@ -1805,6 +1817,43 @@ sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 
nObject )
     return nObject;
 }
 
+// every structure element already has a unique object id - just use it for ID
+static OString GenerateID(sal_Int32 const nObjectId)
+{
+    return "id" + OString::number(nObjectId);
+}
+
+sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
+{
+    // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 
10.15
+    if (nObject < 0)
+    {
+        return nObject;
+    }
+    // the name tree entries must be sorted lexicographically.
+    std::map<OString, sal_Int32> ids;
+    for (auto n : m_StructElemObjsWithID)
+    {
+        ids.emplace(GenerateID(n), n);
+    }
+    OStringBuffer buf;
+    appendObjectID(nObject, buf);
+    buf.append("<</Names [\n");
+    for (auto const& it : ids)
+    {
+        appendLiteralStringEncrypt(it.first, nObject, buf);
+        buf.append(" ");
+        appendObjectReference(it.second, buf);
+        buf.append("\n");
+    }
+    buf.append("] >>\nendobj\n\n");
+
+    CHECK_RETURN( updateObject(nObject) );
+    CHECK_RETURN( writeBuffer(buf.getStr(), buf.getLength()) );
+
+    return nObject;
+}
+
 const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
 {
     static std::map< PDFWriter::StructAttribute, const char* > 
aAttributeStrings;
@@ -2088,6 +2137,7 @@ sal_Int32 PDFWriterImpl::emitStructure( 
PDFStructureElement& rEle )
     aLine.append( " 0 obj\n"
                   "<</Type" );
     sal_Int32 nParentTree = -1;
+    sal_Int32 nIDTree = -1;
     if( rEle.m_nOwnElement == rEle.m_nParentElement )
     {
         nParentTree = createObject();
@@ -2109,6 +2159,13 @@ sal_Int32 PDFWriterImpl::emitStructure( 
PDFStructureElement& rEle )
             }
             aLine.append( ">>\n" );
         }
+        if (!m_StructElemObjsWithID.empty())
+        {
+            nIDTree = createObject();
+            aLine.append("/IDTree ");
+            appendObjectReference(nIDTree, aLine);
+            aLine.append("\n");
+        }
     }
     else
     {
@@ -2118,6 +2175,11 @@ sal_Int32 PDFWriterImpl::emitStructure( 
PDFStructureElement& rEle )
             aLine.append( rEle.m_aAlias );
         else
             aLine.append( getStructureTag( rEle.m_eType ) );
+        if (m_StructElemObjsWithID.find(rEle.m_nObject) != 
m_StructElemObjsWithID.end())
+        {
+            aLine.append("\n/ID ");
+            appendLiteralStringEncrypt(GenerateID(rEle.m_nObject), 
rEle.m_nObject, aLine);
+        }
         aLine.append( "\n"
                       "/P " );
         aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
@@ -2211,6 +2273,7 @@ sal_Int32 PDFWriterImpl::emitStructure( 
PDFStructureElement& rEle )
     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
 
     CHECK_RETURN( emitStructParentTree( nParentTree ) );
+    CHECK_RETURN( emitStructIDTree(nIDTree) );
 
     return rEle.m_nObject;
 }
@@ -3780,18 +3843,6 @@ void appendAnnotationRect(tools::Rectangle const & 
rRectangle, OStringBuffer & a
     aLine.append("] ");
 }
 
-void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
-{
-    aLine.append(nObjectID);
-    aLine.append(" 0 obj\n");
-}
-
-void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
-{
-    aLine.append(nObjectID);
-    aLine.append(" 0 R ");
-}
-
 } // end anonymous namespace
 
 void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry 
const & rNote)
@@ -10653,6 +10704,11 @@ sal_Int32 PDFWriterImpl::beginStructureElement( 
PDFWriter::StructElement eType,
         rEle.m_nObject      = createObject();
         // update parent's kids list
         m_aStructure[ rEle.m_nParentElement 
].m_aKids.emplace_back(rEle.m_nObject);
+        // ISO 14289-1:2014, Clause: 7.9
+        if (rEle.m_eType == PDFWriter::Note)
+        {
+            m_StructElemObjsWithID.insert(rEle.m_nObject);
+        }
     }
     return nNewId;
 }

Reply via email to