From 1a9eeb9e1b50e748698cdd5951382f989dc2e408 Mon Sep 17 00:00:00 2001 From: JonRC Date: Mon, 2 Nov 2026 12:33:02 -0300 Subject: [PATCH] Lottie: warn when embedded glyphs (`chars`) are not imported A Lottie file can carry glyph outlines in `chars` for players that lack the fonts. The import drops them and sets the text in installed fonts, which can draw it in a fallback font. It now says so in the warnings, naming the font families the glyphs were for. Fixes #2844. --- crates/lottie/src/import.rs | 11 +++++++++++ crates/lottie/src/tests.rs | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/crates/lottie/src/import.rs b/crates/lottie/src/import.rs index 6d7cc1f5a..cc1bacea4 100644 --- a/crates/lottie/src/import.rs +++ b/crates/lottie/src/import.rs @@ -135,6 +135,17 @@ pub fn import( cx.fonts.push((n, fam, st)); } } + // Glyph outlines embedded for players without the fonts. Text is set in installed fonts. + if let Some(chars) = doc.get("chars").and_then(Json::as_array).filter(|c| !c.is_empty()) { + let mut fams: Vec<&str> = vec![]; + for fam in chars.iter().filter_map(|c| s(c, "fFamily")) { + if !fams.contains(&fam) { + fams.push(fam); + } + } + let of = if fams.is_empty() { String::new() } else { format!(" of {}", fams.join(", ")) }; + cx.im.warn(format!("embedded glyphs (`chars`){of} are not imported; text uses the installed fonts")); + } let (w, h) = (w.round().max(1.0) as u32, h.round().max(1.0) as u32); let duration = tb.tick(op); // Pass 1: asset items (precomps empty for now, images stored). diff --git a/crates/lottie/src/tests.rs b/crates/lottie/src/tests.rs index 83c81e242..d8fcc5661 100644 --- a/crates/lottie/src/tests.rs +++ b/crates/lottie/src/tests.rs @@ -520,6 +520,17 @@ fn zero_time_stretch_is_not_imported() { assert!(imp.warnings.iter().any(|w| w.contains("time stretch")), "{:?}", imp.warnings); } +#[test] +fn embedded_glyphs_are_reported_when_dropped() { + let glyph = |ch: &str, fam: &str| json!({"ch": ch, "size": 100, "style": "Regular", "w": 60, "fFamily": fam, "data": {"shapes": []}}); + let doc = json!({"w": 20, "h": 20, "fr": 30, "ip": 0, "op": 30, "assets": [], "layers": [], + "fonts": {"list": [{"fName": "Sans-Regular", "fFamily": "Sans", "fStyle": "Regular"}]}, + "chars": [glyph("A", "Sans"), glyph("B", "Sans"), glyph("C", "Serif")]}); + let mut q = Project::default(); + let imp = import(&mut q, doc.to_string().as_bytes(), "Imported", "", &mut |_| None).unwrap(); + assert_eq!(imp.warnings, ["embedded glyphs (`chars`) of Sans, Serif are not imported; text uses the installed fonts"]); +} + #[test] fn precomp_with_time_remap() { let (mut p, cid) = setup();