use vello::kurbo::Affine;
use vello::peniko::{Brush, Fill, FontData};
use vello::{Glyph, Scene};
use skrifa::{MetadataProvider, raw::{FileRef, FontRef}};
use vello::peniko::color::AlphaColor;
use vello::peniko::color::Srgb;
pub type Color = AlphaColor<Srgb>;
use uzor::fonts::FontFamily;
use crate::context::{get_cached_font, get_fallback_fonts};
pub(crate) fn get_text_font(bold: bool) -> &'static FontData {
get_cached_font(FontFamily::Roboto, bold, false)
}
pub(crate) fn font_data_to_ref(font: &FontData) -> Option<FontRef<'_>> {
let file_ref = FileRef::new(font.data.as_ref()).ok()?;
match file_ref {
FileRef::Font(f) => Some(f),
FileRef::Collection(c) => c.get(font.index).ok(),
}
}
struct ResolvedGlyph {
font_index: Option<usize>,
glyph_id: u32,
x: f32,
advance: f32,
}
fn resolve_glyphs(
text: &str,
primary_ref: &FontRef<'_>,
font_size: f32,
) -> Vec<ResolvedGlyph> {
let size = skrifa::instance::Size::new(font_size);
let var_loc = skrifa::instance::LocationRef::default();
let primary_charmap = primary_ref.charmap();
let primary_metrics = primary_ref.glyph_metrics(size, var_loc);
let fallbacks = get_fallback_fonts();
let mut pen_x = 0.0f32;
let mut result = Vec::with_capacity(text.len());
for ch in text.chars() {
let primary_gid = primary_charmap.map(ch).unwrap_or_default();
if primary_gid != skrifa::GlyphId::new(0) {
let adv = primary_metrics.advance_width(primary_gid).unwrap_or_default();
result.push(ResolvedGlyph {
font_index: None,
glyph_id: primary_gid.to_u32(),
x: pen_x,
advance: adv,
});
pen_x += adv;
} else {
let mut found_index = None;
let mut found_gid = primary_gid;
let mut found_adv = primary_metrics.advance_width(primary_gid).unwrap_or_default();
for (idx, fb_font) in fallbacks.iter().enumerate() {
if let Some(fb_ref) = font_data_to_ref(fb_font) {
let fb_gid = fb_ref.charmap().map(ch).unwrap_or_default();
if fb_gid != skrifa::GlyphId::new(0) {
let fb_metrics = fb_ref.glyph_metrics(size, var_loc);
found_adv = fb_metrics.advance_width(fb_gid).unwrap_or_default();
found_gid = fb_gid;
found_index = Some(idx);
break;
}
}
}
result.push(ResolvedGlyph {
font_index: found_index,
glyph_id: found_gid.to_u32(),
x: pen_x,
advance: found_adv,
});
pen_x += found_adv;
}
}
result
}
fn total_width(glyphs: &[ResolvedGlyph]) -> f32 {
glyphs.last().map_or(0.0, |g| g.x + g.advance)
}
const COLOR_EMOJI_FALLBACK_IDX: usize = 2;
fn draw_resolved(
scene: &mut Scene,
glyphs: &[ResolvedGlyph],
primary_font: &FontData,
fallbacks: &[FontData],
font_size: f32,
transform: Affine,
color: Color,
) {
if glyphs.is_empty() {
return;
}
let foreground_brush = Brush::Solid(color);
let emoji_brush = Brush::Solid(vello::peniko::color::palette::css::WHITE);
let mut i = 0;
while i < glyphs.len() {
let run_font_index = glyphs[i].font_index;
let run_start = i;
while i < glyphs.len() && glyphs[i].font_index == run_font_index {
i += 1;
}
let run = &glyphs[run_start..i];
let is_color_emoji = run_font_index == Some(COLOR_EMOJI_FALLBACK_IDX);
let font = match run_font_index {
None => primary_font,
Some(idx) if idx < fallbacks.len() => &fallbacks[idx],
_ => primary_font,
};
let brush = if is_color_emoji { &emoji_brush } else { &foreground_brush };
scene
.draw_glyphs(font)
.font_size(font_size)
.transform(transform)
.brush(brush)
.hint(!is_color_emoji)
.draw(
Fill::NonZero,
run.iter().map(|g| Glyph {
id: g.glyph_id,
x: g.x,
y: 0.0,
}),
);
}
}
pub fn draw_text_to_scene(
scene: &mut Scene,
text: &str,
x: f64,
y: f64,
font_size: f32,
bold: bool,
color: Color,
) {
if text.is_empty() {
return;
}
let primary_font = get_text_font(bold);
let primary_ref = match font_data_to_ref(primary_font) {
Some(f) => f,
None => return,
};
let resolved = resolve_glyphs(text, &primary_ref, font_size);
let transform = Affine::translate((x, y));
let fallbacks = get_fallback_fonts();
draw_resolved(scene, &resolved, primary_font, fallbacks, font_size, transform, color);
}
pub fn measure_text_width(text: &str, font_size: f32, bold: bool) -> f64 {
let primary_font = get_text_font(bold);
let primary_ref = match font_data_to_ref(primary_font) {
Some(f) => f,
None => return text.len() as f64 * font_size as f64 * 0.6,
};
let resolved = resolve_glyphs(text, &primary_ref, font_size);
total_width(&resolved) as f64
}