vcl/inc/pdf/COSWriter.hxx | 6 ++ vcl/inc/pdf/XmpMetadata.hxx | 2 vcl/inc/pdf/pdfwriter_impl.hxx | 4 + vcl/source/gdi/pdfwriter_impl.cxx | 81 ++++++++++++++++++++++++++++++++------ vcl/source/pdf/XmpMetadata.cxx | 10 +++- 5 files changed, 89 insertions(+), 14 deletions(-)
New commits: commit ec40bb7725ce74ebac83baafb3d063dfa53d0bee Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Wed Jan 22 13:19:43 2025 +0900 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Feb 6 08:47:49 2025 +0100 tdf#160196 add support for PDF/UA-2 and NS for struct. elem. This adds support for PDF/UA-2 which is used when using PDF 2.0 (and PDF/UA-1 when using PDF 1.x). PDF/UA-2 requires that the PDF 2.0 introduced namespaces are set for the structure elements, so this also adds support for the namespaces with always setting the PDF 2.0 namespace for all the structured elements (we don't use others anyway). Change-Id: Ifbd8f8126d9bcc0d4dcd06a5f56bacdb0804adbd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180565 Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> Tested-by: Jenkins Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180876 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/vcl/inc/pdf/COSWriter.hxx b/vcl/inc/pdf/COSWriter.hxx index b80f0f84f424..ae3c4c876f03 100644 --- a/vcl/inc/pdf/COSWriter.hxx +++ b/vcl/inc/pdf/COSWriter.hxx @@ -117,6 +117,12 @@ public: mrBuffer.append(")"); } + void writeKeyAndLiteral(std::string_view key, std::string_view value) + { + mrBuffer.append(key); + writeLiteral(value); + } + void writeLiteralEncrypt(std::u16string_view value, sal_Int32 nObject, rtl_TextEncoding nEncoding = RTL_TEXTENCODING_ASCII_US); diff --git a/vcl/inc/pdf/XmpMetadata.hxx b/vcl/inc/pdf/XmpMetadata.hxx index 452ad7f85267..a7cf5a223ff8 100644 --- a/vcl/inc/pdf/XmpMetadata.hxx +++ b/vcl/inc/pdf/XmpMetadata.hxx @@ -46,7 +46,7 @@ public: OString m_sCreateDate; sal_Int32 mnPDF_A = 0; - bool mbPDF_UA = false; + sal_Int32 mnPDF_UA = 0; public: XmpMetadata(); diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index 8f738484550f..0da6ad7698af 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -824,6 +824,8 @@ private: ::comphelper::Hash m_DocDigest; + std::unordered_map<std::string_view, sal_Int32> m_aNamespacesMap; + sal_uInt64 getCurrentFilePosition() { sal_uInt64 nPosition{}; @@ -974,6 +976,8 @@ private: //check if internal dummy container are needed in the structure elements void addInternalStructureContainer( PDFStructureElement& rEle ); //<---i94258 + // writes namespaces + void emitNamespaces(); // writes document structure sal_Int32 emitStructure( PDFStructureElement& rEle ); // writes structure parent tree diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 851663a9e101..871bc328ac87 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -110,6 +110,8 @@ static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSIO namespace { +constexpr std::string_view constNamespacePDF2("http://iso.org/pdf2/ssn"); + constexpr sal_Int32 nLog10Divisor = 3; constexpr double fDivisor = 1000.0; @@ -1335,6 +1337,12 @@ PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, m_aContext.Tagged = true; } + // Add common PDF 2.0 namespace when we are using PDF 2.0 + if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0) + { + m_aNamespacesMap.emplace(constNamespacePDF2, createObject()); + } + if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 ); else @@ -1911,6 +1919,30 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) return aRet.makeStringAndClear(); } +// Write the namespace objects to the stream +void PDFWriterImpl::emitNamespaces() +{ + if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) + return; + + for (auto&[sNamespace, nObject] : m_aNamespacesMap) + { + if (!updateObject(nObject)) + return; + + COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor); + aWriter.startObject(nObject); + aWriter.startDict(); + aWriter.write("/Type", "/Namespace"); + aWriter.writeKeyAndLiteral("/NS", sNamespace); + aWriter.endDict(); + aWriter.endObject(); + + if (!writeBuffer(aWriter.getLine())) + m_aNamespacesMap[sNamespace] = 0; + } +} + sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) { assert(rEle.m_nOwnElement == 0 || rEle.m_oType); @@ -1955,11 +1987,22 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) if( rEle.m_nOwnElement == rEle.m_nParentElement ) { nParentTree = createObject(); - CHECK_RETURN( nParentTree ); - aLine.append( "/StructTreeRoot " - "/ParentTree " - + OString::number(nParentTree) - + " 0 R " ); + if (!nParentTree) + return 0; + aLine.append("/StructTreeRoot "); + aWriter.writeKeyAndReference("/ParentTree", nParentTree); + + // Write the reference to the PDF 2.0 namespace + if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) + { + auto iterator = m_aNamespacesMap.find(constNamespacePDF2); + if (iterator != m_aNamespacesMap.end()) + { + aLine.append("/Namespaces ["); + aWriter.writeReference(iterator->second); + aLine.append("]"); + } + } if( ! m_aRoleMap.empty() ) { aLine.append( "/RoleMap<<" ); @@ -1979,8 +2022,16 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) } else { - aLine.append( "/StructElem " - "/S/" ); + aLine.append("/StructElem"); + + // Write the reference to the PDF 2.0 namespace + if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) + { + auto iterator = m_aNamespacesMap.find(constNamespacePDF2); + if (iterator != m_aNamespacesMap.end()) + aWriter.writeKeyAndReference("/NS", iterator->second); + } + aLine.append("/S/"); if( !rEle.m_aAlias.isEmpty() ) aLine.append( rEle.m_aAlias ); else @@ -5099,6 +5150,8 @@ bool PDFWriterImpl::emitCatalog() // emit metadata sal_Int32 nMetadataObject = emitDocumentMetadata(); + emitNamespaces(); + sal_Int32 nStructureDict = 0; if(m_aStructure.size() > 1) { @@ -5304,12 +5357,13 @@ bool PDFWriterImpl::emitCatalog() } // viewer preferences, if we had some, then emit - if( m_aContext.HideViewerToolbar || + if (m_aContext.HideViewerToolbar || (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_aContext.HideViewerMenubar || m_aContext.HideViewerWindowControls || m_aContext.FitWindow || m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) || - m_aContext.OpenInFullScreenMode ) + m_aContext.OpenInFullScreenMode || + m_bIsPDF_UA) { aLine.append( "/ViewerPreferences<<" ); if( m_aContext.HideViewerToolbar ) @@ -5322,7 +5376,8 @@ bool PDFWriterImpl::emitCatalog() aLine.append( "/FitWindow true " ); if( m_aContext.CenterWindow ) aLine.append( "/CenterWindow true " ); - if (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) + // PDF/UA-2 requires /DisplayDocTitle set to true + if ((!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_bIsPDF_UA) aLine.append( "/DisplayDocTitle true " ); if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) aLine.append( "/Direction/R2L " ); @@ -5835,7 +5890,11 @@ sal_Int32 PDFWriterImpl::emitDocumentMetadata() if (m_nPDFA_Version > 0) aMetadata.mnPDF_A = m_nPDFA_Version; - aMetadata.mbPDF_UA = m_bIsPDF_UA; + if (m_bIsPDF_UA) + { + // If PDF 2.0+ we need to use PDF/UA-2 otherwise PDF/UA-1 + aMetadata.mnPDF_UA = (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) ? 2 : 1; + } lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle); lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor); diff --git a/vcl/source/pdf/XmpMetadata.cxx b/vcl/source/pdf/XmpMetadata.cxx index 24909ae9a519..6ebaad66add6 100644 --- a/vcl/source/pdf/XmpMetadata.cxx +++ b/vcl/source/pdf/XmpMetadata.cxx @@ -194,7 +194,7 @@ void XmpMetadata::write() } // PDF/UA - if (mbPDF_UA) + if (mnPDF_UA > 0) { if (mnPDF_A != 0) { // tdf#157517 PDF/A extension schema is required @@ -277,7 +277,7 @@ void XmpMetadata::write() aXmlWriter.endElement(); // pdfaExtension:schemas aXmlWriter.endElement(); // rdf:Description } - OString sPdfUaVersion = OString::number(1); + OString sPdfUaVersion = OString::number(mnPDF_UA); aXmlWriter.startElement("rdf:Description"); aXmlWriter.attribute("rdf:about", ""_ostr); aXmlWriter.attribute("xmlns:pdfuaid", "http://www.aiim.org/pdfua/ns/id/"_ostr); @@ -286,6 +286,12 @@ void XmpMetadata::write() aXmlWriter.content(sPdfUaVersion); aXmlWriter.endElement(); + if (mnPDF_UA == 2) + { + aXmlWriter.startElement("pdfuaid:rev"); + aXmlWriter.content("2024"_ostr); + aXmlWriter.endElement(); + } aXmlWriter.endElement(); }