fix(xlsx): quantize interior horizontal bar-chart category baselines - #1765
Open
developer0hye wants to merge 1 commit into
Open
developer0hye wants to merge 1 commit into
developer0hye wants to merge 1 commit into
Conversation
…1621) Native Excel snaps a horizontal bar chart's interior category-label baseline to floor(sheet_frame_top_pt + row_top + row / 2) + K, with K a size-only integer (4 at 8/10pt, 5 at 14pt) measured across two charts and two category counts. bar_category_label_baseline_pt implements this and generate_chart_axis applies it to every category row strictly between the plot rectangle's own top and bottom edge, for a category-axis size this issue actually measured against native Excel. A regression check against all 24 measured rows showed the same formula cannot be safely applied to edge rows: income's top row already passes the issue's 0.5pt gate at 10pt/14pt, and the naive formula would push it to exactly -1.0 sheet points, failing a case that passes today. Edge rows keep their existing continuous placement instead of guessing at that still-unexplained residual, so issue #1621 stays open for them. Verified on the issue's own fixture: all 7 interior category-label rows across both charts now land on native's whole sheet point exactly; a before/after self-diff confirms only those 7 text instances move and nothing else on the page does (rects 738/738, 0 geometry deviations). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
13 tasks
pull Bot
pushed a commit
to RadaKichenin/office2pdf
that referenced
this pull request
Sep 17, 2026
Fresh native Excel export vs. the fixed office2pdf output for issue_1181_fit_to_height.xlsx page 2, plus a strict render-cluster disposition reused by exact bbox match from PR developer0hye#1765's report on the same fixture/page (bit-identical before/after cluster set). Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com> Co-Authored-By: Claude Sonnet 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
Native Excel snaps a horizontal bar chart's interior category-label baseline to
floor(sheet_frame_top_pt + row_top + row / 2) + K, withKan integer that depends only on the declared category-axis size (4at 8/10pt,5at 14pt) — not on which chart, how many categories it has, or the row's own position. Measured across two worksheet bar charts, two category counts and three sizes over three prior sessions (#1763, #1764, both merged, measurement-only).bar_category_label_baseline_pt(new,typst_gen_diagrams.rs) implements this, andgenerate_chart_axisapplies it to every category row strictly between the plot rectangle's own top and bottom edge, gated to a worksheet-anchored chart and a category-axis size this issue actually measured against native Excel.Why edge rows are excluded, not "not yet fixed": a regression check against all 24 previously-measured rows (
assets/validation/issue-1621/measurements.json) before writing any code showed the same formula cannot be safely applied to every row. Income's top edge row already sits inside this issue's own 0.5pt gate at 10pt (+0.096pt) and 14pt (+0.324pt); the naive formula would push it to exactly -1.0 sheet points (-0.78 printed points), failing a case that passes today. Expense's edges fail the same way in the other direction. So "interior rows only" is the largest rule this data supports without regressing an already-passing row — seeassets/validation/issue-1621/README.md's new "Shipped: interior rows only" section for the full arithmetic.The remaining edge-row mechanism (why an edge deviates and in which direction is chart-dependent, for a reason not yet isolated) is unchanged from the prior sessions' findings and keeps #1621 open.
Testing
bar_category_label_baseline_matches_measured_native_interior_rows(15 real native-Excel data points across 3 plot geometries and 3 sizes — the pure function),bar_category_label_baseline_keeps_no_rule_for_an_unmeasured_size,a_worksheet_bar_chart_seats_only_interior_category_labels_on_the_quantized_baselineanda_worksheet_bar_chart_at_an_unmeasured_size_keeps_every_labels_centred_seat(the wiring) all fail without the fix.cargo test --locked -p office2pdf --lib: 3097 passed.cargo test --locked --workspace: all suites green.cargo fmt --all --check/cargo clippy --locked -p office2pdf --all-targets: clean (pre-existing unrelated warnings only).python3 assets/validation/issue-1621/verify_measurements.py:PASS(unchanged claims; this PR ships the interior-row half of claim 4, the edge-row exception set in claims 4/5 is still open).compare_layout.py,--noise-floor 0.01 --fine-shift 0.01): exactly the 7 interior category labels move (4 expense, 3 income), each landing on native's whole sheet point; 592 text instances compared, 0 missing/extra; rect census bit-identical (738/738, 0 geometry deviations) — nothing else on the page moves.PASS(two passes; the first caught a doc-comment/behavior mismatch and a forward-referenced README claim, both fixed and re-verified).Visual impact
Visual audit
Issue: XLSX: horizontal bar-chart category baselines deviate from native row placement #1621
Fixture:
tests/fixtures/xlsx/issue_1181_fit_to_height.xlsx(unmodified; SHA-2562b4a2d8dceda58758593c88409875efbda05780559154c02bd13fef4f7a1c65b)Page(s): 2
Renderer and DPI: Microsoft Excel for Mac native reference; pdftoppm, 300 DPI
Evidence mode:
fixLayout audit report:
assets/bugfixes/issue-1621/layout-audit.jsonRender cluster reports:
assets/bugfixes/issue-1621/render-clusters-page-2.jsonReference exporter differences: None
Fine-detail threshold: 0.5pt
Layout audit page count: Pass
Layout audit text flow: XLSX: title+header-row text on one native baseline reports as a 1-into-2 line reflow #1760
Layout audit visible fills: Pass
Layout audit rectangle geometry: XLSX: fitted worksheet cell fills sit one sheet point above Excel's #1632, XLSX: horizontal bar-chart value-bar fills sit ~0.544pt above native, widest bar 11pt narrow #1759
Layout audit large shifts: Pass
Layout audit fine shifts: XLSX: horizontal bar-chart category baselines deviate from native row placement #1621, XLSX: fitted horizontal bar-chart value labels sit about 0.61pt too high #1622, XLSX: large left-aligned Cambria title starts 1.255pt left of native #1623, XLSX: right-aligned cells on the fitted budget sheet land 0.9pt left to 2.2pt right of Excel by number format #1631, XLSX: fitted worksheet text keeps the physical margin origin while its paint layer snaps to Excel's sheet-space origin #1719, XLSX: a form-control label row sits 2.16pt above Excel where the rest of the fitted sheet sits ~0.6-0.8pt off #1757, XLSX: repeated %INC column-header text sits 0.39pt right of native #1761
New follow-up issues found in this audit: None
Model vision findings: Full GT/before/after page-2 crops were inspected at 300 DPI (
assets/bugfixes/issue-1621/gt.jpg/before.jpg/after.jpg), plus tight crops of both bar charts' label columns. At normal viewing scale the fix is invisible — each affected label moves under 1 printed point and stays vertically centered on its own bar in both the before and after renders, matching the near-zero measured residual. Thecompare_render.py --strict-clusterspass (395/395 clusters dispositioned) surfaced 15 clusters not present in the last full committed baseline (assets/bugfixes/issue-1564/render-clusters-page-2.json, 380/395 reused by exact bbox match); all 15 were individually cropped and inspected this session: 9 are XLSX: horizontal bar-chart value-bar fills sit ~0.544pt above native, widest bar 11pt narrow #1759's own thin bar-fill edge strips (visually confirmed against XLSX: horizontal bar-chart value-bar fills sit ~0.544pt above native, widest bar 11pt narrow #1759's description), 3 are XLSX: repeated %INC column-header text sits 0.39pt right of native #1761's repeated "% INC" header text, 2 (the "JAN" spinner band and a small chart indicator fill) directly match XLSX: fitted worksheet cell fills sit one sheet point above Excel's #1632's own already-written disposition note text and, for the indicator fill, its exact measured -0.78pt (one sheet point) offset, and 1 is a comma-grouped amount ("$1,056") matching XLSX: right-aligned cells on the fitted budget sheet land 0.9pt left to 2.2pt right of Excel by number format #1631's number-format cell family. A before/after self-diff at the pixel-cluster level is bit-identical (395/395, 0 new/removed clusters) — this fix's effect sits under the 5%-fuzz clustering floor at 300 DPI.other expensesandroom & board(expense-chart edge rows) and 8 render clusters newly attributed to XLSX: a 14-15pt bottom-aligned cell in a 19.5pt fixed row is centred as a tight row where Excel bottom-seats it #1721 (a 14-15pt bottom-aligned cell being centred as a tight row, all off the plot area) were individually cropped and confirmed against those issues' own descriptions; XLSX: title+header-row text on one native baseline reports as a 1-into-2 line reflow #1760's pre-existing 1-into-2 line reflow is unchanged by this fix.GT:
assets/bugfixes/issue-1621/gt.jpgBefore:
assets/bugfixes/issue-1621/before.jpgAfter:
assets/bugfixes/issue-1621/after.jpgNative: None
Compare:
assets/bugfixes/issue-1621/compare.jpg(filed with the original issue)Required inspection
Deviation audit
Checklist
Signed-off-bylineRelated: #1621
🤖 Generated with Claude Code