render: the bidi-override marker overwrites the preceding character #257
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#257
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The Trojan-Source bidi marker (REQ-909) is painted on the cell that precedes the bidi codepoint,
so the marker covers a legitimate character instead of occupying a cell of its own. The preceding
character becomes unreadable.
Reproduction
(U+2068 FIRST STRONG ISOLATE / U+2069 POP DIRECTIONAL ISOLATE — the pair
fluent-bundleemits aroundevery placeable, so any Fluent-localized CLI produces them;
fj auth loginis one.)Expected:
for<marker>manyara.tail4108.ts.net<marker>end— every real character still legible.Actual: felis renders
for█manyara.tail4108.ts.net█ end. The space beforemanyaraand the finaltof
netare replaced by the yellow marker block. Verified with a window capture on macOS(
felis-macos-gui-debug).Cause
U+2066–U+2069 and U+202A–U+202E are all zero-width per
unicode-width(confirmed:
'\u{2068}'.width() == Some(0)).Grid::put_graphemetherefore treats them as combiningmarks —
extends_previous_graphemereturnstrueonwidth == 0(
crates/felis-grid/src/editing.rs:447) — and folds them into the previous cell's cluster.The renderer then finds a bidi override inside that cluster
(
cell_contains_bidi_override,crates/felis-render-wgpu/src/instances.rs:1070) and repaints thewhole cell with
BIDI_MARKER_BG/BIDI_MARKER_FG(instances.rs:506). The cell it repaints is theone holding the base character, which is why the base character disappears.
Notes for a fix
The requirement is a visible marker on the cell whose grapheme contains the codepoint
(
docs/explanation/security-model.md"Text rendering"); nothing requires destroying a neighbouringglyph, and losing a character is itself a legibility problem for the reader the requirement is meant
to protect. Two directions:
bidi::is_overridefrom thewidth == 0fold) and give each one its own single-width marker cell. This costs a column percodepoint but keeps every real character on screen.
underline/outline rather than a background+fg swap).
The first matches how the marker reads today (a block) and keeps
cell_contains_bidi_override'scluster walk for the base-char-plus-RLO case that motivated
iter_contains_override.Whichever lands needs a renderer test alongside
bidi_override_cell_paints_with_warning_marker(
instances.rs:2199) that asserts the preceding cell still draws its own glyph.