1use crate::kurbo::{Affine, BezPath, Vec2};
7use crate::peniko::Font;
8use alloc::boxed::Box;
9use core::fmt::{Debug, Formatter};
10use skrifa::instance::{LocationRef, Size};
11use skrifa::outline::DrawSettings;
12use skrifa::raw::TableProvider;
13use skrifa::{FontRef, OutlineGlyphCollection};
14use skrifa::{
15 GlyphId, MetadataProvider,
16 outline::{HintingInstance, HintingOptions, OutlinePen},
17};
18
19use crate::colr::convert_bounding_box;
20use crate::encode::x_y_advances;
21use crate::kurbo::Rect;
22use crate::pixmap::Pixmap;
23use skrifa::bitmap::{BitmapData, BitmapFormat, BitmapStrikes, Origin};
24
25#[cfg(not(feature = "std"))]
26use peniko::kurbo::common::FloatFuncs as _;
27
28#[derive(Copy, Clone, Default, Debug)]
30pub struct Glyph {
31 pub id: u32,
36 pub x: f32,
38 pub y: f32,
40}
41
42#[derive(Debug)]
44pub enum GlyphType<'a> {
45 Outline(OutlineGlyph<'a>),
47 Bitmap(BitmapGlyph),
49 Colr(Box<ColorGlyph<'a>>),
51}
52
53#[derive(Debug)]
55pub struct PreparedGlyph<'a> {
56 pub glyph_type: GlyphType<'a>,
58 pub transform: Affine,
60}
61
62#[derive(Debug)]
64pub struct OutlineGlyph<'a> {
65 pub path: &'a BezPath,
67}
68
69#[derive(Debug)]
71pub struct BitmapGlyph {
72 pub pixmap: Pixmap,
74 pub area: Rect,
76}
77
78pub struct ColorGlyph<'a> {
84 pub(crate) skrifa_glyph: skrifa::color::ColorGlyph<'a>,
85 pub(crate) location: LocationRef<'a>,
86 pub(crate) font_ref: &'a FontRef<'a>,
87 pub(crate) draw_transform: Affine,
88 pub area: Rect,
91 pub pix_width: u16,
93 pub pix_height: u16,
95}
96
97impl Debug for ColorGlyph<'_> {
98 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
99 write!(f, "ColorGlyph")
100 }
101}
102
103pub trait GlyphRenderer {
105 fn fill_glyph(&mut self, glyph: PreparedGlyph<'_>);
107
108 fn stroke_glyph(&mut self, glyph: PreparedGlyph<'_>);
110}
111
112#[derive(Debug)]
114#[must_use = "Methods on the builder don't do anything until `render` is called."]
115pub struct GlyphRunBuilder<'a, T: GlyphRenderer + 'a> {
116 run: GlyphRun<'a>,
117 renderer: &'a mut T,
118}
119
120impl<'a, T: GlyphRenderer + 'a> GlyphRunBuilder<'a, T> {
121 pub fn new(font: Font, transform: Affine, renderer: &'a mut T) -> Self {
123 Self {
124 run: GlyphRun {
125 font,
126 font_size: 16.0,
127 transform,
128 glyph_transform: None,
129 hint: true,
130 normalized_coords: &[],
131 },
132 renderer,
133 }
134 }
135
136 pub fn font_size(mut self, size: f32) -> Self {
138 self.run.font_size = size;
139 self
140 }
141
142 pub fn glyph_transform(mut self, transform: Affine) -> Self {
145 self.run.glyph_transform = Some(transform);
146 self
147 }
148
149 pub fn hint(mut self, hint: bool) -> Self {
154 self.run.hint = hint;
155 self
156 }
157
158 pub fn normalized_coords(mut self, coords: &'a [NormalizedCoord]) -> Self {
160 self.run.normalized_coords = bytemuck::cast_slice(coords);
161 self
162 }
163
164 pub fn fill_glyphs(self, glyphs: impl Iterator<Item = Glyph>) {
166 self.render(glyphs, Style::Fill);
167 }
168
169 pub fn stroke_glyphs(self, glyphs: impl Iterator<Item = Glyph>) {
171 self.render(glyphs, Style::Stroke);
172 }
173
174 fn render(self, glyphs: impl Iterator<Item = Glyph>, style: Style) {
175 let font_ref =
176 FontRef::from_index(self.run.font.data.as_ref(), self.run.font.index).unwrap();
177
178 let upem: f32 = font_ref.head().map(|h| h.units_per_em()).unwrap().into();
179
180 let outlines = font_ref.outline_glyphs();
181 let color_glyphs = font_ref.color_glyphs();
182 let bitmaps = font_ref.bitmap_strikes();
183
184 let PreparedGlyphRun {
185 transform: initial_transform,
186 size,
187 normalized_coords,
188 hinting_instance,
189 } = prepare_glyph_run(&self.run, &outlines);
190
191 let render_glyph = match style {
192 Style::Fill => GlyphRenderer::fill_glyph,
193 Style::Stroke => GlyphRenderer::stroke_glyph,
194 };
195
196 let mut outline_path = OutlinePath::new();
198
199 for glyph in glyphs {
200 let bitmap_data = bitmaps
201 .glyph_for_size(Size::new(self.run.font_size), GlyphId::new(glyph.id))
202 .and_then(|g| match g.data {
203 #[cfg(feature = "png")]
204 BitmapData::Png(data) => Pixmap::from_png(data).ok().map(|d| (g, d)),
205 #[cfg(not(feature = "png"))]
206 BitmapData::Png(_) => None,
207 BitmapData::Bgra(_) => None,
210 BitmapData::Mask(_) => None,
211 });
212
213 let (glyph_type, transform) =
214 if let Some(color_glyph) = color_glyphs.get(GlyphId::new(glyph.id)) {
215 prepare_colr_glyph(
216 &font_ref,
217 glyph,
218 self.run.font_size,
219 upem,
220 initial_transform,
221 color_glyph,
222 normalized_coords,
223 )
224 } else if let Some((bitmap_glyph, pixmap)) = bitmap_data {
225 prepare_bitmap_glyph(
226 &bitmaps,
227 glyph,
228 pixmap,
229 self.run.font_size,
230 upem,
231 initial_transform,
232 bitmap_glyph,
233 )
234 } else {
235 let Some(outline) = outlines.get(GlyphId::new(glyph.id)) else {
236 continue;
237 };
238
239 prepare_outline_glyph(
240 glyph,
241 size,
242 initial_transform,
243 self.run.transform,
244 &mut outline_path,
245 &outline,
246 hinting_instance.as_ref(),
247 normalized_coords,
248 )
249 };
250
251 let prepared_glyph = PreparedGlyph {
252 glyph_type,
253 transform,
254 };
255
256 render_glyph(self.renderer, prepared_glyph);
257 }
258 }
259}
260
261fn prepare_outline_glyph<'a>(
262 glyph: Glyph,
263 size: Size,
264 initial_transform: Affine,
266 run_transform: Affine,
268 path: &'a mut OutlinePath,
269 outline_glyph: &skrifa::outline::OutlineGlyph<'a>,
270 hinting_instance: Option<&HintingInstance>,
271 normalized_coords: &[skrifa::instance::NormalizedCoord],
272) -> (GlyphType<'a>, Affine) {
273 let draw_settings = if let Some(hinting_instance) = hinting_instance {
274 DrawSettings::hinted(hinting_instance, false)
275 } else {
276 DrawSettings::unhinted(size, normalized_coords)
277 };
278
279 path.0.truncate(0);
280 let _ = outline_glyph.draw(draw_settings, path);
281
282 let [a, b, c, d, _, _] = run_transform.as_coeffs();
289 let translation = Vec2::new(
290 a * f64::from(glyph.x) + c * f64::from(glyph.y),
291 b * f64::from(glyph.x) + d * f64::from(glyph.y),
292 );
293
294 let mut final_transform = initial_transform
297 .then_translate(translation)
298 .pre_scale_non_uniform(1.0, -1.0)
301 .as_coeffs();
302
303 if hinting_instance.is_some() {
304 final_transform[5] = final_transform[5].round();
305 }
306
307 (
308 GlyphType::Outline(OutlineGlyph { path: &path.0 }),
309 Affine::new(final_transform),
310 )
311}
312
313fn prepare_bitmap_glyph<'a>(
314 bitmaps: &BitmapStrikes<'_>,
315 glyph: Glyph,
316 pixmap: Pixmap,
317 font_size: f32,
318 upem: f32,
319 initial_transform: Affine,
320 bitmap_glyph: skrifa::bitmap::BitmapGlyph<'a>,
321) -> (GlyphType<'a>, Affine) {
322 let x_scale_factor = font_size / bitmap_glyph.ppem_x;
323 let y_scale_factor = font_size / bitmap_glyph.ppem_y;
324 let font_units_to_size = font_size / upem;
325
326 let bearing_y = if bitmap_glyph.bearing_y == 0.0 && bitmaps.format() == Some(BitmapFormat::Sbix)
332 {
333 100.0
334 } else {
335 bitmap_glyph.bearing_y
336 };
337
338 let origin_shift = match bitmap_glyph.placement_origin {
339 Origin::TopLeft => Vec2::default(),
340 Origin::BottomLeft => Vec2 {
341 x: 0.,
342 y: -f64::from(pixmap.height()),
343 },
344 };
345
346 let transform = initial_transform
347 .pre_translate(Vec2::new(glyph.x.into(), glyph.y.into()))
348 .pre_translate(Vec2 {
350 x: (-bitmap_glyph.bearing_x * font_units_to_size).into(),
351 y: (bearing_y * font_units_to_size).into(),
352 })
353 .pre_scale_non_uniform(f64::from(x_scale_factor), f64::from(y_scale_factor))
355 .pre_translate(Vec2 {
357 x: (-bitmap_glyph.inner_bearing_x).into(),
358 y: (-bitmap_glyph.inner_bearing_y).into(),
359 })
360 .pre_translate(origin_shift);
361
362 let area = Rect::new(
365 0.0,
366 0.0,
367 f64::from(pixmap.width()),
368 f64::from(pixmap.height()),
369 );
370
371 (GlyphType::Bitmap(BitmapGlyph { pixmap, area }), transform)
372}
373
374fn prepare_colr_glyph<'a>(
375 font_ref: &'a FontRef<'a>,
376 glyph: Glyph,
377 font_size: f32,
378 upem: f32,
379 run_transform: Affine,
380 color_glyph: skrifa::color::ColorGlyph<'a>,
381 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
382) -> (GlyphType<'a>, Affine) {
383 let scale = font_size / upem;
403
404 let transform = run_transform.pre_translate(Vec2::new(glyph.x.into(), glyph.y.into()));
405
406 let scale_factor = {
411 let (x_vec, y_vec) = x_y_advances(&transform.pre_scale(f64::from(scale)));
412 x_vec.length().max(y_vec.length())
413 };
414
415 let bbox = color_glyph
416 .bounding_box(LocationRef::default(), Size::unscaled())
417 .map(convert_bounding_box)
418 .unwrap_or(Rect::new(0.0, 0.0, f64::from(upem), f64::from(upem)));
419
420 let scaled_bbox = bbox.scale_from_origin(scale_factor);
423
424 let glyph_transform = transform
425 * Affine::scale_non_uniform(1.0, -1.0)
435 * Affine::translate((scaled_bbox.x0, scaled_bbox.y0));
438
439 let (pix_width, pix_height) = (
440 scaled_bbox.width().ceil() as u16,
441 scaled_bbox.height().ceil() as u16,
442 );
443
444 let draw_transform =
445 Affine::translate((-scaled_bbox.x0, -scaled_bbox.y0)) *
448 Affine::scale(scale_factor);
450
451 let area = Rect::new(0.0, 0.0, scaled_bbox.width(), scaled_bbox.height());
454
455 (
456 GlyphType::Colr(Box::new(ColorGlyph {
457 skrifa_glyph: color_glyph,
458 font_ref,
459 location: LocationRef::new(normalized_coords),
460 area,
461 pix_width,
462 pix_height,
463 draw_transform,
464 })),
465 glyph_transform,
466 )
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq)]
471pub enum Style {
472 Fill,
474 Stroke,
476}
477
478#[derive(Clone, Debug)]
480struct GlyphRun<'a> {
481 font: Font,
483 font_size: f32,
485 transform: Affine,
487 glyph_transform: Option<Affine>,
490 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
492 hint: bool,
494}
495
496struct PreparedGlyphRun<'a> {
497 transform: Affine,
500 size: Size,
502 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
503 hinting_instance: Option<HintingInstance>,
504}
505
506fn prepare_glyph_run<'a>(
511 run: &GlyphRun<'a>,
512 outlines: &OutlineGlyphCollection<'_>,
513) -> PreparedGlyphRun<'a> {
514 if !run.hint {
515 return PreparedGlyphRun {
516 transform: run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY),
517 size: Size::new(run.font_size),
518 normalized_coords: run.normalized_coords,
519 hinting_instance: None,
520 };
521 }
522
523 let total_transform = run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY);
535 let [t_a, t_b, t_c, t_d, t_e, t_f] = total_transform.as_coeffs();
536
537 let uniform_scale = t_a == t_d;
538 let vertically_uniform = t_b == 0.;
539
540 if uniform_scale && vertically_uniform {
541 let vertical_font_size = run.font_size * t_d as f32;
542 let size = Size::new(vertical_font_size);
543 let hinting_instance =
544 HintingInstance::new(outlines, size, run.normalized_coords, HINTING_OPTIONS).ok();
545 PreparedGlyphRun {
546 transform: Affine::new([1., 0., t_c, 1., t_e, t_f]),
547 size,
548 normalized_coords: run.normalized_coords,
549 hinting_instance,
550 }
551 } else {
552 PreparedGlyphRun {
553 transform: run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY),
554 size: Size::new(run.font_size),
555 normalized_coords: run.normalized_coords,
556 hinting_instance: None,
557 }
558 }
559}
560
561const HINTING_OPTIONS: HintingOptions = HintingOptions {
564 engine: skrifa::outline::Engine::AutoFallback,
565 target: skrifa::outline::Target::Smooth {
566 mode: skrifa::outline::SmoothMode::Lcd,
567 symmetric_rendering: false,
568 preserve_linear_metrics: true,
569 },
570};
571
572pub(crate) struct OutlinePath(pub(crate) BezPath);
573
574impl OutlinePath {
575 pub(crate) fn new() -> Self {
576 Self(BezPath::new())
577 }
578}
579
580impl OutlinePen for OutlinePath {
582 #[inline]
583 fn move_to(&mut self, x: f32, y: f32) {
584 self.0.move_to((x, y));
585 }
586
587 #[inline]
588 fn line_to(&mut self, x: f32, y: f32) {
589 self.0.line_to((x, y));
590 }
591
592 #[inline]
593 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
594 self.0.curve_to((cx0, cy0), (cx1, cy1), (x, y));
595 }
596
597 #[inline]
598 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
599 self.0.quad_to((cx, cy), (x, y));
600 }
601
602 #[inline]
603 fn close(&mut self) {
604 self.0.close_path();
605 }
606}
607
608pub type NormalizedCoord = i16;
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 const _NORMALISED_COORD_SIZE_MATCHES: () =
624 assert!(size_of::<skrifa::instance::NormalizedCoord>() == size_of::<NormalizedCoord>());
625}