mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
Text: the Han fallback test accepts an earlier Chinese family (#2039)
The macOS fallback test selected the first installed family from the Simplified list, so a Mac that also has PingFang SC (or Microsoft YaHei / Noto Sans CJK SC) picked that one and then failed an assertion that only accepted the three macOS faces. Accept any family from the list -- every one is Chinese -- and check that each installed macOS face covers the shared ideographs instead. Also exercise fallback_for itself under a zh-CN request, which the direct resolve_in lookup bypassed, and serialise the process-global locale with the layout CJK test so a cached family order cannot leak between them. Tests: effectcraft-text 75 lib + 3 integration passed; effectcraft-render 233 passed, 1 ignored; cargo fmt --all -- --check and cargo clippy -p effectcraft-text --all-targets -- -D warnings clean.
This commit is contained in:
@@ -744,6 +744,12 @@ const THAI_FAMILIES: &[&str] = &["Leelawadee UI", "Tahoma", "Thonburi", "Noto Sa
|
||||
/// The CJK locale the host asked fallbacks to prefer, when it has one (see [`set_cjk_locale`]).
|
||||
static REQUESTED_CJK_LOCALE: RwLock<Option<String>> = RwLock::new(None);
|
||||
|
||||
/// Serialises the tests that change the process-global CJK locale with the tests that read it
|
||||
/// through [`fallback_for`] (the layout tests), which would otherwise cache the other order's
|
||||
/// families.
|
||||
#[cfg(test)]
|
||||
pub(crate) static TEST_CJK_LOCALE: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
/// Ask CJK font fallbacks to prefer `tag`'s language for Han ideographs instead of the locale
|
||||
/// environment, so a Chinese interface is drawn with a Chinese face on a machine whose locale is
|
||||
/// Japanese (and the other way round). The interface sets this from its language setting; an empty
|
||||
@@ -989,6 +995,15 @@ fn system_fallback(c: char, style: &str) -> Option<FaceId> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Clears the requested CJK locale when a test that set it ends, panic or not.
|
||||
struct LocaleReset;
|
||||
|
||||
impl Drop for LocaleReset {
|
||||
fn drop(&mut self) {
|
||||
set_cjk_locale("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_font_dirs_come_from_a_path_list() {
|
||||
assert!(extra_dirs(None).is_empty());
|
||||
@@ -1164,6 +1179,7 @@ mod tests {
|
||||
/// loaded a CJK font first. Skipped where no family from the list is installed.
|
||||
#[test]
|
||||
fn cjk_falls_back_to_a_system_family() {
|
||||
let _locale = TEST_CJK_LOCALE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
scan_system();
|
||||
let inter = resolve("Inter", "Bold").face;
|
||||
let installed = JAPANESE_FAMILIES.iter().any(|f| resolve_in(f, "Regular").is_some());
|
||||
@@ -1284,6 +1300,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_han_order_follows_the_cjk_locale() {
|
||||
let _locale = TEST_CJK_LOCALE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
// Chinese first for any Chinese tag, whether or not it names a script.
|
||||
for tag in ["zh", "zh-CN", "zh_Hans", "ZH-Hant-TW.UTF-8", "zh_MO"] {
|
||||
assert_eq!(han_order_for(tag)[0], FallbackScript::Han, "{tag}");
|
||||
@@ -1301,6 +1318,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_chinese_family_list_follows_the_script_the_locale_asks_for() {
|
||||
let _locale = TEST_CJK_LOCALE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
for tag in ["zh", "zh-CN", "zh_Hans", "zh_CN.UTF-8", "zh-SG"] {
|
||||
assert_eq!(chinese_families_for(tag), SIMPLIFIED_CHINESE_FAMILIES, "{tag}");
|
||||
}
|
||||
@@ -1342,11 +1360,48 @@ mod tests {
|
||||
// exhaustive search would have found (Hiragino Sans), and it covers the shared ideographs.
|
||||
let (family, resolved) =
|
||||
SIMPLIFIED_CHINESE_FAMILIES.iter().find_map(|f| resolve_in(f, "Regular").map(|r| (*f, r))).expect("an installed Simplified Chinese family");
|
||||
assert!(installed.iter().any(|i| i.eq_ignore_ascii_case(family)), "{family} is not a macOS Chinese face");
|
||||
// An earlier name can win the walk when this Mac has it as well (`PingFang SC` on an older
|
||||
// install, or Microsoft YaHei / Noto Sans CJK SC through Office or Homebrew). That is just
|
||||
// as valid a Chinese fallback as the macOS trio; only the Japanese face would be a
|
||||
// regression. Each installed macOS face is on the list (checked above) and covers the
|
||||
// shared ideographs.
|
||||
assert!(!JAPANESE_FAMILIES.iter().any(|j| j.eq_ignore_ascii_case(family)), "{family} is a Japanese family");
|
||||
for c in ['骨', '直', '日'] {
|
||||
assert!(face(resolved.face).has_char(c), "{family} cannot draw {c}");
|
||||
}
|
||||
for name in installed {
|
||||
let resolved = resolve_in(name, "Regular").expect("installed above");
|
||||
for c in ['骨', '直', '日'] {
|
||||
assert!(face(resolved.face).has_char(c), "{name} cannot draw {c}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// #453 under a Chinese interface: Han text in a Latin-only family falls back through the Han
|
||||
/// list, in the locale's order, to an installed Simplified Chinese family; it must not reach the
|
||||
/// Japanese face the exhaustive search would pick. The direct family lookup above would not
|
||||
/// catch a `fallback_for` that skipped `han_order`. Skipped where no Simplified family is
|
||||
/// installed.
|
||||
#[test]
|
||||
fn chinese_han_text_falls_back_to_a_simplified_chinese_family() {
|
||||
let _locale = TEST_CJK_LOCALE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
scan_system();
|
||||
if !SIMPLIFIED_CHINESE_FAMILIES.iter().any(|f| resolve_in(f, "Regular").is_some()) {
|
||||
eprintln!("no Simplified Chinese family installed; skipping");
|
||||
return;
|
||||
}
|
||||
set_cjk_locale("zh-CN");
|
||||
let _reset = LocaleReset;
|
||||
let inter = resolve("Inter", "Regular").face;
|
||||
for c in ['骨', '直', '日', '真'] {
|
||||
let f = face(fallback_for(c, inter));
|
||||
assert!(f.has_char(c), "{c}: {} {} lacks it", f.info.family, f.info.style);
|
||||
assert!(
|
||||
SIMPLIFIED_CHINESE_FAMILIES.iter().any(|n| n.eq_ignore_ascii_case(&f.info.family)),
|
||||
"{c} → {} is not a Simplified Chinese family",
|
||||
f.info.family
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1289,6 +1289,8 @@ mod tests {
|
||||
/// (#453). Skipped where no CJK face is installed, like the font-fallback tests.
|
||||
#[test]
|
||||
fn cjk_text_falls_back_to_a_face_with_the_glyph() {
|
||||
// The CJK locale is process-global; keep a font test from changing it while this lays out.
|
||||
let _locale = crate::fonts::TEST_CJK_LOCALE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let primary = fonts::resolve("Inter", "Regular").face;
|
||||
if !fonts::face(fonts::fallback_for('日', primary)).has_char('日') {
|
||||
eprintln!("no Japanese face installed; skipping");
|
||||
|
||||
Reference in New Issue
Block a user