use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::hash::{Hash, Hasher};
use cosmic_text::{CacheKey, Command};
use lyon_path::math::Point;
use lyon_path::Path;
use lyon_tessellation::{
FillOptions, FillTessellator, LineCap, LineJoin, StrokeOptions, StrokeTessellator,
VertexBuffers, geometry_builder::simple_builder,
};
use crate::slug::outline::commands_to_path;
use crate::slug::path_effect::apply_path_effect;
const EVICT_FRAMES: u64 = 120;
#[derive(Clone, PartialEq, Eq)]
pub struct StrokeTessKey {
width_bits: u32,
cap: u8,
join: u8,
miter_bits: u32,
path_effect_hash: u64,
}
impl StrokeTessKey {
pub fn new(
width: f32,
cap: repose_core::StrokeCap,
join: repose_core::StrokeJoin,
miter: f32,
path_effect: &Option<repose_core::PathEffect>,
) -> Self {
Self {
width_bits: width.to_bits(),
cap: cap as u8,
join: join as u8,
miter_bits: miter.to_bits(),
path_effect_hash: hash_path_effect(path_effect),
}
}
}
impl Hash for StrokeTessKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.width_bits.hash(state);
self.cap.hash(state);
self.join.hash(state);
self.miter_bits.hash(state);
self.path_effect_hash.hash(state);
}
}
fn hash_path_effect(effect: &Option<repose_core::PathEffect>) -> u64 {
use repose_core::PathEffect;
match effect {
None => 0,
Some(PathEffect::Corner { radius }) => {
1u64.wrapping_mul(31).wrapping_add(radius.to_bits() as u64)
}
Some(PathEffect::Dash { intervals, phase }) => {
let mut h: u64 = 2;
for &v in intervals {
h = h.wrapping_mul(31).wrapping_add(v.to_bits() as u64);
}
h.wrapping_mul(31).wrapping_add(phase.to_bits() as u64)
}
}
}
pub struct CachedTessGlyph {
pub fill_vertices: Option<Vec<[f32; 2]>>,
pub stroke_variants: HashMap<StrokeTessKey, Vec<[f32; 2]>>,
pub last_used: u64,
}
pub struct GlyphSlugCache {
map: HashMap<CacheKey, CachedTessGlyph>,
frame: u64,
}
impl GlyphSlugCache {
pub fn new() -> Self {
Self {
map: HashMap::new(),
frame: 0,
}
}
pub fn next_frame(&mut self) {
self.evict_stale();
self.frame += 1;
}
pub fn contains(&self, key: &CacheKey) -> bool {
self.map.contains_key(key)
}
fn tessellate_fill<'a>(
font_size: f32,
commands: &[Command],
entry: Entry<'a, CacheKey, CachedTessGlyph>,
frame: u64,
) -> Option<&'a CachedTessGlyph> {
match entry {
Entry::Occupied(mut e) => {
let glyph = e.get_mut();
glyph.last_used = frame;
if glyph.fill_vertices.is_none() {
let path = commands_to_path(commands, font_size)?;
let mut tess = FillTessellator::new();
let tolerance = (0.5 / font_size).max(0.001);
let mut buffers: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate_path(
&path,
&FillOptions::default().with_tolerance(tolerance),
&mut simple_builder(&mut buffers),
)
.ok()?;
if buffers.indices.is_empty() {
return None;
}
let num_verts = buffers.indices.len();
let mut vertices = Vec::with_capacity(num_verts);
for &i in &buffers.indices {
let v = &buffers.vertices[i as usize];
vertices.push([v.x, v.y]);
}
glyph.fill_vertices = Some(vertices);
}
Some(e.into_mut())
}
Entry::Vacant(e) => {
let path = commands_to_path(commands, font_size)?;
let mut tess = FillTessellator::new();
let tolerance = (0.5 / font_size).max(0.001);
let mut buffers: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate_path(
&path,
&FillOptions::default().with_tolerance(tolerance),
&mut simple_builder(&mut buffers),
)
.ok()?;
if buffers.indices.is_empty() {
return None;
}
let num_verts = buffers.indices.len();
let mut vertices = Vec::with_capacity(num_verts);
for &i in &buffers.indices {
let v = &buffers.vertices[i as usize];
vertices.push([v.x, v.y]);
}
Some(e.insert(CachedTessGlyph {
fill_vertices: Some(vertices),
stroke_variants: HashMap::new(),
last_used: frame,
}))
}
}
}
pub fn get_or_insert(
&mut self,
key: CacheKey,
font_size: f32,
commands: &[Command],
) -> Option<&CachedTessGlyph> {
Self::tessellate_fill(font_size, commands, self.map.entry(key), self.frame)
}
pub fn get_or_insert_stroke(
&mut self,
key: CacheKey,
font_size: f32,
commands: &[Command],
width_em: f32,
cap: repose_core::StrokeCap,
join: repose_core::StrokeJoin,
miter: f32,
path_effect: &Option<repose_core::PathEffect>,
) -> Option<&CachedTessGlyph> {
let tess_key =
StrokeTessKey::new(width_em, cap, join, miter, path_effect);
let lyon_cap = match cap {
repose_core::StrokeCap::Butt => LineCap::Butt,
repose_core::StrokeCap::Round => LineCap::Round,
repose_core::StrokeCap::Square => LineCap::Square,
};
let lyon_join = match join {
repose_core::StrokeJoin::Miter => LineJoin::Miter,
repose_core::StrokeJoin::Round => LineJoin::Round,
repose_core::StrokeJoin::Bevel => LineJoin::Bevel,
};
let build_path = |font_size| -> Option<Path> {
let path = commands_to_path(commands, font_size)?;
if let Some(effect) = path_effect {
let tolerance = (0.5 / font_size).max(0.001);
Some(apply_path_effect(&path, effect, tolerance))
} else {
Some(path)
}
};
match self.map.entry(key) {
Entry::Occupied(mut e) => {
let glyph = e.get_mut();
glyph.last_used = self.frame;
if !glyph.stroke_variants.contains_key(&tess_key) {
let path = build_path(font_size)?;
let mut tess = StrokeTessellator::new();
let tolerance = (0.5 / font_size).max(0.001);
let mut buffers: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate_path(
&path,
&StrokeOptions::default()
.with_tolerance(tolerance)
.with_line_width(width_em)
.with_line_cap(lyon_cap)
.with_line_join(lyon_join)
.with_miter_limit(miter),
&mut simple_builder(&mut buffers),
)
.ok()?;
if buffers.indices.is_empty() {
return None;
}
let num_verts = buffers.indices.len();
let mut vertices = Vec::with_capacity(num_verts);
for &i in &buffers.indices {
let v = &buffers.vertices[i as usize];
vertices.push([v.x, v.y]);
}
glyph.stroke_variants.insert(tess_key, vertices);
}
Some(e.into_mut())
}
Entry::Vacant(e) => {
let path = build_path(font_size)?;
let mut tess = StrokeTessellator::new();
let tolerance = (0.5 / font_size).max(0.001);
let mut buffers: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate_path(
&path,
&StrokeOptions::default()
.with_tolerance(tolerance)
.with_line_width(width_em)
.with_line_cap(lyon_cap)
.with_line_join(lyon_join)
.with_miter_limit(miter),
&mut simple_builder(&mut buffers),
)
.ok()?;
if buffers.indices.is_empty() {
return None;
}
let num_verts = buffers.indices.len();
let mut vertices = Vec::with_capacity(num_verts);
for &i in &buffers.indices {
let v = &buffers.vertices[i as usize];
vertices.push([v.x, v.y]);
}
let mut variants = HashMap::new();
variants.insert(tess_key, vertices);
Some(e.insert(CachedTessGlyph {
fill_vertices: None,
stroke_variants: variants,
last_used: self.frame,
}))
}
}
}
pub fn get(&self, key: &CacheKey) -> Option<&CachedTessGlyph> {
self.map.get(key)
}
pub fn touch(&mut self, key: &CacheKey) {
if let Some(entry) = self.map.get_mut(key) {
entry.last_used = self.frame;
}
}
fn evict_stale(&mut self) {
let frame = self.frame;
self.map
.retain(|_, e| frame.wrapping_sub(e.last_used) < EVICT_FRAMES);
}
}