mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-14 06:42:20 +00:00
Merge #6202: M11: PDF/A verify reports integers, reals, strings, names, CIDs and page boundaries past the implementation limits (optimize.pdfa-validate)
# Conflicts: # crates/preflight/src/lib.rs # parity/acrobat-features.toml
This commit is contained in:
commit
25ac77c360
4 files changed
+389
-2
No files matched your search
@@ -22,6 +22,7 @@
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
pub mod icc;
|
||||
mod limits;
|
||||
mod xmp;
|
||||
mod xmp_extension;
|
||||
mod xmp_types;
|
||||
@@ -164,6 +165,12 @@ pub const WORDINGS: &[&str] = &[
|
||||
"Metadata stream {object} writes the namespace {namespace} with the prefix {written}, not {prefix}",
|
||||
// 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³⁸, 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",
|
||||
"Page {} has a boundary smaller than 3 or larger than 14400 units",
|
||||
"Device colour is used, but there is no PDF/A output intent",
|
||||
"DeviceCMYK is used: it needs a CMYK output intent (not added automatically)",
|
||||
"DeviceCMYK is used, but the PDF/A output intent isn't a CMYK profile",
|
||||
@@ -714,6 +721,28 @@ pub fn verify(doc: &Document, level: Level) -> Vec<Issue> {
|
||||
}
|
||||
let drawing = drawing_fonts(doc, &objs);
|
||||
check_objects(doc, level, &objs, &drawing, &page_of, &mut issue);
|
||||
// 6.1.13: the implementation limits of ISO 32000-1 Annex C.
|
||||
let over = limits::exceeded(doc, &objs, &referenced_content(doc, &objs));
|
||||
if over.int {
|
||||
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³⁸, 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);
|
||||
}
|
||||
if limits::cid_over_limit(doc, &objs) {
|
||||
issue("6.1.13", "A CMap maps a character code to a CID above 65535", None, None, false);
|
||||
}
|
||||
if over.name {
|
||||
issue("6.1.13", "A name is longer than 127 bytes", None, None, false);
|
||||
}
|
||||
for (i, p) in pages.iter().enumerate() {
|
||||
if limits::page_box_out_of_range(doc, &p.dict) {
|
||||
issue("6.1.13", "Page {} has a boundary smaller than 3 or larger than 14400 units", Some((i + 1).to_string()), Some(i), false);
|
||||
}
|
||||
}
|
||||
// One message per kind of problem is enough for repeated ones (as preflight summaries do).
|
||||
let mut out = out.into_inner();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
//! PDF/A-2 6.1.13: a file stays within the implementation limits of ISO 32000-1 Annex C (Table C.1
|
||||
//! and the page size limits after it): integers that fit in 32 bits, strings of at most 32767
|
||||
//! bytes, names of at most 127 bytes, and page boundaries between 3 and 14400 units wide and high.
|
||||
//!
|
||||
//! The values are read from the trailer, the indirect objects and the content streams, as parsed:
|
||||
//! 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 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.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use pdfcraft_cos::{DecodeBudget, Dict, Document, ObjRef, Object};
|
||||
|
||||
/// Table C.1: the smallest and largest integer values.
|
||||
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.
|
||||
const MAX_CMAP_BYTES: usize = 16 << 20;
|
||||
/// How many CMaps a `/UseCMap` chain is followed through.
|
||||
const MAX_CMAP_CHAIN: usize = 8;
|
||||
/// Table C.1: the longest string, in bytes.
|
||||
const MAX_STRING: usize = 32767;
|
||||
/// Table C.1: the longest name, in bytes.
|
||||
const MAX_NAME: usize = 127;
|
||||
/// The smallest and largest page boundary, in default user space units.
|
||||
const MIN_PAGE: f64 = 3.0;
|
||||
const MAX_PAGE: f64 = 14400.0;
|
||||
/// How deep a direct object is followed (arrays and dictionaries inside one another): as deep as
|
||||
/// the parser nests them.
|
||||
const MAX_DEPTH: u8 = 128;
|
||||
/// The decoded content, across all the document's content streams, the scan reads.
|
||||
const MAX_CONTENT_BYTES: usize = 256 << 20;
|
||||
/// The page boundaries, of which only the media and crop boxes are inherited.
|
||||
const BOXES: [&[u8]; 5] = [b"MediaBox", b"CropBox", b"BleedBox", b"TrimBox", b"ArtBox"];
|
||||
|
||||
/// Which limits a value went past.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct Exceeded {
|
||||
pub(crate) int: bool,
|
||||
pub(crate) real: bool,
|
||||
pub(crate) string: bool,
|
||||
pub(crate) name: bool,
|
||||
}
|
||||
|
||||
impl Exceeded {
|
||||
fn all(self) -> bool {
|
||||
self.int && self.real && self.string && self.name
|
||||
}
|
||||
|
||||
fn name(&mut self, n: &[u8]) {
|
||||
self.name |= n.len() > MAX_NAME;
|
||||
}
|
||||
|
||||
/// One value and, within [`MAX_DEPTH`], what it holds: a dictionary's keys count as names.
|
||||
fn value(&mut self, o: &Object, depth: u8) {
|
||||
if self.all() || depth > MAX_DEPTH {
|
||||
return;
|
||||
}
|
||||
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 || (*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) => {
|
||||
for v in a {
|
||||
self.value(v, depth.saturating_add(1));
|
||||
}
|
||||
}
|
||||
Object::Dict(d) => self.dict(d, depth),
|
||||
Object::Stream(s) => self.dict(&s.dict, depth),
|
||||
Object::Null | Object::Bool(_) | Object::Ref(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The operands of one content stream, inline images' dictionaries included.
|
||||
fn content(&mut self, data: &[u8]) {
|
||||
for op in pdfcraft_content::ops(data) {
|
||||
if self.all() {
|
||||
return;
|
||||
}
|
||||
for v in &op.operands {
|
||||
self.value(v, 0);
|
||||
}
|
||||
if let Some((image, _)) = &op.inline {
|
||||
self.dict(image, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dict(&mut self, d: &Dict, depth: u8) {
|
||||
for (k, v) in d.iter() {
|
||||
self.name(k);
|
||||
self.value(v, depth.saturating_add(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The limits that the trailer, the indirect objects `objs` and the content streams go past.
|
||||
/// `content` holds the content streams only a reference identifies (a page's `/Contents`, Type 3
|
||||
/// glyph procedures), besides form XObjects and appearance streams, which say so themselves.
|
||||
pub(crate) fn exceeded(doc: &Document, objs: &[(ObjRef, std::sync::Arc<Object>)], content: &HashSet<ObjRef>) -> Exceeded {
|
||||
let mut found = Exceeded::default();
|
||||
found.dict(doc.trailer(), 0);
|
||||
for (_, o) in objs {
|
||||
if found.all() {
|
||||
return found;
|
||||
}
|
||||
found.value(o, 0);
|
||||
}
|
||||
let mut budget = DecodeBudget::new(MAX_CONTENT_BYTES, 0);
|
||||
let mut read: HashSet<ObjRef> = HashSet::new();
|
||||
for page in pdfcraft_model::pages(doc) {
|
||||
if found.all() || budget.remaining() == 0 {
|
||||
return found;
|
||||
}
|
||||
let refs = contents(doc, &page.dict);
|
||||
let mut joined = Vec::new();
|
||||
for r in &refs {
|
||||
if let Object::Stream(s) = &*doc.get(*r)
|
||||
&& let Ok(data) = budget.decode(s)
|
||||
{
|
||||
joined.extend_from_slice(&data);
|
||||
joined.push(b' ');
|
||||
}
|
||||
}
|
||||
read.extend(refs);
|
||||
found.content(&joined);
|
||||
}
|
||||
for (r, o) in objs {
|
||||
if found.all() || budget.remaining() == 0 {
|
||||
break;
|
||||
}
|
||||
if let Object::Stream(s) = &**o
|
||||
&& !read.contains(r)
|
||||
&& (content.contains(r) || crate::is_content(&s.dict))
|
||||
&& let Ok(data) = budget.decode(s)
|
||||
{
|
||||
found.content(&data);
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// A page's `/Contents` streams, in order.
|
||||
fn contents(doc: &Document, page: &Dict) -> Vec<ObjRef> {
|
||||
match page.get(b"Contents") {
|
||||
Some(Object::Ref(r)) => match &*doc.get(*r) {
|
||||
Object::Array(a) => a.iter().filter_map(Object::as_ref).collect(),
|
||||
_ => vec![*r],
|
||||
},
|
||||
Some(Object::Array(a)) => a.iter().filter_map(Object::as_ref).collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether one of a page's boundaries (with the media and crop boxes it inherits) is narrower or
|
||||
/// lower than 3 units, or wider or higher than 14400.
|
||||
pub(crate) fn page_box_out_of_range(doc: &Document, page: &Dict) -> bool {
|
||||
BOXES.iter().filter_map(|k| page.get(k)).any(|b| {
|
||||
let b = doc.resolve(b);
|
||||
let v: Vec<f64> = b.as_array().map(|a| a.iter().filter_map(|x| doc.resolve(x).as_f64()).collect()).unwrap_or_default();
|
||||
let [x0, y0, x1, y1] = v.as_slice() else { return false };
|
||||
let (w, h) = ((x1 - x0).abs(), (y1 - y0).abs());
|
||||
// A size past `f64` (corners near ±1e308) is as much too large as any other.
|
||||
[w, h].iter().any(|s| !(MIN_PAGE..=MAX_PAGE).contains(s))
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether an embedded CMap of a Type 0 font among `objs`, or one it uses through `/UseCMap`, maps
|
||||
/// a code to a CID above 65535: a `begincidrange` entry `<low> <high> cid` reaches
|
||||
/// `cid + high − low`, a `begincidchar` entry `<code> cid` reaches `cid` (ISO 32000-1 9.7.5.4). A
|
||||
/// CMap named rather than embedded is a predefined one, within the limit. The CMaps are decoded
|
||||
/// within one allowance for the document.
|
||||
pub(crate) fn cid_over_limit(doc: &Document, objs: &[(ObjRef, std::sync::Arc<Object>)]) -> bool {
|
||||
let mut budget = DecodeBudget::new(MAX_CMAP_BYTES, 0);
|
||||
let mut read: HashSet<ObjRef> = HashSet::new();
|
||||
for (_, o) in objs {
|
||||
let Some(d) = o.as_dict() else { continue };
|
||||
if d.name(b"Subtype") != Some(b"Type0") {
|
||||
continue;
|
||||
}
|
||||
let mut next = d.get(b"Encoding").and_then(Object::as_ref);
|
||||
for _ in 0..MAX_CMAP_CHAIN {
|
||||
let Some(r) = next.filter(|r| read.insert(*r)) else { break };
|
||||
if budget.remaining() == 0 {
|
||||
return false;
|
||||
}
|
||||
let Object::Stream(cmap) = &*doc.get(r) else { break };
|
||||
if budget.decode(cmap).is_ok_and(|data| maps_past_limit(&data)) {
|
||||
return true;
|
||||
}
|
||||
next = cmap.dict.get(b"UseCMap").and_then(Object::as_ref);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether the CMap program `data` maps a code to a CID above [`MAX_CID`]. The entries of a
|
||||
/// `begincidrange` … `endcidrange` block are the operands of its `endcidrange`, read as content is.
|
||||
fn maps_past_limit(data: &[u8]) -> bool {
|
||||
let value = |o: &Object| -> Option<u64> {
|
||||
match o {
|
||||
Object::String(s) if s.bytes.len() <= 4 => Some(s.bytes.iter().fold(0u64, |v, b| (v << 8) | u64::from(*b))),
|
||||
Object::Int(i) => u64::try_from(*i).ok(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
pdfcraft_content::ops(data).any(|op| match op.op.as_slice() {
|
||||
b"endcidrange" => op.operands.as_chunks::<3>().0.iter().any(|[low, high, cid]| match (value(low), value(high), value(cid)) {
|
||||
(Some(low), Some(high), Some(cid)) => cid.saturating_add(high.saturating_sub(low)) > MAX_CID,
|
||||
_ => false,
|
||||
}),
|
||||
b"endcidchar" => op.operands.as_chunks::<2>().0.iter().any(|[_, cid]| value(cid).is_some_and(|cid| cid > MAX_CID)),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
@@ -491,6 +491,137 @@ fn an_output_intent_profile_header_is_read_within_a_size_cap() {
|
||||
}
|
||||
}
|
||||
|
||||
/// PDF/A-2 6.1.13 holds a file to the implementation limits of ISO 32000-1 Annex C (Table C.1):
|
||||
/// an integer past 32 bits, a string over 32767 bytes or a name over 127 bytes (decoded: a name
|
||||
/// written with `#xx` escapes counts its bytes once read), in an object or in content; and a page
|
||||
/// boundary under 3 or over 14400 units, read after inheritance, so a page that overrides its
|
||||
/// parent's box is judged by its own.
|
||||
#[test]
|
||||
fn implementation_limits_are_reported() {
|
||||
let limits =
|
||||
|doc: &Document| -> Vec<String> { verify(doc, Level::A2b).into_iter().filter(|i| i.clause == "6.1.13").map(|i| i.message).collect() };
|
||||
let doc = |extra: &str, content: &str, parent_box: &str, page_box: &str| {
|
||||
let objects = [
|
||||
format!("<< /Type /Catalog /Pages 2 0 R {extra} >>"),
|
||||
format!("<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox {parent_box} >>"),
|
||||
format!("<< /Type /Page /Parent 2 0 R /Contents 4 0 R {page_box} >>"),
|
||||
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
|
||||
];
|
||||
let refs: Vec<&str> = objects.iter().map(String::as_str).collect();
|
||||
build(&refs, "")
|
||||
};
|
||||
let ok = "[0 0 200 200]";
|
||||
assert!(limits(&doc("", "", ok, "")).is_empty(), "{:?}", limits(&doc("", "", ok, "")));
|
||||
assert_eq!(limits(&doc("/Big 2147483648", "", ok, "")), ["An integer is outside the 32-bit range"]);
|
||||
assert_eq!(limits(&doc("/Small -2147483649", "", ok, "")), ["An integer is outside the 32-bit range"]);
|
||||
assert!(limits(&doc("/Edge -2147483648 /Top 2147483647", "", ok, "")).is_empty());
|
||||
assert_eq!(limits(&doc("", "2147483648 0 Td", ok, "")), ["An integer is outside the 32-bit range"]);
|
||||
let long = "x".repeat(32768);
|
||||
assert_eq!(limits(&doc(&format!("/S ({long})"), "", ok, "")), ["A string is longer than 32767 bytes"]);
|
||||
assert_eq!(limits(&doc("", &format!("BT ({long}) Tj ET"), ok, "")), ["A string is longer than 32767 bytes"]);
|
||||
assert!(limits(&doc(&format!("/S ({})", "x".repeat(32767)), "", ok, "")).is_empty());
|
||||
let name = "n".repeat(128);
|
||||
assert_eq!(limits(&doc(&format!("/V /{name}"), "", ok, "")), ["A name is longer than 127 bytes"]);
|
||||
assert_eq!(limits(&doc(&format!("/{name} 1"), "", ok, "")), ["A name is longer than 127 bytes"]);
|
||||
assert_eq!(limits(&doc("", &format!("/{name} Do"), ok, "")), ["A name is longer than 127 bytes"]);
|
||||
// 200 bytes as written, 66 once the escapes are read.
|
||||
let escaped = format!("A{}", "#41".repeat(65) + &"B".repeat(4));
|
||||
assert!(limits(&doc(&format!("/V /{escaped}"), "", ok, "")).is_empty());
|
||||
assert_eq!(limits(&doc("", "", "[0 0 2 200]", "")), ["Page 1 has a boundary smaller than 3 or larger than 14400 units"]);
|
||||
assert_eq!(limits(&doc("", "", ok, "/CropBox [0 0 14401 200]")), ["Page 1 has a boundary smaller than 3 or larger than 14400 units"]);
|
||||
assert!(limits(&doc("", "", "[0 0 2 2]", "/MediaBox [0 0 3 14400]")).is_empty(), "the page's own box overrides its parent's");
|
||||
}
|
||||
|
||||
/// 6.1.13 also reads the trailer, a page's `/Contents` streams joined (operands may end one
|
||||
/// stream and their operator start the next, ISO 32000-1 7.8.2), values nested as deep as the
|
||||
/// parser allows, and a page box whose size overflows to infinity.
|
||||
#[test]
|
||||
fn implementation_limits_reach_the_trailer_split_content_deep_values_and_huge_boxes() {
|
||||
let limits =
|
||||
|doc: &Document| -> Vec<String> { verify(doc, Level::A2b).into_iter().filter(|i| i.clause == "6.1.13").map(|i| i.message).collect() };
|
||||
let stream = |data: &str| {
|
||||
format!(
|
||||
"<< /Length {} >>
|
||||
stream
|
||||
{data}
|
||||
endstream",
|
||||
data.len()
|
||||
)
|
||||
};
|
||||
let page = |contents: &str, catalog_extra: &str, media: &str, trailer: &str| {
|
||||
let (first, second) = contents.split_once('|').unwrap_or((contents, ""));
|
||||
build(
|
||||
&[
|
||||
&format!("<< /Type /Catalog /Pages 2 0 R {catalog_extra} >>"),
|
||||
&format!("<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox {media} >>"),
|
||||
"<< /Type /Page /Parent 2 0 R /Contents [4 0 R 5 0 R] >>",
|
||||
&stream(first),
|
||||
&stream(second),
|
||||
],
|
||||
trailer,
|
||||
)
|
||||
};
|
||||
let ok = "[0 0 200 200]";
|
||||
assert!(limits(&page("|", "", ok, "")).is_empty());
|
||||
assert_eq!(limits(&page("|", "", ok, "/Custom 2147483648")), ["An integer is outside the 32-bit range"]);
|
||||
let long = "x".repeat(32768);
|
||||
assert_eq!(limits(&page(&format!("BT ({long})|Tj ET"), "", ok, "")), ["A string is longer than 32767 bytes"]);
|
||||
let deep = format!("/Deep {}2147483648{}", "[".repeat(40), "]".repeat(40));
|
||||
assert_eq!(limits(&page("|", &deep, ok, "")), ["An integer is outside the 32-bit range"]);
|
||||
// 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³⁸, 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³⁸ (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]
|
||||
fn implementation_limits_cover_reals_and_cids() {
|
||||
let limits =
|
||||
|doc: &Document| -> Vec<String> { verify(doc, Level::A2b).into_iter().filter(|i| i.clause == "6.1.13").map(|i| i.message).collect() };
|
||||
let stream = |dict: &str, data: &str| format!("<< {dict} /Length {} >>\nstream\n{data}\nendstream", data.len());
|
||||
let doc = |catalog_extra: &str, cmap: &str, used: &str| {
|
||||
let first = stream("/Type /CMap /CMapName /Test-A /UseCMap 6 0 R", cmap);
|
||||
let second = stream("/Type /CMap /CMapName /Test-B", used);
|
||||
build(
|
||||
&[
|
||||
&format!("<< /Type /Catalog /Pages 2 0 R {catalog_extra} >>"),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 200 200] >>",
|
||||
"<< /Type /Page /Parent 2 0 R /Resources << /Font << /F 4 0 R >> >> >>",
|
||||
"<< /Type /Font /Subtype /Type0 /BaseFont /Test /Encoding 5 0 R /DescendantFonts [] >>",
|
||||
&first,
|
||||
&second,
|
||||
],
|
||||
"",
|
||||
)
|
||||
};
|
||||
let range = |low: &str, high: &str, cid: u32| format!("1 begincidrange <{low}> <{high}> {cid} endcidrange");
|
||||
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³⁸, 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");
|
||||
assert_eq!(limits(&doc("", "1 begincidchar <01> 70000 endcidchar", "")), cid);
|
||||
assert_eq!(limits(&doc("", &range("0000", "00ff", 0), &range("3f00", "3fff", 65536))), cid, "through /UseCMap");
|
||||
}
|
||||
|
||||
const XMP_NS: &str = concat!(
|
||||
r#"xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" "#,
|
||||
r#"xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" "#,
|
||||
|
||||
@@ -8017,8 +8017,8 @@ milestone = "M11"
|
||||
status = "partial"
|
||||
commands = ["standards.pdfa"]
|
||||
tools = ["pdfa_verify"]
|
||||
evidence = ["crates/preflight/src/tests.rs::verify_reports_what_breaks_pdfa", "crates/preflight/src/tests.rs::standards_identification_uses_namespace_not_prefix", "crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/preflight/src/tests.rs::verify_reports_an_output_intent_that_does_not_match_device_colour", "crates/preflight/src/tests.rs::xmp_simple_values_have_their_lexical_forms", "crates/preflight/src/tests.rs::xmp_arrays_have_their_kind_and_item_types", "crates/preflight/src/tests.rs::xmp_structures_have_their_field_types", "crates/preflight/src/tests.rs::xmp_value_types_are_reported_by_verify", "crates/preflight/src/tests.rs::xmp_exif_schemas_have_their_value_types", "crates/preflight/src/tests.rs::xmp_exif_properties_are_known_by_namespace_uri", "crates/preflight/src/tests.rs::xmp_extension_schema_descriptions_are_complete", "crates/preflight/src/tests.rs::xmp_extension_findings_say_where_they_are", "crates/preflight/src/tests.rs::xmp_extension_naming_entries_are_not_empty", "crates/preflight/src/tests.rs::xmp_mistyped_properties_name_their_type", "crates/preflight/src/tests.rs::xmp_properties_are_known_by_namespace_uri_and_named_as_written", "crates/automation/tests/it/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/it/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||||
notes = "PDF/A-2b/3b structural rules: identification, the value types of the predefined XMP schemas (6.6.2.3.1: dc, xmp, xmpRights, xmpMM, xmpBJ, xmpTPg, xmpDM, pdf, photoshop, crs, and tiff, exif and aux from the September 2005 XMP Specification, with their structures), XMP extension schema descriptions (6.6.2.3.3, after PDF Association TechNote 0009 section 4: required entries of the schema, property, value type and field descriptions, none of the naming entries empty, and the five required prefixes; that a property's custom value type has its own pdfaSchema:valueType description is not yet checked), encryption, output intent (present, and RGB/CMYK to match DeviceRGB/DeviceCMYK use), fonts embedded, annotations, actions, images, embedded files. Not yet: content-stream operator checks, transparency details, PDF/A-1 and -4, a veraPDF comparison."
|
||||
evidence = ["crates/preflight/src/tests.rs::verify_reports_what_breaks_pdfa", "crates/preflight/src/tests.rs::standards_identification_uses_namespace_not_prefix", "crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/preflight/src/tests.rs::verify_reports_an_output_intent_that_does_not_match_device_colour", "crates/preflight/src/tests.rs::xmp_simple_values_have_their_lexical_forms", "crates/preflight/src/tests.rs::xmp_arrays_have_their_kind_and_item_types", "crates/preflight/src/tests.rs::xmp_structures_have_their_field_types", "crates/preflight/src/tests.rs::xmp_value_types_are_reported_by_verify", "crates/preflight/src/tests.rs::xmp_exif_schemas_have_their_value_types", "crates/preflight/src/tests.rs::xmp_exif_properties_are_known_by_namespace_uri", "crates/preflight/src/tests.rs::xmp_extension_schema_descriptions_are_complete", "crates/preflight/src/tests.rs::xmp_extension_findings_say_where_they_are", "crates/preflight/src/tests.rs::xmp_extension_naming_entries_are_not_empty", "crates/preflight/src/tests.rs::xmp_mistyped_properties_name_their_type", "crates/preflight/src/tests.rs::xmp_properties_are_known_by_namespace_uri_and_named_as_written", "crates/automation/tests/it/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/it/compare.rs::pdfa_dialog_verifies_and_converts", "crates/preflight/src/tests.rs::implementation_limits_are_reported", "crates/preflight/src/tests.rs::implementation_limits_reach_the_trailer_split_content_deep_values_and_huge_boxes", "crates/preflight/src/tests.rs::implementation_limits_cover_reals_and_cids"]
|
||||
notes = "PDF/A-2b/3b structural rules: identification, the value types of the predefined XMP schemas (6.6.2.3.1: dc, xmp, xmpRights, xmpMM, xmpBJ, xmpTPg, xmpDM, pdf, photoshop, crs, and tiff, exif and aux from the September 2005 XMP Specification, with their structures), XMP extension schema descriptions (6.6.2.3.3, after PDF Association TechNote 0009 section 4: required entries of the schema, property, value type and field descriptions, none of the naming entries empty, and the five required prefixes; that a property's custom value type has its own pdfaSchema:valueType description is not yet checked), encryption, output intent (present, and RGB/CMYK to match DeviceRGB/DeviceCMYK use), fonts embedded, annotations, actions, images, embedded files, and the implementation limits of 6.1.13 (integers, reals, strings, names, CIDs and page boundaries, ISO 32000-1 Annex C). Not yet: content-stream operator checks, transparency details, PDF/A-1 and -4, a veraPDF comparison."
|
||||
|
||||
[[feature]]
|
||||
id = "optimize.pdfa-convert"
|
||||
|
||||
Reference in new issue
Block a user