Repository navigation
Forms: measure a field's own simple font, not Helvetica, when laying out its text - #2900
marcoarnulfo wants to merge 2 commits into
Conversation
…out its text (fixes storytold#2894)
|
Test report for b2150f3 on Windows 11 (x64), compared with a
Both now wrap against the field's own font: Courier no longer runs past the box, and Times uses the width it has. |
ahmadmuj
left a comment
There was a problem hiding this comment.
Found a standard-14 regression with Helvetica, /Differences [65 /W 87 /A], and no /Widths. AAAA encodes as WWWW, but the new measurement uses W's width instead of the mapped A glyph.
At 10 Tf, a 200 pt right-aligned field with a one-point border starts at 159.24 pt instead of 170.32 pt. The saved-appearance test fails on b2150f3 and passes when only this width change is removed. Could you cover this case and preserve mapped glyph widths? The complete-width and auto-size save/reopen probes pass.
FredApps
left a comment
There was a problem hiding this comment.
Requesting changes at b2150f3. This confirms the regression ahmadmuj reported in the earlier review.
Defect: FontText::width (crates/forms/src/appearance.rs, the new Self::Simple(metrics) arm) sums metrics.width(code) over the encoded bytes. For a standard-14 font with no /Widths, that table is looked up by code, so a /Differences remap is ignored: the width used is that of the glyph the code has in the base encoding, not of the glyph the code now draws.
Reproduction: a Helvetica font with /Encoding << /BaseEncoding /WinAnsiEncoding /Differences [65 /W 87 /A] >>, no /Widths, reused from /DR. A 200 pt wide field with /Q 2 (right-aligned) and /DA (/Dif 10 Tf 0 g) holds "AAAA". The text encodes to code 87 four times (glyph A, 0.667 em), so it should start at 198 - 26.68 = 171.32. With this PR it starts at 160.24, i.e. it is laid out with the width of W (0.944 em). On the merge base the same field starts at 171.32. So this is a regression against the current behaviour for any remapped standard-14 font, in alignment, wrapping and auto size.
Probe I ran (not part of the PR): batchfa6_probe_differences_widths asserts the start is within 0.05 of 171.32. It passes on the merge base and fails on this head with "starts at 160.24". Please measure by the glyph the code draws (the /Differences name) when there is no /Widths, and add a test for it.
Everything else checked out, so this is the only blocker:
reused_standard_fonts_wrap_and_align_with_their_own_widthspasses on this head (1 passed) and fails withcrates/forms/src/appearance.rsrestored to the merge base, at tests.rs:393 ("every line fits the box in Courier: [128, 71]"), so it does prove the Courier and Times behaviour.cargo test --locked -p pdfcraft-forms: 79 passed, 0 failedcargo fmt --all -- --check,cargo clippy --locked -p pdfcraft-forms --all-targets -- -D warnings,cargo check --locked -p pdfcraft-forms --target wasm32-unknown-unknown,cargo xtask layers: all pass
Run on a Linux validation host in a detached worktree. Not verified: native Windows or macOS, and fonts with an explicit /Widths plus /Differences.
A standard-14 font with no /Widths was measured by code in the WinAnsi-indexed table, so a /Differences remap took the width of the glyph WinAnsi gives that code: Helvetica with [65 /W 87 /A] laid out "AAAA" four W's wide. The code is now looked up through the font's encoding first; /Widths still go by code, and Symbol, ZapfDingbats and glyphs WinAnsi has no code for keep the code itself.
|
@ahmadmuj @FredApps you're right, and thanks for measuring it on both sides of the merge base, that made it quick to pin down. The lookup was wrong one level down: Tests:
Since the fix is in I couldn't run the wasm check locally (target not installed here); the change is plain Rust with no platform code. |
mtlcl410
left a comment
There was a problem hiding this comment.
Re-tested a61bd92 on Windows 11 x64 (CLI built from this head, compared with main @ d8b2705).
Regression fixed. This is the case @ahmadmuj and @FredApps reported: Helvetica, /Differences [65 /W 87 /A], no /Widths, /Q 2, a 200 pt field, "AAAA". The appearance is now 1 0 0 1 171.32 7.445 Tm (WWWW) Tj, the expected start (b2150f3 gave 160.24).
The original fix still holds. On the issue's Courier fixture and my Times-Roman variant, the widget appearance is identical to what I validated on b2150f3: Courier wraps to lines of 91/86/21 characters (widest 546 pt in the 570 pt box), Times to 139/60.
The StandardEncoding side effect you mentioned is right. A Times-Roman /DR font with no /Encoding, right-aligned, filled with it''s ``ok'':
- the encoder writes StandardEncoding codes (
0xA9quotesingle,0xC1grave); - this head starts the line at 162.19, exactly what the Times-Roman AFM widths of those glyphs give (198 − 35.81);
mainstarts it at 160.36, measuring those codes as WinAnsi's © and Á.
Tests fail without the fix. Calling f.width(code) instead of f.width(self.std14_code(f, code)) fails standard_fonts_without_widths_measure_the_glyph_their_differences_name. The --lib suites of forms (80), fonts (71), edit (156), redact (43) and a11y (49) pass on this head.
A small leftover, not a blocker. The standard-14 tables are WinAnsi-ordered, so a glyph the encoding names that WinAnsi has no slot for still falls back to the raw code. Examples are StandardEncoding's fi/fl (0xAE/0xAF), Lslash/lslash and dotlessi, or a /Differences entry naming Lslash. They are measured as whatever WinAnsi has at that code (fi as ®: 760 vs 556 in Times). That's the documented fallback and rare in form text. #3076 adds Core14 widths for the Latin Extended-A letters, which could serve those names later.
FredApps
left a comment
There was a problem hiding this comment.
Re-reviewed at a61bd92. The /Differences width blocker is fixed.
- Fix:
Metrics::width(crates/fonts/src/pdf.rs:769) now looks up the standard-14 table bystd14_code(pdf.rs:781-786). That takes the glyph the font's encoding (base plus/Differences) gives the code, then maps it to its WinAnsi slot, which is how the table is indexed. Symbol and ZapfDingbats keep their own code, and so does any glyph WinAnsi has no slot for. Fonts that have/Widthsstill go by code. Because the fix is in the shared fonts crate, it also covers the forms appearance path I reported, not only forms. - My reproduction: the right-aligned "AAAA" field with
/Differences [65 /W 87 /A]and no/Widthsnow starts at 171.32, the same as on the merge base. The new forms test checks exactly that case, plus the/Widthscase I hadn't verified before (178.0). The new fonts unit test covers the A/W swap, a glyph with no WinAnsi slot, an unreadable name, and/Widthsoverriding by code. - Tests guard the fix: I put back b2150f3's
crates/fonts/src/pdf.rsand kept the new tests. Both fail: the fonts test at pdf.rs:1343 and the forms test at tests.rs:439 (the 171.32 assertion). Both pass with the fix.
Local checks on this head, Linux: cargo fmt --all -- --check OK; cargo clippy --locked --workspace --all-targets -- -D warnings clean; cargo test --locked --workspace all passing (167 test binaries); cargo xtask layers OK; cargo xtask wasm (31 crates) OK. Hosted ubuntu, wasm32 and licence jobs are green. Windows is still pending, and I didn't test it or macOS locally.
ahmadmuj
left a comment
There was a problem hiding this comment.
Rechecked a61bd92 with the retained bordered fixture. The saved/reopened AAAA appearance now starts at 170.32 pt; restoring the old lookup gives 159.24 pt. This resolves my earlier finding. The one-point difference from your fixture comes from the border padding.
The five explicit-width/save-reopen cases and StandardEncoding, Symbol and Dingbats controls pass. Fonts/forms tests (154), formatting and scoped Clippy pass too. I did not run a viewer check.
Fixes #2894.
Since #1044 a field is drawn in the
/DAfont from/DRwhen it can encode the value, butFontText::widthstill measured that font with Helvetica's table, so wrapping, quadding and0 Tfauto size were laid out for a different face. In Courier (0.6 em) mixed-case text overflows: the issue's field gets a 128-character first line in a box that holds 94. This measures a reused simple font with its ownMetrics, i.e. its/Widths, or the standard-14 table when it has none (Courier, Times, Helvetica and their styles), which is what @MONUABST suggested in the issue, thanks for the clear write-up and the repro. For a standard-14 font without/Widths,Metrics::width(pdfcraft-fonts) now looks a code up through the font's encoding, so a/Differencesremap is measured as the glyph it draws rather than the one WinAnsi has at that code; this also applies to the otherMetrics::widthcallers (edit, redact, a11y). Helvetica fallback and the CJK path are unchanged; Symbol and ZapfDingbats never get here, sincedr_fontalready sends them to Helvetica. The module comment said widths were Helvetica's, so it's updated too.#2466 adds a
Compositearm next to the same line with the same measurement, so whichever lands second has a one-line conflict in thatmatch, happy to rebase.Validation:
reused_standard_fonts_wrap_and_align_with_their_own_widths(crates/forms): the issue's Courier field plus right-aligned Courier and Times-Roman fields. Fails on main (every line fits the box in Courier: [128, 71]), passes here; measuring every simple font as Courier makes it fail on the Times field (Times: 174)pdfcraft-cli runandrender: the text now wraps at "going" and stays in the boxcargo test -p pdfcraft-forms --lib(appearance tests),cargo clippy -p pdfcraft-forms --all-targets -- -D warnings,cargo fmt --all -- --checkremapped_standard_fonts_are_measured_by_the_glyph_each_code_draws(crates/forms) andstandard_fonts_without_widths_measure_the_glyph_their_differences_name(crates/fonts): Helvetica with/Differences [65 /W 87 /A]and no/Widths. The forms test fails on b2150f3 (no /Widths: 160.24) and with the encoding lookup removed; explicit/Widthsstill go by codecargo test -p pdfcraft-a11y --lib,cargo test -p pdfcraft-edit --lib,cargo test -p pdfcraft-redact --lib,cargo clippy -p pdfcraft-forms -p pdfcraft-fonts --all-targets -- -D warnings,cargo xtask layersSummary by CodeRabbit