Repository navigation
fix(docx): hang a list item's marker into the margin by its signed indent - #2387
Merged
Merged
Conversation
…dent Word puts a list item's marker at the margin plus its w:ind w:left minus its hanging span, and its lines at the margin plus w:left, even when that lies left of the margin. paragraph_list_indent clamped the left indent and the marker origin at zero, and when the origin clamped onto the indent it returned no geometry at all, so Typst's default enum layout took over. In docxide-pdf case97 (w:left="-100" w:hanging="5") Word draws "1." at 66.775pt and the tabbed body at 80.525pt; main drew them at 72pt and 88pt, and now draws them at 66.75pt and 80.55pt. Word flow lists keep the sign; Typst's list indent accepts a negative length. Table cells keep the clamp until they clip at the cell edge (#2261), and slide lists keep theirs. A corpus scan found that a paragraph style's w:ind still stands in for a direct w:numPr level's indent (#2383). Hanging that wrong indent into the margin would move such markers further from Word, so those paragraphs keep main's geometry until #2383 is fixed. Related: #2233 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> 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.
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.
Summary
Second milestone of #2233: body list items now take a signed indent. Word puts a list item's marker at the margin plus its
w:ind w:leftminus its hanging span, and its lines at the margin plusw:left, even when that lies left of the margin.paragraph_list_indentclamped both the left indent and the marker origin at zero. When the marker origin clamped onto the left indent, it returned no geometry at all, and Typst's default enum layout took over. In docxide-pdfcase97(w:left="-100" w:hanging="5"), Word draws "1." at 66.775pt and the tabbed body at 80.525pt; main drew them at 72pt and 88pt. They now land at 66.75pt and 80.55pt.typst_gen_lists.rs):paragraph_list_indentkeeps the sign when a newListEojeolWrap::hangs_into_marginflag is set. Typst'senum/listindent:takes a negative length, so the marker and body start left of the margin. Only the Word flow body sets the flag. Table cells keep the clamp underTODO(#2261 cell clip), like cell paragraphs since fix(docx): hang a body paragraph with a negative indent into the margin #2382, and slide lists keep theirs.docx_lists.rs,docx.rs): a corpus scan showed that a paragraph style'sw:indstill stands in for a directw:numPrlevel's indent (filed as DOCX: a paragraph style's w:ind overrides a direct w:numPr level's indent, so the marker starts 18pt left of Word #2383). With signed geometry that wrong indent would hang such markers further from Word: docxideslovak_fuel_pricing_reportwould go from 17.95pt to 35.95pt off Word's 88.80pt. Until DOCX: a paragraph style's w:ind overrides a direct w:numPr level's indent, so the marker starts 18pt left of Word #2383 is fixed, a list paragraph whose style chain supplies an indent its level also declares keeps main's geometry (TODO(#2383)). A dedicated test pins the gate and should be deleted along with it.Corpus scan
Base
ca194434and candidate release CLIs were built in separate run-owned targets (cmpdiffer). Both converted the 648 DOCX files that have a numbering part (fixtures-v1 bulk corpus, docxide-pdffe7b8beacases/andscraped/) plus 40 random documents without one. I comparedmutool draw -F stextper-glyph positions. All 688 conversions succeeded on both builds. The controls and the three gated documents (slovak,poi/checkboxes,libreoffice/2129393649) are unchanged. Four documents moved:case97libreoffice/tdf123054(w:ind w:left="0" w:hanging="284")lvlJc="right", filed as #2385poi/bug65649p13 (w:left="0", level hanging 18.75pt)w:jc="center"), which list items ignore before and after; filed as #2386poi/bug59058p54w:left="0", hanging 18pt)w:vanish; under the paragraph-mark rule in #2384 Word would hide this bullet (no Word reference)Follow-ups filed with compare images in this PR: #2383 (style indent beats a direct numbering level), #2384 (marker formatted from the runs instead of the paragraph mark), #2385 (
w:lvlJcignored) and #2386 (list items ignorew:jc).Related issue
Related: #2233. The issue stays open: table-cell paragraphs and lists still clamp until cells clip at their edge (#2261).
Testing
cargo test --locked -p office2pdf --profile ci --lib -- docx_list_marker_and_body_hang docx_list_hanging_into_the_margin: both new tests failed on the unfixed code (marker at 85.05pt instead of 31.05pt; "1." at 72pt instead of 66.775pt) and pass now.docx_style_indent_under_direct_numbering_keeps_the_marker_at_the_marginfails with the gate disabled (marker at 67.05pt) and passes with it.cargo test --locked --workspace --profile ci: all suites pass (office2pdf lib 3863 passed, 0 failed).cargo fmt --all -- --check;cargo clippy --locked --workspace --all-targets -- -D warnings: clean.Visual impact
Visual audit
tests/fixtures/docx/docxide/numbering_negative_indent.docx(docxide-pdfcase97, unmodified; GT is its Microsoft Wordreference.pdf)fixassets/bugfixes/issue-2233/layout-audit.jsonassets/bugfixes/issue-2233/render-clusters-page-1.jsonassets/bugfixes/issue-2233/gt.jpgassets/bugfixes/issue-2233/before.jpgassets/bugfixes/issue-2233/after.jpgVisual comparison
Required inspection
Deviation audit
compare_text_layer.py)Final performance pass
ca194434and this branch, built in separate run-owned targets (cmpdiffer)./usr/bin/time -f "%e %M"(elapsed s, peak RSS); one warm-up pass, then 12 interleaved base/branch runs per input, warm page cache, output to /tmp. Intel i5-10400 (12 threads), Linux 7.0.0-38, dev-box fonts (Liberation/Noto substitutes, no MS core fonts). Medians, base → branch:tdf123054(mover): 0.465 → 0.460 s, 57.3 → 57.1 MiB.bug65649(178 pages, list-heavy, mover): 4.325 → 4.300 s, 1152.3 → 1138.7 MiB.spaced_section_mark.docx(unchanged output): 0.265 → 0.260 s, 42.4 → 42.3 MiB.bug65649run). There is no bottleneck to optimize, so no optimization code changed. The tests and the visual comparison above are the final revalidation.Checklist
Signed-off-byline🤖 Generated with Claude Code