From 8a9f174f63ec09881de996ff6751cc830e80ff21 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 3 May 2018 16:30:20 -0700 Subject: [PATCH] Fix XMP validation issue with /CreationDate Related to previous validation issue. If the /CreationDate had no timezone, Ghostscript also creates invalid metadata. Work around this. Also fix up PDF date decoding, and transcode dates to standardize them. --- src/ocrmypdf/pdfa.py | 37 +++++++++++++++++++++---------------- tests/test_metadata.py | 13 +++++++++---- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/ocrmypdf/pdfa.py b/src/ocrmypdf/pdfa.py index 405a4d4a..93f11a22 100644 --- a/src/ocrmypdf/pdfa.py +++ b/src/ocrmypdf/pdfa.py @@ -125,31 +125,31 @@ def encode_pdf_date(d: datetime) -> str: s = d.strftime(pdfmark_date_fmt) tz = d.strftime('%z') - if tz == 'Z': + if tz == 'Z' or tz == '': + # Ghostscript <= 9.23 handles missing timezones incorrectly, so if + # timezone is missing, move it into GMT. + # https://bugs.ghostscript.com/show_bug.cgi?id=699182 s += "+00'00'" - elif tz != '': + else: sign, tz_hours, tz_mins = tz[0], tz[1:3], tz[3:5] s += "{}{}'{}'".format(sign, tz_hours, tz_mins) - else: - raise ValueError("Naive timezone not supported") return s def decode_pdf_date(s: str) -> datetime: - pdfmark_date_fmts = ( - r'%Y%m%d%H%M%S%z', # +0430 etc - r'%Y%m%d%H%M%S', # no time zone - r'%Y%m%d%H%M%SZ') # trailing Z - if s.startswith('D:'): s = s[2:] + + # Literal Z00'00', is incorrect but found in the wild, + # probably made by OS X Quartz -- standardize + if s.endswith("Z00'00'"): + s = s.replace("Z00'00'", '+0000') + elif s.endswith('Z'): + s = s.replace('Z', '+0000') + s = s.replace("'", "") # Remove apos from PDF time strings - for fmt in pdfmark_date_fmts: - try: - return datetime.strptime(s, fmt) - except ValueError: - continue - return None + + return datetime.strptime(s, r'%Y%m%d%H%M%S%z') def _get_pdfmark_dates(pdfmark): @@ -170,7 +170,12 @@ def _get_pdfmark_dates(pdfmark): date_str = pdfmark[key] if date_str.startswith('D:'): date_str = date_str[2:] - yield ' {} (D:{})'.format(key, date_str) + try: + yield ' {} (D:{})'.format( + key, + encode_pdf_date(decode_pdf_date(date_str))) + except ValueError: + yield ' {} null'.format(key) def _get_pdfa_def(icc_profile, icc_identifier, pdfmark): diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4c6b4f94..adf5a361 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -73,11 +73,16 @@ def test_override_metadata(spoof_tesseract_noop, output_type, resources, assert p.returncode == ExitCode.ok, err - reader = pypdf.PdfFileReader(outpdf) + before = pypdf.PdfFileReader(str(input_file)) + after = pypdf.PdfFileReader(outpdf) - assert reader.documentInfo['/Title'] == german - assert reader.documentInfo['/Author'] == chinese - assert reader.documentInfo.get('/Keywords', '') == '' + assert after.documentInfo['/Title'] == german + assert after.documentInfo['/Author'] == chinese + assert after.documentInfo.get('/Keywords', '') == '' + + before_date = decode_pdf_date(before.documentInfo['/CreationDate']) + after_date = decode_pdf_date(after.documentInfo['/CreationDate']) + assert before_date == after_date pdfa_info = file_claims_pdfa(outpdf) assert pdfa_info['output'] == output_type