Skip to content

feat(xlsx): print worksheet gridlines when printOptions asks for them - #721

Merged
developer0hye merged 1 commit into
mainfrom
fix/xlsx-print-gridlines
Jul 30, 2026
Merged

feat(xlsx): print worksheet gridlines when printOptions asks for them#721
developer0hye merged 1 commit into
mainfrom
fix/xlsx-print-gridlines

Conversation

@developer0hye

Copy link
Copy Markdown
Owner

Summary

<printOptions gridLines="1"/> prints Excel's worksheet gridlines across the printed range; the attribute was never read, so grid-structured sheets came out with no rules at all.

Measured off the native Excel GT of NumberFormatTests (traces cited in the code): printed gridlines are 1pt pure-black boundary-anchored [B, B+1] bands — the #619 thin-border convention, not the commonly assumed gray — covering exactly the printed range, closed on all four sides at every page break. An explicit border of any style owns its boundary over the gridline (a hair border replaces the black rule with its dotted band), and a cell fill suppresses all four adjacent segments (Excel truncates verticals at a filled row and omits the horizontal at its bottom edge).

Implementation seeds gridline declarations into the #619 boundary-resolution pass (lowest precedence, both-neighbour seeding so grids close at page breaks), riding the existing band emission including repeating headers and width splits. The flag is read by a raw-XML scanner that skips customSheetViews subtrees (a custom view's nested printOptions must not shadow the sheet-level element) and honours the ECMA-376 gridLines AND gridLinesSet conjunction; umya models neither attribute.

Known limitation (TODO in code): a background-filled row that lands as the first row of a page leaves the previous page's grid open at that one boundary — kept because the alternative paints a black rule over the fill's top edge on every within-page filled row, the far more common case.

Related issue

Fixes #622

Testing

  • cargo test --workspace — green (red-first tests: measured band geometry on all four sides, both-neighbour interior seeding, explicit-border precedence incl. hair, fill suppression, repeating-header carry, flag-off negatives; scanner tests for customSheetViews shadowing both directions, gridLinesSet veto, "true" spelling)
  • cargo +1.97 clippy --workspace --all-targets — clean; cargo fmt --check — clean
  • GT parity on NumberFormatTests: every rule 1.0pt #000000 at boundary bands matching GT's fills; fill suppression mirrored; pages closed top and bottom; Flags-sheet variable-height rows match GT offsets
  • Perf: conversion 474ms → 547ms (+15%) with ~5,400 added bands; parse time unchanged

Visual impact

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

Visual audit

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
  • Inspected matched region crops at full resolution
  • Ran the 5% fuzz pixel-difference sweep
  • Inventoried hairlines and border dash styles
  • Inventoried font weight, italic, and underline emphasis

Deviation audit

Check Result
Page count/order Remaining: #658 — 12pt rows vs GT 13pt pull ~7 extra rows onto page 1; Remaining: #717 — A4 page vs GT's schema-default Letter
Element presence Remaining: #623 — row/column heading gutters and the outer heading frame are absent; Remaining: #718 — the formatted empty column E (gridlines and fill segment included) is absent
Position/size Remaining: #623 — the cell area sits at the page origin instead of after GT's heading gutter; column boundaries align exactly once shifted, and column widths match GT within 1px
Rotation/flip No deviation observed
Fill Matches GT — header fill #FFCC99 identical over A1:D1; the missing E1 segment is tracked under #718
Stroke/border Fixed — printed gridlines now appear as 1pt pure-black rules (2px at 150 DPI, same bands as GT) across the whole range, with GT's fill-edge suppression reproduced; before: zero rules on the page
Text content Remaining: #719 — rows 44–46 drop the xml:space="preserve" leading spaces; all other text matches
Font family/weight/style Matches GT — italic sans header + regular monospace body
Text color Matches GT — black throughout
Alignment Remaining: #657 — left-aligned text at 2.6pt from the rule vs GT 3.6pt (the 1pt inset deficit); right alignment matches
Line/paragraph spacing Remaining: #658 — uniform 12pt row pitch vs GT 13pt
Clipping/overflow No deviation observed

Checklist

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

🤖 Generated with Claude Code

<printOptions gridLines="1"/> tells Excel to rule every cell boundary
of the printed range, independently of border styling. The attribute
was never read, so sheets that rely on printed gridlines for their
structure came out with no rules at all.

Measured off the native Excel GT of NumberFormatTests (both sheets set
the flag; traces cited in the code): gridlines are 1pt pure-black
boundary-anchored [B, B+1] bands — the same convention as #619 thin
borders, and not the gray usually assumed — covering exactly the
printed range and closed on all four sides at every page break. An
explicit border of any style owns its boundary over the gridline (a
hair border replaces the black rule with its dotted band), and a cell
fill suppresses all four adjacent segments: Excel truncates the
verticals at a filled row and omits the horizontal at its bottom edge,
because fills paint last and bleed over the bands.

Implementation seeds gridline declarations into the #619
boundary-resolution pass on every boundary that carries no explicit
border from either neighbour and is not adjacent to a fill, so the
bands ride the existing emission (offsets, run extension, repeating
headers, width splits). The flag is read by a raw-XML scanner that
skips customSheetViews subtrees (a custom view's nested printOptions
must not shadow the sheet-level element) and honours the ECMA-376
gridLines AND gridLinesSet conjunction. umya models neither attribute.

Verified against GT: every rule 1.0pt black at the boundary bands, fill
suppression mirrored, pages closed top and bottom; conversion time on
the 337-row fixture rises 474ms -> 547ms with the ~5,400 extra bands.

Related: #622

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
@developer0hye
developer0hye merged commit 493b209 into main Jul 30, 2026
17 checks passed
@developer0hye
developer0hye deleted the fix/xlsx-print-gridlines branch July 30, 2026 11:12
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: printOptions gridLines="1" is ignored, so printed cell gridlines are missing entirely

1 participant