makepad_draw/text/
shaper.rs

1use {
2    super::{
3        font::{Font, GlyphId},
4        slice::SliceExt,
5        substr::Substr,
6    },
7    makepad_rustybuzz as rustybuzz,
8    rustybuzz::UnicodeBuffer,
9    std::{
10        collections::{HashMap, VecDeque},
11        hash::Hash,
12        mem,
13        rc::Rc,
14    },
15    unicode_segmentation::UnicodeSegmentation,
16};
17
18#[derive(Debug)]
19pub struct Shaper {
20    reusable_glyphs: Vec<Vec<ShapedGlyph>>,
21    reusable_unicode_buffer: UnicodeBuffer,
22    cache_size: usize,
23    cached_params: VecDeque<ShapeParams>,
24    cached_results: HashMap<ShapeParams, Rc<ShapedText>>,
25}
26
27impl Shaper {
28    pub fn new(settings: Settings) -> Self {
29        Self {
30            reusable_glyphs: Vec::new(),
31            reusable_unicode_buffer: UnicodeBuffer::new(),
32            cache_size: settings.cache_size,
33            cached_params: VecDeque::with_capacity(settings.cache_size),
34            cached_results: HashMap::with_capacity(settings.cache_size),
35        }
36    }
37
38    pub fn get_or_shape(&mut self, params: ShapeParams) -> Rc<ShapedText> {
39        if let Some(result) = self.cached_results.get(&params) {
40            return result.clone();
41        }
42        if self.cached_params.len() == self.cache_size {
43            let params = self.cached_params.pop_front().unwrap();
44            self.cached_results.remove(&params);
45        }
46        let result = Rc::new(self.shape(params.clone()));
47        self.cached_params.push_back(params.clone());
48        self.cached_results.insert(params, result.clone());
49        result
50    }
51
52    fn shape(&mut self, params: ShapeParams) -> ShapedText {
53        let mut glyphs = Vec::new();
54        if params.fonts.is_empty() {
55            println!("WARNING: encountered empty font family");
56        } else {
57            self.shape_recursive(
58                &params.text,
59                &params.fonts,
60                0,
61                params.text.len(),
62                &mut glyphs,
63            );
64        }
65        ShapedText {
66            text: params.text,
67            width_in_ems: glyphs.iter().map(|glyph| glyph.advance_in_ems).sum(),
68            glyphs,
69        }
70    }
71
72    fn shape_recursive(
73        &mut self,
74        text: &str,
75        fonts: &[Rc<Font>],
76        start: usize,
77        end: usize,
78        out_glyphs: &mut Vec<ShapedGlyph>,
79    ) {
80        let (font, fonts) = fonts.split_first().unwrap();
81        let mut glyphs = self.reusable_glyphs.pop().unwrap_or(Vec::new());
82        self.shape_step(text, font, start, end, &mut glyphs);
83        let mut glyph_groups = glyphs
84            .group_by(|glyph_0, glyph_1| glyph_0.cluster == glyph_1.cluster)
85            .peekable();
86        while let Some(glyph_group) = glyph_groups.next() {
87            if glyph_group.iter().any(|glyph| glyph.id == 0) && !fonts.is_empty() {
88                let missing_start = glyph_group[0].cluster;
89                while glyph_groups.peek().map_or(false, |glyph_group| {
90                    glyph_group.iter().any(|glyph| glyph.id == 0)
91                }) {
92                    glyph_groups.next();
93                }
94                let missing_end = glyph_groups
95                    .peek()
96                    .map_or(end, |next_glyph_group| next_glyph_group[0].cluster);
97                self.shape_recursive(text, fonts, missing_start, missing_end, out_glyphs);
98            } else {
99                out_glyphs.extend(glyph_group.iter().cloned());
100            }
101        }
102        drop(glyph_groups);
103        glyphs.clear();
104        self.reusable_glyphs.push(glyphs);
105    }
106
107    fn shape_step(
108        &mut self,
109        text: &str,
110        font: &Rc<Font>,
111        start: usize,
112        end: usize,
113        out_glyphs: &mut Vec<ShapedGlyph>,
114    ) {
115        let mut unicode_buffer = mem::take(&mut self.reusable_unicode_buffer);
116        for (index, grapheme) in text[start..end].grapheme_indices(true) {
117            let cluster = start + index;
118            for char in grapheme.chars() {
119                unicode_buffer.add(char, cluster as u32);
120            }
121        }
122        let mut glyph_buffer = rustybuzz::shape(font.rustybuzz_face(), &[], unicode_buffer);
123        let out_glyph_start = out_glyphs.len();
124        out_glyphs.extend(
125            glyph_buffer
126                .glyph_infos()
127                .iter()
128                .zip(glyph_buffer.glyph_positions())
129                .map(|(glyph_info, glyph_position)| ShapedGlyph {
130                    font: font.clone(),
131                    id: glyph_info.glyph_id as u16,
132                    cluster: glyph_info.cluster as usize,
133                    advance_in_ems: glyph_position.x_advance as f32 / font.units_per_em(),
134                    offset_in_ems: glyph_position.x_offset as f32 / font.units_per_em(),
135                }),
136        );
137
138        // HACK: We don't support right-to-left scripts yet. When such scripts are used, cluster
139        // values may be monotonically decreasing instead of increasing, as we assume. This should
140        // be fixed by properly handling bidirectional text (by further breaking up spans into
141        // spans with a single direction), but for now we just check if the cluster values are
142        // monotonically increasing. If they are not, we re-shape the text by replacing each
143        // grapheme with a single character. This is not ideal, but it works for now.
144        if out_glyphs[out_glyph_start..]
145            .windows(2)
146            .any(|glyphs| {
147                glyphs[0].cluster > glyphs[1].cluster
148            })
149        {
150            out_glyphs.truncate(out_glyph_start);
151            let mut unicode_buffer = glyph_buffer.clear();
152            for (index, _) in text[start..end].grapheme_indices(true) {
153                let cluster = start + index;
154                unicode_buffer.add('□', cluster as u32);
155            }
156            glyph_buffer = rustybuzz::shape(font.rustybuzz_face(), &[], unicode_buffer);
157            out_glyphs.extend(
158                glyph_buffer
159                    .glyph_infos()
160                    .iter()
161                    .zip(glyph_buffer.glyph_positions())
162                    .map(|(glyph_info, glyph_position)| ShapedGlyph {
163                        font: font.clone(),
164                        id: glyph_info.glyph_id as u16,
165                        cluster: glyph_info.cluster as usize,
166                        advance_in_ems: glyph_position.x_advance as f32 / font.units_per_em(),
167                        offset_in_ems: glyph_position.x_offset as f32 / font.units_per_em(),
168                    }),
169            );
170        }
171
172        self.reusable_unicode_buffer = glyph_buffer.clear();
173    }
174}
175
176#[derive(Clone, Copy, Debug)]
177pub struct Settings {
178    pub cache_size: usize,
179}
180
181#[derive(Clone, Debug, Eq, Hash, PartialEq)]
182pub struct ShapeParams {
183    pub text: Substr,
184    pub fonts: Rc<[Rc<Font>]>,
185}
186
187#[derive(Clone, Debug)]
188pub struct ShapedText {
189    pub text: Substr,
190    pub width_in_ems: f32,
191    pub glyphs: Vec<ShapedGlyph>,
192}
193
194#[derive(Clone, Debug)]
195pub struct ShapedGlyph {
196    pub font: Rc<Font>,
197    pub id: GlyphId,
198    pub cluster: usize,
199    pub advance_in_ems: f32,
200    pub offset_in_ems: f32,
201}