From bf20038b0bfe9da9864af6cf03476a05bcba6fd4 Mon Sep 17 00:00:00 2001 From: mtlcl410 Date: Thu, 5 Nov 2026 08:46:57 +0800 Subject: [PATCH] =?UTF-8?q?M11:=20PDF/A=20verify=20reports=20nonzero=20rea?= =?UTF-8?q?ls=20nearer=20zero=20than=20=C2=B11.175=20=C3=97=2010=E2=81=BB?= =?UTF-8?q?=C2=B3=E2=81=B8=20(6.1.13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PDF Association TechNote 0010 A018: PDF/A-2 does not allow the nonzero reals that ISO 32000-1 Table C.1 lets a reader round to zero. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn --- crates/preflight/src/lib.rs | 4 ++-- crates/preflight/src/limits.rs | 8 ++++++-- crates/preflight/src/tests.rs | 19 +++++++++++++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/crates/preflight/src/lib.rs b/crates/preflight/src/lib.rs index 40f0bb78d1..6004ff7fa7 100644 --- a/crates/preflight/src/lib.rs +++ b/crates/preflight/src/lib.rs @@ -117,7 +117,7 @@ pub const WORDINGS: &[&str] = &[ // The document as a whole. "The document is encrypted", "An integer is outside the 32-bit range", - "A real number is outside ±3.403 × 10³⁸", + "A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸", "A CMap maps a character code to a CID above 65535", "A string is longer than 32767 bytes", "A name is longer than 127 bytes", @@ -528,7 +528,7 @@ pub fn verify(doc: &Document, level: Level) -> Vec { issue("6.1.13", "An integer is outside the 32-bit range", None, None, false); } if over.real { - issue("6.1.13", "A real number is outside ±3.403 × 10³⁸", None, None, false); + issue("6.1.13", "A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸", None, None, false); } if over.string { issue("6.1.13", "A string is longer than 32767 bytes", None, None, false); diff --git a/crates/preflight/src/limits.rs b/crates/preflight/src/limits.rs index ec9886dcf5..9d986d98aa 100644 --- a/crates/preflight/src/limits.rs +++ b/crates/preflight/src/limits.rs @@ -6,7 +6,9 @@ //! a name written with `#xx` escapes counts its decoded bytes. A page's `/Contents` streams are read //! joined, as one content stream (an operator's operands may end one stream and the operator start //! the next, ISO 32000-1 7.8.2). Content is decoded within one allowance for the whole document. -//! Real numbers beyond ±3.403 × 10³⁸ are reported, and so is an embedded CMap of a Type 0 font +//! Real numbers beyond ±3.403 × 10³⁸ are reported, and so are nonzero ones nearer zero than +//! ±1.175 × 10⁻³⁸ (ISO 32000-1 Table C.1 lets a reader round them to zero; PDF/A-2 does not allow +//! them, PDF Association TechNote 0010 A018), and so is an embedded CMap of a Type 0 font //! (or one it uses through `/UseCMap`) that maps a code to a CID above 65535. An integer token too //! long even for a real reads as one past that range, and is reported as a real. @@ -19,6 +21,8 @@ const MIN_INT: i64 = i32::MIN as i64; const MAX_INT: i64 = i32::MAX as i64; /// Table C.1: the largest real value (approximate). const MAX_REAL: f64 = 3.403e38; +/// Table C.1: the nonzero real value closest to zero (approximate). +const MIN_REAL: f64 = 1.175e-38; /// Table C.1: the largest character identifier. const MAX_CID: u64 = 65535; /// The decoded CMap data, across all the document's embedded CMaps, the scan reads. @@ -65,7 +69,7 @@ impl Exceeded { } match o { Object::Int(i) => self.int |= !(MIN_INT..=MAX_INT).contains(i), - Object::Real(r) => self.real |= !r.is_finite() || r.abs() > MAX_REAL, + Object::Real(r) => self.real |= !r.is_finite() || r.abs() > MAX_REAL || (*r != 0.0 && r.abs() < MIN_REAL), Object::String(s) => self.string |= s.bytes.len() > MAX_STRING, Object::Name(n) => self.name(n), Object::Array(a) => { diff --git a/crates/preflight/src/tests.rs b/crates/preflight/src/tests.rs index 6893a42e2f..bd861b3561 100644 --- a/crates/preflight/src/tests.rs +++ b/crates/preflight/src/tests.rs @@ -569,11 +569,15 @@ endstream", // Corners near ±1e308 are reals past the limit too, and the width overflows to infinity. assert_eq!( limits(&page("|", "", "[-1e308 0 1e308 200]", "")), - ["A real number is outside ±3.403 × 10³⁸", "Page 1 has a boundary smaller than 3 or larger than 14400 units"] + [ + "A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸", + "Page 1 has a boundary smaller than 3 or larger than 14400 units" + ] ); } -/// 6.1.13's two remaining limits of ISO 32000-1 Annex C: a real number beyond ±3.403 × 10³⁸, and +/// 6.1.13's two remaining limits of ISO 32000-1 Annex C: a real number beyond ±3.403 × 10³⁸ (or +/// nonzero and nearer zero than ±1.175 × 10⁻³⁸), and /// a CID above 65535 that an embedded CMap of a Type 0 font maps to (a `begincidrange` reaching /// past it, a `begincidchar` naming one, or a CMap it uses through `/UseCMap`). #[test] @@ -600,8 +604,15 @@ fn implementation_limits_cover_reals_and_cids() { let near = format!("34{}.0", "0".repeat(37)); let far = format!("4{}.0", "0".repeat(38)); assert!(limits(&doc(&format!("/R {near} /S -{near}"), &range("0000", "00ff", 65280), "")).is_empty(), "both at their limits"); - assert_eq!(limits(&doc(&format!("/R {far}"), "", "")), ["A real number is outside ±3.403 × 10³⁸"]); - assert_eq!(limits(&doc(&format!("/R -{far}"), "", "")), ["A real number is outside ±3.403 × 10³⁸"]); + assert_eq!(limits(&doc(&format!("/R {far}"), "", "")), ["A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸"]); + assert_eq!(limits(&doc(&format!("/R -{far}"), "", "")), ["A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸"]); + // A nonzero real nearer zero than ±1.175 × 10⁻³⁸ is not allowed either; zero and the + // smallest allowed magnitude are. + let tiny = format!("0.{}1", "0".repeat(39)); + let smallest = format!("0.{}1175", "0".repeat(37)); + assert!(limits(&doc(&format!("/Z 0.0 /M -0.0 /S {smallest} /T -{smallest}"), "", "")).is_empty(), "zero and the smallest real"); + assert_eq!(limits(&doc(&format!("/R {tiny}"), "", "")), ["A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸"]); + assert_eq!(limits(&doc(&format!("/R -{tiny}"), "", "")), ["A real number is outside ±3.403 × 10³⁸, or nonzero but within ±1.175 × 10⁻³⁸"]); let cid = ["A CMap maps a character code to a CID above 65535"]; assert_eq!(limits(&doc("", &range("3f00", "3fff", 65536), "")), cid); assert_eq!(limits(&doc("", &range("0000", "0100", 65280), "")), cid, "the range reaches 65536");