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