Replace GPLv3-derived PDF/A template with PostScript generator
This commit is contained in:
+50
-29
@@ -1,4 +1,4 @@
|
|||||||
# © 2015 James R. Barlow: github.com/jbarlow83
|
# © 2020 James R. Barlow: github.com/jbarlow83
|
||||||
#
|
#
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@@ -11,8 +11,7 @@ Utilities for PDF/A production and confirmation with Ghostspcript.
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from string import Template
|
from typing import Dict, Iterator, Union
|
||||||
from typing import Dict, Union
|
|
||||||
|
|
||||||
import pikepdf
|
import pikepdf
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
@@ -22,29 +21,56 @@ ICC_PROFILE_RELPATH = 'data/sRGB.icc'
|
|||||||
SRGB_ICC_PROFILE = pkg_resources.resource_filename('ocrmypdf', ICC_PROFILE_RELPATH)
|
SRGB_ICC_PROFILE = pkg_resources.resource_filename('ocrmypdf', ICC_PROFILE_RELPATH)
|
||||||
|
|
||||||
|
|
||||||
# This is a template written in PostScript which is needed to create PDF/A
|
def _postscript_objdef(
|
||||||
# files, from the Ghostscript documentation. Lines beginning with % are
|
alias: str,
|
||||||
# comments. Python substitution variables have a '$' prefix.
|
dictionary: Dict[str, str],
|
||||||
pdfa_def_template = u"""%!
|
*,
|
||||||
% Define an ICC profile :
|
stream_name: str = None,
|
||||||
/ICCProfile $icc_profile
|
stream_data: bytes = None,
|
||||||
def
|
) -> Iterator[str]:
|
||||||
|
assert (stream_name is None) == (stream_data is None)
|
||||||
|
|
||||||
[/_objdef {icc_PDFA} /type /stream /OBJ pdfmark
|
objtype = '/stream' if stream_name else '/dict'
|
||||||
[{icc_PDFA} << /N 3 >> /PUT pdfmark
|
|
||||||
[{icc_PDFA} ICCProfile /PUT pdfmark
|
|
||||||
|
|
||||||
% Define the output intent dictionary :
|
if stream_name:
|
||||||
|
a85_data = base64.a85encode(stream_data, adobe=True).decode('ascii')
|
||||||
|
yield f'{stream_name} ' + a85_data
|
||||||
|
yield 'def'
|
||||||
|
|
||||||
[/_objdef {OutputIntent_PDFA} /type /dict /OBJ pdfmark
|
if alias != '{Catalog}': # Catalog needs no definition
|
||||||
[{OutputIntent_PDFA} <<
|
yield f'[/_objdef {alias} /type {objtype} /OBJ pdfmark'
|
||||||
/Type /OutputIntent % Must be so (the standard requires).
|
|
||||||
/S /GTS_PDFA1 % Must be so (the standard requires).
|
yield f'[{alias} <<'
|
||||||
/DestOutputProfile {icc_PDFA} % Must be so (see above).
|
for key, val in dictionary.items():
|
||||||
/OutputConditionIdentifier ($icc_identifier)
|
yield f' {key} {val}'
|
||||||
>> /PUT pdfmark
|
yield '>> /PUT pdfmark'
|
||||||
[{Catalog} <</OutputIntents [ {OutputIntent_PDFA} ]>> /PUT pdfmark
|
|
||||||
"""
|
if stream_name:
|
||||||
|
yield f'[{alias} {stream_name[1:]} /PUT pdfmark'
|
||||||
|
|
||||||
|
|
||||||
|
def _make_postscript(icc_name: str, icc_data: bytes, colors: int) -> Iterator[str]:
|
||||||
|
yield '%!'
|
||||||
|
yield from _postscript_objdef(
|
||||||
|
'{icc_PDFA}', # Not an f-string
|
||||||
|
{'/N': str(colors)},
|
||||||
|
stream_name='/ICCProfile',
|
||||||
|
stream_data=icc_data,
|
||||||
|
)
|
||||||
|
yield ''
|
||||||
|
yield from _postscript_objdef(
|
||||||
|
'{OutputIntent_PDFA}',
|
||||||
|
{
|
||||||
|
'/Type': '/OutputIntent',
|
||||||
|
'/S': '/GTS_PDFA1',
|
||||||
|
'/DestOutputProfile': '{icc_PDFA}',
|
||||||
|
'/OutputConditionIdentifier': f'({icc_name})', # Only f-string
|
||||||
|
},
|
||||||
|
)
|
||||||
|
yield ''
|
||||||
|
yield from _postscript_objdef(
|
||||||
|
'{Catalog}', {'/OutputIntents': '[ {OutputIntent_PDFA} ]'}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'):
|
def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'):
|
||||||
@@ -75,13 +101,8 @@ def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError("Only supporting sRGB")
|
raise NotImplementedError("Only supporting sRGB")
|
||||||
|
|
||||||
# Read the ICC profile, encode as ASCII85 and convert to a string which we
|
|
||||||
# will insert in the .ps file
|
|
||||||
bytes_icc_profile = Path(icc_profile).read_bytes()
|
bytes_icc_profile = Path(icc_profile).read_bytes()
|
||||||
icc_profile = base64.a85encode(bytes_icc_profile, adobe=True).decode('ascii')
|
ps = '\n'.join(_make_postscript(icc, bytes_icc_profile, 3))
|
||||||
|
|
||||||
t = Template(pdfa_def_template)
|
|
||||||
ps = t.substitute(icc_profile=icc_profile, icc_identifier=icc)
|
|
||||||
|
|
||||||
# We should have encoded everything to pure ASCII by this point, and
|
# We should have encoded everything to pure ASCII by this point, and
|
||||||
# to be safe, only allow ASCII in PostScript
|
# to be safe, only allow ASCII in PostScript
|
||||||
|
|||||||
Reference in New Issue
Block a user