1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use std::ops::Deref;
use std::sync::Arc;

use figures::{Displayable, Figure, Round};

use crate::color::Color;
use crate::math::{Pixels, Point};
use crate::scene::{Element, Target};
use crate::text::Font;

/// A vertical metrics measurement.
#[derive(Copy, Clone, Debug)]
pub struct VMetrics {
    /// The amount of pixels above the baseline.
    pub ascent: Figure<f32, Pixels>,
    /// The amount of pixels below the baseline. Typically a negative number.
    pub descent: Figure<f32, Pixels>,
    /// The amount of pixels to allow between lines.
    pub line_gap: Figure<f32, Pixels>,
}

impl From<rusttype::VMetrics> for VMetrics {
    fn from(value: rusttype::VMetrics) -> Self {
        Self {
            ascent: Figure::new(value.ascent),
            descent: Figure::new(value.descent),
            line_gap: Figure::new(value.line_gap),
        }
    }
}

impl VMetrics {
    /// The total height of the line.
    #[must_use]
    pub fn line_height(&self) -> Figure<f32, Pixels> {
        self.height() + self.line_gap
    }

    /// The height of the ascent and descent combined.
    #[must_use]
    pub fn height(&self) -> Figure<f32, Pixels> {
        self.ascent - self.descent
    }
}

/// A formatted span of text that is ready to render. Cheap to clone.
#[derive(Clone, Debug)]
pub struct PreparedSpan {
    /// The location of the span.
    pub location: Point<f32, Pixels>,
    data: Arc<PreparedSpanData>,
}

impl PreparedSpan {
    #[must_use]
    pub(crate) fn new(
        font: Font,
        size: Figure<f32, Pixels>,
        color: Color,
        width: Figure<f32, Pixels>,
        characters: Vec<char>,
        glyphs: Vec<GlyphInfo>,
        metrics: rusttype::VMetrics,
    ) -> Self {
        Self {
            location: Point::default(),
            data: Arc::new(PreparedSpanData {
                font,
                size,
                color,
                width,
                characters,
                glyphs,
                metrics,
            }),
        }
    }

    #[must_use]
    pub(crate) fn translate(&self, location: Point<f32, Pixels>) -> Self {
        Self {
            // We want to ensure that we are pixel-aligned when rendering a span's start.
            location: location.round(),
            data: self.data.clone(),
        }
    }

    /// Renders the text in `scene` with the baseline at `location`
    #[allow(clippy::needless_pass_by_value)]
    pub fn render_baseline_at(
        &self,
        scene: &Target,
        location: impl Displayable<f32, Pixels = Point<f32, Pixels>>,
    ) -> crate::Result<()> {
        let effective_scale_factor = scene.scale();

        let location =
            scene.offset_point_raw(location.to_pixels(effective_scale_factor) + self.location);
        scene.push_element(Element::Text {
            span: self.translate(location),
            clip: scene.clip,
        });
        Ok(())
    }
}

impl Deref for PreparedSpan {
    type Target = PreparedSpanData;

    fn deref(&self) -> &Self::Target {
        self.data.as_ref()
    }
}

/// The shared data of a [`PreparedSpan`].
#[derive(Debug)]
pub struct PreparedSpanData {
    /// The font being rendered.
    pub font: Font,
    /// The font size.
    pub size: Figure<f32, Pixels>,
    /// The color to render.
    pub color: Color,
    /// The total width of the span.
    pub width: Figure<f32, Pixels>,
    /// THe characters that compose this span.
    pub characters: Vec<char>,
    /// The glyphs that will be rendered.
    pub glyphs: Vec<GlyphInfo>,
    /// The vertical metrics of the span.
    pub metrics: rusttype::VMetrics,
}

/// Information about a font glyph
#[derive(Debug)]
pub struct GlyphInfo {
    /// The offset of the glyph within the source string.
    pub source_offset: usize,
    /// The character responsible for this glyph.
    pub source: char,
    /// The positioned glyph.
    pub glyph: rusttype::PositionedGlyph<'static>,
}

impl GlyphInfo {
    /// The width of the glyph.
    #[must_use]
    pub fn width(&self) -> Figure<f32, Pixels> {
        Figure::new(self.glyph.unpositioned().h_metrics().advance_width)
    }

    /// The location of the glyph, relative to the span start.
    #[must_use]
    pub fn location(&self) -> Point<f32, Pixels> {
        Point::new(self.glyph.position().x, self.glyph.position().y)
    }
}