Skip to content

fix(xlsx): clip page fills one sheet point inside the top and left gridlines - #1743

Merged
developer0hye merged 1 commit into
mainfrom
fix/issue-1605-fill-clip
Sep 15, 2026
Merged

developer0hye merged 1 commit into
mainfrom
fix/issue-1605-fill-clip

Conversation

@developer0hye

Copy link
Copy Markdown
Owner

File submission policy

Before attaching or committing files, read the submission policy.
Complete a security review and obtain your organization's internal approval for
public sharing where applicable. You are responsible for lawful disclosure;
the project and maintainers disclaim related liability to the extent permitted
by applicable law.

  • Any submitted sample files or attachments satisfy the submission policy, or none are submitted.

Summary

Excel clips every printed page's cell fills to that page's grid region inset by one sheet point on the top and left edges, while the bottom and right edges keep the one-point positive-axis bleed and every later row and column keeps its raw origin. The converter painted the first row and first column from the raw gridline, so the rose title of the reported workbook started at x=50/y=54 where native shows x=51/y=55.

The rule was read off three native Excel 16.112 exports of the public #982 workbook rather than argued from one page: the unscaled first sheet without a print area clips its fills to [51, 55, 474, 418] around a raw title rectangle of [50, 54, 474, 145]; the fitted 0.82 sheet with the explicit A1:R12 print area clips at [55.76, 54.12] for a grid origin of [54.94, 53.30] (one scaled sheet point); and that sheet's unscaled export (#1598) clips its continuation page at x=473 for a raw fill that starts at x=472, so the clip is per page, not per sheet. Cell text is unaffected because Excel already seats content inside [B+1, B_next].

