makepad_draw/shader/
draw_text.rs

1use {
2    crate::{
3        cx_2d::Cx2d,
4        cx_draw::CxDraw,
5        geometry::GeometryQuad2D,
6        makepad_platform::*,
7        text::{
8            color::Color,
9            font::FontId,
10            font_family::FontFamilyId,
11            fonts::Fonts,
12            geom::{Point, Rect, Size, Transform},
13            layouter::{
14                BorrowedLayoutParams, LaidoutGlyph, LaidoutRow, LaidoutText, LayoutOptions, Span,
15                Style,
16            },
17            loader::{FontDefinition, FontFamilyDefinition},
18            rasterizer::{AtlasKind, RasterizedGlyph},
19            selection::{Cursor, Selection},
20        },
21        turtle::*,
22        turtle::{Align, Walk},
23    },
24    std::{cell::RefCell, rc::Rc},
25};
26
27live_design! {
28    use link::shaders::*;
29
30    pub DrawText = {{DrawText}} {
31        color: #ffff,
32
33        uniform radius: float;
34        uniform cutoff: float;
35        uniform grayscale_atlas_size: vec2;
36        uniform color_atlas_size: vec2;
37
38        texture grayscale_texture: texture2d
39        texture color_texture: texture2d
40
41        varying pos: vec2
42        varying t: vec2
43        varying world: vec4
44        
45        fn vertex(self) -> vec4 {
46            let p = mix(self.rect_pos, self.rect_pos + self.rect_size, self.geom_pos);
47            let p_clipped = clamp(p, self.draw_clip.xy, self.draw_clip.zw);
48            let p_normalized: vec2 = (p_clipped - self.rect_pos) / self.rect_size;
49
50            self.pos = p_normalized;
51            self.t = mix(self.t_min, self.t_max, p_normalized.xy);
52            self.world = self.view_transform * vec4(
53                p_clipped.x,
54                p_clipped.y,
55                self.glyph_depth + self.draw_zbias,
56                1.
57            );
58            return self.camera_projection * (self.camera_view * (self.world));
59        }
60
61        fn sdf(self, scale: float, p: vec2) -> float {
62            let s = sample2d(self.grayscale_texture, p).x;
63            // 1.1 factor to compensate for text being magically too dark after the fontstack refactor
64            s = clamp(((s - (1.0 - self.cutoff)) * self.radius / scale + 0.5)*1.1, 0.0, 1.0);
65            return s;
66        }
67
68        fn get_color(self) -> vec4 {
69            return self.color
70        }
71        
72        fn fragment(self) -> vec4 {
73            return depth_clip(self.world, self.pixel(), self.depth_clip);
74        }
75        
76        fn pixel(self) -> vec4 {
77            let dxt = length(dFdx(self.t));
78            let dyt = length(dFdy(self.t));
79            let color = #0000
80            if self.texture_index == 0 {
81                // TODO: Support non square atlases?
82                let scale = (dxt + dyt) * self.grayscale_atlas_size.x * 0.5;
83                let s = self.sdf(scale, self.t.xy);
84                let c = self.get_color();
85                return s * vec4(c.rgb * c.a, c.a);
86            } else {
87                let c = sample2d(self.color_texture, self.t);
88                return vec4(c.rgb * c.a, c.a);
89            }
90        }
91    }
92}
93
94#[derive(Live, LiveRegister)]
95#[repr(C)]
96pub struct DrawText {
97    #[live]
98    pub geometry: GeometryQuad2D,
99    #[live]
100    pub text_style: TextStyle,
101    #[live(1.0)]
102    pub font_scale: f32,
103    #[live(1.0)]
104    pub draw_depth: f32,
105    #[live]
106    pub debug: bool,
107    
108    #[live]
109    pub temp_y_shift: f32,
110    
111    #[deref]
112    pub draw_vars: DrawVars,
113    #[calc]
114    pub rect_pos: Vec2,
115    #[calc]
116    pub rect_size: Vec2,
117    #[calc]
118    pub draw_clip: Vec4,
119    #[live(1.0)] 
120    pub depth_clip: f32,
121    #[calc]
122    pub glyph_depth: f32,
123    #[live]
124    pub color: Vec4,
125    #[calc]
126    pub texture_index: f32,
127    #[calc]
128    pub t_min: Vec2,
129    #[calc]
130    pub t_max: Vec2,
131}
132
133impl LiveHook for DrawText {
134    fn before_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
135        self.draw_vars
136            .before_apply_init_shader(cx, apply, index, nodes, &self.geometry);
137    }
138
139    fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
140        self.draw_vars
141            .after_apply_update_self(cx, apply, index, nodes, &self.geometry);
142    }
143}
144
145impl DrawText {
146    pub fn draw_abs(&mut self, cx: &mut Cx2d, pos: DVec2, text: &str) {
147        let text = self.layout(cx, 0.0, 0.0, None, false, Align::default(), text);
148        self.draw_text(cx, Point::new(pos.x as f32, pos.y as f32), &text);
149    }
150
151    pub fn draw_walk(
152        &mut self,
153        cx: &mut Cx2d,
154        walk: Walk,
155        align: Align,
156        text: &str,
157    ) -> makepad_platform::Rect {
158        let turtle_rect = cx.turtle().padded_rect();
159        let max_width_in_lpxs = if !turtle_rect.size.x.is_nan() {
160            Some(turtle_rect.size.x as f32)
161        } else {
162            None
163        };
164        let wrap = cx.turtle().layout().flow == Flow::RightWrap;
165
166        let text = self.layout(cx, 0.0, 0.0, max_width_in_lpxs, wrap, align, text);
167        self.draw_walk_laidout(cx, walk, &text)
168    }
169
170    pub fn draw_walk_laidout(
171        &mut self,
172        cx: &mut Cx2d,
173        walk: Walk,
174        laidout_text: &LaidoutText,
175    ) -> makepad_platform::Rect {
176        use crate::text::geom::{Point, Size};
177        use crate::turtle;
178
179        let size_in_lpxs = laidout_text.size_in_lpxs * self.font_scale;
180        let max_size_in_lpxs = Size::new(
181            cx.turtle()
182                .max_width(walk)
183                .map_or(size_in_lpxs.width, |max_width| max_width as f32),
184            cx.turtle()
185                .max_height(walk)
186                .map_or(size_in_lpxs.height, |max_height| max_height as f32),
187        );
188        let turtle_rect = cx.walk_turtle(Walk {
189            abs_pos: walk.abs_pos,
190            margin: walk.margin,
191            width: turtle::Size::Fixed(max_size_in_lpxs.width as f64),
192            height: turtle::Size::Fixed(max_size_in_lpxs.height as f64),
193        });
194
195        if self.debug {
196            let mut area = Area::Empty;
197            cx.add_aligned_rect_area(&mut area, turtle_rect);
198            cx.cx.debug.area(area, vec4(1.0, 1.0, 1.0, 1.0));
199        }
200
201        let origin_in_lpxs = Point::new(
202            turtle_rect.pos.x as f32,
203            turtle_rect.pos.y as f32,
204        );
205        self.draw_text(cx, origin_in_lpxs, &laidout_text);
206
207        rect(
208            origin_in_lpxs.x as f64,
209            origin_in_lpxs.y as f64,
210            size_in_lpxs.width as f64,
211            size_in_lpxs.height as f64,
212        )
213    }
214
215    pub fn draw_walk_resumable_with(
216        &mut self,
217        cx: &mut Cx2d,
218        text_str: &str,
219        mut f: impl FnMut(&mut Cx2d, makepad_platform::Rect),
220    ) {
221        let turtle_pos = cx.turtle().pos();
222        let turtle_rect = cx.turtle().padded_rect();
223        let origin_in_lpxs = Point::new(turtle_rect.pos.x as f32, turtle_pos.y as f32);
224        let first_row_indent_in_lpxs = turtle_pos.x as f32 - origin_in_lpxs.x;
225        let row_height = cx.turtle().row_height();
226        
227        // lets draw a debug rect
228        /*
229        if text_str.starts_with("markdownedited"){
230            let mut area = Area::Empty;
231            cx.add_aligned_rect_area(
232                &mut area,
233                makepad_platform::rect(
234                    origin_in_lpxs.x as f64,
235                    origin_in_lpxs.y as f64,
236                    100.0 as f64,
237                    row_height as f64,
238                ),
239            );
240            cx.cx
241            .debug
242            .area(area, makepad_platform::vec4(1.0, 0.0, 0.0, 1.0));
243        }*/
244        
245        let max_width_in_lpxs = if !turtle_rect.size.x.is_nan() {
246            Some(turtle_rect.size.x as f32)
247        } else {
248            None
249        };
250        let wrap = cx.turtle().layout().flow == Flow::RightWrap;
251
252        let text = self.layout(
253            cx,
254            first_row_indent_in_lpxs,
255            row_height as f32,
256            max_width_in_lpxs,
257            wrap,
258            Align::default(),
259            text_str,
260        );
261        self.draw_text(cx, origin_in_lpxs, &text);
262
263        let last_row = text.rows.last().unwrap();
264        let new_turtle_pos = origin_in_lpxs
265            + Size::new(
266                last_row.width_in_lpxs,
267                last_row.origin_in_lpxs.y - last_row.ascender_in_lpxs,
268            ) * self.font_scale;
269        let used_size_in_lpxs = text.size_in_lpxs * self.font_scale;
270        let new_turtle_pos = dvec2(new_turtle_pos.x as f64, new_turtle_pos.y as f64);
271        let turtle = cx.turtle_mut();
272        
273        turtle.set_pos(new_turtle_pos);
274        turtle.update_width_max(origin_in_lpxs.x as f64, used_size_in_lpxs.width as f64);
275        turtle.update_height_max(origin_in_lpxs.y as f64, used_size_in_lpxs.height as f64);
276        
277        turtle.set_wrap_spacing((
278           last_row.line_spacing_above_in_lpxs - last_row.ascender_in_lpxs
279        )as f64);
280        
281        cx.emit_turtle_walk(makepad_platform::Rect {
282            pos: new_turtle_pos,
283            size: dvec2(
284                used_size_in_lpxs.width as f64,
285                used_size_in_lpxs.height as f64,
286            ),
287        });
288        
289        let shift = if let Some(row) = text.rows.get(0){
290            if let Some(glyph) = row.glyphs.get(0){
291                glyph.font_size_in_lpxs * self.temp_y_shift
292            }
293            else{0.0}
294        }
295        else{0.0};
296                
297        for rect_in_lpxs in text.selection_rects_in_lpxs(Selection {
298            anchor: Cursor {
299                index: 0,
300                prefer_next_row: false,
301            },
302            cursor: Cursor {
303                index: text.text.len(),
304                prefer_next_row: false,
305            },
306        }) {
307            let rect_in_lpxs = Rect::new(
308                origin_in_lpxs + Size::from(rect_in_lpxs.origin) * self.font_scale,
309                rect_in_lpxs.size * self.font_scale,
310            );
311            f(
312                cx,
313                makepad_platform::rect(
314                    rect_in_lpxs.origin.x as f64,
315                    rect_in_lpxs.origin.y as f64 + shift as f64,
316                    rect_in_lpxs.size.width as f64,
317                    rect_in_lpxs.size.height as f64,
318                ),
319            )
320        }
321    }
322
323    pub fn layout(
324        &self,
325        cx: &mut Cx,
326        first_row_indent_in_lpxs: f32,
327        first_row_min_line_spacing_below_in_lpxs: f32,
328        max_width_in_lpxs: Option<f32>,
329        wrap: bool,
330        align: Align,
331        text: &str,
332    ) -> Rc<LaidoutText> {
333        CxDraw::lazy_construct_fonts(cx);
334        let fonts = cx.get_global::<Rc<RefCell<Fonts>>>().clone();
335        let mut fonts = fonts.borrow_mut();
336
337        let text_len = text.len();
338        fonts.get_or_layout(BorrowedLayoutParams {
339            text,
340            spans: &[Span {
341                style: Style {
342                    font_family_id: self.text_style.font_family.to_font_family_id(),
343                    font_size_in_pts: self.text_style.font_size,
344                    color: None,
345                },
346                len: text_len,
347            }],
348            options: LayoutOptions {
349                first_row_indent_in_lpxs,
350                first_row_min_line_spacing_below_in_lpxs,
351                max_width_in_lpxs,
352                wrap,
353                align: align.x as f32,
354                line_spacing_scale: self.text_style.line_spacing as f32,
355            },
356        })
357    }
358
359    fn draw_text(&mut self, cx: &mut Cx2d, origin_in_lpxs: Point<f32>, text: &LaidoutText) {
360        self.update_draw_vars(cx);
361        let Some(mut instances) = cx.begin_many_aligned_instances(&self.draw_vars) else {
362            return;
363        };
364        self.glyph_depth = self.draw_depth;
365        for row in &text.rows {
366            self.draw_row(
367                cx,
368                origin_in_lpxs + Size::from(row.origin_in_lpxs) * self.font_scale,
369                row,
370                &mut instances.instances,
371            );
372        }
373        let area = cx.end_many_instances(instances);
374        self.draw_vars.area = cx.update_area_refs(self.draw_vars.area, area);
375    }
376
377    fn update_draw_vars(&mut self, cx: &mut Cx2d) {
378        let fonts = cx.fonts.borrow();
379        let rasterizer = fonts.rasterizer().borrow();
380        let sdfer_settings = rasterizer.sdfer().settings();
381        self.draw_vars.user_uniforms[0] = sdfer_settings.radius;
382        self.draw_vars.user_uniforms[1] = sdfer_settings.cutoff;
383        let grayscale_atlas_size = rasterizer.grayscale_atlas().size();
384        self.draw_vars.user_uniforms[2] = grayscale_atlas_size.width as f32;
385        self.draw_vars.user_uniforms[3] = grayscale_atlas_size.height as f32;
386        let color_atlas_size = rasterizer.color_atlas().size();
387        self.draw_vars.user_uniforms[4] = color_atlas_size.width as f32;
388        self.draw_vars.user_uniforms[5] = color_atlas_size.height as f32;
389        self.draw_vars.texture_slots[0] = Some(fonts.grayscale_texture().clone());
390        self.draw_vars.texture_slots[1] = Some(fonts.color_texture().clone());
391    }
392
393    fn draw_row(
394        &mut self,
395        cx: &mut Cx2d,
396        origin_in_lpxs: Point<f32>,
397        row: &LaidoutRow,
398        out_instances: &mut Vec<f32>,
399    ) {
400        for glyph in &row.glyphs {
401            self.draw_glyph(
402                cx,
403                origin_in_lpxs + Size::from(glyph.origin_in_lpxs) * self.font_scale,
404                glyph,
405                out_instances,
406            );
407        }
408
409        let width_in_lpxs = row.width_in_lpxs * self.font_scale;
410        if self.debug {
411            let mut area = Area::Empty;
412            cx.add_aligned_rect_area(
413                &mut area,
414                makepad_platform::rect(
415                    origin_in_lpxs.x as f64,
416                    (origin_in_lpxs.y - row.ascender_in_lpxs * self.font_scale) as f64,
417                    width_in_lpxs as f64,
418                    1.0,
419                ),
420            );
421            cx.cx
422                .debug
423                .area(area, makepad_platform::vec4(1.0, 0.0, 0.0, 1.0));
424            let mut area = Area::Empty;
425            cx.add_aligned_rect_area(
426                &mut area,
427                makepad_platform::rect(
428                    origin_in_lpxs.x as f64,
429                    origin_in_lpxs.y as f64,
430                    width_in_lpxs as f64,
431                    1.0,
432                ),
433            );
434            cx.cx
435                .debug
436                .area(area, makepad_platform::vec4(0.0, 1.0, 0.0, 1.0));
437            let mut area = Area::Empty;
438            cx.add_aligned_rect_area(
439                &mut area,
440                makepad_platform::rect(
441                    origin_in_lpxs.x as f64,
442                    (origin_in_lpxs.y - row.descender_in_lpxs * self.font_scale) as f64,
443                    width_in_lpxs as f64,
444                    1.0,
445                ),
446            );
447            cx.cx
448                .debug
449                .area(area, makepad_platform::vec4(0.0, 0.0, 1.0, 1.0));
450        }
451    }
452
453    fn draw_glyph(
454        &mut self,
455        cx: &mut Cx2d,
456        origin_in_lpxs: Point<f32>,
457        glyph: &LaidoutGlyph,
458        output: &mut Vec<f32>,
459    ) {
460        use crate::text::geom::Point;
461        let font_size_in_dpxs = glyph.font_size_in_lpxs * cx.current_dpi_factor() as f32;
462        if let Some(rasterized_glyph) = glyph.rasterize(font_size_in_dpxs) {
463            self.draw_rasterized_glyph(
464                Point::new(
465                    origin_in_lpxs.x + glyph.offset_in_lpxs() * self.font_scale,
466                    origin_in_lpxs.y,
467                ),
468                glyph.font_size_in_lpxs,
469                glyph.color,
470                rasterized_glyph,
471                output,
472            );
473        }
474    }
475
476    fn draw_rasterized_glyph(
477        &mut self,
478        origin_in_lpxs: Point<f32>,
479        font_size_in_lpxs: f32,
480        color: Option<Color>,
481        glyph: RasterizedGlyph,
482        output: &mut Vec<f32>,
483    ) {
484        fn tex_coord(point: Point<usize>, size: Size<usize>) -> Point<f32> {
485            Point::new(
486                point.x as f32 / size.width as f32,
487                point.y as f32 / size.height as f32,
488            )
489        }
490
491        let texture_index = match glyph.atlas_kind {
492            AtlasKind::Grayscale => 0.0,
493            AtlasKind::Color => 1.0,
494        };
495
496        let atlas_image_bounds = glyph.atlas_image_bounds;
497        let atlas_size = glyph.atlas_size;
498        let t_min = tex_coord(glyph.atlas_image_bounds.min(), atlas_size);
499        let t_max = tex_coord(glyph.atlas_image_bounds.max(), atlas_size);
500
501        let atlas_image_padding = glyph.atlas_image_padding;
502        let atlas_image_size = atlas_image_bounds.size;
503        let origin_in_dpxs = glyph.origin_in_dpxs;
504        let bounds_in_dpxs = Rect::new(
505            Point::new(
506                origin_in_dpxs.x - atlas_image_padding as f32,
507                -origin_in_dpxs.y - atlas_image_size.height as f32 + (atlas_image_padding as f32),
508            ),
509            Size::new(atlas_image_size.width as f32, atlas_image_size.height as f32),
510        );
511        let bounds_in_lpxs = bounds_in_dpxs.apply_transform(
512            Transform::from_scale_uniform(font_size_in_lpxs / glyph.dpxs_per_em * self.font_scale)
513                .translate(origin_in_lpxs.x, origin_in_lpxs.y),
514        );
515
516        self.rect_pos = vec2(bounds_in_lpxs.origin.x, bounds_in_lpxs.origin.y) + vec2(0.0,self.temp_y_shift* font_size_in_lpxs);
517        self.rect_size = vec2(bounds_in_lpxs.size.width, bounds_in_lpxs.size.height);
518        if let Some(color) = color {
519            self.color = vec4(
520                color.r as f32,
521                color.g as f32,
522                color.b as f32,
523                color.a as f32,
524            ) / 255.0;
525        }
526        self.texture_index = texture_index;
527        self.t_min = vec2(t_min.x, t_min.y);
528        self.t_max = vec2(t_max.x, t_max.y);
529
530        output.extend_from_slice(self.draw_vars.as_slice());
531        self.glyph_depth += 0.000001;
532    }
533}
534
535#[derive(Debug, Clone, Live, LiveHook, LiveRegister)]
536#[live_ignore]
537pub struct TextStyle {
538    #[live]
539    pub font_family: FontFamily,
540    #[live(10.0)]
541    pub font_size: f32,
542    #[live(1.0)]
543    pub line_spacing: f32,
544}
545
546#[derive(Debug, Clone, Live, LiveRegister, PartialEq)]
547pub struct FontFamily {
548    #[rust]
549    id: LiveId,
550}
551
552impl FontFamily {
553    fn to_font_family_id(&self) -> FontFamilyId {
554        (self.id.0).into()
555    }
556}
557
558impl LiveHook for FontFamily {
559    fn skip_apply(
560        &mut self,
561        cx: &mut Cx,
562        _apply: &mut Apply,
563        index: usize,
564        nodes: &[LiveNode],
565    ) -> Option<usize> {
566        CxDraw::lazy_construct_fonts(cx);
567        let fonts = cx.get_global::<Rc<RefCell<Fonts>>>().clone();
568        let mut fonts = fonts.borrow_mut();
569
570        let mut id = LiveId::seeded();
571        let mut next_child_index = Some(index + 1);
572        while let Some(child_index) = next_child_index {
573            if let LiveValue::Font(font) = &nodes[child_index].value {
574                id = id.id_append(font.to_live_id());
575            }
576            next_child_index = nodes.next_child(child_index);
577        }
578        self.id = id;
579
580        let font_family_id = self.to_font_family_id();
581        if !fonts.is_font_family_known(font_family_id) {
582            let mut font_ids = Vec::new();
583            let mut next_child_index = Some(index + 1);
584            while let Some(child_index) = next_child_index {
585                if let LiveValue::Font(font) = &nodes[child_index].value {
586                    let font_id: FontId = (font.to_live_id().0).into();
587                    if !fonts.is_font_known(font_id) {
588                        // alright so if we have a multipart font we have to combine it here
589                        let data = if font.paths.len()>1{
590                            // combine them. TODO do this better.
591                            let mut data = Vec::new();
592                            for path in &*font.paths{
593                                let dep = cx.get_dependency(path).unwrap();
594                                data.extend(&*dep);
595                            }
596                            Rc::new(data)
597                        }
598                        else{
599                            cx.get_dependency(font.paths[0].as_str()).unwrap().into()
600                        };
601                        fonts.define_font(
602                            font_id,
603                            FontDefinition {
604                                data,
605                                index: 0,
606                                ascender_fudge_in_ems: font.ascender_fudge,
607                                descender_fudge_in_ems: font.descender_fudge,
608                            },
609                        );
610                    }
611                    font_ids.push(font_id);
612                }
613                next_child_index = nodes.next_child(child_index);
614            }
615            fonts.define_font_family(font_family_id, FontFamilyDefinition { font_ids });
616        }
617
618        Some(nodes.skip_node(index))
619    }
620}