fyrox_ui/
formatted_text.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    brush::Brush,
23    core::{
24        algebra::Vector2, color::Color, math::Rect, reflect::prelude::*, uuid_provider,
25        variable::InheritableVariable, visitor::prelude::*,
26    },
27    font::{Font, FontGlyph, FontResource},
28    style::StyledProperty,
29    HorizontalAlignment, VerticalAlignment,
30};
31use std::ops::Range;
32use strum_macros::{AsRefStr, EnumString, VariantNames};
33
34mod textwrapper;
35use textwrapper::*;
36
37/// Defines a position in the text. It is just a coordinates of a character in text.
38#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, Visit, Reflect)]
39pub struct Position {
40    /// Line index.
41    pub line: usize,
42
43    /// Offset from the beginning of the line.
44    pub offset: usize,
45}
46
47#[derive(Debug, Clone, Default)]
48pub struct TextGlyph {
49    pub bounds: Rect<f32>,
50    pub tex_coords: [Vector2<f32>; 4],
51    pub atlas_page_index: usize,
52}
53
54#[derive(Copy, Clone, Debug, Default)]
55pub struct TextLine {
56    /// Index of starting symbol in text array.
57    pub begin: usize,
58    /// Index of ending symbol in text array.
59    pub end: usize,
60    /// Total width of line.
61    pub width: f32,
62    /// Total height of line. Usually just ascender of a font.
63    pub height: f32,
64    /// Local horizontal position of line.
65    pub x_offset: f32,
66    /// Local vertical position of line.
67    pub y_offset: f32,
68}
69
70impl TextLine {
71    fn new() -> TextLine {
72        TextLine {
73            begin: 0,
74            end: 0,
75            width: 0.0,
76            height: 0.0,
77            x_offset: 0.0,
78            y_offset: 0.0,
79        }
80    }
81
82    pub fn len(&self) -> usize {
83        self.end - self.begin
84    }
85
86    pub fn is_empty(&self) -> bool {
87        self.end == self.begin
88    }
89
90    pub fn y_distance(&self, y: f32) -> f32 {
91        (self.y_offset + self.height / 2.0 - y).abs()
92    }
93}
94
95/// Wrapping mode for formatted text.
96#[derive(
97    Default,
98    Copy,
99    Clone,
100    PartialOrd,
101    PartialEq,
102    Hash,
103    Debug,
104    Eq,
105    Visit,
106    Reflect,
107    AsRefStr,
108    EnumString,
109    VariantNames,
110)]
111pub enum WrapMode {
112    /// No wrapping needed.
113    #[default]
114    NoWrap,
115
116    /// Letter-based wrapping.
117    Letter,
118
119    /// Word-based wrapping.
120    Word,
121}
122
123uuid_provider!(WrapMode = "f1290ceb-3fee-461f-a1e9-f9450bd06805");
124
125struct GlyphMetrics<'a> {
126    font: &'a mut Font,
127    size: f32,
128}
129
130impl GlyphMetrics<'_> {
131    fn ascender(&self) -> f32 {
132        self.font.ascender(self.size)
133    }
134
135    fn descender(&self) -> f32 {
136        self.font.descender(self.size)
137    }
138
139    fn newline_advance(&self) -> f32 {
140        self.size / 2.0
141    }
142
143    fn horizontal_kerning(&self, left: char, right: char) -> Option<f32> {
144        self.font.horizontal_kerning(self.size, left, right)
145    }
146
147    fn advance(&mut self, c: char) -> f32 {
148        match c {
149            '\n' => self.newline_advance(),
150            _ => self.font.glyph_advance(c, self.size),
151        }
152    }
153
154    fn glyph(&mut self, c: char, super_sampling_scale: f32) -> Option<&FontGlyph> {
155        self.font.glyph(c, self.size * super_sampling_scale)
156    }
157}
158
159fn build_glyph(
160    metrics: &mut GlyphMetrics,
161    mut x: f32,
162    mut y: f32,
163    character: char,
164    prev_character: Option<char>,
165    super_sampling_scale: f32,
166) -> (TextGlyph, f32) {
167    let ascender = metrics.ascender();
168    let font_size = metrics.size;
169
170    x = x.floor();
171    y = y.floor();
172
173    // Request larger glyph with super sampling scaling.
174    match metrics.glyph(character, super_sampling_scale) {
175        Some(glyph) => {
176            // Discard super sampling scaling in the produced glyphs, because we're interested only
177            // in larger texture size, not the "physical" size.
178            let k = 1.0 / super_sampling_scale;
179            // Insert glyph
180            let rect = Rect::new(
181                x + glyph.bitmap_left * k,
182                y + ascender.floor() - glyph.bitmap_top * k - (glyph.bitmap_height * k),
183                glyph.bitmap_width * k,
184                glyph.bitmap_height * k,
185            );
186            let text_glyph = TextGlyph {
187                bounds: rect,
188                tex_coords: glyph.tex_coords,
189                atlas_page_index: glyph.page_index,
190            };
191            let advance = glyph.advance
192                + prev_character
193                    .and_then(|prev| metrics.horizontal_kerning(prev, character))
194                    .unwrap_or_default();
195            (text_glyph, advance * k)
196        }
197        None => {
198            // Insert invalid symbol
199            let rect = Rect::new(x, y + ascender, font_size, font_size);
200            let text_glyph = TextGlyph {
201                bounds: rect,
202                tex_coords: [Vector2::default(); 4],
203                atlas_page_index: 0,
204            };
205            (text_glyph, rect.w())
206        }
207    }
208}
209
210struct WrapSink<'a> {
211    lines: &'a mut Vec<TextLine>,
212    max_width: f32,
213}
214
215impl LineSink for WrapSink<'_> {
216    fn push_line(&mut self, range: Range<usize>, width: f32) {
217        let mut line = TextLine::new();
218        line.begin = range.start;
219        line.end = range.end;
220        line.width = width;
221        self.lines.push(line);
222    }
223
224    fn max_width(&self) -> f32 {
225        self.max_width
226    }
227}
228
229#[derive(Default, Clone, Debug, Visit, Reflect)]
230pub struct FormattedText {
231    font: InheritableVariable<FontResource>,
232    text: InheritableVariable<Vec<char>>,
233    // Temporary buffer used to split text on lines. We need it to reduce memory allocations
234    // when we changing text too frequently, here we sacrifice some memory in order to get
235    // more performance.
236    #[reflect(hidden)]
237    #[visit(skip)]
238    lines: Vec<TextLine>,
239    // Final glyphs for draw buffer.
240    #[visit(skip)]
241    #[reflect(hidden)]
242    glyphs: Vec<TextGlyph>,
243    vertical_alignment: InheritableVariable<VerticalAlignment>,
244    horizontal_alignment: InheritableVariable<HorizontalAlignment>,
245    brush: InheritableVariable<Brush>,
246    #[visit(skip)]
247    #[reflect(hidden)]
248    constraint: Vector2<f32>,
249    wrap: InheritableVariable<WrapMode>,
250    mask_char: InheritableVariable<Option<char>>,
251    #[visit(skip)]
252    #[reflect(hidden)]
253    pub(crate) super_sampling_scale: f32,
254    #[visit(rename = "Height")]
255    font_size: InheritableVariable<StyledProperty<f32>>,
256    pub shadow: InheritableVariable<bool>,
257    pub shadow_brush: InheritableVariable<Brush>,
258    pub shadow_dilation: InheritableVariable<f32>,
259    pub shadow_offset: InheritableVariable<Vector2<f32>>,
260}
261
262impl FormattedText {
263    pub fn nearest_valid_position(&self, start: Position) -> Position {
264        if self.lines.is_empty() {
265            return Position::default();
266        }
267        let mut pos = start;
268        pos.line = usize::min(pos.line, self.lines.len() - 1);
269        pos.offset = usize::min(pos.offset, self.lines[pos.line].len());
270        pos
271    }
272    pub fn get_relative_position_x(&self, start: Position, offset: isize) -> Position {
273        if self.lines.is_empty() {
274            return Position::default();
275        }
276        let mut pos = self.nearest_valid_position(start);
277        let distance = offset.abs();
278        for _ in 0..distance {
279            if offset < 0 {
280                if pos.offset > 0 {
281                    pos.offset -= 1
282                } else if pos.line > 0 {
283                    pos.line -= 1;
284                    pos.offset = self.lines[pos.line].len().saturating_sub(1);
285                } else {
286                    pos.offset = 0;
287                    break;
288                }
289            } else {
290                let line = &self.lines[pos.line];
291                if pos.offset + 1 < line.len() {
292                    pos.offset += 1;
293                } else if pos.line + 1 < self.lines.len() {
294                    pos.line += 1;
295                    pos.offset = 0;
296                } else {
297                    pos.offset = line.len();
298                    break;
299                }
300            }
301        }
302        pos
303    }
304
305    pub fn get_relative_position_y(&self, start: Position, offset: isize) -> Position {
306        let mut pos = self.nearest_valid_position(start);
307        pos.line = pos.line.saturating_add_signed(offset);
308        self.nearest_valid_position(pos)
309    }
310
311    pub fn get_line_range(&self, line: usize) -> Range<Position> {
312        let length = self.lines.get(line).map(TextLine::len).unwrap_or(0);
313        Range {
314            start: Position { line, offset: 0 },
315            end: Position {
316                line,
317                offset: length,
318            },
319        }
320    }
321
322    pub fn iter_line_ranges_within(
323        &self,
324        range: Range<Position>,
325    ) -> impl Iterator<Item = Range<Position>> + '_ {
326        (range.start.line..=range.end.line).map(move |i| {
327            let r = self.get_line_range(i);
328            Range {
329                start: Position::max(range.start, r.start),
330                end: Position::min(range.end, r.end),
331            }
332        })
333    }
334
335    pub fn end_position(&self) -> Position {
336        match self.lines.iter().enumerate().last() {
337            Some((i, line)) => Position {
338                line: i,
339                offset: line.len(),
340            },
341            None => Position::default(),
342        }
343    }
344
345    fn position_to_char_index_internal(&self, position: Position, clamp: bool) -> Option<usize> {
346        self.lines.get(position.line).map(|line| {
347            line.begin
348                + position.offset.min(if clamp {
349                    line.len().saturating_sub(1)
350                } else {
351                    line.len()
352                })
353        })
354    }
355    pub fn position_range_to_char_index_range(&self, range: Range<Position>) -> Range<usize> {
356        let start = self
357            .position_to_char_index_unclamped(range.start)
358            .unwrap_or(0);
359        let end = self
360            .position_to_char_index_unclamped(range.end)
361            .unwrap_or(self.text.len());
362        start..end
363    }
364    /// Maps input [`Position`] to a linear position in character array.
365    /// The index returned is the index of the character after the position, which may be
366    /// out-of-bounds if thee position is at the end of the text.
367    /// You should check the index before trying to use it to fetch data from inner array of characters.
368    pub fn position_to_char_index_unclamped(&self, position: Position) -> Option<usize> {
369        self.position_to_char_index_internal(position, false)
370    }
371
372    /// Maps input [`Position`] to a linear position in character array.
373    /// The index returned is usually the index of the character after the position,
374    /// but if the position is at the end of a line then return the index of the character _before_ the position.
375    /// In other words, the last two positions of each line are mapped to the same character index.
376    /// Output index will always be valid for fetching, if the method returned `Some(index)`.
377    /// The index however cannot be used for text insertion, because it cannot point to a "place after last char".
378    pub fn position_to_char_index_clamped(&self, position: Position) -> Option<usize> {
379        self.position_to_char_index_internal(position, true)
380    }
381
382    /// Maps linear character index (as in string) to its actual location in the text.
383    pub fn char_index_to_position(&self, i: usize) -> Option<Position> {
384        self.lines
385            .iter()
386            .enumerate()
387            .find_map(|(line_index, line)| {
388                if (line.begin..line.end).contains(&i) {
389                    Some(Position {
390                        line: line_index,
391                        offset: i - line.begin,
392                    })
393                } else {
394                    None
395                }
396            })
397            .or(Some(self.end_position()))
398    }
399
400    pub fn position_to_local(&self, position: Position) -> Vector2<f32> {
401        let mut state = self.font.state();
402        let Some(font) = state.data() else {
403            return Default::default();
404        };
405        let mut metrics = GlyphMetrics {
406            font,
407            size: **self.font_size,
408        };
409        let mut caret_pos = Vector2::default();
410        let position = self.nearest_valid_position(position);
411
412        let line = self.lines[position.line];
413        let raw_text = self.get_raw_text();
414        caret_pos += Vector2::new(line.x_offset, line.y_offset);
415        for (offset, char_index) in (line.begin..line.end).enumerate() {
416            if offset >= position.offset {
417                break;
418            }
419            if let Some(advance) = raw_text.get(char_index).map(|c| metrics.advance(*c)) {
420                caret_pos.x += advance;
421            } else {
422                caret_pos.x += metrics.size;
423            }
424        }
425        caret_pos
426    }
427
428    pub fn local_to_position(&self, point: Vector2<f32>) -> Position {
429        let font_size = **self.font_size();
430        let font = self.get_font();
431        let mut state = font.state();
432        let Some(font) = state.data() else {
433            return Position::default();
434        };
435        let mut metrics = GlyphMetrics {
436            font,
437            size: font_size,
438        };
439        let y = point.y;
440
441        let Some(line_index) = self
442            .lines
443            .iter()
444            .enumerate()
445            .map(|(i, a)| (i, a.y_distance(y)))
446            .min_by(|a, b| f32::total_cmp(&a.1, &b.1))
447            .map(|(i, _)| i)
448        else {
449            return Position::default();
450        };
451        let line = self.lines[line_index];
452        let x = point.x - line.x_offset;
453        let mut glyph_x: f32 = 0.0;
454        let mut min_dist: f32 = x.abs();
455        let mut min_index: usize = 0;
456        let raw_text = self.get_raw_text();
457        for (offset, char_index) in (line.begin..line.end).enumerate() {
458            if let Some(advance) = raw_text.get(char_index).map(|c| metrics.advance(*c)) {
459                glyph_x += advance;
460            } else {
461                glyph_x += font_size;
462            }
463            let dist = (x - glyph_x).abs();
464            if dist < min_dist {
465                min_dist = dist;
466                min_index = offset + 1;
467            }
468        }
469        Position {
470            line: line_index,
471            offset: min_index,
472        }
473    }
474
475    pub fn get_glyphs(&self) -> &[TextGlyph] {
476        &self.glyphs
477    }
478
479    pub fn get_font(&self) -> FontResource {
480        (*self.font).clone()
481    }
482
483    pub fn set_font(&mut self, font: FontResource) -> &mut Self {
484        self.font.set_value_and_mark_modified(font);
485        self
486    }
487
488    pub fn font_size(&self) -> &StyledProperty<f32> {
489        &self.font_size
490    }
491
492    pub fn super_sampled_font_size(&self) -> f32 {
493        **self.font_size * self.super_sampling_scale
494    }
495
496    pub fn set_font_size(&mut self, font_size: StyledProperty<f32>) -> &mut Self {
497        self.font_size.set_value_and_mark_modified(font_size);
498        self
499    }
500
501    pub fn get_lines(&self) -> &[TextLine] {
502        &self.lines
503    }
504
505    pub fn set_vertical_alignment(&mut self, vertical_alignment: VerticalAlignment) -> &mut Self {
506        self.vertical_alignment
507            .set_value_and_mark_modified(vertical_alignment);
508        self
509    }
510
511    pub fn vertical_alignment(&self) -> VerticalAlignment {
512        *self.vertical_alignment
513    }
514
515    pub fn set_horizontal_alignment(
516        &mut self,
517        horizontal_alignment: HorizontalAlignment,
518    ) -> &mut Self {
519        self.horizontal_alignment
520            .set_value_and_mark_modified(horizontal_alignment);
521        self
522    }
523
524    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
525        *self.horizontal_alignment
526    }
527
528    pub fn set_brush(&mut self, brush: Brush) -> &mut Self {
529        self.brush.set_value_and_mark_modified(brush);
530        self
531    }
532
533    pub fn brush(&self) -> Brush {
534        (*self.brush).clone()
535    }
536
537    pub fn set_super_sampling_scale(&mut self, scale: f32) -> &mut Self {
538        self.super_sampling_scale = scale;
539        self
540    }
541
542    pub fn set_constraint(&mut self, constraint: Vector2<f32>) -> &mut Self {
543        self.constraint = constraint;
544        self
545    }
546
547    pub fn get_raw_text(&self) -> &[char] {
548        &self.text
549    }
550
551    pub fn text(&self) -> String {
552        self.text.iter().collect()
553    }
554
555    pub fn text_range(&self, range: Range<usize>) -> String {
556        self.text[range].iter().collect()
557    }
558
559    pub fn get_range_width<T: IntoIterator<Item = usize>>(&self, range: T) -> f32 {
560        let mut width = 0.0;
561        if let Some(font) = self.font.state().data() {
562            let mut metrics = GlyphMetrics {
563                font,
564                size: **self.font_size(),
565            };
566            for index in range {
567                // We can't trust the range values, check to prevent panic.
568                if let Some(glyph) = self.text.get(index) {
569                    width += metrics.advance(*glyph);
570                }
571            }
572        }
573        width
574    }
575
576    pub fn set_text<P: AsRef<str>>(&mut self, text: P) -> &mut Self {
577        self.text
578            .set_value_and_mark_modified(text.as_ref().chars().collect());
579        self
580    }
581
582    pub fn set_wrap(&mut self, wrap: WrapMode) -> &mut Self {
583        self.wrap.set_value_and_mark_modified(wrap);
584        self
585    }
586
587    /// Sets whether the shadow enabled or not.
588    pub fn set_shadow(&mut self, shadow: bool) -> &mut Self {
589        self.shadow.set_value_and_mark_modified(shadow);
590        self
591    }
592
593    /// Sets desired shadow brush. It will be used to render the shadow.
594    pub fn set_shadow_brush(&mut self, brush: Brush) -> &mut Self {
595        self.shadow_brush.set_value_and_mark_modified(brush);
596        self
597    }
598
599    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
600    /// not percentage-based.
601    pub fn set_shadow_dilation(&mut self, thickness: f32) -> &mut Self {
602        self.shadow_dilation.set_value_and_mark_modified(thickness);
603        self
604    }
605
606    /// Sets desired shadow offset in units.
607    pub fn set_shadow_offset(&mut self, offset: Vector2<f32>) -> &mut Self {
608        self.shadow_offset.set_value_and_mark_modified(offset);
609        self
610    }
611
612    pub fn wrap_mode(&self) -> WrapMode {
613        *self.wrap
614    }
615
616    pub fn insert_char(&mut self, code: char, index: usize) -> &mut Self {
617        self.text.insert(index, code);
618        self
619    }
620
621    pub fn insert_str(&mut self, str: &str, position: usize) -> &mut Self {
622        for (i, code) in str.chars().enumerate() {
623            self.text.insert(position + i, code);
624        }
625
626        self
627    }
628
629    pub fn remove_range(&mut self, range: Range<usize>) -> &mut Self {
630        self.text.drain(range);
631        self
632    }
633
634    pub fn remove_at(&mut self, index: usize) -> &mut Self {
635        self.text.remove(index);
636        self
637    }
638
639    pub fn build(&mut self) -> Vector2<f32> {
640        let mut font_state = self.font.state();
641        let Some(font) = font_state.data() else {
642            return Default::default();
643        };
644        let mut metrics = GlyphMetrics {
645            font,
646            size: **self.font_size(),
647        };
648        let line_height: f32 = metrics.ascender();
649
650        self.lines.clear();
651        let sink = WrapSink {
652            lines: &mut self.lines,
653            max_width: self.constraint.x,
654        };
655        if let Some(mask) = *self.mask_char {
656            let advance = metrics.advance(mask);
657            match *self.wrap {
658                WrapMode::NoWrap => wrap_mask(NoWrap::new(sink), self.text.len(), mask, advance),
659                WrapMode::Letter => wrap_mask(
660                    LetterWrap::new(sink),
661                    self.text.len(),
662                    mask,
663                    **self.font_size,
664                ),
665                WrapMode::Word => wrap_mask(WordWrap::new(sink), self.text.len(), mask, advance),
666            }
667        } else {
668            match *self.wrap {
669                WrapMode::NoWrap => wrap(NoWrap::new(sink), &mut metrics, self.text.as_slice()),
670                WrapMode::Letter => wrap(LetterWrap::new(sink), &mut metrics, self.text.as_slice()),
671                WrapMode::Word => wrap(WordWrap::new(sink), &mut metrics, self.text.as_slice()),
672            }
673        }
674
675        let total_height = line_height * self.lines.len() as f32;
676        // Align lines according to desired alignment.
677        for line in self.lines.iter_mut() {
678            match *self.horizontal_alignment {
679                HorizontalAlignment::Left => line.x_offset = 0.0,
680                HorizontalAlignment::Center => {
681                    if self.constraint.x.is_infinite() {
682                        line.x_offset = 0.0;
683                    } else {
684                        line.x_offset = 0.5 * (self.constraint.x - line.width).max(0.0);
685                    }
686                }
687                HorizontalAlignment::Right => {
688                    if self.constraint.x.is_infinite() {
689                        line.x_offset = 0.0;
690                    } else {
691                        line.x_offset = (self.constraint.x - line.width).max(0.0)
692                    }
693                }
694                HorizontalAlignment::Stretch => line.x_offset = 0.0,
695            }
696        }
697
698        // Generate glyphs for each text line.
699        self.glyphs.clear();
700
701        let cursor_y_start = match *self.vertical_alignment {
702            VerticalAlignment::Top => 0.0,
703            VerticalAlignment::Center => {
704                if self.constraint.y.is_infinite() {
705                    0.0
706                } else {
707                    (self.constraint.y - total_height).max(0.0) * 0.5
708                }
709            }
710            VerticalAlignment::Bottom => {
711                if self.constraint.y.is_infinite() {
712                    0.0
713                } else {
714                    (self.constraint.y - total_height).max(0.0)
715                }
716            }
717            VerticalAlignment::Stretch => 0.0,
718        };
719
720        let mut y: f32 = cursor_y_start.floor();
721        for line in self.lines.iter_mut() {
722            let mut x = line.x_offset.floor();
723            if let Some(mask) = *self.mask_char {
724                let mut prev = None;
725                for c in std::iter::repeat::<char>(mask).take(line.len()) {
726                    let (glyph, advance) =
727                        build_glyph(&mut metrics, x, y, c, prev, self.super_sampling_scale);
728                    self.glyphs.push(glyph);
729                    x += advance;
730                    prev = Some(c);
731                }
732            } else {
733                let mut prev = None;
734                for c in self.text.iter().take(line.end).skip(line.begin).cloned() {
735                    match c {
736                        '\n' => {
737                            x += metrics.newline_advance();
738                        }
739                        _ => {
740                            let (glyph, advance) =
741                                build_glyph(&mut metrics, x, y, c, prev, self.super_sampling_scale);
742                            self.glyphs.push(glyph);
743                            x += advance;
744                        }
745                    }
746                    prev = Some(c);
747                }
748            }
749            line.height = line_height;
750            line.y_offset = y;
751            y += line_height;
752        }
753
754        let size_x = self
755            .lines
756            .iter()
757            .map(|line| line.width)
758            .max_by(f32::total_cmp)
759            .unwrap_or_default();
760        // Minus here is because descender has negative value.
761        let size_y = total_height - metrics.descender();
762        Vector2::new(size_x, size_y)
763    }
764}
765
766fn wrap<W: TextWrapper>(mut wrapper: W, metrics: &mut GlyphMetrics, text: &[char]) {
767    for &character in text.iter() {
768        let advance = metrics.advance(character);
769        wrapper.push(character, advance);
770    }
771    wrapper.finish();
772}
773
774fn wrap_mask<W: TextWrapper>(mut wrapper: W, length: usize, mask_char: char, advance: f32) {
775    for _ in 0..length {
776        wrapper.push(mask_char, advance);
777    }
778    wrapper.finish();
779}
780
781pub struct FormattedTextBuilder {
782    font: FontResource,
783    brush: Brush,
784    constraint: Vector2<f32>,
785    text: String,
786    vertical_alignment: VerticalAlignment,
787    horizontal_alignment: HorizontalAlignment,
788    wrap: WrapMode,
789    mask_char: Option<char>,
790    shadow: bool,
791    shadow_brush: Brush,
792    shadow_dilation: f32,
793    shadow_offset: Vector2<f32>,
794    font_size: StyledProperty<f32>,
795    super_sampling_scaling: f32,
796}
797
798impl FormattedTextBuilder {
799    /// Creates new formatted text builder with default parameters.
800    pub fn new(font: FontResource) -> FormattedTextBuilder {
801        FormattedTextBuilder {
802            font,
803            text: "".to_owned(),
804            horizontal_alignment: HorizontalAlignment::Left,
805            vertical_alignment: VerticalAlignment::Top,
806            brush: Brush::Solid(Color::WHITE),
807            constraint: Vector2::new(128.0, 128.0),
808            wrap: WrapMode::NoWrap,
809            mask_char: None,
810            shadow: false,
811            shadow_brush: Brush::Solid(Color::BLACK),
812            shadow_dilation: 1.0,
813            shadow_offset: Vector2::new(1.0, 1.0),
814            font_size: 14.0f32.into(),
815            super_sampling_scaling: 1.0,
816        }
817    }
818
819    pub fn with_vertical_alignment(mut self, vertical_alignment: VerticalAlignment) -> Self {
820        self.vertical_alignment = vertical_alignment;
821        self
822    }
823
824    pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
825        self.wrap = wrap;
826        self
827    }
828
829    pub fn with_horizontal_alignment(mut self, horizontal_alignment: HorizontalAlignment) -> Self {
830        self.horizontal_alignment = horizontal_alignment;
831        self
832    }
833
834    pub fn with_text(mut self, text: String) -> Self {
835        self.text = text;
836        self
837    }
838
839    pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
840        self.font_size = font_size;
841        self
842    }
843
844    pub fn with_constraint(mut self, constraint: Vector2<f32>) -> Self {
845        self.constraint = constraint;
846        self
847    }
848
849    pub fn with_brush(mut self, brush: Brush) -> Self {
850        self.brush = brush;
851        self
852    }
853
854    pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
855        self.mask_char = mask_char;
856        self
857    }
858
859    /// Whether the shadow enabled or not.
860    pub fn with_shadow(mut self, shadow: bool) -> Self {
861        self.shadow = shadow;
862        self
863    }
864
865    /// Sets desired shadow brush. It will be used to render the shadow.
866    pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
867        self.shadow_brush = brush;
868        self
869    }
870
871    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
872    /// not percentage-based.
873    pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
874        self.shadow_dilation = thickness;
875        self
876    }
877
878    /// Sets desired shadow offset in units.
879    pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
880        self.shadow_offset = offset;
881        self
882    }
883
884    /// Sets desired super sampling scaling.
885    pub fn with_super_sampling_scaling(mut self, scaling: f32) -> Self {
886        self.super_sampling_scaling = scaling;
887        self
888    }
889
890    pub fn build(self) -> FormattedText {
891        FormattedText {
892            text: self.text.chars().collect::<Vec<char>>().into(),
893            lines: Vec::new(),
894            glyphs: Vec::new(),
895            vertical_alignment: self.vertical_alignment.into(),
896            horizontal_alignment: self.horizontal_alignment.into(),
897            brush: self.brush.into(),
898            constraint: self.constraint,
899            wrap: self.wrap.into(),
900            mask_char: self.mask_char.into(),
901            super_sampling_scale: self.super_sampling_scaling,
902            font_size: self.font_size.into(),
903            shadow: self.shadow.into(),
904            shadow_brush: self.shadow_brush.into(),
905            font: self.font.into(),
906            shadow_dilation: self.shadow_dilation.into(),
907            shadow_offset: self.shadow_offset.into(),
908        }
909    }
910}