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 <quikee@gmail.com>
Tested-by: Jenkins
This commit is contained in:
Tomaž Vajngerl
2025-01-22 10:11:56 +01:00
committed by Tomaž Vajngerl
parent 88906ae23d
commit d23c5560c1
5 changed files with 88 additions and 13 deletions
+6
View File
@@ -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);
+1 -1
View File
@@ -46,7 +46,7 @@ public:
OString m_sCreateDate;
sal_Int32 mnPDF_A = 0;
bool mbPDF_UA = false;
sal_Int32 mnPDF_UA = 0;
public:
XmpMetadata();
+4
View File
@@ -835,6 +835,8 @@ private:
::comphelper::Hash m_DocDigest;
std::unordered_map<std::string_view, sal_Int32> m_aNamespacesMap;
// reduce repeated allocations
OStringBuffer updateGraphicsStateLine{256};
OStringBuffer drawBitmapLine{80};
@@ -989,6 +991,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
+69 -10
View File
@@ -111,6 +111,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;
@@ -1334,6 +1336,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
@@ -1933,6 +1941,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);
@@ -1979,10 +2011,21 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
nParentTree = createObject();
if (!nParentTree)
return 0;
aLine.append( "/StructTreeRoot\n"
"/ParentTree "
+ OString::number(nParentTree)
+ " 0 R\n" );
aLine.append("/StructTreeRoot\n");
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<<" );
@@ -2002,8 +2045,16 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
}
else
{
aLine.append( "/StructElem\n"
"/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
@@ -5149,6 +5200,8 @@ bool PDFWriterImpl::emitCatalog()
// emit metadata
sal_Int32 nMetadataObject = emitDocumentMetadata();
emitNamespaces();
sal_Int32 nStructureDict = 0;
if(m_aStructure.size() > 1)
{
@@ -5341,12 +5394,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 )
@@ -5359,7 +5413,8 @@ bool PDFWriterImpl::emitCatalog()
aLine.append( "/FitWindow true\n" );
if( m_aContext.CenterWindow )
aLine.append( "/CenterWindow true\n" );
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\n" );
if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
aLine.append( "/Direction/R2L\n" );
@@ -5877,7 +5932,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);
+8 -2
View File
@@ -206,7 +206,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
@@ -289,7 +289,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);
@@ -298,6 +298,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();
}