🎉 Implement font fallback to support multiple languages

This commit is contained in:
Elena Torro 2025-06-03 08:21:15 +02:00
parent 9733c41ae4
commit c40de5fb87
7 changed files with 179 additions and 38 deletions

View file

@ -1,4 +1,5 @@
use skia_safe::{self as skia, textlayout, Font, FontMgr};
use std::collections::HashSet;
use crate::shapes::{FontFamily, FontStyle};
use crate::uuid::Uuid;
@ -21,6 +22,7 @@ pub struct FontStore {
font_provider: textlayout::TypefaceFontProvider,
font_collection: textlayout::FontCollection,
debug_font: Font,
fallback_fonts: HashSet<String>,
}
impl FontStore {
@ -41,6 +43,7 @@ impl FontStore {
font_provider,
font_collection,
debug_font,
fallback_fonts: HashSet::new(),
}
}
@ -61,6 +64,7 @@ impl FontStore {
family: FontFamily,
font_data: &[u8],
is_emoji: bool,
is_fallback: bool,
) -> Result<(), String> {
if self.has_family(&family) {
return Ok(());
@ -80,6 +84,11 @@ impl FontStore {
self.font_provider.register_typeface(typeface, font_name);
self.font_collection.clear_caches();
if is_fallback {
self.fallback_fonts.insert(alias);
}
Ok(())
}
@ -87,6 +96,10 @@ impl FontStore {
let serialized = format!("{}", family);
self.font_provider.family_names().any(|x| x == serialized)
}
pub fn get_fallback(&self) -> &HashSet<String> {
&self.fallback_fonts
}
}
fn load_default_provider(font_mgr: &FontMgr) -> skia::textlayout::TypefaceFontProvider {

View file

@ -7,10 +7,11 @@ use skia_safe::{
paint::Paint,
textlayout::{FontCollection, ParagraphBuilder, ParagraphStyle},
};
use std::collections::HashSet;
use super::FontFamily;
use crate::shapes::{self, merge_fills, set_paint_fill, Stroke, StrokeKind};
use crate::utils::uuid_from_u32;
use crate::utils::{get_fallback_fonts, uuid_from_u32};
use crate::wasm::fills::parse_fills_from_bytes;
use crate::Uuid;
@ -94,6 +95,7 @@ impl TextContent {
}
pub fn to_paragraphs(&self, fonts: &FontCollection) -> Vec<Vec<skia::textlayout::Paragraph>> {
let fallback_fonts = get_fallback_fonts();
let mut paragraph_group = Vec::new();
let paragraphs = self
.paragraphs
@ -102,7 +104,7 @@ impl TextContent {
let paragraph_style = p.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &p.children {
let text_style = leaf.to_style(p, &self.bounds); // FIXME
let text_style = leaf.to_style(p, &self.bounds, fallback_fonts); // FIXME
let text = leaf.apply_text_transform();
builder.push_style(&text_style);
builder.add_text(&text);
@ -121,6 +123,7 @@ impl TextContent {
bounds: &Rect,
fonts: &FontCollection,
) -> Vec<Vec<skia::textlayout::Paragraph>> {
let fallback_fonts = get_fallback_fonts();
let mut paragraph_group = Vec::new();
let stroke_paints = get_text_stroke_paints(stroke, bounds);
@ -130,7 +133,8 @@ impl TextContent {
let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &paragraph.children {
let stroke_style = leaf.to_stroke_style(paragraph, &stroke_paint);
let stroke_style =
leaf.to_stroke_style(paragraph, &stroke_paint, fallback_fonts);
let text: String = leaf.apply_text_transform();
builder.push_style(&stroke_style);
builder.add_text(&text);
@ -336,6 +340,7 @@ impl TextLeaf {
&self,
paragraph: &Paragraph,
content_bounds: &Rect,
fallback_fonts: &HashSet<String>,
) -> skia::textlayout::TextStyle {
let mut style = skia::textlayout::TextStyle::default();
@ -359,14 +364,18 @@ impl TextLeaf {
3 => skia::textlayout::TextDecoration::OVERLINE,
_ => skia::textlayout::TextDecoration::NO_DECORATION,
});
// FIXME
// FIXME fix decoration styles
style.set_decoration_color(paint.color());
style.set_font_families(&[
let mut font_families = vec![
self.serialized_font_family(),
default_font(),
DEFAULT_EMOJI_FONT.to_string(),
]);
];
font_families.extend(fallback_fonts.iter().cloned());
style.set_font_families(&font_families);
style
}
@ -375,8 +384,9 @@ impl TextLeaf {
&self,
paragraph: &Paragraph,
stroke_paint: &Paint,
fallback_fonts: &HashSet<String>,
) -> skia::textlayout::TextStyle {
let mut style = self.to_style(paragraph, &Rect::default());
let mut style = self.to_style(paragraph, &Rect::default(), fallback_fonts);
style.set_foreground_paint(stroke_paint);
style.set_font_size(self.font_size);
style.set_letter_spacing(paragraph.letter_spacing);

View file

@ -2,6 +2,7 @@ use crate::skia::Image;
use crate::uuid::Uuid;
use crate::with_state;
use crate::STATE;
use std::collections::HashSet;
pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid {
let hi: u64 = ((a as u64) << 32) | b as u64;
@ -25,3 +26,8 @@ pub fn uuid_from_u32(id: [u32; 4]) -> Uuid {
pub fn get_image(image_id: &Uuid) -> Option<&Image> {
with_state!(state, { state.render_state().images.get(image_id) })
}
// FIXME: move to a different place ?
pub fn get_fallback_fonts() -> &'static HashSet<String> {
with_state!(state, { state.render_state().fonts().get_fallback() })
}

View file

@ -14,19 +14,19 @@ pub extern "C" fn store_font(
weight: u32,
style: u8,
is_emoji: bool,
is_fallback: bool,
) {
with_state!(state, {
let id = uuid_from_u32_quartet(a, b, c, d);
let font_bytes = mem::bytes();
let family = FontFamily::new(id, weight, style.into());
let res = state
let _ = state
.render_state()
.fonts_mut()
.add(family, &font_bytes, is_emoji);
.add(family, &font_bytes, is_emoji, is_fallback);
if let Err(msg) = res {
eprintln!("{}", msg);
}
mem::free_bytes();
});
}