mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
M10: FIGCRAFT_EMBED_SCRIPTS embeds other scripts' craft-fonts faces in a web build (#1902)
The browser has no system fonts and the text crate's build script keeps the web catalog to Inter, Noto Sans Georgian, the wordmark and (with FIGCRAFT_EMBED_CJK) Noto Sans CJK, so craft-fonts' Noto Sans Arabic never reached a web build and Persian or Arabic text drew as missing-glyph boxes. FIGCRAFT_EMBED_SCRIPTS names script tags as the manifest lists them (Arab, Hebr, ...); a web build keeps the faces of those scripts as well. The default build is unchanged. Test: web_embeds_other_scripts_faces_only_when_asked (build script). Docs: localization-parity (web fonts paragraph, Arabic row, status line, revision history), translating.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
35684eaa02
commit
1354c6bb6f
3 files changed
+74
-25
No files matched your search
+64
-21
@@ -3,22 +3,28 @@
|
||||
//! `CRAFT_FONTS` is empty and native apps use system fonts. Required web releases enforce Inter
|
||||
//! Regular/SemiBold and Noto Sans Georgian Regular after filtering. Recipe: craft-fonts `docs/integration.md`.
|
||||
//! Noto Sans CJK only with `FIGCRAFT_EMBED_CJK` set (the web build's only way to show CJK UI text).
|
||||
//! The other scripts' faces (Noto Sans Arabic, Hebrew, Thai, Devanagari…) stay out of web builds unless
|
||||
//! `FIGCRAFT_EMBED_SCRIPTS` names their script tags (`Arab,Hebr`, as the manifest's scripts column
|
||||
//! lists them; #1902).
|
||||
use std::fmt::Write as _;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Font build script: emit `craft_fonts.rs` from `input_fonts`, passing the
|
||||
/// `CRAFT_FONTS_DIR`, `CRAFT_FONTS_REQUIRED`, wasm target and `FIGCRAFT_EMBED_CJK` inputs;
|
||||
/// `CRAFT_FONTS_DIR`, `CRAFT_FONTS_REQUIRED`, wasm target, `FIGCRAFT_EMBED_CJK` and
|
||||
/// `FIGCRAFT_EMBED_SCRIPTS` inputs;
|
||||
/// report generation and write failures with `cargo::error`.
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR");
|
||||
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED");
|
||||
println!("cargo::rerun-if-env-changed=FIGCRAFT_EMBED_CJK");
|
||||
println!("cargo::rerun-if-env-changed=FIGCRAFT_EMBED_SCRIPTS");
|
||||
let mut src = String::from("pub static CRAFT_FONTS: &[CraftFont] = &[\n");
|
||||
let dir = std::env::var_os("CRAFT_FONTS_DIR").map(PathBuf::from);
|
||||
let required = std::env::var_os("CRAFT_FONTS_REQUIRED").is_some();
|
||||
let wasm = std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|a| a == "wasm32");
|
||||
let embed_cjk = std::env::var_os("FIGCRAFT_EMBED_CJK").is_some();
|
||||
match input_fonts(dir.as_deref(), wasm, required, embed_cjk) {
|
||||
let embed_scripts = scripts_from(std::env::var("FIGCRAFT_EMBED_SCRIPTS").ok().as_deref());
|
||||
match input_fonts(dir.as_deref(), wasm, required, embed_cjk, &embed_scripts) {
|
||||
Ok(entries) => src.push_str(&entries),
|
||||
Err(e) => println!("cargo::error={e}"),
|
||||
}
|
||||
@@ -29,8 +35,15 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The script tags `FIGCRAFT_EMBED_SCRIPTS` names (comma- or space-separated), trimmed; none when
|
||||
/// it is unset.
|
||||
fn scripts_from(var: Option<&str>) -> Vec<String> {
|
||||
var.unwrap_or_default().split([',', ' ']).map(str::trim).filter(|s| !s.is_empty()).map(str::to_owned).collect()
|
||||
}
|
||||
|
||||
/// Collect the effective catalog and enforce the required web release faces after filtering.
|
||||
fn input_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool, embed_cjk: bool) -> Result<String, String> {
|
||||
/// `embed_scripts`: script tags whose faces a web build keeps as well.
|
||||
fn input_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool, embed_cjk: bool, embed_scripts: &[String]) -> Result<String, String> {
|
||||
let Some(dir) = dir else {
|
||||
return if required { Err("CRAFT_FONTS_REQUIRED is set but CRAFT_FONTS_DIR is missing".into()) } else { Ok(String::new()) };
|
||||
};
|
||||
@@ -42,7 +55,7 @@ fn input_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool, embed_
|
||||
watch(&path);
|
||||
continue;
|
||||
}
|
||||
match craft_fonts(dir, manifest, wasm, embed_cjk) {
|
||||
match craft_fonts(dir, manifest, wasm, embed_cjk, embed_scripts) {
|
||||
Ok(catalog) => {
|
||||
out.push_str(&catalog.entries);
|
||||
faces.extend(catalog.faces);
|
||||
@@ -82,9 +95,9 @@ struct Catalog {
|
||||
|
||||
/// Parse a font manifest of `family | style | file | scripts` rows into `CraftFont`
|
||||
/// entries: skip Adobe-authored families, skip CJK unless requested, keep the web catalog
|
||||
/// to its budgeted families, canonicalize and watch each face, and emit `include_bytes!`
|
||||
/// items.
|
||||
fn craft_fonts(dir: &std::path::Path, manifest_rel: &str, wasm: bool, embed_cjk: bool) -> Result<Catalog, String> {
|
||||
/// to its budgeted families (plus the faces of the scripts in `embed_scripts`), canonicalize and
|
||||
/// watch each face, and emit `include_bytes!` items.
|
||||
fn craft_fonts(dir: &std::path::Path, manifest_rel: &str, wasm: bool, embed_cjk: bool, embed_scripts: &[String]) -> Result<Catalog, String> {
|
||||
let manifest = dir.join(manifest_rel);
|
||||
watch(&manifest);
|
||||
let text = std::fs::read_to_string(&manifest).map_err(|e| format!("{}: {e}", manifest.display()))?;
|
||||
@@ -105,9 +118,12 @@ fn craft_fonts(dir: &std::path::Path, manifest_rel: &str, wasm: bool, embed_cjk:
|
||||
continue;
|
||||
}
|
||||
// Web builds have a size budget: Inter, Noto Sans Georgian (small, for the Georgian UI), only the
|
||||
// SemiBold wordmark, and optionally Noto Sans CJK (every CJK script, for CJK UI text).
|
||||
// SemiBold wordmark, optionally Noto Sans CJK (every CJK script, for CJK UI text), and the faces
|
||||
// of the scripts `FIGCRAFT_EMBED_SCRIPTS` names (an Arabic or Persian document needs Noto Sans
|
||||
// Arabic, +0.8 MB; the browser has no system fonts to fall back on, #1902).
|
||||
let wordmark = *family == "Archivo" && *style == "SemiBold";
|
||||
if wasm && !matches!(*family, "Inter" | "Noto Sans Georgian") && !wordmark && !cjk {
|
||||
let asked = scripts.split(',').map(str::trim).any(|s| embed_scripts.iter().any(|e| e.eq_ignore_ascii_case(s)));
|
||||
if wasm && !matches!(*family, "Inter" | "Noto Sans Georgian") && !wordmark && !cjk && !asked {
|
||||
continue;
|
||||
}
|
||||
let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?;
|
||||
@@ -161,9 +177,9 @@ mod tests {
|
||||
fn restored_latin_catalog_preserves_permitted_native_faces_and_web_budget() {
|
||||
let f = Fixture::new();
|
||||
std::fs::write(f.0.join("fonts/latin-manifest.txt"), include_str!("tests/fixtures/latin-families.txt")).unwrap();
|
||||
let native = craft_fonts(&f.0, "fonts/latin-manifest.txt", false, false).unwrap();
|
||||
let native = craft_fonts(&f.0, "fonts/latin-manifest.txt", false, false, &[]).unwrap();
|
||||
assert_eq!(native.faces.len(), 43, "all prior permitted presentation faces remain available");
|
||||
let web = craft_fonts(&f.0, "fonts/latin-manifest.txt", true, false).unwrap();
|
||||
let web = craft_fonts(&f.0, "fonts/latin-manifest.txt", true, false, &[]).unwrap();
|
||||
assert_eq!(web.faces.len(), 4, "the web catalog keeps only the four Inter faces");
|
||||
for catalog in [&native, &web] {
|
||||
assert!(!catalog.entries.contains("Source Sans 3"));
|
||||
@@ -171,16 +187,16 @@ mod tests {
|
||||
}
|
||||
assert!(native.faces.iter().any(|(family, _)| family == "Carlito"));
|
||||
assert!(native.faces.iter().any(|(family, _)| family == "Liberation Serif"));
|
||||
assert!(input_fonts(Some(&f.0), true, true, false).is_ok());
|
||||
assert!(input_fonts(Some(&f.0), true, true, false, &[]).is_ok());
|
||||
}
|
||||
|
||||
/// Asserts required web input rejects a catalog missing Inter Regular or Inter SemiBold.
|
||||
#[test]
|
||||
fn required_web_rejects_core_only_and_partial_latin_catalogs() {
|
||||
let f = Fixture::new();
|
||||
assert!(input_fonts(Some(&f.0), true, true, false).unwrap_err().contains("Inter Regular"));
|
||||
assert!(input_fonts(Some(&f.0), true, true, false, &[]).unwrap_err().contains("Inter Regular"));
|
||||
f.manifest("latin-manifest.txt", &[("Inter", "Regular"), ("Unrelated", "SemiBold")]);
|
||||
assert!(input_fonts(Some(&f.0), true, true, false).unwrap_err().contains("Inter SemiBold"));
|
||||
assert!(input_fonts(Some(&f.0), true, true, false, &[]).unwrap_err().contains("Inter SemiBold"));
|
||||
}
|
||||
|
||||
/// Asserts required web input embeds the Inter and Georgian catalogs (three entries) and
|
||||
@@ -189,12 +205,12 @@ mod tests {
|
||||
fn required_web_embeds_both_catalogs_and_requires_georgian() {
|
||||
let f = Fixture::new();
|
||||
f.manifest("latin-manifest.txt", &[("Inter", "Regular"), ("Inter", "SemiBold")]);
|
||||
let entries = input_fonts(Some(&f.0), true, true, false).unwrap();
|
||||
let entries = input_fonts(Some(&f.0), true, true, false, &[]).unwrap();
|
||||
assert_eq!(entries.matches("CraftFont {").count(), 3);
|
||||
assert!(entries.contains("family: \"Inter\""));
|
||||
assert!(entries.contains("family: \"Noto Sans Georgian\""));
|
||||
f.manifest("manifest.txt", &[("Other", "Regular")]);
|
||||
assert!(input_fonts(Some(&f.0), true, true, false).unwrap_err().contains("Noto Sans Georgian Regular"));
|
||||
assert!(input_fonts(Some(&f.0), true, true, false, &[]).unwrap_err().contains("Noto Sans Georgian Regular"));
|
||||
}
|
||||
|
||||
/// Asserts absent font input is empty when optional and an error when required, while present
|
||||
@@ -202,10 +218,10 @@ mod tests {
|
||||
#[test]
|
||||
fn optional_and_native_inputs_keep_their_existing_policy() {
|
||||
let f = Fixture::new();
|
||||
assert!(input_fonts(None, true, false, false).unwrap().is_empty());
|
||||
assert!(input_fonts(None, true, true, false).unwrap_err().contains("CRAFT_FONTS_DIR is missing"));
|
||||
assert!(input_fonts(Some(&f.0), true, false, false).unwrap().contains("Noto Sans Georgian"));
|
||||
assert!(input_fonts(Some(&f.0), false, true, false).unwrap().contains("Noto Sans Georgian"));
|
||||
assert!(input_fonts(None, true, false, false, &[]).unwrap().is_empty());
|
||||
assert!(input_fonts(None, true, true, false, &[]).unwrap_err().contains("CRAFT_FONTS_DIR is missing"));
|
||||
assert!(input_fonts(Some(&f.0), true, false, false, &[]).unwrap().contains("Noto Sans Georgian"));
|
||||
assert!(input_fonts(Some(&f.0), false, true, false, &[]).unwrap().contains("Noto Sans Georgian"));
|
||||
}
|
||||
|
||||
/// Asserts required web input rejects a manifest whose face file is missing, even when every
|
||||
@@ -215,6 +231,33 @@ mod tests {
|
||||
let f = Fixture::new();
|
||||
f.manifest("latin-manifest.txt", &[("Inter", "Regular"), ("Inter", "SemiBold")]);
|
||||
std::fs::remove_file(f.0.join("fonts/face.ttf")).unwrap();
|
||||
assert!(input_fonts(Some(&f.0), true, true, false).unwrap_err().contains("fonts/face.ttf"));
|
||||
assert!(input_fonts(Some(&f.0), true, true, false, &[]).unwrap_err().contains("fonts/face.ttf"));
|
||||
}
|
||||
|
||||
/// #1902: a web build leaves the other scripts' faces out (an Arabic or Persian document shows
|
||||
/// missing-glyph boxes) unless `FIGCRAFT_EMBED_SCRIPTS` names their script tags; native builds
|
||||
/// keep every face, as before.
|
||||
#[test]
|
||||
fn web_embeds_other_scripts_faces_only_when_asked() {
|
||||
let f = Fixture::new();
|
||||
std::fs::write(
|
||||
f.0.join("fonts/manifest.txt"),
|
||||
"Noto Sans Georgian | Regular | fonts/face.ttf | Geor\nNoto Sans Arabic | Regular | fonts/face.ttf | Arab\nNoto Sans Hebrew | Regular | fonts/face.ttf | Hebr\nNoto Sans Symbols | Regular | fonts/face.ttf | Zsym,Latn\n",
|
||||
)
|
||||
.unwrap();
|
||||
let families = |c: &Catalog| c.faces.iter().map(|(f, _)| f.clone()).collect::<Vec<String>>();
|
||||
let web = craft_fonts(&f.0, "fonts/manifest.txt", true, false, &[]).unwrap();
|
||||
assert_eq!(families(&web), ["Noto Sans Georgian"], "the web budget: Georgian only");
|
||||
let arabic = craft_fonts(&f.0, "fonts/manifest.txt", true, false, &["Arab".to_string()]).unwrap();
|
||||
assert_eq!(families(&arabic), ["Noto Sans Georgian", "Noto Sans Arabic"]);
|
||||
// Several tags, any case, spaces, and a tag among a face's several scripts.
|
||||
let more = craft_fonts(&f.0, "fonts/manifest.txt", true, false, &scripts_from(Some(" hebr, LATN "))).unwrap();
|
||||
assert_eq!(families(&more), ["Noto Sans Georgian", "Noto Sans Hebrew", "Noto Sans Symbols"]);
|
||||
// Native builds keep every face whatever the variable says.
|
||||
assert_eq!(craft_fonts(&f.0, "fonts/manifest.txt", false, false, &[]).unwrap().faces.len(), 4);
|
||||
// The variable reaches the catalog through `input_fonts`.
|
||||
assert!(input_fonts(Some(&f.0), true, false, false, &["Arab".into()]).unwrap().contains("Noto Sans Arabic"));
|
||||
assert!(!input_fonts(Some(&f.0), true, false, false, &[]).unwrap().contains("Noto Sans Arabic"));
|
||||
assert!(scripts_from(None).is_empty());
|
||||
}
|
||||
}
|
||||
@@ -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-03 · **Change:** minor (`FIGCRAFT_EMBED_SCRIPTS` embeds other scripts' craft-fonts faces in a web build, so Arabic and Persian documents draw there (#1902); 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
|
||||
@@ -81,7 +81,10 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
|
||||
nothing (startup and memory measured unchanged); no CJK font installed means tofu, never a
|
||||
crash. The web build has no system fonts: built with craft-fonts and `FIGCRAFT_EMBED_CJK=1` it
|
||||
embeds Noto Sans CJK SC (+16 MB) and offers Japanese, Korean and Simplified Chinese; otherwise the language picker and
|
||||
System leave them out. No font file is in this repo.
|
||||
System leave them out. `FIGCRAFT_EMBED_SCRIPTS=Arab,Hebr` (script tags as the craft-fonts manifest
|
||||
lists them) embeds those scripts' faces as well, so an Arabic or Persian document draws in Noto Sans
|
||||
Arabic (+0.8 MB) instead of missing-glyph boxes (#1902); the picker's languages don't change. No
|
||||
font file is in this repo.
|
||||
|
||||
## Languages
|
||||
|
||||
@@ -91,7 +94,7 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
|
||||
| Simplified Chinese (Mandarin) | zh-Hans | 2,216 / 2,352 (94.2%, draft) | yes | CJK text and IME in documents; UI text in an installed Chinese face, ahead of Japanese ones (web: the `FIGCRAFT_EMBED_CJK` build); no Traditional Chinese catalog (zh-TW, zh-HK read English) | no | draft | 1–2 (after native review) |
|
||||
| Spanish | es, es-419 | 2,352 / 2,352 (100%, draft); es-419 overlays 104 Latin American wordings (draft) | yes | Latin | no | draft | 0–1 (after native review) |
|
||||
| Hindi | hi | 0 (0%) | no | Devanagari shaping works in documents; UI font fallback to check | no | none | 4–7 |
|
||||
| Arabic | ar | 1,857 / 1,857 UI strings, 620 command labels, 31 menu wordings (draft, counted 2026-10-28) | catalog complete; native review pending | Existing bidi/shaping and Arabic system fallback; web not offered (font budget excludes Arabic); panels do not mirror | no | draft | review + panel mirroring (not re-estimated) |
|
||||
| Arabic | ar | 1,857 / 1,857 UI strings, 620 command labels, 31 menu wordings (draft, counted 2026-10-28) | catalog complete; native review pending | Existing bidi/shaping and Arabic system fallback; web: documents draw in Noto Sans Arabic only in a build with `FIGCRAFT_EMBED_SCRIPTS=Arab` (the font budget leaves it out otherwise, #1902), the UI language not offered; panels do not mirror | no | draft | review + panel mirroring (not re-estimated) |
|
||||
| French | fr | 2,328 / 2,352 (99.0%, draft) | yes | Latin | no | draft | 1–2 (after native review) |
|
||||
| Portuguese | pt-BR | 2,331 / 2,352 (99.1%, draft) | yes | Latin | no | draft | 1–2 (after native review) |
|
||||
| Indonesian | id | 2,292 / 2,352 (97.4%, draft) | yes | Latin | no | draft | 1–2 (after native review) |
|
||||
@@ -125,6 +128,7 @@ Hindi and Arabic. Traditional Chinese is not planned (no catalog; its users see
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | patch | M10, #1902: the web build drew Persian and Arabic text as missing-glyph boxes. The browser has no system fonts, and `crates/text/build.rs` keeps the web catalog to Inter, Noto Sans Georgian, the wordmark and (with `FIGCRAFT_EMBED_CJK`) Noto Sans CJK, so craft-fonts' Noto Sans Arabic never reached it. `FIGCRAFT_EMBED_SCRIPTS=Arab,Hebr,…` (script tags as the manifest's scripts column lists them) now embeds those scripts' faces in a web build; the default build, its size and the UI languages on offer are unchanged. Test: `web_embeds_other_scripts_faces_only_when_asked` in the build script. |
|
||||
| 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 |
|
||||
|
||||
+3
-1
@@ -78,7 +78,9 @@ CJK and Thai text need their own fonts, which egui and Inter lack: the desktop a
|
||||
one when something on screen needs it, so layer and page names in those scripts show whatever the
|
||||
UI language (see `crates/ui-egui/src/cjk.rs`). The web build has no system fonts,
|
||||
so Japanese, Korean and Chinese appear there only in a build with craft-fonts and
|
||||
`FIGCRAFT_EMBED_CJK=1` (Noto Sans CJK SC, +16 MB); otherwise they are left out of the picker. A
|
||||
`FIGCRAFT_EMBED_CJK=1` (Noto Sans CJK SC, +16 MB); otherwise they are left out of the picker. The other
|
||||
scripts' faces (Noto Sans Arabic, Hebrew, Thai, Devanagari) join a web build only with
|
||||
`FIGCRAFT_EMBED_SCRIPTS=Arab,Hebr` (the manifest's script tags), for documents in those scripts (#1902). A
|
||||
Chinese UI puts its Chinese face first so Han takes Simplified Chinese shapes. Georgian is drawn in
|
||||
craft-fonts' Noto Sans Georgian, one of the built-in faces in `theme::install_fonts`, or else in an
|
||||
installed font on the desktop. Web builds keep it only when the craft-fonts they're built with has
|
||||
|
||||
Reference in new issue
Block a user