1use crate::atlas::{Atlas, AtlasContent};
2use cosmic_text::{SubpixelBin, SwashContent, SwashImage};
3use rect_packer::Rect;
4use std::collections::HashMap;
5
6#[derive(Copy, Clone, Debug)]
7pub struct AtlasInfo {
8 pub rect: Option<Rect>,
9 pub left: i32,
10 pub top: i32,
11 pub colored: bool,
12}
13
14pub enum PixelFormat {
15 Rgba,
17}
18
19pub struct Image {
20 pub width: u32,
21 pub height: u32,
22 pub data: Vec<u8>,
23 pub pixel_format: PixelFormat,
24}
25
26pub struct GlyphCache {
27 pub size: u32,
28 pub mask_atlas: Atlas,
29 pub color_atlas: Atlas,
30 glyph_infos: HashMap<
31 (
32 cosmic_text::fontdb::ID,
33 u16,
34 u32,
35 (SubpixelBin, SubpixelBin),
36 ),
37 AtlasInfo,
38 >,
39 svg_infos: HashMap<Vec<u8>, HashMap<(u32, u32), AtlasInfo>>,
40 img_infos: HashMap<Vec<u8>, AtlasInfo>,
41}
42
43impl GlyphCache {
44 pub fn new(device: &wgpu::Device) -> Self {
45 let size = 1024;
46 Self {
47 size,
48 mask_atlas: Atlas::new(device, AtlasContent::Mask, size, size),
49 color_atlas: Atlas::new(device, AtlasContent::Color, size, size),
50 glyph_infos: HashMap::new(),
51 img_infos: HashMap::new(),
52 svg_infos: HashMap::new(),
53 }
54 }
55
56 pub fn get_image_mask(&mut self, hash: &[u8], image_fn: impl FnOnce() -> Image) -> AtlasInfo {
57 if let Some(info) = self.img_infos.get(hash) {
58 return *info;
59 }
60
61 let image = image_fn();
62 let rect = self
63 .color_atlas
64 .add_region(&image.data, image.width, image.height);
65 let info = AtlasInfo {
66 rect,
67 left: 0,
68 top: 0,
69 colored: true,
70 };
71 self.img_infos.insert(hash.to_vec(), info);
72
73 info
74 }
75
76 pub fn get_svg_mask(
77 &mut self,
78 hash: &[u8],
79 width: u32,
80 height: u32,
81 image: impl FnOnce() -> Vec<u8>,
82 ) -> AtlasInfo {
83 if !self.svg_infos.contains_key(hash) {
84 self.svg_infos.insert(hash.to_vec(), HashMap::new());
85 }
86
87 {
88 let svg_infos = self.svg_infos.get(hash).unwrap();
89 if let Some(info) = svg_infos.get(&(width, height)) {
90 return *info;
91 }
92 }
93
94 let data = image();
95 let rect = self.color_atlas.add_region(&data, width, height);
96 let info = AtlasInfo {
97 rect,
98 left: 0,
99 top: 0,
100 colored: true,
101 };
102
103 let svg_infos = self.svg_infos.get_mut(hash).unwrap();
104 svg_infos.insert((width, height), info);
105
106 info
107 }
108
109 pub fn get_glyph_mask(
110 &mut self,
111 font_id: cosmic_text::fontdb::ID,
112 glyph_id: u16,
113 size: u32,
114 subpx: (SubpixelBin, SubpixelBin),
115 image: impl FnOnce() -> SwashImage,
116 ) -> AtlasInfo {
117 let key = (font_id, glyph_id, size, subpx);
118 if let Some(rect) = self.glyph_infos.get(&key) {
119 return *rect;
120 }
121
122 let image = image();
123 let rect = match image.content {
124 SwashContent::Mask => self.mask_atlas.add_region(
125 &image.data,
126 image.placement.width,
127 image.placement.height,
128 ),
129 SwashContent::SubpixelMask | SwashContent::Color => self.color_atlas.add_region(
130 &image.data,
131 image.placement.width,
132 image.placement.height,
133 ),
134 };
135 let info = AtlasInfo {
136 rect,
137 left: image.placement.left,
138 top: image.placement.top,
139 colored: image.content != SwashContent::Mask,
140 };
141 self.glyph_infos.insert(key, info);
142 info
143 }
144
145 pub fn update(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder) {
146 self.mask_atlas.update(device, encoder);
147 self.color_atlas.update(device, encoder);
148 }
149
150 pub fn check_usage(&mut self, device: &wgpu::Device) -> bool {
151 let max_seen = (self.mask_atlas.max_seen as f32 * 2.0)
152 .max(self.color_atlas.max_seen as f32 * 2.0) as u32;
153 if max_seen > self.size {
154 self.size = max_seen;
155 self.mask_atlas.resize(device, self.size, self.size);
156 self.color_atlas.resize(device, self.size, self.size);
157 self.clear();
158 true
159 } else if self.mask_atlas.usage() > 0.7 || self.color_atlas.usage() > 0.7 {
160 self.clear();
161 false
162 } else {
163 false
164 }
165 }
166
167 pub fn clear(&mut self) {
168 self.mask_atlas.clear();
169 self.color_atlas.clear();
170 self.glyph_infos.clear();
171 self.svg_infos.clear();
172 self.img_infos.clear();
173 }
174}