1#![allow(
7 clippy::cast_possible_truncation,
8 reason = "We temporarily ignore these because the casts\
9only break in edge cases, and some of them are also only related to conversions from f64 to f32."
10)]
11
12use crate::Pixmap;
13use crate::atlas::AtlasSlot;
14use crate::atlas::GlyphCacheKey;
15use crate::atlas::key::{SUBPIXEL_BITMAP, SUBPIXEL_COLR, pack_color};
16use crate::atlas::{GlyphAtlas, ImageCache};
17use crate::color::PremulRgba8;
18use crate::color::palette::css::BLACK;
19use crate::colr::{convert_bounding_box, get_colr_info};
20use crate::kurbo::Point;
21use crate::kurbo::Rect;
22use crate::kurbo::Vec2;
23use crate::kurbo::{self, Affine, BezPath, Diagonal2, Join, Shape};
24use crate::kurbo::{Line, ParamCurve as _, PathSeg};
25use crate::peniko::FontData;
26use crate::renderer::{fill_glyph, render_cached_glyph, stroke_glyph};
27use crate::util::AffineExt;
28use alloc::boxed::Box;
29use alloc::sync::Arc;
30use alloc::vec::Vec;
31use core::fmt::{Debug, Formatter};
32use core::ops::RangeInclusive;
33#[cfg(not(feature = "std"))]
34use core_maths::CoreFloat as _;
35use hashbrown::hash_map::{Entry, RawEntryMut};
36use hashbrown::{Equivalent, HashMap};
37use skrifa::bitmap::{BitmapData, BitmapFormat, BitmapStrikes, Origin};
38use skrifa::instance::{LocationRef, Size};
39use skrifa::outline::{DrawSettings, OutlineGlyphFormat};
40use skrifa::outline::{HintingInstance, HintingOptions, OutlinePen};
41use skrifa::raw::TableProvider;
42use skrifa::{FontRef, OutlineGlyphCollection};
43use skrifa::{GlyphId, MetadataProvider};
44use smallvec::SmallVec;
45
46#[derive(Copy, Clone, Default, Debug)]
48pub struct Glyph {
49 pub id: u32,
54 pub x: f32,
56 pub y: f32,
58}
59
60#[derive(Clone, Copy, Debug)]
62pub struct FontEmbolden {
63 pub amount: Diagonal2,
65 pub join: Join,
67 pub miter_limit: f64,
69 pub tolerance: f64,
71}
72
73impl FontEmbolden {
74 pub fn new(amount: Diagonal2) -> Self {
76 Self {
77 amount,
78 ..Self::default()
79 }
80 }
81
82 pub fn with_join(mut self, join: Join) -> Self {
84 self.join = join;
85 self
86 }
87
88 pub fn with_miter_limit(mut self, miter_limit: f64) -> Self {
90 self.miter_limit = miter_limit;
91 self
92 }
93
94 pub fn with_tolerance(mut self, tolerance: f64) -> Self {
96 self.tolerance = tolerance;
97 self
98 }
99}
100
101impl Default for FontEmbolden {
102 fn default() -> Self {
103 Self {
104 amount: Diagonal2::new(0.0, 0.0),
105 join: Join::Miter,
106 miter_limit: 4.0,
107 tolerance: 0.1,
108 }
109 }
110}
111
112const BLACK_PACKED: u32 = PremulRgba8 {
114 r: 0,
115 g: 0,
116 b: 0,
117 a: 255,
118}
119.to_u32();
120
121#[derive(Debug)]
123pub(crate) enum GlyphType<'a> {
124 Outline(GlyphOutline),
126 Bitmap(GlyphBitmap),
128 Colr(Box<GlyphColr<'a>>),
130}
131
132#[derive(Debug, Clone, Copy)]
136pub(crate) enum CachedGlyphType {
137 Outline,
139 Bitmap,
141 Colr(Rect),
145}
146
147#[derive(Debug)]
149pub(crate) struct PreparedGlyph<'a> {
150 pub(crate) glyph_type: GlyphType<'a>,
152 pub(crate) transform: Affine,
154 pub(crate) cache_key: Option<GlyphCacheKey>,
160}
161
162#[derive(Debug)]
164pub(crate) struct GlyphOutline {
165 pub(crate) path: Arc<BezPath>,
167}
168
169#[derive(Debug)]
171pub(crate) struct GlyphBitmap {
172 pub(crate) pixmap: Arc<Pixmap>,
174 pub(crate) area: Rect,
176}
177
178pub struct GlyphColr<'a> {
184 pub skrifa_glyph: skrifa::color::ColorGlyph<'a>,
186 pub location: LocationRef<'a>,
188 pub font_ref: &'a FontRef<'a>,
190 pub draw_transform: Affine,
192 pub area: Rect,
195 pub pix_width: u16,
197 pub pix_height: u16,
199 pub has_non_default_blend: bool,
201}
202
203impl Debug for GlyphColr<'_> {
204 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
205 write!(f, "GlyphColr")
206 }
207}
208
209#[derive(Debug, Default)]
211pub struct GlyphPrepCache {
212 pub(crate) outline_cache: OutlineCache,
214 pub(crate) hinting_cache: HintCache,
216 pub(crate) underline_exclusions: Vec<(f64, f64)>,
218}
219
220impl GlyphPrepCache {
221 pub fn as_mut(&mut self) -> GlyphPrepCacheMut<'_> {
223 GlyphPrepCacheMut {
224 outline_cache: &mut self.outline_cache,
225 hinting_cache: &mut self.hinting_cache,
226 underline_exclusions: &mut self.underline_exclusions,
227 }
228 }
229
230 pub fn clear(&mut self) {
232 self.outline_cache.clear();
233 self.hinting_cache.clear();
234 self.underline_exclusions.clear();
235 }
236
237 pub fn maintain(&mut self) {
239 self.outline_cache.maintain();
240 }
241}
242
243#[derive(Debug)]
245pub struct GlyphPrepCacheMut<'a> {
246 pub(crate) outline_cache: &'a mut OutlineCache,
248 pub(crate) hinting_cache: &'a mut HintCache,
250 pub(crate) underline_exclusions: &'a mut Vec<(f64, f64)>,
252}
253
254#[derive(Debug)]
256pub enum AtlasCacher<'a> {
257 Disabled,
259 Enabled(&'a mut GlyphAtlas, &'a mut ImageCache),
262}
263
264impl AtlasCacher<'_> {
265 fn config(&self) -> Option<&crate::atlas::GlyphCacheConfig> {
266 match self {
267 Self::Disabled => None,
268 Self::Enabled(glyph_atlas, _) => Some(glyph_atlas.config()),
269 }
270 }
271
272 fn get(&mut self, key: &GlyphCacheKey) -> Option<AtlasSlot> {
273 match self {
274 Self::Disabled => None,
275 Self::Enabled(glyph_atlas, _) => glyph_atlas.get(key),
276 }
277 }
278}
279
280pub trait GlyphRunBackend<'a>: Sized {
282 fn atlas_cache(self, enabled: bool) -> Self;
284
285 fn fill_glyphs<Glyphs>(self, run: GlyphRun<'a>, glyphs: Glyphs)
287 where
288 Glyphs: Iterator<Item = Glyph> + Clone;
289
290 fn stroke_glyphs<Glyphs>(self, run: GlyphRun<'a>, glyphs: Glyphs)
292 where
293 Glyphs: Iterator<Item = Glyph> + Clone;
294
295 fn render_decoration<Glyphs>(
297 self,
298 run: GlyphRun<'a>,
299 glyphs: Glyphs,
300 x_range: RangeInclusive<f32>,
301 baseline_y: f32,
302 offset: f32,
303 size: f32,
304 buffer: f32,
305 ) where
306 Glyphs: Iterator<Item = Glyph> + Clone;
307}
308
309#[derive(Debug)]
311pub struct GlyphRunRenderer<'a, 'b, Glyphs: Iterator<Item = Glyph> + Clone> {
312 prepared_run: PreparedGlyphRun<'a>,
313 outline_cache: &'b mut OutlineCache,
314 underline_span_cache: &'b mut Vec<(f64, f64)>,
315 glyph_iterator: Glyphs,
316 atlas_cacher: AtlasCacher<'b>,
317}
318
319impl<'a, 'b, Glyphs: Iterator<Item = Glyph> + Clone> GlyphRunRenderer<'a, 'b, Glyphs> {
320 pub fn fill_glyphs(&mut self, renderer: &mut impl crate::GlyphRenderer) {
322 self.draw_glyphs(Style::Fill, renderer);
323 }
324
325 pub fn stroke_glyphs(&mut self, renderer: &mut impl crate::GlyphRenderer) {
327 self.draw_glyphs(Style::Stroke, renderer);
328 }
329
330 fn draw_glyphs(&mut self, style: Style, renderer: &mut impl crate::GlyphRenderer) {
338 let font_ref = self.prepared_run.font.as_skrifa();
339 let upem: f32 = font_ref.head().map(|h| h.units_per_em()).unwrap().into();
340
341 let outlines = font_ref.outline_glyphs();
342 let color_glyphs = font_ref.color_glyphs();
343 let bitmaps = font_ref.bitmap_strikes();
344
345 let mut outline_cache_session = OutlineCacheSession::new(
346 self.outline_cache,
347 VarLookupKey(self.prepared_run.normalized_coords),
348 );
349 let PreparedGlyphRun {
350 draw_props,
351 run_size: _,
352 font_embolden,
353 normalized_coords,
354 hinting_instance,
355 ..
356 } = self.prepared_run;
357
358 let font_id = self.prepared_run.font.data.id();
359 let font_index = self.prepared_run.font.index;
360 let hinted = hinting_instance.is_some();
361
362 let colr_bitmap_cache_enabled = self
363 .atlas_cacher
364 .config()
365 .is_some_and(|config| draw_props.font_size <= config.max_cached_font_size);
366 let outline_cache_enabled = colr_bitmap_cache_enabled
367 && style == Style::Fill;
371
372 let context_color = renderer.get_context_color();
373 let context_color_packed = pack_color(context_color);
374 for glyph in self.glyph_iterator.clone() {
375 let glyph_id = GlyphId::new(glyph.id);
379
380 let outline_transform =
386 calculate_outline_transform(glyph, draw_props, hinting_instance);
387 let outline_cache_key = outline_cache_enabled.then(|| {
388 let fractional_x = outline_transform.translation().x.fract() as f32;
389 GlyphCacheKey::new(
390 font_id,
391 font_index,
392 glyph.id,
393 draw_props.font_size,
394 hinted,
395 fractional_x,
396 BLACK,
397 BLACK_PACKED,
398 font_embolden,
399 normalized_coords,
400 )
401 });
402 if let Some(ref key) = outline_cache_key
403 && let Some(cached_slot) = self.atlas_cacher.get(key)
404 {
405 render_cached_glyph(
406 renderer,
407 cached_slot,
408 outline_transform,
409 CachedGlyphType::Outline,
410 );
411 continue;
412 }
413
414 if let Some(color_glyph) = color_glyphs.get(glyph_id) {
416 let location = LocationRef::new(normalized_coords);
417 let metrics = calculate_colr_metrics(
418 draw_props.font_size,
419 upem,
420 draw_props,
421 glyph,
422 &font_ref,
423 &color_glyph,
424 location,
425 );
426 let transform = calculate_colr_transform(&metrics);
427
428 let cache_key = colr_bitmap_cache_enabled.then(|| GlyphCacheKey {
431 font_id,
432 font_index,
433 glyph_id: glyph.id,
434 size_bits: draw_props.font_size.to_bits(),
435 hinted: false,
436 subpixel_x: SUBPIXEL_COLR,
437 context_color,
438 context_color_packed,
439 embolden_x_bits: 0,
440 embolden_y_bits: 0,
441 embolden_join_bits: join_bits(Join::Miter),
442 embolden_miter_limit_bits: 4.0_f32.to_bits(),
443 embolden_tolerance_bits: 0.1_f32.to_bits(),
444 var_coords: SmallVec::from_slice(normalized_coords),
445 });
446
447 if let Some(ref key) = cache_key
448 && let Some(cached_slot) = self.atlas_cacher.get(key)
449 {
450 let area = Rect::new(
452 0.0,
453 0.0,
454 metrics.scaled_bbox.width(),
455 metrics.scaled_bbox.height(),
456 );
457 render_cached_glyph(
458 renderer,
459 cached_slot,
460 transform,
461 CachedGlyphType::Colr(area),
462 );
463 continue;
464 }
465
466 let glyph_type =
468 create_colr_glyph(&font_ref, &metrics, color_glyph, normalized_coords);
469
470 let prepared_glyph = PreparedGlyph {
471 glyph_type,
472 transform,
473 cache_key,
474 };
475 match style {
476 Style::Fill => fill_glyph(renderer, prepared_glyph, &mut self.atlas_cacher),
477 Style::Stroke => stroke_glyph(renderer, prepared_glyph, &mut self.atlas_cacher),
478 }
479 continue;
480 }
481
482 let bitmap_data: Option<(skrifa::bitmap::BitmapGlyph<'_>, Pixmap)> = bitmaps
484 .glyph_for_size(Size::new(draw_props.font_size), glyph_id)
485 .and_then(|g| match g.data {
486 #[cfg(feature = "png")]
487 BitmapData::Png(data) => Pixmap::from_png(std::io::Cursor::new(data))
488 .ok()
489 .map(|d| (g, d)),
490 #[cfg(not(feature = "png"))]
491 BitmapData::Png(_) => None,
492 BitmapData::Bgra(_) => None,
495 BitmapData::Mask(_) => None,
496 });
497
498 if let Some((bitmap_glyph, pixmap)) = bitmap_data {
499 let bitmap_ppem = bitmap_glyph.ppem_x;
502 let transform = calculate_bitmap_transform(
503 glyph,
504 &pixmap,
505 draw_props,
506 draw_props.font_size,
507 upem,
508 &bitmap_glyph,
509 &bitmaps,
510 );
511
512 let cache_key = colr_bitmap_cache_enabled.then(|| GlyphCacheKey {
515 font_id,
516 font_index,
517 glyph_id: glyph.id,
518 size_bits: bitmap_ppem.to_bits(),
519 hinted: false,
520 subpixel_x: SUBPIXEL_BITMAP,
521 context_color: BLACK,
522 context_color_packed: BLACK_PACKED,
523 embolden_x_bits: 0,
524 embolden_y_bits: 0,
525 embolden_join_bits: join_bits(Join::Miter),
526 embolden_miter_limit_bits: 4.0_f32.to_bits(),
527 embolden_tolerance_bits: 0.1_f32.to_bits(),
528 var_coords: SmallVec::new(),
529 });
530
531 if let Some(ref key) = cache_key
532 && let Some(cached_slot) = self.atlas_cacher.get(key)
533 {
534 render_cached_glyph(renderer, cached_slot, transform, CachedGlyphType::Bitmap);
535 continue;
536 }
537
538 let glyph_type = create_bitmap_glyph(pixmap);
540
541 let prepared_glyph = PreparedGlyph {
542 glyph_type,
543 transform,
544 cache_key,
545 };
546 match style {
547 Style::Fill => fill_glyph(renderer, prepared_glyph, &mut self.atlas_cacher),
548 Style::Stroke => stroke_glyph(renderer, prepared_glyph, &mut self.atlas_cacher),
549 }
550 continue;
551 }
552
553 let Some(outline) = outlines.get(glyph_id) else {
560 continue;
561 };
562
563 let glyph_type = create_outline_glyph(
564 glyph.id,
565 font_id,
566 font_index,
567 &mut outline_cache_session,
568 draw_props.font_size,
569 font_embolden,
570 &outline,
571 hinting_instance,
572 normalized_coords,
573 );
574
575 let prepared_glyph = PreparedGlyph {
576 glyph_type,
577 transform: outline_transform,
578 cache_key: outline_cache_key,
579 };
580 match style {
581 Style::Fill => fill_glyph(renderer, prepared_glyph, &mut self.atlas_cacher),
582 Style::Stroke => stroke_glyph(renderer, prepared_glyph, &mut self.atlas_cacher),
583 }
584 }
585 }
586
587 pub fn stroke_adjustment(&self) -> f64 {
590 let run_size = self.prepared_run.run_size;
591
592 if run_size == 0.0 {
593 1.0
594 } else {
595 f64::from(self.prepared_run.draw_props.font_size / run_size)
596 }
597 }
598
599 pub fn render_decoration(
608 &mut self,
609 x_range: RangeInclusive<f32>,
610 baseline_y: f32,
611 offset: f32,
612 size: f32,
613 buffer: f32,
614 renderer: &mut impl crate::DrawSink,
615 ) {
616 self.decoration_spans(x_range, baseline_y, offset, size, buffer)
617 .for_each(|rect| {
618 renderer.fill_rect(&rect);
619 });
620 }
621
622 fn decoration_spans<'c>(
623 &'c mut self,
624 x_range: RangeInclusive<f32>,
625 baseline_y: f32,
626 offset: f32,
627 size: f32,
628 buffer: f32,
629 ) -> impl Iterator<Item = Rect> + 'c {
630 let font_ref = self.prepared_run.font.as_skrifa();
631 let outlines = font_ref.outline_glyphs();
632
633 let PreparedGlyphRun {
634 draw_props,
635 font_embolden,
636 hinting_instance,
637 ..
638 } = self.prepared_run;
639
640 let outline_to_nominal_scale = f64::from(self.prepared_run.run_size / draw_props.font_size);
648 let outline_transform = self
649 .prepared_run
650 .glyph_transform
651 .unwrap_or(Affine::IDENTITY)
652 * Affine::FLIP_Y
653 * Affine::scale(outline_to_nominal_scale);
654
655 let buffer = f64::from(buffer);
657
658 let x0 = f64::from(*x_range.start());
660 let x1 = f64::from(*x_range.end());
661
662 let layout_y0 = f64::from(-offset);
665 let layout_y1 = f64::from(-offset + size);
666
667 let var_key = VarLookupKey(self.prepared_run.normalized_coords);
669 let mut outline_cache_session = OutlineCacheSession::new(self.outline_cache, var_key);
670
671 let exclusions = &mut self.underline_span_cache;
673 exclusions.truncate(0);
675
676 for glyph in self.glyph_iterator.clone() {
677 let Some(outline) = outlines.get(GlyphId::new(glyph.id)) else {
679 continue;
680 };
681
682 let cached = outline_cache_session.get_or_insert(
683 glyph.id,
684 self.prepared_run.font.data.id(),
685 self.prepared_run.font.index,
686 draw_props.font_size,
687 font_embolden,
688 var_key,
689 &outline,
690 hinting_instance,
691 );
692
693 let [_, b, _, d, _, f] = outline_transform.as_coeffs();
699 let (y_min, y_max) = {
700 let bx0 = b * cached.bbox.x0;
701 let bx1 = b * cached.bbox.x1;
702 let dy0 = d * cached.bbox.y0;
703 let dy1 = d * cached.bbox.y1;
704 (
705 f + bx0.min(bx1) + dy0.min(dy1),
706 f + bx0.max(bx1) + dy0.max(dy1),
707 )
708 };
709 if y_max < layout_y0 || y_min > layout_y1 {
710 continue;
711 }
712
713 let mut rect = Rect {
714 x0: f64::INFINITY,
715 x1: f64::NEG_INFINITY,
716 y0: layout_y0,
717 y1: layout_y1,
718 };
719
720 for seg in cached.path.segments() {
721 let seg = outline_transform * seg;
723 expand_rect_with_segment(&mut rect, seg, layout_y0..=layout_y1);
724 }
725
726 let excl_start = (rect.x0 + f64::from(glyph.x) - buffer).max(x0);
728 let excl_end = (rect.x1 + f64::from(glyph.x) + buffer).min(x1);
729
730 if excl_start >= excl_end {
732 continue;
733 }
734
735 insert_and_merge_range(exclusions, excl_start, excl_end);
737 }
738
739 let y0 = f64::from(baseline_y) + layout_y0;
741 let y1 = f64::from(baseline_y) + layout_y1;
742
743 let mut state = Some((exclusions.drain(..), x0));
744 core::iter::from_fn(move || {
745 let (iter, current_x) = state.as_mut()?;
746 let Some((excl_start, excl_end)) = iter.next() else {
747 let final_rect = Rect::new(*current_x, y0, x1, y1);
749 state = None;
750 return (final_rect.width() > 0.0).then_some(final_rect);
751 };
752
753 let rect = Rect::new(*current_x, y0, excl_start, y1);
755 *current_x = excl_end;
756 Some(rect)
757 })
758 }
759}
760
761#[derive(Debug)]
763#[must_use = "Methods on the builder don't do anything until `render` is called."]
764pub struct GlyphRunBuilder<'a, B> {
765 run: GlyphRun<'a>,
766 backend: B,
767}
768
769impl<'a, B> GlyphRunBuilder<'a, B> {
770 pub fn new(font: FontData, transform: Affine, backend: B) -> Self {
772 Self {
773 run: GlyphRun {
775 font,
776 font_size: 16.0,
777 font_embolden: FontEmbolden::default(),
778 transform,
779 glyph_transform: None,
780 hint: true,
781 normalized_coords: &[],
782 },
783 backend,
784 }
785 }
786
787 pub fn font_size(mut self, size: f32) -> Self {
789 self.run.font_size = size;
790 self
791 }
792
793 pub fn font_embolden(mut self, embolden: FontEmbolden) -> Self {
795 self.run.font_embolden = embolden;
796 self
797 }
798
799 pub fn glyph_transform(mut self, transform: Affine) -> Self {
802 self.run.glyph_transform = Some(transform);
803 self
804 }
805
806 pub fn hint(mut self, hint: bool) -> Self {
811 self.run.hint = hint;
812 self
813 }
814
815 pub fn normalized_coords(mut self, coords: &'a [NormalizedCoord]) -> Self {
817 self.run.normalized_coords = bytemuck::cast_slice(coords);
818 self
819 }
820}
821
822impl<'a> GlyphRun<'a> {
823 #[doc(hidden)]
829 pub fn build<'b: 'a, Glyphs: Iterator<Item = Glyph> + Clone>(
830 self,
831 glyphs: Glyphs,
832 prep_cache: GlyphPrepCacheMut<'b>,
833 atlas_cacher: AtlasCacher<'b>,
834 ) -> GlyphRunRenderer<'a, 'b, Glyphs> {
835 let prepared_run = prepare_glyph_run(self, prep_cache.hinting_cache);
836 GlyphRunRenderer {
837 prepared_run,
838 glyph_iterator: glyphs,
839 outline_cache: prep_cache.outline_cache,
840 underline_span_cache: prep_cache.underline_exclusions,
841 atlas_cacher,
842 }
843 }
844}
845
846impl<'a, B> GlyphRunBuilder<'a, B>
847where
848 B: GlyphRunBackend<'a>,
849{
850 pub fn atlas_cache(self, enabled: bool) -> Self {
852 Self {
853 run: self.run,
854 backend: self.backend.atlas_cache(enabled),
855 }
856 }
857
858 pub fn fill_glyphs<Glyphs>(self, glyphs: Glyphs)
860 where
861 Glyphs: Iterator<Item = Glyph> + Clone,
862 {
863 let GlyphRunBuilder { run, backend } = self;
864 backend.fill_glyphs(run, glyphs);
865 }
866
867 pub fn stroke_glyphs<Glyphs>(self, glyphs: Glyphs)
869 where
870 Glyphs: Iterator<Item = Glyph> + Clone,
871 {
872 let GlyphRunBuilder { run, backend } = self;
873 backend.stroke_glyphs(run, glyphs);
874 }
875
876 pub fn render_decoration<Glyphs>(
880 self,
881 glyphs: Glyphs,
882 x_range: RangeInclusive<f32>,
883 baseline_y: f32,
884 offset: f32,
885 size: f32,
886 buffer: f32,
887 ) where
888 Glyphs: Iterator<Item = Glyph> + Clone,
889 {
890 let GlyphRunBuilder { run, backend } = self;
891 backend.render_decoration(run, glyphs, x_range, baseline_y, offset, size, buffer);
892 }
893}
894
895fn insert_and_merge_range(ranges: &mut Vec<(f64, f64)>, start: f64, end: f64) {
897 let insert_pos = ranges
900 .iter()
901 .rposition(|r| r.0 <= start)
902 .map_or(0, |i| i + 1);
903
904 let merge_start = insert_pos
906 .checked_sub(1)
907 .filter(|&i| ranges[i].1 >= start)
908 .unwrap_or(insert_pos);
909
910 let new_end = ranges[merge_start..]
912 .iter()
913 .take_while(|(s, _)| *s <= end)
914 .fold(end, |acc, (_, e)| acc.max(*e));
915
916 let merge_end = merge_start
917 + ranges[merge_start..]
918 .iter()
919 .take_while(|(s, _)| *s <= new_end)
920 .count();
921
922 if merge_start < merge_end {
924 let new_start = start.min(ranges[merge_start].0);
925 ranges.splice(merge_start..merge_end, [(new_start, new_end)]);
926 } else {
927 ranges.insert(insert_pos, (start, end));
928 }
929}
930
931fn expand_rect_with_segment(rect: &mut Rect, seg: PathSeg, y_span: RangeInclusive<f64>) {
932 let (mut x_bounds, y_bounds) = match seg {
936 PathSeg::Line(line) => (
937 (line.p0.x.min(line.p1.x), line.p0.x.max(line.p1.x)),
938 (line.p0.y.min(line.p1.y), line.p0.y.max(line.p1.y)),
939 ),
940 PathSeg::Quad(quad) => (
941 (
942 quad.p0.x.min(quad.p1.x).min(quad.p2.x),
943 quad.p0.x.max(quad.p1.x).max(quad.p2.x),
944 ),
945 (
946 quad.p0.y.min(quad.p1.y).min(quad.p2.y),
947 quad.p0.y.max(quad.p1.y).max(quad.p2.y),
948 ),
949 ),
950 PathSeg::Cubic(cubic) => (
951 (
952 cubic.p0.x.min(cubic.p1.x).min(cubic.p2.x).min(cubic.p3.x),
953 cubic.p0.x.max(cubic.p1.x).max(cubic.p2.x).max(cubic.p3.x),
954 ),
955 (
956 cubic.p0.y.min(cubic.p1.y).min(cubic.p2.y).min(cubic.p3.y),
957 cubic.p0.y.max(cubic.p1.y).max(cubic.p2.y).max(cubic.p3.y),
958 ),
959 ),
960 };
961 if y_bounds.1 < *y_span.start() || y_bounds.0 > *y_span.end() {
963 return;
964 }
965
966 x_bounds.0 -= 1.0;
969 x_bounds.1 += 1.0;
970 let top_line = Line::new((x_bounds.0, *y_span.start()), (x_bounds.1, *y_span.start()));
971 let bottom_line = Line::new((x_bounds.0, *y_span.end()), (x_bounds.1, *y_span.end()));
972
973 for intersection in seg.intersect_line(top_line) {
974 let point = top_line.eval(intersection.line_t);
975 rect.x0 = rect.x0.min(point.x);
978 rect.x1 = rect.x1.max(point.x);
979 }
980
981 for intersection in seg.intersect_line(bottom_line) {
982 let point = bottom_line.eval(intersection.line_t);
983 rect.x0 = rect.x0.min(point.x);
984 rect.x1 = rect.x1.max(point.x);
985 }
986
987 let (seg_start, seg_end) = match seg {
989 PathSeg::Line(line) => (line.p0, line.p1),
990 PathSeg::Quad(quad) => (quad.p0, quad.p2),
991 PathSeg::Cubic(cubic) => (cubic.p0, cubic.p3),
992 };
993
994 for point in [seg_start, seg_end] {
995 if (*y_span.start()..=*y_span.end()).contains(&point.y) {
996 rect.x0 = rect.x0.min(point.x);
997 rect.x1 = rect.x1.max(point.x);
998 }
999 }
1000}
1001
1002fn create_outline_glyph<'a>(
1007 glyph_id: u32,
1008 font_id: u64,
1009 font_index: u32,
1010 outline_cache: &'a mut OutlineCacheSession<'_>,
1011 size: f32,
1012 embolden: FontEmbolden,
1013 outline_glyph: &skrifa::outline::OutlineGlyph<'a>,
1014 hinting_instance: Option<&HintingInstance>,
1015 normalized_coords: &[skrifa::instance::NormalizedCoord],
1016) -> GlyphType<'a> {
1017 let cached = outline_cache.get_or_insert(
1018 glyph_id,
1019 font_id,
1020 font_index,
1021 size,
1022 embolden,
1023 VarLookupKey(normalized_coords),
1024 outline_glyph,
1025 hinting_instance,
1026 );
1027
1028 GlyphType::Outline(GlyphOutline {
1029 path: Arc::clone(cached.path),
1030 })
1031}
1032
1033fn calculate_outline_transform(
1041 glyph: Glyph,
1042 draw_props: DrawProps,
1043 hinting_instance: Option<&HintingInstance>,
1044) -> Affine {
1045 let mut final_transform = draw_props
1046 .positioned_transform(glyph)
1047 .pre_scale_non_uniform(1.0, -1.0)
1048 .as_coeffs();
1049
1050 if hinting_instance.is_some() {
1051 final_transform[5] = final_transform[5].round();
1052 }
1053
1054 Affine::new(final_transform)
1055}
1056
1057fn create_bitmap_glyph(pixmap: Pixmap) -> GlyphType<'static> {
1062 let area = Rect::new(
1065 0.0,
1066 0.0,
1067 f64::from(pixmap.width()),
1068 f64::from(pixmap.height()),
1069 );
1070
1071 GlyphType::Bitmap(GlyphBitmap {
1072 pixmap: Arc::new(pixmap),
1073 area,
1074 })
1075}
1076
1077fn calculate_bitmap_transform(
1086 glyph: Glyph,
1087 pixmap: &Pixmap,
1088 draw_props: DrawProps,
1089 font_size: f32,
1090 upem: f32,
1091 bitmap_glyph: &skrifa::bitmap::BitmapGlyph<'_>,
1092 bitmaps: &BitmapStrikes<'_>,
1093) -> Affine {
1094 let x_scale_factor = font_size / bitmap_glyph.ppem_x;
1095 let y_scale_factor = font_size / bitmap_glyph.ppem_y;
1096 let font_units_to_size = font_size / upem;
1097
1098 let bearing_y = if bitmap_glyph.bearing_y == 0.0 && bitmaps.format() == Some(BitmapFormat::Sbix)
1104 {
1105 100.0
1106 } else {
1107 bitmap_glyph.bearing_y
1108 };
1109
1110 let origin_shift = match bitmap_glyph.placement_origin {
1111 Origin::TopLeft => Vec2::default(),
1112 Origin::BottomLeft => Vec2 {
1113 x: 0.,
1114 y: -f64::from(pixmap.height()),
1115 },
1116 };
1117
1118 draw_props
1119 .positioned_transform(glyph)
1120 .pre_translate(Vec2 {
1122 x: (-bitmap_glyph.bearing_x * font_units_to_size).into(),
1123 y: (bearing_y * font_units_to_size).into(),
1124 })
1125 .pre_scale_non_uniform(f64::from(x_scale_factor), f64::from(y_scale_factor))
1127 .pre_translate(Vec2 {
1129 x: (-bitmap_glyph.inner_bearing_x).into(),
1130 y: (-bitmap_glyph.inner_bearing_y).into(),
1131 })
1132 .pre_translate(origin_shift)
1133}
1134
1135struct ColrMetrics {
1137 transform: Affine,
1139 scaled_bbox: Rect,
1141 scale_factor_x: f64,
1143 scale_factor_y: f64,
1145 font_size_scale: f64,
1147 has_non_default_blend: bool,
1148}
1149
1150fn calculate_colr_metrics(
1155 font_size: f32,
1156 upem: f32,
1157 draw_props: DrawProps,
1158 glyph: Glyph,
1159 font_ref: &FontRef<'_>,
1160 color_glyph: &skrifa::color::ColorGlyph<'_>,
1161 location: LocationRef<'_>,
1162) -> ColrMetrics {
1163 let font_size_scale = (font_size / upem) as f64;
1165 let transform = draw_props.positioned_transform(glyph);
1166
1167 let (scale_factor_x, scale_factor_y) = {
1171 let (x_vec, y_vec) = x_y_advances(&transform.pre_scale(font_size_scale));
1172 (x_vec.length(), y_vec.length())
1173 };
1174
1175 let colr_info = get_colr_info(font_ref, color_glyph, location);
1177 let bbox = color_glyph
1178 .bounding_box(location, Size::unscaled())
1181 .map(convert_bounding_box)
1182 .or(colr_info.bbox)
1184 .unwrap_or(Rect::ZERO);
1185
1186 let scaled_bbox = Rect {
1189 x0: bbox.x0 * scale_factor_x,
1190 y0: bbox.y0 * scale_factor_y,
1191 x1: bbox.x1 * scale_factor_x,
1192 y1: bbox.y1 * scale_factor_y,
1193 };
1194
1195 ColrMetrics {
1196 transform,
1197 scaled_bbox,
1198 scale_factor_x,
1199 scale_factor_y,
1200 font_size_scale,
1201 has_non_default_blend: colr_info.has_non_default_blend,
1202 }
1203}
1204
1205fn calculate_colr_transform(metrics: &ColrMetrics) -> Affine {
1213 metrics.transform
1214 * Affine::scale_non_uniform(1.0, -1.0)
1224 * Affine::scale_non_uniform(
1232 metrics.font_size_scale / metrics.scale_factor_x,
1233 metrics.font_size_scale / metrics.scale_factor_y,
1234 )
1235 * Affine::translate((metrics.scaled_bbox.x0, metrics.scaled_bbox.y0))
1238}
1239
1240fn create_colr_glyph<'a>(
1245 font_ref: &'a FontRef<'a>,
1246 metrics: &ColrMetrics,
1247 color_glyph: skrifa::color::ColorGlyph<'a>,
1248 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
1249) -> GlyphType<'a> {
1250 let (pix_width, pix_height) = (
1251 metrics.scaled_bbox.width().ceil() as u16,
1252 metrics.scaled_bbox.height().ceil() as u16,
1253 );
1254
1255 let draw_transform =
1256 Affine::translate((-metrics.scaled_bbox.x0, -metrics.scaled_bbox.y0)) *
1259 Affine::scale_non_uniform(metrics.scale_factor_x, metrics.scale_factor_y);
1261
1262 let area = Rect::new(
1265 0.0,
1266 0.0,
1267 metrics.scaled_bbox.width(),
1268 metrics.scaled_bbox.height(),
1269 );
1270
1271 let location = LocationRef::new(normalized_coords);
1272
1273 GlyphType::Colr(Box::new(GlyphColr {
1274 skrifa_glyph: color_glyph,
1275 font_ref,
1276 location,
1277 area,
1278 pix_width,
1279 pix_height,
1280 draw_transform,
1281 has_non_default_blend: metrics.has_non_default_blend,
1282 }))
1283}
1284
1285trait FontDataExt {
1286 fn as_skrifa(&self) -> FontRef<'_>;
1287}
1288
1289impl FontDataExt for FontData {
1290 fn as_skrifa(&self) -> FontRef<'_> {
1291 FontRef::from_index(self.data.data(), self.index).unwrap()
1292 }
1293}
1294
1295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1297pub(crate) enum Style {
1298 Fill,
1300 Stroke,
1302}
1303
1304#[derive(Clone, Debug)]
1306pub struct GlyphRun<'a> {
1307 font: FontData,
1309 font_size: f32,
1311 font_embolden: FontEmbolden,
1313 transform: Affine,
1315 glyph_transform: Option<Affine>,
1318 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
1320 hint: bool,
1322}
1323
1324struct PreparedGlyphRun<'a> {
1325 font: FontData,
1327 run_size: f32,
1335 font_embolden: FontEmbolden,
1337 glyph_transform: Option<Affine>,
1339 draw_props: DrawProps,
1357 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
1358 hinting_instance: Option<&'a HintingInstance>,
1359}
1360
1361#[derive(Clone, Copy, Debug)]
1363struct DrawProps {
1364 positioning_transform: Affine,
1375 effective_transform: Affine,
1377 font_size: f32,
1380}
1381
1382impl DrawProps {
1383 #[inline]
1384 fn positioned_transform(self, glyph: Glyph) -> Affine {
1385 let translation = self.positioning_transform * Point::new(glyph.x as f64, glyph.y as f64);
1390
1391 Affine::translate(translation.to_vec2()) * self.effective_transform
1394 }
1395}
1396
1397impl Debug for PreparedGlyphRun<'_> {
1398 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1399 f.debug_struct("PreparedGlyphRun")
1401 .field("font", &self.font)
1402 .field("run_size", &self.run_size)
1403 .field("font_embolden", &self.font_embolden)
1404 .field("glyph_transform", &self.glyph_transform)
1405 .field("transforms", &self.draw_props)
1406 .field("normalized_coords", &self.normalized_coords)
1407 .finish()
1408 }
1409}
1410
1411fn prepare_glyph_run<'a>(run: GlyphRun<'a>, hint_cache: &'a mut HintCache) -> PreparedGlyphRun<'a> {
1413 let full_transform = run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY);
1414 let [_, _, t_c, t_d, t_e, t_f] = full_transform.as_coeffs();
1415
1416 #[derive(Clone, Copy, Debug)]
1418 enum PreparedGlyphRunMode {
1419 Direct,
1424 AbsorbScaleUnhinted,
1426 AbsorbScaleHinted,
1428 }
1429
1430 let mode = if !run.hint {
1431 if full_transform.is_positive_uniform_scale_without_skew() {
1436 PreparedGlyphRunMode::AbsorbScaleUnhinted
1437 } else {
1438 PreparedGlyphRunMode::Direct
1439 }
1440 } else {
1441 if full_transform.is_positive_uniform_scale_without_vertical_skew() {
1452 PreparedGlyphRunMode::AbsorbScaleHinted
1453 } else {
1454 PreparedGlyphRunMode::Direct
1455 }
1456 };
1457
1458 let (effective_transform, draw_font_size, hinting_instance) = match mode {
1459 PreparedGlyphRunMode::Direct => (full_transform, run.font_size, None),
1460 PreparedGlyphRunMode::AbsorbScaleUnhinted => (
1461 Affine::new([1., 0., 0., 1., t_e, t_f]),
1462 run.font_size * t_d as f32,
1463 None,
1464 ),
1465 PreparedGlyphRunMode::AbsorbScaleHinted => {
1466 let vertical_font_size = run.font_size * t_d as f32;
1467 let font_ref = run.font.as_skrifa();
1468 let outlines = font_ref.outline_glyphs();
1469 let hinting_instance = hint_cache.get(&HintKey {
1470 font_id: run.font.data.id(),
1471 font_index: run.font.index,
1472 outlines: &outlines,
1473 size: vertical_font_size,
1474 coords: run.normalized_coords,
1475 });
1476
1477 (
1478 Affine::new([1., 0., t_c / t_d, 1., t_e, t_f]),
1483 vertical_font_size,
1484 hinting_instance,
1485 )
1486 }
1487 };
1488
1489 PreparedGlyphRun {
1490 font: run.font,
1491 run_size: run.font_size,
1492 font_embolden: run.font_embolden,
1493 glyph_transform: run.glyph_transform,
1494 draw_props: DrawProps {
1495 positioning_transform: run
1496 .transform
1497 .with_translation(Vec2::ZERO),
1500 effective_transform,
1501 font_size: draw_font_size,
1502 },
1503 normalized_coords: run.normalized_coords,
1504 hinting_instance,
1505 }
1506}
1507
1508const HINTING_OPTIONS: HintingOptions = HintingOptions {
1511 engine: skrifa::outline::Engine::AutoFallback,
1512 target: skrifa::outline::Target::Smooth {
1513 mode: skrifa::outline::SmoothMode::Lcd,
1514 symmetric_rendering: false,
1515 preserve_linear_metrics: true,
1516 },
1517};
1518
1519#[derive(Clone, Default)]
1520pub(crate) struct OutlinePath {
1521 pub(crate) path: BezPath,
1522 pub(crate) bbox: Rect,
1523}
1524
1525impl OutlinePath {
1526 pub(crate) fn new() -> Self {
1527 Self {
1528 path: BezPath::new(),
1529 bbox: Rect {
1530 x0: f64::INFINITY,
1531 y0: f64::INFINITY,
1532 x1: f64::NEG_INFINITY,
1533 y1: f64::NEG_INFINITY,
1534 },
1535 }
1536 }
1537
1538 pub(crate) fn reuse(&mut self) {
1539 self.path.truncate(0);
1540 self.bbox = Rect {
1541 x0: f64::INFINITY,
1542 y0: f64::INFINITY,
1543 x1: f64::NEG_INFINITY,
1544 y1: f64::NEG_INFINITY,
1545 };
1546 }
1547}
1548
1549impl OutlinePen for OutlinePath {
1551 #[inline]
1552 fn move_to(&mut self, x: f32, y: f32) {
1553 self.path.move_to((x, y));
1554 self.bbox = self.bbox.union_pt((x, y));
1555 }
1556
1557 #[inline]
1558 fn line_to(&mut self, x: f32, y: f32) {
1559 self.path.line_to((x, y));
1560 self.bbox = self.bbox.union_pt((x, y));
1561 }
1562
1563 #[inline]
1564 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
1565 self.path.curve_to((cx0, cy0), (cx1, cy1), (x, y));
1566 self.bbox = self.bbox.union_pt((cx0, cy0));
1567 self.bbox = self.bbox.union_pt((cx1, cy1));
1568 self.bbox = self.bbox.union_pt((x, y));
1569 }
1570
1571 #[inline]
1572 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
1573 self.path.quad_to((cx, cy), (x, y));
1574 self.bbox = self.bbox.union_pt((cx, cy));
1575 self.bbox = self.bbox.union_pt((x, y));
1576 }
1577
1578 #[inline]
1579 fn close(&mut self) {
1580 self.path.close_path();
1581 }
1582}
1583
1584pub type NormalizedCoord = i16;
1594
1595#[cfg(test)]
1596mod tests {
1597 use super::*;
1598
1599 const _NORMALISED_COORD_SIZE_MATCHES: () =
1600 assert!(size_of::<skrifa::instance::NormalizedCoord>() == size_of::<NormalizedCoord>());
1601}
1602
1603#[derive(Debug, Default)]
1609pub struct GlyphCaches {
1610 pub(crate) outline_cache: OutlineCache,
1612 pub(crate) hinting_cache: HintCache,
1614 pub(crate) underline_exclusions: Vec<(f64, f64)>,
1616 pub(crate) glyph_atlas: GlyphAtlas,
1618}
1619
1620impl GlyphCaches {
1621 pub fn clear(&mut self) {
1623 self.outline_cache.clear();
1624 self.hinting_cache.clear();
1625 self.underline_exclusions.clear();
1626 self.glyph_atlas.clear();
1627 }
1628
1629 pub fn maintain(&mut self, image_cache: &mut ImageCache) {
1637 self.outline_cache.maintain();
1638 self.glyph_atlas.maintain(image_cache);
1639 }
1640}
1641
1642#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
1643struct OutlineKey {
1644 font_id: u64,
1645 font_index: u32,
1646 glyph_id: u32,
1647 size_bits: u32,
1648 embolden_x_bits: u32,
1649 embolden_y_bits: u32,
1650 embolden_join_bits: u8,
1651 embolden_miter_limit_bits: u32,
1652 embolden_tolerance_bits: u32,
1653 hint: bool,
1654}
1655
1656#[inline(always)]
1657fn join_bits(join: Join) -> u8 {
1658 match join {
1659 Join::Bevel => 0,
1660 Join::Miter => 1,
1661 Join::Round => 2,
1662 }
1663}
1664
1665#[expect(
1666 clippy::cast_possible_truncation,
1667 reason = "Cache keys intentionally store embolden parameters at f32 precision."
1668)]
1669#[inline(always)]
1670fn f32_bits(value: f64) -> u32 {
1671 (value as f32).to_bits()
1672}
1673
1674struct OutlineEntry {
1675 path: Arc<BezPath>,
1676 bbox: Rect,
1677 serial: u32,
1678}
1679
1680impl OutlineEntry {
1681 fn new(path: Arc<BezPath>, bbox: Rect, serial: u32) -> Self {
1682 Self { path, bbox, serial }
1683 }
1684
1685 fn take_path(&mut self) -> Option<OutlinePath> {
1687 let arc = core::mem::replace(&mut self.path, Arc::new(BezPath::new()));
1688 Arc::try_unwrap(arc).ok().map(|path| OutlinePath {
1689 path,
1690 bbox: Rect::ZERO,
1691 })
1692 }
1693}
1694
1695pub(crate) struct CachedOutline<'a> {
1697 pub(crate) path: &'a Arc<BezPath>,
1698 pub(crate) bbox: Rect,
1699}
1700
1701#[derive(Default)]
1704pub struct OutlineCache {
1705 free_list: Vec<OutlinePath>,
1706 static_map: HashMap<OutlineKey, OutlineEntry>,
1707 variable_map: HashMap<VarKey, HashMap<OutlineKey, OutlineEntry>>,
1708 cached_count: usize,
1709 serial: u32,
1710 last_prune_serial: u32,
1711}
1712
1713impl Debug for OutlineCache {
1714 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1715 f.debug_struct("OutlineCache")
1716 .field("free_list", &self.free_list.len())
1717 .field("static_map", &self.static_map.len())
1718 .field("variable_map", &self.variable_map.len())
1719 .field("cached_count", &self.cached_count)
1720 .field("serial", &self.serial)
1721 .field("last_prune_serial", &self.last_prune_serial)
1722 .finish()
1723 }
1724}
1725
1726impl OutlineCache {
1727 pub fn maintain(&mut self) {
1731 const MAX_ENTRY_AGE: u32 = 64;
1733 const PRUNE_FREQUENCY: u32 = 64;
1735 const CACHED_COUNT_THRESHOLD: usize = 256;
1737 const MAX_FREE_LIST_SIZE: usize = 128;
1739
1740 let free_list = &mut self.free_list;
1741 let serial = self.serial;
1742 self.serial += 1;
1743 if serial - self.last_prune_serial < PRUNE_FREQUENCY
1745 && self.cached_count < CACHED_COUNT_THRESHOLD
1746 {
1747 return;
1748 }
1749 self.last_prune_serial = serial;
1750 self.static_map.retain(|_, entry| {
1751 if serial - entry.serial > MAX_ENTRY_AGE {
1752 if free_list.len() < MAX_FREE_LIST_SIZE {
1753 if let Some(path) = entry.take_path() {
1756 free_list.push(path);
1757 }
1758 }
1759 self.cached_count -= 1;
1760 false
1761 } else {
1762 true
1763 }
1764 });
1765 self.variable_map.retain(|_, map| {
1766 map.retain(|_, entry| {
1767 if serial - entry.serial > MAX_ENTRY_AGE {
1768 if free_list.len() < MAX_FREE_LIST_SIZE
1769 && let Some(path) = entry.take_path()
1770 {
1771 free_list.push(path);
1772 }
1773 self.cached_count -= 1;
1774 false
1775 } else {
1776 true
1777 }
1778 });
1779 !map.is_empty()
1780 });
1781 }
1782
1783 pub fn clear(&mut self) {
1785 self.free_list.clear();
1786 self.static_map.clear();
1787 self.variable_map.clear();
1788 self.cached_count = 0;
1789 self.serial = 0;
1790 self.last_prune_serial = 0;
1791 }
1792}
1793
1794struct OutlineCacheSession<'a> {
1795 map: &'a mut HashMap<OutlineKey, OutlineEntry>,
1796 free_list: &'a mut Vec<OutlinePath>,
1797 serial: u32,
1798 cached_count: &'a mut usize,
1799}
1800
1801impl<'a> OutlineCacheSession<'a> {
1802 fn new(outline_cache: &'a mut OutlineCache, var_key: VarLookupKey<'_>) -> Self {
1803 let map = if var_key.0.is_empty() {
1804 &mut outline_cache.static_map
1805 } else {
1806 match outline_cache
1807 .variable_map
1808 .raw_entry_mut()
1809 .from_key(&var_key)
1810 {
1811 RawEntryMut::Occupied(entry) => entry.into_mut(),
1812 RawEntryMut::Vacant(entry) => entry.insert(var_key.into(), HashMap::new()).1,
1813 }
1814 };
1815 Self {
1816 map,
1817 free_list: &mut outline_cache.free_list,
1818 serial: outline_cache.serial,
1819 cached_count: &mut outline_cache.cached_count,
1820 }
1821 }
1822
1823 fn get_or_insert(
1824 &mut self,
1825 glyph_id: u32,
1826 font_id: u64,
1827 font_index: u32,
1828 size: f32,
1829 embolden: FontEmbolden,
1830 var_key: VarLookupKey<'_>,
1831 outline_glyph: &skrifa::outline::OutlineGlyph<'_>,
1832 hinting_instance: Option<&HintingInstance>,
1833 ) -> CachedOutline<'_> {
1834 let key = OutlineKey {
1835 glyph_id,
1836 font_id,
1837 font_index,
1838 size_bits: size.to_bits(),
1839 embolden_x_bits: f32_bits(embolden.amount.xx),
1840 embolden_y_bits: f32_bits(embolden.amount.yy),
1841 embolden_join_bits: join_bits(embolden.join),
1842 embolden_miter_limit_bits: f32_bits(embolden.miter_limit),
1843 embolden_tolerance_bits: f32_bits(embolden.tolerance),
1844 hint: hinting_instance.is_some(),
1845 };
1846
1847 match self.map.entry(key) {
1848 Entry::Occupied(mut entry) => {
1849 entry.get_mut().serial = self.serial;
1850 let entry = entry.into_mut();
1851 CachedOutline {
1852 path: &entry.path,
1853 bbox: entry.bbox,
1854 }
1855 }
1856 Entry::Vacant(entry) => {
1857 let mut drawing_buf = self.free_list.pop().unwrap_or_default();
1859
1860 let draw_settings = if let Some(hinting_instance) = hinting_instance {
1861 DrawSettings::hinted(hinting_instance, false)
1862 } else {
1863 DrawSettings::unhinted(Size::new(size), var_key.0)
1864 };
1865
1866 drawing_buf.reuse();
1867 outline_glyph.draw(draw_settings, &mut drawing_buf).unwrap();
1868 if embolden.amount != Diagonal2::new(0.0, 0.0) {
1869 drawing_buf.path = kurbo::expand_path(
1870 &drawing_buf.path,
1871 embolden.amount,
1872 embolden.join,
1873 embolden.miter_limit,
1874 embolden.tolerance,
1875 );
1876 drawing_buf.bbox = drawing_buf.path.bounding_box();
1877 }
1878
1879 let bbox = drawing_buf.bbox;
1880 let entry = entry.insert(OutlineEntry::new(
1881 Arc::new(drawing_buf.path),
1882 bbox,
1883 self.serial,
1884 ));
1885 *self.cached_count += 1;
1886 CachedOutline {
1887 path: &entry.path,
1888 bbox: entry.bbox,
1889 }
1890 }
1891 }
1892 }
1893}
1894
1895type VarKey = SmallVec<[skrifa::instance::NormalizedCoord; 4]>;
1897
1898#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
1900struct VarLookupKey<'a>(&'a [skrifa::instance::NormalizedCoord]);
1901
1902impl Equivalent<VarKey> for VarLookupKey<'_> {
1903 fn equivalent(&self, other: &VarKey) -> bool {
1904 self.0 == other.as_slice()
1905 }
1906}
1907
1908impl From<VarLookupKey<'_>> for VarKey {
1909 fn from(key: VarLookupKey<'_>) -> Self {
1910 Self::from_slice(key.0)
1911 }
1912}
1913
1914const MAX_CACHED_HINT_INSTANCES: usize = 16;
1918
1919#[derive(Debug)]
1921pub struct HintKey<'a> {
1922 font_id: u64,
1923 font_index: u32,
1924 outlines: &'a OutlineGlyphCollection<'a>,
1925 size: f32,
1926 coords: &'a [skrifa::instance::NormalizedCoord],
1927}
1928
1929impl HintKey<'_> {
1930 fn instance(&self) -> Option<HintingInstance> {
1931 HintingInstance::new(
1932 self.outlines,
1933 Size::new(self.size),
1934 self.coords,
1935 HINTING_OPTIONS,
1936 )
1937 .ok()
1938 }
1939}
1940
1941#[derive(Default)]
1945pub struct HintCache {
1946 glyf_entries: Vec<HintEntry>,
1949 cff_entries: Vec<HintEntry>,
1950 varc_entries: Vec<HintEntry>,
1951 serial: u64,
1952}
1953
1954impl Debug for HintCache {
1955 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1956 f.debug_struct("HintCache")
1957 .field("glyf_entries", &self.glyf_entries.len())
1958 .field("cff_entries", &self.cff_entries.len())
1959 .field("varc_entries", &self.varc_entries.len())
1960 .field("serial", &self.serial)
1961 .finish()
1962 }
1963}
1964
1965impl HintCache {
1966 pub fn get(&mut self, key: &HintKey<'_>) -> Option<&HintingInstance> {
1968 let entries = match key.outlines.format()? {
1969 OutlineGlyphFormat::Glyf => &mut self.glyf_entries,
1970 OutlineGlyphFormat::Cff | OutlineGlyphFormat::Cff2 => &mut self.cff_entries,
1971 OutlineGlyphFormat::Varc => &mut self.varc_entries,
1972 };
1973 let (entry_ix, is_current) = find_hint_entry(entries, key)?;
1974 let entry = entries.get_mut(entry_ix)?;
1975 self.serial += 1;
1976 entry.serial = self.serial;
1977 if !is_current {
1978 entry.font_id = key.font_id;
1979 entry.font_index = key.font_index;
1980 entry
1981 .instance
1982 .reconfigure(
1983 key.outlines,
1984 Size::new(key.size),
1985 key.coords,
1986 HINTING_OPTIONS,
1987 )
1988 .ok()?;
1989 }
1990 Some(&entry.instance)
1991 }
1992
1993 pub fn clear(&mut self) {
1995 self.glyf_entries.clear();
1996 self.cff_entries.clear();
1997 self.varc_entries.clear();
1998 self.serial = 0;
1999 }
2000}
2001
2002struct HintEntry {
2003 font_id: u64,
2004 font_index: u32,
2005 instance: HintingInstance,
2006 serial: u64,
2007}
2008
2009fn find_hint_entry(entries: &mut Vec<HintEntry>, key: &HintKey<'_>) -> Option<(usize, bool)> {
2010 let mut found_serial = u64::MAX;
2011 let mut found_index = 0;
2012 for (ix, entry) in entries.iter().enumerate() {
2013 if entry.font_id == key.font_id
2014 && entry.font_index == key.font_index
2015 && entry.instance.size() == Size::new(key.size)
2016 && entry.instance.location().coords() == key.coords
2017 {
2018 return Some((ix, true));
2019 }
2020 if entry.serial < found_serial {
2021 found_serial = entry.serial;
2022 found_index = ix;
2023 }
2024 }
2025 if entries.len() < MAX_CACHED_HINT_INSTANCES {
2026 let instance = key.instance()?;
2027 let ix = entries.len();
2028 entries.push(HintEntry {
2029 font_id: key.font_id,
2030 font_index: key.font_index,
2031 instance,
2032 serial: 0,
2034 });
2035 Some((ix, true))
2036 } else {
2037 Some((found_index, false))
2038 }
2039}
2040
2041fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
2042 let scale_skew_transform = {
2043 let c = transform.as_coeffs();
2044 Affine::new([c[0], c[1], c[2], c[3], 0.0, 0.0])
2045 };
2046
2047 let x_advance = scale_skew_transform * Point::new(1.0, 0.0);
2048 let y_advance = scale_skew_transform * Point::new(0.0, 1.0);
2049
2050 (
2051 Vec2::new(x_advance.x, x_advance.y),
2052 Vec2::new(y_advance.x, y_advance.y),
2053 )
2054}