1use super::atlas::AtlasAllocator;
2use crate::{
3 fonts::{FontId, GlyphId},
4 geometry::vector::{vec2f, Vector2F, Vector2I},
5 platform::{self, RasterizationOptions},
6};
7use collections::hash_map::Entry;
8use metal::{MTLPixelFormat, TextureDescriptor};
9use ordered_float::OrderedFloat;
10use std::{borrow::Cow, collections::HashMap, sync::Arc};
11
12#[derive(Hash, Eq, PartialEq)]
13struct GlyphDescriptor {
14 font_id: FontId,
15 font_size: 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 scale_factor: f32,
48}
49
50impl SpriteCache {
51 pub fn new(
52 device: metal::Device,
53 size: Vector2I,
54 scale_factor: f32,
55 fonts: Arc<dyn platform::FontSystem>,
56 ) -> Self {
57 let descriptor = TextureDescriptor::new();
58 descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
59 descriptor.set_width(size.x() as u64);
60 descriptor.set_height(size.y() as u64);
61 Self {
62 fonts,
63 atlases: AtlasAllocator::new(device, descriptor),
64 glyphs: Default::default(),
65 icons: Default::default(),
66 scale_factor,
67 }
68 }
69
70 pub fn set_scale_factor(&mut self, scale_factor: f32) {
71 if scale_factor != self.scale_factor {
72 self.icons.clear();
73 self.glyphs.clear();
74 self.atlases.clear();
75 }
76 self.scale_factor = scale_factor;
77 }
78
79 pub fn render_glyph(
80 &mut self,
81 font_id: FontId,
82 font_size: f32,
83 glyph_id: GlyphId,
84 target_position: Vector2F,
85 ) -> Option<GlyphSprite> {
86 const SUBPIXEL_VARIANTS: u8 = 4;
87
88 let scale_factor = self.scale_factor;
89 let target_position = target_position * scale_factor;
90 let fonts = &self.fonts;
91 let atlases = &mut self.atlases;
92 let subpixel_variant = (
93 (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8
94 % SUBPIXEL_VARIANTS,
95 (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8
96 % SUBPIXEL_VARIANTS,
97 );
98
99 self.glyphs
100 .entry(GlyphDescriptor {
101 font_id,
102 font_size: OrderedFloat(font_size),
103 glyph_id,
104 subpixel_variant,
105 })
106 .or_insert_with(|| {
107 let subpixel_shift = vec2f(
108 subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
109 subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
110 );
111 let (glyph_bounds, mask) = fonts.rasterize_glyph(
112 font_id,
113 font_size,
114 glyph_id,
115 subpixel_shift,
116 scale_factor,
117 RasterizationOptions::Alpha,
118 )?;
119
120 let (alloc_id, atlas_bounds) = atlases
121 .upload(glyph_bounds.size(), &mask)
122 .expect("could not upload glyph");
123 Some(GlyphSprite {
124 atlas_id: alloc_id.atlas_id,
125 atlas_origin: atlas_bounds.origin(),
126 offset: glyph_bounds.origin(),
127 size: glyph_bounds.size(),
128 })
129 })
130 .clone()
131 }
132
133 pub fn render_icon(
134 &mut self,
135 size: Vector2I,
136 path: Cow<'static, str>,
137 svg: usvg::Tree,
138 ) -> Option<IconSprite> {
139 let atlases = &mut self.atlases;
140 match self.icons.entry(IconDescriptor {
141 path,
142 width: size.x(),
143 height: size.y(),
144 }) {
145 Entry::Occupied(entry) => Some(entry.get().clone()),
146 Entry::Vacant(entry) => {
147 let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
148 resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
149 let mask = pixmap
150 .pixels()
151 .iter()
152 .map(|a| a.alpha())
153 .collect::<Vec<_>>();
154 let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
155 let icon_sprite = IconSprite {
156 atlas_id: alloc_id.atlas_id,
157 atlas_origin: atlas_bounds.origin(),
158 size,
159 };
160 Some(entry.insert(icon_sprite).clone())
161 }
162 }
163 }
164
165 pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
166 self.atlases.texture(atlas_id)
167 }
168}