makepad_render/
fonts.rs

1use crate::cx::*;
2use makepad_trapezoidator::Trapezoidator;
3use makepad_geometry::{AffineTransformation, Transform, Vector};
4use makepad_internal_iter::*;
5use makepad_path::PathIterator;
6
7impl Cx {
8    /*
9    pub fn load_font_style(&mut self, style: &str) -> Font {
10        self.load_font_path(&self.font(style))
11    }
12    */
13    pub fn reset_font_atlas_and_redraw(&mut self){
14        for font in &mut self.fonts{
15            font.atlas_pages.truncate(0);
16        }
17        self.fonts_atlas.alloc_xpos = 0.;
18        self.fonts_atlas.alloc_ypos = 0.;
19        self.fonts_atlas.alloc_hmax = 0.;
20        self.fonts_atlas.clear_buffer = true;
21        self.redraw_child_area(Area::All);
22    }
23    
24    pub fn load_font(&mut self, path: &str) -> Font {
25        let found = self.fonts.iter().position( | v | v.path == path);
26        if let Some(font_id) = found {
27            return Font {
28                font_id: Some(font_id),
29            }
30        }
31        
32        let font_id = self.fonts.len();
33        self.fonts.push(CxFont {
34            path: path.to_string(),
35            ..Default::default()
36        });
37        
38        return Font {
39            font_id: Some(font_id)
40        }
41    }
42}
43
44#[derive(Copy, Clone, Default)]
45pub struct Font{
46    pub font_id: Option<usize>
47}
48
49pub struct TrapezoidText {
50    shader: Shader,
51    trapezoidator: Trapezoidator
52}
53 
54impl TrapezoidText {
55    pub fn style(cx: &mut Cx) -> Self {
56        Self {
57            shader: cx.add_shader(Self::def_trapezoid_shader(), "TrapezoidShader"),
58            trapezoidator: Trapezoidator::default(),
59        }
60    }
61    
62    fn instance_a_xs()->InstanceVec2{uid!()}
63    fn instance_a_ys()->InstanceVec4{uid!()}
64    fn instance_chan()->InstanceFloat{uid!()}
65    
66    pub fn def_trapezoid_shader() -> ShaderGen { 
67        let mut sg = ShaderGen::new();
68        sg.geometry_vertices = vec![0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
69        sg.geometry_indices = vec![0, 1, 2, 2, 3, 0];
70        
71        sg.compose(shader_ast!({
72            
73            let geom: vec2<Geometry>;
74            
75            let a_xs: Self::instance_a_xs();
76            let a_ys: Self::instance_a_ys();
77            let chan: Self::instance_chan();
78            
79            let v_p0: vec2<Varying>;
80            let v_p1: vec2<Varying>;
81            let v_p2: vec2<Varying>;
82            let v_p3: vec2<Varying>;
83            let v_pixel: vec2<Varying>;
84            
85            fn intersect_line_segment_with_vertical_line(p0: vec2, p1: vec2, x: float) -> vec2 {
86                return vec2(
87                    x,
88                    mix(p0.y, p1.y, (x - p0.x) / (p1.x - p0.x))
89                );
90            }
91            
92            fn intersect_line_segment_with_horizontal_line(p0: vec2, p1: vec2, y: float) -> vec2 {
93                return vec2(
94                    mix(p0.x, p1.x, (y - p0.y) / (p1.y - p0.y)),
95                    y
96                );
97            }
98            
99            fn compute_clamped_right_trapezoid_area(p0: vec2, p1: vec2, p_min: vec2, p_max: vec2) -> float {
100                let x0 = clamp(p0.x, p_min.x, p_max.x);
101                let x1 = clamp(p1.x, p_min.x, p_max.x);
102                if (p0.x < p_min.x && p_min.x < p1.x) {
103                    p0 = intersect_line_segment_with_vertical_line(p0, p1, p_min.x);
104                }
105                if (p0.x < p_max.x && p_max.x < p1.x) {
106                    p1 = intersect_line_segment_with_vertical_line(p0, p1, p_max.x);
107                }
108                if (p0.y < p_min.y && p_min.y < p1.y) {
109                    p0 = intersect_line_segment_with_horizontal_line(p0, p1, p_min.y);
110                }
111                if (p1.y < p_min.y && p_min.y < p0.y) {
112                    p1 = intersect_line_segment_with_horizontal_line(p1, p0, p_min.y);
113                }
114                if (p0.y < p_max.y && p_max.y < p1.y) {
115                    p1 = intersect_line_segment_with_horizontal_line(p0, p1, p_max.y);
116                }
117                if (p1.y < p_max.y && p_max.y < p0.y) {
118                    p0 = intersect_line_segment_with_horizontal_line(p1, p0, p_max.y);
119                }
120                p0 = clamp(p0, p_min, p_max);
121                p1 = clamp(p1, p_min, p_max);
122                let h0 = p_max.y - p0.y;
123                let h1 = p_max.y - p1.y;
124                let a0 = (p0.x - x0) * h0;
125                let a1 = (p1.x - p0.x) * (h0 + h1) * 0.5;
126                let a2 = (x1 - p1.x) * h1;
127                return a0 + a1 + a2;
128            }
129            
130            fn compute_clamped_trapezoid_area(p_min: vec2, p_max: vec2) -> float {
131                let a0 = compute_clamped_right_trapezoid_area(v_p0, v_p1, p_min, p_max);
132                let a1 = compute_clamped_right_trapezoid_area(v_p2, v_p3, p_min, p_max);
133                return a0 - a1;
134            }
135            
136            fn pixel() -> vec4 {
137                //if fmod(v_pixel.x,2.0) > 1.0 && fmod(v_pixel.y,2.0) > 1.0{
138                //    return color("white")
139               // }
140               // return color("black");
141                let p_min = v_pixel.xy - 0.5;
142                let p_max = v_pixel.xy + 0.5;
143                let t_area = compute_clamped_trapezoid_area(p_min, p_max);
144                if chan < 0.5{
145                    return vec4(t_area,0.,0.,0.);
146                }
147                if chan < 1.5{
148                    return vec4(0., t_area, 0.,0.);
149                }
150                if chan < 2.5{
151                    return vec4(0., 0., t_area,0.);
152                }
153                return vec4(t_area, t_area, t_area,0.);
154                
155                //let b_minx = p_min.x + 1.0 / 3.0;
156                //let r_minx = p_min.x - 1.0 / 3.0;
157                //let b_maxx = p_max.x + 1.0 / 3.0;
158                //let r_maxx = p_max.x - 1.0 / 3.0;
159                //return vec4(
160                //    compute_clamped_trapezoid_area(vec2(r_minx, p_min.y), vec2(r_maxx, p_max.y)),
161                //    compute_clamped_trapezoid_area(p_min, p_max),
162                //    compute_clamped_trapezoid_area(vec2(b_minx, p_min.y), vec2(b_maxx, p_max.y)),
163                //    0.0
164                //);
165            }
166            
167            fn vertex() -> vec4 {
168                let pos_min = vec2(a_xs.x, min(a_ys.x, a_ys.y));
169                let pos_max = vec2(a_xs.y, max(a_ys.z, a_ys.w));
170                let pos = mix(pos_min - 1.0, pos_max + 1.0, geom);
171                
172                // set the varyings
173                v_p0 = vec2(a_xs.x, a_ys.x);
174                v_p1 = vec2(a_xs.y, a_ys.y);
175                v_p2 = vec2(a_xs.x, a_ys.z);
176                v_p3 = vec2(a_xs.y, a_ys.w);
177                v_pixel = pos;
178                //return vec4(vec2(pos.x, 1.0-pos.y) * 2.0 / vec2(4096.,4096.) - 1.0, 0.0, 1.0);
179                return camera_projection * vec4(pos, 0.0, 1.0);
180            }
181        }))
182    }
183
184    // test api for directly drawing a glyph
185    pub fn draw_char(&mut self, cx: &mut Cx, c:char, font_id:usize, font_size:f32) {
186        // now lets make a draw_character function
187        let inst = cx.new_instance(&self.shader, 1);
188        if inst.need_uniforms_now(cx) {
189        }
190        
191        let trapezoids = {
192            let cxfont = &cx.fonts[font_id];
193            let font = cxfont.font_loaded.as_ref().unwrap();
194            
195            let slot = if c < '\u{10000}' {
196                cx.fonts[font_id].font_loaded.as_ref().unwrap().char_code_to_glyph_index_map[c as usize]
197            } else {
198                0
199            };
200            
201            if slot == 0 {
202                return
203            }
204            let glyph = &cx.fonts[font_id].font_loaded.as_ref().unwrap().glyphs[slot];
205            let dpi_factor = cx.current_dpi_factor;
206            let pos = cx.get_turtle_pos(); 
207            let font_scale_logical = font_size * 96.0 / (72.0 * font.units_per_em);
208            let font_scale_pixels = font_scale_logical * dpi_factor;
209            
210            let mut trapezoids = Vec::new();
211            trapezoids.extend_from_internal_iter(
212                self.trapezoidator.trapezoidate(
213                    glyph
214                        .outline
215                        .commands()
216                        .map({
217                        move | command | {
218                            command.transform(
219                                &AffineTransformation::identity()
220                                    .translate(Vector::new(-glyph.bounds.p_min.x, -glyph.bounds.p_min.y))
221                                    .uniform_scale(font_scale_pixels)
222                                    .translate(Vector::new(pos.x, pos.y))
223                            )
224                        }
225                    }).linearize(0.5),
226                ),
227            );
228            trapezoids
229        };
230        for trapezoid in trapezoids {
231            let data = [
232                trapezoid.xs[0],
233                trapezoid.xs[1],
234                trapezoid.ys[0],
235                trapezoid.ys[1],
236                trapezoid.ys[2],
237                trapezoid.ys[3],
238                3.0
239            ];
240            inst.push_slice(cx, &data);
241        }
242    }
243    
244    // atlas drawing function used by CxAfterDraw
245    pub fn draw_todo(&mut self, cx: &mut Cx, todo: CxFontsAtlasTodo) {
246        let inst = cx.new_instance(&self.shader, 1);
247        if inst.need_uniforms_now(cx) {
248        }
249        
250        let mut size = 1.0;
251        for i in 0..3{
252            if i == 1{
253                size = 0.75;
254            }
255            if i == 2{
256                size = 0.6;
257            }
258            let trapezoids = {
259                let cxfont = &cx.fonts[todo.font_id];
260                let font = cxfont.font_loaded.as_ref().unwrap();
261                let atlas_page = &cxfont.atlas_pages[todo.atlas_page_id];
262                let glyph = &font.glyphs[todo.glyph_id];
263                
264                if todo.glyph_id == font.char_code_to_glyph_index_map[10] ||
265                todo.glyph_id == font.char_code_to_glyph_index_map[9] ||
266                todo.glyph_id == font.char_code_to_glyph_index_map[13] {
267                    return
268                }
269                
270                let glyphtc = atlas_page.atlas_glyphs[todo.glyph_id][todo.subpixel_id].unwrap();
271                let tx = glyphtc.tx1 * cx.fonts_atlas.texture_size.x + todo.subpixel_x_fract* atlas_page.dpi_factor;
272                let ty = 1.0 + glyphtc.ty1 * cx.fonts_atlas.texture_size.y - todo.subpixel_y_fract* atlas_page.dpi_factor;
273                
274                let font_scale_logical = atlas_page.font_size * 96.0 / (72.0 * font.units_per_em);
275                let font_scale_pixels = font_scale_logical * atlas_page.dpi_factor;
276                let mut trapezoids = Vec::new();
277                trapezoids.extend_from_internal_iter(
278                    self.trapezoidator.trapezoidate(
279                        glyph
280                            .outline
281                            .commands()
282                            .map({
283                            move | command | {
284                                command.transform(
285                                    &AffineTransformation::identity()
286                                        .translate(Vector::new(-glyph.bounds.p_min.x, -glyph.bounds.p_min.y))
287                                        .uniform_scale(font_scale_pixels * size)
288                                        .translate(Vector::new(tx, ty))
289                                )
290                            }
291                        }).linearize(0.5),
292                    ),
293                );
294                trapezoids
295            };
296            for trapezoid in trapezoids {
297                let data = [
298                    trapezoid.xs[0],
299                    trapezoid.xs[1],
300                    trapezoid.ys[0],
301                    trapezoid.ys[1],
302                    trapezoid.ys[2],
303                    trapezoid.ys[3],
304                    i as f32
305                ];
306                inst.push_slice(cx, &data);
307            }
308        }
309    }
310}
311
312pub struct CxAfterDraw {
313    pub trapezoid_text: TrapezoidText,
314    pub atlas_pass: Pass,
315    pub atlas_view: View,
316    pub atlas_texture: Texture
317}
318
319impl CxAfterDraw {
320    pub fn new(cx: &mut Cx) -> Self {
321        cx.fonts_atlas.texture_size = Vec2 {x: 2048.0, y: 2048.0};
322        let mut atlas_texture = Texture::default();
323        atlas_texture.set_desc(cx, None);
324        cx.fonts_atlas.texture_id = atlas_texture.texture_id.unwrap();
325        
326        Self {
327            trapezoid_text: TrapezoidText::style(cx),
328            atlas_pass: Pass::default(),
329            atlas_view: View {
330                always_redraw: true,
331                ..View::new(cx)
332            },
333            atlas_texture: atlas_texture
334        }
335    }
336    
337    pub fn after_draw(&mut self, cx: &mut Cx) {
338        //let start = Cx::profile_time_ns();
339        
340        // we need to start a pass that just uses the texture
341        if cx.fonts_atlas.atlas_todo.len()>0 {
342            self.atlas_pass.begin_pass(cx);
343            self.atlas_pass.set_size(cx, cx.fonts_atlas.texture_size);
344            let clear = if cx.fonts_atlas.clear_buffer{
345                cx.fonts_atlas.clear_buffer = false;
346                ClearColor::ClearWith(Color::default())
347            }
348            else{
349                ClearColor::InitWith(Color::default())
350            };
351            self.atlas_pass.add_color_texture(cx, &mut self.atlas_texture, clear);
352            let _ = self.atlas_view.begin_view(cx, Layout::default());
353            let mut atlas_todo = Vec::new();
354            std::mem::swap(&mut cx.fonts_atlas.atlas_todo, &mut atlas_todo);
355            for todo in atlas_todo {
356                self.trapezoid_text.draw_todo(cx, todo);
357                // ok we have to draw a font_id
358                //break;
359            }
360            self.atlas_view.end_view(cx);
361            self.atlas_pass.end_pass(cx);
362        }
363        //println!("TOTALT TIME {}", Cx::profile_time_ns() - start);
364    }
365}
366
367#[derive(Default)]
368pub struct CxFont {
369    pub path: String,
370    pub font_loaded: Option<makepad_font::Font>,
371    pub atlas_pages: Vec<CxFontAtlasPage>,
372}
373
374pub const ATLAS_SUBPIXEL_SLOTS: usize = 64;
375
376pub struct CxFontAtlasPage {
377    pub dpi_factor: f32,
378    pub font_size: f32,
379    pub atlas_glyphs: Vec<[Option<CxFontAtlasGlyph>; ATLAS_SUBPIXEL_SLOTS]>
380}
381
382#[derive(Clone, Copy)]
383pub struct CxFontAtlasGlyph {
384    pub tx1: f32,
385    pub ty1: f32,
386    pub tx2: f32,
387    pub ty2: f32,
388}
389
390#[derive(Default)]
391pub struct CxFontsAtlasTodo {
392    pub subpixel_x_fract: f32,
393    pub subpixel_y_fract: f32,
394    pub font_id: usize,
395    pub atlas_page_id: usize,
396    pub glyph_id: usize,
397    pub subpixel_id: usize
398}
399
400#[derive(Default)]
401pub struct CxFontsAtlas {
402    pub texture_id: usize,
403    pub texture_size: Vec2,
404    pub clear_buffer: bool,
405    pub alloc_xpos: f32,
406    pub alloc_ypos: f32,
407    pub alloc_hmax: f32,
408    pub atlas_todo: Vec<CxFontsAtlasTodo>,
409}
410
411impl CxFontsAtlas {
412    pub fn alloc_atlas_glyph(&mut self, path: &str, w: f32, h: f32) -> CxFontAtlasGlyph {
413        if w + self.alloc_xpos >= self.texture_size.x {
414            self.alloc_xpos = 0.0;
415            self.alloc_ypos += self.alloc_hmax + 1.0;
416            self.alloc_hmax = 0.0;
417        }
418        if h + self.alloc_ypos >= self.texture_size.y {
419            println!("FONT ATLAS FULL {}, TODO FIX THIS", path);
420        }
421        if h > self.alloc_hmax {
422            self.alloc_hmax = h;
423        }
424        
425        let tx1 = self.alloc_xpos / self.texture_size.x;
426        let ty1 = self.alloc_ypos / self.texture_size.y;
427        
428        self.alloc_xpos += w + 1.0;
429        
430        if h > self.alloc_hmax {
431            self.alloc_hmax = h;
432        }
433        
434        CxFontAtlasGlyph {
435            tx1: tx1,
436            ty1: ty1,
437            tx2: tx1 + (w / self.texture_size.x),
438            ty2: ty1 + (h / self.texture_size.y)
439        }
440    }
441}
442
443impl CxFont {
444    pub fn load_from_ttf_bytes(&mut self, bytes: &[u8]) -> makepad_ttf_parser::Result<()> {
445        let font = makepad_ttf_parser::parse_ttf(bytes) ?;
446        self.font_loaded = Some(font);
447        Ok(())
448    }
449    
450    pub fn get_atlas_page_id(&mut self, dpi_factor: f32, font_size: f32) -> usize {
451        for (index, sg) in self.atlas_pages.iter().enumerate() {
452            if sg.dpi_factor == dpi_factor
453                && sg.font_size == font_size {
454                return index
455            }
456        }
457        if let Some(font) = &self.font_loaded {
458            self.atlas_pages.push(CxFontAtlasPage {
459                dpi_factor: dpi_factor,
460                font_size: font_size,
461                atlas_glyphs: {
462                    let mut v = Vec::new();
463                    v.resize(font.glyphs.len(), [None; ATLAS_SUBPIXEL_SLOTS]);
464                    v
465                }
466            });
467            self.atlas_pages.len() - 1
468        }
469        else {
470            panic!("Font not loaded {}", self.path);
471        }
472    }
473}