Repository navigation
fix(render): keep a paginated footnote's first-paragraph indentation (#1130) - #1152
Merged
Merged
Conversation
…aph (#1130) The note number was a sibling of the note content and the first paragraph was forced display:inline to share its line, so that paragraph ignored its own margins and text-indent: a hanging-indented note wrapped its later lines to the band edge. The number is now the first content of the first paragraph, as Word's reference-mark run is, and no rule overrides a note paragraph's display. Partial notes no longer copy a nested number, and the editor treats note numbers as chrome and keeps them across a block repaint.
…1130) With the first note paragraph an ordinary block, its spacing after now separates notes, as in LibreOffice, but the band's last paragraph also kept it at the bottom margin, lifting every page's notes 6pt where LibreOffice sets the last note line on the margin. Also covers undo/redo of a note edit in the paginated editor.
A paragraph whose style carries an outline level is written as a heading element, not a <p>, so the helper that opens a paginated note's first paragraph with its number skipped it and put the number into the next paragraph instead. Accept every element the converter writes a paragraph as. The .footnote-item comment also still said the number inherits the item's zero line height; it declares its own now.
The number is now a non-editable island inside the editable note paragraph, and the browser deletes such an island as a unit when a caret deletion reaches it: Backspace just after it, Delete just before it, or typing over a selection that spans it. Nothing in the session re-creates the number (it is the renderer's, not run text), and the repaint copied it from the live paragraph, so once gone it stayed gone until a remount. Refuse caret deletions whose target range touches the number, make Backspace at the start of a numbered note paragraph a no-op (the note is its own story, so there is nothing to merge into), and keep a pristine copy of each wired block's number for the repaint to fall back to when a replaced selection took it out. Two Playwright tests drive the real keyboard through both gestures; both failed before this change.
…paragraph (#1130) The converter no longer renders a note number beside the note content, so the line-metrics lookup for that shape was dead and the one hand-written fixture that relied on it (the #1120 exact-baseline guard) exercised a path real output cannot reach. The fixture now uses the in-paragraph shape, the lookup is gone, and the continuation wrapper's comments no longer describe the removed first-paragraph-inline rule.
JSv4
force-pushed
the
fix/issue-1130
branch
2 times, most recently
from
October 16, 2026 06:29
017c86f to
a24e194
Compare
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.
Fixes #1130
Why
In the paginated view, a footnote whose first paragraph has a hanging indent (
w:left="360" w:hanging="360") wrapped every line to the left edge of the note area. The continuation lines should start 18pt in, as they do in Word and LibreOffice. On main every line started at 0pt.The cause was the HTML structure. The converter put the note number beside the note content, then forced the first paragraph to
display: inlineso the number and text would share a line. An inline paragraph ignores its own margins and text-indent, so its indentation was lost.How it works
w:footnoteRef) at the start of the note's first paragraph. The converter now does the same: the number becomes the first child of the first<p>. Paginated endnotes already worked this way and now share one helper,AddNoteNumberToFirstParagraph. Every note paragraph is an ordinary block again, so its own indentation and spacing apply. The number declaresline-height: 0itself, which keeps the Export: footnote text sits 1.2 pt above Word's since #850 #955 fix; it used to inherit that from the note item..footnote-number/.endnote-numberas generated chrome, like list markers and citation markers. They are not editable and never committed as note text. When a note edit re-renders its paragraph, the editor carries the number over to the new paragraph. A single-paragraph render does not include the number.Measurements (NVCA model charter,
TestFiles/NVCA-Model-COI.docx)The coordinator asked whether the extra pages are faithful. LibreOffice 25.8.7.3 values come from
pdftotext -bbox-layouton its PDF. Docxodus values come from Range client rects in the paginated view, in points from the page top. The bottom margin is at 720pt.Page counts:
docs/ooxml_corner_cases.md.Validation
Tests that failed without the fix:
GeneratedCssEscapingTests.FootnoteRulesSurviveSerialization. It replaces the test that asserted the old, deadfirst-of-typerule.line starts 0,0,0,…);Regression guards that also pass on main:
Results with the fix:
DocxSessionIncrementalIndexOracleTests(DocxSessionIncrementalIndexOracleTests snapshot-sharing assertions fail intermittently in CI #1141); it passes on rerun.npm-exportand the visual-parity benchmark were not run locally.Adversarial review
One round, before opening:
p:last-of-typealso zeroes the spacing after of a paragraph followed by a trailing table, or of each cell's last paragraph, in the band's last note.w:position) still split by range;Second review round (after opening)
A second adversarial pass over the opened PR, after rebasing onto
main(the only conflict was the two sides' CHANGELOG entries;WmlToHtmlConverter.csmerged cleanly with the twip-precision and right-to-left border fixes).Fixed:
beforeinputguard refuses caret deletions whose target range touches the number, Backspace at the start of a numbered note paragraph is a no-op (the note is its own story, so there is nothing to merge into), and each wired block keeps a pristine copy of its number that the repaint falls back to when a replaced selection took it out. Two new Playwright tests drive the real keyboard through both gestures; both failed on the previous bundle (the number was gone after the commit).<p>, but a paragraph whose style carries an outline level is written as<h1>–<h6>, so the number landed in the second paragraph. The helper now accepts every element the converter writes a paragraph as. A new .NET test builds such a note and failed before the fix.line-metrics.tsstill looked for a number rendered beside the content, a shape the converter no longer emits; the lookup is gone and the one hand-written fixture that relied on it (the Later footnote paragraphs gain an inset and spacing because paginated output inherits web note CSS #1120 exact-baseline guard) now uses the in-paragraph shape, so it exercises the real path. The continuation wrapper and the.footnote-itemCSS comments described the removed first-paragraph-inline rule.Considered, left as is:
margin-bottom: 0rule is CSS keyed on:last-child, so it also zeroes the paragraph before a trailing table in the last note; moving it into the paginator is a larger change than this fix. Opened as a follow-up if wanted.w:afterwas measured against LibreOffice. FootnoteText rarely carries spacing before.Validation after the round:
TrackedRangeDeletion_ScalesWithTheRange, a scaling-time test in code this PR does not touch; it ran under a machine load of 40 and passes alone.