Skip to main content

nvg/
fonts.rs

1use crate::context::{ImageId, TextMetrics};
2use crate::renderer::TextureType;
3use crate::{Align, Bounds, Extent, ImageFlags, Renderer};
4use bitflags::_core::borrow::Borrow;
5use rusttype::gpu_cache::Cache;
6use rusttype::{Font, Glyph, Point, PositionedGlyph, Scale};
7use slab::Slab;
8use std::collections::HashMap;
9
10const TEX_WIDTH: usize = 1024;
11const TEX_HEIGHT: usize = 1024;
12
13pub type FontId = usize;
14
15#[derive(Debug)]
16pub struct LayoutChar {
17    id: FontId,
18    pub x: f32,
19    pub next_x: f32,
20    pub c: char,
21    pub idx: usize,
22    glyph: PositionedGlyph<'static>,
23    pub uv: Bounds,
24    pub bounds: Bounds,
25}
26
27struct FontData {
28    font: Font<'static>,
29    fallback_fonts: Vec<FontId>,
30}
31
32pub struct Fonts {
33    fonts: Slab<FontData>,
34    fonts_by_name: HashMap<String, FontId>,
35    cache: Cache<'static>,
36    pub(crate) img: ImageId,
37}
38
39impl Fonts {
40    pub fn new<R: Renderer>(renderer: &mut R) -> anyhow::Result<Fonts> {
41        Ok(Fonts {
42            fonts: Default::default(),
43            fonts_by_name: Default::default(),
44            img: renderer.create_texture(
45                TextureType::Alpha,
46                TEX_WIDTH,
47                TEX_HEIGHT,
48                ImageFlags::empty(),
49                None,
50            )?,
51            cache: Cache::builder()
52                .multithread(true)
53                .dimensions(TEX_WIDTH as u32, TEX_HEIGHT as u32)
54                .build(),
55        })
56    }
57
58    pub fn add_font<N: Into<String>, D: Into<Vec<u8>>>(
59        &mut self,
60        name: N,
61        data: D,
62    ) -> anyhow::Result<FontId> {
63        let font = Font::<'static>::from_bytes(data.into())?;
64        let fd = FontData {
65            font,
66            fallback_fonts: Default::default(),
67        };
68        let id = self.fonts.insert(fd);
69        self.fonts_by_name.insert(name.into(), id);
70        Ok(id)
71    }
72
73    pub fn find<N: Borrow<str>>(&self, name: N) -> Option<FontId> {
74        self.fonts_by_name.get(name.borrow()).map(ToOwned::to_owned)
75    }
76
77    pub fn add_fallback(&mut self, base: FontId, fallback: FontId) {
78        if let Some(fd) = self.fonts.get_mut(base) {
79            fd.fallback_fonts.push(fallback);
80        }
81    }
82
83    fn glyph(&self, id: FontId, c: char) -> Option<(FontId, Glyph<'static>)> {
84        if let Some(fd) = self.fonts.get(id) {
85            let glyph = fd.font.glyph(c);
86            if glyph.id().0 != 0 {
87                Some((id, glyph))
88            } else {
89                for id in &fd.fallback_fonts {
90                    if let Some(fd) = self.fonts.get(*id) {
91                        let glyph = fd.font.glyph(c);
92                        if glyph.id().0 != 0 {
93                            return Some((*id, glyph));
94                        }
95                    }
96                }
97                None
98            }
99        } else {
100            None
101        }
102    }
103
104    fn render_texture<R: Renderer>(&mut self, renderer: &mut R) -> anyhow::Result<()> {
105        let img = self.img.clone();
106        self.cache.cache_queued(move |rect, data| {
107            renderer
108                .update_texture(
109                    img.clone(),
110                    rect.min.x as usize,
111                    rect.min.y as usize,
112                    (rect.max.x - rect.min.x) as usize,
113                    (rect.max.y - rect.min.y) as usize,
114                    data,
115                )
116                .unwrap();
117        })?;
118        Ok(())
119    }
120
121    pub fn text_metrics(&self, id: FontId, size: f32) -> TextMetrics {
122        if let Some(fd) = self.fonts.get(id) {
123            let scale = Scale::uniform(size);
124            let v_metrics = fd.font.v_metrics(scale);
125            TextMetrics {
126                ascender: v_metrics.descent,
127                descender: v_metrics.descent,
128                line_gap: v_metrics.line_gap,
129            }
130        } else {
131            TextMetrics {
132                ascender: 0.0,
133                descender: 0.0,
134                line_gap: 0.0,
135            }
136        }
137    }
138
139    pub fn text_size(&self, text: &str, id: FontId, size: f32, spacing: f32) -> Extent {
140        if let Some(fd) = self.fonts.get(id) {
141            let scale = Scale::uniform(size);
142            let v_metrics = fd.font.v_metrics(scale);
143            let mut extent = Extent::new(
144                0.0,
145                v_metrics.ascent - v_metrics.descent + v_metrics.line_gap,
146            );
147            let mut last_glyph = None;
148            let mut char_count = 0;
149
150            for c in text.chars() {
151                if let Some((_, glyph)) = self.glyph(id, c) {
152                    let glyph = glyph.scaled(scale);
153                    let h_metrics = glyph.h_metrics();
154                    extent.width += h_metrics.advance_width;
155
156                    if let Some(last_glyph) = last_glyph {
157                        extent.width += fd.font.pair_kerning(scale, last_glyph, glyph.id());
158                    }
159
160                    last_glyph = Some(glyph.id());
161                    char_count += 1;
162                }
163            }
164
165            if char_count >= 2 {
166                extent.width += spacing * (char_count - 1) as f32;
167            }
168
169            extent
170        } else {
171            Default::default()
172        }
173    }
174
175    pub fn layout_text<R: Renderer>(
176        &mut self,
177        renderer: &mut R,
178        text: &str,
179        id: FontId,
180        position: crate::Point,
181        size: f32,
182        align: Align,
183        spacing: f32,
184        cache: bool,
185        result: &mut Vec<LayoutChar>,
186    ) -> anyhow::Result<()> {
187        result.clear();
188
189        if let Some(fd) = self.fonts.get(id) {
190            let mut offset = Point { x: 0.0, y: 0.0 };
191            let scale = Scale::uniform(size);
192            let v_metrics = fd.font.v_metrics(scale);
193
194            let sz = if align.contains(Align::CENTER)
195                || align.contains(Align::RIGHT)
196                || align.contains(Align::MIDDLE)
197            {
198                self.text_size(text, id, size, spacing)
199            } else {
200                Extent::new(0.0, 0.0)
201            };
202
203            if align.contains(Align::CENTER) {
204                offset.x -= sz.width / 2.0;
205            } else if align.contains(Align::RIGHT) {
206                offset.x -= sz.width;
207            }
208
209            if align.contains(Align::MIDDLE) {
210                offset.y = v_metrics.descent + sz.height / 2.0;
211            } else if align.contains(Align::BOTTOM) {
212                offset.y = v_metrics.descent;
213            } else if align.contains(Align::TOP) {
214                offset.y = v_metrics.ascent;
215            }
216
217            let mut position = Point {
218                x: position.x + offset.x,
219                y: position.y + offset.y,
220            };
221            let mut last_glyph = None;
222
223            for (idx, c) in text.chars().enumerate() {
224                if let Some((id, glyph)) = self.glyph(id, c) {
225                    let g = glyph.scaled(scale);
226                    let h_metrics = g.h_metrics();
227
228                    let glyph = g.positioned(Point {
229                        x: position.x,
230                        y: position.y,
231                    });
232
233                    let mut next_x = position.x + h_metrics.advance_width;
234                    if let Some(last_glyph) = last_glyph {
235                        next_x += fd.font.pair_kerning(scale, last_glyph, glyph.id());
236                    }
237
238                    if let Some(bb) = glyph.pixel_bounding_box() {
239                        self.cache.queue_glyph(id, glyph.clone());
240
241                        result.push(LayoutChar {
242                            id,
243                            idx,
244                            c,
245                            x: position.x,
246                            next_x,
247                            glyph: glyph.clone(),
248                            uv: Default::default(),
249                            bounds: Bounds {
250                                min: (bb.min.x, bb.min.y).into(),
251                                max: (bb.max.x, bb.max.y).into(),
252                            },
253                        });
254                    }
255
256                    position.x = next_x;
257                    last_glyph = Some(glyph.id());
258                }
259            }
260
261            if cache {
262                self.render_texture(renderer)?;
263
264                for lc in result {
265                    if let Ok(Some((uv, _))) = self.cache.rect_for(lc.id, &lc.glyph) {
266                        lc.uv = Bounds {
267                            min: crate::Point {
268                                x: uv.min.x,
269                                y: uv.min.y,
270                            },
271                            max: crate::Point {
272                                x: uv.max.x,
273                                y: uv.max.y,
274                            },
275                        };
276                    }
277                }
278            }
279        }
280
281        Ok(())
282    }
283}