Avoid slicing text inside multi-byte characters

Right-to-left line reordering walked each atom's byte range and sliced the
line at every byte, panicking when a neutral multi-byte character such as a
guillemet preceded the first strong character. Walk characters instead.

FIND/FINDALL located the REPLACE keyword in a Unicode-uppercased copy and
sliced the original with that offset; uppercasing can change byte length
(dotless i, ligatures). Uppercase ASCII only, which keeps offsets aligned.
FILLET and CHAMFER inline R/D shorthands now test the original text before
slicing past the first character.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Christopher OverbeckandClaude Opus 5 committed 2026-09-17 10:33:18 -05:00
1 parent ef0c6bf403
commit 38aa2ee34d
3 files changed
+32 -6

No files matched your search

+10 -2
View File
@@ -798,9 +798,11 @@ pub fn reorder_line_atoms(atoms: Vec<LayoutAtom>, is_rtl: bool) -> Vec<LayoutAto
.iter()
.map(|r| {
let mut lvl = None;
for i in r.clone() {
// `bidi.levels` is indexed by byte, so walk characters rather than
// bytes to avoid slicing inside a multi-byte character.
for (offset, c) in line_text[r.clone()].char_indices() {
let i = r.start + offset;
if i < bidi.levels.len() {
let c = line_text[i..].chars().next().unwrap_or(' ');
match unicode_bidi::bidi_class(c) {
unicode_bidi::BidiClass::L
| unicode_bidi::BidiClass::R
@@ -2966,6 +2968,12 @@ mod v_anchor_tests {
reordered_b,
vec!["2026 ", "is ", "here!", "שנה ", "שלום ", "مرحبا! ", "ہے۔ ", "اردو ", "یہ "]
);
// Case 6: a word starting with multi-byte neutral punctuation must
// not be sliced inside that character.
let words6 = ["שלום", " ", "«x"];
let reordered6 = reorder_atoms(&words6, false);
assert_eq!(reordered6, vec!["שלום", " ", "«x"]);
}
#[test]