fix(docx): render a paragraph's left and right indent#470
Merged
Conversation
`w:pPr/w:ind` reached the IR but only the list path ever read it, so every indented body paragraph started at the page margin. In the technical-manual golden mock the shaded command lines carry a 240-twip indent and rendered 11.95pt left of Word, while every unindented line on the same page matched within 0.05pt. Wrap an indented paragraph in a block whose inset is the indent. The fill and border stay on the inner block, because Word paints `w:shd` and `w:pBdr` from the left indent to the right indent rather than across the whole text column: measured on the Word export, the shaded band of the 12pt indented code block starts at 81.6pt against a 70.8pt margin. The `w:spacing` gaps move to the outer block so an indent cannot separate them from the neighbouring paragraphs they collapse against. Every command line now matches Word's x position within 0.05pt and the shaded band starts at the indent. Vertical drift over the business corpus is unchanged at 13.52pt mean absolute. `needs_block_wrapper` deliberately still ignores the indent: the fixed-text paths share that predicate but emit no inset, so counting it there would open a bare wrapper that only leaks Typst's default block spacing. Fixes #464 Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
An indented paragraph with no runs takes `generate_paragraph`'s early return, which closed the style block but not the indent wrapper opened before it. The generated Typst then carried an unclosed delimiter and the whole document failed to compile: 73 third-party fixtures went from success to error, dropping the bulk success rate from 99.07% to 96.52%. Close the wrapper on that path too, and assert bracket balance for the case in a unit test. Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
developer0hye
force-pushed
the
fix/docx-paragraph-indent
branch
from
July 26, 2026 01:38
dd2bfed to
e7a1aa4
Compare
Both evidence renders now come from current main, which carries the #463 list-spacing fix, so the before/after pair isolates the indent change alone. The 5% fuzz difference against the Word export goes 57,544 to 54,480, and the deviation audit no longer defers the list boundary to #463, which has since merged. 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
w:indleft and right indent as a block insetw:spacingon the outermost block so an indent cannot break gap collapsingRelated issue
Fixes #464
Details
w:pPr/w:indreached the IR but only the list path ever read it, so every indented body paragraph started at the page margin. In the technical-manual golden mock the shaded command lines carry a 240-twip (12pt) indent and rendered 11.95pt left of Word, while every unindented line on the same page matched within 0.05pt.Line left edge (
xMin) on page 1:$ cargo install office2pdf-cli$ office2pdf --versionoffice2pdf 0.6.0$ office2pdf report.docx$ office2pdf *.xlsx --outdir ./pdfs/Word paints
w:shdfrom the indent, not from the margin. Sampling the shaded band of the first code block at 150 DPI: Word starts it at 81.6pt against a 70.8pt margin, office2pdf started it at 71.0pt and now starts it at 83.0pt. The fill therefore belongs to a block nested inside the inset.Vertical drift over the business corpus is unchanged at 13.52pt mean absolute, so the indent costs nothing elsewhere.
Testing
cargo fmt --all -- --checkcargo test --workspacecargo clippy --workspace --all-targets --all-featurespython3 scripts/validate_business_golden_mocks.pypdftotext -bboxx-position comparison against all 10 Word-produced DOCX golden PDFsmagick compare -metric AE -fuzz 5%Visual impact
Visual audit
tests/golden_mocks/business/sources/docx/05_technical_manual_en.docxpdftoppm, 150 DPIfixassets/bugfixes/issue-464/gt.jpgassets/bugfixes/issue-464/before.jpgassets/bugfixes/issue-464/after.jpgVisual comparison
Required inspection
The 5% fuzz sweep against the size-normalized GT falls from 57,544 to 54,480 differing pixels, both measured against current main so the #463 list-spacing fix is in each. The residual clusters are font antialiasing. The page's only hairline is the header rule, which matches GT in position, width, and weight on the full-resolution crop; there are no dashed or dotted borders. Bold headings, the monospace runs, and the italic disclaimer all keep the same emphasis as GT.
Deviation audit
F4F4F4shading now starts at the indent, 83.0pt against Word's 81.6ptChecklist
Signed-off-byline