fix(xlsx): seat bottom-aligned cell text on the descender line - #711
Merged
Conversation
Excel puts a bottom-aligned cell's descent bottom on the row's bottom inset edge, all slack above. office2pdf left the slack below the text through two mechanisms, measured off native Excel GT: 1. The spill wrapper (unwrapped cells overflowing empty neighbours) hardcoded #place(.. + horizon), so the table-level align: bottom only reached the zero-width strut and the visible line stayed vertically centred — off by (rowHeight − lineHeight)/2, 2.20pt on the inventory title and 9.54pt on WidthsAndHeights B1. Its 1.3em clip box also resolved at the ambient 11pt, not the cell's font size. 2. East Asian rows carry Word's 1.3x hhea line factor (#518) with the surplus split symmetrically, so the line-box bottom that Typst's align: bottom seats on the inset edge sits 0.15 x pitch below the real descender — payroll row-9 numbers floated 4.41pt up where the font's descent is 2.4pt, putting bottom-aligned numbers above their centre-aligned Korean label in the same 14pt row. The fix: in fixed-height rows only, the spill wrapper anchors at the cell's effective vertical alignment with a pt-sized strut derived from the paragraph's own metrics, and a new Table flag seats_bottom_aligned_text_on_descender (set only by the XLSX parsers, false for DOCX/PPTX whose seating is unverified against Word GT) makes bottom-aligned cells emit bottom-edge: -descender with the removed surplus moved into par leading, keeping multi-line advance unchanged. Auto rows keep the symmetric box: their intrinsic heights were calibrated against GT (#396/#411/#498) and only fixed rows have slack to distribute. Measured after the fix (mutool draw -F trace): - 03_inventory_en title baseline 88.33 -> 90.53 (Excel 92.00; the 1.47pt residual is the row-1 track, #658) - WidthsAndHeights B1 baseline 75.01 -> 84.55 (residual is #658) - 04_payroll_ko row-9 numbers 243.54 -> 245.54; the impossible bottom-above-centre inversion shrinks 3.14pt -> 1.15pt (residual is the EA auto-row line box, #709) Latin fixed rows are byte-identical (hhea lineGap folds into the ascender, so the seated bottom edge equals the old one); DOCX/PPTX are byte-identical, guarded by a regression test. Visual audit on 03_inventory_en p1 filed the two residual root causes it exposed: #709 (EA auto rows +5.79pt/row) and #710 (Latin auto rows +1pt/row). Related: #618 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
XLSX
<alignment vertical="bottom"/>cells (Excel's default) rendered their text too high — the row's slack landed below the text. Excel's rule, measured off native GT: the last line's descent bottom rests on the row's bottom inset edge, all slack above. Two mechanisms caused the drift, both fixed for fixed-height rows only:typst_gen_tables.rs): unwrapped cells spilling over neighbours emitted#place(.. + horizon, box(height: 1.3em, ..))— a hardcoded centre anchor with an ambient-sized strut. The wrapper now anchors at the cell's effective vertical alignment with a pt-sized clip box and strut derived from the paragraph's own line metrics. Centre-anchored spill cells keep the byte-identical legacy emission.typst_gen_text.rs): the EA line box splits Word's 1.3× hhea factor symmetrically, soalign: bottomseated the line-box bottom 0.15×pitch below the real descender. A new Table IR flagseats_bottom_aligned_text_on_descender(set only by the XLSX parsers) re-seats bottom-aligned cells:bottom-edge: -descenderwith the removed surplus moved intopar leading, keeping multi-line advance unchanged.Auto-height rows keep the symmetric box — their intrinsic heights were GT-calibrated (#396/#411/#498) and only fixed rows have slack to distribute. DOCX/PPTX output is byte-identical (flag false; guarded by a regression test).
Related issue
Fixes #618
Testing
cargo test --workspace— 2112 passed, 0 failed (5 new TDD tests written red-first: bottom spill anchor, centre spill guard, descender seat, centre symmetric guard, Word-table guard; plus an auto-height-row regression guard)cargo +1.97 clippy --workspace --all-targets— cleancargo fmt --check— cleanVisual impact
Visual audit
fixassets/bugfixes/issue-618/gt.jpgassets/bugfixes/issue-618/before.jpgassets/bugfixes/issue-618/after.jpgVisual comparison
Required inspection
Deviation audit
Checklist
Signed-off-byline🤖 Generated with Claude Code