mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 05:46:46 -06:00
M4: Thai faces in the text fallback list
Thai typed into a layer whose font has no Thai glyphs (Inter, which new text starts in) was drawn as missing-glyph boxes, on the desktop and on the web, even with a Thai face embedded or installed: FALLBACK_FAMILIES listed Latin, CJK, Hangul, Arabic and symbol families but no Thai one. Noto Sans Thai, Noto Sans Thai UI, Leelawadee UI, Thonburi and Tahoma go after Noto Sans Arabic; they are only consulted for characters the layer's own font lacks. Tests: the list has a Thai face for each platform, and with one installed or embedded every Thai cluster in a Latin layer, marks included, is drawn from it (skips without one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
80f916326f
commit
1340e32b16
@@ -265,6 +265,35 @@ fn hangul_comes_from_a_korean_font_when_one_is_installed() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thai_comes_from_a_thai_font_when_one_is_installed() {
|
||||
// The default (Latin) face has no Thai. With a Thai face (Noto Sans Thai from craft-fonts or
|
||||
// Linux, Leelawadee UI or Tahoma on Windows, Thonburi on macOS) the fallback draws every Thai
|
||||
// cluster, marks included, instead of the missing-glyph box. Skips on a machine without one.
|
||||
let Some(f) = face() else { return };
|
||||
if f.has_char('\u{0E01}') {
|
||||
return;
|
||||
}
|
||||
let thai = ["Noto Sans Thai", "Noto Sans Thai UI", "Leelawadee UI", "Thonburi", "Tahoma"];
|
||||
if !thai.iter().any(|t| find_face(t, "Regular").is_some_and(|t| t.has_char('\u{0E01}'))) {
|
||||
return;
|
||||
}
|
||||
// "ภาษาไทย" (the Thai language) and "น้ำ" (water: a tone mark over SARA AM).
|
||||
let s = "Ab \u{0E20}\u{0E32}\u{0E29}\u{0E32}\u{0E44}\u{0E17}\u{0E22} \u{0E19}\u{0E49}\u{0E33}";
|
||||
let chars: Vec<char> = s.chars().collect();
|
||||
let l = layout(&text(s, TextAutoResize::WidthAndHeight), None, None);
|
||||
let is_thai = |g: &&Glyph| chars.get(g.char_start).is_some_and(|c| ('\u{0E00}'..='\u{0E7F}').contains(c));
|
||||
let glyphs: Vec<_> = l.glyphs.iter().filter(is_thai).collect();
|
||||
assert!(!glyphs.is_empty());
|
||||
for g in &glyphs {
|
||||
assert!(g.gid != 0, "char {}: missing glyph in {} {}", g.char_start, g.face.family, g.face.style);
|
||||
assert!(g.face.has_char('\u{0E01}'), "char {}: drawn by {}, which has no Thai", g.char_start, g.face.family);
|
||||
}
|
||||
let drawn: std::collections::BTreeSet<usize> = glyphs.iter().flat_map(|g| g.char_start..g.char_end).collect();
|
||||
let want: std::collections::BTreeSet<usize> = (3..chars.len()).filter(|i| chars[*i] != ' ').collect();
|
||||
assert_eq!(drawn, want, "every Thai char is in a drawn cluster");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_opportunities() {
|
||||
// (before, after, may break between, derivation)
|
||||
|
||||
@@ -406,7 +406,7 @@ fn names(f: &skrifa::FontRef) -> (Option<String>, Option<String>) {
|
||||
}
|
||||
|
||||
/// Families tried, in order, for characters a text's own font lacks.
|
||||
const FALLBACK_FAMILIES: [&str; 20] = [
|
||||
const FALLBACK_FAMILIES: [&str; 25] = [
|
||||
"Inter",
|
||||
"Helvetica Neue",
|
||||
"Arial",
|
||||
@@ -425,6 +425,11 @@ const FALLBACK_FAMILIES: [&str; 20] = [
|
||||
"Noto Sans CJK KR",
|
||||
"Noto Sans KR",
|
||||
"Noto Sans Arabic",
|
||||
"Noto Sans Thai",
|
||||
"Noto Sans Thai UI",
|
||||
"Leelawadee UI",
|
||||
"Thonburi",
|
||||
"Tahoma",
|
||||
"Apple Symbols",
|
||||
"Segoe UI Symbol",
|
||||
];
|
||||
|
||||
@@ -263,3 +263,15 @@ fn font_bytes_load_into_the_db() {
|
||||
assert_eq!(db().read().unwrap().face_count(), faces);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_platform_has_a_thai_font_in_the_fallbacks() {
|
||||
// Thai typed into a Latin layer (new text starts in Inter) is drawn by the first fallback that
|
||||
// has it: Noto Sans Thai (craft-fonts, Linux), Leelawadee UI or Tahoma (Windows), Thonburi
|
||||
// (macOS). They come after the Latin faces, which go on drawing Latin text.
|
||||
let at = |family: &str| FALLBACK_FAMILIES.iter().position(|f| *f == family);
|
||||
let latin = at("Segoe UI").unwrap();
|
||||
for thai in ["Noto Sans Thai", "Leelawadee UI", "Tahoma", "Thonburi"] {
|
||||
assert!(at(thai).is_some_and(|i| i > latin), "{thai}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user