vg/
text.rs

1use std::cell::RefCell;
2use std::ffi::OsStr;
3use std::fs;
4use std::hash::{
5    Hash,
6    Hasher,
7};
8use std::ops::Range;
9use std::path::Path as FilePath;
10use std::rc::Rc;
11
12use fnv::{
13    FnvBuildHasher,
14    FnvHashMap,
15    FnvHasher,
16};
17use generational_arena::{
18    Arena,
19    Index,
20};
21use lru::LruCache;
22
23use unicode_bidi::BidiInfo;
24use unicode_segmentation::UnicodeSegmentation;
25
26use crate::{
27    Canvas,
28    Color,
29    ErrorKind,
30    FillRule,
31    ImageFlags,
32    ImageId,
33    ImageInfo,
34    Paint,
35    PixelFormat,
36    RenderTarget,
37    Renderer,
38};
39
40mod atlas;
41pub use atlas::Atlas;
42
43mod font;
44use font::Font;
45pub use font::FontMetrics;
46
47use self::font::GlyphRendering;
48
49// This padding is an empty border around the glyph’s pixels but inside the
50// sampled area (texture coordinates) for the quad in render_atlas().
51const GLYPH_PADDING: u32 = 1;
52// We add an additional margin of 1 pixel outside of the sampled area,
53// to deal with the linear interpolation of texels at the edge of that area
54// which mixes in the texels just outside of the edge.
55// This manifests as noise around the glyph, outside of the padding.
56const GLYPH_MARGIN: u32 = 1;
57
58const TEXTURE_SIZE: usize = 512;
59const LRU_CACHE_CAPACITY: usize = 1000;
60
61/// A font handle.
62#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
63pub struct FontId(Index);
64
65/// Text baseline vertical alignment.
66#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
67#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
68pub enum Baseline {
69    /// The text baseline is the top of the em square.
70    Top,
71    /// The text baseline is the middle of the em square.
72    Middle,
73    /// The text baseline is the normal alphabetic baseline. Default value.
74    Alphabetic,
75    /// The text baseline is the bottom of the bounding box.
76    Bottom,
77}
78
79impl Default for Baseline {
80    fn default() -> Self {
81        Self::Alphabetic
82    }
83}
84
85/// Text horizontal alignment.
86#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
87#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
88pub enum Align {
89    /// The text is left-aligned.
90    Left,
91    /// The text is centered.
92    Center,
93    /// The text is right-aligned.
94    Right,
95}
96
97impl Default for Align {
98    fn default() -> Self {
99        Self::Left
100    }
101}
102
103#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
104pub enum RenderMode {
105    Fill,
106    Stroke,
107}
108
109impl Default for RenderMode {
110    fn default() -> Self {
111        Self::Fill
112    }
113}
114
115#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
116pub(crate) struct RenderedGlyphId {
117    glyph_index: u32,
118    font_id: FontId,
119    size: u32,
120    line_width: u32,
121    render_mode: RenderMode,
122    subpixel_location: u8,
123}
124
125impl RenderedGlyphId {
126    fn new(glyph_index: u32, font_id: FontId, paint: &Paint, mode: RenderMode, subpixel_location: u8) -> Self {
127        Self {
128            glyph_index,
129            font_id,
130            size: (paint.font_size * 10.0).trunc() as u32,
131            line_width: (paint.line_width * 10.0).trunc() as u32,
132            render_mode: mode,
133            subpixel_location,
134        }
135    }
136}
137
138#[allow(dead_code)]
139#[derive(Copy, Clone, Debug)]
140pub(crate) struct RenderedGlyph {
141    texture_index: usize,
142    width: u32,
143    height: u32,
144    bearing_y: i32,
145    atlas_x: u32,
146    atlas_y: u32,
147    padding: u32,
148    color_glyph: bool,
149}
150
151#[derive(Copy, Clone, Debug)]
152pub struct ShapedGlyph {
153    pub x: f32,
154    pub y: f32,
155    pub c: char,
156    pub byte_index: usize,
157    pub font_id: FontId,
158    pub codepoint: u32,
159    pub width: f32,
160    pub height: f32,
161    pub advance_x: f32,
162    pub advance_y: f32,
163    pub offset_x: f32,
164    pub offset_y: f32,
165    pub bearing_x: f32,
166    pub bearing_y: f32,
167    pub bitmap_glyph: bool,
168}
169
170#[derive(Clone, Debug, Default)]
171struct ShapedWord {
172    glyphs: Vec<ShapedGlyph>,
173    width: f32,
174}
175
176#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
177struct ShapingId {
178    size: u32,
179    word_hash: u64,
180    font_ids: [Option<FontId>; 8],
181}
182
183impl ShapingId {
184    fn new(paint: &Paint, word: &str, max_width: Option<f32>) -> Self {
185        let mut hasher = FnvHasher::default();
186        word.hash(&mut hasher);
187        if let Some(max_width) = max_width {
188            (max_width.trunc() as i32).hash(&mut hasher);
189        }
190
191        Self {
192            size: (paint.font_size * 10.0).trunc() as u32,
193            word_hash: hasher.finish(),
194            font_ids: paint.font_ids,
195        }
196    }
197}
198
199type ShapedWordsCache<H> = LruCache<ShapingId, Result<ShapedWord, ErrorKind>, H>;
200type ShapingRunCache<H> = LruCache<ShapingId, TextMetrics, H>;
201
202pub(crate) struct FontTexture {
203    atlas: Atlas,
204    pub(crate) image_id: ImageId,
205}
206
207/// TextContext provides functionality for text processing in VG.
208///
209/// You can /// add fonts using the [`Self::add_font_file()`], [`Self::add_font_mem()`] and
210/// [`Self::add_font_dir()`] functions. For each registered font a [`FontId`] is
211/// returned.
212///
213/// The [`FontId`] can be supplied to [`crate::Paint`] along with additional parameters
214/// such as the font size.
215///
216/// The paint is needed when using TextContext's measurement functions such as
217/// [`Self::measure_text()`].
218///
219/// Note that the measurements are done entirely with the supplied sizes in the paint
220/// parameter. If you need measurements that take a [`crate::Canvas`]'s transform or dpi into
221/// account (see [`crate::Canvas::set_size()`]), you need to use the measurement functions
222/// on the canvas.
223#[derive(Default, Clone)]
224pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>);
225
226impl TextContext {
227    /// Registers all .ttf files from a directory with this text context. If successful, the
228    /// font ids of all registered fonts are returned.
229    pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> {
230        self.0.as_ref().borrow_mut().add_font_dir(path)
231    }
232
233    /// Registers the .ttf file from the specified path with this text context. If successful,
234    /// the font id is returned.
235    pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> {
236        self.0.as_ref().borrow_mut().add_font_file(path)
237    }
238
239    /// Registers the in-memory representation of a TrueType font pointed to by the data
240    /// parameter with this text context. If successful, the font id is returned.
241    pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> {
242        self.0.as_ref().borrow_mut().add_font_mem(data)
243    }
244
245    /// Registers the in-memory representation of a TrueType font pointed to by the shared data
246    /// parameter with this text context. If successful, the font id is returned. The face_index
247    /// specifies the face index if the font data is a true type font collection. For plain true
248    /// type fonts, use 0 as index.
249    pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
250        &self,
251        data: T,
252        face_index: u32,
253    ) -> Result<FontId, ErrorKind> {
254        self.0
255            .as_ref()
256            .borrow_mut()
257            .add_shared_font_with_index(data, face_index)
258    }
259
260    /// Returns information on how the provided text will be drawn with the specified paint.
261    pub fn measure_text<S: AsRef<str>>(&self, x: f32, y: f32, text: S, paint: Paint) -> Result<TextMetrics, ErrorKind> {
262        self.0.as_ref().borrow_mut().measure_text(x, y, text, paint)
263    }
264
265    /// Returns the maximum index-th byte of text that will fit inside max_width.
266    ///
267    /// The retuned index will always lie at the start and/or end of a UTF-8 code point sequence or at the start or end of the text
268    pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: Paint) -> Result<usize, ErrorKind> {
269        self.0.as_ref().borrow_mut().break_text(max_width, text, paint)
270    }
271
272    /// Returnes a list of ranges representing each line of text that will fit inside max_width
273    pub fn break_text_vec<S: AsRef<str>>(
274        &self,
275        max_width: f32,
276        text: S,
277        paint: Paint,
278    ) -> Result<Vec<Range<usize>>, ErrorKind> {
279        self.0.as_ref().borrow_mut().break_text_vec(max_width, text, paint)
280    }
281
282    /// Returns font metrics for a particular Paint.
283    pub fn measure_font(&self, paint: Paint) -> Result<FontMetrics, ErrorKind> {
284        self.0.as_ref().borrow_mut().measure_font(paint)
285    }
286}
287
288pub(crate) struct TextContextImpl {
289    fonts: Arena<Font>,
290    shaping_run_cache: ShapingRunCache<FnvBuildHasher>,
291    shaped_words_cache: ShapedWordsCache<FnvBuildHasher>,
292}
293
294impl Default for TextContextImpl {
295    fn default() -> Self {
296        let fnv_run = FnvBuildHasher::default();
297        let fnv_words = FnvBuildHasher::default();
298
299        Self {
300            fonts: Default::default(),
301            shaping_run_cache: LruCache::with_hasher(LRU_CACHE_CAPACITY, fnv_run),
302            shaped_words_cache: LruCache::with_hasher(LRU_CACHE_CAPACITY, fnv_words),
303        }
304    }
305}
306
307impl TextContextImpl {
308    pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> {
309        let path = path.as_ref();
310        let mut fonts = Vec::new();
311
312        if path.is_dir() {
313            for entry in fs::read_dir(path)? {
314                let entry = entry?;
315                let path = entry.path();
316
317                if path.is_dir() {
318                    self.add_font_dir(&path)?;
319                } else if let Some("ttf") = path.extension().and_then(OsStr::to_str) {
320                    fonts.push(self.add_font_file(path)?);
321                } else if let Some("ttc") = path.extension().and_then(OsStr::to_str) {
322                    fonts.extend(self.add_font_file_collection(path)?);
323                }
324            }
325        }
326
327        Ok(fonts)
328    }
329
330    pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> {
331        let data = std::fs::read(path)?;
332
333        self.add_font_mem(&data)
334    }
335
336    pub fn add_font_file_collection<T: AsRef<FilePath>>(
337        &mut self,
338        path: T,
339    ) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> {
340        let data = std::fs::read(path)?;
341
342        let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
343        Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok()))
344    }
345
346    pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
347        self.add_font_mem_with_index(data, 0)
348    }
349
350    pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> {
351        self.clear_caches();
352
353        let font = Font::new_with_data(data.to_owned(), face_index)?;
354        Ok(FontId(self.fonts.insert(font)))
355    }
356
357    pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
358        &mut self,
359        data: T,
360        face_index: u32,
361    ) -> Result<FontId, ErrorKind> {
362        self.clear_caches();
363
364        let font = Font::new_with_data(data, face_index)?;
365        Ok(FontId(self.fonts.insert(font)))
366    }
367
368    pub fn font(&self, id: FontId) -> Option<&Font> {
369        self.fonts.get(id.0)
370    }
371
372    pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> {
373        self.fonts.get_mut(id.0)
374    }
375
376    pub fn find_font<F, T>(&mut self, paint: &Paint, mut callback: F) -> Result<T, ErrorKind>
377    where
378        F: FnMut((FontId, &mut Font)) -> (bool, T),
379    {
380        // Try each font in the paint
381        for maybe_font_id in paint.font_ids.iter() {
382            if let Some(font_id) = maybe_font_id {
383                if let Some(font) = self.fonts.get_mut(font_id.0) {
384                    let (has_missing, result) = callback((*font_id, font));
385
386                    if !has_missing {
387                        return Ok(result);
388                    }
389                }
390            } else {
391                break;
392            }
393        }
394
395        // Try each registered font
396        // An optimisation here would be to skip fonts that were tried by the paint
397        for (id, font) in &mut self.fonts {
398            let (has_missing, result) = callback((FontId(id), font));
399
400            if !has_missing {
401                return Ok(result);
402            }
403        }
404
405        // Just return the first font at this point and let it render .nodef glyphs
406        if let Some((id, font)) = self.fonts.iter_mut().next() {
407            return Ok(callback((FontId(id), font)).1);
408        }
409
410        Err(ErrorKind::NoFontFound)
411    }
412
413    fn clear_caches(&mut self) {
414        self.shaped_words_cache.clear();
415    }
416
417    pub fn measure_text<S: AsRef<str>>(
418        &mut self,
419        x: f32,
420        y: f32,
421        text: S,
422        paint: Paint,
423    ) -> Result<TextMetrics, ErrorKind> {
424        shape(x, y, self, &paint, text.as_ref(), None)
425    }
426
427    pub fn break_text<S: AsRef<str>>(&mut self, max_width: f32, text: S, paint: Paint) -> Result<usize, ErrorKind> {
428        let layout = shape(0.0, 0.0, self, &paint, text.as_ref(), Some(max_width))?;
429
430        Ok(layout.final_byte_index)
431    }
432
433    pub fn break_text_vec<S: AsRef<str>>(
434        &mut self,
435        max_width: f32,
436        text: S,
437        paint: Paint,
438    ) -> Result<Vec<Range<usize>>, ErrorKind> {
439        let text = text.as_ref();
440
441        let mut res = Vec::new();
442        let mut start = 0;
443
444        while start < text.len() {
445            if let Ok(index) = self.break_text(max_width, &text[start..], paint) {
446                if index == 0 {
447                    break;
448                }
449
450                let index = start + index;
451                res.push(start..index);
452                start += &text[start..index].len();
453            } else {
454                break;
455            }
456        }
457
458        Ok(res)
459    }
460
461    pub fn measure_font(&mut self, paint: Paint) -> Result<FontMetrics, ErrorKind> {
462        if let Some(Some(id)) = paint.font_ids.get(0) {
463            if let Some(font) = self.font(*id) {
464                return Ok(font.metrics(paint.font_size));
465            }
466        }
467
468        Err(ErrorKind::NoFontFound)
469    }
470}
471
472/// Result of a shaping run.
473#[derive(Clone, Default, Debug)]
474pub struct TextMetrics {
475    /// Represents x position
476    pub x: f32,
477    /// Represents y position
478    pub y: f32,
479    width: f32,
480    height: f32,
481    /// Represents vector of ShapedGlyph's
482    pub glyphs: Vec<ShapedGlyph>,
483    pub(crate) final_byte_index: usize,
484}
485
486impl TextMetrics {
487    pub(crate) fn scale(&mut self, scale: f32) {
488        self.x *= scale;
489        self.y *= scale;
490        self.width *= scale;
491        self.height *= scale;
492
493        for glyph in &mut self.glyphs {
494            glyph.x *= scale;
495            glyph.y *= scale;
496            glyph.width *= scale;
497            glyph.height *= scale;
498        }
499    }
500
501    /// width of the glyphs as drawn
502    pub fn width(&self) -> f32 {
503        self.width
504    }
505
506    /// height of the glyphs as drawn
507    pub fn height(&self) -> f32 {
508        self.height
509    }
510
511    pub(crate) fn has_bitmap_glyphs(&self) -> bool {
512        self.glyphs.iter().find(|g| g.bitmap_glyph).is_some()
513    }
514}
515
516// Shaper
517
518pub(crate) fn shape(
519    x: f32,
520    y: f32,
521    context: &mut TextContextImpl,
522    paint: &Paint,
523    text: &str,
524    max_width: Option<f32>,
525) -> Result<TextMetrics, ErrorKind> {
526    let id = ShapingId::new(paint, text, max_width);
527
528    if !context.shaping_run_cache.contains(&id) {
529        let metrics = shape_run(context, paint, text, max_width)?;
530        context.shaping_run_cache.put(id, metrics);
531    }
532
533    if let Some(mut metrics) = context.shaping_run_cache.get(&id).cloned() {
534        layout(x, y, context, &mut metrics, paint)?;
535
536        return Ok(metrics);
537    }
538
539    Err(ErrorKind::UnknownError)
540}
541
542fn shape_run(
543    context: &mut TextContextImpl,
544    paint: &Paint,
545    text: &str,
546    max_width: Option<f32>,
547) -> Result<TextMetrics, ErrorKind> {
548    let mut result = TextMetrics {
549        x: 0.0,
550        y: 0.0,
551        width: 0.0,
552        height: 0.0,
553        glyphs: Vec::with_capacity(text.len()),
554        final_byte_index: 0,
555    };
556
557    let bidi_info = BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
558
559    if let Some(paragraph) = bidi_info.paragraphs.get(0) {
560        let line = paragraph.range.clone();
561
562        let (levels, runs) = bidi_info.visual_runs(paragraph, line);
563
564        for run in runs.iter() {
565            let sub_text = &text[run.clone()];
566
567            if sub_text.is_empty() {
568                continue;
569            }
570
571            let hb_direction = if levels[run.start].is_rtl() {
572                rustybuzz::Direction::RightToLeft
573            } else {
574                rustybuzz::Direction::LeftToRight
575            };
576
577            let mut words = Vec::new();
578            let mut word_break_reached = false;
579            let mut byte_index = run.start;
580
581            for word in sub_text.split_word_bounds() {
582                let id = ShapingId::new(paint, word, max_width);
583
584                if !context.shaped_words_cache.contains(&id) {
585                    let word = shape_word(word, hb_direction, context, paint);
586                    context.shaped_words_cache.put(id, word);
587                }
588
589                if let Some(Ok(word)) = context.shaped_words_cache.get(&id) {
590                    let mut word = word.clone();
591
592                    if let Some(max_width) = max_width {
593                        if result.width + word.width >= max_width {
594                            word_break_reached = true;
595                            break;
596                        }
597                    }
598
599                    result.width += word.width;
600
601                    for glyph in &mut word.glyphs {
602                        glyph.byte_index += byte_index;
603                        debug_assert!(text.get(glyph.byte_index..).is_some());
604                    }
605
606                    words.push(word);
607                }
608
609                byte_index += word.len();
610            }
611
612            if levels[run.start].is_rtl() {
613                words.reverse();
614            }
615
616            for word in words {
617                result.glyphs.extend(word.glyphs.clone());
618            }
619
620            result.final_byte_index = byte_index;
621
622            if word_break_reached {
623                break;
624            }
625        }
626    }
627
628    Ok(result)
629}
630
631fn shape_word(
632    word: &str,
633    hb_direction: rustybuzz::Direction,
634    context: &mut TextContextImpl,
635    paint: &Paint,
636) -> Result<ShapedWord, ErrorKind> {
637    // find_font will call the closure with each font matching the provided style
638    // until a font capable of shaping the word is found
639    context.find_font(paint, |(font_id, font)| {
640        // Call harfbuzz
641        let output = {
642            let face = font.face_ref();
643
644            let mut buffer = rustybuzz::UnicodeBuffer::new();
645            buffer.push_str(word);
646            buffer.set_direction(hb_direction);
647
648            rustybuzz::shape(face, &[], buffer)
649        };
650
651        let positions = output.glyph_positions();
652        let infos = output.glyph_infos();
653
654        let mut shaped_word = ShapedWord {
655            glyphs: Vec::with_capacity(positions.len()),
656            width: 0.0,
657        };
658
659        let mut has_missing = false;
660
661        for (position, (info, c)) in positions.iter().zip(infos.iter().zip(word.chars())) {
662            if info.glyph_id == 0 {
663                has_missing = true;
664            }
665
666            let scale = font.scale(paint.font_size);
667
668            let mut g = ShapedGlyph {
669                x: 0.0,
670                y: 0.0,
671                c,
672                byte_index: info.cluster as usize,
673                font_id,
674                codepoint: info.glyph_id,
675                width: 0.0,
676                height: 0.0,
677                advance_x: position.x_advance as f32 * scale,
678                advance_y: position.y_advance as f32 * scale,
679                offset_x: position.x_offset as f32 * scale,
680                offset_y: position.y_offset as f32 * scale,
681                bearing_x: 0.0,
682                bearing_y: 0.0,
683                bitmap_glyph: false,
684            };
685
686            if let Some(glyph) = font.glyph(info.glyph_id as u16) {
687                g.width = glyph.metrics.width * scale;
688                g.height = glyph.metrics.height * scale;
689                g.bearing_x = glyph.metrics.bearing_x * scale;
690                g.bearing_y = glyph.metrics.bearing_y * scale;
691                g.bitmap_glyph = glyph.path.is_none();
692            }
693
694            shaped_word.width += g.advance_x + paint.letter_spacing;
695            shaped_word.glyphs.push(g);
696        }
697
698        (has_missing, shaped_word)
699    })
700}
701
702// Calculates the x,y coordinates for each glyph based on their advances. Calculates total width and height of the shaped text run
703fn layout(
704    x: f32,
705    y: f32,
706    context: &mut TextContextImpl,
707    res: &mut TextMetrics,
708    paint: &Paint,
709) -> Result<(), ErrorKind> {
710    let mut cursor_x = x;
711    let mut cursor_y = y;
712
713    // Horizontal alignment
714    match paint.text_align {
715        Align::Center => cursor_x -= res.width / 2.0,
716        Align::Right => cursor_x -= res.width,
717        _ => (),
718    }
719
720    res.x = cursor_x;
721
722    let mut min_y = cursor_y;
723    let mut max_y = cursor_y;
724
725    let mut ascender: f32 = 0.;
726    let mut descender: f32 = 0.;
727
728    for glyph in &mut res.glyphs {
729        let font = context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
730        let metrics = font.metrics(paint.font_size);
731        ascender = ascender.max(metrics.ascender());
732        descender = descender.min(metrics.descender());
733    }
734
735    let primary_metrics = context.find_font(paint, |(_, font)| (false, font.metrics(paint.font_size)))?;
736    if ascender.abs() < std::f32::EPSILON {
737        ascender = primary_metrics.ascender();
738    }
739    if descender.abs() < std::f32::EPSILON {
740        descender = primary_metrics.descender();
741    }
742
743    // Baseline alignment
744    let alignment_offset_y = match paint.text_baseline {
745        Baseline::Top => ascender,
746        Baseline::Middle => (ascender + descender) / 2.0,
747        Baseline::Alphabetic => 0.0,
748        Baseline::Bottom => descender,
749    };
750
751    for glyph in &mut res.glyphs {
752        glyph.x = cursor_x + glyph.offset_x + glyph.bearing_x;
753        glyph.y = (cursor_y + alignment_offset_y).round() + glyph.offset_y - glyph.bearing_y;
754
755        min_y = min_y.min(glyph.y);
756        max_y = max_y.max(glyph.y + glyph.height);
757
758        cursor_x += glyph.advance_x + paint.letter_spacing;
759        cursor_y += glyph.advance_y;
760    }
761
762    res.y = min_y;
763    res.height = max_y - min_y;
764
765    Ok(())
766}
767
768// Renderer
769
770#[derive(Clone, Debug)]
771pub(crate) struct DrawCmd {
772    pub image_id: ImageId,
773    pub quads: Vec<Quad>,
774}
775
776#[derive(Copy, Clone, Default, Debug)]
777pub(crate) struct Quad {
778    pub x0: f32,
779    pub y0: f32,
780    pub s0: f32,
781    pub t0: f32,
782    pub x1: f32,
783    pub y1: f32,
784    pub s1: f32,
785    pub t1: f32,
786}
787
788pub(crate) struct GlyphDrawCommands {
789    pub(crate) alpha_glyphs: Vec<DrawCmd>,
790    pub(crate) color_glyphs: Vec<DrawCmd>,
791}
792
793#[derive(Default)]
794pub(crate) struct GlyphAtlas {
795    pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>,
796    pub glyph_textures: RefCell<Vec<FontTexture>>,
797}
798
799impl GlyphAtlas {
800    pub(crate) fn render_atlas<T: Renderer>(
801        &self,
802        canvas: &mut Canvas<T>,
803        text_layout: &TextMetrics,
804        paint: &Paint,
805        mode: RenderMode,
806    ) -> Result<GlyphDrawCommands, ErrorKind> {
807        let mut alpha_cmd_map = FnvHashMap::default();
808        let mut color_cmd_map = FnvHashMap::default();
809
810        let line_width_offset = if mode == RenderMode::Stroke {
811            (paint.line_width / 2.0).ceil()
812        } else {
813            0.0
814        };
815
816        let initial_render_target = canvas.current_render_target;
817
818        for glyph in &text_layout.glyphs {
819            let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0;
820
821            let id = RenderedGlyphId::new(glyph.codepoint, glyph.font_id, paint, mode, subpixel_location as u8);
822
823            if !self.rendered_glyphs.borrow().contains_key(&id) {
824                let glyph = self.render_glyph(canvas, paint, mode, glyph)?;
825
826                self.rendered_glyphs.borrow_mut().insert(id, glyph);
827            }
828
829            let rendered_glyphs = self.rendered_glyphs.borrow();
830            let rendered = rendered_glyphs.get(&id).unwrap();
831
832            if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) {
833                let image_id = texture.image_id;
834                let size = texture.atlas.size();
835                let itw = 1.0 / size.0 as f32;
836                let ith = 1.0 / size.1 as f32;
837
838                let cmd_map = if rendered.color_glyph {
839                    &mut color_cmd_map
840                } else {
841                    &mut alpha_cmd_map
842                };
843
844                let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCmd {
845                    image_id,
846                    quads: Vec::new(),
847                });
848
849                let mut q = Quad::default();
850
851                let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset };
852
853                q.x0 = glyph.x.trunc() - line_width_offset - GLYPH_PADDING as f32;
854                q.y0 = (glyph.y + glyph.bearing_y).round()
855                    - rendered.bearing_y as f32
856                    - line_width_offset
857                    - GLYPH_PADDING as f32;
858                q.x1 = q.x0 + rendered.width as f32;
859                q.y1 = q.y0 + rendered.height as f32;
860
861                q.s0 = rendered.atlas_x as f32 * itw;
862                q.t0 = rendered.atlas_y as f32 * ith;
863                q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw;
864                q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith;
865
866                cmd.quads.push(q);
867            }
868        }
869
870        canvas.set_render_target(initial_render_target);
871
872        Ok(GlyphDrawCommands {
873            alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
874            color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
875        })
876    }
877
878    fn render_glyph<T: Renderer>(
879        &self,
880        canvas: &mut Canvas<T>,
881        paint: &Paint,
882        mode: RenderMode,
883        glyph: &ShapedGlyph,
884    ) -> Result<RenderedGlyph, ErrorKind> {
885        let padding = GLYPH_PADDING + GLYPH_MARGIN;
886
887        let text_context = canvas.text_context.clone();
888        let mut text_context = text_context.borrow_mut();
889
890        let (mut maybe_glyph_representation, scale) = {
891            let font = text_context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
892            let scale = font.scale(paint.font_size);
893
894            let maybe_glyph_representation =
895                font.glyph_rendering_representation(glyph.codepoint as u16, paint.font_size as u16);
896            (maybe_glyph_representation, scale)
897        };
898
899        #[cfg(feature = "image-loading")]
900        let color_glyph = matches!(maybe_glyph_representation, Some(GlyphRendering::RenderAsImage(..)));
901        #[cfg(not(feature = "image-loading"))]
902        let color_glyph = false;
903
904        let line_width = if color_glyph || mode != RenderMode::Stroke {
905            0.0
906        } else {
907            paint.line_width
908        };
909
910        let line_width_offset = (line_width / 2.0).ceil();
911
912        let width = glyph.width.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
913        let height = glyph.height.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
914
915        let (dst_index, dst_image_id, (dst_x, dst_y)) =
916            self.find_texture_or_alloc(canvas, width as usize, height as usize)?;
917
918        // render glyph to image
919        canvas.save();
920        canvas.reset();
921
922        let rendered_bearing_y = glyph.bearing_y.round();
923        let x_quant = crate::geometry::quantize(glyph.x.fract(), 0.1);
924        let x = dst_x as f32 - glyph.bearing_x + line_width_offset + padding as f32 + x_quant;
925        let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32;
926
927        let rendered_glyph = RenderedGlyph {
928            width: width - 2 * GLYPH_MARGIN,
929            height: height - 2 * GLYPH_MARGIN,
930            bearing_y: rendered_bearing_y as i32,
931            atlas_x: dst_x as u32 + GLYPH_MARGIN,
932            atlas_y: dst_y as u32 + GLYPH_MARGIN,
933            texture_index: dst_index,
934            padding: padding - GLYPH_MARGIN,
935            color_glyph,
936        };
937
938        match maybe_glyph_representation.as_mut() {
939            Some(GlyphRendering::RenderAsPath(ref mut path)) => {
940                canvas.translate(x, y);
941
942                canvas.set_render_target(RenderTarget::Image(dst_image_id));
943                canvas.clear_rect(
944                    dst_x as u32,
945                    TEXTURE_SIZE as u32 - dst_y as u32 - height as u32,
946                    width as u32,
947                    height as u32,
948                    Color::black(),
949                );
950                let factor = 1.0 / 8.0;
951
952                let mut mask_paint = Paint::color(Color::rgbf(factor, factor, factor));
953                mask_paint.set_fill_rule(FillRule::EvenOdd);
954                mask_paint.set_anti_alias(false);
955
956                if mode == RenderMode::Stroke {
957                    mask_paint.line_width = line_width / scale;
958                }
959
960                canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One);
961
962                // 4x
963                // let points = [
964                //     (-3.0/8.0, 1.0/8.0),
965                //     (1.0/8.0, 3.0/8.0),
966                //     (3.0/8.0, -1.0/8.0),
967                //     (-1.0/8.0, -3.0/8.0),
968                // ];
969
970                // 8x
971                let points = [
972                    (-7.0 / 16.0, -1.0 / 16.0),
973                    (-1.0 / 16.0, -5.0 / 16.0),
974                    (3.0 / 16.0, -7.0 / 16.0),
975                    (5.0 / 16.0, -3.0 / 16.0),
976                    (7.0 / 16.0, 1.0 / 16.0),
977                    (1.0 / 16.0, 5.0 / 16.0),
978                    (-3.0 / 16.0, 7.0 / 16.0),
979                    (-5.0 / 16.0, 3.0 / 16.0),
980                ];
981
982                for point in &points {
983                    canvas.save();
984                    canvas.translate(point.0, point.1);
985
986                    canvas.scale(scale, scale);
987
988                    if mode == RenderMode::Stroke {
989                        canvas.stroke_path(path, mask_paint);
990                    } else {
991                        canvas.fill_path(path, mask_paint);
992                    }
993
994                    canvas.restore();
995                }
996            }
997            #[cfg(feature = "image-loading")]
998            Some(GlyphRendering::RenderAsImage(image_buffer)) => {
999                use std::convert::TryFrom;
1000                let target_x = rendered_glyph.atlas_x as usize;
1001                let target_y = rendered_glyph.atlas_y as usize;
1002                let target_width = rendered_glyph.width as u32;
1003                let target_height = rendered_glyph.height as u32;
1004
1005                let image_buffer =
1006                    image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest);
1007                if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) {
1008                    canvas.update_image(dst_image_id, image, target_x, target_y).unwrap();
1009                }
1010            }
1011            _ => {}
1012        }
1013
1014        canvas.restore();
1015
1016        Ok(rendered_glyph)
1017    }
1018
1019    // Returns (texture index, image id, glyph padding box)
1020    fn find_texture_or_alloc<T: Renderer>(
1021        &self,
1022        canvas: &mut Canvas<T>,
1023        width: usize,
1024        height: usize,
1025    ) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> {
1026        // Find a free location in one of the atlases
1027        let mut texture_search_result = {
1028            let mut glyph_textures = self.glyph_textures.borrow_mut();
1029            let mut textures = glyph_textures.iter_mut().enumerate();
1030            textures.find_map(|(index, texture)| {
1031                texture
1032                    .atlas
1033                    .add_rect(width, height)
1034                    .map(|loc| (index, texture.image_id, loc))
1035            })
1036        };
1037
1038        if texture_search_result.is_none() {
1039            // All atlases are exausted and a new one must be created
1040            let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
1041
1042            let loc = atlas
1043                .add_rect(width, height)
1044                .ok_or(ErrorKind::FontSizeTooLargeForAtlas)?;
1045
1046            // Using PixelFormat::Gray8 works perfectly and takes less VRAM.
1047            // We keep Rgba8 for now because it might be useful for sub-pixel
1048            // anti-aliasing (ClearType®), and the atlas debug display is much
1049            // clearer with different colors. Also, Rgba8 is required for color
1050            // fonts (typically used for emojis).
1051            let info = ImageInfo::new(ImageFlags::empty(), atlas.size().0, atlas.size().1, PixelFormat::Rgba8);
1052            let image_id = canvas.images.alloc(&mut canvas.renderer, info)?;
1053
1054            #[cfg(feature = "debug_inspector")]
1055            if cfg!(debug_assertions) {
1056                // Fill the texture with red pixels only in debug builds.
1057                if let Ok(size) = canvas.image_size(image_id) {
1058                    // With image-loading we then subsequently support color fonts, where
1059                    // the color glyphs are uploaded directly. Since that's immediately and
1060                    // the clear_rect() is run much later, it would overwrite any uploaded
1061                    // glyphs. So then when for the debug-inspector, use an image to clear.
1062                    #[cfg(feature = "image-loading")]
1063                    {
1064                        use rgb::FromSlice;
1065                        let clear_image = image::RgbaImage::from_pixel(
1066                            size.0 as u32,
1067                            size.1 as u32,
1068                            image::Rgba::<u8>([255, 0, 0, 0]),
1069                        );
1070                        canvas
1071                            .update_image(
1072                                image_id,
1073                                crate::image::ImageSource::from(imgref::Img::new(
1074                                    clear_image.as_ref().as_rgba(),
1075                                    clear_image.width() as usize,
1076                                    clear_image.height() as usize,
1077                                )),
1078                                0,
1079                                0,
1080                            )
1081                            .unwrap();
1082                    }
1083                    #[cfg(not(feature = "image-loading"))]
1084                    {
1085                        canvas.save();
1086                        canvas.reset();
1087                        canvas.set_render_target(RenderTarget::Image(image_id));
1088                        canvas.clear_rect(
1089                            0,
1090                            0,
1091                            size.0 as u32,
1092                            size.1 as u32,
1093                            Color::rgb(255, 0, 0), // Shown as white if using Gray8.,
1094                        );
1095                        canvas.restore();
1096                    }
1097                }
1098            }
1099
1100            self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id });
1101
1102            let index = self.glyph_textures.borrow().len() - 1;
1103            texture_search_result = Some((index, image_id, loc));
1104        }
1105
1106        texture_search_result.ok_or(ErrorKind::UnknownError)
1107    }
1108
1109    pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) {
1110        let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut())
1111            .into_iter()
1112            .map(|font_texture| font_texture.image_id);
1113        image_ids.for_each(|id| canvas.delete_image(id));
1114
1115        self.rendered_glyphs.borrow_mut().clear();
1116    }
1117}
1118
1119pub(crate) fn render_direct<T: Renderer>(
1120    canvas: &mut Canvas<T>,
1121    text_layout: &TextMetrics,
1122    paint: &Paint,
1123    mode: RenderMode,
1124    invscale: f32,
1125) -> Result<(), ErrorKind> {
1126    let mut paint = *paint;
1127    paint.set_fill_rule(FillRule::EvenOdd);
1128
1129    let text_context = canvas.text_context.clone();
1130    let mut text_context = text_context.borrow_mut();
1131
1132    let mut scaled = false;
1133
1134    for glyph in &text_layout.glyphs {
1135        let (glyph_rendering, scale) = {
1136            let font = text_context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
1137
1138            let scale = font.scale(paint.font_size);
1139
1140            let glyph_rendering = if let Some(glyph_rendering) =
1141                font.glyph_rendering_representation(glyph.codepoint as u16, paint.font_size as u16)
1142            {
1143                glyph_rendering
1144            } else {
1145                continue;
1146            };
1147
1148            (glyph_rendering, scale)
1149        };
1150
1151        canvas.save();
1152
1153        if mode == RenderMode::Stroke && !scaled {
1154            paint.line_width /= scale;
1155            scaled = true;
1156        }
1157
1158        canvas.translate(
1159            (glyph.x - glyph.bearing_x) * invscale,
1160            (glyph.y + glyph.bearing_y) * invscale,
1161        );
1162        canvas.scale(scale * invscale, -scale * invscale);
1163
1164        match glyph_rendering {
1165            GlyphRendering::RenderAsPath(path) => {
1166                if mode == RenderMode::Stroke {
1167                    canvas.stroke_path(path, paint);
1168                } else {
1169                    canvas.fill_path(path, paint);
1170                }
1171            }
1172            #[cfg(feature = "image-loading")]
1173            GlyphRendering::RenderAsImage(_) => unreachable!(),
1174        }
1175
1176        canvas.restore();
1177    }
1178
1179    Ok(())
1180}