Skip to content

XLSX: vertical="bottom" cells leave their slack below the text, so bottom-aligned runs sit up to 12pt high #618

Description

@developer0hye

Summary

bottom is OOXML's default cell vertical alignment, and <alignment vertical="bottom"/>
is also set explicitly on the title and numeric cells of most audit workbooks. Excel puts
the descender bottom of the last line on the row's bottom edge. office2pdf instead leaves
the row's slack below the text — the line box behaves as if it were centred, or as if an
oversized descent were reserved under it — so bottom-aligned text sits high by an amount
that scales with (rowHeight − lineHeight).

Cells that ask for vertical="center" land correctly in every file checked, which isolates
the fault to the bottom-alignment path rather than to the line model in general.

Measured

case GT (Excel) office2pdf delta
03_inventory_en p1, merged A1 title (Arial Bold 14 in a 23pt row) baseline 92.00pt 88.33pt 3.67pt high
05_project_schedule_en / 06_sales_dashboard_en p1, merged A1 title baseline 92.00pt 88.33pt 3.67pt high
09_expense_report_en / 10_kpi_tracker_en p1, title A1 baseline 92.00pt 88.33pt 3.67pt high
02_financial_model_en p1, row-1 title, ink bottom @150 DPI y = 197 px y = 189 px 3.8pt high
04_payroll_ko p1, row 9 (합계): label vs numbers both on baseline 218.00pt numbers 243.54pt, label 246.69pt 3.15pt split
WidthsAndHeights p1, B1 beside a two-line wrapped A1 (ht="37.5" customHeight="1") bottom y ≈ 87.1pt 75.1pt 12.0pt high

GT's baselines are exactly what bottom alignment predicts: rowBottom − descent
(Arial 14pt descent 0.212 em = 2.97pt → 95 − 2.97 = 92.03; Malgun Gothic 14pt descent
4.06pt → 90.94).

The 04_payroll_ko total row is the sharpest case because it removes row height as a
variable: that row measures 14.00pt in both GT and output, yet the vertical="center"
merged label (xf7) ends up 3.15pt below the vertical="bottom" numbers (xf5/6/8) in the
same row. A centred cell cannot legitimately sit lower than a bottom-aligned one.

Reproduction

cargo run -p office2pdf-cli -- tests/golden_mocks/business/sources/xlsx/03_inventory_en.xlsx -o out.pdf
mutool draw -F trace -o - out.pdf 1 | grep -i 'Warehouse Inventory'   # baseline 88.33, Excel 92.00

cargo run -p office2pdf-cli -- tests/fixtures/xlsx/poi/WidthsAndHeights.xlsx -o wh.pdf

Expected

A vertical="bottom" cell places its last line so the font's descent bottom rests on the
row's bottom inset edge, leaving all the slack above the text.

Actual

The slack is split (or left below the text), so the run sits 3.7-3.8pt high on a 14pt
heading, splits baselines by 3.15pt when a row mixes CJK and Latin cells, and reaches 12pt
when a row mixes wrapped and unwrapped cells.

Related: #298

Found in the 2026-07-29 full-corpus visual audit at c1adfbd (native Office GT, 150 DPI, mutool-trace geometry).

Visual comparison

Compare
Compare

GT left, office2pdf right, 150 DPI, same page.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions