The last two items of NIGIG_PDF_FEATURE_PARITY_PLAN.md Phase 2. Design and
merge criteria in REVIEWS/adr/0014-pdf-type3-fonts-and-streaming.md.
TYPE 3 FONTS DREW NOTHING
A Type 3 font's glyphs are not outlines - they are content streams, listed
in /CharProcs and mapped to text space by /FontMatrix. Probing a document
with one:
fonts on page: ["T3"]
T3: subtype=Type3 base=Unknown
-> are the glyph procedures reachable? no CharProcs field exists
-> is /FontMatrix exposed? no field exists
The font was detected and then nothing could be done with it. /CharProcs and
/FontMatrix appeared nowhere in the crate, so the procedures were unreachable
and the text was silently invisible - a page that renders, reports no error,
and is missing content.
New pdf-document/src/type3.rs parses /FontMatrix, /CharProcs, /Differences,
/Widths, /FontBBox and the font's own /Resources, and resolves a character
code to its glyph procedure's decoded bytes. /FontMatrix is applied as
written rather than assumed to be the common 0.001 scale - Type 3 fonts
routinely use other matrices, which is the point of the entry. A missing
/CharProcs entry is a typed error naming the glyph, not a blank.
A THIRD BUG, FOUND WHILE WIRING d0/d1
The interpreter parsed both operators and discarded them:
PdfOp::Type3Width(_wx, _wy) => {}
PdfOp::Type3BBox(_x1, _y1, _x2, _y2) => {}
They are how a Type 3 glyph declares its advance, so even a renderer that
could draw the glyphs would stack them all at one point. Wiring them to the
device exposed that `d1` takes SIX operands - wx wy llx lly urx ury - and the
parser read four, so the "bounding box" was really the advance and the
advance was lost entirely. Now `Type3BBox { wx, wy, bbox }`, reading all six.
STREAMING INTERPRETATION
parse_content_stream materialised every operator into a Vec before
interpreting any of them: peak memory proportional to the whole content
stream, on a stream walked once and discarded. Adds ContentStreamIter and
interpret_streaming, with parse_content_stream reimplemented on top of the
iterator so there is ONE tokeniser rather than two that can drift.
Equivalence is proven, not asserted: a test compares both paths across every
corpus fixture, and a streaming_interpreter fuzz target compares them over
arbitrary bytes, which is where a divergence would actually hide.
4 corpus fixtures, 13 acceptance tests, 9 unit tests. Mutation-checked:
reverting d0 to a no-op fails glyph_advances_reach_the_device.
Phase 2 is now complete; the plan is updated with an item-by-item audit.
Several entries were already done (inline images, Do, text state, shading);
the plan's "biggest gap" was xref streams, closed in ADR 0013.
TEST_TARGET=pdf 615 -> 637, TEST_TARGET=pdf-ui 660 -> 682.
rustfmt and clippy -D warnings clean.
105 lines
1.8 KiB
TOML
105 lines
1.8 KiB
TOML
[package]
|
|
name = "nigig-pdf-fuzz"
|
|
version = "0.0.0"
|
|
publish = false
|
|
edition = "2021"
|
|
|
|
# Not a workspace member: cargo-fuzz builds this standalone with a nightly
|
|
# toolchain, and it must not drag libfuzzer into the normal build graph.
|
|
[workspace]
|
|
|
|
[package.metadata]
|
|
cargo-fuzz = true
|
|
|
|
[dependencies]
|
|
libfuzzer-sys = "0.4"
|
|
nigig-pdf-cos = { path = ".." }
|
|
nigig-pdf-document = { path = "../../pdf-document" }
|
|
nigig-pdf-graphics = { path = "../../pdf-graphics" }
|
|
|
|
[profile.release]
|
|
debug = 1
|
|
|
|
[[bin]]
|
|
name = "parse_object"
|
|
path = "fuzz_targets/parse_object.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_xref"
|
|
path = "fuzz_targets/parse_xref.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_content_stream"
|
|
path = "fuzz_targets/parse_content_stream.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_document"
|
|
path = "fuzz_targets/parse_document.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "decode_stream"
|
|
path = "fuzz_targets/decode_stream.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_revision_chain"
|
|
path = "fuzz_targets/parse_revision_chain.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "decrypt"
|
|
path = "fuzz_targets/decrypt.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "eval_function"
|
|
path = "fuzz_targets/eval_function.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_colorspace"
|
|
path = "fuzz_targets/parse_colorspace.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_ext_gstate"
|
|
path = "fuzz_targets/parse_ext_gstate.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_signature"
|
|
path = "fuzz_targets/parse_signature.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_content_dict"
|
|
path = "fuzz_targets/parse_content_dict.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "parse_xref_stream"
|
|
path = "fuzz_targets/parse_xref_stream.rs"
|
|
test = false
|
|
doc = false
|
|
|
|
[[bin]]
|
|
name = "streaming_interpreter"
|
|
path = "fuzz_targets/streaming_interpreter.rs"
|
|
test = false
|
|
doc = false
|