1use crate::core::alignment;
2use crate::core::text::{Alignment, Shaping};
3use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation};
4use crate::graphics::text::cache::{self, Cache};
5use crate::graphics::text::editor;
6use crate::graphics::text::font_system;
7use crate::graphics::text::paragraph;
8
9use rustc_hash::{FxHashMap, FxHashSet};
10use std::borrow::Cow;
11use std::cell::RefCell;
12use std::collections::hash_map;
13
14#[derive(Debug)]
15pub struct Pipeline {
16 glyph_cache: GlyphCache,
17 cache: RefCell<Cache>,
18}
19
20impl Pipeline {
21 pub fn new() -> Self {
22 Pipeline {
23 glyph_cache: GlyphCache::new(),
24 cache: RefCell::new(Cache::new()),
25 }
26 }
27
28 #[allow(dead_code)]
30 pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
31 font_system()
32 .write()
33 .expect("Write font system")
34 .load_font(bytes);
35
36 self.cache = RefCell::new(Cache::new());
37 }
38
39 pub fn draw_paragraph(
40 &mut self,
41 paragraph: ¶graph::Weak,
42 position: Point,
43 color: Color,
44 pixels: &mut tiny_skia::PixmapMut<'_>,
45 clip_mask: Option<&tiny_skia::Mask>,
46 transformation: Transformation,
47 ) {
48 let Some(paragraph) = paragraph.upgrade() else {
49 return;
50 };
51
52 let mut font_system = font_system().write().expect("Write font system");
53
54 draw(
55 font_system.raw(),
56 &mut self.glyph_cache,
57 paragraph.buffer(),
58 position,
59 color,
60 pixels,
61 clip_mask,
62 transformation,
63 );
64 }
65
66 pub fn draw_editor(
67 &mut self,
68 editor: &editor::Weak,
69 position: Point,
70 color: Color,
71 pixels: &mut tiny_skia::PixmapMut<'_>,
72 clip_mask: Option<&tiny_skia::Mask>,
73 transformation: Transformation,
74 ) {
75 let Some(editor) = editor.upgrade() else {
76 return;
77 };
78
79 let mut font_system = font_system().write().expect("Write font system");
80
81 draw(
82 font_system.raw(),
83 &mut self.glyph_cache,
84 editor.buffer(),
85 position,
86 color,
87 pixels,
88 clip_mask,
89 transformation,
90 );
91 }
92
93 pub fn draw_cached(
94 &mut self,
95 content: &str,
96 bounds: Rectangle,
97 color: Color,
98 size: Pixels,
99 line_height: Pixels,
100 font: Font,
101 align_x: Alignment,
102 align_y: alignment::Vertical,
103 shaping: Shaping,
104 pixels: &mut tiny_skia::PixmapMut<'_>,
105 clip_mask: Option<&tiny_skia::Mask>,
106 transformation: Transformation,
107 ) {
108 let line_height = f32::from(line_height);
109
110 let mut font_system = font_system().write().expect("Write font system");
111 let font_system = font_system.raw();
112
113 let key = cache::Key {
114 bounds: bounds.size(),
115 content,
116 font,
117 size: size.into(),
118 line_height,
119 shaping,
120 align_x,
121 };
122
123 let (_, entry) = self.cache.get_mut().allocate(font_system, key);
124
125 let width = entry.min_bounds.width;
126 let height = entry.min_bounds.height;
127
128 let x = match align_x {
129 Alignment::Default | Alignment::Left | Alignment::Justified => bounds.x,
130 Alignment::Center => bounds.x - width / 2.0,
131 Alignment::Right => bounds.x - width,
132 };
133
134 let y = match align_y {
135 alignment::Vertical::Top => bounds.y,
136 alignment::Vertical::Center => bounds.y - height / 2.0,
137 alignment::Vertical::Bottom => bounds.y - height,
138 };
139
140 draw(
141 font_system,
142 &mut self.glyph_cache,
143 &entry.buffer,
144 Point::new(x, y),
145 color,
146 pixels,
147 clip_mask,
148 transformation,
149 );
150 }
151
152 pub fn draw_raw(
153 &mut self,
154 buffer: &cosmic_text::Buffer,
155 position: Point,
156 color: Color,
157 pixels: &mut tiny_skia::PixmapMut<'_>,
158 clip_mask: Option<&tiny_skia::Mask>,
159 transformation: Transformation,
160 ) {
161 let mut font_system = font_system().write().expect("Write font system");
162
163 draw(
164 font_system.raw(),
165 &mut self.glyph_cache,
166 buffer,
167 position,
168 color,
169 pixels,
170 clip_mask,
171 transformation,
172 );
173 }
174
175 pub fn trim_cache(&mut self) {
176 self.cache.get_mut().trim();
177 self.glyph_cache.trim();
178 }
179}
180
181fn draw(
182 font_system: &mut cosmic_text::FontSystem,
183 glyph_cache: &mut GlyphCache,
184 buffer: &cosmic_text::Buffer,
185 position: Point,
186 color: Color,
187 pixels: &mut tiny_skia::PixmapMut<'_>,
188 clip_mask: Option<&tiny_skia::Mask>,
189 transformation: Transformation,
190) {
191 let position = position * transformation;
192
193 let mut swash = cosmic_text::SwashCache::new();
194
195 for run in buffer.layout_runs() {
196 for glyph in run.glyphs {
197 let physical_glyph =
198 glyph.physical((position.x, position.y), transformation.scale_factor());
199
200 if let Some((buffer, placement)) = glyph_cache.allocate(
201 physical_glyph.cache_key,
202 glyph.color_opt.map(from_color).unwrap_or(color),
203 font_system,
204 &mut swash,
205 ) {
206 let pixmap =
207 tiny_skia::PixmapRef::from_bytes(buffer, placement.width, placement.height)
208 .expect("Create glyph pixel map");
209
210 let opacity =
211 color.a * glyph.color_opt.map(|c| c.a() as f32 / 255.0).unwrap_or(1.0);
212
213 pixels.draw_pixmap(
214 physical_glyph.x + placement.left,
215 physical_glyph.y - placement.top
216 + (run.line_y * transformation.scale_factor()).round() as i32,
217 pixmap,
218 &tiny_skia::PixmapPaint {
219 opacity,
220 ..tiny_skia::PixmapPaint::default()
221 },
222 tiny_skia::Transform::identity(),
223 clip_mask,
224 );
225 }
226 }
227 }
228}
229
230fn from_color(color: cosmic_text::Color) -> Color {
231 let [r, g, b, a] = color.as_rgba();
232
233 Color::from_rgba8(r, g, b, a as f32 / 255.0)
234}
235
236#[derive(Debug, Clone, Default)]
237struct GlyphCache {
238 entries: FxHashMap<(cosmic_text::CacheKey, [u8; 3]), (Vec<u32>, cosmic_text::Placement)>,
239 recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>,
240 trim_count: usize,
241}
242
243impl GlyphCache {
244 const TRIM_INTERVAL: usize = 300;
245 const CAPACITY_LIMIT: usize = 16 * 1024;
246
247 fn new() -> Self {
248 GlyphCache::default()
249 }
250
251 fn allocate(
252 &mut self,
253 cache_key: cosmic_text::CacheKey,
254 color: Color,
255 font_system: &mut cosmic_text::FontSystem,
256 swash: &mut cosmic_text::SwashCache,
257 ) -> Option<(&[u8], cosmic_text::Placement)> {
258 let [r, g, b, _a] = color.into_rgba8();
259 let key = (cache_key, [r, g, b]);
260
261 if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) {
262 let image = swash.get_image_uncached(font_system, cache_key)?;
264
265 let glyph_size = image.placement.width as usize * image.placement.height as usize;
266
267 if glyph_size == 0 {
268 return None;
269 }
270
271 let mut buffer = vec![0u32; glyph_size];
272
273 match image.content {
274 cosmic_text::SwashContent::Mask => {
275 let mut i = 0;
276
277 for _y in 0..image.placement.height {
280 for _x in 0..image.placement.width {
281 buffer[i] = bytemuck::cast(
282 tiny_skia::ColorU8::from_rgba(b, g, r, image.data[i]).premultiply(),
283 );
284
285 i += 1;
286 }
287 }
288 }
289 cosmic_text::SwashContent::Color => {
290 let mut i = 0;
291
292 for _y in 0..image.placement.height {
293 for _x in 0..image.placement.width {
294 buffer[i >> 2] = bytemuck::cast(
296 tiny_skia::ColorU8::from_rgba(
297 image.data[i + 2],
298 image.data[i + 1],
299 image.data[i],
300 image.data[i + 3],
301 )
302 .premultiply(),
303 );
304
305 i += 4;
306 }
307 }
308 }
309 cosmic_text::SwashContent::SubpixelMask => {
310 }
312 }
313
314 let _ = entry.insert((buffer, image.placement));
315 }
316
317 let _ = self.recently_used.insert(key);
318
319 self.entries
320 .get(&key)
321 .map(|(buffer, placement)| (bytemuck::cast_slice(buffer.as_slice()), *placement))
322 }
323
324 pub fn trim(&mut self) {
325 if self.trim_count > Self::TRIM_INTERVAL || self.recently_used.len() >= Self::CAPACITY_LIMIT
326 {
327 self.entries
328 .retain(|key, _| self.recently_used.contains(key));
329
330 self.recently_used.clear();
331
332 self.entries.shrink_to(Self::CAPACITY_LIMIT);
333 self.recently_used.shrink_to(Self::CAPACITY_LIMIT);
334
335 self.trim_count = 0;
336 } else {
337 self.trim_count += 1;
338 }
339 }
340}