From 293a2e52b3fbd6dac2c89dfb35a546cdc027eb1b Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 13 May 2023 16:27:07 -0400 Subject: [PATCH] Disregard appearance state when irrelevant (fixes #949) If /AP is a dictionary of streams rather than a dictionary of dictionaries, disregard /AS, which is supposed to point to a subkey of one of the dictionaries. This fix prevents qpdf's annotation flattening from discarding some annotations when /AS is erroneously set. --- ChangeLog | 9 +++++++++ libqpdf/QPDFAnnotationObjectHelper.cc | 9 ++++++++- manual/release-notes.rst | 8 ++++++++ qpdf/qtest/qpdf/form-form-bad-fields-array.out | 4 ++-- qpdf/qtest/qpdf/form-form-document-defaults.out | 4 ++-- qpdf/qtest/qpdf/form-form-empty-from-odt.out | 4 ++-- qpdf/qtest/qpdf/form-form-errors.out | 4 ++-- qpdf/qtest/qpdf/form-form-mod1.out | 4 ++-- 8 files changed, 35 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index ea4a9c5d..00864384 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,15 @@ reserved object fits better in the QPDF API. The old call just delegates to the new one. +2023-05-13 Jay Berkenbilt + + * When an annotation dictionary's appearance dictionary (`/AP`) + has a key that is a stream, disregard `/AS` (which is supposed to + point to a subkey). This enables qpdf to not ignore annotations + that have incorrect values for `/AS` when the appearance stream is + directly in the `/AP` dictionary instead of in a subkey. Fixes + #949. + 2023-04-02 Jay Berkenbilt * Allow QPDFJob's workflow to be split into a reading phase and a writing phase to allow the caller to operate on the QPDF object diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc index c14d98f9..ff985ed0 100644 --- a/libqpdf/QPDFAnnotationObjectHelper.cc +++ b/libqpdf/QPDFAnnotationObjectHelper.cc @@ -54,7 +54,14 @@ QPDFAnnotationObjectHelper::getAppearanceStream( std::string desired_state = state.empty() ? getAppearanceState() : state; if (ap.isDictionary()) { QPDFObjectHandle ap_sub = ap.getKey(which); - if (ap_sub.isStream() && desired_state.empty()) { + if (ap_sub.isStream()) { + // According to the spec, Appearance State is supposed to + // refer to a subkey of the appearance stream when /AP is + // a dictionary, but files have been seen in the wild + // where Appearance State is `/N` and `/AP` is a stream. + // Therefore, if `which` points to a stream, disregard + // state and just use the stream. See qpdf issue #949 for + // details. QTC::TC("qpdf", "QPDFAnnotationObjectHelper AP stream"); return ap_sub; } diff --git a/manual/release-notes.rst b/manual/release-notes.rst index c0d2fcca..7b6a8876 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -26,6 +26,14 @@ For a detailed list of changes, please see the file - Add ``QPDF::newReserved`` as a better alternative to ``QPDFObjectHandle::newReserved``. + - Bug fixes + + - Ignore an annotation's appearance state when the annotation only + has one appearance. This prevents qpdf's annotation flattening + logic from throwing away appearances of annotations whose + annotation state is set incorrectly, as has been seen in some + PDF files. + 11.3.0: February 25, 2023 - CLI Enhancements diff --git a/qpdf/qtest/qpdf/form-form-bad-fields-array.out b/qpdf/qtest/qpdf/form-form-bad-fields-array.out index 1347c8de..0a3500c0 100644 --- a/qpdf/qtest/qpdf/form-form-bad-fields-array.out +++ b/qpdf/qtest/qpdf/form-form-bad-fields-array.out @@ -178,7 +178,7 @@ Page: 11 0 R Subtype: /Widget Rect: [123.4, 692.1, 260.9, 706.7] Appearance stream (/N): 14 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 14 0 R Annotation: 16 0 R Field: 16 0 R Subtype: /Widget @@ -249,5 +249,5 @@ Page: 35 0 R Subtype: /Widget Rect: [113.6, 378.5, 351.3, 396.3] Appearance stream (/N): 36 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 36 0 R test 43 done diff --git a/qpdf/qtest/qpdf/form-form-document-defaults.out b/qpdf/qtest/qpdf/form-form-document-defaults.out index 555e7898..31622cf7 100644 --- a/qpdf/qtest/qpdf/form-form-document-defaults.out +++ b/qpdf/qtest/qpdf/form-form-document-defaults.out @@ -166,7 +166,7 @@ Page: 11 0 R Subtype: /Widget Rect: [123.4, 692.1, 260.9, 706.7] Appearance stream (/N): 14 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 14 0 R Annotation: 16 0 R Field: 16 0 R Subtype: /Widget @@ -237,5 +237,5 @@ Page: 35 0 R Subtype: /Widget Rect: [113.6, 378.5, 351.3, 396.3] Appearance stream (/N): 36 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 36 0 R test 43 done diff --git a/qpdf/qtest/qpdf/form-form-empty-from-odt.out b/qpdf/qtest/qpdf/form-form-empty-from-odt.out index 83597c8d..c9b47a99 100644 --- a/qpdf/qtest/qpdf/form-form-empty-from-odt.out +++ b/qpdf/qtest/qpdf/form-form-empty-from-odt.out @@ -166,7 +166,7 @@ Page: 11 0 R Subtype: /Widget Rect: [123.4, 692.1, 260.9, 706.7] Appearance stream (/N): 14 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 14 0 R Annotation: 16 0 R Field: 16 0 R Subtype: /Widget @@ -237,5 +237,5 @@ Page: 35 0 R Subtype: /Widget Rect: [113.6, 378.5, 351.3, 396.3] Appearance stream (/N): 36 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 36 0 R test 43 done diff --git a/qpdf/qtest/qpdf/form-form-errors.out b/qpdf/qtest/qpdf/form-form-errors.out index 130e3536..0225f068 100644 --- a/qpdf/qtest/qpdf/form-form-errors.out +++ b/qpdf/qtest/qpdf/form-form-errors.out @@ -171,7 +171,7 @@ Page: 11 0 R Subtype: /Widget Rect: [123.4, 692.1, 260.9, 706.7] Appearance stream (/N): 14 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 14 0 R Annotation: 16 0 R Field: 16 0 R Subtype: /Widget @@ -242,5 +242,5 @@ Page: 35 0 R Subtype: /Widget Rect: [113.6, 378.5, 351.3, 396.3] Appearance stream (/N): 36 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 36 0 R test 43 done diff --git a/qpdf/qtest/qpdf/form-form-mod1.out b/qpdf/qtest/qpdf/form-form-mod1.out index 8e8f1a94..1c7fea76 100644 --- a/qpdf/qtest/qpdf/form-form-mod1.out +++ b/qpdf/qtest/qpdf/form-form-mod1.out @@ -166,7 +166,7 @@ Page: 11 0 R Subtype: /Widget Rect: [123.4, 692.1, 260.9, 706.7] Appearance stream (/N): 14 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 14 0 R Annotation: 16 0 R Field: 16 0 R Subtype: /Widget @@ -237,5 +237,5 @@ Page: 35 0 R Subtype: /Widget Rect: [113.6, 378.5, 351.3, 396.3] Appearance stream (/N): 36 0 R - Appearance stream (/N, /3): null + Appearance stream (/N, /3): 36 0 R test 43 done