Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/windowing/winit/fonts/swash_rasterizer.rs
StratoSDK / crates / strato-ui-renderer / src / windowing / winit / fonts / swash_rasterizer.rs
1//! Module that rasterizes text using `swash`.
2 
3use crate::fonts::canvas::{Canvas, RasterFormat};
4use crate::fonts::{FontId, GlyphId, RasterizedGlyph, SubpixelAlignment};
5use crate::platform::FontDB as _;
6use crate::rendering::GlyphConfig;
7use crate::windowing::winit::fonts::FontDB;
8use anyhow::{anyhow, Result};
9use cosmic_text::{CacheKey, CacheKeyFlags};
10use pathfinder_geometry::rect::RectI;
11use pathfinder_geometry::vector::{vec2i, Vector2F, Vector2I};
12 
13impl FontDB {
14 pub(super) fn glyph_raster_bounds(
15 &self,
16 font_id: FontId,
17 size: f32,
18 glyph_id: GlyphId,
19 scale: Vector2F,
20 _glyph_config: &GlyphConfig,
21 ) -> Result<RectI> {
22 let Ok(_typographic_bounds) = self
23 .glyph_typographic_bounds(font_id, glyph_id)
24 .map(|bounds| bounds.to_f32())
25 else {
26 // We can't render this glyph using this font, return an empty rect to indicate we
27 // don't need to rasterize this glyph. This can happen if the font doesn't contain
28 // a glyph _or_ if the glyph isn't renderable (some fonts contain a glyph for the
29 // space character, but don't provide outlines for it).
30 return Ok(RectI::new(Vector2I::zero(), Vector2I::zero()));
31 };
32 
33 let id = *self
34 .text_layout_system
35 .font_id_map
36 .read()
37 .get_by_left(&font_id)
38 .unwrap();
39 let image = self
40 .swash_cache
41 .write()
42 .get_image_uncached(
43 &mut self.text_layout_system.font_store.write(),
44 CacheKey::new(
45 id,
46 glyph_id as u16,
47 size * scale.x(),
48 (0., 0.),
49 CacheKeyFlags::empty(),
50 )
51 .0,
52 )
53 .clone()
54 .ok_or_else(|| anyhow!("Failed to get raster image"))?;
55 
56 let origin = vec2i(image.placement.left, -image.placement.top);
57 let size = vec2i(image.placement.width as i32, image.placement.height as i32);
58 Ok(RectI::new(origin, size))
59 }
60 
61 #[allow(clippy::too_many_arguments)]
62 pub(super) fn rasterize_glyph(
63 &self,
64 font_id: FontId,
65 size: f32,
66 glyph_id: GlyphId,
67 scale: Vector2F,
68 subpixel_alignment: SubpixelAlignment,
69 glyph_config: &GlyphConfig,
70 requested_format: RasterFormat,
71 ) -> Result<RasterizedGlyph> {
72 let raster_bounds =
73 self.glyph_raster_bounds(font_id, size, glyph_id, scale, glyph_config)?;
74 
75 let id = *self
76 .text_layout_system
77 .font_id_map
78 .read()
79 .get_by_left(&font_id)
80 .unwrap();
81 // Get the raster image without caching--the parent FontDB handles all caching for us.
82 let image = self
83 .swash_cache
84 .write()
85 .get_image_uncached(
86 &mut self.text_layout_system.font_store.write(),
87 CacheKey::new(
88 id,
89 glyph_id as u16,
90 size * scale.x(),
91 (subpixel_alignment.to_offset().x(), 0.),
92 CacheKeyFlags::empty(),
93 )
94 .0,
95 )
96 .clone()
97 .unwrap();
98 
99 let (original_format, is_color) = match image.content {
100 cosmic_text::SwashContent::Mask => (RasterFormat::A8, false),
101 cosmic_text::SwashContent::SubpixelMask => (RasterFormat::Rgba32, false),
102 cosmic_text::SwashContent::Color => (RasterFormat::Rgba32, true),
103 };
104 
105 // Ensure the pixmap is in the correct requested format (in practice this converts A8 to
106 // RGBA32).
107 // TODO(alokedesai): Ensure our font rasterization code is robust to returned formats that
108 // are different than incoming formats. Right now, we create text bounds based on the
109 // _incoming_ format.
110 let pixmap = if original_format == RasterFormat::A8 {
111 let bytes_per_pixel = requested_format.bytes_per_pixel() as usize;
112 let mut pixmap = Vec::with_capacity(image.data.len() * bytes_per_pixel);
113 for byte in image.data {
114 for _ in 0..bytes_per_pixel {
115 pixmap.push(byte);
116 }
117 }
118 pixmap
119 } else {
120 image.data
121 };
122 
123 let canvas = Canvas {
124 pixels: pixmap,
125 size: raster_bounds.size(),
126 row_stride: image.placement.width as usize * original_format.bytes_per_pixel() as usize,
127 format: RasterFormat::Rgba32,
128 };
129 
130 anyhow::Ok(RasterizedGlyph {
131 canvas,
132 is_emoji: is_color,
133 })
134 }
135}
136