1use super::atlas::AtlasAllocator;
2use crate::{
3 fonts::{FontId, GlyphId},
4 geometry::vector::{vec2f, Vector2F, Vector2I},
5 platform,
6};
7use metal::{MTLPixelFormat, TextureDescriptor};
8use ordered_float::OrderedFloat;
9use std::{borrow::Cow, collections::HashMap, sync::Arc};
10
11#[derive(Hash, Eq, PartialEq)]
12struct GlyphDescriptor {
13 font_id: FontId,
14 font_size: OrderedFloat<f32>,
15 scale_factor: OrderedFloat<f32>,
16 glyph_id: GlyphId,
17 subpixel_variant: (u8, u8),
18}
19
20#[derive(Clone)]
21pub struct GlyphSprite {
22 pub atlas_id: usize,
23 pub atlas_origin: Vector2I,
24 pub offset: Vector2I,
25 pub size: Vector2I,
26}
27
28#[derive(Hash, Eq, PartialEq)]
29struct IconDescriptor {
30 path: Cow<'static, str>,
31 width: i32,
32 height: i32,
33}
34
35#[derive(Clone)]
36pub struct IconSprite {
37 pub atlas_id: usize,
38 pub atlas_origin: Vector2I,
39 pub size: Vector2I,
40}
41
42pub struct SpriteCache {
43 fonts: Arc<dyn platform::FontSystem>,
44 atlases: AtlasAllocator,
45 glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
46 icons: HashMap<IconDescriptor, IconSprite>,
47}
48
49impl SpriteCache {
50 pub fn new(
51 device: metal::Device,
52 size: Vector2I,
53 fonts: Arc<dyn platform::FontSystem>,
54 ) -> Self {
55 let descriptor = TextureDescriptor::new();
56 descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
57 descriptor.set_width(size.x() as u64);
58 descriptor.set_height(size.y() as u64);
59 Self {
60 fonts,
61 atlases: AtlasAllocator::new(device, descriptor),
62 glyphs: Default::default(),
63 icons: Default::default(),
64 }
65 }
66
67 pub fn render_glyph(
68 &mut self,
69 font_id: FontId,
70 font_size: f32,
71 glyph_id: GlyphId,
72 target_position: Vector2F,
73 scale_factor: f32,
74 ) -> Option<GlyphSprite> {
75 const SUBPIXEL_VARIANTS: u8 = 4;
76
77 let target_position = target_position * scale_factor;
78 let fonts = &self.fonts;
79 let atlases = &mut self.atlases;
80 let subpixel_variant = (
81 (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
82 % SUBPIXEL_VARIANTS,
83 (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
84 % SUBPIXEL_VARIANTS,
85 );
86 self.glyphs
87 .entry(GlyphDescriptor {
88 font_id,
89 font_size: OrderedFloat(font_size),
90 scale_factor: OrderedFloat(scale_factor),
91 glyph_id,
92 subpixel_variant,
93 })
94 .or_insert_with(|| {
95 let subpixel_shift = vec2f(
96 subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
97 subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
98 );
99 let (glyph_bounds, mask) = fonts.rasterize_glyph(
100 font_id,
101 font_size,
102 glyph_id,
103 subpixel_shift,
104 scale_factor,
105 )?;
106
107 let (alloc_id, atlas_bounds) = atlases.upload(glyph_bounds.size(), &mask);
108 Some(GlyphSprite {
109 atlas_id: alloc_id.atlas_id,
110 atlas_origin: atlas_bounds.origin(),
111 offset: glyph_bounds.origin(),
112 size: glyph_bounds.size(),
113 })
114 })
115 .clone()
116 }
117
118 pub fn render_icon(
119 &mut self,
120 size: Vector2I,
121 path: Cow<'static, str>,
122 svg: usvg::Tree,
123 ) -> IconSprite {
124 let atlases = &mut self.atlases;
125 self.icons
126 .entry(IconDescriptor {
127 path,
128 width: size.x(),
129 height: size.y(),
130 })
131 .or_insert_with(|| {
132 let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap();
133 resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
134 let mask = pixmap
135 .pixels()
136 .iter()
137 .map(|a| a.alpha())
138 .collect::<Vec<_>>();
139
140 let (alloc_id, atlas_bounds) = atlases.upload(size, &mask);
141 IconSprite {
142 atlas_id: alloc_id.atlas_id,
143 atlas_origin: atlas_bounds.origin(),
144 size,
145 }
146 })
147 .clone()
148 }
149
150 pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
151 self.atlases.texture(atlas_id)
152 }
153}