fix(mtext): support arabic cursive shaping and bidirectional rtl text layout

- Enable fallback font metrics for HarfBuzz shaping in TTF glyph resolver

- Implement Unicode Bidirectional Algorithm (UBA) paragraph base direction detection

- Anchor right-to-left paragraphs to right container boundaries by default

- Reorder line atoms with base direction level to preserve neutral punctuation attachment

- Right-align RTL single-line TEXT and handle editor caret placement on RTL glyphs
This commit is contained in:
Karim Jerbi committed 2026-09-12 11:06:11 +01:00
1 parent ef189d7781
commit f1d5284fb9
8 files changed
+577 -73

No files matched your search

+3 -3
View File
@@ -640,16 +640,16 @@ fn vertical_caret_target(
let anchor = boxes
.iter()
.find(|item| item.vis == caret)
.map(|item| (item.xmin, item.ymin, item.ymax))
.map(|item| ((if item.is_rtl { item.xmax } else { item.xmin }), item.ymin, item.ymax))
.or_else(|| {
caret.checked_sub(1).and_then(|previous| {
boxes
.iter()
.find(|item| item.vis == previous)
.map(|item| (item.xmax, item.ymin, item.ymax))
.map(|item| ((if item.is_rtl { item.xmin } else { item.xmax }), item.ymin, item.ymax))
})
})
.or_else(|| boxes.first().map(|item| (item.xmin, item.ymin, item.ymax)));
.or_else(|| boxes.first().map(|item| ((if item.is_rtl { item.xmax } else { item.xmin }), item.ymin, item.ymax)));
let Some((anchor_x, anchor_y, anchor_top)) = anchor else {
return caret.min(visible_count);
};
+22 -7
View File
@@ -141,10 +141,18 @@ impl MTextPreview {
let d = dy * 1000.0 + dx; // prefer the correct line first
if d < best_d {
best_d = d;
best = b.vis;
// After the glyph centre → caret sits after this char.
if wx > (b.xmin + b.xmax) * 0.5 {
best = b.vis + 1;
if b.is_rtl {
if wx < (b.xmin + b.xmax) * 0.5 {
best = b.vis + 1;
} else {
best = b.vis;
}
} else {
best = b.vis;
// After the glyph centre → caret sits after this char.
if wx > (b.xmin + b.xmax) * 0.5 {
best = b.vis + 1;
}
}
}
}
@@ -280,14 +288,21 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
);
} else if collapsed {
let bar = if let Some(b) = self.boxes.iter().find(|b| b.vis == self.caret) {
Some((b.xmin, b.ymin, b.ymax)) // left edge of the caret's glyph
let cx = if b.is_rtl { b.xmax } else { b.xmin };
Some((cx, b.ymin, b.ymax))
} else if self.caret > 0 {
self.boxes
.iter()
.find(|b| b.vis == self.caret - 1)
.map(|b| (b.xmax, b.ymin, b.ymax)) // after the last glyph
.map(|b| {
let cx = if b.is_rtl { b.xmin } else { b.xmax };
(cx, b.ymin, b.ymax)
})
} else {
self.boxes.first().map(|b| (b.xmin, b.ymin, b.ymax))
self.boxes.first().map(|b| {
let cx = if b.is_rtl { b.xmax } else { b.xmin };
(cx, b.ymin, b.ymax)
})
};
if let Some((cx, y0, y1)) = bar {
let p0 = map(cx, y0);