Skip to content

Document grid: lines per page and characters per line (#391) - #401

Merged
echelon merged 2 commits into
storytold:mainfrom
vangel76:feat/391-document-grid
Oct 11, 2026
Merged

echelon merged 2 commits into
storytold:mainfrom
vangel76:feat/391-document-grid

Conversation

@vangel76

Copy link
Copy Markdown
Contributor

What

Layout › Page Setup › Document Grid (w:docGrid, ECMA-376 §17.6.5).

  • Model + DOCX: SectionProps::doc_grid (type default|lines|linesAndChars|snapToChars, line pitch, w:charSpace) and paragraph w:snapToGrid, read and written (w:docGrid written after w:bidi in sectPr; Word's no-grid <w:docGrid w:linePitch="360"/> survives a round trip).
  • Layout:
    • Line grid: in paragraphs that snap (the default; w:snapToGrid w:val="0" turns it off), each line's height rounds up to whole line pitches with the text centred in them. Multiple spacing is a multiple of the snapped height; Exactly isn't snapped. Headers and footers don't snap.
    • Character grid: linesAndChars gives East Asian characters (CJK ideographs, kana, Hangul, CJK punctuation, full-width forms) whole cells of the character pitch, glyph centred. The pitch is the Normal font size plus charSpace/4096 pt. A larger character takes round(size / default size) cells. Latin text keeps its own widths. With snapToChars, all text snaps to whole cells.
    • Hostile values (pitch 0, negative, NaN, huge; extreme charSpace) are clamped to 1–1584 pt.
    • The grid is part of the paragraph-layout cache key.
  • Command layout.documentGrid {"type"?, "linesPerPage"?, "charsPerLine"?, "linePitch"?, "charSpace"?}: section scope like the other page-setup commands, one undo step, returns lines per page and characters per line. New catalog row: Layout › Page Setup › Document Grid (403/435).
  • UI: the Page Setup dialog has a Document Grid section (no grid / lines only / lines and characters / text snaps to characters, characters per line, lines per page). When the section has a grid, View › Gridlines draws it instead of the drawing grid. New strings are in all 9 catalogs (including et, sr and sr-latn).
  • The Asian Typography paragraph options (kinsoku, word wrap…) and kinsoku in layout were already on main. This PR doesn't change them.
  • Docs: layout-parity, file-format-parity, target-app-parity (East Asian 15% → 25%), gaps, ROADMAP progress log, parity checklist.

How tested

  • crates/layout/src/tests_grid.rs:
    • Every line height is a whole multiple of the pitch, and the first page holds floor(text height / line height) lines.
    • snapToGrid off gives the natural heights.
    • CJK clusters sit at k × pitch, and Latin advances don't change. No assertion depends on font metrics, and the test works when CJK glyphs are missing.
  • crates/docx/tests/roundtrip.rs: section grid (linesAndChars, 17.5 pt, negative charSpace), a default grid, and snapToGrid round-trip.
  • crates/engine/src/tests.rs: layout.documentGrid sets 40 lines / 30 chars, is one undo step, clamps hostile values, rejects an unknown type, and undoes back to no grid.
  • crates/doc unit test: pitch clamping and lines/chars per page.
  • Ran: cargo test on doc, docx, layout, engine and ui-egui (lib); clippy -D warnings on those crates; cargo fmt; cargo xtask layers, wasm and parity.
  • Checked headlessly with ui_shot: Japanese text on a 30×30 grid, with the gridlines and the dialog shown.

Not done

  • Run-level w:snapToGrid isn't read.
  • Paragraph spacing isn't counted in grid lines.
  • Word's exact vertical placement of glyphs in a grid cell isn't matched. Text is centred, which is close.
  • Word's slight right-margin adjustment so that exactly N characters fit isn't done.
  • Grid in headers/footers, footnotes and text boxes: body only (footnotes laid out with the body may snap).
  • The grid isn't written to RTF or ODT.

Fixes #391

🤖 Generated with Claude Code

- Model: `SectionProps::doc_grid` (`w:docGrid` type, line pitch, charSpace) and the
  paragraph `w:snapToGrid` flag; DOCX read and write (ECMA-376 §17.6.5).
- Layout: with a line grid, lines of snapping paragraphs take whole line pitches with
  their text centred; with a character grid, East Asian characters take whole cells of
  the character pitch (all text with snapToChars), Latin text stays proportional.
  Hostile pitches and charSpace are clamped. Headers and footers don't snap.
- Command `layout.documentGrid` (type, linesPerPage, charsPerLine, linePitch,
  charSpace), one undo step, section scope; catalog row under Layout › Page Setup.
- Page Setup dialog: Document Grid section; View › Gridlines draws the document
  grid of a section that has one. UI strings in every catalog (incl. et, sr, sr-latn).
- Tests: DOCX round trip, line pitch and lines per page, character pitch placement,
  command with clamping and undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vangel76
vangel76 force-pushed the feat/391-document-grid branch from f35cffe to ca1ed4b Compare October 11, 2026 18:57
# Conflicts:
#	ROADMAP.md
#	crates/doc/src/section.rs
#	crates/docx/src/read/props.rs
#	crates/layout/src/lib.rs
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/et.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr-latn.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	docs/file-format-parity.md
#	docs/gaps.md
@echelon
echelon merged commit 6526b13 into storytold:main Oct 11, 2026
@echelon

echelon commented Oct 11, 2026

Copy link
Copy Markdown
Member

Thanks for the contribution, @vangel76! This is now merged into main. If you haven't already, please come join us on Discord: https://discord.gg/artcraft

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.

Parity: Layout › Page Setup › Document Grid (w:docGrid) and East Asian line-break options

2 participants