Skip to content

fix(painter-dom): keep the empty-line caret on the start of RTL lines - #3950

Open
palmoni5 wants to merge 1 commit into
superdoc:mainfrom
palmoni5:fix/rtl-empty-line-caret
Open

fix(painter-dom): keep the empty-line caret on the start of RTL lines#3950
palmoni5 wants to merge 1 commit into
superdoc:mainfrom
palmoni5:fix/rtl-empty-line-caret

Conversation

@palmoni5

@palmoni5 palmoni5 commented Aug 31, 2026

Copy link
Copy Markdown

The bug

Put the caret on an empty paragraph in a right-to-left document and it does not
sit on the start of the line. It sits a space width inside it.

Cause

renderLine paints an empty line with a superdoc-empty-run span that carries
the line's PM positions, and fills it with  :

// Preserve PM positions for DOM caret mapping on empty lines.
if (runsForLine.length === 0) {
  ...
  span.innerHTML = ' ';

A non-breaking space has a real advance width, and the caret for an empty line
is drawn at the placeholder's left edge. On an LTR line that edge is the line
start, so nothing shows. On an RTL line the line starts at the right edge, so
the caret ends up one space width inside the line.

Measured

A Hebrew document, Arial 16px, empty paragraph after a line of text:

x
line box right edge (= line start, RTL) 697.7
.superdoc-empty-run box 693.3 → 697.7
caret 693.3

4.4px off the start, exactly the placeholder's width and exactly its left edge.

Two checks that it really is the placeholder's advance driving the caret, both
done from the page with CSS only, no code change:

  • .superdoc-empty-run { letter-spacing: 20px } → placeholder box widens to
    673.3 → 697.7, caret moves to 673.3. It tracks the box.
  • placeholder given zero advance → caret lands on 697.7, the line start.

The gap is proportional to the font size, so it grows with zoom and with larger
text.

Fix

Fill the placeholder with a zero-width space. It keeps the span's font metrics,
and with them the caret's height (measured: unchanged at 17px), while
contributing no advance, so the caret lands on the line start in both
directions.

Tests

  • New: renderLine empty-line placeholder in render-line.test.ts — asserts
    the zero-width filler and the preserved pmStart/pmEnd, on an LTR and an
    RTL line. Both fail on main and pass with the fix.
  • Updated: the existing empty-line placeholder assertion in index.test.ts.
  • packages/layout-engine/painters/dom suite: 61 files, 1547 tests, all pass.

One note on how I ran them: vp would not start on my machine (Windows, Node
22.20) — it fails resolving #module-sync-enabled out of vite-plus-core
before it reads any config — so I ran the painter suite with the workspace
vitest directly, using the aliases from vite.sourceResolve.ts. For the same
reason I could not run pnpm run format:check or pnpm run lint; the change
follows the surrounding style and stays well inside the line width used in the
file.

Review in cubic

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@palmoni5
palmoni5 force-pushed the fix/rtl-empty-line-caret branch from 9174af6 to 7f7c03f Compare August 31, 2026 23:40
An empty line is painted with a `superdoc-empty-run` span that carries the
line's PM positions. The span was filled with a non-breaking space, which has a
real advance width, and the caret for an empty line is drawn at the span's left
edge. On an LTR line that edge is the line start, so the space is invisible; on
an RTL line the line starts at the right edge, so the caret lands one space
width inside the line instead of on its start.

Measured in a Hebrew document at 16px Arial: the line box ended at x=697.7 and
the caret sat at x=693.3, the placeholder's left edge, 4.4px off the start. The
gap scales with the font size, so it grows with zoom and larger text.

Fill the placeholder with a zero-width space instead. It keeps the span's font
metrics, and with them the caret's height, while contributing no advance, so
the caret lands on the line start in both directions.
@palmoni5
palmoni5 force-pushed the fix/rtl-empty-line-caret branch from 7f7c03f to ee43a9f Compare August 31, 2026 23:41
@palmoni5

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant