makepad_draw/
font_atlas.rs

1pub use {
2    std::{
3        borrow::Borrow,
4        collections::VecDeque,
5        hash::{Hash, Hasher},
6        rc::Rc,
7        cell::RefCell,
8        io::prelude::*,
9        fs::File,
10        collections::HashMap,
11    },
12    crate::{
13        makepad_platform::*,
14        cx_2d::Cx2d,
15        turtle::{Walk, Layout},
16        draw_list_2d::{ManyInstances, DrawList2d, RedrawingApi},
17        geometry::GeometryQuad2D,
18        shader::draw_trapezoid::DrawTrapezoidVector,
19        makepad_vector::font::Glyph,
20        makepad_vector::trapezoidator::Trapezoidator,
21        makepad_vector::geometry::{AffineTransformation, Transform, Vector},
22        makepad_vector::internal_iter::ExtendFromInternalIterator,
23        makepad_vector::path::PathIterator,
24    },
25    rustybuzz::{Direction, GlyphInfo, UnicodeBuffer},
26};
27
28pub struct CxFontsAtlas {
29    pub fonts: Vec<Option<CxFont >>,
30    pub path_to_font_id: HashMap<String, usize>,
31    pub texture_id: TextureId,
32    pub clear_buffer: bool,
33    pub alloc: CxFontsAtlasAlloc
34}
35
36#[derive(Default)]
37pub struct CxFontsAtlasAlloc {
38    pub texture_size: DVec2,
39    pub full: bool,
40    pub xpos: f64,
41    pub ypos: f64,
42    pub hmax: f64,
43    pub todo: Vec<CxFontsAtlasTodo>,
44}
45
46impl CxFontsAtlas {
47    pub fn new(texture_id: TextureId) -> Self {
48        Self {
49            fonts: Vec::new(),
50            path_to_font_id: HashMap::new(),
51            texture_id,
52            clear_buffer: false,
53            alloc: CxFontsAtlasAlloc {
54                full: false,
55                texture_size: DVec2 {x: 4096.0, y: 4096.0},
56                xpos: 0.0,
57                ypos: 0.0,
58                hmax: 0.0,
59                todo: Vec::new(),
60            }
61        }
62    }
63}
64impl CxFontsAtlasAlloc {
65    pub fn alloc_atlas_glyph(&mut self, w: f64, h: f64) -> CxFontAtlasGlyph {
66        if w + self.xpos >= self.texture_size.x {
67            self.xpos = 0.0;
68            self.ypos += self.hmax + 1.0;
69            self.hmax = 0.0;
70        }
71        if h + self.ypos >= self.texture_size.y {
72            // ok so the fontatlas is full..
73            self.full = true;
74            println!("FONT ATLAS FULL, TODO FIX THIS {} > {},", h + self.ypos, self.texture_size.y);
75        }
76        if h > self.hmax {
77            self.hmax = h;
78        }
79        
80        let tx1 = self.xpos / self.texture_size.x;
81        let ty1 = self.ypos / self.texture_size.y;
82        
83        self.xpos += w + 1.0;
84        
85        CxFontAtlasGlyph {
86            t1: dvec2(tx1, ty1).into(),
87            t2: dvec2( tx1 + (w / self.texture_size.x), ty1 + (h / self.texture_size.y)).into()
88        }
89    }
90}
91
92#[derive(Clone, Live)]
93pub struct Font {
94    #[rust] pub font_id: Option<usize>,
95    #[live] pub path: LiveDependency
96}
97
98#[derive(Clone)]
99pub struct CxFontsAtlasRc(pub Rc<RefCell<CxFontsAtlas >>);
100
101impl LiveHook for Font {
102    fn after_apply(&mut self, cx: &mut Cx, _apply_from: ApplyFrom, _index: usize, _nodes: &[LiveNode]) {
103        Cx2d::lazy_construct_font_atlas(cx);
104        let atlas = cx.get_global::<CxFontsAtlasRc>().clone();
105        self.font_id = Some(atlas.0.borrow_mut().get_font_by_path(cx, self.path.as_str()));
106    }
107}
108
109impl CxFontsAtlas {
110    pub fn get_font_by_path(&mut self, cx: &Cx, path: &str) -> usize {
111        if path.len() == 0{
112            return 0
113        }
114        if let Some(item) = self.path_to_font_id.get(path) {
115            return *item;
116        }
117        let font_id = self.fonts.len();
118        self.fonts.push(None);
119        self.path_to_font_id.insert(path.to_string(), font_id);
120        
121        match cx.get_dependency(path) {
122            // FIXME(eddyb) this clones the `data` `Vec<u8>`, in order to own it
123            // inside a `owned_font_face::OwnedFace`.
124            Ok(data) => match CxFont::load_from_ttf_bytes(data) {
125                Err(_) => {
126                    error!("Error loading font {} ", path);
127                }
128                Ok(cxfont) => {
129                    self.fonts[font_id] = Some(cxfont);
130                }
131            }
132            Err(err) => {
133                error!("get_font_by_path - {} {}", path, err)
134            }
135        }
136        font_id
137    }
138    
139    pub fn reset_fonts_atlas(&mut self) {
140        for cxfont in &mut self.fonts {
141            if let Some(cxfont) = cxfont {
142                cxfont.atlas_pages.clear();
143            }
144        }
145        self.alloc.xpos = 0.;
146        self.alloc.ypos = 0.;
147        self.alloc.hmax = 0.;
148        self.clear_buffer = true;
149    }
150    
151    pub fn get_internal_font_atlas_texture_id(&self) -> TextureId {
152        self.texture_id
153    }
154}
155
156impl DrawTrapezoidVector {
157    
158    // atlas drawing function used by CxAfterDraw
159    fn draw_todo(&mut self, fonts_atlas: &mut CxFontsAtlas, todo: CxFontsAtlasTodo, many: &mut ManyInstances) {
160        //let fonts_atlas = cx.fonts_atlas_rc.0.borrow_mut();
161        let mut size = 1.0;
162        for i in 0..1 {
163            if i == 1 {
164                size = 0.75;
165            }
166            if i == 2 {
167                size = 0.6;
168            }
169            let trapezoids = {
170                let cxfont = fonts_atlas.fonts[todo.font_id].as_mut().unwrap();
171                let units_per_em = cxfont.ttf_font.units_per_em;
172                let atlas_page = &cxfont.atlas_pages[todo.atlas_page_id];
173                let glyph = cxfont.owned_font_face.with_ref(|face| cxfont.ttf_font.get_glyph_by_id(face, todo.glyph_id).unwrap());
174                
175                let is_one_of_tab_lf_cr = ['\t', '\n', '\r'].iter().any(|&c| {
176                    Some(todo.glyph_id) == cxfont.owned_font_face.with_ref(|face| face.glyph_index(c).map(|id| id.0 as usize))
177                });
178                if is_one_of_tab_lf_cr {
179                    return
180                }
181                
182                let glyphtc = atlas_page.atlas_glyphs.get(&todo.glyph_id).unwrap()[todo.subpixel_id].unwrap();
183                let tx = glyphtc.t1.x as f64 * fonts_atlas.alloc.texture_size.x + todo.subpixel_x_fract * atlas_page.dpi_factor;
184                let ty = 1.0 + glyphtc.t1.y as f64 * fonts_atlas.alloc.texture_size.y - todo.subpixel_y_fract * atlas_page.dpi_factor;
185                
186                let font_scale_logical = atlas_page.font_size * 96.0 / (72.0 * units_per_em);
187                let font_scale_pixels = font_scale_logical * atlas_page.dpi_factor;
188                let mut trapezoids = Vec::new();
189                //log_str(&format!("Serializing char {} {} {} {}", glyphtc.tx1 , cx.fonts_atlas.texture_size.x ,todo.subpixel_x_fract ,atlas_page.dpi_factor));
190                let trapezoidate = self.trapezoidator.trapezoidate(
191                    glyph
192                        .outline
193                        .iter()
194                        .map({
195                        move | command | {
196                            let cmd = command.transform(
197                                &AffineTransformation::identity()
198                                    .translate(Vector::new(-glyph.bounds.p_min.x, -glyph.bounds.p_min.y))
199                                    .uniform_scale(font_scale_pixels * size)
200                                    .translate(Vector::new(tx, ty))
201                        );
202                        
203                            cmd
204                        }
205                    }).linearize(0.5),
206                );
207                if let Some(trapezoidate) = trapezoidate {
208                    trapezoids.extend_from_internal_iter(
209                        trapezoidate
210                    );
211                }
212                trapezoids
213            };
214            for trapezoid in trapezoids {
215                self.a_xs = Vec2 {x: trapezoid.xs[0], y: trapezoid.xs[1]};
216                self.a_ys = Vec4 {x: trapezoid.ys[0], y: trapezoid.ys[1], z: trapezoid.ys[2], w: trapezoid.ys[3]};
217                self.chan = i as f32;
218                many.instances.extend_from_slice(self.draw_vars.as_slice());
219            }
220        }
221    }
222}
223
224#[derive(Clone)]
225pub struct CxDrawFontsAtlasRc(pub Rc<RefCell<CxDrawFontsAtlas >>);
226
227pub struct CxDrawFontsAtlas {
228    pub draw_trapezoid: DrawTrapezoidVector,
229    pub atlas_pass: Pass,
230    pub atlas_draw_list: DrawList2d,
231    pub atlas_texture: Texture,
232    pub counter: usize
233}
234
235impl CxDrawFontsAtlas {
236    pub fn new(cx: &mut Cx) -> Self {
237        
238        let atlas_texture = Texture::new(cx);
239        
240        //cx.fonts_atlas.texture_id = Some(atlas_texture.texture_id());
241        
242        let draw_trapezoid = DrawTrapezoidVector::new_local(cx);
243        // ok we need to initialize drawtrapezoidtext from a live pointer.
244        Self {
245            counter: 0,
246            draw_trapezoid,
247            atlas_pass: Pass::new(cx),
248            atlas_draw_list: DrawList2d::new(cx),
249            atlas_texture: atlas_texture
250        }
251    }
252}
253
254impl<'a> Cx2d<'a> {
255    pub fn lazy_construct_font_atlas(cx: &mut Cx){
256        // ok lets fetch/instance our CxFontsAtlasRc
257        if !cx.has_global::<CxFontsAtlasRc>() {
258            
259            let draw_fonts_atlas = CxDrawFontsAtlas::new(cx);
260            let texture_id = draw_fonts_atlas.atlas_texture.texture_id();
261            cx.set_global(CxDrawFontsAtlasRc(Rc::new(RefCell::new(draw_fonts_atlas))));
262            
263            let fonts_atlas = CxFontsAtlas::new(texture_id);
264            cx.set_global(CxFontsAtlasRc(Rc::new(RefCell::new(fonts_atlas))));
265        }
266    }
267    
268    pub fn reset_fonts_atlas(cx:&mut Cx){
269        if cx.has_global::<CxFontsAtlasRc>() {
270            let mut fonts_atlas = cx.get_global::<CxFontsAtlasRc>().0.borrow_mut();
271            fonts_atlas.reset_fonts_atlas();
272        }
273    }
274        
275    pub fn draw_font_atlas(&mut self) {
276        let draw_fonts_atlas_rc = self.cx.get_global::<CxDrawFontsAtlasRc>().clone();
277        let mut draw_fonts_atlas = draw_fonts_atlas_rc.0.borrow_mut();
278        let fonts_atlas_rc = self.fonts_atlas_rc.clone();
279        let mut fonts_atlas = fonts_atlas_rc.0.borrow_mut();
280        let fonts_atlas = &mut*fonts_atlas;
281        //let start = Cx::profile_time_ns();
282        // we need to start a pass that just uses the texture
283        if fonts_atlas.alloc.todo.len()>0 {
284            self.begin_pass(&draw_fonts_atlas.atlas_pass, None);
285
286            let texture_size = fonts_atlas.alloc.texture_size;
287            draw_fonts_atlas.atlas_pass.set_size(self.cx, texture_size);
288            
289            let clear = if fonts_atlas.clear_buffer {
290                fonts_atlas.clear_buffer = false;
291                PassClearColor::ClearWith(Vec4::default())
292            }
293            else {
294                PassClearColor::InitWith(Vec4::default())
295            };
296            
297            draw_fonts_atlas.atlas_pass.clear_color_textures(self.cx);
298            draw_fonts_atlas.atlas_pass.add_color_texture(self.cx, &draw_fonts_atlas.atlas_texture, clear);
299            draw_fonts_atlas.atlas_draw_list.begin_always(self);
300
301            let mut atlas_todo = Vec::new();
302            std::mem::swap(&mut fonts_atlas.alloc.todo, &mut atlas_todo);
303            
304            if let Some(mut many) = self.begin_many_instances(&draw_fonts_atlas.draw_trapezoid.draw_vars) {
305
306                for todo in atlas_todo {
307                    draw_fonts_atlas.draw_trapezoid.draw_todo(fonts_atlas, todo, &mut many);
308                }
309                
310                self.end_many_instances(many);
311            }
312            
313            draw_fonts_atlas.counter += 1;
314            draw_fonts_atlas.atlas_draw_list.end(self);
315            self.end_pass(&draw_fonts_atlas.atlas_pass);
316        }
317        //println!("TOTALT TIME {}", Cx::profile_time_ns() - start);
318    }
319}
320
321pub struct CxFont {
322    pub ttf_font: makepad_vector::font::TTFFont,
323    pub owned_font_face: crate::owned_font_face::OwnedFace,
324    pub atlas_pages: Vec<CxFontAtlasPage>,
325    pub shape_cache: ShapeCache,
326}
327
328pub struct ShapeCache {
329    pub keys: VecDeque<(Direction, Rc<str>)>,
330    pub glyph_ids: HashMap<(Direction, Rc<str>), Vec<usize>>,
331}
332
333impl ShapeCache {
334    // The maximum number of keys that can be stored in the cache.
335    const MAX_SIZE: usize = 4096;
336
337    pub fn new() -> Self {
338        Self {
339            keys: VecDeque::new(),
340            glyph_ids: HashMap::new(),
341        }
342    }
343
344    // If there is an entry for the given key in the cache, returns the corresponding list of
345    // glyph indices for that key. Otherwise, uses the given UnicodeBuffer and OwnedFace to
346    // compute the list of glyph indices for the key, inserts that in the cache and then returns
347    // the corresponding list.
348    //
349    // This method takes a UnicodeBuffer by value, and then returns the same buffer by value. This
350    // is necessary because rustybuzz::shape consumes the UnicodeBuffer and then returns a
351    // GlyphBuffer that reuses the same storage. Once we are done with the GlyphBuffer, we consume
352    // it and then return yet another UnicodeBuffer that reuses the same storage. This allows us to
353    // avoid unnecessary heap allocations.
354    //
355    // Note that owned_font_face should be the same as the CxFont to which this cache belongs,
356    // otherwise you will not get correct results.
357    pub fn get_or_compute_glyph_ids(
358        &mut self, 
359        key: (Direction, &str),
360        mut rustybuzz_buffer: UnicodeBuffer,
361        owned_font_face: &crate::owned_font_face::OwnedFace
362    ) -> (&[usize], UnicodeBuffer) {
363        if !self.glyph_ids.contains_key(&key as &dyn ShapeCacheKey) {
364            if self.keys.len() == Self::MAX_SIZE {
365                for run in self.keys.drain(..Self::MAX_SIZE / 2) {
366                    self.glyph_ids.remove(&run);
367                }
368            }
369
370            let (direction, string) = key;
371            rustybuzz_buffer.set_direction(direction);
372            rustybuzz_buffer.push_str(string);
373            let glyph_buffer = owned_font_face.with_ref( | face | rustybuzz::shape(face, &[], rustybuzz_buffer));
374            let glyph_ids: Vec<_> = glyph_buffer.glyph_infos().iter().map( | glyph | glyph.glyph_id as usize).collect();
375            rustybuzz_buffer = glyph_buffer.clear();
376
377            let owned_string: Rc<str> = string.into();
378            self.keys.push_back((direction, owned_string.clone()));
379            self.glyph_ids.insert((direction, owned_string), glyph_ids);
380        }
381        (&self.glyph_ids[&key as &dyn ShapeCacheKey], rustybuzz_buffer)
382    }
383}
384
385// When doing inserts on the shape cache, we want to use (Direction, Rc<str>) as our key type. When
386// doing lookups on the shape cache, we want to use (Direction, &str) as our key type.
387// Unfortunately, Rust does not allow this, since (Direction, Rc<str>) can only be borrowed as
388// &(Direction, Rc<str>). So we'd have to create a temporary key, and then borrow from that.
389//
390// This is unacceptable, because creating a temporary key requires us to do a heap allocation every
391// time we want to do a lookup on the shape cache, which is on a very hot path. Instead, we resort
392// to a bit of trickery, inspired by the following post on Stackoverflow:
393// https://stackoverflow.com/questions/45786717/how-to-implement-hashmap-with-two-keys/46044391#46044391
394//
395// The idea is that we cannot borrow (Direction, Rc<str>) as a (Direction, &str). But what we *can* do is
396// define a trait ShapeCacheKey to represent our key, with methods to access both the direction and the
397// string, implement that for both (Direction, Rc<str>) and (Direction, &str), and then borrow
398// (Direction, Rc<str>) as &dyn ShapeCacheKey (that is, a reference to a trait object). We can turn a
399// (Direction, &str) into a &dyn ShapeCacheKey without creating a temporary key or doing any heap
400// allocations, so this allows us to do what we want.
401pub trait ShapeCacheKey {
402    fn direction(&self) -> Direction;
403    fn string(&self) -> &str;
404}
405
406impl<'a> Borrow<dyn ShapeCacheKey + 'a> for (Direction, Rc<str>) {
407    fn borrow(&self) -> &(dyn ShapeCacheKey + 'a) {
408        self
409    }
410}
411
412impl Eq for dyn ShapeCacheKey + '_ {}
413
414impl Hash for dyn ShapeCacheKey + '_ {
415    fn hash<H: Hasher>(&self, hasher: &mut H) {
416        self.direction().hash(hasher);
417        self.string().hash(hasher);
418    }
419}
420
421impl PartialEq for dyn ShapeCacheKey + '_ {
422    fn eq(&self, other: &Self) -> bool {
423        if self.direction() != other.direction() {
424            return false;
425        }
426        if self.string() != other.string() {
427            return false;
428        }
429        true
430    }
431}
432
433impl ShapeCacheKey for (Direction, &str) {
434    fn direction(&self) -> Direction {
435        self.0
436    }
437
438    fn string(&self) -> &str {
439        self.1
440    }
441}
442
443impl ShapeCacheKey for (Direction, Rc<str>) {
444    fn direction(&self) -> Direction {
445        self.0
446    }
447
448    fn string(&self) -> &str {
449        &self.1
450    }
451}
452
453pub const ATLAS_SUBPIXEL_SLOTS: usize = 64;
454
455#[derive(Clone)]
456pub struct CxFontAtlasPage {
457    pub dpi_factor: f64,
458    pub font_size: f64,
459    pub atlas_glyphs: HashMap<usize,[Option<CxFontAtlasGlyph>; ATLAS_SUBPIXEL_SLOTS]>
460}
461
462#[derive(Clone, Copy)]
463pub struct CxFontAtlasGlyph {
464    pub t1: Vec2,
465    pub t2: Vec2,
466}
467
468#[derive(Default, Debug)]
469pub struct CxFontsAtlasTodo {
470    pub subpixel_x_fract: f64,
471    pub subpixel_y_fract: f64,
472    pub font_id: usize,
473    pub atlas_page_id: usize,
474    pub glyph_id: usize,
475    pub subpixel_id: usize
476}
477
478impl CxFont {
479    pub fn load_from_ttf_bytes(bytes: Rc<Vec<u8>>) -> Result<Self, crate::owned_font_face::FaceParsingError> {
480        let owned_font_face = crate::owned_font_face::OwnedFace::parse(bytes, 0)?;
481        let ttf_font = owned_font_face.with_ref(|face| makepad_vector::ttf_parser::from_ttf_parser_face(face));
482        Ok(Self {
483            ttf_font,
484            owned_font_face,
485            atlas_pages: Vec::new(),
486            shape_cache: ShapeCache::new(),
487        })
488    }
489    
490    pub fn get_atlas_page_id(&mut self, dpi_factor: f64, font_size: f64) -> usize {
491        for (index, sg) in self.atlas_pages.iter().enumerate() {
492            if sg.dpi_factor == dpi_factor
493                && sg.font_size == font_size {
494                return index
495            }
496        }
497        self.atlas_pages.push(CxFontAtlasPage {
498            dpi_factor: dpi_factor,
499            font_size: font_size,
500            atlas_glyphs:HashMap::new(),/* {
501                let mut v = Vec::new();
502                v.resize(self.owned_font_face.with_ref(|face| face.number_of_glyphs() as usize), [None; ATLAS_SUBPIXEL_SLOTS]);
503                v
504            }*/
505        });
506        self.atlas_pages.len() - 1
507    }
508
509    pub fn get_glyph(&mut self, c:char)->Option<&Glyph>{
510        if c < '\u{10000}' {
511            Some(self.get_glyph_by_id(self.owned_font_face.with_ref(|face| face.glyph_index(c))?.0 as usize).unwrap())
512        } else {
513            None
514        }
515    }
516
517    pub fn get_glyph_by_id(&mut self, id: usize) -> makepad_vector::ttf_parser::Result<&Glyph> {
518        self.owned_font_face.with_ref(|face| self.ttf_font.get_glyph_by_id(face, id))
519    }
520}