1use crate::atlas::commands::{AtlasCommand, AtlasCommandRecorder};
7use crate::atlas::key::subpixel_offset;
8use crate::atlas::{AtlasSlot, GlyphAtlas, GlyphCacheKey, ImageCache, RasterMetrics};
9use crate::colr::ColrPainter;
10use crate::glyph::{
11 AtlasCacher, CachedGlyphType, GlyphBitmap, GlyphColr, GlyphOutline, GlyphType,
12 OutlineCacheSession, PreparedGlyph,
13};
14use crate::interface::{DrawSink, GlyphRenderer};
15use crate::util::AffineExt;
16use crate::{kurbo, peniko};
17use alloc::sync::Arc;
18use alloc::vec::Vec;
19#[cfg(not(feature = "std"))]
20use core_maths::CoreFloat as _;
21use kurbo::{Affine, BezPath, Rect, Shape};
22use peniko::color::palette::css::BLACK;
23use peniko::color::{AlphaColor, Srgb};
24use peniko::{Extend, ImageQuality, ImageSampler};
25use vello_common::paint::{Image, ImageSource, Tint, TintMode};
26
27enum CacheResult {
31 CachedAndRendered,
33 UnsupportedTransform,
36 AtlasFull,
39}
40
41pub(crate) fn fill_glyph(
44 renderer: &mut impl GlyphRenderer,
45 prepared_glyph: PreparedGlyph<'_>,
46 atlas_cacher: &mut AtlasCacher<'_>,
47 outline_cache: &mut OutlineCacheSession<'_>,
48) {
49 let AtlasCacher::Enabled(glyph_atlas, image_cache) = atlas_cacher else {
50 let transform = prepared_glyph.outline_transform;
51 let paint_transform = prepared_glyph.relative_paint_transform;
52
53 return match prepared_glyph.glyph_type {
54 GlyphType::Outline(glyph) => {
55 fill_uncached_outline_glyph(
56 renderer,
57 &glyph.path,
58 glyph.scale,
59 transform,
60 paint_transform,
61 );
62 }
63 GlyphType::Bitmap(glyph) => render_uncached_bitmap_glyph(renderer, glyph, transform),
64 GlyphType::Colr(glyph) => {
65 let context_color = renderer.get_context_color();
66 render_uncached_colr_glyph(
67 renderer,
68 &glyph,
69 transform,
70 context_color,
71 outline_cache,
72 );
73 }
74 };
75 };
76
77 let mut cache_key = prepared_glyph.cache_key;
78 let transform = prepared_glyph.outline_transform;
79 let paint_transform = prepared_glyph.relative_paint_transform;
80
81 match prepared_glyph.glyph_type {
82 GlyphType::Outline(glyph) => {
83 let tint_color = renderer.get_context_color();
84 if let Some(key) = cache_key.take()
85 && let CacheResult::CachedAndRendered = insert_and_render_outline(
86 renderer,
87 &glyph,
88 transform,
89 key,
90 glyph_atlas,
91 image_cache,
92 tint_color,
93 )
94 {
95 return;
96 }
97
98 fill_uncached_outline_glyph(
99 renderer,
100 &glyph.path,
101 glyph.scale,
102 transform,
103 paint_transform,
104 );
105 }
106 GlyphType::Bitmap(glyph) => {
107 if let Some(key) = cache_key.take()
108 && let CacheResult::CachedAndRendered = insert_and_render_bitmap(
109 renderer,
110 &glyph,
111 transform,
112 key,
113 glyph_atlas,
114 image_cache,
115 )
116 {
117 return;
118 }
119
120 render_uncached_bitmap_glyph(renderer, glyph, transform);
121 }
122 GlyphType::Colr(glyph) => {
123 if let Some(key) = cache_key.take()
124 && let CacheResult::CachedAndRendered = insert_and_render_colr(
125 renderer,
126 &glyph,
127 transform,
128 key,
129 glyph_atlas,
130 image_cache,
131 outline_cache,
132 )
133 {
134 return;
135 }
136
137 let context_color = renderer.get_context_color();
138 render_uncached_colr_glyph(renderer, &glyph, transform, context_color, outline_cache);
139 }
140 }
141}
142
143pub(crate) fn stroke_glyph(
146 renderer: &mut impl GlyphRenderer,
147 prepared_glyph: PreparedGlyph<'_>,
148 atlas_cacher: &mut AtlasCacher<'_>,
149 outline_cache: &mut OutlineCacheSession<'_>,
150) {
151 let AtlasCacher::Enabled(glyph_atlas, image_cache) = atlas_cacher else {
152 let outline_transform = prepared_glyph.outline_transform;
153 let paint_transform = prepared_glyph.relative_paint_transform;
154 return match prepared_glyph.glyph_type {
155 GlyphType::Outline(glyph) => {
156 stroke_uncached_outline_glyph(
157 renderer,
158 &glyph.path,
159 glyph.scale,
160 outline_transform,
161 paint_transform,
162 );
163 }
164 GlyphType::Bitmap(_) | GlyphType::Colr(_) => {
165 fill_glyph(renderer, prepared_glyph, atlas_cacher, outline_cache);
166 }
167 };
168 };
169
170 match prepared_glyph.glyph_type {
171 GlyphType::Outline(glyph) => {
172 let mut cache_key = prepared_glyph.cache_key;
173 let outline_transform = prepared_glyph.outline_transform;
174 let paint_transform = prepared_glyph.relative_paint_transform;
175 let tint_color = renderer.get_context_color();
176
177 if let Some(key) = cache_key.take()
178 && let CacheResult::CachedAndRendered = insert_and_render_outline(
179 renderer,
180 &glyph,
181 outline_transform,
182 key,
183 glyph_atlas,
184 image_cache,
185 tint_color,
186 )
187 {
188 return;
189 }
190
191 stroke_uncached_outline_glyph(
192 renderer,
193 &glyph.path,
194 glyph.scale,
195 outline_transform,
196 paint_transform,
197 );
198 }
199 GlyphType::Bitmap(_) | GlyphType::Colr(_) => {
200 fill_glyph(renderer, prepared_glyph, atlas_cacher, outline_cache);
201 }
202 }
203}
204
205fn fill_uncached_outline_glyph(
206 renderer: &mut impl GlyphRenderer,
207 path: &BezPath,
208 scale: f64,
209 outline_transform: Affine,
210 paint_transform: Affine,
211) {
212 let state = renderer.save_state();
213 renderer.set_transform(outline_transform.pre_scale(scale));
214 renderer.set_paint_transform(paint_transform);
215 renderer.fill_path(path);
216 renderer.restore_state(state);
217}
218
219fn stroke_uncached_outline_glyph(
220 renderer: &mut impl GlyphRenderer,
221 path: &BezPath,
222 scale: f64,
223 outline_transform: Affine,
224 paint_transform: Affine,
225) {
226 let state = renderer.save_state();
227 renderer.set_transform(outline_transform.pre_scale(scale));
228 renderer.set_paint_transform(paint_transform);
229 renderer.stroke_path(path);
230 renderer.restore_state(state);
231}
232
233fn render_uncached_bitmap_glyph(
234 renderer: &mut impl GlyphRenderer,
235 glyph: GlyphBitmap,
236 outline_transform: Affine,
237) {
238 let image = Image {
239 image: ImageSource::Pixmap(glyph.pixmap),
240 sampler: ImageSampler {
241 x_extend: Extend::Pad,
242 y_extend: Extend::Pad,
243 quality: quality_for_scale(&outline_transform),
244 alpha: 1.0,
245 },
246 };
247
248 let state = renderer.save_state();
249 renderer.set_transform(outline_transform);
250 renderer.set_paint_image(image);
251 renderer.fill_rect(&glyph.area);
252 renderer.restore_state(state);
253}
254
255fn render_uncached_colr_glyph(
256 renderer: &mut impl GlyphRenderer,
257 glyph: &GlyphColr<'_>,
258 outline_transform: Affine,
259 context_color: AlphaColor<Srgb>,
260 outline_cache: &mut OutlineCacheSession<'_>,
261) {
262 let state = renderer.save_state();
263 renderer.set_transform(outline_transform);
264 if glyph.has_non_default_blend {
272 renderer.push_clip_layer(&glyph.area.to_path(0.1));
273 } else {
274 renderer.push_clip_path(&glyph.area.to_path(0.1));
275 }
276
277 let mut colr_painter = ColrPainter::new(glyph, context_color, renderer, outline_cache);
279 colr_painter.paint();
280 if glyph.has_non_default_blend {
281 renderer.pop_layer();
282 } else {
283 renderer.pop_clip_path();
284 }
285
286 renderer.restore_state(state);
287}
288
289pub(crate) fn render_cached_glyph(
291 renderer: &mut impl GlyphRenderer,
292 cached_slot: AtlasSlot,
293 transform: Affine,
294 glyph_type: CachedGlyphType,
295) {
296 match glyph_type {
297 CachedGlyphType::Outline => {
298 let tint = renderer.get_context_color();
299 render_outline_glyph_from_atlas(renderer, cached_slot, transform, tint);
300 }
301 CachedGlyphType::Bitmap => {
302 render_bitmap_glyph_from_atlas(renderer, cached_slot, transform);
303 }
304 CachedGlyphType::Colr(area) => {
305 render_colr_glyph_from_atlas(renderer, cached_slot, transform, area);
306 }
307 }
308}
309
310fn render_from_atlas(
312 renderer: &mut impl GlyphRenderer,
313 atlas_slot: AtlasSlot,
314 rect_transform: Affine,
315 area: Rect,
316 quality: ImageQuality,
317 tint: Option<Tint>,
318) {
319 let paint_transform = renderer.atlas_paint_transform(&atlas_slot);
320 let image_source = renderer.atlas_image_source(&atlas_slot);
321 let image = Image {
322 image: image_source,
323 sampler: ImageSampler {
324 x_extend: Extend::Pad,
325 y_extend: Extend::Pad,
326 quality,
327 alpha: 1.0,
328 },
329 };
330
331 let state = renderer.save_state();
332 renderer.set_tint(tint);
333 renderer.set_transform(rect_transform);
334 renderer.set_paint_image(image);
335 renderer.set_paint_transform(paint_transform);
336 renderer.fill_rect(&area);
337 renderer.set_tint(None);
338 renderer.restore_state(state);
339}
340
341fn render_outline_to_atlas(
343 path: &Arc<BezPath>,
344 scale: f64,
345 subpixel_offset: f32,
346 recorder: &mut AtlasCommandRecorder,
347 atlas_slot: AtlasSlot,
348 raster_metrics: RasterMetrics,
349) {
350 let outline_transform =
351 Affine::scale_non_uniform(scale, -scale).then_translate(kurbo::Vec2::new(
352 atlas_slot.x as f64 - raster_metrics.bearing_x as f64 + subpixel_offset as f64,
353 atlas_slot.y as f64 - raster_metrics.bearing_y as f64,
354 ));
355 recorder.set_transform(outline_transform);
356 recorder.set_paint(BLACK.into());
357 recorder.fill_path(path);
358}
359
360fn render_colr_to_atlas(
362 glyph: &GlyphColr<'_>,
363 context_color: AlphaColor<Srgb>,
364 recorder: &mut AtlasCommandRecorder,
365 atlas_slot: AtlasSlot,
366 outline_cache: &mut OutlineCacheSession<'_>,
367) {
368 recorder.set_transform(Affine::translate((
369 atlas_slot.x as f64,
370 atlas_slot.y as f64,
371 )));
372 if glyph.has_non_default_blend {
375 recorder.push_clip_layer(&glyph.area.to_path(0.1));
376 } else {
377 recorder.push_clip_path(&glyph.area.to_path(0.1));
378 }
379
380 let mut colr_painter = ColrPainter::new(glyph, context_color, recorder, outline_cache);
382 colr_painter.paint();
383
384 if glyph.has_non_default_blend {
385 recorder.pop_layer();
386 } else {
387 recorder.pop_clip_path();
388 }
389}
390
391fn insert_and_render_outline(
397 renderer: &mut impl GlyphRenderer,
398 glyph: &GlyphOutline,
399 outline_transform: Affine,
400 cache_key: GlyphCacheKey,
401 glyph_atlas: &mut GlyphAtlas,
402 image_cache: &mut ImageCache,
403 tint_color: AlphaColor<Srgb>,
404) -> CacheResult {
405 if !supports_atlas_caching(&outline_transform, CachedGlyphType::Outline) {
406 return CacheResult::UnsupportedTransform;
407 }
408
409 let bounds = glyph.bbox.scale_from_origin(glyph.scale);
410 let raster_metrics = calculate_raster_metrics(&bounds);
411
412 let subpixel_offset = subpixel_offset(cache_key.subpixel_x);
413
414 let Some((atlas_slot, recorder)) = glyph_atlas.insert(image_cache, cache_key, raster_metrics)
415 else {
416 return CacheResult::AtlasFull;
417 };
418
419 render_outline_to_atlas(
420 &glyph.path,
421 glyph.scale,
422 subpixel_offset,
423 recorder,
424 atlas_slot,
425 raster_metrics,
426 );
427
428 render_outline_glyph_from_atlas(renderer, atlas_slot, outline_transform, tint_color);
429 CacheResult::CachedAndRendered
430}
431
432fn insert_and_render_bitmap(
433 renderer: &mut impl GlyphRenderer,
434 glyph: &GlyphBitmap,
435 transform: Affine,
436 cache_key: GlyphCacheKey,
437 glyph_atlas: &mut GlyphAtlas,
438 image_cache: &mut ImageCache,
439) -> CacheResult {
440 if !supports_atlas_caching(&transform, CachedGlyphType::Bitmap) {
441 return CacheResult::UnsupportedTransform;
442 }
443
444 let width = glyph.pixmap.width();
445 let height = glyph.pixmap.height();
446
447 let raster_metrics = RasterMetrics {
448 width,
449 height,
450 bearing_x: 0,
451 bearing_y: 0,
452 };
453
454 let Some((atlas_slot, _)) = glyph_atlas.insert(image_cache, cache_key, raster_metrics) else {
457 return CacheResult::AtlasFull;
458 };
459
460 glyph_atlas.push_pending_upload(atlas_slot.image_id, Arc::clone(&glyph.pixmap), atlas_slot);
463
464 render_from_atlas(
465 renderer,
466 atlas_slot,
467 transform,
468 glyph.area,
469 quality_for_scale(&transform),
470 None,
471 );
472 CacheResult::CachedAndRendered
473}
474
475fn insert_and_render_colr(
476 renderer: &mut impl GlyphRenderer,
477 glyph: &GlyphColr<'_>,
478 outline_transform: Affine,
479 cache_key: GlyphCacheKey,
480 glyph_atlas: &mut GlyphAtlas,
481 image_cache: &mut ImageCache,
482 outline_cache: &mut OutlineCacheSession<'_>,
483) -> CacheResult {
484 if !supports_atlas_caching(&outline_transform, CachedGlyphType::Colr(Rect::ZERO)) {
485 return CacheResult::UnsupportedTransform;
486 }
487
488 let width = glyph.pix_width;
489 let height = glyph.pix_height;
490
491 let raster_metrics = RasterMetrics {
492 width,
493 height,
494 bearing_x: 0,
495 bearing_y: 0,
496 };
497
498 let area = glyph.area;
499
500 let context_color = cache_key.context_color;
501 let Some((atlas_slot, recorder)) = glyph_atlas.insert(image_cache, cache_key, raster_metrics)
502 else {
503 return CacheResult::AtlasFull;
504 };
505
506 render_colr_to_atlas(glyph, context_color, recorder, atlas_slot, outline_cache);
507
508 render_from_atlas(
509 renderer,
510 atlas_slot,
511 outline_transform,
512 area,
513 quality_for_skew(&outline_transform),
514 None,
515 );
516 CacheResult::CachedAndRendered
517}
518
519#[inline]
521fn render_outline_glyph_from_atlas(
522 renderer: &mut impl GlyphRenderer,
523 atlas_slot: AtlasSlot,
524 outline_transform: Affine,
525 tint_color: AlphaColor<Srgb>,
526) {
527 let [_, _, _, _, tx, ty] = outline_transform.as_coeffs();
528 let rect_transform = Affine::translate((
529 tx.floor() + atlas_slot.bearing_x as f64,
530 ty.floor() + atlas_slot.bearing_y as f64,
531 ));
532 let area = Rect::new(0.0, 0.0, atlas_slot.width as f64, atlas_slot.height as f64);
533 render_from_atlas(
534 renderer,
535 atlas_slot,
536 rect_transform,
537 area,
538 ImageQuality::Low,
539 Some(Tint {
540 color: tint_color,
541 mode: TintMode::AlphaMask,
542 }),
543 );
544}
545
546#[inline]
548fn render_bitmap_glyph_from_atlas(
549 renderer: &mut impl GlyphRenderer,
550 atlas_slot: AtlasSlot,
551 transform: Affine,
552) {
553 let area = Rect::new(0.0, 0.0, atlas_slot.width as f64, atlas_slot.height as f64);
554 render_from_atlas(
555 renderer,
556 atlas_slot,
557 transform,
558 area,
559 quality_for_scale(&transform),
560 None,
561 );
562}
563
564#[inline]
569fn render_colr_glyph_from_atlas(
570 renderer: &mut impl GlyphRenderer,
571 atlas_slot: AtlasSlot,
572 transform: Affine,
573 area: Rect,
574) {
575 render_from_atlas(
576 renderer,
577 atlas_slot,
578 transform,
579 area,
580 quality_for_skew(&transform),
581 None,
582 );
583}
584
585#[expect(
587 clippy::cast_possible_truncation,
588 reason = "glyph bounds fit in i32/u16/i16 at reasonable ppem values"
589)]
590#[inline]
591pub(crate) fn calculate_raster_metrics(bounds: &Rect) -> RasterMetrics {
592 let min_x = bounds.x0.floor() as i32;
598 let max_x = bounds.x1.ceil() as i32 + 1;
599
600 let flipped_min_y = (-bounds.y1).floor() as i32;
603 let flipped_max_y = (-bounds.y0).ceil() as i32;
604
605 let width = (max_x - min_x) as u16;
606 let height = (flipped_max_y - flipped_min_y) as u16;
607
608 RasterMetrics {
609 width,
610 height,
611 bearing_x: min_x as i16,
612 bearing_y: flipped_min_y as i16,
613 }
614}
615
616#[inline]
621pub fn quality_for_scale(transform: &Affine) -> ImageQuality {
622 let [a, _, _, d, _, _] = transform.as_coeffs();
623 if a < 0.5 || d < 0.5 {
624 ImageQuality::High
625 } else {
626 ImageQuality::Medium
627 }
628}
629
630#[inline]
636pub(crate) fn quality_for_skew(transform: &Affine) -> ImageQuality {
637 if transform.has_skew() {
638 ImageQuality::Medium
639 } else {
640 ImageQuality::Low
641 }
642}
643
644pub fn replay_atlas_commands(commands: &mut Vec<AtlasCommand>, target: &mut impl DrawSink) {
648 for cmd in commands.drain(..) {
649 match cmd {
650 AtlasCommand::SetTransform(t) => target.set_transform(t),
651 AtlasCommand::SetPaint(p) => target.set_paint(p),
652 AtlasCommand::SetPaintTransform(t) => target.set_paint_transform(t),
653 AtlasCommand::FillPath(p) => target.fill_path(&p),
654 AtlasCommand::FillRect(r) => target.fill_rect(&r),
655 AtlasCommand::PushClipLayer(c) => target.push_clip_layer(&c),
656 AtlasCommand::PushClipPath(c) => target.push_clip_path(&c),
657 AtlasCommand::PushBlendLayer(m) => target.push_blend_layer(m),
658 AtlasCommand::PopLayer => target.pop_layer(),
659 AtlasCommand::PopClipPath => target.pop_clip_path(),
660 }
661 }
662}
663
664#[inline]
666pub(crate) fn supports_atlas_caching(transform: &Affine, glyph_type: CachedGlyphType) -> bool {
667 let [a, _, _, d, _, _] = transform.as_coeffs();
672
673 match glyph_type {
674 CachedGlyphType::Outline | CachedGlyphType::Colr(_) => {
677 !transform.has_non_unit_skew_or_scale() && a.is_sign_positive() && d.is_sign_negative()
678 }
679 CachedGlyphType::Bitmap => {
683 !transform.has_skew() && a.is_sign_positive() && d.is_sign_positive()
684 }
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::supports_atlas_caching;
691 use crate::glyph::CachedGlyphType;
692 use peniko::kurbo::Affine;
693 use peniko::kurbo::Rect;
694
695 #[test]
696 fn supports_bitmap_caching_for_identity_and_translation() {
697 assert!(supports_atlas_caching(
698 &Affine::IDENTITY,
699 CachedGlyphType::Bitmap
700 ));
701 assert!(supports_atlas_caching(
702 &Affine::translate((12.0, -3.5)),
703 CachedGlyphType::Bitmap
704 ));
705 }
706
707 #[test]
708 fn rejects_skewed_transforms() {
709 assert!(!supports_atlas_caching(
710 &Affine::new([1.0, 0.1, 0.0, 1.0, 0.0, 0.0]),
711 CachedGlyphType::Bitmap
712 ));
713 assert!(!supports_atlas_caching(
714 &Affine::skew(0.2, 0.0),
715 CachedGlyphType::Outline
716 ));
717 assert!(!supports_atlas_caching(
718 &Affine::skew(0.2, 0.0),
719 CachedGlyphType::Colr(Rect::ZERO)
720 ));
721 }
722
723 #[test]
724 fn outline_and_colr_reject_non_unit_scales() {
725 assert!(!supports_atlas_caching(
726 &Affine::scale(2.0),
727 CachedGlyphType::Outline
728 ));
729 assert!(!supports_atlas_caching(
730 &Affine::scale_non_uniform(1.0, -0.5),
731 CachedGlyphType::Outline
732 ));
733 assert!(!supports_atlas_caching(
734 &Affine::scale(2.0),
735 CachedGlyphType::Colr(Rect::ZERO)
736 ));
737 }
738
739 #[test]
740 fn outline_and_colr_requires_negative_y_and_positive_x() {
741 assert!(supports_atlas_caching(
742 &Affine::scale_non_uniform(1.0, -1.0),
743 CachedGlyphType::Outline
744 ));
745 assert!(supports_atlas_caching(
746 &Affine::scale_non_uniform(1.0, -1.0),
747 CachedGlyphType::Colr(Rect::ZERO)
748 ));
749 assert!(!supports_atlas_caching(
750 &Affine::scale_non_uniform(-1.0, -1.0),
751 CachedGlyphType::Outline
752 ));
753 assert!(!supports_atlas_caching(
754 &Affine::scale_non_uniform(1.0, 1.0),
755 CachedGlyphType::Outline
756 ));
757 }
758
759 #[test]
760 fn bitmap_allows_positive_scales_only() {
761 assert!(supports_atlas_caching(
762 &Affine::scale_non_uniform(1.0, 1.0),
763 CachedGlyphType::Bitmap
764 ));
765 assert!(supports_atlas_caching(
766 &Affine::scale_non_uniform(2.0, 3.0),
767 CachedGlyphType::Bitmap
768 ));
769 assert!(!supports_atlas_caching(
770 &Affine::scale_non_uniform(1.0, -1.0),
771 CachedGlyphType::Bitmap
772 ));
773 assert!(!supports_atlas_caching(
774 &Affine::scale_non_uniform(-1.0, 1.0),
775 CachedGlyphType::Bitmap
776 ));
777 }
778}