1use cosmic_text::{FontSystem, SwashCache, SwashContent, SwashImage};
10use crabshell_ui::{
11 Color, Content, IconView, NodeView, Rect, Size, Style, TextSystem, TextView, WindowView,
12};
13use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, PixmapMut, PixmapPaint, Transform};
14
15use crate::damage::{BufferAge, DamageHistory, PixelRect};
16use crate::icons::Icons;
17
18const TILE: u32 = 64;
23
24const GLYPH_CACHE_LIMIT: usize = 2048;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum PixelFormat {
32 Argb8888,
34 Abgr8888,
36}
37
38pub struct Canvas<'a> {
40 pub pixels: &'a mut [u8],
42 pub width: u32,
46 pub height: u32,
48 pub scale: f32,
50 pub format: PixelFormat,
52 pub age: &'a mut BufferAge,
54}
55
56pub struct Renderer {
59 glyphs: SwashCache,
60 icons: Icons,
61 scratch: Vec<u8>,
64 work: Work,
65}
66
67#[derive(Clone, Copy, Default)]
70#[cfg_attr(not(test), allow(dead_code, reason = "only tests read it"))]
71struct Work {
72 layer_pixels: u64,
74 nodes: u64,
76}
77
78impl Renderer {
79 pub fn new() -> Renderer {
82 Renderer {
83 glyphs: SwashCache::new(),
84 icons: Icons::new(),
85 scratch: Vec::new(),
86 work: Work::default(),
87 }
88 }
89
90 #[cfg(test)]
92 pub(crate) fn cached_glyphs(&self) -> usize {
93 self.glyphs.image_cache.len()
94 }
95
96 #[cfg(test)]
99 pub(crate) fn work(&self) -> (u64, u64) {
100 (self.work.layer_pixels, self.work.nodes)
101 }
102
103 #[cfg(test)]
106 pub(crate) fn with_icon_dirs(bases: Vec<std::path::PathBuf>) -> Renderer {
107 Renderer {
108 icons: Icons::with_themes(crate::theme::Themes::new(bases)),
109 ..Renderer::new()
110 }
111 }
112
113 pub fn set_icon_theme(&mut self, theme: impl Into<String>) {
115 self.icons.set_theme(theme.into());
116 }
117
118 #[cfg(test)]
120 pub(crate) fn cached_icons(&self) -> usize {
121 self.icons.cached()
122 }
123
124 #[cfg(test)]
126 pub(crate) fn rasterized_icons(&self) -> usize {
127 self.icons.rasterized()
128 }
129
130 pub fn draw(
148 &mut self,
149 view: &WindowView<'_>,
150 damage: &[Rect],
151 text: &mut TextSystem,
152 history: &mut DamageHistory,
153 canvas: Canvas<'_>,
154 ) -> Result<Vec<PixelRect>, CanvasError> {
155 let Canvas {
156 pixels,
157 width,
158 height,
159 scale,
160 format,
161 age,
162 } = canvas;
163 if pixels.len() as u64 != u64::from(width) * u64::from(height) * 4
164 || !(scale.is_finite() && scale > 0.0)
165 {
166 return Err(CanvasError {
167 width,
168 height,
169 scale,
170 bytes: pixels.len(),
171 });
172 }
173 let plan = history.plan(damage, scale, width, height, age);
174 let mut tiles: Vec<(u32, u32)> = Vec::new();
175 for area in plan.repaint.iter().filter(|area| area.area() > 0) {
176 for row in area.y / TILE..=(area.y + area.height - 1) / TILE {
177 tiles
178 .extend((area.x / TILE..=(area.x + area.width - 1) / TILE).map(|column| (row, column)));
179 }
180 }
181 tiles.sort_unstable();
182 tiles.dedup();
183 for (row, column) in tiles {
184 let (x, y) = (column * TILE, row * TILE);
185 let size = (width.saturating_sub(x), height.saturating_sub(y));
186 let area = PixelRect::new(x, y, TILE.min(size.0), TILE.min(size.1));
187 self.scratch.clear();
188 self.scratch.resize(area.area() as usize * 4, 0);
189 let Some(mut pixmap) = PixmapMut::from_bytes(&mut self.scratch, area.width, area.height)
190 else {
191 continue;
192 };
193 let mut painter = Painter {
194 grid: Grid {
195 scale,
196 window: view.size(),
197 width,
198 height,
199 left: area.x as f32,
200 top: area.y as f32,
201 },
202 glyphs: &mut self.glyphs,
203 icons: &mut self.icons,
204 fonts: text.font_system(),
205 work: &mut self.work,
206 };
207 painter.node(&mut pixmap, view.root());
208 copy(&self.scratch, area, pixels, width, format);
209 }
210 self.icons.end_frame();
211 if self.glyphs.image_cache.len() > GLYPH_CACHE_LIMIT {
212 self.glyphs.image_cache.clear();
213 self.glyphs.outline_command_cache.clear();
214 }
215 Ok(plan.report)
216 }
217}
218
219#[derive(Clone, Copy, Debug, PartialEq)]
222pub struct CanvasError {
223 pub width: u32,
225 pub height: u32,
227 pub scale: f32,
229 pub bytes: usize,
231}
232
233impl std::fmt::Display for CanvasError {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 write!(
236 f,
237 "a {}x{} canvas at scale {} needs {} bytes, but has {}",
238 self.width,
239 self.height,
240 self.scale,
241 u64::from(self.width) * u64::from(self.height) * 4,
242 self.bytes
243 )
244 }
245}
246
247impl std::error::Error for CanvasError {}
248
249impl Default for Renderer {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255fn copy(scratch: &[u8], area: PixelRect, pixels: &mut [u8], width: u32, format: PixelFormat) {
257 let stride = width as usize * 4;
258 let row_bytes = area.width as usize * 4;
259 for (row, source) in scratch.chunks_exact(row_bytes).enumerate() {
260 let start = (area.y as usize + row) * stride + area.x as usize * 4;
261 let target = &mut pixels[start..start + row_bytes];
262 match format {
263 PixelFormat::Abgr8888 => target.copy_from_slice(source),
264 PixelFormat::Argb8888 => {
265 let (to, _) = target.as_chunks_mut::<4>();
266 let (from, _) = source.as_chunks::<4>();
267 for (to, from) in to.iter_mut().zip(from) {
268 *to = [from[2], from[1], from[0], from[3]];
269 }
270 }
271 }
272 }
273}
274
275#[derive(Clone, Copy)]
277struct Grid {
278 scale: f32,
279 window: Size,
281 width: u32,
283 height: u32,
284 left: f32,
286 top: f32,
287}
288
289impl Grid {
290 fn x(&self, x: f32) -> f32 {
293 let pixel = if x >= self.window.width {
294 self.width as f32
295 } else {
296 (x * self.scale).round()
297 };
298 pixel - self.left
299 }
300
301 fn y(&self, y: f32) -> f32 {
303 let pixel = if y >= self.window.height {
304 self.height as f32
305 } else {
306 (y * self.scale).round()
307 };
308 pixel - self.top
309 }
310
311 fn cover(&self, rect: Rect, width: u32, height: u32) -> Option<(u32, u32, u32, u32)> {
315 if !(rect.width > 0.0 && rect.height > 0.0) {
316 return None;
317 }
318 let left = ((rect.x * self.scale).floor() - self.left - 1.0).max(0.0) as u32;
320 let top = ((rect.y * self.scale).floor() - self.top - 1.0).max(0.0) as u32;
321 let right = (((rect.right() * self.scale).ceil() - self.left + 1.0).max(0.0) as u32).min(width);
322 let bottom =
323 (((rect.bottom() * self.scale).ceil() - self.top + 1.0).max(0.0) as u32).min(height);
324 (left < right && top < bottom).then(|| (left, top, right - left, bottom - top))
325 }
326
327 fn moved(&self, x: u32, y: u32) -> Grid {
329 Grid {
330 left: self.left + x as f32,
331 top: self.top + y as f32,
332 ..*self
333 }
334 }
335
336 fn rect(&self, rect: Rect) -> Option<tiny_skia::Rect> {
339 tiny_skia::Rect::from_ltrb(
340 self.x(rect.x),
341 self.y(rect.y),
342 self.x(rect.right()),
343 self.y(rect.bottom()),
344 )
345 .filter(|rect| rect.width() >= 1.0 && rect.height() >= 1.0)
346 }
347}
348
349struct Painter<'a> {
351 grid: Grid,
352 glyphs: &'a mut SwashCache,
353 icons: &'a mut Icons,
354 fonts: &'a mut FontSystem,
355 work: &'a mut Work,
356}
357
358impl Painter<'_> {
359 fn node(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>) {
363 let Some((x, y, width, height)) =
365 self
366 .grid
367 .cover(node.bounds(), pixmap.width(), pixmap.height())
368 else {
369 return;
370 };
371 let style = node.style();
372 let opacity = style.opacity.unwrap_or(1.0);
373 let opacity = if opacity.is_nan() {
374 1.0
375 } else {
376 opacity.clamp(0.0, 1.0)
377 };
378 if opacity == 0.0 {
379 return;
380 }
381 if opacity < 1.0 {
382 let Some(mut layer) = Pixmap::new(width, height) else {
384 return;
385 };
386 self.work.layer_pixels += u64::from(layer.width()) * u64::from(layer.height());
387 let mut inner = Painter {
388 grid: self.grid.moved(x, y),
389 glyphs: &mut *self.glyphs,
390 icons: &mut *self.icons,
391 fonts: &mut *self.fonts,
392 work: &mut *self.work,
393 };
394 inner.node_contents(&mut layer.as_mut(), node, &style);
395 let paint = PixmapPaint {
396 opacity,
397 ..PixmapPaint::default()
398 };
399 let (x, y) = (x as i32, y as i32);
400 pixmap.draw_pixmap(x, y, layer.as_ref(), &paint, Transform::identity(), None);
401 } else {
402 self.node_contents(pixmap, node, &style);
403 }
404 }
405
406 fn node_contents(&mut self, pixmap: &mut PixmapMut<'_>, node: NodeView<'_>, style: &Style) {
407 self.work.nodes += 1;
408 self.draw_box(pixmap, node.rect(), style);
409 match node.content() {
410 Content::Text(text) => self.text(pixmap, &text),
411 Content::Icon(icon) => self.icon(pixmap, &icon),
412 Content::None => {}
413 }
414 for child in node.children() {
415 self.node(pixmap, child);
416 }
417 }
418
419 fn draw_box(&self, pixmap: &mut PixmapMut<'_>, rect: Rect, style: &Style) {
421 if style.background.is_none() && style.border.is_none() {
422 return;
423 }
424 let Some(area) = self.grid.rect(rect) else {
425 return;
426 };
427 if !visible(pixmap, area) {
428 return;
429 }
430 let radius = (style.radius.unwrap_or(0.0) * self.grid.scale)
431 .clamp(0.0, area.width().min(area.height()) / 2.0);
432 let radius = if radius.is_nan() { 0.0 } else { radius };
433
434 if let Some(color) = style.background {
435 let mut path = PathBuilder::new();
436 rounded_rect(&mut path, area, radius);
437 fill(pixmap, path, color, FillRule::Winding);
438 }
439 if let Some(border) = style.border {
440 let width = (border.width * self.grid.scale).floor();
442 let width = if border.width > 0.0 {
443 width.max(1.0)
444 } else {
445 width
446 };
447 if width >= 1.0 {
448 let mut path = PathBuilder::new();
450 rounded_rect(&mut path, area, radius);
451 if let Some(inner) = tiny_skia::Rect::from_ltrb(
452 area.left() + width,
453 area.top() + width,
454 area.right() - width,
455 area.bottom() - width,
456 ) {
457 rounded_rect(&mut path, inner, (radius - width).max(0.0));
458 }
459 fill(pixmap, path, border.color, FillRule::EvenOdd);
460 }
461 }
462 }
463
464 fn text(&mut self, pixmap: &mut PixmapMut<'_>, text: &TextView<'_>) {
468 let color = text.color();
469 if color.a == 0 {
470 return;
471 }
472 let Grid {
473 scale, left, top, ..
474 } = self.grid;
475 let origin = text.origin();
476 let x = origin.x * scale - left;
477 for run in text.runs() {
478 let baseline = ((origin.y + run.line_y) * scale).round() - top;
480 if baseline + run.line_height * scale < 0.0
481 || baseline - run.line_height * scale > pixmap.height() as f32
482 {
483 continue;
484 }
485 for glyph in run.glyphs {
486 let physical = glyph.physical((x, baseline), scale);
487 let Some(image) = self.glyphs.get_image(self.fonts, physical.cache_key) else {
488 continue;
489 };
490 let left = physical.x + image.placement.left;
491 let top = physical.y - image.placement.top;
492 let (width, height) = (image.placement.width, image.placement.height);
493 match image.content {
494 SwashContent::Mask => {
495 blend_mask(pixmap, left, top, width, height, &image.data, color);
496 }
497 SwashContent::Color => {
498 let premultiplied = is_color_outline(image);
499 let rgba = (image.data.as_slice(), premultiplied);
500 blend_rgba(pixmap, left, top, width, height, rgba, color.a);
501 }
502 SwashContent::SubpixelMask => {}
504 }
505 }
506 }
507 }
508
509 fn icon(&mut self, pixmap: &mut PixmapMut<'_>, icon: &IconView<'_>) {
511 let Grid {
512 scale, left, top, ..
513 } = self.grid;
514 let side = (icon.rect.width * scale).round();
515 if side.is_nan() || side < 1.0 {
516 return;
517 }
518 let x = (icon.rect.x * scale).round() - left;
519 let y = (icon.rect.y * scale).round() - top;
520 let size = icon.rect.width.round() as u32;
522 let theme_scale = scale.ceil().max(1.0) as u32;
523 if let Some(image) = self.icons.get(icon.source, side as u32, size, theme_scale) {
524 pixmap.draw_pixmap(
525 x as i32,
526 y as i32,
527 image.as_ref(),
528 &PixmapPaint::default(),
529 Transform::identity(),
530 None,
531 );
532 }
533 }
534}
535
536fn visible(pixmap: &PixmapMut<'_>, area: tiny_skia::Rect) -> bool {
538 area.right() > 0.0
539 && area.bottom() > 0.0
540 && area.left() < pixmap.width() as f32
541 && area.top() < pixmap.height() as f32
542}
543
544fn fill(pixmap: &mut PixmapMut<'_>, path: PathBuilder, color: Color, rule: FillRule) {
545 let Some(path) = path.finish() else {
546 return;
547 };
548 let mut paint = Paint::default();
549 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
550 paint.anti_alias = true;
551 pixmap.fill_path(&path, &paint, rule, Transform::identity(), None);
552}
553
554fn rounded_rect(path: &mut PathBuilder, rect: tiny_skia::Rect, radius: f32) {
556 if radius <= 0.0 {
557 path.push_rect(rect);
558 return;
559 }
560 const KAPPA: f32 = 0.552_284_8;
563 let (l, t, r, b) = (rect.left(), rect.top(), rect.right(), rect.bottom());
564 let k = radius * (1.0 - KAPPA);
565 path.move_to(l + radius, t);
566 path.line_to(r - radius, t);
567 path.cubic_to(r - k, t, r, t + k, r, t + radius);
568 path.line_to(r, b - radius);
569 path.cubic_to(r, b - k, r - k, b, r - radius, b);
570 path.line_to(l + radius, b);
571 path.cubic_to(l + k, b, l, b - k, l, b - radius);
572 path.line_to(l, t + radius);
573 path.cubic_to(l, t + k, l + k, t, l + radius, t);
574 path.close();
575}
576
577fn mul(a: u8, b: u8) -> u8 {
579 ((u16::from(a) * u16::from(b) + 127) / 255) as u8
580}
581
582fn over(data: &mut [u8], index: usize, source: [u8; 4]) {
584 let rest = 255 - source[3];
585 for (channel, value) in data[index..index + 4].iter_mut().zip(source) {
586 *channel = value.saturating_add(mul(*channel, rest));
587 }
588}
589
590fn overlap(
594 pixmap: &PixmapMut<'_>,
595 left: i32,
596 top: i32,
597 width: u32,
598 height: u32,
599) -> Option<(usize, usize, usize, usize)> {
600 let column_start = (-i64::from(left)).max(0);
601 let row_start = (-i64::from(top)).max(0);
602 let column_end = i64::from(width).min(i64::from(pixmap.width()) - i64::from(left));
603 let row_end = i64::from(height).min(i64::from(pixmap.height()) - i64::from(top));
604 (column_start < column_end && row_start < row_end).then_some((
605 column_start as usize,
606 column_end as usize,
607 row_start as usize,
608 row_end as usize,
609 ))
610}
611
612fn blend_mask(
614 pixmap: &mut PixmapMut<'_>,
615 left: i32,
616 top: i32,
617 width: u32,
618 height: u32,
619 mask: &[u8],
620 color: Color,
621) {
622 if mask.len() < width as usize * height as usize {
623 return;
624 }
625 let Some((columns, columns_end, rows, rows_end)) = overlap(pixmap, left, top, width, height)
626 else {
627 return;
628 };
629 let stride = pixmap.width() as usize;
630 let data = pixmap.data_mut();
631 for row in rows..rows_end {
632 for column in columns..columns_end {
633 let coverage = mask[row * width as usize + column];
634 if coverage == 0 {
635 continue;
636 }
637 let alpha = mul(color.a, coverage);
638 let source = [
639 mul(color.r, alpha),
640 mul(color.g, alpha),
641 mul(color.b, alpha),
642 alpha,
643 ];
644 let x = (i64::from(left) + column as i64) as usize;
645 let y = (i64::from(top) + row as i64) as usize;
646 over(data, (y * stride + x) * 4, source);
647 }
648 }
649}
650
651fn is_color_outline(image: &SwashImage) -> bool {
658 format!("{:?}", image.source).starts_with("ColorOutline")
659}
660
661fn blend_rgba(
664 pixmap: &mut PixmapMut<'_>,
665 left: i32,
666 top: i32,
667 width: u32,
668 height: u32,
669 (rgba, premultiplied): (&[u8], bool),
670 opacity: u8,
671) {
672 if rgba.len() < width as usize * height as usize * 4 {
673 return;
674 }
675 let Some((columns, columns_end, rows, rows_end)) = overlap(pixmap, left, top, width, height)
676 else {
677 return;
678 };
679 let stride = pixmap.width() as usize;
680 let data = pixmap.data_mut();
681 for row in rows..rows_end {
682 for column in columns..columns_end {
683 let index = (row * width as usize + column) * 4;
684 let alpha = mul(rgba[index + 3], opacity);
685 if alpha == 0 {
686 continue;
687 }
688 let factor = if premultiplied { opacity } else { alpha };
691 let source = [
692 mul(rgba[index], factor),
693 mul(rgba[index + 1], factor),
694 mul(rgba[index + 2], factor),
695 alpha,
696 ];
697 let x = (i64::from(left) + column as i64) as usize;
698 let y = (i64::from(top) + row as i64) as usize;
699 over(data, (y * stride + x) * 4, source);
700 }
701 }
702}