Skip to content

fix(xlsx): quantize interior horizontal bar-chart category baselines - #1765

Open
developer0hye wants to merge 1 commit into
mainfrom
fix/issue-1621-bar-category-interior-baseline
Open

developer0hye wants to merge 1 commit into
mainfrom
fix/issue-1621-bar-category-interior-baseline

Conversation

@developer0hye

@developer0hye developer0hye commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

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 an integer that depends only on the declared category-axis size (4 at 8/10pt, 5 at 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, and generate_chart_axis applies 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 — see assets/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

  • Red/green: 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_baseline and a_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).
  • Before/after self-diff of office2pdf's own output in isolation (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.
  • Required delegated read-only documentation freshness audit: PASS (two passes; the first caught a doc-comment/behavior mismatch and a forward-referenced README claim, both fixed and re-verified).

Visual impact

  • No rendered PDF change
  • Rendered PDF change or visual evidence added

Visual audit

Required inspection

  • Rendered all evidence at 150 DPI or higher
  • Stored progressive JPEG quality 86 assets with metadata stripped
  • Used Codex/Claude vision to inspect the full GT/output pages, diff, and matched crops
  • Inspected matched region crops at full resolution
  • Ran compare_layout.py --audit --fine-shift PT and dispositioned every fine/large text-instance shift, rectangle geometry deviation, painted-text visibility mismatch, and visible-fill occlusion
  • Ran compare_render.py --cluster-report PATH --strict-clusters and dispositioned every material 5% fuzz diff cluster by explicit ID
  • Inventoried hairlines and border dash styles
  • Inventoried font weight, italic, and underline emphasis

Deviation audit

Check Result
Page count/order Matches GT
Element presence Matches GT
Position/size Remaining: #1621 (2 edge rows), #1622, #1623, #1631, #1719, #1721, #1757; 7 interior category-label rows fixed by this PR.
Rotation/flip No deviation observed
Fill Remaining: #1632, #1759
Stroke/border No deviation observed
Shape outline geometry No deviation observed
Text content No deviation observed
Font family/weight/style Matches GT
Text color Matches GT
Alignment Remaining: #1621 (2 edge rows), #1631, #1719, #1721, #1761; 7 interior category-label rows fixed by this PR.
Line/paragraph spacing Remaining: #1760
Clipping/overflow No deviation observed

Checklist

  • Commits include a Signed-off-by line
  • PR scope contains one root cause
  • Remaining converter or harness deviations each reference an open issue

Related: #1621

🤖 Generated with Claude Code

…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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant