ascending_graphics/font/
text.rs

1use std::cell::RefCell;
2
3use crate::{
4    Bounds, CameraView, Color, DrawOrder, GpuRenderer, GraphicsError, Index,
5    OrderedIndex, TextAtlas, TextVertex, Vec2, Vec3,
6};
7use cosmic_text::{
8    Align, Attrs, Buffer, Cursor, FontSystem, Metrics, SwashCache,
9    SwashContent, Wrap,
10};
11#[cfg(feature = "rayon")]
12use rayon::prelude::*;
13
14/// [`Text`] Option Handler for [`Text::measure_string`].
15///
16#[derive(Debug)]
17pub struct TextOptions {
18    pub shaping: cosmic_text::Shaping,
19    pub metrics: Option<Metrics>,
20    pub buffer_width: Option<f32>,
21    pub buffer_height: Option<f32>,
22    pub scale: f32,
23    pub wrap: Wrap,
24}
25
26impl Default for TextOptions {
27    fn default() -> Self {
28        Self {
29            shaping: cosmic_text::Shaping::Advanced,
30            metrics: Some(Metrics::new(16.0, 16.0).scale(1.0)),
31            buffer_width: None,
32            buffer_height: None,
33            scale: 1.0,
34            wrap: Wrap::None,
35        }
36    }
37}
38
39/// [`Text`] visible width and lines details
40///
41#[derive(Debug)]
42pub struct VisibleDetails {
43    /// Visible Width the Text can render as.
44    pub width: f32,
45    /// Visible Lines the Text can Render too.
46    pub lines: usize,
47    /// Max Height each line is rendered as.
48    pub line_height: f32,
49}
50
51/// Text to render to screen.
52///
53#[derive(Debug)]
54pub struct Text {
55    /// Cosmic Text [`Buffer`].
56    pub buffer: Buffer,
57    /// Position on the Screen.
58    pub pos: Vec3,
59    /// Width and Height of the Text Area.
60    pub size: Vec2,
61    /// Scale of the Text.
62    pub scale: f32,
63    /// Default Text Font Color.
64    pub default_color: Color,
65    /// Clip Bounds of Text.
66    pub bounds: Bounds,
67    /// Instance Buffer Store Index of Text Buffer.
68    pub store_id: Index,
69    /// the draw order of the Text. created/updated when update is called.
70    pub order: DrawOrder,
71    /// Cursor the shaping is set too.
72    pub cursor: Cursor,
73    /// line the shaping is set too.
74    pub line: usize,
75    /// set scroll to render too.
76    pub scroll: cosmic_text::Scroll,
77    /// Word Wrap Type. Default is Wrap::Word.
78    pub wrap: Wrap,
79    /// [`CameraView`] used to render with.
80    pub camera_view: CameraView,
81    /// If anything got updated we need to update the buffers too.
82    pub changed: bool,
83}
84
85thread_local! {
86    static GLYPH_VERTICES: RefCell<Vec<TextVertex>> = RefCell::new(Vec::with_capacity(1024));
87}
88
89impl Text {
90    /// Updates the [`Text`]'s Buffers to prepare them for rendering.
91    ///
92    pub fn create_quad(
93        &mut self,
94        cache: &mut SwashCache,
95        atlas: &mut TextAtlas,
96        renderer: &mut GpuRenderer,
97    ) -> Result<(), GraphicsError> {
98        #[cfg(feature = "rayon")]
99        let count: usize = self
100            .buffer
101            .lines
102            .par_iter()
103            .map(|line| line.text().len())
104            .sum();
105
106        #[cfg(not(feature = "rayon"))]
107        let count: usize =
108            self.buffer.lines.iter().map(|line| line.text().len()).sum();
109
110        let mut is_alpha = false;
111        let screensize = renderer.size();
112        let bounds_min_x = self.bounds.left.max(0.0);
113        let bounds_min_y = self.bounds.bottom.max(0.0);
114        let bounds_max_x = self.bounds.right.min(screensize.width);
115        let bounds_max_y = self.bounds.top.min(screensize.height);
116
117        GLYPH_VERTICES.with_borrow_mut(|vertices| {
118            vertices.clear();
119
120            if vertices.capacity() < count {
121                vertices.reserve(count);
122            }
123        });
124
125        // From Glyphon good optimization.
126        let is_run_visible = |run: &cosmic_text::LayoutRun| {
127            let start_y = self.pos.y + self.size.y - run.line_top;
128            let end_y = self.pos.y + self.size.y
129                - run.line_top
130                - (run.line_height * 0.5);
131
132            start_y <= bounds_max_y + (run.line_height * 0.5)
133                && bounds_min_y <= end_y
134        };
135
136        self.buffer
137            .layout_runs()
138            .skip_while(|run| !is_run_visible(run))
139            .take_while(is_run_visible)
140            .for_each(|run| {
141                run.glyphs.iter().for_each(|glyph| {
142                    let physical_glyph = glyph.physical(
143                        (self.pos.x, self.pos.y + self.size.y),
144                        self.scale,
145                    );
146
147                    let (allocation, is_color) =
148                        if let Some((allocation, is_color)) =
149                            atlas.get_by_key(&physical_glyph.cache_key)
150                        {
151                            (allocation, is_color)
152                        } else {
153                            let image = cache
154                                .get_image_uncached(
155                                    &mut renderer.font_sys,
156                                    physical_glyph.cache_key,
157                                )
158                                .unwrap();
159
160                            if image.placement.width > 0
161                                && image.placement.height > 0
162                            {
163                                atlas
164                                    .upload_with_alloc(
165                                        renderer,
166                                        image.content == SwashContent::Color,
167                                        physical_glyph.cache_key,
168                                        &image,
169                                    )
170                                    .unwrap()
171                            } else {
172                                return;
173                            }
174                        };
175
176                    let position = allocation.data;
177                    let (u, v, width, height) = allocation.rect();
178                    let (mut u, mut v, mut width, mut height) =
179                        (u as f32, v as f32, width as f32, height as f32);
180                    let (mut x, mut y) = (
181                        physical_glyph.x as f32 + position.x,
182                        physical_glyph.y as f32
183                            + ((position.y - height)
184                                - (run.line_y * self.scale).round()),
185                    );
186                    let color = if is_color {
187                        Color::rgba(255, 255, 255, 255)
188                    } else {
189                        glyph.color_opt.unwrap_or(self.default_color)
190                    };
191
192                    if color.a() < 255 {
193                        is_alpha = true;
194                    }
195
196                    // Starts beyond right edge or ends beyond left edge
197                    let max_x = x + width;
198                    if x > bounds_max_x || max_x < bounds_min_x {
199                        return;
200                    }
201
202                    // Clip left edge
203                    if x < bounds_min_x {
204                        let right_shift = bounds_min_x - x;
205
206                        x = bounds_min_x;
207                        width = max_x - bounds_min_x;
208                        u += right_shift;
209                    }
210
211                    // Clip right edge
212                    if x + width > bounds_max_x {
213                        width = bounds_max_x - x;
214                    }
215
216                    // Clip top edge
217                    if y < bounds_min_y {
218                        height -= bounds_min_y - y;
219                        y = bounds_min_y;
220                    }
221
222                    // Clip top edge
223                    if y + height > bounds_max_y {
224                        let bottom_shift = (y + height) - bounds_max_y;
225
226                        v += bottom_shift;
227                        height -= bottom_shift;
228                    }
229
230                    GLYPH_VERTICES.with_borrow_mut(|vertices| {
231                        vertices.push(TextVertex {
232                            pos: [x, y, self.pos.z],
233                            size: [width, height],
234                            tex_coord: [u, v],
235                            layer: allocation.layer as u32,
236                            color: color.0,
237                            camera_view: self.camera_view as u32,
238                            is_color: is_color as u32,
239                        })
240                    });
241                });
242            });
243
244        if let Some(store) = renderer.get_buffer_mut(self.store_id) {
245            GLYPH_VERTICES.with_borrow(|vertices| {
246                let bytes: &[u8] = bytemuck::cast_slice(vertices);
247
248                if bytes.len() != store.store.len() {
249                    store.store.resize_with(bytes.len(), || 0);
250                }
251
252                store.store.copy_from_slice(bytes);
253                store.changed = true;
254            });
255        }
256
257        self.order.alpha = is_alpha;
258
259        Ok(())
260    }
261
262    /// Creates a new [`Text`].
263    ///
264    /// order_layer: Rendering Layer of the Text used in DrawOrder.
265    pub fn new(
266        renderer: &mut GpuRenderer,
267        metrics: Option<Metrics>,
268        pos: Vec3,
269        size: Vec2,
270        scale: f32,
271        order_layer: u32,
272    ) -> Self {
273        let text_starter_size =
274            bytemuck::bytes_of(&TextVertex::default()).len() * 64;
275
276        Self {
277            buffer: Buffer::new(
278                &mut renderer.font_sys,
279                metrics.unwrap_or(Metrics::new(16.0, 16.0).scale(scale)),
280            ),
281            pos,
282            size,
283            bounds: Bounds::default(),
284            store_id: renderer.new_buffer(text_starter_size, 0),
285            order: DrawOrder::new(false, pos, order_layer),
286            changed: true,
287            default_color: Color::rgba(0, 0, 0, 255),
288            camera_view: CameraView::default(),
289            cursor: Cursor::default(),
290            wrap: Wrap::Word,
291            line: 0,
292            scroll: cosmic_text::Scroll::default(),
293            scale,
294        }
295    }
296
297    /// Sets the [`Text`]'s [`CameraView`] for rendering.
298    ///
299    pub fn set_camera_view(&mut self, camera_view: CameraView) {
300        self.camera_view = camera_view;
301
302        self.changed = true;
303    }
304
305    /// Unloads the [`Text`] from the Instance Buffers Store and its outline from the VBO Store.
306    ///
307    pub fn unload(self, renderer: &mut GpuRenderer) {
308        renderer.remove_buffer(self.store_id);
309    }
310
311    /// Updates the [`Text`]'s order to overide the last set position.
312    /// Use this after calls to set_position to set it to a specific rendering order.
313    ///
314    pub fn set_order_override(&mut self, order_override: Vec3) -> &mut Self {
315        self.order.set_pos(order_override);
316        self
317    }
318
319    /// Updates the [`Text`]'s orders Render Layer.
320    ///
321    pub fn set_order_layer(&mut self, order_layer: u32) -> &mut Self {
322        self.order.order_layer = order_layer;
323        self
324    }
325
326    /// Resets the [`Text`] to contain the new text only.
327    ///
328    pub fn set_text(
329        &mut self,
330        renderer: &mut GpuRenderer,
331        text: &str,
332        attrs: &Attrs,
333        shaping: cosmic_text::Shaping,
334        alignment: Option<Align>,
335    ) -> &mut Self {
336        self.buffer.set_text(
337            &mut renderer.font_sys,
338            text,
339            attrs,
340            shaping,
341            alignment,
342        );
343        self.changed = true;
344        self
345    }
346
347    /// Resets the [`Text`] to contain the new span of text only.
348    ///
349    pub fn set_rich_text<'r, 's, I>(
350        &mut self,
351        renderer: &mut GpuRenderer,
352        spans: I,
353        default_attr: &Attrs,
354        shaping: cosmic_text::Shaping,
355        alignment: Option<Align>,
356    ) -> &mut Self
357    where
358        I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
359    {
360        self.buffer.set_rich_text(
361            &mut renderer.font_sys,
362            spans,
363            default_attr,
364            shaping,
365            alignment,
366        );
367        self.changed = true;
368        self
369    }
370
371    /// For more advanced shaping and usage. Use [`Text::set_change`] to set if you need it to make changes or not.
372    /// This will not set the change to true. when changes are made you must set changed to true.
373    ///
374    pub fn get_text_buffer(&mut self) -> &mut Buffer {
375        &mut self.buffer
376    }
377
378    /// cursor shaping sets the [`Text`]'s location to shape from and sets the buffers scroll.
379    ///
380    pub fn shape_until_cursor(
381        &mut self,
382        renderer: &mut GpuRenderer,
383        cursor: Cursor,
384    ) -> &mut Self {
385        if self.cursor != cursor || self.changed {
386            self.cursor = cursor;
387            self.line = 0;
388            self.changed = true;
389            self.buffer.shape_until_cursor(
390                &mut renderer.font_sys,
391                cursor,
392                false,
393            );
394            self.scroll = self.buffer.scroll();
395        }
396
397        self
398    }
399
400    /// cursor shaping sets the [`Text`]'s location to shape from.
401    ///
402    pub fn shape_until(
403        &mut self,
404        renderer: &mut GpuRenderer,
405        line: usize,
406    ) -> &mut Self {
407        if self.line != line || self.changed {
408            self.cursor = Cursor::new(line, 0);
409            self.line = line;
410            self.changed = true;
411            self.buffer.shape_until_cursor(
412                &mut renderer.font_sys,
413                self.cursor,
414                false,
415            );
416        }
417        self
418    }
419
420    /// scroll shaping sets the [`Text`]'s location to shape from.
421    ///
422    pub fn shape_until_scroll(
423        &mut self,
424        renderer: &mut GpuRenderer,
425    ) -> &mut Self {
426        if self.changed {
427            self.buffer
428                .shape_until_scroll(&mut renderer.font_sys, false);
429        }
430
431        self
432    }
433
434    /// sets scroll for shaping and sets the [`Text`]'s location to shape from.
435    ///
436    pub fn set_scroll(
437        &mut self,
438        renderer: &mut GpuRenderer,
439        scroll: cosmic_text::Scroll,
440    ) -> &mut Self {
441        if self.scroll != scroll {
442            self.scroll = scroll;
443            self.buffer.set_scroll(scroll);
444            self.changed = true;
445            self.buffer
446                .shape_until_scroll(&mut renderer.font_sys, false);
447        }
448
449        self
450    }
451
452    /// Sets the [`Text`] as changed for updating.
453    ///
454    pub fn set_change(&mut self, changed: bool) -> &mut Self {
455        self.changed = changed;
456        self
457    }
458
459    /// Sets the [`Text`] wrapping.
460    ///
461    pub fn set_wrap(
462        &mut self,
463        renderer: &mut GpuRenderer,
464        wrap: Wrap,
465    ) -> &mut Self {
466        if self.wrap != wrap {
467            self.wrap = wrap;
468            self.buffer.set_wrap(&mut renderer.font_sys, wrap);
469            self.changed = true;
470        }
471
472        self
473    }
474
475    /// Sets the [`Text`]'s clipping bounds.
476    ///
477    pub fn set_bounds(&mut self, bounds: Bounds) -> &mut Self {
478        self.bounds = bounds;
479        self.changed = true;
480        self
481    }
482
483    /// Sets the [`Text`]'s screen Posaition.
484    ///
485    pub fn set_pos(&mut self, pos: Vec3) -> &mut Self {
486        self.pos = pos;
487        self.order.set_pos(pos);
488        self.changed = true;
489        self
490    }
491
492    /// Sets the [`Text`]'s default color.
493    ///
494    pub fn set_default_color(&mut self, color: Color) -> &mut Self {
495        self.default_color = color;
496        self.changed = true;
497        self
498    }
499
500    /// Sets the [`Text`]'s cosmic text buffer size.
501    ///
502    pub fn set_buffer_size(
503        &mut self,
504        renderer: &mut GpuRenderer,
505        width: Option<f32>,
506        height: Option<f32>,
507    ) -> &mut Self {
508        self.buffer.set_size(&mut renderer.font_sys, width, height);
509        self.changed = true;
510        self
511    }
512
513    /// clears the [`Text`] buffer.
514    ///
515    pub fn clear(&mut self, renderer: &mut GpuRenderer) -> &mut Self {
516        self.buffer.set_text(
517            &mut renderer.font_sys,
518            "",
519            &cosmic_text::Attrs::new(),
520            cosmic_text::Shaping::Basic,
521            None,
522        );
523        self.changed = true;
524        self
525    }
526
527    // Used to check and update the vertex array.
528    /// Returns a [`OrderedIndex`] used in Rendering.
529    ///
530    pub fn update(
531        &mut self,
532        cache: &mut SwashCache,
533        atlas: &mut TextAtlas,
534        renderer: &mut GpuRenderer,
535    ) -> Result<OrderedIndex, GraphicsError> {
536        if self.changed {
537            self.create_quad(cache, atlas, renderer)?;
538            self.changed = false;
539        }
540
541        Ok(OrderedIndex::new(self.order, self.store_id, 0))
542    }
543
544    /// Checks if mouse_pos is within the [`Text`]'s location.
545    ///
546    pub fn check_mouse_bounds(&self, mouse_pos: Vec2) -> bool {
547        mouse_pos[0] > self.pos.x
548            && mouse_pos[0] < self.pos.x + self.size.x
549            && mouse_pos[1] > self.pos.y
550            && mouse_pos[1] < self.pos.y + self.size.y
551    }
552
553    /// Returns Visible Width and Line details of the Rendered [`Text`].
554    pub fn visible_details(&self) -> VisibleDetails {
555        #[cfg(not(feature = "rayon"))]
556        let (width, lines) = self.buffer.layout_runs().fold(
557            (0.0, 0usize),
558            |(width, total_lines), run| {
559                (run.line_w.max(width), total_lines + 1)
560            },
561        );
562
563        #[cfg(feature = "rayon")]
564        let (width, lines) = self
565            .buffer
566            .layout_runs()
567            .par_bridge()
568            .fold(
569                || (0.0, 0usize),
570                |(width, total_lines), run| {
571                    (run.line_w.max(width), total_lines + 1)
572                },
573            )
574            .reduce(
575                || (0.0, 0usize),
576                |(w1, t1), (w2, t2)| (w1.max(w2), t1 + t2),
577            );
578
579        VisibleDetails {
580            line_height: self.buffer.metrics().line_height,
581            lines,
582            width,
583        }
584    }
585
586    /// measure's the [`Text`]'s Rendering Size.
587    ///
588    pub fn measure(&self) -> Vec2 {
589        let details = self.visible_details();
590
591        let (max_width, max_height) = self.buffer.size();
592        let height = details.lines as f32 * details.line_height;
593
594        Vec2::new(
595            details
596                .width
597                .min(max_width.unwrap_or(0.0).max(details.width)),
598            height.min(max_height.unwrap_or(0.0).max(height)),
599        )
600    }
601
602    /// Allows measuring the String for how big it will be when Rendering.
603    /// This will not create any buffers in the rendering system.
604    ///
605    pub fn measure_string(
606        font_system: &mut FontSystem,
607        text: &str,
608        attrs: &Attrs,
609        options: TextOptions,
610        alignment: Option<Align>,
611    ) -> Vec2 {
612        let mut buffer = Buffer::new(
613            font_system,
614            options
615                .metrics
616                .unwrap_or(Metrics::new(16.0, 16.0).scale(options.scale)),
617        );
618
619        buffer.set_wrap(font_system, options.wrap);
620        buffer.set_size(
621            font_system,
622            options.buffer_width,
623            options.buffer_height,
624        );
625        buffer.set_text(font_system, text, attrs, options.shaping, alignment);
626
627        #[cfg(not(feature = "rayon"))]
628        let (width, total_lines) = buffer.layout_runs().fold(
629            (0.0, 0usize),
630            |(width, total_lines), run| {
631                (run.line_w.max(width), total_lines + 1)
632            },
633        );
634
635        #[cfg(feature = "rayon")]
636        let (width, total_lines) = buffer
637            .layout_runs()
638            .par_bridge()
639            .fold(
640                || (0.0, 0usize),
641                |(width, total_lines), run| {
642                    (run.line_w.max(width), total_lines + 1)
643                },
644            )
645            .reduce(
646                || (0.0, 0usize),
647                |(w1, t1), (w2, t2)| (w1.max(w2), t1 + t2),
648            );
649
650        let (max_width, max_height) = buffer.size();
651        let height = total_lines as f32 * buffer.metrics().line_height;
652
653        Vec2::new(
654            width.min(max_width.unwrap_or(0.0).max(width)),
655            height.min(max_height.unwrap_or(0.0).max(height)),
656        )
657    }
658
659    /// Allows measuring the String character's Glyph and returning a Vec of their Sizes per character.
660    /// This will not create any buffers in the rendering system.
661    ///
662    pub fn measure_glyphs(
663        font_system: &mut FontSystem,
664        text: &str,
665        attrs: &Attrs,
666        options: TextOptions,
667        alignment: Option<Align>,
668    ) -> Vec<Vec2> {
669        let mut buffer = Buffer::new(
670            font_system,
671            options
672                .metrics
673                .unwrap_or(Metrics::new(16.0, 16.0).scale(options.scale)),
674        );
675
676        buffer.set_wrap(font_system, options.wrap);
677        buffer.set_size(
678            font_system,
679            options.buffer_width,
680            options.buffer_height,
681        );
682
683        text.char_indices()
684            .map(|(_position, ch)| {
685                //let mut buffer = buffer.clone();
686
687                let n = ch.len_utf8();
688                let mut buf = vec![0; n];
689                let u = ch.encode_utf8(&mut buf);
690
691                buffer.set_text(
692                    font_system,
693                    u,
694                    attrs,
695                    options.shaping,
696                    alignment,
697                );
698
699                #[cfg(not(feature = "rayon"))]
700                let (width, total_lines) = buffer.layout_runs().fold(
701                    (0.0, 0usize),
702                    |(width, total_lines), run| {
703                        (run.line_w.max(width), total_lines + 1)
704                    },
705                );
706
707                #[cfg(feature = "rayon")]
708                let (width, total_lines) = buffer
709                    .layout_runs()
710                    .par_bridge()
711                    .fold(
712                        || (0.0, 0usize),
713                        |(width, total_lines), run| {
714                            (run.line_w.max(width), total_lines + 1)
715                        },
716                    )
717                    .reduce(
718                        || (0.0, 0usize),
719                        |(w1, t1), (w2, t2)| (w1.max(w2), t1 + t2),
720                    );
721
722                let (max_width, max_height) = buffer.size();
723                let height = total_lines as f32 * buffer.metrics().line_height;
724
725                Vec2::new(
726                    width.min(max_width.unwrap_or(0.0).max(width)),
727                    height.min(max_height.unwrap_or(0.0).max(height)),
728                )
729            })
730            .collect()
731    }
732}