Skip to main content

speedy2d/
font.rs

1/*
2 *  Copyright 2021 QuantumBadger
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17use std::collections::VecDeque;
18use std::convert::TryInto;
19use std::fmt::{Debug, Formatter};
20use std::hash::{Hash, Hasher};
21use std::iter::Peekable;
22use std::ops::Deref;
23use std::slice::Iter;
24use std::sync::atomic::{AtomicUsize, Ordering};
25use std::sync::Arc;
26use std::vec::IntoIter;
27
28use rusttype::Scale;
29use smallvec::{smallvec, SmallVec};
30use unicode_normalization::UnicodeNormalization;
31
32use crate::dimen::{Vec2, Vector2};
33use crate::error::{BacktraceError, ErrorMessage};
34use crate::shape::{Rect, Rectangle};
35
36static FONT_ID_GENERATOR: AtomicUsize = AtomicUsize::new(10000);
37
38/// Type returned by the [FormattedGlyph::user_index()] function.
39///
40/// The `user_index` field allows you to determine which output glyph
41/// corresponds to which input codepoint.
42pub type UserGlyphIndex = u32;
43
44/// An internal identifier for a font. Each font which is loaded receives a
45/// unique ID.
46pub type FontId = usize;
47
48type FormattedGlyphVec = SmallVec<[FormattedGlyph; 8]>;
49type FormattedTextLineVec = SmallVec<[FormattedTextLine; 1]>;
50
51/// A struct representing a Unicode codepoint, for the purposes of text layout.
52/// The `user_index` field allows you to determine which output glyph
53/// corresponds to which input codepoint.
54#[derive(Debug, Hash, Eq, PartialEq, Clone)]
55pub struct Codepoint
56{
57    user_index: UserGlyphIndex,
58    codepoint: char
59}
60
61impl Codepoint
62{
63    /// The Unicode codepoint for a zero width space. You may use this to denote
64    /// places where it would be appropriate to insert a line break when
65    /// wrapping.
66    pub const ZERO_WIDTH_SPACE: char = '\u{200B}';
67
68    /// Instantiates a new `Codepoint`. The value provided for `user_index` will
69    /// be present in the corresponding `FormattedGlyph` object returned
70    /// during layout.
71    #[inline]
72    #[must_use]
73    pub fn new(user_index: UserGlyphIndex, codepoint: char) -> Self
74    {
75        Codepoint {
76            user_index,
77            codepoint
78        }
79    }
80
81    fn from_unindexed_codepoints(unindexed_codepoints: &[char]) -> Vec<Self>
82    {
83        let mut codepoints = Vec::with_capacity(unindexed_codepoints.len());
84
85        for (i, codepoint) in unindexed_codepoints.iter().enumerate() {
86            codepoints.push(Codepoint::new(i.try_into().unwrap(), *codepoint));
87        }
88
89        codepoints
90    }
91}
92
93#[derive(Debug, Eq, PartialEq, Clone, Hash)]
94struct RenderableWord
95{
96    codepoints: Vec<Codepoint>,
97    is_whitespace: bool
98}
99
100impl RenderableWord
101{
102    fn starting_from_codepoint_location(mut self, location: usize) -> Self
103    {
104        self.codepoints.drain(0..location);
105
106        RenderableWord {
107            codepoints: self.codepoints,
108            is_whitespace: self.is_whitespace
109        }
110    }
111}
112
113#[derive(Debug, Eq, PartialEq, Clone, Hash)]
114enum Word
115{
116    Renderable(RenderableWord),
117    Newline
118}
119
120impl Word
121{
122    fn split_words(codepoints: &[Codepoint]) -> Vec<Word>
123    {
124        let mut reader = codepoints.iter().peekable();
125
126        let mut result = Vec::new();
127
128        while let Some(first_token) = reader.next() {
129            match first_token.codepoint {
130                Codepoint::ZERO_WIDTH_SPACE | '\r' => {
131                    // Do nothing here, just ignore it
132                }
133
134                '\n' => result.push(Word::Newline),
135
136                ' ' | '\t' => {
137                    result.push(Word::Renderable(RenderableWord {
138                        codepoints: vec![first_token.clone()],
139                        is_whitespace: true
140                    }));
141                }
142
143                _ => {
144                    // Non-whitespace word
145
146                    let mut word_codepoints = Vec::with_capacity(16);
147                    word_codepoints.push(first_token.clone());
148
149                    while let Some(next) = reader.peek() {
150                        match next.codepoint {
151                            ' ' | '\t' | '\r' | '\n' | Codepoint::ZERO_WIDTH_SPACE => {
152                                break
153                            }
154                            _ => word_codepoints.push(reader.next().unwrap().clone())
155                        }
156                    }
157
158                    result.push(Word::Renderable(RenderableWord {
159                        codepoints: word_codepoints,
160                        is_whitespace: false
161                    }));
162                }
163            }
164        }
165
166        result
167    }
168}
169
170/// A struct representing a glyph in a font.
171pub struct FontGlyph
172{
173    glyph: rusttype::Glyph<'static>,
174    font: Font
175}
176
177struct WordsIterator
178{
179    words: Peekable<IntoIter<Word>>,
180    pending: VecDeque<Word>
181}
182
183impl WordsIterator
184{
185    fn from(words: Vec<Word>) -> Self
186    {
187        WordsIterator {
188            words: words.into_iter().peekable(),
189            pending: VecDeque::new()
190        }
191    }
192
193    #[inline]
194    #[must_use]
195    fn has_next(&self) -> bool
196    {
197        self.words.len() > 0 || !self.pending.is_empty()
198    }
199
200    #[inline]
201    #[must_use]
202    fn peek(&mut self) -> Option<&Word>
203    {
204        if let Some(word) = self.pending.front() {
205            return Some(word);
206        }
207
208        if let Some(word) = self.words.peek() {
209            return Some(word);
210        }
211
212        None
213    }
214
215    #[inline]
216    fn next(&mut self) -> Option<Word>
217    {
218        if let Some(word) = self.pending.pop_front() {
219            return Some(word);
220        }
221
222        if let Some(word) = self.words.next() {
223            return Some(word);
224        }
225
226        None
227    }
228
229    #[inline]
230    fn add_pending(&mut self, word: Word)
231    {
232        self.pending.push_back(word);
233    }
234}
235
236#[derive(Clone, Debug)]
237struct LineLayoutMetrics
238{
239    x_pos: f32,
240    max_ascent: f32,
241    min_descent: f32,
242    max_line_gap: f32,
243    last_glyph_id: Option<rusttype::GlyphId>,
244    last_font_id: Option<FontId>
245}
246
247impl LineLayoutMetrics
248{
249    fn new() -> Self
250    {
251        LineLayoutMetrics {
252            x_pos: 0.0,
253            max_ascent: 0.0,
254            min_descent: 0.0,
255            max_line_gap: 0.0,
256            last_glyph_id: None,
257            last_font_id: None
258        }
259    }
260
261    #[inline]
262    #[must_use]
263    fn height(&self) -> f32
264    {
265        self.max_ascent - self.min_descent
266    }
267
268    fn update_and_get_render_pos_x(
269        &mut self,
270        glyph: &rusttype::ScaledGlyph,
271        font_id: FontId,
272        scale: &Scale,
273        options: &TextOptions
274    ) -> f32
275    {
276        if let Some(last_glyph_id) = self.last_glyph_id {
277            if self.last_font_id == Some(font_id) {
278                self.x_pos +=
279                    glyph.font().pair_kerning(*scale, last_glyph_id, glyph.id());
280            }
281
282            self.x_pos += options.tracking;
283        }
284
285        if self.last_font_id != Some(font_id) {
286            let v_metrics = glyph.font().v_metrics(*scale);
287
288            self.max_ascent = crate::numeric::max(self.max_ascent, v_metrics.ascent);
289            self.min_descent = crate::numeric::min(self.min_descent, v_metrics.descent);
290            self.max_line_gap =
291                crate::numeric::max(self.max_line_gap, v_metrics.line_gap);
292        }
293
294        let advance_width = glyph.h_metrics().advance_width;
295
296        let glyph_x_pos_start = self.x_pos;
297        self.x_pos += advance_width;
298
299        self.last_font_id = Some(font_id);
300        self.last_glyph_id = Some(glyph.id());
301
302        glyph_x_pos_start
303    }
304}
305
306enum WordLayoutResult
307{
308    Success(LineLayoutMetrics),
309    PartialWord(LineLayoutMetrics),
310    NotEnoughSpace
311}
312
313impl WordLayoutResult
314{
315    fn get_metrics(&self) -> Option<&LineLayoutMetrics>
316    {
317        match self {
318            WordLayoutResult::Success(metrics) => Some(metrics),
319            WordLayoutResult::PartialWord(metrics) => Some(metrics),
320            WordLayoutResult::NotEnoughSpace => None
321        }
322    }
323
324    fn end_of_line(&self) -> bool
325    {
326        match self {
327            WordLayoutResult::Success(_) => false,
328            WordLayoutResult::PartialWord(_) => true,
329            WordLayoutResult::NotEnoughSpace => true
330        }
331    }
332}
333
334#[allow(clippy::too_many_arguments)]
335fn try_layout_word_internal<T: TextLayout + ?Sized>(
336    layout_helper: &T,
337    word: RenderableWord,
338    remaining_words: &mut WordsIterator,
339    scale: &Scale,
340    options: &TextOptions,
341    pos_y_baseline: f32,
342    first_word_on_line: bool,
343    previous_metrics: &LineLayoutMetrics,
344    output: &mut FormattedGlyphVec
345) -> WordLayoutResult
346{
347    let mut new_word_metrics = previous_metrics.clone();
348    let pos_x_max = options.wrap_words_after_width;
349
350    let mut glyphs = FormattedGlyphVec::new();
351
352    for (
353        i,
354        Codepoint {
355            user_index,
356            codepoint: c
357        }
358    ) in word.codepoints.iter().enumerate()
359    {
360        // We can't modify the actual values until we're sure we can render this glyph
361        let mut new_glyph_metrics = new_word_metrics.clone();
362
363        let glyph = match layout_helper.lookup_glyph_for_codepoint(*c) {
364            None => {
365                match layout_helper
366                    .lookup_glyph_for_codepoint('□')
367                    .or_else(|| layout_helper.lookup_glyph_for_codepoint('?'))
368                {
369                    None => continue,
370                    Some(glyph) => glyph
371                }
372            }
373            Some(glyph) => glyph
374        };
375
376        let scaled_glyph = glyph.glyph.scaled(*scale);
377
378        let glyph_x_pos_start = new_glyph_metrics.update_and_get_render_pos_x(
379            &scaled_glyph,
380            glyph.font.id(),
381            scale,
382            options
383        );
384
385        let formatted_glyph = FormattedGlyph {
386            user_index: *user_index,
387            glyph: scaled_glyph.positioned(rusttype::point(glyph_x_pos_start, 0.0)),
388            font_id: glyph.font.id()
389        };
390
391        if let Some(pos_x_max) = pos_x_max {
392            if new_glyph_metrics.x_pos > pos_x_max {
393                return if first_word_on_line {
394                    if i == 0 {
395                        // First glyph in word, we should render it even though it goes
396                        // over the boundary
397                        glyphs.push(formatted_glyph);
398                        new_word_metrics = new_glyph_metrics;
399
400                        // If there are more codepoints, we need to split the word
401                        if word.codepoints.len() > 1 {
402                            remaining_words.add_pending(Word::Renderable(
403                                word.starting_from_codepoint_location(i + 1)
404                            ));
405                        }
406                    } else {
407                        remaining_words.add_pending(Word::Renderable(
408                            word.starting_from_codepoint_location(i)
409                        ));
410                    }
411
412                    glyphs.iter_mut().for_each(|glyph| {
413                        glyph.reposition_y(pos_y_baseline + new_word_metrics.max_ascent);
414                    });
415
416                    output.append(&mut glyphs);
417                    WordLayoutResult::PartialWord(new_word_metrics)
418                } else {
419                    remaining_words.add_pending(Word::Renderable(word));
420                    WordLayoutResult::NotEnoughSpace
421                };
422            }
423        }
424
425        glyphs.push(formatted_glyph);
426        new_word_metrics = new_glyph_metrics;
427    }
428
429    glyphs.iter_mut().for_each(|glyph| {
430        glyph.reposition_y(pos_y_baseline + new_word_metrics.max_ascent);
431    });
432
433    output.append(&mut glyphs);
434
435    WordLayoutResult::Success(new_word_metrics)
436}
437
438fn layout_line_internal<T: TextLayout + ?Sized>(
439    layout_helper: &T,
440    words: &mut WordsIterator,
441    scale: &Scale,
442    options: &TextOptions,
443    pos_y_baseline: f32
444) -> FormattedTextLine
445{
446    let mut line_metrics = LineLayoutMetrics::new();
447    let mut glyphs = SmallVec::new();
448
449    let mut first_word_on_line = true;
450
451    if options.trim_each_line {
452        // Skip whitespace
453        while let Some(Word::Renderable(word)) = words.peek() {
454            if word.is_whitespace {
455                words.next().unwrap();
456            } else {
457                break;
458            }
459        }
460    }
461
462    while let Some(Word::Renderable(word)) = words.next() {
463        let result = try_layout_word_internal(
464            layout_helper,
465            word,
466            words,
467            scale,
468            options,
469            pos_y_baseline,
470            first_word_on_line,
471            &line_metrics,
472            &mut glyphs
473        );
474
475        if let Some(metrics) = result.get_metrics() {
476            line_metrics = metrics.clone();
477        }
478
479        if result.end_of_line() {
480            break;
481        }
482
483        first_word_on_line = false;
484    }
485
486    if glyphs.is_empty() {
487        let empty_metrics = layout_helper.empty_line_vertical_metrics(scale.y);
488        line_metrics.max_ascent = empty_metrics.ascent;
489        line_metrics.min_descent = empty_metrics.descent;
490        line_metrics.max_line_gap = empty_metrics.line_gap;
491    }
492
493    if let Some(max_width) = options.wrap_words_after_width {
494        let offset_x = match options.alignment {
495            TextAlignment::Left => None,
496            TextAlignment::Center => Some((max_width - line_metrics.x_pos) / 2.0),
497            TextAlignment::Right => Some(max_width - line_metrics.x_pos)
498        };
499
500        if let Some(offset_x) = offset_x {
501            for glyph in glyphs.iter_mut() {
502                glyph.add_offset_x(offset_x);
503            }
504        }
505    }
506
507    FormattedTextLine {
508        glyphs: Arc::new(glyphs),
509        baseline_vertical_position: pos_y_baseline,
510        width: line_metrics.x_pos,
511        height: line_metrics.height(),
512        ascent: line_metrics.max_ascent,
513        descent: line_metrics.min_descent,
514        line_gap: line_metrics.max_line_gap
515    }
516}
517
518fn layout_multiple_lines_internal<T: TextLayout + ?Sized>(
519    layout_helper: &T,
520    codepoints: &[Codepoint],
521    scale: f32,
522    options: TextOptions
523) -> FormattedTextBlock
524{
525    let scale = Scale::uniform(scale);
526
527    let mut iterator = WordsIterator::from(Word::split_words(codepoints));
528
529    let mut pos_y = 0.0;
530    let mut lines = SmallVec::new();
531
532    let mut width = 0.0;
533
534    while iterator.has_next() {
535        let line =
536            layout_line_internal(layout_helper, &mut iterator, &scale, &options, pos_y);
537
538        pos_y += line.height * options.line_spacing_multiplier;
539
540        if iterator.has_next() {
541            pos_y += line.line_gap * options.line_spacing_multiplier;
542        }
543
544        width = crate::numeric::max(width, line.width);
545
546        lines.push(line);
547    }
548
549    FormattedTextBlock {
550        lines: Arc::new(lines),
551        width,
552        height: pos_y
553    }
554}
555
556/// The vertical metrics of a line of text.
557#[derive(Debug, Clone, PartialEq)]
558pub struct LineVerticalMetrics
559{
560    /// The ascent of the line in pixels.
561    ascent: f32,
562    /// The descent of the line in pixels.
563    descent: f32,
564    /// The gap between this line and the next line, in pixels.
565    line_gap: f32
566}
567
568impl LineVerticalMetrics
569{
570    /// The height of the line in pixels.
571    pub fn height(&self) -> f32
572    {
573        self.ascent - self.descent
574    }
575}
576
577/// Objects implementing this trait are able to lay out text, ready for
578/// rendering.
579pub trait TextLayout
580{
581    /// Returns the glyph corresponding to the provided codepoint. If the glyph
582    /// cannot be found, `None` is returned.
583    fn lookup_glyph_for_codepoint(&self, codepoint: char) -> Option<FontGlyph>;
584
585    /// Lays out a block of text with the specified scale and options. The
586    /// result may be passed to `Graphics2D::draw_text`.
587    ///
588    /// As the string undergoes normalization before being laid out, the
589    /// `user_index` of each `FormattedGlyph` is undefined. To gain control
590    /// over the `user_index` field, consider using
591    /// either `layout_text_line_from_codepoints()` or
592    /// `layout_text_line_from_unindexed_codepoints()`.
593    #[inline]
594    #[must_use]
595    fn layout_text(
596        &self,
597        text: &str,
598        scale: f32,
599        options: TextOptions
600    ) -> FormattedTextBlock
601    {
602        let codepoints: Vec<char> = text.nfc().collect();
603        self.layout_text_from_unindexed_codepoints(codepoints.as_slice(), scale, options)
604    }
605
606    /// Lays out a block of text with the specified scale and options. The
607    /// result may be passed to `Graphics2D::draw_text`.
608    ///
609    /// The `user_index` field of each `FormattedGlyph` will be set to the
610    /// location of the input codepoint in `unindexed_codepoints`, starting
611    /// from zero.
612    #[inline]
613    #[must_use]
614    fn layout_text_from_unindexed_codepoints(
615        &self,
616        unindexed_codepoints: &[char],
617        scale: f32,
618        options: TextOptions
619    ) -> FormattedTextBlock
620    {
621        self.layout_text_from_codepoints(
622            Codepoint::from_unindexed_codepoints(unindexed_codepoints).as_slice(),
623            scale,
624            options
625        )
626    }
627
628    /// Lays out a block of text with the specified scale and options. The
629    /// result may be passed to `Graphics2D::draw_text`.
630    ///
631    /// The `user_index` field of each `FormattedGlyph` will be set to the
632    /// `user_index` of the corresponding `Codepoint`.
633    #[must_use]
634    fn layout_text_from_codepoints(
635        &self,
636        codepoints: &[Codepoint],
637        scale: f32,
638        options: TextOptions
639    ) -> FormattedTextBlock
640    {
641        layout_multiple_lines_internal(self, codepoints, scale, options)
642    }
643
644    /// The default metrics of a line which contains no characters.
645    #[must_use]
646    fn empty_line_vertical_metrics(&self, scale: f32) -> LineVerticalMetrics;
647}
648
649/// A struct representing a font.
650#[derive(Clone)]
651pub struct Font
652{
653    id: usize,
654    font: Arc<rusttype::Font<'static>>
655}
656
657impl Font
658{
659    /// Constructs a new font from the specified bytes.
660    ///
661    /// The font may be in TrueType or OpenType format. Support for OpenType
662    /// fonts may be limited.
663    pub fn new(bytes: &[u8]) -> Result<Font, BacktraceError<ErrorMessage>>
664    {
665        let font = rusttype::Font::try_from_vec(bytes.to_vec())
666            .ok_or_else(|| ErrorMessage::msg("Failed to load font"))?;
667
668        Ok(Font {
669            id: FONT_ID_GENERATOR.fetch_add(1, Ordering::SeqCst),
670            font: Arc::new(font)
671        })
672    }
673
674    #[inline]
675    fn id(&self) -> usize
676    {
677        self.id
678    }
679
680    #[inline]
681    fn font(&self) -> &rusttype::Font<'static>
682    {
683        &self.font
684    }
685}
686
687impl TextLayout for FontFamily
688{
689    fn lookup_glyph_for_codepoint(&self, codepoint: char) -> Option<FontGlyph>
690    {
691        for font in &*self.fonts {
692            if let Some(glyph) = font.lookup_glyph_for_codepoint(codepoint) {
693                return Some(glyph);
694            }
695        }
696
697        None
698    }
699
700    fn empty_line_vertical_metrics(&self, scale: f32) -> LineVerticalMetrics
701    {
702        match Arc::deref(&self.fonts).first() {
703            None => LineVerticalMetrics {
704                ascent: 0.0,
705                descent: 0.0,
706                line_gap: 0.0
707            },
708            Some(font) => {
709                let metrics = font.font.v_metrics(Scale::uniform(scale));
710                LineVerticalMetrics {
711                    ascent: metrics.ascent,
712                    descent: metrics.descent,
713                    line_gap: metrics.line_gap
714                }
715            }
716        }
717    }
718}
719
720impl TextLayout for Font
721{
722    fn lookup_glyph_for_codepoint(&self, codepoint: char) -> Option<FontGlyph>
723    {
724        let glyph = self.font().glyph(codepoint);
725
726        if glyph.id().0 == 0 {
727            None
728        } else {
729            Some(FontGlyph {
730                glyph,
731                font: self.clone()
732            })
733        }
734    }
735
736    fn empty_line_vertical_metrics(&self, scale: f32) -> LineVerticalMetrics
737    {
738        let metrics = self.font.v_metrics(Scale::uniform(scale));
739        LineVerticalMetrics {
740            ascent: metrics.ascent,
741            descent: metrics.descent,
742            line_gap: metrics.line_gap
743        }
744    }
745}
746
747impl PartialEq for Font
748{
749    #[inline]
750    fn eq(&self, other: &Self) -> bool
751    {
752        self.id() == other.id()
753    }
754}
755
756impl Eq for Font {}
757
758impl Hash for Font
759{
760    #[inline]
761    fn hash<H: Hasher>(&self, state: &mut H)
762    {
763        self.id().hash(state);
764    }
765}
766
767impl Debug for Font
768{
769    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result
770    {
771        f.debug_struct("Font").field("id", &self.id()).finish()
772    }
773}
774
775/// A collection of fonts, in decreasing order of priority. When laying out
776/// text, if a codepoint cannot be found in the first font in the list, the
777/// subsequent fonts will also be searched.
778#[derive(Clone, Debug, PartialEq, Eq, Hash)]
779pub struct FontFamily
780{
781    fonts: Arc<Vec<Font>>
782}
783
784impl FontFamily
785{
786    /// Instantiates a new font family, containing the specified fonts in
787    /// decreasing order of priority.
788    #[must_use]
789    pub fn new(fonts: Vec<Font>) -> Self
790    {
791        FontFamily {
792            fonts: Arc::new(fonts)
793        }
794    }
795}
796
797/// The horizontal alignment of a block of text. This can be set when calling
798/// `TextOptions::with_wrap_words_after_width`.
799#[derive(Clone, Debug, Hash, Eq, PartialEq)]
800pub enum TextAlignment
801{
802    /// Align the text to the left.
803    Left,
804    /// Center the text in the maximum width.
805    Center,
806    /// Align the text to the rightmost point within the maximum width.
807    Right
808}
809
810/// A series of options for specifying how text should be laid out.
811pub struct TextOptions
812{
813    tracking: f32,
814    wrap_words_after_width: Option<f32>,
815    alignment: TextAlignment,
816    line_spacing_multiplier: f32,
817    trim_each_line: bool
818}
819
820impl TextOptions
821{
822    /// Instantiates a new `TextOptions` with the default settings.
823    #[inline]
824    #[must_use]
825    pub fn new() -> Self
826    {
827        TextOptions {
828            tracking: 0.0,
829            wrap_words_after_width: None,
830            alignment: TextAlignment::Left,
831            line_spacing_multiplier: 1.0,
832            trim_each_line: true
833        }
834    }
835
836    /// Sets the tracking of the font. This is the amount of extra space (in
837    /// pixels) to put between each character.
838    ///
839    /// The default is `0.0`.
840    #[inline]
841    #[must_use]
842    pub fn with_tracking(mut self, tracking: f32) -> Self
843    {
844        self.tracking = tracking;
845        self
846    }
847
848    /// Limits the width of the text block to the specified pixel value,
849    /// wrapping words to a new line if they exceed that limit.
850    ///
851    /// This function also sets the alignment, within the specified width.
852    ///
853    /// The default is to not wrap text.
854    #[inline]
855    #[must_use]
856    pub fn with_wrap_to_width(
857        mut self,
858        wrap_words_after_width_px: f32,
859        alignment: TextAlignment
860    ) -> Self
861    {
862        self.wrap_words_after_width = Some(wrap_words_after_width_px);
863        self.alignment = alignment;
864        self
865    }
866
867    /// Sets the amount of space between each line of text. The gap between the
868    /// baseline of each line of text is multiplied by this value.
869    ///
870    /// The default is `1.0`.
871    #[inline]
872    #[must_use]
873    pub fn with_line_spacing_multiplier(mut self, line_spacing_multiplier: f32) -> Self
874    {
875        self.line_spacing_multiplier = line_spacing_multiplier;
876        self
877    }
878
879    /// True if whitespace should be trimmed at the beginning of each line,
880    /// false to preserve whitespace.
881    ///
882    /// The default is `true`.
883    #[inline]
884    #[must_use]
885    pub fn with_trim_each_line(mut self, trim_each_line: bool) -> Self
886    {
887        self.trim_each_line = trim_each_line;
888        self
889    }
890}
891
892impl Default for TextOptions
893{
894    fn default() -> Self
895    {
896        Self::new()
897    }
898}
899
900/// Represents a glyph which has been laid out as part of a line of text.
901#[derive(Clone)]
902pub struct FormattedGlyph
903{
904    glyph: rusttype::PositionedGlyph<'static>,
905    font_id: FontId,
906    user_index: UserGlyphIndex
907}
908
909impl FormattedGlyph
910{
911    #[inline]
912    #[must_use]
913    pub(crate) fn glyph(&self) -> &rusttype::PositionedGlyph<'static>
914    {
915        &self.glyph
916    }
917
918    /// The identifier of the font which was used to render this glyph.
919    #[inline]
920    #[must_use]
921    pub fn font_id(&self) -> FontId
922    {
923        self.font_id
924    }
925
926    /// The `user_index` of the corresponding `Codepoint`. This allows you to
927    /// identify which input `Codepoint` corresponds to the output
928    /// `FormattedGlyph`.
929    #[inline]
930    #[must_use]
931    pub fn user_index(&self) -> UserGlyphIndex
932    {
933        self.user_index
934    }
935
936    /// The `x` coordinate of this glyph, relative to the start of the line
937    #[inline]
938    #[must_use]
939    pub fn position_x(&self) -> f32
940    {
941        self.glyph.position().x
942    }
943
944    /// The character's advance width. In the absence of any kerning
945    /// information, this would represent the horizontal distance between
946    /// the position of this character, and the position of the next
947    /// character.
948    #[inline]
949    #[must_use]
950    pub fn advance_width(&self) -> f32
951    {
952        self.glyph.unpositioned().h_metrics().advance_width
953    }
954
955    /// The bounding box of this glyph in pixels. This encloses the
956    /// total renderable area of the glyph.
957    ///
958    /// Some glyphs, such as a space, might not render anything at all -- in
959    /// this case, this function will return `None`.
960    #[inline]
961    #[must_use]
962    pub fn pixel_bounding_box(&self) -> Option<Rect>
963    {
964        self.glyph.pixel_bounding_box().map(|r| {
965            Rect::from_tuples(
966                (r.min.x as f32, r.min.y as f32),
967                (r.max.x as f32, r.max.y as f32)
968            )
969        })
970    }
971
972    #[inline]
973    fn reposition_y(&mut self, y_pos: f32)
974    {
975        let existing_pos = self.glyph.position();
976        self.glyph
977            .set_position(rusttype::point(existing_pos.x, y_pos));
978    }
979
980    #[inline]
981    fn add_offset_x(&mut self, offset_x: f32)
982    {
983        let existing_pos = self.glyph.position();
984        self.glyph
985            .set_position(rusttype::point(existing_pos.x + offset_x, existing_pos.y));
986    }
987}
988
989/// Represents a block of text which has been laid out.
990#[derive(Clone)]
991pub struct FormattedTextBlock
992{
993    lines: Arc<FormattedTextLineVec>,
994    width: f32,
995    height: f32
996}
997
998impl FormattedTextBlock
999{
1000    /// Iterate over the lines of text in this block.
1001    #[inline]
1002    pub fn iter_lines(&self) -> Iter<'_, FormattedTextLine>
1003    {
1004        self.lines.iter()
1005    }
1006
1007    /// The width (in pixels) of this text block.
1008    #[inline]
1009    #[must_use]
1010    pub fn width(&self) -> f32
1011    {
1012        self.width
1013    }
1014
1015    /// The height (in pixels) of this text block.
1016    #[inline]
1017    #[must_use]
1018    pub fn height(&self) -> f32
1019    {
1020        self.height
1021    }
1022
1023    /// The size (in pixels) of this text block.
1024    #[inline]
1025    #[must_use]
1026    pub fn size(&self) -> Vec2
1027    {
1028        Vec2::new(self.width, self.height)
1029    }
1030}
1031
1032/// Represents a line of text which has been laid out as part of a block.
1033#[derive(Clone)]
1034pub struct FormattedTextLine
1035{
1036    glyphs: Arc<FormattedGlyphVec>,
1037    baseline_vertical_position: f32,
1038    width: f32,
1039    height: f32,
1040    ascent: f32,
1041    descent: f32,
1042    line_gap: f32
1043}
1044
1045impl FormattedTextLine
1046{
1047    /// Iterate over the glyphs in this line.
1048    #[inline]
1049    pub fn iter_glyphs(&self) -> Iter<'_, FormattedGlyph>
1050    {
1051        self.glyphs.iter()
1052    }
1053
1054    /// Convert this line of text into an individually-renderable block (while
1055    /// maintaining the same vertical offset).
1056    #[inline]
1057    #[must_use]
1058    pub fn as_block(&self) -> FormattedTextBlock
1059    {
1060        FormattedTextBlock {
1061            lines: Arc::new(smallvec![self.clone()]),
1062            width: self.width,
1063            height: self.height
1064        }
1065    }
1066
1067    /// The width (in pixels) of this text line.
1068    #[inline]
1069    #[must_use]
1070    pub fn width(&self) -> f32
1071    {
1072        self.width
1073    }
1074
1075    /// The height (in pixels) of this text line. This is equal to the
1076    /// `ascent()` minus the `descent()`.
1077    #[inline]
1078    #[must_use]
1079    pub fn height(&self) -> f32
1080    {
1081        self.height
1082    }
1083
1084    /// The ascent (in pixels) of this text line. This is the maximum height of
1085    /// each glyph above the text baseline.
1086    #[inline]
1087    #[must_use]
1088    pub fn ascent(&self) -> f32
1089    {
1090        self.ascent
1091    }
1092
1093    /// The descent (in pixels) of this text line. This is the furthest distance
1094    /// of each glyph below the text baseline.
1095    ///
1096    /// This is negative: a value of `-10.0` means the font can descend `10`
1097    /// pixels below the baseline.
1098    #[inline]
1099    #[must_use]
1100    pub fn descent(&self) -> f32
1101    {
1102        self.descent
1103    }
1104
1105    /// The recommended gap to put between each line of text, as encoded by the
1106    /// font authors.
1107    #[inline]
1108    #[must_use]
1109    pub fn line_gap(&self) -> f32
1110    {
1111        self.line_gap
1112    }
1113
1114    /// The vertical position of this line's baseline within the block of text.
1115    #[inline]
1116    #[must_use]
1117    pub fn baseline_position(&self) -> f32
1118    {
1119        self.baseline_vertical_position
1120    }
1121}
1122
1123impl<T: Copy> From<&rusttype::Rect<T>> for Rectangle<T>
1124{
1125    #[inline]
1126    fn from(rect: &rusttype::Rect<T>) -> Self
1127    {
1128        Rectangle::new(
1129            Vector2::new(rect.min.x, rect.min.y),
1130            Vector2::new(rect.max.x, rect.max.y)
1131        )
1132    }
1133}
1134
1135#[cfg(test)]
1136mod test
1137{
1138    use super::*;
1139
1140    #[test]
1141    fn test_word_split_1()
1142    {
1143        let codepoints = Codepoint::from_unindexed_codepoints(&['a', 'b', ' ', 'c', 'd']);
1144
1145        let words = Word::split_words(&codepoints);
1146
1147        assert_eq!(
1148            vec![
1149                Word::Renderable(RenderableWord {
1150                    codepoints: vec![Codepoint::new(0, 'a'), Codepoint::new(1, 'b')],
1151                    is_whitespace: false
1152                }),
1153                Word::Renderable(RenderableWord {
1154                    codepoints: vec![Codepoint::new(2, ' ')],
1155                    is_whitespace: true
1156                }),
1157                Word::Renderable(RenderableWord {
1158                    codepoints: vec![Codepoint::new(3, 'c'), Codepoint::new(4, 'd')],
1159                    is_whitespace: false
1160                })
1161            ],
1162            words
1163        )
1164    }
1165
1166    #[test]
1167    fn test_word_split_2()
1168    {
1169        let codepoints = Codepoint::from_unindexed_codepoints(&[
1170            'a', 'b', '\t', ' ', '\n', 'c', 'd', '\n', '\n', ' '
1171        ]);
1172
1173        let words = Word::split_words(&codepoints);
1174
1175        assert_eq!(
1176            vec![
1177                Word::Renderable(RenderableWord {
1178                    codepoints: vec![Codepoint::new(0, 'a'), Codepoint::new(1, 'b')],
1179                    is_whitespace: false
1180                }),
1181                Word::Renderable(RenderableWord {
1182                    codepoints: vec![Codepoint::new(2, '\t'),],
1183                    is_whitespace: true
1184                }),
1185                Word::Renderable(RenderableWord {
1186                    codepoints: vec![Codepoint::new(3, ' '),],
1187                    is_whitespace: true
1188                }),
1189                Word::Newline,
1190                Word::Renderable(RenderableWord {
1191                    codepoints: vec![Codepoint::new(5, 'c'), Codepoint::new(6, 'd')],
1192                    is_whitespace: false
1193                }),
1194                Word::Newline,
1195                Word::Newline,
1196                Word::Renderable(RenderableWord {
1197                    codepoints: vec![Codepoint::new(9, ' ')],
1198                    is_whitespace: true
1199                })
1200            ],
1201            words
1202        )
1203    }
1204}