iced_graphics/geometry/
text.rs

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use crate::core::alignment;
use crate::core::text::{LineHeight, Shaping};
use crate::core::{Color, Font, Pixels, Point, Size, Vector};
use crate::geometry::Path;
use crate::text;

/// A bunch of text that can be drawn to a canvas
#[derive(Debug, Clone)]
pub struct Text {
    /// The contents of the text
    pub content: String,
    /// The position of the text relative to the alignment properties.
    /// By default, this position will be relative to the top-left corner coordinate meaning that
    /// if the horizontal and vertical alignments are unchanged, this property will tell where the
    /// top-left corner of the text should be placed.
    /// By changing the horizontal_alignment and vertical_alignment properties, you are are able to
    /// change what part of text is placed at this positions.
    /// For example, when the horizontal_alignment and vertical_alignment are set to Center, the
    /// center of the text will be placed at the given position NOT the top-left coordinate.
    pub position: Point,
    /// The color of the text
    pub color: Color,
    /// The size of the text
    pub size: Pixels,
    /// The line height of the text.
    pub line_height: LineHeight,
    /// The font of the text
    pub font: Font,
    /// The horizontal alignment of the text
    pub horizontal_alignment: alignment::Horizontal,
    /// The vertical alignment of the text
    pub vertical_alignment: alignment::Vertical,
    /// The shaping strategy of the text.
    pub shaping: Shaping,
}

impl Text {
    /// Computes the [`Path`]s of the [`Text`] and draws them using
    /// the given closure.
    pub fn draw_with(&self, mut f: impl FnMut(Path, Color)) {
        let mut font_system =
            text::font_system().write().expect("Write font system");

        let mut buffer = cosmic_text::BufferLine::new(
            &self.content,
            cosmic_text::LineEnding::default(),
            cosmic_text::AttrsList::new(text::to_attributes(self.font)),
            text::to_shaping(self.shaping),
        );

        let layout = buffer.layout(
            font_system.raw(),
            self.size.0,
            None,
            cosmic_text::Wrap::None,
            None,
            4,
        );

        let translation_x = match self.horizontal_alignment {
            alignment::Horizontal::Left => self.position.x,
            alignment::Horizontal::Center | alignment::Horizontal::Right => {
                let mut line_width = 0.0f32;

                for line in layout.iter() {
                    line_width = line_width.max(line.w);
                }

                if self.horizontal_alignment == alignment::Horizontal::Center {
                    self.position.x - line_width / 2.0
                } else {
                    self.position.x - line_width
                }
            }
        };

        let translation_y = {
            let line_height = self.line_height.to_absolute(self.size);

            match self.vertical_alignment {
                alignment::Vertical::Top => self.position.y,
                alignment::Vertical::Center => {
                    self.position.y - line_height.0 / 2.0
                }
                alignment::Vertical::Bottom => self.position.y - line_height.0,
            }
        };

        let mut swash_cache = cosmic_text::SwashCache::new();

        for run in layout.iter() {
            for glyph in run.glyphs.iter() {
                let physical_glyph = glyph.physical((0.0, 0.0), 1.0);

                let start_x = translation_x + glyph.x + glyph.x_offset;
                let start_y = translation_y + glyph.y_offset + self.size.0;
                let offset = Vector::new(start_x, start_y);

                if let Some(commands) = swash_cache.get_outline_commands(
                    font_system.raw(),
                    physical_glyph.cache_key,
                ) {
                    let glyph = Path::new(|path| {
                        use cosmic_text::Command;

                        for command in commands {
                            match command {
                                Command::MoveTo(p) => {
                                    path.move_to(
                                        Point::new(p.x, -p.y) + offset,
                                    );
                                }
                                Command::LineTo(p) => {
                                    path.line_to(
                                        Point::new(p.x, -p.y) + offset,
                                    );
                                }
                                Command::CurveTo(control_a, control_b, to) => {
                                    path.bezier_curve_to(
                                        Point::new(control_a.x, -control_a.y)
                                            + offset,
                                        Point::new(control_b.x, -control_b.y)
                                            + offset,
                                        Point::new(to.x, -to.y) + offset,
                                    );
                                }
                                Command::QuadTo(control, to) => {
                                    path.quadratic_curve_to(
                                        Point::new(control.x, -control.y)
                                            + offset,
                                        Point::new(to.x, -to.y) + offset,
                                    );
                                }
                                Command::Close => {
                                    path.close();
                                }
                            }
                        }
                    });

                    f(glyph, self.color);
                } else {
                    // TODO: Raster image support for `Canvas`
                    let [r, g, b, a] = self.color.into_rgba8();

                    swash_cache.with_pixels(
                        font_system.raw(),
                        physical_glyph.cache_key,
                        cosmic_text::Color::rgba(r, g, b, a),
                        |x, y, color| {
                            f(
                                Path::rectangle(
                                    Point::new(x as f32, y as f32) + offset,
                                    Size::new(1.0, 1.0),
                                ),
                                Color::from_rgba8(
                                    color.r(),
                                    color.g(),
                                    color.b(),
                                    color.a() as f32 / 255.0,
                                ),
                            );
                        },
                    );
                }
            }
        }
    }
}

impl Default for Text {
    fn default() -> Text {
        Text {
            content: String::new(),
            position: Point::ORIGIN,
            color: Color::BLACK,
            size: Pixels(16.0),
            line_height: LineHeight::Relative(1.2),
            font: Font::default(),
            horizontal_alignment: alignment::Horizontal::Left,
            vertical_alignment: alignment::Vertical::Top,
            shaping: Shaping::Basic,
        }
    }
}

impl From<String> for Text {
    fn from(content: String) -> Text {
        Text {
            content,
            ..Default::default()
        }
    }
}

impl From<&str> for Text {
    fn from(content: &str) -> Text {
        String::from(content).into()
    }
}