femtovg/
text.rs

1use std::{borrow::Borrow, cell::RefCell, ffi::OsStr, fs, hash::Hash, path::Path as FilePath, rc::Rc};
2
3use fnv::FnvHashMap;
4#[cfg(feature = "textlayout")]
5use rustybuzz::ttf_parser;
6use slotmap::{DefaultKey, SlotMap};
7
8use crate::{
9    paint::{PaintFlavor, StrokeSettings},
10    Canvas, Color, ErrorKind, FillRule, ImageFlags, ImageId, ImageInfo, Paint, PixelFormat, PositionedGlyph,
11    RenderTarget, Renderer,
12};
13
14mod atlas;
15pub use atlas::Atlas;
16
17mod font;
18pub use font::FontMetrics;
19use font::{Font, GlyphRendering};
20
21#[cfg(feature = "textlayout")]
22mod textlayout;
23#[cfg(feature = "textlayout")]
24pub use textlayout::*;
25
26// This padding is an empty border around the glyph’s pixels but inside the
27// sampled area (texture coordinates) for the quad in render_atlas().
28const GLYPH_PADDING: u32 = 1;
29// We add an additional margin of 1 pixel outside of the sampled area,
30// to deal with the linear interpolation of texels at the edge of that area
31// which mixes in the texels just outside of the edge.
32// This manifests as noise around the glyph, outside of the padding.
33const GLYPH_MARGIN: u32 = 1;
34
35const TEXTURE_SIZE: usize = 512;
36#[cfg(feature = "textlayout")]
37const DEFAULT_LRU_CACHE_CAPACITY: usize = 1000;
38
39/// A font handle.
40#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
41pub struct FontId(DefaultKey);
42
43/// Represents the vertical alignment of a text baseline.
44///
45/// The default value is `Alphabetic`.
46#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48pub enum Baseline {
49    /// The text baseline is the top of the em square.
50    Top,
51    /// The text baseline is the middle of the em square.
52    Middle,
53    /// The text baseline is the normal alphabetic baseline.
54    #[default]
55    Alphabetic,
56    /// The text baseline is the bottom of the bounding box.
57    Bottom,
58}
59
60/// Represents the horizontal alignment of text.
61///
62/// The default value is `Left`.
63#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65pub enum Align {
66    /// The text is left-aligned.
67    #[default]
68    Left,
69    /// The text is centered.
70    Center,
71    /// The text is right-aligned.
72    Right,
73}
74
75/// Represents the rendering mode for a path.
76///
77/// The default value is `Fill`.
78#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
79pub enum RenderMode {
80    /// The path is filled.
81    #[default]
82    Fill,
83    /// The path is stroked.
84    Stroke,
85}
86
87#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
88pub struct RenderedGlyphId {
89    glyph_index: u16,
90    font_id: FontId,
91    size: u32,
92    line_width: u32,
93    render_mode: RenderMode,
94    subpixel_location: u8,
95}
96
97impl RenderedGlyphId {
98    fn new(
99        glyph_index: u16,
100        font_id: FontId,
101        font_size: f32,
102        line_width: f32,
103        mode: RenderMode,
104        subpixel_location: u8,
105    ) -> Self {
106        Self {
107            glyph_index,
108            font_id,
109            size: (font_size * 10.0).trunc() as u32,
110            line_width: (line_width * 10.0).trunc() as u32,
111            render_mode: mode,
112            subpixel_location,
113        }
114    }
115}
116
117#[derive(Copy, Clone, Debug)]
118pub struct RenderedGlyph {
119    texture_index: usize,
120    width: u32,
121    height: u32,
122    bearing_x: i32,
123    bearing_y: i32,
124    atlas_x: u32,
125    atlas_y: u32,
126    color_glyph: bool,
127}
128
129pub struct FontTexture {
130    pub atlas: Atlas,
131    pub(crate) image_id: ImageId,
132}
133
134/// `TextContext` provides functionality for text processing in femtovg.
135///
136/// You can add fonts using the [`Self::add_font_file()`], [`Self::add_font_mem()`] and
137/// [`Self::add_font_dir()`] functions. For each registered font a [`FontId`] is
138/// returned.
139///
140/// The [`FontId`] can be supplied to [`crate::Paint`] along with additional parameters
141/// such as the font size.
142///
143/// The paint is needed when using `TextContext`'s measurement functions such as
144/// [`Self::measure_text()`].
145///
146/// Note that the measurements are done entirely with the supplied sizes in the paint
147/// parameter. If you need measurements that take a [`crate::Canvas`]'s transform or dpi into
148/// account (see [`crate::Canvas::set_size()`]), you need to use the measurement functions
149/// on the canvas.
150#[derive(Clone, Default)]
151pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>);
152
153impl TextContext {
154    /// Registers all .ttf files from a directory with this text context. If successful, the
155    /// font ids of all registered fonts are returned.
156    pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> {
157        self.0.borrow_mut().add_font_dir(path)
158    }
159
160    /// Registers the .ttf file from the specified path with this text context. If successful,
161    /// the font id is returned.
162    pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> {
163        self.0.borrow_mut().add_font_file(path)
164    }
165
166    /// Registers the in-memory representation of a TrueType font pointed to by the data
167    /// parameter with this text context. If successful, the font id is returned.
168    pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> {
169        self.0.borrow_mut().add_font_mem(data)
170    }
171
172    /// Registers the in-memory representation of a TrueType font pointed to by the shared data
173    /// parameter with this text context. If successful, the font id is returned. The `face_index`
174    /// specifies the face index if the font data is a true type font collection. For plain true
175    /// type fonts, use 0 as index.
176    pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
177        &self,
178        data: T,
179        face_index: u32,
180    ) -> Result<FontId, ErrorKind> {
181        self.0.borrow_mut().add_shared_font_with_index(data, face_index)
182    }
183
184    /// Returns font metrics for a particular Paint.
185    pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> {
186        self.0
187            .borrow_mut()
188            .measure_font(paint.text.font_size, &paint.text.font_ids)
189    }
190}
191
192pub struct TextContextImpl {
193    fonts: SlotMap<DefaultKey, Font>,
194    #[cfg(feature = "textlayout")]
195    shaping_run_cache: textlayout::ShapingRunCache<fnv::FnvBuildHasher>,
196    #[cfg(feature = "textlayout")]
197    shaped_words_cache: textlayout::ShapedWordsCache<fnv::FnvBuildHasher>,
198}
199
200impl Default for TextContextImpl {
201    fn default() -> Self {
202        #[cfg(feature = "textlayout")]
203        let fnv_run = fnv::FnvBuildHasher::default();
204        #[cfg(feature = "textlayout")]
205        let fnv_words = fnv::FnvBuildHasher::default();
206
207        Self {
208            fonts: SlotMap::default(),
209            #[cfg(feature = "textlayout")]
210            shaping_run_cache: lru::LruCache::with_hasher(
211                std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
212                fnv_run,
213            ),
214            #[cfg(feature = "textlayout")]
215            shaped_words_cache: lru::LruCache::with_hasher(
216                std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
217                fnv_words,
218            ),
219        }
220    }
221}
222
223impl TextContextImpl {
224    pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> {
225        let path = path.as_ref();
226        let mut fonts = Vec::new();
227
228        if path.is_dir() {
229            for entry in fs::read_dir(path)? {
230                let entry = entry?;
231                let path = entry.path();
232
233                if path.is_dir() {
234                    self.add_font_dir(&path)?;
235                } else if Some("ttf") == path.extension().and_then(OsStr::to_str) {
236                    fonts.push(self.add_font_file(path)?);
237                } else if Some("ttc") == path.extension().and_then(OsStr::to_str) {
238                    fonts.extend(self.add_font_file_collection(path)?);
239                }
240            }
241        }
242
243        Ok(fonts)
244    }
245
246    pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> {
247        let data = std::fs::read(path)?;
248
249        self.add_font_mem(&data)
250    }
251
252    pub fn add_font_file_collection<T: AsRef<FilePath>>(
253        &mut self,
254        path: T,
255    ) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> {
256        let data = std::fs::read(path)?;
257
258        let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
259        Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok()))
260    }
261
262    pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
263        self.add_font_mem_with_index(data, 0)
264    }
265
266    pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> {
267        self.clear_caches();
268
269        let data_copy = data.to_owned();
270        let font = Font::new_with_data(data_copy, face_index)?;
271        Ok(FontId(self.fonts.insert(font)))
272    }
273
274    pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
275        &mut self,
276        data: T,
277        face_index: u32,
278    ) -> Result<FontId, ErrorKind> {
279        self.clear_caches();
280
281        let font = Font::new_with_data(data, face_index)?;
282        Ok(FontId(self.fonts.insert(font)))
283    }
284
285    pub fn font(&self, id: FontId) -> Option<&Font> {
286        self.fonts.get(id.0)
287    }
288
289    pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> {
290        self.fonts.get_mut(id.0)
291    }
292
293    #[cfg(feature = "textlayout")]
294    pub fn find_font<F, T>(&mut self, font_ids: &[Option<FontId>; 8], mut callback: F) -> Result<T, ErrorKind>
295    where
296        F: FnMut((FontId, &mut Font)) -> (bool, T),
297    {
298        // Try each font in the paint
299        for maybe_font_id in font_ids {
300            if let &Some(font_id) = maybe_font_id {
301                if let Some(font) = self.fonts.get_mut(font_id.0) {
302                    let (has_missing, result) = callback((font_id, font));
303
304                    if !has_missing {
305                        return Ok(result);
306                    }
307                }
308            } else {
309                break;
310            }
311        }
312
313        // Try each registered font
314        // An optimisation here would be to skip fonts that were tried by the paint
315        for (id, font) in &mut self.fonts {
316            let (has_missing, result) = callback((FontId(id), font));
317
318            if !has_missing {
319                return Ok(result);
320            }
321        }
322
323        // Just return the first font at this point and let it render .nodef glyphs
324        if let Some((id, font)) = self.fonts.iter_mut().next() {
325            return Ok(callback((FontId(id), font)).1);
326        }
327
328        Err(ErrorKind::NoFontFound)
329    }
330
331    fn clear_caches(&mut self) {
332        #[cfg(feature = "textlayout")]
333        self.shaped_words_cache.clear();
334    }
335
336    pub fn measure_font(&self, font_size: f32, font_ids: &[Option<FontId>; 8]) -> Result<FontMetrics, ErrorKind> {
337        if let Some(Some(id)) = font_ids.first() {
338            if let Some(font) = self.font(*id) {
339                return Ok(font.metrics(font_size));
340            }
341        }
342
343        Err(ErrorKind::NoFontFound)
344    }
345}
346
347// Renderer
348
349/// Represents a command to draw an image with a set of quads.
350#[derive(Clone, Debug)]
351pub struct DrawCommand {
352    /// The ID of the image to draw.
353    pub image_id: ImageId,
354    /// The quads defining the positions and texture coordinates for drawing the image.
355    pub quads: Vec<Quad>,
356}
357
358/// Represents a quad with position and texture coordinates.
359#[derive(Copy, Clone, Default, Debug)]
360pub struct Quad {
361    /// X-coordinate of the top-left corner of the quad.
362    pub x0: f32,
363    /// Y-coordinate of the top-left corner of the quad.
364    pub y0: f32,
365    /// U-coordinate (horizontal texture coordinate) of the top-left corner of the quad.
366    pub s0: f32,
367    /// V-coordinate (vertical texture coordinate) of the top-left corner of the quad.
368    pub t0: f32,
369    /// X-coordinate of the bottom-right corner of the quad.
370    pub x1: f32,
371    /// Y-coordinate of the bottom-right corner of the quad.
372    pub y1: f32,
373    /// U-coordinate (horizontal texture coordinate) of the bottom-right corner of the quad.
374    pub s1: f32,
375    /// V-coordinate (vertical texture coordinate) of the bottom-right corner of the quad.
376    pub t1: f32,
377}
378
379/// Represents the drawing commands for glyphs, separated into alpha and color glyphs.
380#[derive(Default)]
381pub struct GlyphDrawCommands {
382    /// Drawing commands for alpha (opacity) glyphs.
383    pub alpha_glyphs: Vec<DrawCommand>,
384    /// Drawing commands for color glyphs.
385    pub color_glyphs: Vec<DrawCommand>,
386}
387
388#[derive(Default)]
389pub struct GlyphAtlas {
390    pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>,
391    pub glyph_textures: RefCell<Vec<FontTexture>>,
392}
393
394impl GlyphAtlas {
395    pub(crate) fn render_atlas<T: Renderer>(
396        &self,
397        canvas: &mut Canvas<T>,
398        font_id: FontId,
399        font: &Font,
400        font_face: &ttf_parser::Face<'_>,
401        glyphs: impl Iterator<Item = PositionedGlyph>,
402        font_size: f32,
403        line_width: f32,
404        mode: RenderMode,
405    ) -> Result<GlyphDrawCommands, ErrorKind> {
406        let mut alpha_cmd_map = FnvHashMap::default();
407        let mut color_cmd_map = FnvHashMap::default();
408
409        let line_width_offset = if mode == RenderMode::Stroke {
410            (line_width / 2.0).ceil()
411        } else {
412            0.0
413        };
414
415        let initial_render_target = canvas.current_render_target;
416
417        for glyph in glyphs {
418            let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0;
419
420            let id = RenderedGlyphId::new(
421                glyph.glyph_id,
422                font_id,
423                font_size,
424                line_width,
425                mode,
426                subpixel_location as u8,
427            );
428
429            let mut rendered_glyphs = self.rendered_glyphs.borrow_mut();
430            let glyph_cache_entry = rendered_glyphs.entry(id);
431            let glyph_cache_entry = match glyph_cache_entry {
432                std::collections::hash_map::Entry::Occupied(occupied_entry) => occupied_entry,
433                std::collections::hash_map::Entry::Vacant(_) => {
434                    if let Some(glyph) =
435                        self.render_glyph(canvas, font_size, line_width, mode, font, &font_face, glyph.glyph_id)?
436                    {
437                        glyph_cache_entry.insert_entry(glyph)
438                    } else {
439                        continue;
440                    }
441                }
442            };
443
444            let rendered = glyph_cache_entry.get();
445
446            if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) {
447                let image_id = texture.image_id;
448                let size = texture.atlas.size();
449                let itw = 1.0 / size.0 as f32;
450                let ith = 1.0 / size.1 as f32;
451
452                let cmd_map = if rendered.color_glyph {
453                    &mut color_cmd_map
454                } else {
455                    &mut alpha_cmd_map
456                };
457
458                let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand {
459                    image_id,
460                    quads: Vec::new(),
461                });
462
463                let mut q = Quad::default();
464
465                let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset };
466
467                q.x0 = glyph.x.trunc() + rendered.bearing_x as f32 - line_width_offset - GLYPH_PADDING as f32;
468                q.y0 = glyph.y.round() - rendered.bearing_y as f32 - line_width_offset - GLYPH_PADDING as f32;
469                q.x1 = q.x0 + rendered.width as f32;
470                q.y1 = q.y0 + rendered.height as f32;
471
472                q.s0 = rendered.atlas_x as f32 * itw;
473                q.t0 = rendered.atlas_y as f32 * ith;
474                q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw;
475                q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith;
476
477                cmd.quads.push(q);
478            }
479        }
480
481        canvas.set_render_target(initial_render_target);
482
483        Ok(GlyphDrawCommands {
484            alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
485            color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
486        })
487    }
488
489    // Renders the glyph into the atlas and returns the RenderedGlyph struct for it.
490    // Returns Ok(None) if there exists no path or image for the glyph in the font (missing glyph).
491    fn render_glyph<T: Renderer>(
492        &self,
493        canvas: &mut Canvas<T>,
494        font_size: f32,
495        line_width: f32,
496        mode: RenderMode,
497        font: &Font,
498        font_face: &ttf_parser::Face<'_>,
499        glyph_id: u16,
500    ) -> Result<Option<RenderedGlyph>, ErrorKind> {
501        let padding = GLYPH_PADDING + GLYPH_MARGIN;
502
503        let (mut glyph_representation, glyph_metrics, scale) = {
504            let scale = font.scale(font_size);
505            let maybe_glyph_metrics = font.glyph(&font_face, glyph_id).map(|g| g.metrics.clone());
506
507            if let (Some(glyph_representation), Some(glyph_metrics)) = (
508                font.glyph_rendering_representation(&font_face, glyph_id, font_size as u16),
509                maybe_glyph_metrics,
510            ) {
511                (glyph_representation, glyph_metrics, scale)
512            } else {
513                return Ok(None);
514            }
515        };
516
517        #[cfg(feature = "image-loading")]
518        let color_glyph = matches!(glyph_representation, GlyphRendering::RenderAsImage(..));
519        #[cfg(not(feature = "image-loading"))]
520        let color_glyph = false;
521
522        let line_width = if color_glyph || mode != RenderMode::Stroke {
523            0.0
524        } else {
525            line_width
526        };
527
528        let line_width_offset = (line_width / 2.0).ceil();
529
530        let width = (glyph_metrics.width * scale).ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
531        let height = (glyph_metrics.height * scale).ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
532
533        let (dst_index, dst_image_id, (dst_x, dst_y)) =
534            self.find_texture_or_alloc(canvas, width as usize, height as usize)?;
535
536        // render glyph to image
537        canvas.save();
538        canvas.reset();
539
540        let rendered_bearing_x = (glyph_metrics.bearing_x * scale).round();
541        let rendered_bearing_y = (glyph_metrics.bearing_y * scale).round();
542        let x = dst_x as f32 - rendered_bearing_x + line_width_offset + padding as f32;
543        let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32;
544
545        let rendered_glyph = RenderedGlyph {
546            width: width - 2 * GLYPH_MARGIN,
547            height: height - 2 * GLYPH_MARGIN,
548            bearing_x: rendered_bearing_x as i32,
549            bearing_y: rendered_bearing_y as i32,
550            atlas_x: dst_x as u32 + GLYPH_MARGIN,
551            atlas_y: dst_y as u32 + GLYPH_MARGIN,
552            texture_index: dst_index,
553            color_glyph,
554        };
555
556        match glyph_representation {
557            GlyphRendering::RenderAsPath(ref mut path) => {
558                canvas.translate(x, y);
559
560                canvas.set_render_target(RenderTarget::Image(dst_image_id));
561                canvas.clear_rect(
562                    dst_x as u32,
563                    TEXTURE_SIZE as u32 - dst_y as u32 - height,
564                    width,
565                    height,
566                    Color::black(),
567                );
568                let factor = 1.0 / 8.0;
569
570                let mask_color = Color::rgbf(factor, factor, factor);
571
572                let mut line_width = line_width;
573
574                if mode == RenderMode::Stroke {
575                    line_width /= scale;
576                }
577
578                canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One);
579
580                // 4x
581                // let points = [
582                //     (-3.0/8.0, 1.0/8.0),
583                //     (1.0/8.0, 3.0/8.0),
584                //     (3.0/8.0, -1.0/8.0),
585                //     (-1.0/8.0, -3.0/8.0),
586                // ];
587
588                // 8x
589                let points = [
590                    (-7.0 / 16.0, -1.0 / 16.0),
591                    (-1.0 / 16.0, -5.0 / 16.0),
592                    (3.0 / 16.0, -7.0 / 16.0),
593                    (5.0 / 16.0, -3.0 / 16.0),
594                    (7.0 / 16.0, 1.0 / 16.0),
595                    (1.0 / 16.0, 5.0 / 16.0),
596                    (-3.0 / 16.0, 7.0 / 16.0),
597                    (-5.0 / 16.0, 3.0 / 16.0),
598                ];
599
600                for point in &points {
601                    canvas.save();
602                    canvas.translate(point.0, point.1);
603
604                    canvas.scale(scale, scale);
605
606                    if mode == RenderMode::Stroke {
607                        canvas.stroke_path_internal(
608                            path,
609                            &PaintFlavor::Color(mask_color),
610                            false,
611                            &StrokeSettings {
612                                line_width,
613                                ..Default::default()
614                            },
615                        );
616                    } else {
617                        canvas.fill_path_internal(path, &PaintFlavor::Color(mask_color), false, FillRule::NonZero);
618                    }
619
620                    canvas.restore();
621                }
622            }
623            #[cfg(feature = "image-loading")]
624            GlyphRendering::RenderAsImage(image_buffer) => {
625                let target_x = rendered_glyph.atlas_x as usize;
626                let target_y = rendered_glyph.atlas_y as usize;
627                let target_width = rendered_glyph.width;
628                let target_height = rendered_glyph.height;
629
630                let image_buffer =
631                    image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest);
632                if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) {
633                    canvas.update_image(dst_image_id, image, target_x, target_y).unwrap();
634                }
635            }
636        }
637
638        canvas.restore();
639
640        Ok(Some(rendered_glyph))
641    }
642
643    // Returns (texture index, image id, glyph padding box)
644    fn find_texture_or_alloc<T: Renderer>(
645        &self,
646        canvas: &mut Canvas<T>,
647        width: usize,
648        height: usize,
649    ) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> {
650        // Find a free location in one of the atlases
651        let mut texture_search_result = {
652            let mut glyph_textures = self.glyph_textures.borrow_mut();
653            let mut textures = glyph_textures.iter_mut().enumerate();
654            textures.find_map(|(index, texture)| {
655                texture
656                    .atlas
657                    .add_rect(width, height)
658                    .map(|loc| (index, texture.image_id, loc))
659            })
660        };
661
662        if texture_search_result.is_none() {
663            // All atlases are exausted and a new one must be created
664            let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
665
666            let loc = atlas
667                .add_rect(width, height)
668                .ok_or(ErrorKind::FontSizeTooLargeForAtlas)?;
669
670            // Using PixelFormat::Gray8 works perfectly and takes less VRAM.
671            // We keep Rgba8 for now because it might be useful for sub-pixel
672            // anti-aliasing (ClearType®), and the atlas debug display is much
673            // clearer with different colors. Also, Rgba8 is required for color
674            // fonts (typically used for emojis).
675            let info = ImageInfo::new(ImageFlags::NEAREST, atlas.size().0, atlas.size().1, PixelFormat::Rgba8);
676            let image_id = canvas.images.alloc(&mut canvas.renderer, info)?;
677
678            #[cfg(feature = "debug_inspector")]
679            if cfg!(debug_assertions) {
680                // Fill the texture with red pixels only in debug builds.
681                if let Ok(size) = canvas.image_size(image_id) {
682                    // With image-loading we then subsequently support color fonts, where
683                    // the color glyphs are uploaded directly. Since that's immediately and
684                    // the clear_rect() is run much later, it would overwrite any uploaded
685                    // glyphs. So then when for the debug-inspector, use an image to clear.
686                    #[cfg(feature = "image-loading")]
687                    {
688                        use rgb::FromSlice;
689                        let clear_image = image::RgbaImage::from_pixel(
690                            size.0 as u32,
691                            size.1 as u32,
692                            image::Rgba::<u8>([255, 0, 0, 0]),
693                        );
694                        canvas
695                            .update_image(
696                                image_id,
697                                crate::image::ImageSource::from(imgref::Img::new(
698                                    clear_image.as_rgba(),
699                                    clear_image.width() as usize,
700                                    clear_image.height() as usize,
701                                )),
702                                0,
703                                0,
704                            )
705                            .unwrap();
706                    }
707                    #[cfg(not(feature = "image-loading"))]
708                    {
709                        canvas.save();
710                        canvas.reset();
711                        canvas.set_render_target(RenderTarget::Image(image_id));
712                        canvas.clear_rect(
713                            0,
714                            0,
715                            size.0 as u32,
716                            size.1 as u32,
717                            Color::rgb(255, 0, 0), // Shown as white if using Gray8.,
718                        );
719                        canvas.restore();
720                    }
721                }
722            }
723
724            self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id });
725
726            let index = self.glyph_textures.borrow().len() - 1;
727            texture_search_result = Some((index, image_id, loc));
728        }
729
730        texture_search_result.ok_or(ErrorKind::UnknownError)
731    }
732
733    pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) {
734        let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut())
735            .into_iter()
736            .map(|font_texture| font_texture.image_id);
737        image_ids.for_each(|id| canvas.delete_image(id));
738
739        self.rendered_glyphs.borrow_mut().clear();
740    }
741}
742
743pub fn render_direct<T: Renderer>(
744    canvas: &mut Canvas<T>,
745    font: &Font,
746    glyphs: impl Iterator<Item = PositionedGlyph>,
747    paint_flavor: &PaintFlavor,
748    anti_alias: bool,
749    stroke: &StrokeSettings,
750    font_size: f32,
751    mode: RenderMode,
752) -> Result<(), ErrorKind> {
753    let face = font.face_ref();
754
755    for glyph in glyphs {
756        let (glyph_rendering, scale) = {
757            let scale = font.scale(font_size);
758
759            let Some(glyph_rendering) = font.glyph_rendering_representation(&face, glyph.glyph_id, font_size as u16)
760            else {
761                continue;
762            };
763
764            (glyph_rendering, scale)
765        };
766
767        canvas.save();
768
769        let line_width = match mode {
770            RenderMode::Fill => stroke.line_width,
771            RenderMode::Stroke => stroke.line_width / scale,
772        };
773
774        canvas.translate(glyph.x, glyph.y);
775        canvas.scale(scale, -scale);
776
777        match glyph_rendering {
778            GlyphRendering::RenderAsPath(path) => {
779                if mode == RenderMode::Stroke {
780                    canvas.stroke_path_internal(
781                        path.borrow(),
782                        paint_flavor,
783                        anti_alias,
784                        &StrokeSettings {
785                            line_width,
786                            ..stroke.clone()
787                        },
788                    );
789                } else {
790                    canvas.fill_path_internal(path.borrow(), paint_flavor, anti_alias, FillRule::NonZero);
791                }
792            }
793            #[cfg(feature = "image-loading")]
794            GlyphRendering::RenderAsImage(_) => unreachable!(),
795        }
796
797        canvas.restore();
798    }
799
800    Ok(())
801}