fix: tab-stop column measurement + right/center tab alignment - #16
fix: tab-stop column measurement + right/center tab alignment#16jmcopeland wants to merge 3 commits into
Conversation
Plain tab-stop columns positioned the run after a tab using a per-character heuristic for the running line width, while the run itself lays out at real canvas width. On long lower-case runs the heuristic over-estimates, so wrapped/broken second lines drifted left of the stop (e.g. a signature block's second party line landing ~58px short). Track the running width with the same canvas metrics the text renders at for paragraphs that carry explicit tab stops, in both render paths, so tabbed columns land on their stop across every line. Refs extend-hq#15 Co-Authored-By: Justin Copeland <justin.copeland@legalontech.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@jmcopeland is attempting to deploy a commit to the Extend Team on Vercel. A member of the Team first needs to authorize it. |
The plain-tab path advanced to every stop and left-aligned the following text, so a segment after a right (or center) tab stop was never actually right-aligned. Size the tab spacer so the following text segment ends at (right) or straddles (center) the stop, measured with the same canvas metrics the text renders at, when it fits before the stop; otherwise fall back to the plain left-advance (matching Word, which degrades an overrun tab to left). Applied to both the read-only and editable render paths. This unifies the signature-block cases: a short right-column trailer right-aligns at its stop, while a long wrapping run still flows and returns to the margin. Regression test in tests/unit/right-tab-alignment.test.ts. Refs extend-hq#15 Co-Authored-By: Justin Copeland <justin.copeland@legalontech.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Updated: this PR now also makes the plain-tab path alignment-aware. In addition to the canvas-accurate column measurement, a segment after a right (or center) tab stop is now right-aligned / centered at the stop when it fits before it (falling back to the plain left-advance otherwise, matching Word's overrun degrade). This unifies the signature-block cases — a short right-column trailer right-aligns at its stop, while a long wrapping run still flows and returns to the margin. New regression test |
Two correctness gaps and a structural cleanup for plain tab columns: - Wrap-aware running width: the tab tracker only reset at explicit line breaks, so a right/center tab after a run that WRAPS to the margin (e.g. a signature block whose right column is one continuous run) resolved past the stop and the trailer landed mid-line. Simulate the browser's greedy word-wrap (canvas metrics, content-box width) so the tab resolves on the correct visual line. Matches Word. - Tracked-changes path: the read-only viewer delegates to renderParagraphRuns when tracked changes / comments / special tab layouts are shown (the redline browse view's path), which had its own tab tracker that bypassed the above. Threaded the content-box width through ParagraphRunRenderOptions and made it wrap-aware too. - Refactor: the tab-stop parsing, wrap-aware advance, and aligned-spacer resolution were duplicated per render path (how these bugs slipped through one path at a time). Extracted buildTabStopsWithAlignPx / advanceTabLineWidthPx / resolveAlignedTabSpacerPx as the single source of truth; both paths now call them. Refs extend-hq#15 Co-Authored-By: Justin Copeland <justin.copeland@legalontech.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
hi! is this ready or still in draft? |
It's ready |
Summary
Fixes plain tab-stop columns drifting off their stop on wrapped/broken second lines (the second party line of a signature block landing ~58px short of the first).
Root cause: a tab renders as a fixed-width spacer sized
tabStop − runningLineWidth, while the text before it lays out at real canvas width. The running width was tracked with the per-character heuristicestimateTextAdvanceWidthPx, which over-estimates long lower-case runs. When the estimate diverges from the browser's actual width, the post-tab column shifts left by the difference — small for short labels, large for long lines, so the second line of a two-line column visibly misaligns.Fix: track the running line width with the same canvas metrics the text actually renders at (
updateMeasuredLineWidthPxForText, a canvas-accurate analog of the existing estimator) for paragraphs that carry explicit tab stops, in both render paths. Columns now land on their stop across every line. Paragraphs without explicit tab stops keep the existing heuristic, so wrap/line-count behavior elsewhere is unchanged.Refs #15.
Verification
repros/signature-block-variants.docx): the left-tab-only (C) and two-paragraph (D) styles now align both party lines on the stop (measured col-2 at 671–672px on both lines; previously the second line landed at ~614px).tests/unit/plain-tab-line-break-columns.test.tsstubs canvas metrics and assertspreTabWidth + spacerWidth == tabStopon both lines; confirmed to fail against the old heuristic.Not in scope
The signature underline row (
____⇥____) whose second column is wide enough to overrun the right margin still wraps to a new line (browserbreak-word) where Word lets the unbreakable run overflow the margin. The tab now positions that column correctly; the wrap is a separate overflow/line-break-model gap tracked in #15.Test plan
pnpm --filter @extend-ai/react-docx-playground dev, importrepros/signature-block-variants.docx, confirm variant C/D party columns align on both lines.pnpm vitest run tests/unit/plain-tab-line-break-columns.test.ts