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