blob: 3469c13b7b93c973ecf74277ae82f789312b16dd [file] [log] [blame] [edit]
// Copyright 2022 The Vello authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use std::collections::HashMap;
use super::{Encoding, StreamOffsets};
use fello::scale::{Pen, Scaler};
use fello::GlyphId;
use peniko::{
kurbo::{BezPath, Shape},
Fill, Style,
};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
pub struct GlyphKey {
pub font_id: u64,
pub font_index: u32,
pub glyph_id: u32,
pub font_size: u32,
pub hint: bool,
}
#[derive(Default)]
pub struct GlyphCache {
pub encoding: Encoding,
glyphs: HashMap<GlyphKey, CachedRange>,
}
impl GlyphCache {
pub fn clear(&mut self) {
self.encoding.reset(true);
self.glyphs.clear();
}
pub fn get_or_insert(
&mut self,
key: GlyphKey,
style: &Style,
scaler: &mut Scaler,
) -> Option<CachedRange> {
let is_var = !scaler.normalized_coords().is_empty();
let encoding_cache = &mut self.encoding;
let mut encode_glyph = || {
let start = encoding_cache.stream_offsets();
let fill = match style {
Style::Fill(fill) => *fill,
Style::Stroke(_) => Fill::NonZero,
};
encoding_cache.encode_fill_style(fill);
let mut path = encoding_cache.encode_path(true);
match style {
Style::Fill(_) => {
scaler
.outline(GlyphId::new(key.glyph_id as u16), &mut path)
.ok()?;
}
Style::Stroke(stroke) => {
const STROKE_TOLERANCE: f64 = 0.01;
let mut pen = BezPathPen::default();
scaler
.outline(GlyphId::new(key.glyph_id as u16), &mut pen)
.ok()?;
let stroked = peniko::kurbo::stroke(
pen.0.path_elements(STROKE_TOLERANCE),
stroke,
&Default::default(),
STROKE_TOLERANCE,
);
path.shape(&stroked);
}
}
if path.finish(false) == 0 {
return None;
}
let end = encoding_cache.stream_offsets();
Some(CachedRange { start, end })
};
// For now, only cache non-zero filled, non-variable glyphs so we don't need to keep style
// as part of the key.
let range = if matches!(style, Style::Fill(Fill::NonZero)) && !is_var {
use std::collections::hash_map::Entry;
match self.glyphs.entry(key) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => *entry.insert(encode_glyph()?),
}
} else {
encode_glyph()?
};
Some(range)
}
}
#[derive(Copy, Clone, Default, Debug)]
pub struct CachedRange {
pub start: StreamOffsets,
pub end: StreamOffsets,
}
impl CachedRange {
pub fn len(&self) -> StreamOffsets {
StreamOffsets {
path_tags: self.end.path_tags - self.start.path_tags,
path_data: self.end.path_data - self.start.path_data,
draw_tags: self.end.draw_tags - self.start.draw_tags,
draw_data: self.end.draw_data - self.start.draw_data,
transforms: self.end.transforms - self.start.transforms,
linewidths: self.end.linewidths - self.start.linewidths,
}
}
}
// A wrapper newtype so we can implement the `Pen` trait. Arguably, this could
// be in the fello crate, but will go away when we do GPU-side stroking.
#[derive(Default)]
struct BezPathPen(BezPath);
impl Pen for BezPathPen {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to((x as f64, y as f64));
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to((x as f64, y as f64));
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.0
.quad_to((cx0 as f64, cy0 as f64), (x as f64, y as f64));
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.0.curve_to(
(cx0 as f64, cy0 as f64),
(cx1 as f64, cy1 as f64),
(x as f64, y as f64),
);
}
fn close(&mut self) {
self.0.close_path();
}
}