From 0a3f2341a81e1bb08ef51334bccc0a0429f02747 Mon Sep 17 00:00:00 2001 From: Al Rifat Sabbir <150162343+alrifatsabbir@users.noreply.github.com> Date: Mon, 19 Oct 2026 03:10:55 +0600 Subject: [PATCH] Add support for Bengali text in the UI Updated cjk.rs --- crates/ui-egui/src/cjk.rs | 41 +++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/cjk.rs b/crates/ui-egui/src/cjk.rs index fd3ecf8c..dd195dcf 100644 --- a/crates/ui-egui/src/cjk.rs +++ b/crates/ui-egui/src/cjk.rs @@ -1,8 +1,8 @@ -//! Chinese, Japanese and Korean text in the UI. egui's own fonts and Inter have no CJK glyphs, so +//! Chinese, Japanese, Korean and Bengali text in the UI. egui's own fonts and Inter have no CJK or non-Latin glyphs, so //! the UI adds a face per script it needs: the UI language's, the scripts in the open file's names //! and comments, the language picker's and typed text. Desktop finds them among the installed //! fonts (the system font scan), only once one is needed, so English users load none; the web -//! build has only the CJK faces built in from craft-fonts (`FIGCRAFT_EMBED_CJK`), else none. +//! build has only the CJK and non-Latin faces built in from craft-fonts (`FIGCRAFT_EMBED_CJK`), else none. use std::sync::Arc; @@ -12,7 +12,15 @@ pub const KANA: u8 = 1; pub const HANGUL: u8 = 2; /// Han characters (and CJK punctuation and full-width forms). pub const HAN: u8 = 4; +pub const BENGALI: u8 = 8; +const BENGALI_FONTS: &[&str] = &[ + "Noto Sans Bengali", + "Nirmala UI", + "Bangla Sangam MN", + "Kalpurush", + "SolaimanLipi", +]; /// Japanese faces, the built-in one first: Windows, macOS, then Linux names. const JAPANESE: &[&str] = &[ "BIZ UDPGothic", @@ -46,24 +54,26 @@ const CHINESE: &[&str] = &[ "Droid Sans Fallback", ]; -/// The CJK scripts in `s`. +/// The CJK and Bengali scripts in `s`. pub fn scripts(s: &str) -> u8 { s.chars().fold(0, |m, c| { m | match u32::from(c) { 0x3040..=0x30FF | 0x31F0..=0x31FF | 0xFF66..=0xFF9F => KANA, 0x1100..=0x11FF | 0x3130..=0x318F | 0xA960..=0xA97F | 0xAC00..=0xD7FF | 0xFFA0..=0xFFDC => HANGUL, 0x3000..=0x303F | 0x3400..=0x4DBF | 0x4E00..=0x9FFF | 0xF900..=0xFAFF | 0xFF01..=0xFF65 | 0x20000..=0x3FFFF => HAN, + 0x0980..=0x09FF => BENGALI, _ => 0, } }) } -/// The CJK scripts a UI language (a catalog code) is written in. +/// The scripts a UI language (a catalog code) is written in. pub fn language(code: &str) -> u8 { match code.split('-').next() { Some("ja") => KANA | HAN, Some("ko") => HANGUL, Some("zh") => HAN, + Some("bn") => BENGALI, _ => 0, } } @@ -75,7 +85,9 @@ pub fn can_show(code: &str) -> bool { let need = language(code); need == 0 || !cfg!(target_arch = "wasm32") - || samples(need, 0).iter().all(|(c, _)| figcraft_text::face_with_char(*c, &["BIZ UDPGothic", "Noto Sans CJK SC"]).is_some()) + || samples(need, 0).iter().all(|(c, families)| { + figcraft_text::face_with_char(*c, families).is_some() + }) } /// A character standing for each script in `need`, with the families to look for it in, the UI @@ -89,10 +101,21 @@ fn samples(need: u8, ui: u8) -> Vec<(char, Vec<&'static str>)> { (false, HANGUL) => ('漢', [KOREAN, JAPANESE, CHINESE].concat()), (false, _) => ('漢', [JAPANESE, KOREAN, CHINESE].concat()), }; - let mut v = vec![(KANA, ('あ', JAPANESE.to_vec())), (HAN, han), (HANGUL, ('한', KOREAN.to_vec()))]; + let mut v = vec![ + (KANA, ('あ', JAPANESE.to_vec())), + (HAN, han), + (HANGUL, ('한', KOREAN.to_vec())), + (BENGALI, ('অ', BENGALI_FONTS.to_vec())), + ]; match ui { HANGUL => v.reverse(), HAN => v.swap(0, 1), + BENGALI => { + if let Some(pos) = v.iter().position(|(bit, _)| *bit == BENGALI) { + let item = v.remove(pos); + v.insert(0, item); + } + } _ => {} } v.into_iter().filter(|(bit, _)| need & bit != 0).map(|(_, s)| s).collect() @@ -124,11 +147,13 @@ mod tests { assert_eq!(scripts("ボタン"), KANA); assert_eq!(scripts("버튼"), HANGUL); assert_eq!(scripts("按钮"), HAN); + assert_eq!(scripts("বাংলা"), BENGALI); assert_eq!(scripts("日本語のレイヤー(1)"), KANA | HAN); assert_eq!(language("ja"), KANA | HAN); assert_eq!(language("ko"), HANGUL); assert_eq!(language("zh-Hant"), HAN); assert_eq!(language("zh-Hans"), HAN); + assert_eq!(language("bn"), BENGALI); assert_eq!(language("fr"), 0); assert!(["en", "es", "es-419", "fr", "de", "pt-BR", "tr", "id"].iter().all(|c| can_show(c))); } @@ -137,7 +162,7 @@ mod tests { fn the_ui_language_script_comes_first() { let order = |need, ui| samples(need, ui).into_iter().map(|(c, _)| c).collect::(); let first_face = |need, ui| samples(need, ui).first().and_then(|(_, f)| f.first()).copied(); - assert_eq!(order(KANA | HAN | HANGUL, 0), "あ漢한"); + assert_eq!(order(KANA | HAN | HANGUL | BENGALI, 0), "あ漢한অ"); assert_eq!(order(KANA | HAN | HANGUL, KANA | HAN), "あ漢한"); assert_eq!(order(KANA | HAN | HANGUL, HANGUL), "한漢あ"); assert_eq!(order(HANGUL, 0), "한"); @@ -150,6 +175,6 @@ mod tests { assert_eq!(order(KANA | HAN, language("zh-Hans")), "们あ"); assert!(["Microsoft YaHei UI", "PingFang SC", "Noto Sans CJK SC"].iter().all(|f| samples(HAN, HAN)[0].1[..4].contains(f))); assert!(faces(0, "en", &|_| false).is_empty()); - assert!(faces(KANA | HANGUL | HAN, "ja", &|_| true).is_empty(), "nothing is looked up for scripts the UI draws"); + assert!(faces(KANA | HANGUL | HAN | BENGALI, "ja", &|_| true).is_empty(), "nothing is looked up for scripts the UI draws"); } }