review(#1629): one direction mark, no dead RTL paragraph path, tests follow the new layout

- One visible-named LRM constant (ttf_glyph) instead of two invisible
  literals. Stroke and SDF text skip it, so a right-to-left script the
  shaper does not handle no longer gets a 6-unit gap per mark; vertical
  MTEXT does not insert it at all.
- Shaped cells keep a cluster the shaper merged into the leading mark.
- The always-false RTL paragraph flag, its indent/anchor branches and the
  unused reorder_line_atoms are gone; its test now covers reorder_rtl_runs.
- Underline, overline and strike follow each drawn run of a mixed word.
- Justify/distribute count only the gaps the measured width holds.
- The two RTL alignment tests assert the new left-to-right behaviour;
  glyph_boxes keeps its own doc comment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-06 20:49:01 +03:00
1 parent 36871044a6
commit 864c644222
5 files changed
+99 -242

No files matched your search

+4
View File
@@ -338,6 +338,10 @@ pub(crate) fn tokenize_run(text: &str) -> Vec<Tok> {
let mut toks = Vec::new();
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
// A direction mark only steers shaping; it draws nothing.
if ch == crate::scene::text::ttf_glyph::LRM {
continue;
}
if ch == '\\' {
match chars.peek().copied() {
Some('L') => {
+14 -10
View File
@@ -603,8 +603,9 @@ pub fn char_cells(run: &ShapedRun, text: &str) -> Vec<(f32, f32)> {
.collect()
}
/// Left-to-right mark shaped ahead of every run.
const BASE_MARK: char = '‎';
/// Left-to-right mark, shaped ahead of every run so its base direction is
/// left to right; layout also uses it to close a right-to-left run.
pub(crate) const LRM: char = '\u{200E}';
type ShapeCache = HashMap<(String, String), Option<Arc<ShapedRun>>>;
@@ -907,7 +908,7 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
// A leading left-to-right mark keeps the paragraph direction left to
// right, as the reference lays text out (right-to-left runs still read
// backwards); `cells` stay relative to `text`.
let marked = format!("{BASE_MARK}{text}");
let marked = format!("{LRM}{text}");
buffer.set_text(&mut font_system, &marked, &attrs, Shaping::Advanced, None);
buffer.shape_until_scroll(&mut font_system, false);
@@ -917,9 +918,11 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
for run in buffer.layout_runs() {
advance = advance.max(run.line_w * px_to_9);
for glyph in run.glyphs.iter() {
if glyph.start >= BASE_MARK.len_utf8() {
let m = BASE_MARK.len_utf8();
cells.push((glyph.start - m, glyph.end - m, glyph.x * px_to_9, (glyph.x + glyph.w) * px_to_9));
// A cluster the shaper merged into the mark keeps its own part.
let m = LRM.len_utf8();
if glyph.end > m {
let start = glyph.start.saturating_sub(m);
cells.push((start, glyph.end - m, glyph.x * px_to_9, (glyph.x + glyph.w) * px_to_9));
}
let face_index = font_system
.db_mut()
@@ -991,7 +994,7 @@ fn build_shaped(family: &str, text: &str) -> Option<ShapedRun> {
// A leading left-to-right mark keeps the paragraph direction left to
// right, as the reference lays text out (right-to-left runs still read
// backwards); `cells` stay relative to `text`.
let marked = format!("{BASE_MARK}{text}");
let marked = format!("{LRM}{text}");
buf.set_text(&mut fs, &marked, &attrs, Shaping::Advanced, None);
buf.shape_until_scroll(&mut fs, false);
@@ -1031,9 +1034,10 @@ fn build_shaped(family: &str, text: &str) -> Option<ShapedRun> {
for run in buf.layout_runs() {
advance = advance.max(run.line_w * px_to_9);
for g in run.glyphs.iter() {
if g.start >= BASE_MARK.len_utf8() {
let m = BASE_MARK.len_utf8();
cells.push((g.start - m, g.end - m, g.x * px_to_9, (g.x + g.w) * px_to_9));
// A cluster the shaper merged into the mark keeps its own part.
let m = LRM.len_utf8();
if g.end > m {
cells.push((g.start.saturating_sub(m), g.end - m, g.x * px_to_9, (g.x + g.w) * px_to_9));
}
let face_index = fs.db_mut().face(g.font_id).map(|f| f.index).unwrap_or(0);
let Some(font) = fs.get_font(g.font_id, g.font_weight) else {