Merge branch 'pr-1971' into land/2016

This commit is contained in:
Tanish Manku
2026-11-05 14:36:35 -07:00
5 changed files with 57 additions and 16 deletions
+5 -5
View File
@@ -3,10 +3,10 @@
//! empty, `CRAFT_FONTS` is empty and the build is unchanged. It only reads the local checkout
//! (no network). A bad checkout is a warning, or an error with `CRAFT_FONTS_REQUIRED=1`.
//!
//! On wasm32 it embeds only BIZ UDPGothic Regular, the `Arab` faces and the `Thai` Regular faces
//! (about 5.9 MB), to keep the web build within hosting limits (Cloudflare Pages: 25 MiB per
//! file). The web build has no system fonts, so these are its only fallback for Japanese,
//! Arabic-script and Thai text.
//! On wasm32 it embeds only BIZ UDPGothic Regular, the `Arab` and `Geor` faces and the `Thai`
//! Regular faces (about 6.1 MB), to keep the web build within hosting limits (Cloudflare Pages:
//! 25 MiB per file). The web build has no system fonts, so these are its only fallback for
//! Japanese, Arabic-script, Georgian and Thai text.
use std::fmt::Write as _;
use std::path::PathBuf;
@@ -56,7 +56,7 @@ fn craft_fonts(dir: &std::path::Path) -> Result<String, String> {
continue;
}
let has = |script: &str| scripts.split(',').any(|s| s.trim() == script);
let web_face = (*family == "BIZ UDPGothic" && *style == "Regular") || has("Arab") || (has("Thai") && *style == "Regular");
let web_face = (*family == "BIZ UDPGothic" && *style == "Regular") || has("Arab") || has("Geor") || (has("Thai") && *style == "Regular");
if wasm && !web_face {
continue;
}
+3 -2
View File
@@ -1,8 +1,8 @@
//! Fonts from the optional craft-fonts build input (<https://github.com/storytold/craft-fonts>).
//!
//! Built with `CRAFT_FONTS_DIR=<craft-fonts checkout>`, `build.rs` embeds the fonts listed in its
//! `fonts/manifest.txt` (web builds: only BIZ UDPGothic Regular, the Arabic faces and the Thai
//! Regular faces). Without it [`CRAFT_FONTS`] is empty and nothing changes. The font table ([`crate::ttf`])
//! `fonts/manifest.txt` (web builds: only BIZ UDPGothic Regular, the Arabic and Georgian faces and
//! the Thai Regular faces). Without it [`CRAFT_FONTS`] is empty and nothing changes. The font table ([`crate::ttf`])
//! lists the embedded fonts with the installed ones, under [`CraftFont::stem`]. A text style
//! can name one (`BIZ UDPGothic`), and they are fallbacks for characters a text's font lacks.
//! They are copied out of the binary only when first used.
@@ -85,6 +85,7 @@ mod tests {
Some("Arab") => '\u{0628}',
Some("Thai") => '\u{0E01}',
Some("Hang" | "Kore") => '가',
Some("Geor") => 'ა',
_ => 'A',
};
assert!(face.is_some_and(|x| x.has_glyph(sample)), "{} lacks {sample}", f.family);
+1
View File
@@ -276,6 +276,7 @@ fn run_language_command(app: &mut CadApp, id: &str, params: &Value) -> Option<Re
"No installed interface font has the Vietnamese letters, so the interface stays in English. Install one that does (Segoe UI, Arial, DejaVu Sans or Noto Sans) to use Vietnamese."
}
"ar" => "No Arabic font is installed, so the interface stays in English. Install one (Noto Sans Arabic, for example) to use Arabic.",
"ka" => "This build has no Georgian font, so the interface stays in English. Build with craft-fonts (its Geor face) to use Georgian.",
_ => "No Chinese font is installed, so the interface stays in English. Install one (Noto Sans CJK, for example) to use Chinese.",
});
}
+42 -9
View File
@@ -314,7 +314,8 @@ pub fn mono() -> FontId {
/// language: file, layout and layer names can be Japanese or Chinese whatever the interface
/// shows (#513). In a Chinese interface (`language` "zh…") the Chinese font comes first, else a
/// Japanese one, so names take their own glyph forms; in an Arabic interface an installed
/// Arabic font follows. A font embedded from craft-fonts comes
/// Arabic font follows. The embedded Georgian face loads in every language, so the language
/// picker can show ქართული. A font embedded from craft-fonts comes
/// last, for systems without any (and the web build, which has no system fonts). A Korean
/// interface adds a Hangul font only when the CJK font found doesn't cover Hangul
/// ([`korean_font`]).
@@ -482,7 +483,8 @@ pub fn shows_vietnamese(language: &str) -> bool {
/// shows (#513), with the interface's own face leading. A sans-serif face reads best, and a
/// Chinese interface takes the face with its own glyphs ahead of a Japanese one — a Japanese face
/// shares most ideographs but lacks the simplified-only ones (图 园 测), which would be tofu.
/// العربية adds its Arabic face after those, egui's bundled faces carrying no Arabic glyphs.
/// The Georgian face follows in every language (egui's bundled faces carry no Georgian glyphs, and
/// the picker names the language ქართული); العربية adds its Arabic face after those.
fn embedded_ui_faces<'a>(faces: &[&'a cadcraft_fonts::CraftFont], language: &str) -> Vec<&'a cadcraft_fonts::CraftFont> {
let cjk = |f: &&cadcraft_fonts::CraftFont| f.style == "Regular" && f.scripts.iter().any(|s| matches!(*s, "Jpan" | "Hans" | "Hant"));
let mut out: Vec<_> = faces.iter().copied().filter(cjk).collect();
@@ -490,6 +492,7 @@ fn embedded_ui_faces<'a>(faces: &[&'a cadcraft_fonts::CraftFont], language: &str
if language.starts_with("zh") {
out.sort_by_key(|f| !f.scripts.contains(&"Hans"));
}
out.extend(faces.iter().copied().filter(|f| f.style == "Regular" && f.scripts.contains(&"Geor")));
if language == "ar" {
out.extend(faces.iter().copied().filter(|f| f.style == "Regular" && f.scripts.contains(&"Arab")));
}
@@ -728,12 +731,14 @@ pub fn shows_hangul(language: &str) -> bool {
/// Whether the interface can show `language` without missing-glyph boxes: Chinese needs a font
/// with its glyphs (installed, or the CJK face this build embedded from craft-fonts), Korean a
/// font with Hangul, العربية an installed Arabic font or the embedded Arabic face, Tiếng Việt a
/// system interface font with its tone-marked letters ([`vietnamese_font`]).
/// system interface font with its tone-marked letters ([`vietnamese_font`]), ქართული the
/// embedded Georgian face.
pub fn can_show(language: &str) -> bool {
match language {
"ko" => shows_hangul("ko"),
"vi" => vietnamese_font().is_some(),
"ar" => arabic_font().is_some() || embedded_script("Arab"),
"ka" => embedded_script("Geor"),
l if l.starts_with("zh") => cjk_font().is_some() || embedded_script("Hans") || embedded_script("Hant"),
_ => true,
}
@@ -843,13 +848,16 @@ mod tests {
let japanese = face("BIZ UDPGothic", &["Jpan", "Latn"]);
let chinese = face("Han Test Face", &["Hans", "Latn"]);
let arabic = face("Noto Sans Arabic", &["Arab"]);
let faces = [&japanese, &chinese, &arabic];
let georgian = face("Noto Sans Georgian", &["Geor"]);
let faces = [&japanese, &chinese, &arabic, &georgian];
// Every interface reads CJK names, so both CJK faces load, the sans-serif face first.
assert_eq!(families(&super::embedded_ui_faces(&faces, "en")), ["BIZ UDPGothic", "Han Test Face"]);
// The Georgian face loads in every interface too, after them: the picker shows ქართული.
assert_eq!(families(&super::embedded_ui_faces(&faces, "en")), ["BIZ UDPGothic", "Han Test Face", "Noto Sans Georgian"]);
// A Chinese interface takes the Han face first: a Japanese face lacks 图 园 测.
assert_eq!(families(&super::embedded_ui_faces(&faces, "zh")), ["Han Test Face", "BIZ UDPGothic"]);
assert_eq!(families(&super::embedded_ui_faces(&faces, "zh")), ["Han Test Face", "BIZ UDPGothic", "Noto Sans Georgian"]);
// العربية adds the Arabic face, after the CJK ones egui's bundled faces have no glyphs for.
assert_eq!(families(&super::embedded_ui_faces(&faces, "ar")), ["BIZ UDPGothic", "Han Test Face", "Noto Sans Arabic"]);
assert_eq!(families(&super::embedded_ui_faces(&faces, "ar")), ["BIZ UDPGothic", "Han Test Face", "Noto Sans Georgian", "Noto Sans Arabic"]);
assert_eq!(families(&super::embedded_ui_faces(&faces, "ka")), ["BIZ UDPGothic", "Han Test Face", "Noto Sans Georgian"]);
// Another weight is a style of a face, not an interface face of its own.
let bold = CraftFont { family: "BIZ UDPGothic", style: "Bold", scripts: &["Jpan"], bytes: &[] };
assert!(super::embedded_ui_faces(&[&bold], "en").is_empty());
@@ -860,13 +868,38 @@ mod tests {
/// leaving the installed-font answer the gate had before.
#[test]
fn embedded_faces_answer_for_their_scripts() {
let faces = [face("BIZ UDPGothic", &["Jpan", "Latn"]), face("Noto Sans Arabic", &["Arab"])];
assert!(super::any_script(&faces, "Jpan") && super::any_script(&faces, "Arab"));
let faces = [face("BIZ UDPGothic", &["Jpan", "Latn"]), face("Noto Sans Arabic", &["Arab"]), face("Noto Sans Georgian", &["Geor"])];
assert!(super::any_script(&faces, "Jpan") && super::any_script(&faces, "Arab") && super::any_script(&faces, "Geor"));
assert!(!super::any_script(&faces, "Hans") && !super::any_script(&faces, "Hant"));
// A `Bold`-only face is not the interface's, and a script code is matched whole.
let bold = CraftFont { family: "Some Han Bold", style: "Bold", scripts: &["Hans"], bytes: &[] };
assert!(!super::any_script(&[bold], "Hans") && !super::any_script(&faces, "Ara"));
assert!(!super::embedded_script("Hans") || super::can_show("zh"));
// ქართული rides on the embedded Georgian face alone.
assert_eq!(super::can_show("ka"), super::embedded_script("Geor"));
assert!(super::can_show("en") && super::can_show("uk"));
}
/// With the craft-fonts Georgian face (release and web builds), every Mkhedruli letter and the
/// picker's ქართული render in both families, in a Georgian and in an English interface.
#[test]
fn embedded_georgian_face_covers_the_alphabet_in_every_interface() {
if !super::embedded_script("Geor") {
return;
}
let letters: Vec<char> = ('ა'..='ჰ').chain("ქართული".chars()).collect();
for language in ["ka", "en"] {
let ctx = egui::Context::default();
super::install_fonts(&ctx, language);
let mut output = ctx.run_ui(Default::default(), |_| {});
output.textures_delta.clear();
ctx.fonts_mut(|view| {
for family in [egui::FontFamily::Proportional, egui::FontFamily::Monospace] {
for c in &letters {
assert!(view.has_glyph(&egui::FontId::new(12.0, family.clone()), *c), "{language}: missing Georgian glyph {c} in {family:?}");
}
}
});
}
}
}
+6
View File
@@ -101,6 +101,12 @@ catalog, the saved `ko` preference is kept but the interface falls back to Engli
command reports that a font is needed, and the picker uses the readable label **Korean** wherever
Hangul can't be shown. This change does not fix platform IME issues or localize engine output.
The bundled egui fonts have no Georgian letters. Georgian is drawn with the Noto Sans Georgian face
(`Geor`) of the craft-fonts build input, which release builds embed (`craft-fonts-ref` in the
workflows, and `web-craft-fonts` for the web build, whose embedded faces include it). The face loads
in every interface language, so Georgian text (file, layout and layer names, labels) draws. A build
without craft-fonts has no Georgian face.
## Greek (`el`) interface
The Greek translation covers all **1,330** distinct source keys collected from the existing