diff --git a/src/ocrmypdf.py b/src/ocrmypdf.py index be5a35e1..048e6e7a 100755 --- a/src/ocrmypdf.py +++ b/src/ocrmypdf.py @@ -596,17 +596,24 @@ def generate_postscript_stub( input_file, output_file, log): - try: - pdf = pypdf.PdfFileReader(input_file) - pdfmark = { - 'title': pdf.documentInfo['/Title'], - 'author': pdf.documentInfo['/Author'], - 'keywords': pdf.documentInfo['/Keywords'], - 'subject': pdf.documentInfo['/Subject'] - } - except KeyError: - pdfmark = {} + pdf = pypdf.PdfFileReader(input_file) + + def from_document_info(key): + # pdf.documentInfo.get() DOES NOT work as expected + try: + s = pdf.documentInfo[key] + return str(s) + except KeyError: + return '' + + pdfmark = { + 'title': from_document_info('/Title'), + 'author': from_document_info('/Author'), + 'keywords': from_document_info('/Keywords'), + 'subject': from_document_info('/Subject'), + } + print(pdfmark) generate_pdfa_def(output_file, pdfmark) @@ -636,10 +643,13 @@ def merge_pages( pdfinfo_lock): def input_file_order(s): - '''Sort order: Postscript PDF/A header, and then pages followed - by their debug page, if any.''' + '''Sort order: All rendered pages followed + by their debug page, if any, followed by Postscript stub. + Ghostscript documentation has the Postscript stub at the + beginning, but it works at the end and also gets document info + right that way.''' if s.endswith('.ps'): - return -1 + return 99999999 key = int(os.path.basename(s)[0:6]) * 10 if 'debug' in os.path.basename(s): key += 1 diff --git a/src/pdfa.py b/src/pdfa.py index cdd56356..2c274336 100644 --- a/src/pdfa.py +++ b/src/pdfa.py @@ -9,6 +9,7 @@ from __future__ import print_function, absolute_import, division from string import Template from subprocess import Popen, PIPE import os +import codecs # This is a template written in PostScript which is needed to create PDF/A @@ -24,10 +25,10 @@ pdfa_def_template = u"""%! /ICCProfile ($icc_profile) def -[ /Title ($pdf_title) - /Author ($pdf_author) - /Subject ($pdf_subject) - /Keywords ($pdf_keywords) +[ /Title <$title> + /Author <$author> + /Subject <$subject> + /Keywords <$keywords> /DOCINFO pdfmark % Define an ICC profile : @@ -60,14 +61,34 @@ def """ +def encode_text_string(s: str) -> str: + '''Encode text string to hex string for use in a PDF + + From PDF 32000-1:2008 a string object may be included in hexademical form + if it is enclosed in angle brackets. For general Unicode the string should + be UTF-16 (big endian) with byte order marks. A non-hexademical + presentation is possible but this is preferable since it allows the output + Postscript file to be completely ASCII. + ''' + if s == '': + return '' + utf16_bytes = s.encode('utf-16be') + ascii_hex_bytes = codecs.encode(b'\xfe\xff' + utf16_bytes, 'hex') + ascii_hex_str = ascii_hex_bytes.decode('ascii').lower() + return ascii_hex_str + + def _get_pdfa_def(icc_profile, icc_identifier, pdfmark): + pdfmark_utf16 = {k: encode_text_string(v) for k, v in pdfmark.items()} + t = Template(pdfa_def_template) result = t.substitute(icc_profile=icc_profile, icc_identifier=icc_identifier, - pdf_title=pdfmark.get('title', ''), - pdf_author=pdfmark.get('author', ''), - pdf_subject=pdfmark.get('subject', ''), - pdf_keywords=pdfmark.get('keywords', '')) + title=pdfmark_utf16.get('title', ''), + author=pdfmark_utf16.get('author', ''), + subject=pdfmark_utf16.get('subject', ''), + keywords=pdfmark_utf16.get('keywords', '')) + print(result) return result