From 7a907965cc6246ab644be92811e35d9f73a90e86 Mon Sep 17 00:00:00 2001 From: Michael Stahl Date: Tue, 7 Mar 2023 10:40:23 +0100 Subject: vcl,sw: PDF/UA export: tag headers and footers as required ISO 14289-1:2014 has one requirement for specific tagging of artifacts: 7.8 Page headers and footers Running headers and footers shall be identified as Pagination artifacts and shall be classified as either Header or Footer subtypes as per ISO 32000-1:2008, 14.8.2.2.2, Table 330. It was not immediately obvious how to implement this but the functions used for tunnelling structure element attributes through MetaFile can be used for this purpose as well with a few tweaks. Change-Id: I19a3192b1b56b82ed11972c4bbe8d20ab13567be Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148387 Tested-by: Jenkins Reviewed-by: Michael Stahl --- include/vcl/pdfwriter.hxx | 6 +++ sw/source/core/text/EnhancedPDFExportHelper.cxx | 12 ++++++ vcl/qa/cppunit/pdfexport/pdfexport.cxx | 22 +++++++++- vcl/source/gdi/pdfwriter_impl.cxx | 55 ++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index 8e3c139e8943..1e21fae1c5bd 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -135,6 +135,9 @@ public: enum StructAttribute { + // Artifacts + Type, Subtype, + Placement, WritingMode, SpaceBefore, SpaceAfter, StartIndent, EndIndent, TextIndent, TextAlign, Width, Height, BlockAlign, InlineAlign, LineHeight, BaselineShift, TextDecorationType, ListNumbering, @@ -158,6 +161,9 @@ public: { Invalid, NONE, + // Artifacts + Pagination, Layout, Page, Background, + Header, Footer, Watermark, // Placement Block, Inline, Before, After, Start, End, // WritingMode diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 1dde77106624..e9f8d310d3fe 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -602,6 +602,18 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) } break; + case vcl::PDFWriter::NonStructElement: + if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame()) + { + // ISO 14289-1:2014, Clause: 7.8 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination); + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype, + pFrame->IsHeaderFrame() + ? vcl::PDFWriter::Header + : vcl::PDFWriter::Footer); + } + break; + default : break; } diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 4cf31e708220..30d9b5513c97 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -3050,7 +3050,8 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf139736) // Enable PDF/UA uno::Sequence aFilterData( - comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); + comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) }, + { "SelectPdfVersion", uno::Any(sal_Int32(17)) } })); aMediaDescriptor["FilterData"] <<= aFilterData; saveAsPDF(u"tdf139736-1.odt"); @@ -3081,6 +3082,8 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf139736) { Default, Artifact, + ArtifactProps1, + ArtifactProps2, Tagged } state = Default; @@ -3107,6 +3110,23 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf139736) state = Artifact; ++nArtifacts; } + else if (o3tl::starts_with(line, "/Artifact <<")) + { + CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state); + // check header/footer properties + CPPUNIT_ASSERT_EQUAL(std::string_view("/Type/Pagination"), line.substr(12)); + state = ArtifactProps1; + ++nArtifacts; + } + else if (state == ArtifactProps1) + { + CPPUNIT_ASSERT_EQUAL(std::string_view("/Subtype/Header"), line); + state = ArtifactProps2; + } + else if (state == ArtifactProps2 && line == ">> BDC") + { + state = Artifact; + } else if (line == "/Standard<>BDC") { CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state); diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index bbb904458627..a841842a6942 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -1830,6 +1830,8 @@ const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan"; aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan"; aAttributeStrings[ PDFWriter::Scope ] = "Scope"; + aAttributeStrings[ PDFWriter::Type ] = "Type"; + aAttributeStrings[ PDFWriter::Subtype ] = "Subtype"; aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation"; } @@ -1869,6 +1871,13 @@ const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue aValueStrings[ PDFWriter::Row ] = "Row"; aValueStrings[ PDFWriter::Column ] = "Column"; aValueStrings[ PDFWriter::Both ] = "Both"; + aValueStrings[ PDFWriter::Pagination ] = "Pagination"; + aValueStrings[ PDFWriter::Layout ] = "Layout"; + aValueStrings[ PDFWriter::Page ] = "Page"; + aValueStrings[ PDFWriter::Background ] = "Background"; + aValueStrings[ PDFWriter::Header ] = "Header"; + aValueStrings[ PDFWriter::Footer ] = "Footer"; + aValueStrings[ PDFWriter::Watermark ] = "Watermark"; aValueStrings[ PDFWriter::Disc ] = "Disc"; aValueStrings[ PDFWriter::Circle ] = "Circle"; aValueStrings[ PDFWriter::Square ] = "Square"; @@ -10541,8 +10550,24 @@ void PDFWriterImpl::beginStructureElementMCSeq() ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence ) { - OString aLine = "/Artifact BMC\n"; + OString aLine = "/Artifact "; writeBuffer( aLine.getStr(), aLine.getLength() ); + // emit property list if requested + OStringBuffer buf; + for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes) + { + appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false); + } + if (buf.isEmpty()) + { + writeBuffer("BMC\n", 4); + } + else + { + writeBuffer("<<", 2); + writeBuffer(buf.getStr(), buf.getLength()); + writeBuffer(">> BDC\n", 7); + } // mark element MC sequence as open m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true; } @@ -10841,7 +10866,12 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr return false; bool bInsert = false; - if( m_nCurrentStructElement > 0 && m_bEmitStructure ) + if (m_nCurrentStructElement > 0 + && (m_bEmitStructure + // allow it for topmost non-structured element + || (m_aContext.Tagged + && 0 < m_aStructure[m_nCurrentStructElement].m_nParentElement + && m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_eType != PDFWriter::NonStructElement))) { PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; switch( eAttr ) @@ -11008,6 +11038,27 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr } } break; + case PDFWriter::Type: + if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page) + // + Background for PDF >= 1.7 + { + if (eType == PDFWriter::NonStructElement) + { + bInsert = true; + } + } + break; + case PDFWriter::Subtype: + if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark) + { + if (eType == PDFWriter::NonStructElement + && m_aContext.Version != PDFWriter::PDFVersion::PDF_A_1 + && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) + { + bInsert = true; + } + } + break; case PDFWriter::ListNumbering: if( eVal == PDFWriter::NONE || eVal == PDFWriter::Disc || -- cgit