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 => {
130 bounds.x
131 }
132 Alignment::Center => bounds.x - width / 2.0,
133 Alignment::Right => bounds.x - width,
134 };
135
136 let y = match align_y {
137 alignment::Vertical::Top => bounds.y,
138 alignment::Vertical::Center => bounds.y - height / 2.0,
139 alignment::Vertical::Bottom => bounds.y - height,
140 };
141
142 draw(
143 font_system,
144 &mut self.glyph_cache,
145 &entry.buffer,
146 Point::new(x, y),
147 color,
148 pixels,
149 clip_mask,
150 transformation,
151 );
152 }
153
154 pub fn draw_raw(
155 &mut self,
156 buffer: &cosmic_text::Buffer,
157 position: Point,
158 color: Color,
159 pixels: &mut tiny_skia::PixmapMut<'_>,
160 clip_mask: Option<&tiny_skia::Mask>,
161 transformation: Transformation,
162 ) {
163 let mut font_system = font_system().write().expect("Write font system");
164
165 draw(
166 font_system.raw(),
167 &mut self.glyph_cache,
168 buffer,
169 position,
170 color,
171 pixels,
172 clip_mask,
173 transformation,
174 );
175 }
176
177 pub fn trim_cache(&mut self) {
178 self.cache.get_mut().trim();
179 self.glyph_cache.trim();
180 }
181}
182
183fn draw(
184 font_system: &mut cosmic_text::FontSystem,
185 glyph_cache: &mut GlyphCache,
186 buffer: &cosmic_text::Buffer,
187 position: Point,
188 color: Color,
189 pixels: &mut tiny_skia::PixmapMut<'_>,
190 clip_mask: Option<&tiny_skia::Mask>,
191 transformation: Transformation,
192) {
193 let position = position * transformation;
194
195 let mut swash = cosmic_text::SwashCache::new();
196
197 for run in buffer.layout_runs() {
198 for glyph in run.glyphs {
199 let physical_glyph = glyph.physical(
200 (position.x, position.y),
201 transformation.scale_factor(),
202 );
203
204 if let Some((buffer, placement)) = glyph_cache.allocate(
205 physical_glyph.cache_key,
206 glyph.color_opt.map(from_color).unwrap_or(color),
207 font_system,
208 &mut swash,
209 ) {
210 let pixmap = tiny_skia::PixmapRef::from_bytes(
211 buffer,
212 placement.width,
213 placement.height,
214 )
215 .expect("Create glyph pixel map");
216
217 let opacity = color.a
218 * glyph
219 .color_opt
220 .map(|c| c.a() as f32 / 255.0)
221 .unwrap_or(1.0);
222
223 pixels.draw_pixmap(
224 physical_glyph.x + placement.left,
225 physical_glyph.y - placement.top
226 + (run.line_y * transformation.scale_factor()).round()
227 as i32,
228 pixmap,
229 &tiny_skia::PixmapPaint {
230 opacity,
231 ..tiny_skia::PixmapPaint::default()
232 },
233 tiny_skia::Transform::identity(),
234 clip_mask,
235 );
236 }
237 }
238 }
239}
240
241fn from_color(color: cosmic_text::Color) -> Color {
242 let [r, g, b, a] = color.as_rgba();
243
244 Color::from_rgba8(r, g, b, a as f32 / 255.0)
245}
246
247#[derive(Debug, Clone, Default)]
248struct GlyphCache {
249 entries: FxHashMap<
250 (cosmic_text::CacheKey, [u8; 3]),
251 (Vec<u32>, cosmic_text::Placement),
252 >,
253 recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>,
254 trim_count: usize,
255}
256
257impl GlyphCache {
258 const TRIM_INTERVAL: usize = 300;
259 const CAPACITY_LIMIT: usize = 16 * 1024;
260
261 fn new() -> Self {
262 GlyphCache::default()
263 }
264
265 fn allocate(
266 &mut self,
267 cache_key: cosmic_text::CacheKey,
268 color: Color,
269 font_system: &mut cosmic_text::FontSystem,
270 swash: &mut cosmic_text::SwashCache,
271 ) -> Option<(&[u8], cosmic_text::Placement)> {
272 let [r, g, b, _a] = color.into_rgba8();
273 let key = (cache_key, [r, g, b]);
274
275 if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) {
276 let image = swash.get_image_uncached(font_system, cache_key)?;
278
279 let glyph_size = image.placement.width as usize
280 * image.placement.height as usize;
281
282 if glyph_size == 0 {
283 return None;
284 }
285
286 let mut buffer = vec![0u32; glyph_size];
287
288 match image.content {
289 cosmic_text::SwashContent::Mask => {
290 let mut i = 0;
291
292 for _y in 0..image.placement.height {
295 for _x in 0..image.placement.width {
296 buffer[i] = bytemuck::cast(
297 tiny_skia::ColorU8::from_rgba(
298 b,
299 g,
300 r,
301 image.data[i],
302 )
303 .premultiply(),
304 );
305
306 i += 1;
307 }
308 }
309 }
310 cosmic_text::SwashContent::Color => {
311 let mut i = 0;
312
313 for _y in 0..image.placement.height {
314 for _x in 0..image.placement.width {
315 buffer[i >> 2] = bytemuck::cast(
317 tiny_skia::ColorU8::from_rgba(
318 image.data[i + 2],
319 image.data[i + 1],
320 image.data[i],
321 image.data[i + 3],
322 )
323 .premultiply(),
324 );
325
326 i += 4;
327 }
328 }
329 }
330 cosmic_text::SwashContent::SubpixelMask => {
331 }
333 }
334
335 let _ = entry.insert((buffer, image.placement));
336 }
337
338 let _ = self.recently_used.insert(key);
339
340 self.entries.get(&key).map(|(buffer, placement)| {
341 (bytemuck::cast_slice(buffer.as_slice()), *placement)
342 })
343 }
344
345 pub fn trim(&mut self) {
346 if self.trim_count > Self::TRIM_INTERVAL
347 || self.recently_used.len() >= Self::CAPACITY_LIMIT
348 {
349 self.entries
350 .retain(|key, _| self.recently_used.contains(key));
351
352 self.recently_used.clear();
353
354 self.entries.shrink_to(Self::CAPACITY_LIMIT);
355 self.recently_used.shrink_to(Self::CAPACITY_LIMIT);
356
357 self.trim_count = 0;
358 } else {
359 self.trim_count += 1;
360 }
361 }
362}