mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
M10: the UI takes a system face for Ethiopic, the Indic scripts and the other scripts its fonts lack (#2014)
A String variable's Amharic value drew as boxes in the panels: `bidi::script_fallbacks` sampled Hebrew and Arabic only. It now samples one letter of each of 18 scripts against the system families that draw them on Windows, macOS and Linux; `theme::system_fallbacks` takes the first installed one per script, once. Test: `bidi::tests::other_scripts_take_an_installed_system_face`; localization doc and ROADMAP updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4558fd833b
commit
facc2a2178
5 files changed
+132
-21
No files matched your search
@@ -132,6 +132,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
|
||||
|
||||
## Progress log
|
||||
|
||||
- **2026-11-05** · M10, #2014: the UI takes a system face for Ethiopic, the Indic scripts, Sinhala, Thai, Lao, Myanmar, Khmer, Armenian and Georgian as it did for Hebrew and Arabic (`bidi::script_fallbacks`: one sample letter per script, the system's families for each), so a layer name or a String variable's Amharic value draws in the panels instead of boxes. Test: `bidi::tests::other_scripts_take_an_installed_system_face`.
|
||||
- **2026-10-30**: M8, #1125: damaged `trak` size-table offsets are checked instead of wrapping on 32-bit targets, and an incomplete normal track is dropped entirely, never returned in part. Font indexing makes its existing bounds explicit, preserving later faces and sparse name-table reads. Synthetic regression tests in `fontdb_tests.rs`. Numbers unchanged.
|
||||
- **2026-10-30** · Text style dedupe is O(spans) (M8, #1102): `shape_text` deduped spans into styles by a scan over the styles already collected — O(spans × styles), so a styled text's dedupe grew with the square of its runs (a synthetic 50k-run text with 100 alternating styles spent 7.7 ms in it). Now the last two styles again (spans run in blocks), then buckets keyed by a cheap fingerprint (family and style bytes, size bits with −0.0 folded, the enums, fill count and solid colours), each bucket resolved with the full equality: the result is the scan's, never worse than it, and O(spans) when names and sizes differ (the same synthetic text: 2.6 ms). The font-db write lock is untouched: `find()` loads faces, which needs the write. `layout_dedupe_tests`. Numbers unchanged.
|
||||
- **2026-10-29** · M10: correct nine German labels in `de.toml`: gradient stops use Verlaufsstopp, OpenType ss01–ss05 use Stilsatz, and the Home-screen preference names the Startbildschirm consistently in the UI and Actions palette. This is a partial terminology pass; German remains a draft awaiting a full native-speaker review and maintainer approval. Numbers unchanged.
|
||||
|
||||
+73
-17
@@ -48,25 +48,81 @@ pub fn single_line(ui: &mut Ui, id: Id, text: &mut String, font: FontId, width:
|
||||
.response
|
||||
}
|
||||
|
||||
/// One letter per script the UI's own fonts may lack: Hebrew, Arabic, Ethiopic, Devanagari,
|
||||
/// Bengali, Gurmukhi, Gujarati, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Lao, Myanmar,
|
||||
/// Khmer, Armenian and Georgian. egui's shaper selects the joined contextual forms itself.
|
||||
pub(crate) const SCRIPT_SAMPLES: [char; 18] = [
|
||||
'\u{05D0}', '\u{0628}', '\u{1200}', '\u{0915}', '\u{0995}', '\u{0A15}', '\u{0A95}', '\u{0B95}', '\u{0C15}', '\u{0C95}', '\u{0D15}', '\u{0D9A}',
|
||||
'\u{0E01}', '\u{0E81}', '\u{1000}', '\u{1780}', '\u{0531}', '\u{10D0}',
|
||||
];
|
||||
|
||||
/// System families that draw those scripts, in order of preference: the first installed one that
|
||||
/// has a script's letter is taken, so a family covering several scripts (Segoe UI, Nirmala UI,
|
||||
/// Kohinoor, Noto Sans) joins once. Windows, macOS and the common Linux fonts for each script.
|
||||
pub(crate) const SCRIPT_FAMILIES: [&str; 50] = [
|
||||
// Hebrew and Arabic.
|
||||
"Segoe UI",
|
||||
"Arial",
|
||||
"Tahoma",
|
||||
"Noto Sans Hebrew",
|
||||
"Noto Sans Arabic",
|
||||
"Arial Hebrew",
|
||||
"Geeza Pro",
|
||||
"Lucida Grande",
|
||||
"DejaVu Sans",
|
||||
"FreeSans",
|
||||
"Noto Sans",
|
||||
// Ethiopic (#2014).
|
||||
"Ebrima",
|
||||
"Nyala",
|
||||
"Kefa",
|
||||
"Noto Sans Ethiopic",
|
||||
"Abyssinica SIL",
|
||||
// The Indic scripts and Sinhala.
|
||||
"Nirmala UI",
|
||||
"Kohinoor Devanagari",
|
||||
"Kohinoor Bangla",
|
||||
"Kohinoor Gujarati",
|
||||
"Kohinoor Telugu",
|
||||
"Gurmukhi MN",
|
||||
"Tamil Sangam MN",
|
||||
"Kannada Sangam MN",
|
||||
"Malayalam Sangam MN",
|
||||
"Sinhala Sangam MN",
|
||||
"Noto Sans Devanagari",
|
||||
"Noto Sans Bengali",
|
||||
"Noto Sans Gurmukhi",
|
||||
"Noto Sans Gujarati",
|
||||
"Noto Sans Tamil",
|
||||
"Noto Sans Telugu",
|
||||
"Noto Sans Kannada",
|
||||
"Noto Sans Malayalam",
|
||||
"Noto Sans Sinhala",
|
||||
"Lohit Devanagari",
|
||||
// Thai, Lao, Myanmar and Khmer.
|
||||
"Leelawadee UI",
|
||||
"Thonburi",
|
||||
"Lao Sangam MN",
|
||||
"Myanmar Text",
|
||||
"Myanmar Sangam MN",
|
||||
"Khmer Sangam MN",
|
||||
"Noto Sans Thai",
|
||||
"Noto Sans Lao",
|
||||
"Noto Sans Myanmar",
|
||||
"Noto Sans Khmer",
|
||||
// Armenian and Georgian.
|
||||
"Sylfaen",
|
||||
"Mshtakan",
|
||||
"Noto Sans Armenian",
|
||||
"Noto Sans Georgian",
|
||||
];
|
||||
|
||||
/// Faces the UI adds for scripts its own fonts lack (desktop: from the system), as egui font data;
|
||||
/// `taken` as for [`crate::theme::system_fallbacks`].
|
||||
/// `taken` as for [`crate::theme::system_fallbacks`]. Hebrew and Arabic names were covered first;
|
||||
/// Ethiopic and the other scripts in [`SCRIPT_SAMPLES`] drew as boxes in the panels (a String
|
||||
/// variable's Amharic value, #2014) until the system face for each was taken too.
|
||||
pub fn script_fallbacks(have: &dyn Fn(char) -> bool, taken: &mut Vec<Arc<figcraft_text::Face>>) -> Vec<(String, egui::FontData)> {
|
||||
// Hebrew and Arabic letters; egui's shaper selects the joined contextual forms itself.
|
||||
const SAMPLES: [char; 2] = ['\u{05D0}', '\u{0628}'];
|
||||
const FAMILIES: [&str; 11] = [
|
||||
"Segoe UI",
|
||||
"Arial",
|
||||
"Tahoma",
|
||||
"Noto Sans Hebrew",
|
||||
"Noto Sans Arabic",
|
||||
"Arial Hebrew",
|
||||
"Geeza Pro",
|
||||
"Lucida Grande",
|
||||
"DejaVu Sans",
|
||||
"FreeSans",
|
||||
"Noto Sans",
|
||||
];
|
||||
crate::theme::system_fallbacks(SAMPLES, &FAMILIES, have, taken)
|
||||
crate::theme::system_fallbacks(SCRIPT_SAMPLES, &SCRIPT_FAMILIES, have, taken)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -310,3 +310,52 @@ fn clicks_place_the_logical_caret_by_what_is_shown() {
|
||||
run(vec![Event::Text("א".into())], &mut text, &mut rect);
|
||||
assert_eq!(text, "שלוםא");
|
||||
}
|
||||
|
||||
/// Scripts beyond Hebrew and Arabic (#2014): a String variable's Amharic value drew as boxes
|
||||
/// because the UI took system faces for two scripts only. Now every script in `SCRIPT_SAMPLES`
|
||||
/// with an installed face gets one, and the UI's fonts then have its glyphs. A machine without a
|
||||
/// face for a script skips that script (CI has few fonts); a face the UI's own fonts cover is not
|
||||
/// taken twice.
|
||||
#[test]
|
||||
fn other_scripts_take_an_installed_system_face() {
|
||||
figcraft_text::scan_system_fonts();
|
||||
let installed = |c: char| figcraft_text::face_with_char(c, &crate::bidi::SCRIPT_FAMILIES).is_some();
|
||||
let mut taken = Vec::new();
|
||||
let added = crate::bidi::script_fallbacks(&|_| false, &mut taken);
|
||||
let scripts = [('\u{1200}', "Ethiopic"), ('\u{0915}', "Devanagari"), ('\u{0B95}', "Tamil"), ('\u{1000}', "Myanmar"), ('\u{0531}', "Armenian")];
|
||||
let mut checked = 0;
|
||||
for (c, script) in scripts {
|
||||
if !installed(c) {
|
||||
eprintln!("no {script} face on this system; skipping");
|
||||
continue;
|
||||
}
|
||||
let families: Vec<&str> = taken.iter().map(|f| f.family.as_str()).collect();
|
||||
assert!(taken.iter().any(|f| f.has_char(c)), "a face for {script} is taken: {families:?}");
|
||||
checked += 1;
|
||||
}
|
||||
// A face that several scripts share is added once, under its system key.
|
||||
let keys: Vec<&str> = added.iter().map(|(k, _)| k.as_str()).collect();
|
||||
assert_eq!(keys.len(), taken.len(), "{keys:?}");
|
||||
assert!(keys.iter().all(|k| k.starts_with("system-")), "{keys:?}");
|
||||
let mut seen = keys.clone();
|
||||
seen.sort_unstable();
|
||||
seen.dedup();
|
||||
assert_eq!(seen.len(), keys.len(), "no face twice: {keys:?}");
|
||||
// Nothing is taken for a script the UI's own fonts draw.
|
||||
assert!(crate::bidi::script_fallbacks(&|_| true, &mut Vec::new()).is_empty());
|
||||
if checked == 0 {
|
||||
eprintln!("no face for any of the sampled scripts on this system; the UI font check was not run");
|
||||
return;
|
||||
}
|
||||
// Through the whole font setup: the UI draws the letters of every script it found a face for.
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx, true, 0);
|
||||
ctx.run_ui(RawInput::default(), |_| {}).textures_delta.clear();
|
||||
ctx.fonts_mut(|f| {
|
||||
for (c, script) in scripts {
|
||||
if installed(c) {
|
||||
assert!(f.has_glyph(®ular(11.0), c), "the UI fonts draw {script} ({c:?})");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -271,7 +271,8 @@ pub fn on_dark<R>(ui: &mut egui::Ui, f: impl FnOnce(&mut egui::Ui) -> R) -> R {
|
||||
|
||||
/// UI fonts: Inter and the Archivo wordmark (from the craft-fonts build input) when present;
|
||||
/// otherwise egui's defaults. With
|
||||
/// `system`, also system faces for scripts those lack (Hebrew, joined Arabic) so names in them show,
|
||||
/// `system`, also system faces for scripts those lack (Hebrew, joined Arabic, Ethiopic, the Indic
|
||||
/// scripts and the others [`crate::bidi::script_fallbacks`] names) so names in them show,
|
||||
/// and faces for the CJK scripts in `cjk` ([`crate::cjk`]).
|
||||
///
|
||||
/// The craft-fonts set embeds Noto Sans Arabic and Noto Sans Hebrew; with `system` off those
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Bosnian draft catalog, one/few/other plurals; German terminology corrections (M10): gradient stops, OpenType stylistic sets and the Home-screen preference; partial review, catalog remains a draft; Arabic draft catalog, count-neutral plurals and web font gate; Thai draft catalog and Thai UI fonts; a missing translation fails no check: `cargo xtask i18n-report` lists them; Vietnamese draft catalog; string counts re-measured: 2,352 entries; Russian and Turkish catalogs reviewed by native speakers, Russian one/few/many plurals; Czech and Indonesian drafts; the Japanese draft completed; counts re-measured; Windows UI language from the first preferred language; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog; macOS finds its downloadable CJK system fonts (#406); string counts re-measured: 2,272 entries, the drafts at 95–99%; Swedish command labels and menu wordings; Russian, Swedish, Turkish, Czech and Indonesian listed; a duplicated paragraph removed) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** patch (the UI takes a system face for Ethiopic, the Indic scripts, Sinhala, Thai, Lao, Myanmar, Khmer, Armenian and Georgian as it did for Hebrew and Arabic, so a String variable's Amharic value no longer draws as boxes (#2014); Bosnian draft catalog, one/few/other plurals; German terminology corrections (M10): gradient stops, OpenType stylistic sets and the Home-screen preference; partial review, catalog remains a draft; Arabic draft catalog, count-neutral plurals and web font gate; Thai draft catalog and Thai UI fonts; a missing translation fails no check: `cargo xtask i18n-report` lists them; Vietnamese draft catalog; string counts re-measured: 2,352 entries; Russian and Turkish catalogs reviewed by native speakers, Russian one/few/many plurals; Czech and Indonesian drafts; the Japanese draft completed; counts re-measured; Windows UI language from the first preferred language; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog; macOS finds its downloadable CJK system fonts (#406); string counts re-measured: 2,272 entries, the drafts at 95–99%; Swedish command labels and menu wordings; Russian, Swedish, Turkish, Czech and Indonesian listed; a duplicated paragraph removed) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
FigCraft's interface has a localization layer with an English catalog, a Russian catalog reviewed
|
||||
by a native speaker, and Spanish (with a Latin American overlay), French, German, Brazilian
|
||||
@@ -59,8 +59,11 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
|
||||
- **Script support in documents:** Unicode bidi with Arabic shaping and mirrored brackets in text
|
||||
layers; right-to-left list markers and indents; right-to-left names in the layers panel, tabs
|
||||
and canvas labels (visual order, joined Arabic); inline IME composition with the candidate window
|
||||
at the caret; system-font fallback for scripts the UI fonts lack. Devanagari shapes through
|
||||
harfrust. No vertical text (Figma has none either).
|
||||
at the caret; system-font fallback for scripts the UI fonts lack: Hebrew and Arabic, and since
|
||||
#2014 Ethiopic, the Indic scripts, Sinhala, Thai, Lao, Myanmar, Khmer, Armenian and Georgian,
|
||||
so a layer name or a String variable's value in them draws in the panels when the system has a
|
||||
face (`crates/ui-egui/src/bidi.rs`, `SCRIPT_SAMPLES` and `SCRIPT_FAMILIES`). Devanagari shapes
|
||||
through harfrust. No vertical text (Figma has none either).
|
||||
- **Script support in the UI:** menus widen to their longest label; dropdowns, the two-column
|
||||
labels of the right panel and the home screen's cards keep a long translation on one line with an
|
||||
ellipsis and show it in full on hover (checked in German, the longest); no mirrored
|
||||
@@ -125,6 +128,7 @@ Hindi and Arabic. Traditional Chinese is not planned (no catalog; its users see
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-05 | patch | #2014: the panels drew a String variable's Amharic value as boxes: the UI took a system face for Hebrew and Arabic only. `bidi::script_fallbacks` now samples one letter of each of 18 scripts (Ethiopic, the Indic scripts, Sinhala, Thai, Lao, Myanmar, Khmer, Armenian, Georgian as well) against the system's families for each (Windows, macOS, the common Linux fonts), the first installed one that draws the letter taken once. Test: `bidi::tests::other_scripts_take_an_installed_system_face` (skips scripts the machine has no face for). |
|
||||
| 2026-10-30 | minor | Bosnian (`bs`, Bosanski) draft catalog: every UI string (1,906), command label (629) and menu wording (31), machine-drafted in Latin script with ijekavian forms and marked as a draft awaiting native review. Bosnian plurals: one for 1, 21, 31… (not 11), few for 2–4, 22–24… (not 12–14), other for the rest. Grade and hours unchanged (Bosnian is not one of the twelve key languages) |
|
||||
| 2026-10-29 | minor | German terminology corrections (M10): gradient stops, OpenType stylistic sets and the Home-screen preference; partial review, catalog remains a draft. Nine existing translations corrected; keys, placeholders, coverage, grade and estimates unchanged |
|
||||
| 2026-10-28 | minor | Arabic draft: 1,857 UI entries, 620 command labels and 31 menu wordings, with count-neutral plurals. AI-assisted, native review pending. Desktop uses the existing Arabic fallback and bidi; web requires an embedded Arabic face. No panel mirroring or parity-grade increase claimed |
|
||||
|
||||
Reference in new issue
Block a user