use std::cell::RefCell;
use std::collections::HashMap;
use parley::FontData;
use parley::PositionedLayoutItem;
use skrifa::GlyphId;
use skrifa::MetadataProvider;
use skrifa::instance::{LocationRef, Size};
use smudge_core::geometry::{Color, Point, Rect};
use smudge_core::paint::{FontBlob, GlyphRun, PositionedGlyph};
use crate::layout::TextLayout;
thread_local! {
static FONT_BLOBS: RefCell<HashMap<(u64, u32), FontBlob>> = RefCell::new(HashMap::new());
}
impl TextLayout {
pub fn glyph_runs_at(&self, origin: Point, color: Color) -> Vec<GlyphRun> {
glyph_runs(&self.layout, origin, color)
}
}
pub(crate) fn glyph_runs<B: parley::Brush>(
layout: &parley::Layout<B>,
origin: Point,
color: Color,
) -> Vec<GlyphRun> {
layout
.lines()
.flat_map(|line| line.items())
.filter_map(|item| match item {
PositionedLayoutItem::GlyphRun(run) => Some(run),
PositionedLayoutItem::InlineBox(_) => None,
})
.map(|run| {
let parley_run = run.run();
let font = font_blob(parley_run.font());
let size = parley_run.font_size();
let metrics = skrifa::FontRef::from_index(
parley_run.font().data.as_ref(),
parley_run.font().index,
)
.ok()
.map(|font| font.glyph_metrics(Size::new(size), LocationRef::default()));
let mut glyphs = Vec::new();
let mut ink = Rect::default();
for glyph in run.positioned_glyphs() {
let position = PositionedGlyph {
id: glyph.id,
x: origin.x + glyph.x,
y: origin.y + glyph.y,
};
if let Some(bounds) = metrics
.as_ref()
.and_then(|metrics| metrics.bounds(GlyphId::new(glyph.id)))
{
ink = ink.union(Rect::new(
position.x + bounds.x_min,
position.y - bounds.y_max,
position.x + bounds.x_max,
position.y - bounds.y_min,
));
}
glyphs.push(position);
}
GlyphRun {
font,
size,
color,
glyphs,
ink_bounds: expand(ink, 1.0),
}
})
.collect()
}
fn font_blob(font: &FontData) -> FontBlob {
FONT_BLOBS.with(|cache| {
cache
.borrow_mut()
.entry((font.data.id(), font.index))
.or_insert_with(|| {
let (data, id) = font.data.clone().into_raw_parts();
FontBlob {
id,
data,
index: font.index,
}
})
.clone()
})
}
fn expand(rect: Rect, by: f32) -> Rect {
if rect.is_empty() {
rect
} else {
Rect::new(rect.x0 - by, rect.y0 - by, rect.x1 + by, rect.y1 + by)
}
}
#[cfg(test)]
#[path = "glyphs_tests.rs"]
mod tests;