Skip to main content

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, log::Log, math::Rect, reflect::prelude::*, uuid_provider,
25        variable::InheritableVariable, visitor::prelude::*,
26    },
27    font::{Font, FontGlyph, FontHeight, FontResource, BUILT_IN_FONT},
28    style::StyledProperty,
29    HorizontalAlignment, Thickness, VerticalAlignment,
30};
31use fyrox_resource::state::{LoadError, ResourceState};
32pub use run::*;
33use std::{
34    ops::{Range, RangeBounds},
35    path::PathBuf,
36};
37use strum_macros::{AsRefStr, EnumString, VariantNames};
38use textwrapper::*;
39
40mod run;
41mod textwrapper;
42
43/// Width of a tab when multiplied by font size.
44const TAB_WIDTH: f32 = 2.0;
45const ELLIPSIS: char = '…';
46
47/// Defines a position in the text. It is just a coordinates of a character in text.
48#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, Visit, Reflect)]
49pub struct Position {
50    /// Line index.
51    pub line: usize,
52
53    /// Offset from the beginning of the line.
54    pub offset: usize,
55}
56
57#[derive(Debug, Clone, Default)]
58pub struct TextGlyph {
59    pub bounds: Rect<f32>,
60    pub tex_coords: [Vector2<f32>; 4],
61    pub atlas_page_index: usize,
62    pub source_char_index: usize,
63}
64
65#[derive(Copy, Clone, Debug, Default)]
66pub struct TextLine {
67    /// Index of starting symbol in text array.
68    pub begin: usize,
69    /// Index of ending symbol in text array.
70    pub end: usize,
71    /// Total width of line.
72    pub width: f32,
73    /// Total height of line. Usually just ascender of a font.
74    pub height: f32,
75    /// Local horizontal position of line.
76    pub x_offset: f32,
77    /// Local vertical position of line.
78    pub y_offset: f32,
79}
80
81impl TextLine {
82    fn new() -> TextLine {
83        TextLine {
84            begin: 0,
85            end: 0,
86            width: 0.0,
87            height: 0.0,
88            x_offset: 0.0,
89            y_offset: 0.0,
90        }
91    }
92
93    pub fn len(&self) -> usize {
94        self.end - self.begin
95    }
96
97    pub fn is_empty(&self) -> bool {
98        self.end == self.begin
99    }
100
101    pub fn y_distance(&self, y: f32) -> f32 {
102        (self.y_offset + self.height / 2.0 - y).abs()
103    }
104}
105
106/// Wrapping mode for formatted text.
107#[derive(
108    Default,
109    Copy,
110    Clone,
111    PartialOrd,
112    PartialEq,
113    Hash,
114    Debug,
115    Eq,
116    Visit,
117    Reflect,
118    AsRefStr,
119    EnumString,
120    VariantNames,
121)]
122pub enum WrapMode {
123    /// No wrapping needed.
124    #[default]
125    NoWrap,
126
127    /// Letter-based wrapping.
128    Letter,
129
130    /// Word-based wrapping.
131    Word,
132}
133
134uuid_provider!(WrapMode = "f1290ceb-3fee-461f-a1e9-f9450bd06805");
135
136struct GlyphMetrics<'a> {
137    font: &'a mut Font,
138    size: f32,
139}
140
141impl GlyphMetrics<'_> {
142    fn ascender(&self) -> f32 {
143        self.font.ascender(self.size)
144    }
145
146    fn descender(&self) -> f32 {
147        self.font.descender(self.size)
148    }
149
150    fn newline_advance(&self) -> f32 {
151        self.size / 2.0
152    }
153
154    fn horizontal_kerning(&self, left: char, right: char) -> Option<f32> {
155        self.font.horizontal_kerning(self.size, left, right)
156    }
157
158    fn advance(&mut self, c: char) -> f32 {
159        match c {
160            '\n' => self.newline_advance(),
161            _ => self.font.glyph_advance(c, self.size),
162        }
163    }
164
165    fn glyph(&mut self, c: char, super_sampling_scale: f32) -> Option<&FontGlyph> {
166        self.font.glyph(c, self.size * super_sampling_scale)
167    }
168}
169
170fn build_glyph(
171    metrics: &mut GlyphMetrics,
172    mut x: f32,
173    mut y: f32,
174    source_char_index: usize,
175    character: char,
176    prev_character: Option<char>,
177    super_sampling_scale: f32,
178) -> (TextGlyph, f32) {
179    let ascender = metrics.ascender();
180    let font_size = metrics.size;
181
182    x = x.floor();
183    y = y.floor();
184
185    if character == '\t' {
186        let rect = Rect::new(x, y + ascender, font_size * TAB_WIDTH, font_size);
187        let text_glyph = TextGlyph {
188            bounds: rect,
189            tex_coords: [Vector2::default(); 4],
190            atlas_page_index: 0,
191            source_char_index,
192        };
193        return (text_glyph, rect.w());
194    }
195
196    // Request larger glyph with super sampling scaling.
197    match metrics.glyph(character, super_sampling_scale) {
198        Some(glyph) => {
199            // Discard super sampling scaling in the produced glyphs, because we're interested only
200            // in larger texture size, not the "physical" size.
201            let k = 1.0 / super_sampling_scale;
202            // Insert glyph
203            let rect = Rect::new(
204                x + glyph.bitmap_left * k,
205                y + ascender.floor() - glyph.bitmap_top * k - (glyph.bitmap_height * k),
206                glyph.bitmap_width * k,
207                glyph.bitmap_height * k,
208            );
209            let text_glyph = TextGlyph {
210                bounds: rect,
211                tex_coords: glyph.tex_coords,
212                atlas_page_index: glyph.page_index,
213                source_char_index,
214            };
215            let advance = glyph.advance
216                + prev_character
217                    .and_then(|prev| metrics.horizontal_kerning(prev, character))
218                    .unwrap_or_default();
219            (text_glyph, advance * k)
220        }
221        None => {
222            // Insert invalid symbol
223            let rect = Rect::new(x, y + ascender, font_size, font_size);
224            let text_glyph = TextGlyph {
225                bounds: rect,
226                tex_coords: [Vector2::default(); 4],
227                atlas_page_index: 0,
228                source_char_index,
229            };
230            (text_glyph, rect.w())
231        }
232    }
233}
234
235struct WrapSink<'a> {
236    lines: &'a mut Vec<TextLine>,
237    normal_width: f32,
238    first_width: f32,
239}
240
241impl LineSink for WrapSink<'_> {
242    fn push_line(&mut self, range: Range<usize>, width: f32) {
243        let mut line = TextLine::new();
244        line.begin = range.start;
245        line.end = range.end;
246        line.width = width;
247        self.lines.push(line);
248    }
249
250    fn max_width(&self) -> f32 {
251        if self.lines.is_empty() {
252            self.first_width
253        } else {
254            self.normal_width
255        }
256    }
257}
258
259#[derive(Default, Clone, Debug, Visit, Reflect)]
260pub struct FormattedText {
261    font: InheritableVariable<Option<FontResource>>,
262    pub text: InheritableVariable<Vec<char>>,
263    // Temporary buffer used to split text on lines. We need it to reduce memory allocations
264    // when we're changing text too frequently, here we sacrifice some memory in order to get
265    // more performance.
266    #[reflect(hidden)]
267    #[visit(skip)]
268    lines: Vec<TextLine>,
269    // Final glyphs for draw buffer.
270    #[visit(skip)]
271    #[reflect(hidden)]
272    glyphs: Vec<TextGlyph>,
273    vertical_alignment: InheritableVariable<VerticalAlignment>,
274    horizontal_alignment: InheritableVariable<HorizontalAlignment>,
275    #[reflect(hidden)]
276    brush: InheritableVariable<Brush>,
277    #[visit(skip)]
278    #[reflect(hidden)]
279    constraint: Vector2<f32>,
280    wrap: InheritableVariable<WrapMode>,
281    mask_char: InheritableVariable<Option<char>>,
282    #[visit(skip)]
283    #[reflect(hidden)]
284    pub(crate) super_sampling_scale: f32,
285    #[visit(rename = "Height")]
286    font_size: InheritableVariable<StyledProperty<f32>>,
287    pub shadow: InheritableVariable<bool>,
288    pub shadow_brush: InheritableVariable<Brush>,
289    pub shadow_dilation: InheritableVariable<f32>,
290    pub shadow_offset: InheritableVariable<Vector2<f32>>,
291    #[visit(optional)]
292    pub runs: RunSet,
293    /// The indent amount of the first line of the text.
294    /// A negative indent will cause every line except the first to indent.
295    #[visit(optional)]
296    pub line_indent: InheritableVariable<f32>,
297    /// The space between lines.
298    #[visit(optional)]
299    pub line_space: InheritableVariable<f32>,
300    pub padding: InheritableVariable<Thickness>,
301    #[visit(skip)]
302    #[reflect(hidden)]
303    total_height: f32,
304    /// A flag, that defines whether the formatted text should add ellipsis to lines that goes
305    /// outside provided bounds.
306    #[visit(optional)]
307    pub trim_text: InheritableVariable<bool>,
308}
309
310impl FormattedText {
311    pub fn font_at(&self, index: usize) -> FontResource {
312        self.runs.font_at(index).unwrap_or_else(|| self.get_font())
313    }
314    pub fn font_size_at(&self, index: usize) -> f32 {
315        self.runs
316            .font_size_at(index)
317            .unwrap_or_else(|| self.font_size().property)
318    }
319    pub fn brush_at(&self, index: usize) -> Brush {
320        self.runs.brush_at(index).unwrap_or_else(|| self.brush())
321    }
322    pub fn shadow_at(&self, index: usize) -> bool {
323        self.runs.shadow_at(index).unwrap_or(*self.shadow)
324    }
325    pub fn shadow_brush_at(&self, index: usize) -> Brush {
326        self.runs
327            .shadow_brush_at(index)
328            .unwrap_or_else(|| self.shadow_brush.clone_inner())
329    }
330    pub fn shadow_dilation_at(&self, index: usize) -> f32 {
331        self.runs
332            .shadow_dilation_at(index)
333            .unwrap_or(*self.shadow_dilation)
334    }
335    pub fn shadow_offset_at(&self, index: usize) -> Vector2<f32> {
336        self.runs
337            .shadow_offset_at(index)
338            .unwrap_or(*self.shadow_offset)
339    }
340    pub fn nearest_valid_position(&self, start: Position) -> Position {
341        if self.lines.is_empty() {
342            return Position::default();
343        }
344        let mut pos = start;
345        pos.line = usize::min(pos.line, self.lines.len() - 1);
346        pos.offset = usize::min(pos.offset, self.lines[pos.line].len());
347        pos
348    }
349    pub fn get_relative_position_x(&self, start: Position, offset: isize) -> Position {
350        if self.lines.is_empty() {
351            return Position::default();
352        }
353        let mut pos = self.nearest_valid_position(start);
354        let distance = offset.abs();
355        for _ in 0..distance {
356            if offset < 0 {
357                if pos.offset > 0 {
358                    pos.offset -= 1
359                } else if pos.line > 0 {
360                    pos.line -= 1;
361                    pos.offset = self.lines[pos.line].len().saturating_sub(1);
362                } else {
363                    pos.offset = 0;
364                    break;
365                }
366            } else {
367                let line = &self.lines[pos.line];
368                if pos.offset + 1 < line.len() {
369                    pos.offset += 1;
370                } else if pos.line + 1 < self.lines.len() {
371                    pos.line += 1;
372                    pos.offset = 0;
373                } else {
374                    pos.offset = line.len();
375                    break;
376                }
377            }
378        }
379        pos
380    }
381
382    pub fn get_relative_position_y(&self, start: Position, offset: isize) -> Position {
383        let mut pos = self.nearest_valid_position(start);
384        pos.line = pos.line.saturating_add_signed(offset);
385        self.nearest_valid_position(pos)
386    }
387
388    pub fn get_line_range(&self, line: usize) -> Range<Position> {
389        let length = self.lines.get(line).map(TextLine::len).unwrap_or(0);
390        Range {
391            start: Position { line, offset: 0 },
392            end: Position {
393                line,
394                offset: length,
395            },
396        }
397    }
398
399    pub fn iter_line_ranges_within(
400        &self,
401        range: Range<Position>,
402    ) -> impl Iterator<Item = Range<Position>> + '_ {
403        (range.start.line..=range.end.line).map(move |i| {
404            let r = self.get_line_range(i);
405            Range {
406                start: Position::max(range.start, r.start),
407                end: Position::min(range.end, r.end),
408            }
409        })
410    }
411
412    pub fn end_position(&self) -> Position {
413        match self.lines.iter().enumerate().next_back() {
414            Some((i, line)) => Position {
415                line: i,
416                offset: line.len(),
417            },
418            None => Position::default(),
419        }
420    }
421
422    fn position_to_char_index_internal(&self, position: Position, clamp: bool) -> Option<usize> {
423        self.lines.get(position.line).map(|line| {
424            line.begin
425                + position.offset.min(if clamp {
426                    line.len().saturating_sub(1)
427                } else {
428                    line.len()
429                })
430        })
431    }
432
433    pub fn position_range_to_char_index_range(&self, range: Range<Position>) -> Range<usize> {
434        let start = self
435            .position_to_char_index_unclamped(range.start)
436            .unwrap_or(0);
437        let end = self
438            .position_to_char_index_unclamped(range.end)
439            .unwrap_or(self.text.len());
440        start..end
441    }
442    /// Maps input [`Position`] to a linear position in character array.
443    /// The index returned is the index of the character after the position, which may be
444    /// out-of-bounds if the position is at the end of the text.
445    /// You should check the index before trying to use it to fetch data from inner array of characters.
446    pub fn position_to_char_index_unclamped(&self, position: Position) -> Option<usize> {
447        self.position_to_char_index_internal(position, false)
448    }
449
450    /// Maps input [`Position`] to a linear position in character array.
451    /// The index returned is usually the index of the character after the position,
452    /// but if the position is at the end of a line, then return the index of the character _before_ the position.
453    /// In other words, the last two positions of each line are mapped to the same character index.
454    /// Output index will always be valid for fetching if the method returned `Some(index)`.
455    /// The index, however, cannot be used for text insertion because it cannot point to a "place after last char".
456    pub fn position_to_char_index_clamped(&self, position: Position) -> Option<usize> {
457        self.position_to_char_index_internal(position, true)
458    }
459
460    /// Maps linear character index (as in string) to its actual location in the text.
461    pub fn char_index_to_position(&self, i: usize) -> Option<Position> {
462        self.lines
463            .iter()
464            .enumerate()
465            .find_map(|(line_index, line)| {
466                if (line.begin..line.end).contains(&i) {
467                    Some(Position {
468                        line: line_index,
469                        offset: i - line.begin,
470                    })
471                } else {
472                    None
473                }
474            })
475            .or(Some(self.end_position()))
476    }
477
478    pub fn position_to_local(&self, position: Position) -> Vector2<f32> {
479        if self.get_font().state().data().is_none() {
480            return Default::default();
481        }
482        let position = self.nearest_valid_position(position);
483        let line = &self.lines[position.line];
484        let caret_pos = Vector2::new(line.x_offset, line.y_offset);
485        let range = line.begin..line.begin + position.offset;
486        caret_pos + Vector2::new(self.get_range_width(range), 0.0)
487    }
488
489    pub fn local_to_position(&self, point: Vector2<f32>) -> Position {
490        if self.get_font().state().data().is_none() {
491            return Position::default();
492        }
493        let y = point.y;
494
495        let Some(line_index) = self
496            .lines
497            .iter()
498            .enumerate()
499            .map(|(i, a)| (i, a.y_distance(y)))
500            .min_by(|a, b| f32::total_cmp(&a.1, &b.1))
501            .map(|(i, _)| i)
502        else {
503            return Position::default();
504        };
505        let line = self.lines[line_index];
506        let x = point.x - line.x_offset;
507        let mut glyph_x: f32 = 0.0;
508        let mut min_dist: f32 = x.abs();
509        let mut min_index: usize = 0;
510        for (offset, char_index) in (line.begin..line.end).enumerate() {
511            glyph_x += self.get_char_width(char_index).unwrap_or_default();
512            let dist = (x - glyph_x).abs();
513            if dist < min_dist {
514                min_dist = dist;
515                min_index = offset + 1;
516            }
517        }
518        Position {
519            line: line_index,
520            offset: min_index,
521        }
522    }
523
524    pub fn get_glyphs(&self) -> &[TextGlyph] {
525        &self.glyphs
526    }
527
528    pub fn get_glyph_draw_values(
529        &self,
530        layer: DrawValueLayer,
531        glyph: &TextGlyph,
532    ) -> GlyphDrawValues {
533        let atlas_page_index = glyph.atlas_page_index;
534        let i = glyph.source_char_index;
535        let font = self.font_at(i);
536        let height = FontHeight::from(self.font_size_at(i) * self.super_sampling_scale);
537        match layer {
538            DrawValueLayer::Main => GlyphDrawValues {
539                atlas_page_index,
540                font,
541                brush: self.brush_at(i),
542                height,
543            },
544            DrawValueLayer::Shadow => GlyphDrawValues {
545                atlas_page_index,
546                font,
547                brush: self.shadow_brush_at(i),
548                height,
549            },
550        }
551    }
552
553    pub fn get_font(&self) -> FontResource {
554        (*self.font).clone().unwrap_or(BUILT_IN_FONT.resource())
555    }
556
557    pub fn set_font(&mut self, font: FontResource) -> &mut Self {
558        self.font.set_value_and_mark_modified(Some(font));
559        self
560    }
561
562    pub fn font_size(&self) -> &StyledProperty<f32> {
563        &self.font_size
564    }
565
566    pub fn super_sampled_font_size(&self) -> f32 {
567        **self.font_size * self.super_sampling_scale
568    }
569
570    pub fn set_font_size(&mut self, font_size: StyledProperty<f32>) -> &mut Self {
571        self.font_size.set_value_and_mark_modified(font_size);
572        self
573    }
574
575    pub fn get_lines(&self) -> &[TextLine] {
576        &self.lines
577    }
578
579    pub fn set_vertical_alignment(&mut self, vertical_alignment: VerticalAlignment) -> &mut Self {
580        self.vertical_alignment
581            .set_value_and_mark_modified(vertical_alignment);
582        self
583    }
584
585    pub fn vertical_alignment(&self) -> VerticalAlignment {
586        *self.vertical_alignment
587    }
588
589    pub fn set_horizontal_alignment(
590        &mut self,
591        horizontal_alignment: HorizontalAlignment,
592    ) -> &mut Self {
593        self.horizontal_alignment
594            .set_value_and_mark_modified(horizontal_alignment);
595        self
596    }
597
598    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
599        *self.horizontal_alignment
600    }
601
602    pub fn set_brush(&mut self, brush: Brush) -> &mut Self {
603        self.brush.set_value_and_mark_modified(brush);
604        self
605    }
606
607    pub fn brush(&self) -> Brush {
608        (*self.brush).clone()
609    }
610
611    pub fn set_super_sampling_scale(&mut self, scale: f32) -> &mut Self {
612        self.super_sampling_scale = scale;
613        self
614    }
615
616    pub fn super_sampling_scale(&self) -> f32 {
617        self.super_sampling_scale
618    }
619
620    pub fn set_constraint(&mut self, constraint: Vector2<f32>) -> &mut Self {
621        self.constraint = constraint;
622        self
623    }
624
625    pub fn get_raw_text(&self) -> &[char] {
626        &self.text
627    }
628
629    pub fn text(&self) -> String {
630        self.text.iter().collect()
631    }
632
633    pub fn text_range(&self, range: Range<usize>) -> String {
634        self.text[range].iter().collect()
635    }
636
637    /// The width of the character at the given index.
638    pub fn get_char_width(&self, index: usize) -> Option<f32> {
639        let glyph = self.text.get(index)?;
640        Some(
641            GlyphMetrics {
642                font: &mut self.font_at(index).data_ref(),
643                size: self.font_size_at(index),
644            }
645            .advance(*glyph),
646        )
647    }
648
649    /// The width of the characters at the indices in the given iterator.
650    /// This is equivalent to calling [`get_char_width`](Self::get_char_width) repeatedly and summing the results.
651    pub fn get_range_width<T: IntoIterator<Item = usize>>(&self, range: T) -> f32 {
652        let mut width = 0.0;
653        for index in range {
654            width += self.get_char_width(index).unwrap_or_default();
655        }
656        width
657    }
658
659    /// A rectangle relative to the top-left corner of the text that contains the given
660    /// range of characters on the given line. None is returned if the `line` is out of
661    /// bounds. The `range` is relative to the start of the line, so 0 is the first character
662    /// of the line, not the first character of the text.
663    ///
664    /// This rect is appropriate for drawing a selection or highlight for the text,
665    /// and the lower edge of the rectangle can be used to draw an underline.
666    pub fn text_rect<R: RangeBounds<usize>>(&self, line: usize, range: R) -> Option<Rect<f32>> {
667        let line = self.lines.get(line)?;
668        let x = line.x_offset;
669        let y = line.y_offset;
670        let h = line.height;
671        use std::ops::Bound;
672        let start = match range.start_bound() {
673            Bound::Included(&n) => n,
674            Bound::Excluded(&n) => n + 1,
675            Bound::Unbounded => 0,
676        };
677        let end = match range.end_bound() {
678            Bound::Included(&n) => n + 1,
679            Bound::Excluded(&n) => n,
680            Bound::Unbounded => line.len(),
681        };
682        let start = line.begin + start;
683        let end = line.begin + end;
684        let offset = self.get_range_width(line.begin..start);
685        let w = self.get_range_width(start..end);
686        Some(Rect::new(offset + x, y, w, h))
687    }
688
689    pub fn set_text<P: AsRef<str>>(&mut self, text: P) -> &mut Self {
690        self.text
691            .set_value_and_mark_modified(text.as_ref().chars().collect());
692        self
693    }
694
695    pub fn set_chars(&mut self, text: Vec<char>) -> &mut Self {
696        self.text.set_value_and_mark_modified(text);
697        self
698    }
699
700    pub fn set_wrap(&mut self, wrap: WrapMode) -> &mut Self {
701        self.wrap.set_value_and_mark_modified(wrap);
702        self
703    }
704
705    /// Sets whether the shadow enabled or not.
706    pub fn set_shadow(&mut self, shadow: bool) -> &mut Self {
707        self.shadow.set_value_and_mark_modified(shadow);
708        self
709    }
710
711    /// Sets desired shadow brush. It will be used to render the shadow.
712    pub fn set_shadow_brush(&mut self, brush: Brush) -> &mut Self {
713        self.shadow_brush.set_value_and_mark_modified(brush);
714        self
715    }
716
717    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
718    /// not percentage-based.
719    pub fn set_shadow_dilation(&mut self, thickness: f32) -> &mut Self {
720        self.shadow_dilation.set_value_and_mark_modified(thickness);
721        self
722    }
723
724    /// Sets desired shadow offset in units.
725    pub fn set_shadow_offset(&mut self, offset: Vector2<f32>) -> &mut Self {
726        self.shadow_offset.set_value_and_mark_modified(offset);
727        self
728    }
729
730    /// Runs can optionally modify various style settings for portions of the text.
731    /// Later runs override earlier runs if their ranges overlap and the later run
732    /// sets a property that conflicts with an earlier run.
733    pub fn runs(&self) -> &RunSet {
734        &self.runs
735    }
736
737    /// Modify runs of the text to set the style for portions of the text.
738    /// Later runs potentially override earlier runs if the ranges of the runs overlap and the later run
739    /// sets a property that conflicts with an earlier run.
740    pub fn runs_mut(&mut self) -> &mut RunSet {
741        &mut self.runs
742    }
743
744    /// Replace runs of the text to set the style for portions of the text.
745    /// Later runs potentially override earlier runs if the ranges of the runs overlap and the later run
746    /// sets a property that conflicts with an earlier run.
747    pub fn set_runs(&mut self, runs: RunSet) -> &mut Self {
748        self.runs = runs;
749        self
750    }
751
752    /// The amount of indent of the first line, horizontally separating it
753    /// from the start of the remaining lines.
754    /// If the indent is negative, then the first line will not be indented
755    /// while all the other lines will be indented. By default, this is 0.0.
756    pub fn set_line_indent(&mut self, indent: f32) -> &mut Self {
757        self.line_indent.set_value_and_mark_modified(indent);
758        self
759    }
760
761    /// The amount of indent of the first line, horizontally separating it
762    /// from the start of the remaining lines.
763    /// If the indent is negative, then the first line will not be indented
764    /// while all the other lines will be indented. By default, this is 0.0.
765    pub fn line_indent(&mut self) -> f32 {
766        *self.line_indent
767    }
768
769    /// The space separating each line from the line above and below.
770    /// By default, this is 0.0.
771    pub fn set_line_space(&mut self, space: f32) -> &mut Self {
772        self.line_space.set_value_and_mark_modified(space);
773        self
774    }
775
776    /// The space separating each line from the line above and below.
777    /// By default, this is 0.0.
778    pub fn line_space(&self) -> f32 {
779        *self.line_space
780    }
781
782    pub fn wrap_mode(&self) -> WrapMode {
783        *self.wrap
784    }
785
786    pub fn insert_char(&mut self, code: char, index: usize) -> &mut Self {
787        self.text.insert(index, code);
788        self
789    }
790
791    pub fn insert_str(&mut self, str: &str, position: usize) -> &mut Self {
792        for (i, code) in str.chars().enumerate() {
793            self.text.insert(position + i, code);
794        }
795
796        self
797    }
798
799    pub fn remove_range(&mut self, range: Range<usize>) -> &mut Self {
800        self.text.drain(range);
801        self
802    }
803
804    pub fn remove_at(&mut self, index: usize) -> &mut Self {
805        self.text.remove(index);
806        self
807    }
808
809    /// Returns once all fonts used by this FormattedText are finished loading.
810    pub async fn wait_for_fonts(&mut self) -> Result<(), LoadError> {
811        if let Some(font) = self.font.clone_inner() {
812            font.await?;
813        }
814        for run in self.runs.iter() {
815            if let Some(font) = run.font() {
816                font.clone().await?;
817            }
818        }
819        Ok(())
820    }
821
822    /// Returns true if all fonts used by this resource are Ok.
823    /// This `FormattedText` will not build successfully unless this returns true.
824    pub fn are_fonts_loaded(&self) -> bool {
825        if !self.get_font().is_ok() {
826            return false;
827        }
828        for run in self.runs.iter() {
829            if let Some(font) = run.font() {
830                if !font.is_ok() {
831                    return false;
832                }
833            }
834        }
835        true
836    }
837
838    pub fn are_fonts_loading(&self) -> bool {
839        if !self.get_font().is_loading() {
840            return true;
841        }
842        for run in self.runs.iter() {
843            if let Some(font) = run.font() {
844                if !font.is_loading() {
845                    return true;
846                }
847            }
848        }
849        false
850    }
851
852    pub fn font_load_error_list(&self) -> Vec<(PathBuf, LoadError)> {
853        let mut list = vec![];
854        if let ResourceState::LoadError { path, error } = &self.get_font().header().state {
855            list.push((path.clone(), error.clone()));
856        }
857        for run in self.runs.iter() {
858            if let Some(font) = run.font() {
859                if let ResourceState::LoadError { path, error } = &font.header().state {
860                    list.push((path.clone(), error.clone()));
861                }
862            }
863        }
864        list
865    }
866
867    pub fn font_loading_summary(&self) -> String {
868        use std::fmt::Write;
869        let mut result = String::default();
870        write!(result, "Primary font: {}", self.get_font().header().state).unwrap();
871        for run in self.runs.iter() {
872            if let Some(font) = run.font() {
873                write!(result, "\nRun {:?}: {}", run.range, font.header().state).unwrap();
874            }
875        }
876        result
877    }
878
879    pub fn measure_and_arrange(&mut self) -> Vector2<f32> {
880        let size = self.measure();
881        self.arrange(self.constraint);
882        size
883    }
884
885    pub fn measure(&mut self) -> Vector2<f32> {
886        let mut lines = std::mem::take(&mut self.lines);
887        lines.clear();
888        // Fail early if any font is not available.
889        if !self.are_fonts_loaded() {
890            Log::err(format!(
891                "Unable to measure text due to unloaded fonts. {:?}.\n{}",
892                self.text(),
893                self.font_loading_summary(),
894            ));
895            return Vector2::default();
896        }
897        let constraint = Vector2::new(
898            (self.constraint.x - (self.padding.left + self.padding.right)).max(0.0),
899            (self.constraint.y - (self.padding.top + self.padding.bottom)).max(0.0),
900        );
901        let first_indent = self.line_indent.max(0.0);
902        let normal_indent = -self.line_indent.min(0.0);
903        let sink = WrapSink {
904            lines: &mut lines,
905            normal_width: constraint.x - normal_indent,
906            first_width: constraint.x - first_indent,
907        };
908        if let Some(mask) = *self.mask_char {
909            let advance = GlyphMetrics {
910                font: &mut self.get_font().data_ref(),
911                size: **self.font_size,
912            }
913            .advance(mask);
914            match *self.wrap {
915                WrapMode::NoWrap => wrap_mask(NoWrap::new(sink), self.text.len(), mask, advance),
916                WrapMode::Letter => wrap_mask(
917                    LetterWrap::new(sink),
918                    self.text.len(),
919                    mask,
920                    **self.font_size,
921                ),
922                WrapMode::Word => wrap_mask(WordWrap::new(sink), self.text.len(), mask, advance),
923            }
924        } else {
925            let source = self.text.iter().enumerate().map(|(i, c)| {
926                let a = GlyphMetrics {
927                    font: &mut self.font_at(i).data_ref(),
928                    size: self.font_size_at(i),
929                }
930                .advance(*c);
931                (*c, a)
932            });
933            match *self.wrap {
934                WrapMode::NoWrap => wrap(NoWrap::new(sink), source),
935                WrapMode::Letter => wrap(LetterWrap::new(sink), source),
936                WrapMode::Word => wrap(WordWrap::new(sink), source),
937            }
938        }
939
940        self.total_height = 0.0;
941
942        // Calculate line height
943        for line in lines.iter_mut() {
944            if self.mask_char.is_some() || self.runs.is_empty() {
945                line.height = GlyphMetrics {
946                    font: &mut self.get_font().data_ref(),
947                    size: **self.font_size,
948                }
949                .ascender();
950            } else {
951                for i in line.begin..line.end {
952                    let h = GlyphMetrics {
953                        font: &mut self.font_at(i).data_ref(),
954                        size: self.font_size_at(i),
955                    }
956                    .ascender();
957                    line.height = line.height.max(h);
958                }
959            }
960            self.total_height += line.height + self.line_space();
961        }
962        self.total_height -= self.line_space();
963
964        let size_x = if constraint.x.is_finite() {
965            constraint.x
966        } else {
967            lines
968                .iter()
969                .map(|line| line.width)
970                .max_by(f32::total_cmp)
971                .unwrap_or_default()
972        };
973        let size_y = if constraint.y.is_finite() {
974            constraint.y
975        } else {
976            let descender = if self.mask_char.is_some() || self.runs.is_empty() {
977                GlyphMetrics {
978                    font: &mut self.get_font().data_ref(),
979                    size: **self.font_size,
980                }
981                .descender()
982            } else if let Some(line) = lines.last() {
983                (line.begin..line.end)
984                    .map(|i| {
985                        GlyphMetrics {
986                            font: &mut self.font_at(i).data_ref(),
987                            size: self.font_size_at(i),
988                        }
989                        .descender()
990                    })
991                    .min_by(f32::total_cmp)
992                    .unwrap_or_default()
993            } else {
994                0.0
995            };
996            // Minus here is because descender has negative value.
997            self.total_height - descender
998        };
999        self.lines = lines;
1000        Vector2::new(
1001            size_x + self.padding.left + self.padding.right,
1002            size_y + self.padding.top + self.padding.bottom,
1003        )
1004    }
1005
1006    pub fn arrange(&mut self, constraint: Vector2<f32>) {
1007        self.constraint = constraint;
1008        let constraint = Vector2::new(
1009            (self.constraint.x - (self.padding.left + self.padding.right)).max(0.0),
1010            (self.constraint.y - (self.padding.top + self.padding.bottom)).max(0.0),
1011        );
1012        let mut lines = std::mem::take(&mut self.lines);
1013        let first_indent = self.line_indent.max(0.0);
1014        let normal_indent = -self.line_indent.min(0.0);
1015        // Align lines according to desired alignment.
1016        for (i, line) in lines.iter_mut().enumerate() {
1017            let indent = if i == 0 { first_indent } else { normal_indent };
1018            match *self.horizontal_alignment {
1019                HorizontalAlignment::Left => line.x_offset = indent,
1020                HorizontalAlignment::Center => {
1021                    if constraint.x.is_infinite() {
1022                        line.x_offset = indent;
1023                    } else {
1024                        line.x_offset = 0.5 * (constraint.x - line.width).max(0.0);
1025                    }
1026                }
1027                HorizontalAlignment::Right => {
1028                    if constraint.x.is_infinite() {
1029                        line.x_offset = indent;
1030                    } else {
1031                        line.x_offset = (constraint.x - line.width - indent).max(0.0)
1032                    }
1033                }
1034                HorizontalAlignment::Stretch => line.x_offset = indent,
1035            }
1036            line.x_offset += self.padding.left;
1037        }
1038
1039        // Generate glyphs for each text line.
1040        self.glyphs.clear();
1041
1042        let cursor_y_start = self.padding.top
1043            + match *self.vertical_alignment {
1044                VerticalAlignment::Top => 0.0,
1045                VerticalAlignment::Center => {
1046                    if constraint.y.is_infinite() {
1047                        0.0
1048                    } else {
1049                        (constraint.y - self.total_height).max(0.0) * 0.5
1050                    }
1051                }
1052                VerticalAlignment::Bottom => {
1053                    if constraint.y.is_infinite() {
1054                        0.0
1055                    } else {
1056                        (constraint.y - self.total_height).max(0.0)
1057                    }
1058                }
1059                VerticalAlignment::Stretch => 0.0,
1060            };
1061        let mut y: f32 = cursor_y_start.floor();
1062        for line in lines.iter_mut() {
1063            let mut x = line.x_offset.floor();
1064            if let Some(mask) = *self.mask_char {
1065                let mut prev = None;
1066                let font = self.get_font();
1067                let mut metrics = GlyphMetrics {
1068                    font: &mut font.data_ref(),
1069                    size: **self.font_size,
1070                };
1071                for c in std::iter::repeat_n(mask, line.len()) {
1072                    let (glyph, advance) =
1073                        build_glyph(&mut metrics, x, y, 0, c, prev, self.super_sampling_scale);
1074                    self.glyphs.push(glyph);
1075                    x += advance;
1076                    prev = Some(c);
1077                }
1078            } else {
1079                let mut prev = None;
1080                for (i, &c) in self.text.iter().enumerate().take(line.end).skip(line.begin) {
1081                    let font = self.font_at(i);
1082                    let font = &mut font.data_ref();
1083                    let size = self.font_size_at(i);
1084                    let mut metrics = GlyphMetrics { font, size };
1085                    match c {
1086                        '\n' => {
1087                            x += metrics.newline_advance();
1088                        }
1089                        _ => {
1090                            let y1 = y + line.height - metrics.ascender();
1091                            let scale = self.super_sampling_scale;
1092                            let (glyph, advance) =
1093                                build_glyph(&mut metrics, x, y1, i, c, prev, scale);
1094
1095                            if *self.trim_text
1096                                && *self.wrap == WrapMode::NoWrap
1097                                && line.width > constraint.x
1098                            {
1099                                let ellipsis_advance = metrics.advance(ELLIPSIS);
1100                                if x + ellipsis_advance * 1.5 > constraint.x {
1101                                    let (glyph, _) =
1102                                        build_glyph(&mut metrics, x, y1, i, ELLIPSIS, prev, scale);
1103                                    self.glyphs.push(glyph);
1104                                    break;
1105                                }
1106                            }
1107
1108                            self.glyphs.push(glyph);
1109                            x += advance;
1110                        }
1111                    }
1112                    prev = Some(c);
1113                }
1114            }
1115            line.y_offset = y;
1116            y += line.height + self.line_space();
1117        }
1118        self.lines = lines;
1119    }
1120}
1121
1122fn wrap<W, I>(mut wrapper: W, source: I)
1123where
1124    W: TextWrapper,
1125    I: Iterator<Item = (char, f32)>,
1126{
1127    for (character, advance) in source {
1128        wrapper.push(character, advance);
1129    }
1130    wrapper.finish();
1131}
1132
1133fn wrap_mask<W: TextWrapper>(mut wrapper: W, length: usize, mask_char: char, advance: f32) {
1134    for _ in 0..length {
1135        wrapper.push(mask_char, advance);
1136    }
1137    wrapper.finish();
1138}
1139
1140pub struct FormattedTextBuilder {
1141    font: FontResource,
1142    brush: Brush,
1143    constraint: Vector2<f32>,
1144    text: Vec<char>,
1145    vertical_alignment: VerticalAlignment,
1146    horizontal_alignment: HorizontalAlignment,
1147    wrap: WrapMode,
1148    mask_char: Option<char>,
1149    shadow: bool,
1150    shadow_brush: Brush,
1151    shadow_dilation: f32,
1152    shadow_offset: Vector2<f32>,
1153    font_size: StyledProperty<f32>,
1154    super_sampling_scaling: f32,
1155    padding: Thickness,
1156    runs: RunSet,
1157    /// The amount of indentation on the first line of the text.
1158    line_indent: f32,
1159    /// The space between lines.
1160    line_space: f32,
1161    trim_text: bool,
1162}
1163
1164impl FormattedTextBuilder {
1165    /// Creates new formatted text builder with default parameters.
1166    pub fn new(font: FontResource) -> FormattedTextBuilder {
1167        FormattedTextBuilder {
1168            font,
1169            text: Vec::default(),
1170            horizontal_alignment: HorizontalAlignment::Left,
1171            vertical_alignment: VerticalAlignment::Top,
1172            brush: Brush::Solid(Color::WHITE),
1173            constraint: Vector2::new(128.0, 128.0),
1174            wrap: WrapMode::NoWrap,
1175            mask_char: None,
1176            shadow: false,
1177            shadow_brush: Brush::Solid(Color::BLACK),
1178            shadow_dilation: 1.0,
1179            shadow_offset: Vector2::new(1.0, 1.0),
1180            font_size: 14.0f32.into(),
1181            super_sampling_scaling: 1.0,
1182            padding: Thickness::uniform(0.0),
1183            runs: RunSet::default(),
1184            line_indent: 0.0,
1185            line_space: 0.0,
1186            trim_text: false,
1187        }
1188    }
1189
1190    pub fn with_vertical_alignment(mut self, vertical_alignment: VerticalAlignment) -> Self {
1191        self.vertical_alignment = vertical_alignment;
1192        self
1193    }
1194
1195    pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
1196        self.wrap = wrap;
1197        self
1198    }
1199
1200    pub fn with_horizontal_alignment(mut self, horizontal_alignment: HorizontalAlignment) -> Self {
1201        self.horizontal_alignment = horizontal_alignment;
1202        self
1203    }
1204
1205    pub fn with_text(mut self, text: String) -> Self {
1206        self.text = text.chars().collect();
1207        self
1208    }
1209
1210    pub fn with_chars(mut self, text: Vec<char>) -> Self {
1211        self.text = text;
1212        self
1213    }
1214
1215    pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
1216        self.font_size = font_size;
1217        self
1218    }
1219
1220    pub fn with_constraint(mut self, constraint: Vector2<f32>) -> Self {
1221        self.constraint = constraint;
1222        self
1223    }
1224
1225    pub fn with_brush(mut self, brush: Brush) -> Self {
1226        self.brush = brush;
1227        self
1228    }
1229
1230    pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
1231        self.mask_char = mask_char;
1232        self
1233    }
1234
1235    /// Whether the shadow enabled or not.
1236    pub fn with_shadow(mut self, shadow: bool) -> Self {
1237        self.shadow = shadow;
1238        self
1239    }
1240
1241    /// Sets desired shadow brush. It will be used to render the shadow.
1242    pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
1243        self.shadow_brush = brush;
1244        self
1245    }
1246
1247    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
1248    /// not percentage-based.
1249    pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
1250        self.shadow_dilation = thickness;
1251        self
1252    }
1253
1254    /// Sets desired shadow offset in units.
1255    pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
1256        self.shadow_offset = offset;
1257        self
1258    }
1259
1260    pub fn with_padding(mut self, padding: Thickness) -> Self {
1261        self.padding = padding;
1262        self
1263    }
1264
1265    /// Sets desired super sampling scaling.
1266    pub fn with_super_sampling_scaling(mut self, scaling: f32) -> Self {
1267        self.super_sampling_scaling = scaling;
1268        self
1269    }
1270
1271    /// Adds the given run to the text to set the style for a portion of the text.
1272    /// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
1273    /// sets a property that conflicts with an earlier run.
1274    pub fn with_run(mut self, run: Run) -> Self {
1275        self.runs.push(run);
1276        self
1277    }
1278
1279    /// Adds multiple runs to the text to set the style of portions of the text.
1280    /// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
1281    /// sets a property that conflicts with an earlier run.
1282    pub fn with_runs<I: IntoIterator<Item = Run>>(mut self, runs: I) -> Self {
1283        for run in runs {
1284            self.runs.push(run);
1285        }
1286        self
1287    }
1288
1289    /// The amount of indent of the first line, horizontally separating it
1290    /// from the start of the remaining lines.
1291    /// If the indent is negative, then the first line will not be indented
1292    /// while all the other lines will be indented. By default, this is 0.0.
1293    pub fn with_line_indent(mut self, indent: f32) -> Self {
1294        self.line_indent = indent;
1295        self
1296    }
1297
1298    /// The space separating each line from the line above and below.
1299    /// By default, this is 0.0.
1300    pub fn with_line_space(mut self, space: f32) -> Self {
1301        self.line_space = space;
1302        self
1303    }
1304
1305    /// A flag, that defines whether the formatted text should add ellipsis (…) to lines that goes
1306    /// outside provided bounds.
1307    pub fn with_trim_text(mut self, trim: bool) -> Self {
1308        self.trim_text = trim;
1309        self
1310    }
1311
1312    pub fn build(self) -> FormattedText {
1313        FormattedText {
1314            text: self.text.into(),
1315            lines: Vec::new(),
1316            glyphs: Vec::new(),
1317            vertical_alignment: self.vertical_alignment.into(),
1318            horizontal_alignment: self.horizontal_alignment.into(),
1319            brush: self.brush.into(),
1320            constraint: self.constraint,
1321            wrap: self.wrap.into(),
1322            mask_char: self.mask_char.into(),
1323            super_sampling_scale: self.super_sampling_scaling,
1324            font_size: self.font_size.into(),
1325            shadow: self.shadow.into(),
1326            shadow_brush: self.shadow_brush.into(),
1327            font: Some(self.font).into(),
1328            shadow_dilation: self.shadow_dilation.into(),
1329            shadow_offset: self.shadow_offset.into(),
1330            runs: self.runs,
1331            line_indent: self.line_indent.into(),
1332            line_space: self.line_space.into(),
1333            padding: self.padding.into(),
1334            total_height: 0.0,
1335            trim_text: self.trim_text.into(),
1336        }
1337    }
1338}