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, Line, ParamCurve as _, PathSeg, Shape};
24use crate::peniko::FontData;
25use crate::renderer::{fill_glyph, render_cached_glyph, stroke_glyph};
26use crate::util::AffineExt;
27use alloc::boxed::Box;
28use alloc::sync::Arc;
29use alloc::vec::Vec;
30use core::fmt::{Debug, Formatter};
31use core::ops::RangeInclusive;
32#[cfg(not(feature = "std"))]
33use core_maths::CoreFloat as _;
34use hashbrown::hash_map::{Entry, RawEntryMut};
35use hashbrown::{Equivalent, HashMap};
36use skrifa::bitmap::{BitmapData, BitmapFormat, BitmapStrikes, Origin};
37use skrifa::instance::{LocationRef, Size};
38use skrifa::outline::{DrawSettings, OutlineGlyphFormat};
39use skrifa::outline::{HintingInstance, HintingOptions, OutlinePen};
40use skrifa::raw::TableProvider;
41use skrifa::{FontRef, OutlineGlyphCollection};
42use skrifa::{GlyphId, MetadataProvider};
43use smallvec::SmallVec;
44use vello_common::paint::PaintType;
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) outline_transform: Affine,
155 pub(crate) relative_paint_transform: Affine,
158 pub(crate) cache_key: Option<GlyphCacheKey>,
164}
165
166#[derive(Debug)]
168pub(crate) struct GlyphOutline {
169 pub(crate) path: Arc<BezPath>,
171 pub(crate) bbox: Rect,
173 pub(crate) scale: f64,
175}
176
177#[derive(Debug)]
179pub(crate) struct GlyphBitmap {
180 pub(crate) pixmap: Arc<Pixmap>,
182 pub(crate) area: Rect,
184}
185
186#[derive(Clone, Copy, Debug)]
188pub(crate) struct FontInfo {
189 pub(crate) id: u64,
191 pub(crate) index: u32,
193 pub(crate) upem: f32,
195}
196
197pub struct GlyphColr<'a> {
203 pub skrifa_glyph: skrifa::color::ColorGlyph<'a>,
205 pub location: LocationRef<'a>,
207 pub font_ref: &'a FontRef<'a>,
209 pub(crate) font_info: FontInfo,
211 pub draw_transform: Affine,
213 pub area: Rect,
216 pub pix_width: u16,
218 pub pix_height: u16,
220 pub has_non_default_blend: bool,
222}
223
224impl Debug for GlyphColr<'_> {
225 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
226 write!(f, "GlyphColr")
227 }
228}
229
230#[derive(Debug, Default)]
232pub struct GlyphPrepCache {
233 pub(crate) outline_cache: OutlineCache,
235 pub(crate) hinting_cache: HintCache,
237 pub(crate) underline_exclusions: Vec<(f64, f64)>,
239}
240
241impl GlyphPrepCache {
242 pub fn as_mut(&mut self) -> GlyphPrepCacheMut<'_> {
244 GlyphPrepCacheMut {
245 outline_cache: &mut self.outline_cache,
246 hinting_cache: &mut self.hinting_cache,
247 underline_exclusions: &mut self.underline_exclusions,
248 }
249 }
250
251 pub fn clear(&mut self) {
253 self.outline_cache.clear();
254 self.hinting_cache.clear();
255 self.underline_exclusions.clear();
256 }
257
258 pub fn maintain(&mut self) {
260 self.outline_cache.maintain();
261 }
262}
263
264#[derive(Debug)]
266pub struct GlyphPrepCacheMut<'a> {
267 pub(crate) outline_cache: &'a mut OutlineCache,
269 pub(crate) hinting_cache: &'a mut HintCache,
271 pub(crate) underline_exclusions: &'a mut Vec<(f64, f64)>,
273}
274
275#[derive(Debug)]
277pub enum AtlasCacher<'a> {
278 Disabled,
280 Enabled(&'a mut GlyphAtlas, &'a mut ImageCache),
283}
284
285impl AtlasCacher<'_> {
286 fn config(&self) -> Option<&crate::atlas::GlyphCacheConfig> {
287 match self {
288 Self::Disabled => None,
289 Self::Enabled(glyph_atlas, _) => Some(glyph_atlas.config()),
290 }
291 }
292
293 fn get(&mut self, key: &GlyphCacheKey) -> Option<AtlasSlot> {
294 match self {
295 Self::Disabled => None,
296 Self::Enabled(glyph_atlas, _) => glyph_atlas.get(key),
297 }
298 }
299}
300
301pub trait GlyphRunBackend<'a>: Sized {
303 fn atlas_cache(self, enabled: bool) -> Self;
308
309 fn fill_glyphs<Glyphs>(self, run: GlyphRun<'a>, glyphs: Glyphs)
311 where
312 Glyphs: Iterator<Item = Glyph> + Clone;
313
314 fn stroke_glyphs<Glyphs>(self, run: GlyphRun<'a>, glyphs: Glyphs)
316 where
317 Glyphs: Iterator<Item = Glyph> + Clone;
318
319 fn render_decoration<Glyphs>(
321 self,
322 run: GlyphRun<'a>,
323 glyphs: Glyphs,
324 x_range: RangeInclusive<f32>,
325 baseline_y: f32,
326 offset: f32,
327 size: f32,
328 buffer: f32,
329 ) where
330 Glyphs: Iterator<Item = Glyph> + Clone;
331}
332
333#[derive(Debug)]
335pub struct GlyphRunRenderer<'a, 'b, Glyphs: Iterator<Item = Glyph> + Clone> {
336 prepared_run: PreparedGlyphRun<'a>,
337 outline_cache: &'b mut OutlineCache,
338 underline_span_cache: &'b mut Vec<(f64, f64)>,
339 glyph_iterator: Glyphs,
340 atlas_cacher: AtlasCacher<'b>,
341}
342
343impl<'a, 'b, Glyphs: Iterator<Item = Glyph> + Clone> GlyphRunRenderer<'a, 'b, Glyphs> {
344 pub fn fill_glyphs(&mut self, renderer: &mut impl crate::GlyphRenderer) {
346 self.draw_glyphs(Style::Fill, renderer);
347 }
348
349 pub fn stroke_glyphs(&mut self, renderer: &mut impl crate::GlyphRenderer) {
351 self.draw_glyphs(Style::Stroke, renderer);
352 }
353
354 fn draw_glyphs(&mut self, style: Style, renderer: &mut impl crate::GlyphRenderer) {
362 let font_ref = self.prepared_run.font.as_skrifa();
363
364 let outlines = font_ref.outline_glyphs();
365 let color_glyphs = font_ref.color_glyphs();
366 let bitmaps = font_ref.bitmap_strikes();
367
368 let mut outline_cache_session = OutlineCacheSession::new(
369 self.outline_cache,
370 VarLookupKey::new(self.prepared_run.normalized_coords),
371 );
372 let PreparedGlyphRun {
373 draw_props,
374 scene_paint_transform,
375 run_size: _,
376 font_info,
377 font_embolden,
378 normalized_coords,
379 hinting_instance,
380 ..
381 } = self.prepared_run;
382
383 let hinted = hinting_instance.is_some();
384
385 let colr_bitmap_cache_enabled = self
386 .atlas_cacher
387 .config()
388 .is_some_and(|config| draw_props.font_size <= config.max_cached_font_size);
389 let outline_cache_enabled = colr_bitmap_cache_enabled
390 && style == Style::Fill
394 && matches!(renderer.current_paint(), PaintType::Solid(_));
397
398 let context_color = renderer.get_context_color();
399 let context_color_packed = pack_color(context_color);
400 let scale_props =
401 GlyphScaleProperties::new(draw_props.font_size, font_info.upem, hinted, style);
402
403 for glyph in self.glyph_iterator.clone() {
404 let glyph_id = GlyphId::new(glyph.id);
408
409 let outline_transform =
415 calculate_outline_transform(glyph, draw_props, hinting_instance);
416 let outline_draw_transform = outline_transform.pre_scale(scale_props.draw_scale);
417
418 let outline_cache_key = outline_cache_enabled.then(|| {
425 let fractional_x = outline_transform.translation().x.fract() as f32;
426 GlyphCacheKey::new(
427 font_info.id,
428 font_info.index,
429 glyph.id,
430 draw_props.font_size,
431 hinted,
432 fractional_x,
433 BLACK,
434 BLACK_PACKED,
435 font_embolden,
436 normalized_coords,
437 )
438 });
439 if let Some(ref key) = outline_cache_key
440 && let Some(cached_slot) = self.atlas_cacher.get(key)
441 {
442 render_cached_glyph(
443 renderer,
444 cached_slot,
445 outline_transform,
446 CachedGlyphType::Outline,
447 );
448 continue;
449 }
450
451 if let Some(color_glyph) = color_glyphs.get(glyph_id) {
453 let location = LocationRef::new(normalized_coords);
454 let metrics = calculate_colr_metrics(
455 draw_props.font_size,
456 draw_props,
457 glyph,
458 &font_ref,
459 &color_glyph,
460 location,
461 &mut outline_cache_session,
462 font_info,
463 );
464 let outline_transform = calculate_colr_transform(&metrics);
465
466 let cache_key = colr_bitmap_cache_enabled.then(|| GlyphCacheKey {
469 font_id: font_info.id,
470 font_index: font_info.index,
471 glyph_id: glyph.id,
472 size_bits: draw_props.font_size.to_bits(),
473 hinted: false,
474 subpixel_x: SUBPIXEL_COLR,
475 context_color,
476 context_color_packed,
477 embolden_x_bits: 0,
478 embolden_y_bits: 0,
479 embolden_join_bits: join_bits(Join::Miter),
480 embolden_miter_limit_bits: 4.0_f32.to_bits(),
481 embolden_tolerance_bits: 0.1_f32.to_bits(),
482 var_coords: SmallVec::from_slice(normalized_coords),
483 });
484
485 if let Some(ref key) = cache_key
486 && let Some(cached_slot) = self.atlas_cacher.get(key)
487 {
488 let area = Rect::new(
490 0.0,
491 0.0,
492 metrics.scaled_bbox.width(),
493 metrics.scaled_bbox.height(),
494 );
495 render_cached_glyph(
496 renderer,
497 cached_slot,
498 outline_transform,
499 CachedGlyphType::Colr(area),
500 );
501 continue;
502 }
503
504 let glyph_type = create_colr_glyph(
506 &font_ref,
507 &metrics,
508 color_glyph,
509 normalized_coords,
510 font_info,
511 );
512
513 let prepared_glyph = PreparedGlyph {
514 glyph_type,
515 outline_transform,
516 relative_paint_transform: Affine::IDENTITY,
517 cache_key,
518 };
519 match style {
520 Style::Fill => fill_glyph(
521 renderer,
522 prepared_glyph,
523 &mut self.atlas_cacher,
524 &mut outline_cache_session,
525 ),
526 Style::Stroke => stroke_glyph(
527 renderer,
528 prepared_glyph,
529 &mut self.atlas_cacher,
530 &mut outline_cache_session,
531 ),
532 }
533 continue;
534 }
535
536 let bitmap_data: Option<(skrifa::bitmap::BitmapGlyph<'_>, Pixmap)> = bitmaps
538 .glyph_for_size(Size::new(draw_props.font_size), glyph_id)
539 .and_then(|g| match g.data {
540 #[cfg(feature = "png")]
541 BitmapData::Png(data) => Pixmap::from_png(std::io::Cursor::new(data))
542 .ok()
543 .map(|d| (g, d)),
544 #[cfg(not(feature = "png"))]
545 BitmapData::Png(_) => None,
546 BitmapData::Bgra(_) => None,
549 BitmapData::Mask(_) => None,
550 });
551
552 if let Some((bitmap_glyph, pixmap)) = bitmap_data {
553 let bitmap_ppem = bitmap_glyph.ppem_x;
556 let outline_transform = calculate_bitmap_transform(
557 glyph,
558 &pixmap,
559 draw_props,
560 draw_props.font_size,
561 font_info.upem,
562 &bitmap_glyph,
563 &bitmaps,
564 );
565
566 let cache_key = colr_bitmap_cache_enabled.then(|| GlyphCacheKey {
569 font_id: font_info.id,
570 font_index: font_info.index,
571 glyph_id: glyph.id,
572 size_bits: bitmap_ppem.to_bits(),
573 hinted: false,
574 subpixel_x: SUBPIXEL_BITMAP,
575 context_color: BLACK,
576 context_color_packed: BLACK_PACKED,
577 embolden_x_bits: 0,
578 embolden_y_bits: 0,
579 embolden_join_bits: join_bits(Join::Miter),
580 embolden_miter_limit_bits: 4.0_f32.to_bits(),
581 embolden_tolerance_bits: 0.1_f32.to_bits(),
582 var_coords: SmallVec::new(),
583 });
584
585 if let Some(ref key) = cache_key
586 && let Some(cached_slot) = self.atlas_cacher.get(key)
587 {
588 render_cached_glyph(
589 renderer,
590 cached_slot,
591 outline_transform,
592 CachedGlyphType::Bitmap,
593 );
594 continue;
595 }
596
597 let glyph_type = create_bitmap_glyph(pixmap);
599
600 let prepared_glyph = PreparedGlyph {
601 glyph_type,
602 outline_transform,
603 relative_paint_transform: Affine::IDENTITY,
604 cache_key,
605 };
606 match style {
607 Style::Fill => fill_glyph(
608 renderer,
609 prepared_glyph,
610 &mut self.atlas_cacher,
611 &mut outline_cache_session,
612 ),
613 Style::Stroke => stroke_glyph(
614 renderer,
615 prepared_glyph,
616 &mut self.atlas_cacher,
617 &mut outline_cache_session,
618 ),
619 }
620 continue;
621 }
622
623 let Some(outline) = outlines.get(glyph_id) else {
630 continue;
631 };
632
633 let glyph_type = create_outline_glyph(
634 glyph.id,
635 font_info,
636 &mut outline_cache_session,
637 scale_props.cache_size,
638 scale_props.draw_scale,
639 font_embolden,
640 &outline,
641 hinting_instance,
642 normalized_coords,
643 );
644
645 let relative_paint_transform = outline_draw_transform.inverse() * scene_paint_transform;
646
647 let prepared_glyph = PreparedGlyph {
648 glyph_type,
649 outline_transform,
650 relative_paint_transform,
651 cache_key: outline_cache_key,
652 };
653 match style {
654 Style::Fill => fill_glyph(
655 renderer,
656 prepared_glyph,
657 &mut self.atlas_cacher,
658 &mut outline_cache_session,
659 ),
660 Style::Stroke => stroke_glyph(
661 renderer,
662 prepared_glyph,
663 &mut self.atlas_cacher,
664 &mut outline_cache_session,
665 ),
666 }
667 }
668 }
669
670 pub fn stroke_adjustment(&self) -> f64 {
673 let run_size = self.prepared_run.run_size;
674
675 if run_size == 0.0 {
676 1.0
677 } else {
678 f64::from(self.prepared_run.draw_props.font_size / run_size)
679 }
680 }
681
682 pub fn render_decoration(
691 &mut self,
692 x_range: RangeInclusive<f32>,
693 baseline_y: f32,
694 offset: f32,
695 size: f32,
696 buffer: f32,
697 renderer: &mut impl crate::DrawSink,
698 ) {
699 self.decoration_spans(x_range, baseline_y, offset, size, buffer)
700 .for_each(|rect| {
701 renderer.fill_rect(&rect);
702 });
703 }
704
705 fn decoration_spans<'c>(
706 &'c mut self,
707 x_range: RangeInclusive<f32>,
708 baseline_y: f32,
709 offset: f32,
710 size: f32,
711 buffer: f32,
712 ) -> impl Iterator<Item = Rect> + 'c {
713 let font_ref = self.prepared_run.font.as_skrifa();
714 let outlines = font_ref.outline_glyphs();
715
716 let PreparedGlyphRun {
717 draw_props,
718 font_info,
719 font_embolden,
720 hinting_instance,
721 ..
722 } = self.prepared_run;
723
724 let scale_props = GlyphScaleProperties::new(
732 draw_props.font_size,
733 font_info.upem,
734 hinting_instance.is_some(),
735 Style::Fill,
736 );
737 let outline_to_nominal_scale =
738 f64::from(self.prepared_run.run_size / scale_props.cache_size);
739 let outline_transform = self
740 .prepared_run
741 .glyph_transform
742 .unwrap_or(Affine::IDENTITY)
743 * Affine::FLIP_Y
744 * Affine::scale(outline_to_nominal_scale);
745
746 let buffer = f64::from(buffer);
748
749 let x0 = f64::from(*x_range.start());
751 let x1 = f64::from(*x_range.end());
752
753 let layout_y0 = f64::from(-offset);
756 let layout_y1 = f64::from(-offset + size);
757
758 let var_key = VarLookupKey::new(self.prepared_run.normalized_coords);
760 let mut outline_cache_session = OutlineCacheSession::new(self.outline_cache, var_key);
761
762 let exclusions = &mut self.underline_span_cache;
764 exclusions.truncate(0);
766
767 for glyph in self.glyph_iterator.clone() {
768 let Some(outline) = outlines.get(GlyphId::new(glyph.id)) else {
770 continue;
771 };
772
773 let cached = outline_cache_session.get_or_insert(
774 glyph.id,
775 font_info,
776 scale_props.cache_size,
777 font_embolden,
778 var_key,
779 &outline,
780 hinting_instance,
781 );
782
783 let [_, b, _, d, _, f] = outline_transform.as_coeffs();
789 let (y_min, y_max) = {
790 let bx0 = b * cached.bbox.x0;
791 let bx1 = b * cached.bbox.x1;
792 let dy0 = d * cached.bbox.y0;
793 let dy1 = d * cached.bbox.y1;
794 (
795 f + bx0.min(bx1) + dy0.min(dy1),
796 f + bx0.max(bx1) + dy0.max(dy1),
797 )
798 };
799 if y_max < layout_y0 || y_min > layout_y1 {
800 continue;
801 }
802
803 let mut rect = Rect {
804 x0: f64::INFINITY,
805 x1: f64::NEG_INFINITY,
806 y0: layout_y0,
807 y1: layout_y1,
808 };
809
810 for seg in cached.path.segments() {
811 let seg = outline_transform * seg;
813 expand_rect_with_segment(&mut rect, seg, layout_y0..=layout_y1);
814 }
815
816 let excl_start = (rect.x0 + f64::from(glyph.x) - buffer).max(x0);
818 let excl_end = (rect.x1 + f64::from(glyph.x) + buffer).min(x1);
819
820 if excl_start >= excl_end {
822 continue;
823 }
824
825 insert_and_merge_range(exclusions, excl_start, excl_end);
827 }
828
829 let y0 = f64::from(baseline_y) + layout_y0;
831 let y1 = f64::from(baseline_y) + layout_y1;
832
833 let mut state = Some((exclusions.drain(..), x0));
834 core::iter::from_fn(move || {
835 let (iter, current_x) = state.as_mut()?;
836 let Some((excl_start, excl_end)) = iter.next() else {
837 let final_rect = Rect::new(*current_x, y0, x1, y1);
839 state = None;
840 return (final_rect.width() > 0.0).then_some(final_rect);
841 };
842
843 let rect = Rect::new(*current_x, y0, excl_start, y1);
845 *current_x = excl_end;
846 Some(rect)
847 })
848 }
849}
850
851#[derive(Debug)]
853#[must_use = "Methods on the builder don't do anything until `render` is called."]
854pub struct GlyphRunBuilder<'a, B> {
855 run: GlyphRun<'a>,
856 backend: B,
857}
858
859impl<'a, B> GlyphRunBuilder<'a, B> {
860 pub fn new(font: FontData, transform: Affine, paint_transform: Affine, backend: B) -> Self {
862 Self {
863 run: GlyphRun {
865 font,
866 font_size: 16.0,
867 font_embolden: FontEmbolden::default(),
868 transform,
869 scene_paint_transform: transform * paint_transform,
870 glyph_transform: None,
871 hint: true,
872 normalized_coords: &[],
873 },
874 backend,
875 }
876 }
877
878 pub fn font_size(mut self, size: f32) -> Self {
880 self.run.font_size = size;
881 self
882 }
883
884 pub fn font_embolden(mut self, embolden: FontEmbolden) -> Self {
886 self.run.font_embolden = embolden;
887 self
888 }
889
890 pub fn glyph_transform(mut self, transform: Affine) -> Self {
893 self.run.glyph_transform = Some(transform);
894 self
895 }
896
897 pub fn hint(mut self, hint: bool) -> Self {
902 self.run.hint = hint;
903 self
904 }
905
906 pub fn normalized_coords(mut self, coords: &'a [NormalizedCoord]) -> Self {
908 self.run.normalized_coords = bytemuck::cast_slice(coords);
909 self
910 }
911}
912
913impl<'a> GlyphRun<'a> {
914 #[doc(hidden)]
920 pub fn build<'b: 'a, Glyphs: Iterator<Item = Glyph> + Clone>(
921 self,
922 glyphs: Glyphs,
923 prep_cache: GlyphPrepCacheMut<'b>,
924 atlas_cacher: AtlasCacher<'b>,
925 ) -> GlyphRunRenderer<'a, 'b, Glyphs> {
926 let prepared_run = prepare_glyph_run(self, prep_cache.hinting_cache);
927 GlyphRunRenderer {
928 prepared_run,
929 glyph_iterator: glyphs,
930 outline_cache: prep_cache.outline_cache,
931 underline_span_cache: prep_cache.underline_exclusions,
932 atlas_cacher,
933 }
934 }
935}
936
937impl<'a, B> GlyphRunBuilder<'a, B>
938where
939 B: GlyphRunBackend<'a>,
940{
941 pub fn atlas_cache(self, enabled: bool) -> Self {
946 Self {
947 run: self.run,
948 backend: self.backend.atlas_cache(enabled),
949 }
950 }
951
952 pub fn fill_glyphs<Glyphs>(self, glyphs: Glyphs)
954 where
955 Glyphs: Iterator<Item = Glyph> + Clone,
956 {
957 let GlyphRunBuilder { run, backend } = self;
958 backend.fill_glyphs(run, glyphs);
959 }
960
961 pub fn stroke_glyphs<Glyphs>(self, glyphs: Glyphs)
963 where
964 Glyphs: Iterator<Item = Glyph> + Clone,
965 {
966 let GlyphRunBuilder { run, backend } = self;
967 backend.stroke_glyphs(run, glyphs);
968 }
969
970 pub fn render_decoration<Glyphs>(
974 self,
975 glyphs: Glyphs,
976 x_range: RangeInclusive<f32>,
977 baseline_y: f32,
978 offset: f32,
979 size: f32,
980 buffer: f32,
981 ) where
982 Glyphs: Iterator<Item = Glyph> + Clone,
983 {
984 let GlyphRunBuilder { run, backend } = self;
985 backend.render_decoration(run, glyphs, x_range, baseline_y, offset, size, buffer);
986 }
987}
988
989fn insert_and_merge_range(ranges: &mut Vec<(f64, f64)>, start: f64, end: f64) {
991 let insert_pos = ranges
994 .iter()
995 .rposition(|r| r.0 <= start)
996 .map_or(0, |i| i + 1);
997
998 let merge_start = insert_pos
1000 .checked_sub(1)
1001 .filter(|&i| ranges[i].1 >= start)
1002 .unwrap_or(insert_pos);
1003
1004 let new_end = ranges[merge_start..]
1006 .iter()
1007 .take_while(|(s, _)| *s <= end)
1008 .fold(end, |acc, (_, e)| acc.max(*e));
1009
1010 let merge_end = merge_start
1011 + ranges[merge_start..]
1012 .iter()
1013 .take_while(|(s, _)| *s <= new_end)
1014 .count();
1015
1016 if merge_start < merge_end {
1018 let new_start = start.min(ranges[merge_start].0);
1019 ranges.splice(merge_start..merge_end, [(new_start, new_end)]);
1020 } else {
1021 ranges.insert(insert_pos, (start, end));
1022 }
1023}
1024
1025fn expand_rect_with_segment(rect: &mut Rect, seg: PathSeg, y_span: RangeInclusive<f64>) {
1026 let (mut x_bounds, y_bounds) = match seg {
1030 PathSeg::Line(line) => (
1031 (line.p0.x.min(line.p1.x), line.p0.x.max(line.p1.x)),
1032 (line.p0.y.min(line.p1.y), line.p0.y.max(line.p1.y)),
1033 ),
1034 PathSeg::Quad(quad) => (
1035 (
1036 quad.p0.x.min(quad.p1.x).min(quad.p2.x),
1037 quad.p0.x.max(quad.p1.x).max(quad.p2.x),
1038 ),
1039 (
1040 quad.p0.y.min(quad.p1.y).min(quad.p2.y),
1041 quad.p0.y.max(quad.p1.y).max(quad.p2.y),
1042 ),
1043 ),
1044 PathSeg::Cubic(cubic) => (
1045 (
1046 cubic.p0.x.min(cubic.p1.x).min(cubic.p2.x).min(cubic.p3.x),
1047 cubic.p0.x.max(cubic.p1.x).max(cubic.p2.x).max(cubic.p3.x),
1048 ),
1049 (
1050 cubic.p0.y.min(cubic.p1.y).min(cubic.p2.y).min(cubic.p3.y),
1051 cubic.p0.y.max(cubic.p1.y).max(cubic.p2.y).max(cubic.p3.y),
1052 ),
1053 ),
1054 };
1055 if y_bounds.1 < *y_span.start() || y_bounds.0 > *y_span.end() {
1057 return;
1058 }
1059
1060 x_bounds.0 -= 1.0;
1063 x_bounds.1 += 1.0;
1064 let top_line = Line::new((x_bounds.0, *y_span.start()), (x_bounds.1, *y_span.start()));
1065 let bottom_line = Line::new((x_bounds.0, *y_span.end()), (x_bounds.1, *y_span.end()));
1066
1067 for intersection in seg.intersect_line(top_line) {
1068 let point = top_line.eval(intersection.line_t);
1069 rect.x0 = rect.x0.min(point.x);
1072 rect.x1 = rect.x1.max(point.x);
1073 }
1074
1075 for intersection in seg.intersect_line(bottom_line) {
1076 let point = bottom_line.eval(intersection.line_t);
1077 rect.x0 = rect.x0.min(point.x);
1078 rect.x1 = rect.x1.max(point.x);
1079 }
1080
1081 let (seg_start, seg_end) = match seg {
1083 PathSeg::Line(line) => (line.p0, line.p1),
1084 PathSeg::Quad(quad) => (quad.p0, quad.p2),
1085 PathSeg::Cubic(cubic) => (cubic.p0, cubic.p3),
1086 };
1087
1088 for point in [seg_start, seg_end] {
1089 if (*y_span.start()..=*y_span.end()).contains(&point.y) {
1090 rect.x0 = rect.x0.min(point.x);
1091 rect.x1 = rect.x1.max(point.x);
1092 }
1093 }
1094}
1095
1096fn create_outline_glyph<'a>(
1101 glyph_id: u32,
1102 font_info: FontInfo,
1103 outline_cache: &mut OutlineCacheSession<'_>,
1104 size: f32,
1105 scale: f64,
1106 embolden: FontEmbolden,
1107 outline_glyph: &skrifa::outline::OutlineGlyph<'a>,
1108 hinting_instance: Option<&HintingInstance>,
1109 normalized_coords: &[skrifa::instance::NormalizedCoord],
1110) -> GlyphType<'a> {
1111 let cached = outline_cache.get_or_insert(
1112 glyph_id,
1113 font_info,
1114 size,
1115 embolden,
1116 VarLookupKey::new(normalized_coords),
1117 outline_glyph,
1118 hinting_instance,
1119 );
1120
1121 GlyphType::Outline(GlyphOutline {
1122 path: Arc::clone(cached.path),
1123 bbox: cached.bbox,
1124 scale,
1125 })
1126}
1127
1128struct GlyphScaleProperties {
1129 cache_size: f32,
1131 draw_scale: f64,
1134}
1135
1136impl GlyphScaleProperties {
1137 fn new(draw_font_size: f32, upem: f32, hinted: bool, style: Style) -> Self {
1138 if hinted || style == Style::Stroke {
1139 Self {
1146 cache_size: draw_font_size,
1147 draw_scale: 1.0,
1148 }
1149 } else {
1150 Self {
1151 cache_size: upem,
1152 draw_scale: f64::from(draw_font_size / upem),
1153 }
1154 }
1155 }
1156}
1157
1158fn calculate_outline_transform(
1166 glyph: Glyph,
1167 draw_props: DrawProps,
1168 hinting_instance: Option<&HintingInstance>,
1169) -> Affine {
1170 let mut final_transform = draw_props
1171 .positioned_transform(glyph)
1172 .pre_scale_non_uniform(1.0, -1.0)
1173 .as_coeffs();
1174
1175 if hinting_instance.is_some() {
1176 final_transform[5] = final_transform[5].round();
1177 }
1178
1179 Affine::new(final_transform)
1180}
1181
1182fn create_bitmap_glyph(pixmap: Pixmap) -> GlyphType<'static> {
1187 let area = Rect::new(
1190 0.0,
1191 0.0,
1192 f64::from(pixmap.width()),
1193 f64::from(pixmap.height()),
1194 );
1195
1196 GlyphType::Bitmap(GlyphBitmap {
1197 pixmap: Arc::new(pixmap),
1198 area,
1199 })
1200}
1201
1202fn calculate_bitmap_transform(
1211 glyph: Glyph,
1212 pixmap: &Pixmap,
1213 draw_props: DrawProps,
1214 font_size: f32,
1215 upem: f32,
1216 bitmap_glyph: &skrifa::bitmap::BitmapGlyph<'_>,
1217 bitmaps: &BitmapStrikes<'_>,
1218) -> Affine {
1219 let x_scale_factor = font_size / bitmap_glyph.ppem_x;
1220 let y_scale_factor = font_size / bitmap_glyph.ppem_y;
1221 let font_units_to_size = font_size / upem;
1222
1223 let bearing_y = if bitmap_glyph.bearing_y == 0.0 && bitmaps.format() == Some(BitmapFormat::Sbix)
1229 {
1230 100.0
1231 } else {
1232 bitmap_glyph.bearing_y
1233 };
1234
1235 let origin_shift = match bitmap_glyph.placement_origin {
1236 Origin::TopLeft => Vec2::default(),
1237 Origin::BottomLeft => Vec2 {
1238 x: 0.,
1239 y: -f64::from(pixmap.height()),
1240 },
1241 };
1242
1243 draw_props
1244 .positioned_transform(glyph)
1245 .pre_translate(Vec2 {
1247 x: (-bitmap_glyph.bearing_x * font_units_to_size).into(),
1248 y: (bearing_y * font_units_to_size).into(),
1249 })
1250 .pre_scale_non_uniform(f64::from(x_scale_factor), f64::from(y_scale_factor))
1252 .pre_translate(Vec2 {
1254 x: (-bitmap_glyph.inner_bearing_x).into(),
1255 y: (-bitmap_glyph.inner_bearing_y).into(),
1256 })
1257 .pre_translate(origin_shift)
1258}
1259
1260struct ColrMetrics {
1262 transform: Affine,
1264 scaled_bbox: Rect,
1266 scale_factor_x: f64,
1268 scale_factor_y: f64,
1270 font_size_scale: f64,
1272 has_non_default_blend: bool,
1273}
1274
1275fn calculate_colr_metrics<'a>(
1280 font_size: f32,
1281 draw_props: DrawProps,
1282 glyph: Glyph,
1283 font_ref: &'a FontRef<'a>,
1284 color_glyph: &skrifa::color::ColorGlyph<'a>,
1285 location: LocationRef<'a>,
1286 outline_cache: &mut OutlineCacheSession<'_>,
1287 font_info: FontInfo,
1288) -> ColrMetrics {
1289 let font_size_scale = (font_size / font_info.upem) as f64;
1291 let transform = draw_props.positioned_transform(glyph);
1292
1293 let (scale_factor_x, scale_factor_y) = {
1297 let (x_vec, y_vec) = x_y_advances(&transform.pre_scale(font_size_scale));
1298 (x_vec.length(), y_vec.length())
1299 };
1300
1301 let colr_info = get_colr_info(font_ref, color_glyph, location, outline_cache, font_info);
1303 let bbox = color_glyph
1304 .bounding_box(location, Size::unscaled())
1307 .map(convert_bounding_box)
1308 .or(colr_info.bbox)
1310 .unwrap_or(Rect::ZERO);
1311
1312 let scaled_bbox = Rect {
1315 x0: bbox.x0 * scale_factor_x,
1316 y0: bbox.y0 * scale_factor_y,
1317 x1: bbox.x1 * scale_factor_x,
1318 y1: bbox.y1 * scale_factor_y,
1319 };
1320
1321 ColrMetrics {
1322 transform,
1323 scaled_bbox,
1324 scale_factor_x,
1325 scale_factor_y,
1326 font_size_scale,
1327 has_non_default_blend: colr_info.has_non_default_blend,
1328 }
1329}
1330
1331fn calculate_colr_transform(metrics: &ColrMetrics) -> Affine {
1339 metrics.transform
1340 * Affine::scale_non_uniform(1.0, -1.0)
1350 * Affine::scale_non_uniform(
1358 metrics.font_size_scale / metrics.scale_factor_x,
1359 metrics.font_size_scale / metrics.scale_factor_y,
1360 )
1361 * Affine::translate((metrics.scaled_bbox.x0, metrics.scaled_bbox.y0))
1364}
1365
1366fn create_colr_glyph<'a>(
1371 font_ref: &'a FontRef<'a>,
1372 metrics: &ColrMetrics,
1373 color_glyph: skrifa::color::ColorGlyph<'a>,
1374 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
1375 font_info: FontInfo,
1376) -> GlyphType<'a> {
1377 let (pix_width, pix_height) = (
1378 metrics.scaled_bbox.width().ceil() as u16,
1379 metrics.scaled_bbox.height().ceil() as u16,
1380 );
1381
1382 let draw_transform =
1383 Affine::translate((-metrics.scaled_bbox.x0, -metrics.scaled_bbox.y0)) *
1386 Affine::scale_non_uniform(metrics.scale_factor_x, metrics.scale_factor_y);
1388
1389 let area = Rect::new(
1392 0.0,
1393 0.0,
1394 metrics.scaled_bbox.width(),
1395 metrics.scaled_bbox.height(),
1396 );
1397
1398 let location = LocationRef::new(normalized_coords);
1399
1400 GlyphType::Colr(Box::new(GlyphColr {
1401 skrifa_glyph: color_glyph,
1402 font_ref,
1403 location,
1404 area,
1405 pix_width,
1406 pix_height,
1407 draw_transform,
1408 has_non_default_blend: metrics.has_non_default_blend,
1409 font_info,
1410 }))
1411}
1412
1413trait FontDataExt {
1414 fn as_skrifa(&self) -> FontRef<'_>;
1415}
1416
1417impl FontDataExt for FontData {
1418 fn as_skrifa(&self) -> FontRef<'_> {
1419 FontRef::from_index(self.data.data(), self.index).unwrap()
1420 }
1421}
1422
1423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1425pub(crate) enum Style {
1426 Fill,
1428 Stroke,
1430}
1431
1432#[derive(Clone, Debug)]
1434pub struct GlyphRun<'a> {
1435 font: FontData,
1437 font_size: f32,
1439 font_embolden: FontEmbolden,
1441 transform: Affine,
1443 scene_paint_transform: Affine,
1445 glyph_transform: Option<Affine>,
1448 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
1450 hint: bool,
1452}
1453
1454struct PreparedGlyphRun<'a> {
1455 font: FontData,
1457 font_info: FontInfo,
1459 run_size: f32,
1467 font_embolden: FontEmbolden,
1469 glyph_transform: Option<Affine>,
1471 draw_props: DrawProps,
1489 scene_paint_transform: Affine,
1491 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
1492 hinting_instance: Option<&'a HintingInstance>,
1493}
1494
1495#[derive(Clone, Copy, Debug)]
1497struct DrawProps {
1498 positioning_transform: Affine,
1509 effective_transform: Affine,
1511 font_size: f32,
1514}
1515
1516impl DrawProps {
1517 #[inline]
1518 fn positioned_transform(self, glyph: Glyph) -> Affine {
1519 let translation = self.positioning_transform * Point::new(glyph.x as f64, glyph.y as f64);
1524
1525 Affine::translate(translation.to_vec2()) * self.effective_transform
1528 }
1529}
1530
1531impl Debug for PreparedGlyphRun<'_> {
1532 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1533 f.debug_struct("PreparedGlyphRun")
1535 .field("font", &self.font)
1536 .field("font_info", &self.font_info)
1537 .field("run_size", &self.run_size)
1538 .field("font_embolden", &self.font_embolden)
1539 .field("glyph_transform", &self.glyph_transform)
1540 .field("transforms", &self.draw_props)
1541 .field("normalized_coords", &self.normalized_coords)
1542 .finish()
1543 }
1544}
1545
1546fn prepare_glyph_run<'a>(run: GlyphRun<'a>, hint_cache: &'a mut HintCache) -> PreparedGlyphRun<'a> {
1548 let full_transform = run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY);
1549 let [_, _, t_c, t_d, t_e, t_f] = full_transform.as_coeffs();
1550
1551 #[derive(Clone, Copy, Debug)]
1553 enum PreparedGlyphRunMode {
1554 Direct,
1559 AbsorbScaleUnhinted,
1561 AbsorbScaleHinted,
1563 }
1564
1565 let mode = if !run.hint {
1566 if full_transform.is_positive_uniform_scale_without_skew() {
1571 PreparedGlyphRunMode::AbsorbScaleUnhinted
1572 } else {
1573 PreparedGlyphRunMode::Direct
1574 }
1575 } else {
1576 if full_transform.is_positive_uniform_scale_without_vertical_skew() {
1587 PreparedGlyphRunMode::AbsorbScaleHinted
1588 } else {
1589 PreparedGlyphRunMode::Direct
1590 }
1591 };
1592
1593 let (effective_transform, draw_font_size, hinting_instance) = match mode {
1594 PreparedGlyphRunMode::Direct => (full_transform, run.font_size, None),
1595 PreparedGlyphRunMode::AbsorbScaleUnhinted => (
1596 Affine::new([1., 0., 0., 1., t_e, t_f]),
1597 run.font_size * t_d as f32,
1598 None,
1599 ),
1600 PreparedGlyphRunMode::AbsorbScaleHinted => {
1601 let vertical_font_size = run.font_size * t_d as f32;
1602 let font_ref = run.font.as_skrifa();
1603 let outlines = font_ref.outline_glyphs();
1604 let hinting_instance = hint_cache.get(&HintKey {
1605 font_id: run.font.data.id(),
1606 font_index: run.font.index,
1607 outlines: &outlines,
1608 size: vertical_font_size,
1609 coords: run.normalized_coords,
1610 });
1611
1612 (
1613 Affine::new([1., 0., t_c / t_d, 1., t_e, t_f]),
1618 vertical_font_size,
1619 hinting_instance,
1620 )
1621 }
1622 };
1623
1624 let upem = run
1625 .font
1626 .as_skrifa()
1627 .head()
1628 .map(|h| h.units_per_em())
1629 .unwrap()
1630 .into();
1631 let font_info = FontInfo {
1632 id: run.font.data.id(),
1633 index: run.font.index,
1634 upem,
1635 };
1636
1637 PreparedGlyphRun {
1638 font: run.font,
1639 font_info,
1640 run_size: run.font_size,
1641 font_embolden: run.font_embolden,
1642 glyph_transform: run.glyph_transform,
1643 draw_props: DrawProps {
1644 positioning_transform: run
1645 .transform
1646 .with_translation(Vec2::ZERO),
1649 effective_transform,
1650 font_size: draw_font_size,
1651 },
1652 scene_paint_transform: run.scene_paint_transform,
1653 normalized_coords: run.normalized_coords,
1654 hinting_instance,
1655 }
1656}
1657
1658const HINTING_OPTIONS: HintingOptions = HintingOptions {
1661 engine: skrifa::outline::Engine::AutoFallback,
1662 target: skrifa::outline::Target::Smooth {
1663 mode: skrifa::outline::SmoothMode::Lcd,
1664 symmetric_rendering: false,
1665 preserve_linear_metrics: true,
1666 },
1667};
1668
1669#[derive(Clone, Default)]
1670pub(crate) struct OutlinePath {
1671 pub(crate) path: BezPath,
1672}
1673
1674impl OutlinePath {
1675 pub(crate) fn new() -> Self {
1676 Self {
1677 path: BezPath::new(),
1678 }
1679 }
1680
1681 pub(crate) fn reuse(&mut self) {
1682 self.path.truncate(0);
1683 }
1684}
1685
1686impl OutlinePen for OutlinePath {
1688 #[inline]
1689 fn move_to(&mut self, x: f32, y: f32) {
1690 self.path.move_to((x, y));
1691 }
1692
1693 #[inline]
1694 fn line_to(&mut self, x: f32, y: f32) {
1695 self.path.line_to((x, y));
1696 }
1697
1698 #[inline]
1699 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
1700 self.path.curve_to((cx0, cy0), (cx1, cy1), (x, y));
1701 }
1702
1703 #[inline]
1704 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
1705 self.path.quad_to((cx, cy), (x, y));
1706 }
1707
1708 #[inline]
1709 fn close(&mut self) {
1710 self.path.close_path();
1711 }
1712}
1713
1714pub type NormalizedCoord = i16;
1724
1725#[derive(Debug, Default)]
1731pub struct GlyphCaches {
1732 pub(crate) outline_cache: OutlineCache,
1734 pub(crate) hinting_cache: HintCache,
1736 pub(crate) underline_exclusions: Vec<(f64, f64)>,
1738 pub(crate) glyph_atlas: GlyphAtlas,
1740}
1741
1742impl GlyphCaches {
1743 pub fn clear(&mut self) {
1745 self.outline_cache.clear();
1746 self.hinting_cache.clear();
1747 self.underline_exclusions.clear();
1748 self.glyph_atlas.clear();
1749 }
1750
1751 pub fn maintain(&mut self, image_cache: &mut ImageCache) {
1759 self.outline_cache.maintain();
1760 self.glyph_atlas.maintain(image_cache);
1761 }
1762}
1763
1764#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
1765struct OutlineKey {
1766 font_id: u64,
1767 font_index: u32,
1768 glyph_id: u32,
1769 size_bits: u32,
1770 embolden_x_bits: u32,
1771 embolden_y_bits: u32,
1772 embolden_join_bits: u8,
1773 embolden_miter_limit_bits: u32,
1774 embolden_tolerance_bits: u32,
1775 hint: bool,
1776}
1777
1778#[inline(always)]
1779fn join_bits(join: Join) -> u8 {
1780 match join {
1781 Join::Bevel => 0,
1782 Join::Miter => 1,
1783 Join::Round => 2,
1784 }
1785}
1786
1787#[expect(
1788 clippy::cast_possible_truncation,
1789 reason = "Cache keys intentionally store embolden parameters at f32 precision."
1790)]
1791#[inline(always)]
1792fn f32_bits(value: f64) -> u32 {
1793 (value as f32).to_bits()
1794}
1795
1796struct OutlineEntry {
1797 path: Arc<BezPath>,
1798 bbox: Rect,
1799 serial: u32,
1800}
1801
1802impl OutlineEntry {
1803 fn new(path: Arc<BezPath>, bbox: Rect, serial: u32) -> Self {
1804 Self { path, bbox, serial }
1805 }
1806
1807 fn take_path(&mut self) -> Option<OutlinePath> {
1809 let arc = core::mem::replace(&mut self.path, Arc::new(BezPath::new()));
1810 Arc::try_unwrap(arc).ok().map(|path| OutlinePath { path })
1811 }
1812}
1813
1814pub(crate) struct CachedOutline<'a> {
1816 pub(crate) path: &'a Arc<BezPath>,
1817 pub(crate) bbox: Rect,
1818}
1819
1820#[derive(Default)]
1823pub struct OutlineCache {
1824 free_list: Vec<OutlinePath>,
1825 static_map: HashMap<OutlineKey, OutlineEntry>,
1826 variable_map: HashMap<VarKey, HashMap<OutlineKey, OutlineEntry>>,
1827 cached_count: usize,
1828 serial: u32,
1829 last_prune_serial: u32,
1830}
1831
1832impl Debug for OutlineCache {
1833 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1834 f.debug_struct("OutlineCache")
1835 .field("free_list", &self.free_list.len())
1836 .field("static_map", &self.static_map.len())
1837 .field("variable_map", &self.variable_map.len())
1838 .field("cached_count", &self.cached_count)
1839 .field("serial", &self.serial)
1840 .field("last_prune_serial", &self.last_prune_serial)
1841 .finish()
1842 }
1843}
1844
1845impl OutlineCache {
1846 pub fn maintain(&mut self) {
1850 const MAX_ENTRY_AGE: u32 = 64;
1852 const PRUNE_FREQUENCY: u32 = 64;
1854 const CACHED_COUNT_THRESHOLD: usize = 256;
1856 const MAX_FREE_LIST_SIZE: usize = 128;
1858
1859 let free_list = &mut self.free_list;
1860 let serial = self.serial;
1861 self.serial += 1;
1862 if serial - self.last_prune_serial < PRUNE_FREQUENCY
1864 && self.cached_count < CACHED_COUNT_THRESHOLD
1865 {
1866 return;
1867 }
1868 self.last_prune_serial = serial;
1869 self.static_map.retain(|_, entry| {
1870 if serial - entry.serial > MAX_ENTRY_AGE {
1871 if free_list.len() < MAX_FREE_LIST_SIZE {
1872 if let Some(path) = entry.take_path() {
1875 free_list.push(path);
1876 }
1877 }
1878 self.cached_count -= 1;
1879 false
1880 } else {
1881 true
1882 }
1883 });
1884 self.variable_map.retain(|_, map| {
1885 map.retain(|_, entry| {
1886 if serial - entry.serial > MAX_ENTRY_AGE {
1887 if free_list.len() < MAX_FREE_LIST_SIZE
1888 && let Some(path) = entry.take_path()
1889 {
1890 free_list.push(path);
1891 }
1892 self.cached_count -= 1;
1893 false
1894 } else {
1895 true
1896 }
1897 });
1898 !map.is_empty()
1899 });
1900 }
1901
1902 pub fn clear(&mut self) {
1904 self.free_list.clear();
1905 self.static_map.clear();
1906 self.variable_map.clear();
1907 self.cached_count = 0;
1908 self.serial = 0;
1909 self.last_prune_serial = 0;
1910 }
1911}
1912
1913pub(crate) struct OutlineCacheSession<'a> {
1914 map: &'a mut HashMap<OutlineKey, OutlineEntry>,
1915 free_list: &'a mut Vec<OutlinePath>,
1916 serial: u32,
1917 cached_count: &'a mut usize,
1918}
1919
1920impl<'a> OutlineCacheSession<'a> {
1921 fn new(outline_cache: &'a mut OutlineCache, var_key: VarLookupKey<'_>) -> Self {
1922 let map = if var_key.coords().is_empty() {
1923 &mut outline_cache.static_map
1924 } else {
1925 match outline_cache
1926 .variable_map
1927 .raw_entry_mut()
1928 .from_key(&var_key)
1929 {
1930 RawEntryMut::Occupied(entry) => entry.into_mut(),
1931 RawEntryMut::Vacant(entry) => entry.insert(var_key.into(), HashMap::new()).1,
1932 }
1933 };
1934 Self {
1935 map,
1936 free_list: &mut outline_cache.free_list,
1937 serial: outline_cache.serial,
1938 cached_count: &mut outline_cache.cached_count,
1939 }
1940 }
1941
1942 pub(crate) fn get_or_insert(
1943 &mut self,
1944 glyph_id: u32,
1945 font_info: FontInfo,
1946 size: f32,
1947 embolden: FontEmbolden,
1948 var_key: VarLookupKey<'_>,
1949 outline_glyph: &skrifa::outline::OutlineGlyph<'_>,
1950 hinting_instance: Option<&HintingInstance>,
1951 ) -> CachedOutline<'_> {
1952 let key = OutlineKey {
1953 glyph_id,
1954 font_id: font_info.id,
1955 font_index: font_info.index,
1956 size_bits: size.to_bits(),
1957 embolden_x_bits: f32_bits(embolden.amount.xx),
1958 embolden_y_bits: f32_bits(embolden.amount.yy),
1959 embolden_join_bits: join_bits(embolden.join),
1960 embolden_miter_limit_bits: f32_bits(embolden.miter_limit),
1961 embolden_tolerance_bits: f32_bits(embolden.tolerance),
1962 hint: hinting_instance.is_some(),
1963 };
1964
1965 match self.map.entry(key) {
1966 Entry::Occupied(mut entry) => {
1967 entry.get_mut().serial = self.serial;
1968 let entry = entry.into_mut();
1969 CachedOutline {
1970 path: &entry.path,
1971 bbox: entry.bbox,
1972 }
1973 }
1974 Entry::Vacant(entry) => {
1975 let mut drawing_buf = self.free_list.pop().unwrap_or_default();
1977
1978 let draw_settings = if let Some(hinting_instance) = hinting_instance {
1979 DrawSettings::hinted(hinting_instance, false)
1980 } else {
1981 DrawSettings::unhinted(Size::new(size), var_key.coords())
1982 };
1983
1984 drawing_buf.reuse();
1985 outline_glyph.draw(draw_settings, &mut drawing_buf).unwrap();
1986 if embolden.amount != Diagonal2::new(0.0, 0.0) {
1987 drawing_buf.path = kurbo::expand_path(
1988 &drawing_buf.path,
1989 embolden.amount,
1990 embolden.join,
1991 embolden.miter_limit,
1992 embolden.tolerance,
1993 );
1994 }
1995
1996 let bbox = drawing_buf.path.bounding_box();
1997 let entry = entry.insert(OutlineEntry::new(
1998 Arc::new(drawing_buf.path),
1999 bbox,
2000 self.serial,
2001 ));
2002 *self.cached_count += 1;
2003 CachedOutline {
2004 path: &entry.path,
2005 bbox: entry.bbox,
2006 }
2007 }
2008 }
2009 }
2010}
2011
2012type VarKey = SmallVec<[skrifa::instance::NormalizedCoord; 4]>;
2014
2015#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
2017pub(crate) struct VarLookupKey<'a>(&'a [skrifa::instance::NormalizedCoord]);
2018
2019impl<'a> VarLookupKey<'a> {
2020 pub(crate) fn new(coords: &'a [skrifa::instance::NormalizedCoord]) -> Self {
2021 Self(coords)
2022 }
2023
2024 fn coords(self) -> &'a [skrifa::instance::NormalizedCoord] {
2025 self.0
2026 }
2027}
2028
2029impl Equivalent<VarKey> for VarLookupKey<'_> {
2030 fn equivalent(&self, other: &VarKey) -> bool {
2031 self.0 == other.as_slice()
2032 }
2033}
2034
2035impl From<VarLookupKey<'_>> for VarKey {
2036 fn from(key: VarLookupKey<'_>) -> Self {
2037 Self::from_slice(key.0)
2038 }
2039}
2040
2041const MAX_CACHED_HINT_INSTANCES: usize = 16;
2045
2046#[derive(Debug)]
2048pub struct HintKey<'a> {
2049 font_id: u64,
2050 font_index: u32,
2051 outlines: &'a OutlineGlyphCollection<'a>,
2052 size: f32,
2053 coords: &'a [skrifa::instance::NormalizedCoord],
2054}
2055
2056impl HintKey<'_> {
2057 fn instance(&self) -> Option<HintingInstance> {
2058 HintingInstance::new(
2059 self.outlines,
2060 Size::new(self.size),
2061 self.coords,
2062 HINTING_OPTIONS,
2063 )
2064 .ok()
2065 }
2066}
2067
2068#[derive(Default)]
2072pub struct HintCache {
2073 glyf_entries: Vec<HintEntry>,
2076 cff_entries: Vec<HintEntry>,
2077 varc_entries: Vec<HintEntry>,
2078 serial: u64,
2079}
2080
2081impl Debug for HintCache {
2082 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
2083 f.debug_struct("HintCache")
2084 .field("glyf_entries", &self.glyf_entries.len())
2085 .field("cff_entries", &self.cff_entries.len())
2086 .field("varc_entries", &self.varc_entries.len())
2087 .field("serial", &self.serial)
2088 .finish()
2089 }
2090}
2091
2092impl HintCache {
2093 pub fn get(&mut self, key: &HintKey<'_>) -> Option<&HintingInstance> {
2095 let entries = match key.outlines.format()? {
2096 OutlineGlyphFormat::Glyf => &mut self.glyf_entries,
2097 OutlineGlyphFormat::Cff | OutlineGlyphFormat::Cff2 => &mut self.cff_entries,
2098 OutlineGlyphFormat::Varc => &mut self.varc_entries,
2099 };
2100 let (entry_ix, is_current) = find_hint_entry(entries, key)?;
2101 let entry = entries.get_mut(entry_ix)?;
2102 self.serial += 1;
2103 entry.serial = self.serial;
2104 if !is_current {
2105 entry.font_id = key.font_id;
2106 entry.font_index = key.font_index;
2107 entry
2108 .instance
2109 .reconfigure(
2110 key.outlines,
2111 Size::new(key.size),
2112 key.coords,
2113 HINTING_OPTIONS,
2114 )
2115 .ok()?;
2116 }
2117 Some(&entry.instance)
2118 }
2119
2120 pub fn clear(&mut self) {
2122 self.glyf_entries.clear();
2123 self.cff_entries.clear();
2124 self.varc_entries.clear();
2125 self.serial = 0;
2126 }
2127}
2128
2129struct HintEntry {
2130 font_id: u64,
2131 font_index: u32,
2132 instance: HintingInstance,
2133 serial: u64,
2134}
2135
2136fn find_hint_entry(entries: &mut Vec<HintEntry>, key: &HintKey<'_>) -> Option<(usize, bool)> {
2137 let mut found_serial = u64::MAX;
2138 let mut found_index = 0;
2139 for (ix, entry) in entries.iter().enumerate() {
2140 if entry.font_id == key.font_id
2141 && entry.font_index == key.font_index
2142 && entry.instance.size() == Size::new(key.size)
2143 && entry.instance.location().coords() == key.coords
2144 {
2145 return Some((ix, true));
2146 }
2147 if entry.serial < found_serial {
2148 found_serial = entry.serial;
2149 found_index = ix;
2150 }
2151 }
2152 if entries.len() < MAX_CACHED_HINT_INSTANCES {
2153 let instance = key.instance()?;
2154 let ix = entries.len();
2155 entries.push(HintEntry {
2156 font_id: key.font_id,
2157 font_index: key.font_index,
2158 instance,
2159 serial: 0,
2161 });
2162 Some((ix, true))
2163 } else {
2164 Some((found_index, false))
2165 }
2166}
2167
2168fn x_y_advances(transform: &Affine) -> (Vec2, Vec2) {
2169 let scale_skew_transform = {
2170 let c = transform.as_coeffs();
2171 Affine::new([c[0], c[1], c[2], c[3], 0.0, 0.0])
2172 };
2173
2174 let x_advance = scale_skew_transform * Point::new(1.0, 0.0);
2175 let y_advance = scale_skew_transform * Point::new(0.0, 1.0);
2176
2177 (
2178 Vec2::new(x_advance.x, x_advance.y),
2179 Vec2::new(y_advance.x, y_advance.y),
2180 )
2181}
2182
2183#[cfg(test)]
2184mod tests {
2185 use super::*;
2186 use crate::atlas::{AtlasConfig, AtlasPaint};
2187 use crate::interface::{DrawSink, GlyphRenderer};
2188 use crate::peniko::BlendMode;
2189 use crate::peniko::Blob;
2190 use crate::peniko::color::{AlphaColor, Srgb};
2191 use alloc::sync::Arc;
2192 use vello_common::paint::{Image, ImageId, ImageSource, PaintType, Tint};
2193
2194 const _NORMALISED_COORD_SIZE_MATCHES: () =
2195 assert!(size_of::<skrifa::instance::NormalizedCoord>() == size_of::<NormalizedCoord>());
2196
2197 const ROBOTO_FONT: &[u8] = include_bytes!("../../examples/assets/roboto/Roboto-Regular.ttf");
2198 const NOTO_COLR_FONT: &[u8] =
2199 include_bytes!("../../examples/assets/noto_color_emoji/NotoColorEmoji-Subset.ttf");
2200 #[cfg(feature = "png")]
2201 const NOTO_CBTF_FONT: &[u8] =
2202 include_bytes!("../../examples/assets/noto_color_emoji/NotoColorEmoji-CBTF-Subset.ttf");
2203
2204 #[derive(Clone, Copy)]
2205 enum TestGlyphKind {
2206 Outline,
2207 Colr,
2208 #[cfg(feature = "png")]
2209 Bitmap,
2210 }
2211
2212 #[derive(Default)]
2213 struct NoopRenderer;
2214
2215 static BLACK_PAINT: PaintType = PaintType::Solid(BLACK);
2216
2217 struct TestResources {
2218 renderer: NoopRenderer,
2219 prep_cache: GlyphPrepCache,
2220 glyph_atlas: GlyphAtlas,
2221 image_cache: ImageCache,
2222 }
2223
2224 impl Default for TestResources {
2225 fn default() -> Self {
2226 Self {
2227 renderer: NoopRenderer,
2228 prep_cache: GlyphPrepCache::default(),
2229 glyph_atlas: GlyphAtlas::default(),
2230 image_cache: ImageCache::new_with_config(AtlasConfig {
2231 atlas_size: (512, 512),
2232 ..AtlasConfig::default()
2233 }),
2234 }
2235 }
2236 }
2237
2238 impl DrawSink for NoopRenderer {
2239 fn set_transform(&mut self, _t: Affine) {}
2240
2241 fn set_paint(&mut self, _paint: AtlasPaint) {}
2242
2243 fn set_paint_transform(&mut self, _t: Affine) {}
2244
2245 fn fill_path(&mut self, _path: &BezPath) {}
2246
2247 fn fill_rect(&mut self, _rect: &Rect) {}
2248
2249 fn push_clip_layer(&mut self, _clip: &BezPath) {}
2250
2251 fn push_blend_layer(&mut self, _blend_mode: BlendMode) {}
2252
2253 fn pop_layer(&mut self) {}
2254
2255 fn width(&self) -> u16 {
2256 512
2257 }
2258
2259 fn height(&self) -> u16 {
2260 512
2261 }
2262 }
2263
2264 impl GlyphRenderer for NoopRenderer {
2265 type SavedState = ();
2266
2267 fn save_state(&mut self) -> Self::SavedState {}
2268
2269 fn restore_state(&mut self, _state: Self::SavedState) {}
2270
2271 fn stroke_path(&mut self, _path: &BezPath) {}
2272
2273 fn set_paint_image(&mut self, _image: Image) {}
2274
2275 fn set_tint(&mut self, _tint: Option<Tint>) {}
2276
2277 fn get_context_color(&self) -> AlphaColor<Srgb> {
2278 BLACK
2279 }
2280
2281 fn current_paint(&self) -> &PaintType {
2282 &BLACK_PAINT
2283 }
2284
2285 fn atlas_image_source(&self, atlas_slot: &AtlasSlot) -> ImageSource {
2286 ImageSource::opaque_id(ImageId::new(atlas_slot.page_index))
2287 }
2288
2289 fn atlas_paint_transform(&self, atlas_slot: &AtlasSlot) -> Affine {
2290 Affine::translate((-(atlas_slot.x as f64), -(atlas_slot.y as f64)))
2291 }
2292 }
2293
2294 fn test_font(kind: TestGlyphKind) -> FontData {
2295 let bytes = match kind {
2296 TestGlyphKind::Outline => ROBOTO_FONT,
2297 TestGlyphKind::Colr => NOTO_COLR_FONT,
2298 #[cfg(feature = "png")]
2299 TestGlyphKind::Bitmap => NOTO_CBTF_FONT,
2300 };
2301 FontData::new(Blob::new(Arc::new(bytes)), 0)
2302 }
2303
2304 fn test_glyph(font: &FontData, kind: TestGlyphKind) -> Glyph {
2305 let ch = match kind {
2306 TestGlyphKind::Outline => 'H',
2307 TestGlyphKind::Colr => '✅',
2308 #[cfg(feature = "png")]
2309 TestGlyphKind::Bitmap => '✅',
2310 };
2311 let glyph_id = font.as_skrifa().charmap().map(ch).unwrap();
2312 Glyph {
2313 id: glyph_id.to_u32(),
2314 x: 0.0,
2315 y: 0.0,
2316 }
2317 }
2318
2319 fn draw_test_glyph(
2320 font: &FontData,
2321 glyph: Glyph,
2322 atlas_cache_enabled: bool,
2323 style: Style,
2324 resources: &mut TestResources,
2325 ) {
2326 let atlas_cacher = if atlas_cache_enabled {
2327 AtlasCacher::Enabled(&mut resources.glyph_atlas, &mut resources.image_cache)
2328 } else {
2329 AtlasCacher::Disabled
2330 };
2331
2332 let transform = Affine::translate((0.0, 20.0));
2333 let mut run = GlyphRun {
2334 font: font.clone(),
2335 font_size: 20.0,
2336 font_embolden: FontEmbolden::default(),
2337 transform,
2338 scene_paint_transform: transform,
2339 glyph_transform: None,
2340 normalized_coords: &[],
2341 hint: false,
2342 }
2343 .build(
2344 core::iter::once(glyph),
2345 resources.prep_cache.as_mut(),
2346 atlas_cacher,
2347 );
2348
2349 match style {
2350 Style::Fill => run.fill_glyphs(&mut resources.renderer),
2351 Style::Stroke => run.stroke_glyphs(&mut resources.renderer),
2352 }
2353 }
2354
2355 fn ensure_cache(kind: TestGlyphKind, style: Style) {
2356 let font = test_font(kind);
2357 let glyph = test_glyph(&font, kind);
2358 let mut resources = TestResources::default();
2359
2360 draw_test_glyph(&font, glyph, true, style, &mut resources);
2361
2362 assert_eq!(resources.glyph_atlas.len(), 1);
2363 assert_eq!(resources.glyph_atlas.cache_hits(), 0);
2364 assert!(resources.glyph_atlas.cache_misses() > 0);
2369
2370 draw_test_glyph(&font, glyph, true, style, &mut resources);
2371
2372 assert_eq!(resources.glyph_atlas.len(), 1);
2373 assert_eq!(resources.glyph_atlas.cache_hits(), 1);
2374 assert!(resources.glyph_atlas.cache_misses() > 0);
2375 }
2376
2377 fn ensure_no_cache(kind: TestGlyphKind, style: Style, atlas_cache_enabled: bool) {
2378 let font = test_font(kind);
2379 let glyph = test_glyph(&font, kind);
2380 let mut resources = TestResources::default();
2381
2382 draw_test_glyph(&font, glyph, atlas_cache_enabled, style, &mut resources);
2383
2384 assert_eq!(resources.glyph_atlas.len(), 0);
2385 assert_eq!(resources.glyph_atlas.cache_hits(), 0);
2386 assert_eq!(resources.glyph_atlas.cache_misses(), 0);
2387
2388 draw_test_glyph(&font, glyph, atlas_cache_enabled, style, &mut resources);
2389
2390 assert_eq!(resources.glyph_atlas.len(), 0);
2391 assert_eq!(resources.glyph_atlas.cache_hits(), 0);
2392 assert_eq!(resources.glyph_atlas.cache_misses(), 0);
2393 }
2394
2395 #[test]
2396 fn outline_glyph_is_cached_when_atlas_cache_is_enabled() {
2397 ensure_cache(TestGlyphKind::Outline, Style::Fill);
2398 }
2399
2400 #[test]
2401 fn outline_glyph_is_not_cached_when_atlas_cache_is_disabled() {
2402 ensure_no_cache(TestGlyphKind::Outline, Style::Fill, false);
2403 }
2404
2405 #[test]
2407 fn stroked_outline_glyph_is_not_cached_when_atlas_cache_is_enabled() {
2408 ensure_no_cache(TestGlyphKind::Outline, Style::Stroke, true);
2409 }
2410
2411 #[test]
2412 fn stroked_outline_glyph_is_not_cached_when_atlas_cache_is_disabled() {
2413 ensure_no_cache(TestGlyphKind::Outline, Style::Stroke, false);
2414 }
2415
2416 #[test]
2417 fn colr_glyph_is_cached_when_atlas_cache_is_enabled() {
2418 ensure_cache(TestGlyphKind::Colr, Style::Fill);
2419 }
2420
2421 #[test]
2422 fn colr_glyph_is_not_cached_when_atlas_cache_is_disabled() {
2423 ensure_no_cache(TestGlyphKind::Colr, Style::Fill, false);
2424 }
2425
2426 #[cfg(feature = "png")]
2427 #[test]
2428 fn bitmap_glyph_is_cached_when_atlas_cache_is_enabled() {
2429 ensure_cache(TestGlyphKind::Bitmap, Style::Fill);
2430 }
2431
2432 #[cfg(feature = "png")]
2433 #[test]
2434 fn bitmap_glyph_is_not_cached_when_atlas_cache_is_disabled() {
2435 ensure_no_cache(TestGlyphKind::Bitmap, Style::Fill, false);
2436 }
2437}