Key changes:

  • excel_fill_paint.rs: a tracked fill whose track starts on the table's grid origin loses one sheet point (1 × print scale) on that edge — position moves in and the size shrinks — beside the existing bleed extension; interior fills, fills behind an unfilled gutter row or column, the bleed and the row-first paint order (XLSX: cell fill extensions overpaint neighboring colors #1599) are untouched. The origin is the table's start tag in the frame that holds the fills, because Typst inlines a single-item frame into its parent and a one-cell table's fill then carries page coordinates.
  • typst_gen_table_border_tests.rs: page_fills_lose_one_sheet_point_on_the_top_and_left_grid_edges pins the rule at scale 1.0 and 0.82 on one page and on a second sheet page (the parser's continuation page), asserting the inset first row/column, the unchanged interior origins and the surviving bleed. Three existing fill tests derived the grid origin from the first cell's fill and now subtract the clip; the one-cell Excel/Word/centred-stroke comparison asserts the Excel fill starts one point inside the shared origin and reaches one point past its right boundary.
  • assets/bugfixes/issue-1605/: fix-mode evidence (gt/before/after at 300 DPI, layout-audit.json, strict render-clusters-page-1.json).

The layout harness could not see this defect before the fix: matching raw rectangles skip the visible-coverage check, so native's clipped fill against our unclipped one reported geometry 0. Filed as #1742.

Related issue

Fixes #1605
Related: #1742, #1659

Testing

  • cargo test --locked -p office2pdf --lib page_fills_lose_one_sheet_point — fails on main (first column/row fills start at the grid origin at both scales, on both pages) and passes with the change.
  • cargo test --locked -p office2pdf --lib border_tests — 44 passed.
  • cargo test --locked --workspace — 3721 passed, 0 failed.
  • cargo +1.97 clippy --locked --workspace --all-targets -- -D warnings and cargo fmt --all -- --check clean.
  • Native GT: the Excel 16.112 whole-workbook export recorded in XLSX: centered text shifts left on unscaled worksheet print tiles #1600 (SHA-256 2aa03caf…); before binary is main at 4919bfc; both conversions with --font-path for the Office cloud-font cache and Excel's DFonts bundle.
  • mutool draw -F trace on page 1: after the fix the rose title paints [51, 55, 474, 145] (native [51, 55, 474, 145] visible under its clip; before [50, 54, 474, 145]), the pale body starts at y=144 under it as native does. Page 2 is pixel-identical before and after (magick compare -metric AE 0 at 150 DPI, identical trace rectangles), because its first filled cells sit inside gutter rows and columns.
  • python3 scripts/compare_layout.py GT.pdf after.pdf --page 1 --noise-floor 0.5 --audit --fine-shift 0.5 --json; python3 scripts/compare_render.py GT.pdf after.pdf --page 1 --dpi 300 --fine-shift 0.5 --artifacts-dir … --cluster-report … --cluster-dispositions … --strict-clusters (71/71 clusters dispositioned, strict PASS); python3 scripts/compare_text_layer.py GT.pdf after.pdf --page 1 (text layer intact).

Visual impact

  • No rendered PDF change
  • Rendered PDF change or visual evidence added
  • Reason: N/A

Visual audit

  • Issue: XLSX: worksheet fills extend beyond the native used-range clip #1605
  • Fixture: tests/fixtures/xlsx/issue_1603_gift_budget.xlsx (public Gift Budget and Tracker1.xlsx from Gift Budget and Tracker1.xlsx misc issues #982, SHA-256 25f5dc75dab19ea12042979a61842314ddc226e3e45d447e36b2a2a104112613)
  • Page(s): 1
  • Renderer and DPI: pdftoppm, 300 DPI
  • Evidence mode: fix
  • Layout audit report: assets/bugfixes/issue-1605/layout-audit.json
  • Render cluster reports: assets/bugfixes/issue-1605/render-clusters-page-1.json
  • Reference exporter differences: None
  • Fine-detail threshold: 0.5pt
  • Layout audit page count: Pass
  • Layout audit text flow: Pass
  • Layout audit visible fills: Pass
  • Layout audit rectangle geometry: Pass
  • Layout audit large shifts: Pass
  • Layout audit fine shifts: Pass
  • New follow-up issues found in this audit: None
  • Model vision findings: Inspected the full native and output page 1 side by side, the 300 DPI 5% diff page, the 400x300px title-corner crops of GT/before/after, the title TEM|PLATE crop and matched 480x70px crops at the start and end of the long body line Enter in table the names…. In the corner crops the before panel's rose fill begins four device pixels (one point) above and left of native on both edges; the after panel's fill edge coincides with native on both edges, and the diff page shows no line along the panel's top or left boundary while the panel's bottom seam against the pale body is also clean (XLSX: cell fill extensions overpaint neighboring colors #1599 fixed earlier). Every remaining diff cluster is glyph-sized: the body lines match at their start and drift right by about 0.8pt by the end of the line (whole-point glyph advances, XLSX: Excel for Mac advances each sheet glyph a whole point, so long strings drift up to 1.3pt from our fractional advances #1659), the title's M shows the same drift on its diagonals, and the 8pt footer shows outline-only differences. Note: keeps its bold weight and every other run is regular; no italic, underline, hairline or border exists on this page in either render, and no cluster touches the fill edges.
  • GT: assets/bugfixes/issue-1605/gt.jpg
  • Before: assets/bugfixes/issue-1605/before.jpg
  • After: assets/bugfixes/issue-1605/after.jpg
  • Native: None
  • Compare: assets/bugfixes/issue-1605/compare.jpg

Visual comparison

GT Before After
GT Before After

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: #1659 (glyph bodies drift up to about 0.8pt along the long body lines; line origins match within 0.04pt)
Rotation/flip No deviation observed
Fill Fixed: the title and body fills start one sheet point inside the page's top and left gridlines as native clips them
Stroke/border No deviation observed
Shape outline geometry No deviation observed
Text content Matches GT
Font family/weight/style Matches GT
Text color Matches GT
Alignment Matches GT
Line/paragraph spacing Matches GT
Clipping/overflow Fixed: the page fill clip is reproduced on the first row and column

Checklist

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

🤖 Generated with Claude Code

…idlines

Excel clips every printed page's cell fills to the page's grid region inset
by one sheet point on the top and left edges; the bottom and right edges keep
the positive-axis bleed and later rows and columns keep their raw origin. The
converter painted the first row and column from the raw gridline, so the
reported workbook's title fill started at x=50/y=54 where native shows 51/55.

Three native exports of the public #982 workbook settle the rule: the unscaled
sheet without a print area clips at [51, 55, 474, 418] around a raw
[50, 54, 474, 145] fill, the fitted 0.82 explicit-area sheet clips one scaled
point inside its origin, and its unscaled continuation page clips at x=473 for
a raw fill at 472, so the clip is per page.

The fill pass now trims a tracked fill whose track starts on the table's grid
origin, taken from the table's start tag in the frame that holds the fills
because Typst inlines single-item frames. Fill tests that anchored on the
first cell's fill now subtract the clip, and a new test pins the rule at both
print scales on one and two sheet pages. Evidence for page 1 of the fixture
is under assets/bugfixes/issue-1605/.

Fixes #1605
Related: #1742, #1659

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
@developer0hye
developer0hye merged commit d79360d into main Sep 15, 2026
18 checks passed
@developer0hye
developer0hye deleted the fix/issue-1605-fill-clip branch September 15, 2026 09:29
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.

XLSX: worksheet fills extend beyond the native used-range clip

1 participant