Merge pull request #1186 from storytold/fix/thai-caret-clusters
Type: the caret steps over whole clusters (Thai marks, accents, emoji)
This commit is contained in:
commit
90bd91986a
|
|
@ -4813,6 +4813,7 @@ dependencies = [
|
|||
"unicode-bidi",
|
||||
"unicode-general-category",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
"vectorcraft-doc",
|
||||
"vectorcraft-geom",
|
||||
"windows-registry",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -21,6 +21,9 @@ subsetter = { version = "0.2", default-features = false, features = ["variable-f
|
|||
unicode-bidi = "0.3.18"
|
||||
unicode-general-category = "1"
|
||||
unicode-script = "0.5"
|
||||
# Grapheme clusters (a Thai consonant with its vowel and tone marks, an accented letter, an emoji
|
||||
# sequence) for caret movement; MIT OR Apache-2.0, already in the build.
|
||||
unicode-segmentation = "1.13"
|
||||
# Builds a small variable font for tests (`test_fonts`; no font file is committed). MIT OR
|
||||
# Apache-2.0, already in the build through subsetter.
|
||||
write-fonts = { version = "0.48", optional = true }
|
||||
|
|
|
|||
|
|
@ -190,6 +190,21 @@ pub fn next_char(s: &str, i: usize) -> usize {
|
|||
s[i..].chars().next().map(|c| i + c.len_utf8()).unwrap_or(s.len())
|
||||
}
|
||||
|
||||
/// The start of the grapheme cluster before `i` (Left arrow): a Thai consonant with its vowel and
|
||||
/// tone marks, an accented letter or an emoji sequence is passed over whole.
|
||||
pub fn prev_cluster(s: &str, i: usize) -> usize {
|
||||
use unicode_segmentation::GraphemeCursor;
|
||||
let i = floor_char(s, i);
|
||||
GraphemeCursor::new(i, s.len(), true).prev_boundary(s, 0).ok().flatten().unwrap_or(0)
|
||||
}
|
||||
|
||||
/// The end of the grapheme cluster at `i` (Right arrow, Delete): see [`prev_cluster`].
|
||||
pub fn next_cluster(s: &str, i: usize) -> usize {
|
||||
use unicode_segmentation::GraphemeCursor;
|
||||
let i = floor_char(s, i);
|
||||
GraphemeCursor::new(i, s.len(), true).next_boundary(s, 0).ok().flatten().unwrap_or(s.len())
|
||||
}
|
||||
|
||||
fn is_word_char(c: char) -> bool {
|
||||
c.is_alphanumeric() || c == '_' || c == '\'' || c == '’'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -791,3 +791,17 @@ fn proportional_metrics_set_full_width_glyphs_on_their_proportional_widths() {
|
|||
let moved = x0(&half)[1] - x0(&none)[1];
|
||||
assert!((moved - 10.0).abs() < 0.01, "〙's empty half goes past the line's end: あ moved {moved}");
|
||||
}
|
||||
|
||||
/// The arrows pass a Thai consonant with its vowel and tone marks whole (น้ำ: no, mai tho, sara am),
|
||||
/// as an accented letter written with a combining mark; plain text moves a character at a time.
|
||||
#[test]
|
||||
fn caret_moves_over_whole_clusters() {
|
||||
let s = "กน\u{0E49}\u{0E33}e\u{0301}x";
|
||||
let stops: Vec<usize> = std::iter::successors(Some(0), |&i| (i < s.len()).then(|| edit::next_cluster(s, i))).collect();
|
||||
assert_eq!(stops, [0, 3, 12, 15, 16]);
|
||||
let back: Vec<usize> = std::iter::successors(Some(s.len()), |&i| (i > 0).then(|| edit::prev_cluster(s, i))).collect();
|
||||
assert_eq!(back, [16, 15, 12, 3, 0]);
|
||||
// Inside a cluster (a caret left there by a click), the arrows go to its edges.
|
||||
assert_eq!((edit::prev_cluster(s, 6), edit::next_cluster(s, 6)), (3, 12));
|
||||
assert_eq!((edit::next_cluster("", 0), edit::prev_cluster("ab", 99)), (0, 1));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -530,8 +530,10 @@ impl Tool for TypeTool {
|
|||
self.anchor = if word { edit::prev_word(&text, self.caret) } else { edit::prev_char(&text, self.caret) };
|
||||
self.replace(cx, "", None)
|
||||
}
|
||||
// Backspace takes back one character (a mistyped Thai tone mark, say); Delete, the
|
||||
// Left and Right arrows a whole cluster.
|
||||
ToolKey::Delete if self.caret < len => {
|
||||
self.anchor = if word { edit::next_word(&text, self.caret) } else { edit::next_char(&text, self.caret) };
|
||||
self.anchor = if word { edit::next_word(&text, self.caret) } else { edit::next_cluster(&text, self.caret) };
|
||||
self.replace(cx, "", None)
|
||||
}
|
||||
ToolKey::Left => {
|
||||
|
|
@ -549,7 +551,7 @@ impl Tool for TypeTool {
|
|||
if lay.glyphs.iter().any(|g| g.rtl) {
|
||||
vectorcraft_text::caret_horizontal(&lay, self.caret, false)
|
||||
} else {
|
||||
edit::prev_char(&text, self.caret)
|
||||
edit::prev_cluster(&text, self.caret)
|
||||
}
|
||||
};
|
||||
self.move_to(to, mods.shift)
|
||||
|
|
@ -569,7 +571,7 @@ impl Tool for TypeTool {
|
|||
if lay.glyphs.iter().any(|g| g.rtl) {
|
||||
vectorcraft_text::caret_horizontal(&lay, self.caret, true)
|
||||
} else {
|
||||
edit::next_char(&text, self.caret)
|
||||
edit::next_cluster(&text, self.caret)
|
||||
}
|
||||
};
|
||||
self.move_to(to, mods.shift)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Type parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (the caret steps over whole clusters such as Thai consonants with their marks; earlier: Proportional Metrics in vertical type, `vpal`, #966) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
Type is Illustrator's largest single area by use (weight 13 of 105 across "Type core" and "Type advanced" in
|
||||
|
|
@ -37,6 +38,7 @@ against Illustrator's public documentation; **estimated** unless noted.
|
|||
| Hidden characters (Show Hidden Characters) | done | `type.hiddenCharacters` |
|
||||
| Spell check (Check Spelling, Auto Spell Check, custom dictionary) | missing | menu stubs; needs an openly licensed dictionary |
|
||||
| Smart Punctuation, Change Case, Find & Replace | done | |
|
||||
| Caret: the arrows and Delete step over whole clusters (Thai consonant with its marks, combining accents, emoji); Backspace removes one character | done | |
|
||||
| Touch Type tool | missing | listed in the toolbar, not implemented |
|
||||
| Retype (identify fonts in outlines/images) | missing | Illustrator's uses a cloud model |
|
||||
| Snap to Glyph | missing | menu stub |
|
||||
|
|
@ -52,6 +54,7 @@ against Illustrator's public documentation; **estimated** unless noted.
|
|||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | The Type tool's arrows and Delete move by grapheme cluster, never between a Thai consonant and its vowel or tone marks; score unchanged (estimated) |
|
||||
| 2026-10-11 | minor | Thai (Lao, Khmer, Myanmar) missing from the font in use falls back to an installed font made for the script instead of whichever face covering it loaded first; score unchanged (estimated) |
|
||||
| 2026-10-11 | minor | Proportional Metrics done: `palt` in horizontal type, `vpal` in vertical type (#966, #1082); ruby and manual tate-chu-yoko still missing |
|
||||
| 2026-10-10 | minor | #1034: transformed character controls and regression coverage; preserving group font sizes remains a separate enhancement. Scores/hours unchanged at this rounding. |
|
||||
|
|
|
|||
Loading…
Reference in New Issue