comfy_wgpu/
text.rs

1use crate::*;
2
3use etagere::AtlasAllocator;
4use fontdue::{layout::*, *};
5use image::{Rgba, RgbaImage};
6
7#[derive(Debug)]
8pub struct Glyph {
9    pub metrics: fontdue::Metrics,
10    pub bitmap: Vec<u8>,
11    // pub texture: TextureHandle,
12    // pub allocation: etagere::Allocation,
13    pub rect: IRect,
14}
15
16fn make_layout() -> fontdue::layout::Layout {
17    fontdue::layout::Layout::new(fontdue::layout::CoordinateSystem::PositiveYUp)
18}
19
20pub struct TextRasterizer {
21    pub context: GraphicsContext,
22
23    glyphs: HashMap<(FontHandle, OrderedFloat<f32>, char), Glyph>,
24    atlas: etagere::AtlasAllocator,
25
26    texture: TextureHandle,
27
28    pub atlas_size: u32,
29}
30
31impl TextRasterizer {
32    pub fn new(context: GraphicsContext) -> Self {
33        let glyphs = HashMap::new();
34
35        const TEXT_ATLAS_SIZE: u32 = 4096;
36        let size = uvec2(TEXT_ATLAS_SIZE, TEXT_ATLAS_SIZE);
37
38        let texture = context.texture_creator.borrow_mut().handle_from_size(
39            "Font Atlas",
40            size,
41            TRANSPARENT,
42        );
43
44        // TODO: prepare ASCII
45
46        // for c in " 0123456789\n\t!@#$%^&*(){}[]<>/,.\\';:\"|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".chars() {
47        // }
48
49        Self {
50            context,
51            glyphs,
52            atlas: AtlasAllocator::new(etagere::size2(
53                size.x as i32,
54                size.y as i32,
55            )),
56            texture,
57            atlas_size: TEXT_ATLAS_SIZE,
58        }
59    }
60
61    pub fn calculate_text_layout(
62        &mut self,
63        assets: &Assets,
64        text: TextData,
65        pro_params: ProTextParams,
66    ) -> (fontdue::layout::Layout, Rect, Option<Vec<StyledGlyph>>) {
67        let (clean_text, styled_glyphs) = match text {
68            TextData::Raw(raw_text) => (raw_text, None),
69            TextData::Rich(rich_text) => {
70                (rich_text.clean_text, Some(rich_text.styled_glyphs))
71            }
72        };
73
74        let font_handle = pro_params.font;
75        let font = assets.fonts.get(&font_handle).unwrap();
76
77        let layout = self.layout_text(
78            font,
79            &clean_text,
80            pro_params.font_size,
81            &fontdue::layout::LayoutSettings { ..Default::default() },
82        );
83
84        let mut min_x = f32::INFINITY;
85        let mut min_y = f32::INFINITY;
86        let mut max_x = f32::NEG_INFINITY;
87        let mut max_y = f32::NEG_INFINITY;
88
89        for glyph in layout.glyphs() {
90            let glyph_min_x = glyph.x;
91            let glyph_min_y = glyph.y;
92            let glyph_max_x = glyph.x + glyph.width as f32;
93            let glyph_max_y = glyph.y + glyph.height as f32;
94
95            min_x = min_x.min(glyph_min_x);
96            min_y = min_y.min(glyph_min_y);
97            max_x = max_x.max(glyph_max_x);
98            max_y = max_y.max(glyph_max_y);
99        }
100
101        let layout_rect =
102            Rect::from_min_max(vec2(min_x, min_y), vec2(max_x, max_y));
103
104        (layout, layout_rect, styled_glyphs)
105    }
106
107    pub fn get_glyph(
108        &mut self,
109        font_handle: FontHandle,
110        font: &Font,
111        font_size: f32,
112        c: char,
113    ) -> (TextureHandle, IRect) {
114        let key = (font_handle, OrderedFloat(font_size), c);
115        if !self.glyphs.contains_key(&key) {
116            self.prepare_rasterize(font_handle, font, font_size, c);
117        }
118
119        (self.texture, self.glyphs[&key].rect)
120    }
121
122    pub fn prepare_rasterize(
123        &mut self,
124        font_handle: FontHandle,
125        font: &Font,
126        font_size: f32,
127        c: char,
128    ) {
129        let (metrics, bitmap) = font.rasterize(c, font_size);
130
131        // if metrics.width > 0 {
132        //     bitmap.flip_inplace(metrics.width);
133        // }
134
135        let mut rgba_bitmap = vec![];
136
137        for x in bitmap.iter() {
138            rgba_bitmap.push(*x);
139            rgba_bitmap.push(*x);
140            rgba_bitmap.push(*x);
141            rgba_bitmap.push(*x);
142        }
143
144        // println!(
145        //     "metrics are {} {} for '{}'",
146        //     metrics.width, metrics.height, c
147        // );
148
149        if !(metrics.width == 0 || metrics.height == 0) {
150            let mut image =
151                RgbaImage::new(metrics.width as u32, metrics.height as u32);
152
153            for x in 0..metrics.width {
154                for y in 0..metrics.height {
155                    let i = y * metrics.width + x;
156
157                    let v = bitmap[i];
158                    let pixel = Rgba([v, v, v, v]);
159                    image.put_pixel(x as u32, y as u32, pixel);
160                }
161            }
162
163            let image = DynamicImage::ImageRgba8(image).flipv().to_rgba8();
164
165            let pad = 2;
166
167            let allocation = self
168                .atlas
169                .allocate(etagere::size2(
170                    metrics.width as i32 + 2 * pad,
171                    metrics.height as i32 + 2 * pad,
172                ))
173                .unwrap_or_else(|| panic!("FAILED TO FIT GLYPH {}", c));
174
175            if self.atlas.free_space() < self.atlas.allocated_space() {
176                let used = self.atlas.free_space();
177                let total = self.atlas.size().area();
178                info!(
179                    "font atlas has {}/{} space ({:.2}%) used",
180                    used,
181                    total,
182                    (1.0 - used as f32 / total as f32) * 100.0
183                );
184            }
185
186            let rect = allocation.rectangle.to_rect();
187            let inset_rect = IRect::new(
188                ivec2(rect.origin.x + pad, rect.origin.y + pad),
189                ivec2(rect.size.width - 2 * pad, rect.size.height - 2 * pad),
190            );
191            self.context.texture_creator.borrow_mut().update_texture_region(
192                self.texture,
193                &image,
194                inset_rect,
195            );
196
197            let glyph = Glyph { metrics, bitmap, rect: inset_rect };
198
199            self.glyphs
200                .insert((font_handle, OrderedFloat(font_size), c), glyph);
201        };
202    }
203
204    pub fn layout_text(
205        &mut self,
206        font: &Font,
207        text: &str,
208        size: f32,
209        layout_settings: &LayoutSettings,
210    ) -> fontdue::layout::Layout {
211        let mut layout = make_layout();
212        layout.reset(layout_settings);
213
214        layout
215            .append(std::slice::from_ref(font), &TextStyle::new(text, size, 0));
216        layout
217    }
218
219    #[allow(dead_code)]
220    pub fn layout_text_demo(&mut self, font: &Font) -> Vec<GlyphPosition> {
221        // let mut layout = fontdue::layout::Layout::new(
222        //     fontdue::layout::CoordinateSystem::PositiveYUp,
223        // );
224        //
225        // layout.reset(&LayoutSettings {
226        //     ..LayoutSettings::default()
227        // });
228        //
229        // let fonts = &[self.font.clone()];
230        //
231        // layout.append(fonts, &TextStyle::new("Hello ", 35.0, 0));
232        // layout.append(fonts, &TextStyle::new("world!", 40.0, 0));
233        //
234        // layout.glyphs().clone()
235
236        let mut layout = make_layout();
237
238        layout.reset(&LayoutSettings { ..LayoutSettings::default() });
239
240        let fonts = std::slice::from_ref(font);
241
242        layout.append(fonts, &TextStyle::new("Hello\n", 16.0, 0));
243        layout.append(fonts, &TextStyle::new("\tworld!", 16.0, 0));
244
245        layout.glyphs().clone()
246    }
247}
248
249pub trait EtagereRectExtensions {
250    fn to_irect(&self) -> IRect;
251}
252
253impl EtagereRectExtensions for etagere::Allocation {
254    fn to_irect(&self) -> IRect {
255        let rect = self.rectangle.to_rect();
256
257        let offset = ivec2(rect.origin.x, rect.origin.y);
258        let size = ivec2(rect.size.width, rect.size.height);
259
260        IRect { offset, size }
261    }
262}