Keep RGBA decoding unchanged while satisfying the current Clippy lint.
Include the separately verified Rustls 0.23.46 lock prerequisite.
Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
as_chunks::<4>() gives the pixel array directly; upstream main fails
the new chunks_exact_to_as_chunks lint here, which made every pull
request red under rust-clippy 1.99.0.
Upstream moved the Hungarian catalog and std14_metrics forward and added
the Next tab / Previous tab labels (document-tab cycling); the four new
catalogs translate both, and their ATTRIBUTION.toml hashes are
recomputed. The manifest check passes with 274 repository assets.
A PDF can forbid copying its text. `pdfcraft-cli text` honours that and refuses, naming the owner
password as the way through. pdftotext ignores the flag and extracts anyway, so the oracle was
comparing our refusal against its text and scoring 0.000 -- measuring obedience, not quality.
bug1815476.pdf is the case: a one-page Spanish form we render perfectly, whose text we decline to
extract and pdftotext hands over regardless. It sat at the bottom of the worst list looking like a
font bug, which is what sent me looking at it.
Those files are now skipped and counted, not scored, and the count is printed rather than passed
over in silence, on the same rule the oracle crate exists for: a comparison that did not happen
must not look like one that succeeded. Over the first 250 corpus files that moves the mean from
0.811 to 0.854 and drops one file from the sample, with the median unchanged at 0.991.
Permissions come from `Document::permissions`, so this is not a match on our own error text. A
file that cannot be opened at all counts as permitted, because that is a different problem and
dropping it quietly would hide it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
F1 replaced the character-class approximation with the published standard-14 metrics, and
`helvetica_width` has delegated to `Std14::Helvetica` ever since. The README kept describing it as
PdfCraft's own estimate and, more importantly, said "No metrics file or font program from any
vendor is bundled" -- which stopped being true when the generated tables landed.
They are Adobe-authored non-visual technical data, the item AGENTS.md 1.1 permits, carried in
std14_metrics.rs with adobe_data = true in ATTRIBUTION.toml. A contributor reading the README
would have concluded there was no vendor data here at all, which is the one kind of error that
matters in a provenance claim. Says what is bundled now, and what still is not: widths and
encodings, never a font program or a typeface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The render oracle put four JBIG2 fixtures at the top of its disagreement list against Poppler, and
the obvious reading was that hayro-jbig2, which carries local patches, had a bug. Investigating it
found the reverse.
The 96 `bitmap-*.pdf` corpus files are a conformance suite: one small drawing encoded through every
corner of JBIG2 -- each generic-region template, custom AT pixels, TPGDON and TPGRON, refinement,
halftone with and without skip, symbol dictionaries with Huffman tables, MMR, the composition
operators, random access, unknown-height segments. Those features change how the bytes are
compressed, not what the picture is, so a correct decoder renders all 96 identically. That is a
real test with no reference image and no external oracle: the suite checks itself.
We render 1 distinct image across all 96. Poppler renders 8: it draws a blank page for the seven
halftone-skip and symbol fixtures, and its output changes when TPGRON is switched on, which a
correct decoder's cannot. So roughly a fifth of Poppler's JBIG2 output is wrong, the top of the
oracle's worst list is its bug rather than ours, and Poppler is not a usable oracle for JBIG2.
Watched failing by admitting one unrelated file to the set: the test fails and names it as the odd
one out rather than merely reporting a count.
The expectation holds of the pinned corpus. Whoever re-pins it to a newer pdf.js should re-check
this rather than assume it still holds, which the test's own documentation says.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Investigating the worst file of the first 250 turned the headline finding around. bug1552113.pdf
scores 84%, and the cause is Poppler: it paints the link's `/Border [0 0 112]` as a 112-unit band
across a 250x50 page, hiding the two lines of text. The fixture is named "this text should be
visible", and in our rendering it is. The oracle is not ground truth, and the top of its worst
list needs a person to look at both images before it is read as a bug of ours.
That led to a plausible change that the measurement rejected. Poppler synthesises appearances for
annotations that have none, where we draw only what the document carries -- `/AP`-less link borders
are already recorded as not covered under view.render-annotation-appearances -- so hiding
annotations looked like the way to measure page content alone. It is worse: it then scores Poppler
for every appearance we do draw. Over the same 250 files, shown gives mean 2.16%, p90 5.35% and 25
files within 0.1%; hidden gives 2.18%, 5.55% and 22, with annotation-tx3.pdf jumping to 45.64% and
annotation-tx2.pdf to 18.79%. The two biases nearly cancel.
So the default is unchanged and `--hide-annotations` is an opt-in for explaining a single file,
with the numbers written down so the experiment is not repeated. Verified that the default
reproduces the merged baseline exactly and that the flag moves it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The note was cut at an escaped quote when it was ported, which left
parity/acrobat-features.toml unparseable (cargo xtask parity failed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gap 12 of docs/gaps.md: until rendering is measured, every claim about its quality is a judgement.
Text extraction had an oracle and written output had qpdf; rendering had nothing.
`cargo xtask render-oracle` renders page 1 of every corpus file with our CLI and with Poppler's
pdftoppm and reports how far apart they are. The metric is the point. A raw pixel comparison is
useless here -- two correct renderers disagree on every glyph edge, so it calls a perfect page
badly wrong and says nothing about whether a shading is missing. `pixels::compare` counts a pixel
as differing only when it differs from every pixel in the other image's 3x3 neighbourhood by more
than a tolerance, in both directions, taking the worse. A one-pixel shift is forgiven; absent or
miscoloured content is not.
First reading, 250 files at 72 dpi, tolerance 8: 229 compared, median 0.89%, mean 2.16%, p90
5.35%, 25 within 0.1%. It reports rather than gates -- the corpus is not on CI, and a first
reading of a borrowed renderer is a baseline to improve on, not a bar to clear. The worst results
cluster on JBIG2 `bitmap-*` files and on ZapfDingbats, which aims the renderer work at something
specific for the first time. 15 files could not be compared because the two renderers round the
page box to different pixel sizes; they are reported as unmeasurable rather than scored.
Verified against the knob's bounds end to end: tolerance 255 scores every file 0.00%, tolerance 0
takes the median to 0.95% and p90 to 26.36%.
Also turns on `extract_type3_shared_xobjects_matches_poppler_pixels`, which had never run anywhere.
Poppler is now installed on the Linux runner, and the test is version-robust by construction --
both sides of its comparison go through the same pdftoppm, so it is about our two documents rather
than about Poppler's output. Verified in all three states: runs and passes with pdftoppm present,
skips without it, fails without it when PDFCRAFT_ORACLES_REQUIRED is set.
`run_capture` keeps its exact behaviour, including leaving the buffer empty on non-UTF-8 output;
the byte-oriented capture an image needs is a second entry point on the same timeout logic.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 files hand-rolled the same serialiser -- number the objects, record each one's offset, emit a
classic cross-reference table and a trailer -- 111 times over. The object bodies are properly the
business of the crate under test; the envelope around them is not, and every copy is a place for a
fixture to be quietly wrong in a way that makes a test prove less than it appears to.
`pdfcraft-testkit` holds that envelope. It has no workspace dependencies on purpose, so the crates
at the bottom of the layering can use it without a cycle, and it returns bytes rather than a parsed
document for the same reason.
The builder is byte-identical to the serialiser it replaces, asserted against a verbatim copy of
the old code across versions and trailer variations, because a migration that changes a fixture's
bytes silently changes what the test using it proves. Watched failing by breaking the free-entry
generation: three tests caught it. The one intended difference is that an empty stream dictionary
gives `<< /Length N >>` rather than a doubled space.
Migrates cos, measure and redact: 111 copies in 55 files becomes 108 in 53. The rest are
follow-ups rather than one sweep -- ui-egui alone is 27 of those files and is the most contested
area in the repository. Some copies should stay: a test about a malformed cross-reference table
needs to write its own bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every test that wanted an external oracle probed for it by hand and chose for itself what to do
when it was missing. The common choice -- print a line and return -- reports as a pass, so a suite
with no oracles installed looked exactly like one in which every oracle agreed.
That was not hypothetical. No CI runner installed qpdf, so `qpdf_agrees` had never executed on a
runner, while README.md said saved output was "independently checked with qpdf". For R2-R4 that
test is the only check of owner-password authentication (Algorithm 7) that does not go through our
own code.
`pdfcraft-oracle` holds the probes and one rule: a missing oracle is a declared skip or a failure,
never a silent pass. `PDFCRAFT_ORACLES_REQUIRED` picks which, on the same convention as
`CRAFT_FONTS_REQUIRED`. CI installs qpdf and sets it, so a skip notice there is now a red build.
The crate has no panicking path of its own; `or_skip!` expands its assert into the caller, which
is test code.
Verified by running the qpdf tests against qpdf 12.4.2 for the first time -- they pass, so the
README claim is now both enforced and true. Both gate directions were watched: absent and
unrequired skips with a notice, absent and required fails with the install command in the message.
Only qpdf is installed in CI, because it is the only tool the test job uses. The Poppler pixel
comparison in crates/organize stays ignored until a change turns it on and verifies it there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The standalone rule ran before the testkit exemption and returned early, so `cos`, `geom`,
`filters`, `crypt` and `arlington` could not use `pdfcraft-testkit` or `pdfcraft-oracle` even as a
dev-dependency. Both crates were already registered in the layering table as `Class::Testkit`, so
the first real use of either from a standalone crate would have been rejected.
A dev-dependency is not part of the graph a standalone crate publishes, which is what the rule
protects; a normal dependency on test support, and any dependency on a product crate, are still
rejected. The existing test covered neither direction of the standalone case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The terminology rationale lives in the review discussion (fork PR 1 on
pechnikov2026-star/pdfcraft and the #1976 thread); docs/ holds the
project's reference documents, not contribution-process notes.
Applies the rest of the native-speaker review on #1976 on top of the
merged redaction redesign (fork PR 1): redaction marks read 제거 대상
표시 in all ten entries (not 수정 표시), every Flatten label uses 평탄화
(not 병합, which reads as merging documents, or 평면화), Verify is 검증 so
it no longer collides with OK (확인), and Collapse is 접기 so it no longer
collides with Zoom out (축소). ATTRIBUTION.toml hash updated.
2,330-entry clean-room catalog, Albanian month and weekday names for date
formats, Preferences entry, locale matching for sq-*, coverage tests, font
coverage test, attribution entry and docs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Validate serialized PDF bytes and page count before replacing the destination in desktop and automation saves. Reject unreadable or page-losing output before file replacement and saved-state updates.
Keep staging writes synced before publication and retain the regression for a repaired page tree that cannot survive a full rewrite.
Fixes#1698
Carry the watched path and epoch through queued file reads, and reject stale results before changing the document. Preserve newer saved contents and undo history across Save and Save As while retaining ordinary external reloads.
Fixes#1802
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sync staged output after writing all bytes and before replacing its destination, closing the handle before rename. Return sync failures while keeping the old destination and ordinary save state available for retry.
Fixes#1804
Reorder document tabs by dragging while keeping the active document and its view state. Add Ctrl+Tab and Ctrl+Shift+Tab cycling in visible order, including Combine files, and translate the menu labels.
Keep timestamped batch test expectations tied to the originally generated PDF bytes.
Closes#1537Closes#1542
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A font the document does not embed was replaced by whatever the caller
happened to pick, and nothing told the user. `doc_info` said only
`embedded: false`, which is why "my Chinese PDF shows nothing" has been
a mystery rather than a sentence.
`crates/fonts/src/fallback.rs` applies one chain, in a documented order,
and returns what it chose with a reason:
1. embedded -> nothing substituted;
2. /BaseFont names a standard-14 face or a known alias (exact metrics);
3. craft-fonts holds that exact family;
4. /CIDSystemInfo /Ordering names a script we have a face for;
5. the ordering names a script we have no licensed face for -- report
none, so nothing is drawn rather than the wrong glyphs;
6. otherwise the nearest of the fourteen by descriptor flags.
It delegates to the existing per-script selectors instead of restating
their order, so each keeps one source of truth. It answers the
dictionary question only; "which face has a glyph for this character?"
depends on the string being drawn and stays where the drawing happens.
`doc_info` gains `substitute` and `substitution` per font. Both null
means the document's own glyphs are shown; a null `substitute` with a
reason means no face was available at all. That is Acrobat's Actual
Font, minus the interface, which `parity/` now records as still missing.
Two findings on the way:
`Std14::from_base_font` matches loosely, so "SomeHouseSerif" resolved to
Times by its name containing "serif". The face is a fair guess but
reporting it as "names one of the standard 14" would have been untrue of
a font nobody has heard of. `Std14::recognise` now says whether a name
matched an alias or only a family hint, and the two report differently.
`from_base_font` is unchanged.
The generated table moved to `crates/fonts/src/std14_metrics.rs`, data
alone, and the ATTRIBUTION entry and AGENTS.md 1.1 line follow it. F1
put the hash on a file holding both the table and the code reading it,
so this commit's ordinary edits tripped the gate -- and a hash that
churns on unrelated changes gets re-attested reflexively, which is the
protection gone. Verified both directions after the split: editing
std14.rs passes the gate, changing one width in std14_metrics.rs fails
it.
Every new test was watched failing first: removing the script step
breaks the two unit tests and the doc_info integration test.
Co-Authored-By: Claude <noreply@anthropic.com>