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