custom metadata

This commit is contained in:
Ernesto Ruge
2024-12-31 15:14:39 +01:00
committed by Guillaume Ayoub
parent 9ec1ca465c
commit b6d403315a
4 changed files with 113 additions and 18 deletions
+1
View File
@@ -1140,6 +1140,7 @@ def assert_meta(html, **meta):
meta.setdefault('attachments', [])
meta.setdefault('lang', None)
meta.setdefault('custom', {})
meta.setdefault('rdf_metadata_generator', None)
assert vars(FakeHTML(string=html).render().metadata) == meta
+32
View File
@@ -716,3 +716,35 @@ def test_bleed(style, media, bleed, trim):
assert f'/MediaBox {str(media).replace(",", "")}'.encode() in pdf
assert f'/BleedBox {str(bleed).replace(",", "")}'.encode() in pdf
assert f'/TrimBox {str(trim).replace(",", "")}'.encode() in pdf
@assert_no_logs
def test_default_rdf_metadata() -> None:
pdf_document = FakeHTML(string='<body>test</body>').render()
pdf_document.metadata.title = None
pdf_bytes = pdf_document.write_pdf(
pdf_variant='pdf/a-3b',
pdf_identifier=b'example-bytes',
uncompressed_pdf=True,
)
assert b'<rdf:RDF xmlns:pdf="http://ns.adobe.com/pdf/1.3/"' in pdf_bytes
@assert_no_logs
def test_custom_rdf_metadata() -> None:
def rdf_metadata_generator(*args, **kwargs) -> bytes:
return b'TEST_METADATA'
pdf_document = FakeHTML(string='<body>test</body>').render()
pdf_document.metadata.title = None
pdf_document.metadata.rdf_metadata_generator = rdf_metadata_generator
pdf_bytes = pdf_document.write_pdf(
pdf_variant='pdf/a-3b',
pdf_identifier=b'example-bytes',
uncompressed_pdf=True,
)
assert b'TEST_METADATA' in pdf_bytes
+30 -5
View File
@@ -4,8 +4,9 @@ import functools
import io
from hashlib import md5
from pathlib import Path
from typing import Callable, Optional
from . import CSS, DEFAULT_OPTIONS
from . import CSS, DEFAULT_OPTIONS, Attachment
from .anchors import gather_anchors, make_page_bookmark_tree
from .css import get_all_computed_styles
from .css.counters import CounterStyle
@@ -105,12 +106,33 @@ class DocumentMetadata:
"""Meta-information belonging to a whole :class:`Document`.
New attributes may be added in future versions of WeasyPrint.
"""
title: Optional[str]
authors: list[str]
description: Optional[str]
keywords: list[str]
generator: Optional[str]
created: Optional[str]
modified: Optional[str]
attachments: list[Attachment]
lang: Optional[str]
custom: dict
rdf_metadata_generator: Optional[Callable] = None
def __init__(self, title=None, authors=None, description=None,
keywords=None, generator=None, created=None, modified=None,
attachments=None, lang=None, custom=None):
def __init__(
self,
title: Optional[str] = None,
authors: Optional[list[str]] = None,
description: Optional[str] = None,
keywords: Optional[list[str]] = None,
generator: Optional[str] = None,
created: Optional[str] = None,
modified: Optional[str] = None,
attachments: Optional[list[Attachment]] = None,
lang: Optional[str] = None,
custom: Optional[dict] = None,
rdf_metadata_generator: Optional[Callable] = None,
):
#: The title of the document, as a string or :obj:`None`.
#: Extracted from the ``<title>`` element in HTML
#: and written to the ``/Title`` info field in PDF.
@@ -156,6 +178,9 @@ class DocumentMetadata:
#: Custom metadata, as a dict whose keys are the metadata names and
#: values are the metadata values.
self.custom = custom or {}
#: Custom RDF metadata generator, which will replace the default generator.
#: The function should return bytes containing an RDF XML.
self.rdf_metadata_generator = rdf_metadata_generator
class DiskCache:
+50 -13
View File
@@ -1,13 +1,20 @@
"""PDF metadata stream generation."""
"""
PDF metadata stream generation.
"""
from typing import TYPE_CHECKING
from xml.etree.ElementTree import Element, SubElement, register_namespace, tostring
import pydyf
from .. import __version__
from weasyprint import __version__
if TYPE_CHECKING:
from weasyprint.document import DocumentMetadata
# XML namespaces used for metadata
NS = {
NS: dict[str, str] = {
'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'dc': 'http://purl.org/dc/elements/1.1/',
'xmp': 'http://ns.adobe.com/xap/1.0/',
@@ -19,13 +26,50 @@ for key, value in NS.items():
register_namespace(key, value)
def add_metadata(pdf, metadata, variant, version, conformance, compress):
def add_metadata(
pdf: pydyf.PDF,
metadata: 'DocumentMetadata',
variant: str,
version: str,
conformance: str,
compress: bool,
) -> None:
"""Add PDF stream of metadata.
Described in ISO-32000-1:2008, 14.3.2.
"""
# Add metadata
# Add metadata. If `DocumentMetadata` has a generator, we will use it,
# otherwise we will use the default generator.
if metadata.rdf_metadata_generator is None:
xml_data = generate_rdf_metadata(metadata, variant, version, conformance)
else:
xml_data = metadata.rdf_metadata_generator(
metadata=metadata,
variant=variant,
version=version,
conformance=conformance,
)
header = b'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'
footer = b'<?xpacket end="r"?>'
stream_content = b'\n'.join((header, xml_data, footer))
extra = {'Type': '/Metadata', 'Subtype': '/XML'}
metadata = pydyf.Stream([stream_content], extra, compress)
pdf.add_object(metadata)
pdf.catalog['Metadata'] = metadata.reference
def generate_rdf_metadata(
metadata: 'DocumentMetadata',
variant: str,
version: str,
conformance: str,
) -> bytes:
"""Generates RDF metadata. Might be replaced by
DocumentMetadata.rdf_matadata_generator().
"""
namespace = f'pdf{variant}id'
rdf = Element(f'{{{NS["rdf"]}}}RDF')
@@ -82,11 +126,4 @@ def add_metadata(pdf, metadata, variant, version, conformance, compress):
element.attrib[f'{{{NS["rdf"]}}}about'] = ''
element = SubElement(element, f'{{{NS["xmp"]}}}ModifyDate')
element.text = metadata.modified
xml = tostring(rdf, encoding='utf-8')
header = b'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'
footer = b'<?xpacket end="r"?>'
stream_content = b'\n'.join((header, xml, footer))
extra = {'Type': '/Metadata', 'Subtype': '/XML'}
metadata = pydyf.Stream([stream_content], extra, compress)
pdf.add_object(metadata)
pdf.catalog['Metadata'] = metadata.reference
return tostring(rdf, encoding='utf-8')