fix(docx): autofit table columns by compressible slack, not a uniform scale - #726
Merged
Conversation
… scale
A w:tbl without w:tblLayout fixed is auto-fitted by both Word and
office2pdf — by different rules. We scaled the per-column tcW maxima
uniformly, so the invoice's conflicting 4200-twip claims put Description
and Amount at an identical 161.32pt (the equal-share signature), Unit
Price lost 9.6pt, a wrap flipped, and everything below sat 11.3pt high.
Word compresses each grid column in proportion to its compressible
slack above min-content, never below it:
k = (W - Σmin) / (Σpref - Σmin)
width_i = min_i + (pref_i - min_i) · k
pref_i is the widest single-column tcW occupying grid column i (tracked
through gridSpan: a cell following a span-4 cell claims column 5), with
gridCol_i as fallback; min_i is the widest unbreakable token measured
with the run's resolved face, weight and size plus the tcMar side
margins — bold runs use the bold face, every CJK character is its own
token (Word breaks between them), and NBSP does not break. W is tblW
when it does not exceed the grid sum, else the grid sum. Derived and
verified on the invoice's Word GT: every column lands within 0.10pt of
27.9/156.9/47.8/65.3/153.3, the row-2 wrap flips to Word's two lines,
and the totals ride the col-4/5 boundary within 0.28pt.
Measurement uses a new cached render-side text_advance_em (hmtx sums,
per-(family, weight) face cache, wasm None twin). The model runs only
in the GT-verified compression direction; conflict-free tables return
the grid verbatim, and any unmeasurable token — wasm, unresolved face,
missing glyph, all-empty tables — degrades to the exact pre-change
uniform scale, so every other document is byte-identical: all nine
remaining business docx mocks and the 39-page technical brief convert
to byte-identical PDFs against main.
Related: #624 #355
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A
w:tblwithoutw:tblLayout fixedis auto-fitted by both Word and office2pdf — by different rules. We scaled per-columntcWmaxima uniformly: the invoice's conflicting 4200-twip claims put Description and Amount at an identical 161.32pt (the equal-share signature), Unit Price lost 9.6pt, a wrap flipped, and the lower half of the page sat 11.3pt high.Word compresses each grid column in proportion to its compressible slack above min-content, never below it:
k = (W − Σmin)/(Σpref − Σmin),width_i = min_i + (pref_i − min_i)·k. Preferences track grid-column occupancy throughgridSpan; min-content is the widest unbreakable token measured with the run's resolved face/weight/size plustcMar(every CJK character is its own token; NBSP does not break). W istblWcapped at the grid sum. Derived and verified on the invoice's Word GT: every column within 0.10pt, the row-2 wrap flips to Word's two lines, the totals ride the col-4/5 boundary within 0.28pt.Measurement uses a new cached
render::pdf::text_advance_em(hmtx sums, per-(family, weight) face cache, wasm None twin, embedded/user font paths documented as the same accepted limitation as #621's accessor). The model runs only in the GT-verified compression direction; conflict-free tables return the grid verbatim; any unmeasurable token degrades to the exact pre-change uniform scale — all nine other business docx mocks and the 39-page technical brief are byte-identical to main.Related issue
Fixes #624
Testing
cargo test --workspace— green (11 red-first unit tests: slack distribution, gridSpan occupancy, min floors, grid-verbatim, degrade paths, CJK singleton tokens — the reviewer's 200pt-table CJK probe no longer overflows to 377pt — NBSP tokens, tblW cap, undershoot gate, all-empty parity; text_advance_em pins incl. bold-face selection)cargo +1.97 clippy --workspace --all-targets— clean;cargo fmt --check— clean; wasm check cleanVisual impact
Visual audit
fixassets/bugfixes/issue-624/gt.jpgassets/bugfixes/issue-624/before.jpgassets/bugfixes/issue-624/after.jpgVisual comparison
Required inspection
Deviation audit
Checklist
Signed-off-byline🤖 Generated with Claude Code