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 target_position = target_position * self.scale_factor;
89 let subpixel_variant = (
90 (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
91 (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
92 );
93
94 self.glyphs
95 .entry(GlyphDescriptor {
96 font_id,
97 font_size: OrderedFloat(font_size),
98 glyph_id,
99 subpixel_variant,
100 })
101 .or_insert_with(|| {
102 let subpixel_shift = vec2f(
103 subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
104 subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
105 );
106 let (glyph_bounds, mask) = self.fonts.rasterize_glyph(
107 font_id,
108 font_size,
109 glyph_id,
110 subpixel_shift,
111 self.scale_factor,
112 RasterizationOptions::Alpha,
113 )?;
114
115 let (alloc_id, atlas_bounds) = self
116 .atlases
117 .upload(glyph_bounds.size(), &mask)
118 .expect("could not upload glyph");
119 Some(GlyphSprite {
120 atlas_id: alloc_id.atlas_id,
121 atlas_origin: atlas_bounds.origin(),
122 offset: glyph_bounds.origin(),
123 size: glyph_bounds.size(),
124 })
125 })
126 .clone()
127 }
128
129 pub fn render_icon(
130 &mut self,
131 size: Vector2I,
132 path: Cow<'static, str>,
133 svg: usvg::Tree,
134 ) -> Option<IconSprite> {
135 let atlases = &mut self.atlases;
136 match self.icons.entry(IconDescriptor {
137 path,
138 width: size.x(),
139 height: size.y(),
140 }) {
141 Entry::Occupied(entry) => Some(entry.get().clone()),
142 Entry::Vacant(entry) => {
143 let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
144 resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
145 let mask = pixmap
146 .pixels()
147 .iter()
148 .map(|a| a.alpha())
149 .collect::<Vec<_>>();
150 let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
151 let icon_sprite = IconSprite {
152 atlas_id: alloc_id.atlas_id,
153 atlas_origin: atlas_bounds.origin(),
154 size,
155 };
156 Some(entry.insert(icon_sprite).clone())
157 }
158 }
159 }
160
161 pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
162 self.atlases.texture(atlas_id)
163 }
164}