iced_graphics/geometry/
text.rs

1use crate::core::alignment;
2use crate::core::text::{LineHeight, Shaping};
3use crate::core::{Color, Font, Pixels, Point, Size, Vector};
4use crate::geometry::Path;
5use crate::text;
6
7/// A bunch of text that can be drawn to a canvas
8#[derive(Debug, Clone)]
9pub struct Text {
10    /// The contents of the text
11    pub content: String,
12    /// The position of the text relative to the alignment properties.
13    /// By default, this position will be relative to the top-left corner coordinate meaning that
14    /// if the horizontal and vertical alignments are unchanged, this property will tell where the
15    /// top-left corner of the text should be placed.
16    /// By changing the horizontal_alignment and vertical_alignment properties, you are are able to
17    /// change what part of text is placed at this positions.
18    /// For example, when the horizontal_alignment and vertical_alignment are set to Center, the
19    /// center of the text will be placed at the given position NOT the top-left coordinate.
20    pub position: Point,
21    /// The color of the text
22    pub color: Color,
23    /// The size of the text
24    pub size: Pixels,
25    /// The line height of the text.
26    pub line_height: LineHeight,
27    /// The font of the text
28    pub font: Font,
29    /// The horizontal alignment of the text
30    pub horizontal_alignment: alignment::Horizontal,
31    /// The vertical alignment of the text
32    pub vertical_alignment: alignment::Vertical,
33    /// The shaping strategy of the text.
34    pub shaping: Shaping,
35}
36
37impl Text {
38    /// Computes the [`Path`]s of the [`Text`] and draws them using
39    /// the given closure.
40    pub fn draw_with(&self, mut f: impl FnMut(Path, Color)) {
41        let mut font_system =
42            text::font_system().write().expect("Write font system");
43
44        let mut buffer = cosmic_text::BufferLine::new(
45            &self.content,
46            cosmic_text::LineEnding::default(),
47            cosmic_text::AttrsList::new(&text::to_attributes(self.font)),
48            text::to_shaping(self.shaping),
49        );
50
51        let layout = buffer.layout(
52            font_system.raw(),
53            self.size.0,
54            None,
55            cosmic_text::Wrap::None,
56            None,
57            8,
58        );
59
60        let translation_x = match self.horizontal_alignment {
61            alignment::Horizontal::Left => self.position.x,
62            alignment::Horizontal::Center | alignment::Horizontal::Right => {
63                let mut line_width = 0.0f32;
64
65                for line in layout.iter() {
66                    line_width = line_width.max(line.w);
67                }
68
69                if self.horizontal_alignment == alignment::Horizontal::Center {
70                    self.position.x - line_width / 2.0
71                } else {
72                    self.position.x - line_width
73                }
74            }
75        };
76
77        let translation_y = {
78            let line_height = self.line_height.to_absolute(self.size);
79
80            match self.vertical_alignment {
81                alignment::Vertical::Top => self.position.y,
82                alignment::Vertical::Center => {
83                    self.position.y - line_height.0 / 2.0
84                }
85                alignment::Vertical::Bottom => self.position.y - line_height.0,
86            }
87        };
88
89        let mut swash_cache = cosmic_text::SwashCache::new();
90
91        for run in layout.iter() {
92            for glyph in run.glyphs.iter() {
93                let physical_glyph = glyph.physical((0.0, 0.0), 1.0);
94
95                let start_x = translation_x + glyph.x + glyph.x_offset;
96                let start_y = translation_y + glyph.y_offset + self.size.0;
97                let offset = Vector::new(start_x, start_y);
98
99                if let Some(commands) = swash_cache.get_outline_commands(
100                    font_system.raw(),
101                    physical_glyph.cache_key,
102                ) {
103                    let glyph = Path::new(|path| {
104                        use cosmic_text::Command;
105
106                        for command in commands {
107                            match command {
108                                Command::MoveTo(p) => {
109                                    path.move_to(
110                                        Point::new(p.x, -p.y) + offset,
111                                    );
112                                }
113                                Command::LineTo(p) => {
114                                    path.line_to(
115                                        Point::new(p.x, -p.y) + offset,
116                                    );
117                                }
118                                Command::CurveTo(control_a, control_b, to) => {
119                                    path.bezier_curve_to(
120                                        Point::new(control_a.x, -control_a.y)
121                                            + offset,
122                                        Point::new(control_b.x, -control_b.y)
123                                            + offset,
124                                        Point::new(to.x, -to.y) + offset,
125                                    );
126                                }
127                                Command::QuadTo(control, to) => {
128                                    path.quadratic_curve_to(
129                                        Point::new(control.x, -control.y)
130                                            + offset,
131                                        Point::new(to.x, -to.y) + offset,
132                                    );
133                                }
134                                Command::Close => {
135                                    path.close();
136                                }
137                            }
138                        }
139                    });
140
141                    f(glyph, self.color);
142                } else {
143                    // TODO: Raster image support for `Canvas`
144                    let [r, g, b, a] = self.color.into_rgba8();
145
146                    swash_cache.with_pixels(
147                        font_system.raw(),
148                        physical_glyph.cache_key,
149                        cosmic_text::Color::rgba(r, g, b, a),
150                        |x, y, color| {
151                            f(
152                                Path::rectangle(
153                                    Point::new(x as f32, y as f32) + offset,
154                                    Size::new(1.0, 1.0),
155                                ),
156                                Color::from_rgba8(
157                                    color.r(),
158                                    color.g(),
159                                    color.b(),
160                                    color.a() as f32 / 255.0,
161                                ),
162                            );
163                        },
164                    );
165                }
166            }
167        }
168    }
169}
170
171impl Default for Text {
172    fn default() -> Text {
173        Text {
174            content: String::new(),
175            position: Point::ORIGIN,
176            color: Color::BLACK,
177            size: Pixels(14.0),
178            line_height: LineHeight::default(),
179            font: Font::default(),
180            horizontal_alignment: alignment::Horizontal::Left,
181            vertical_alignment: alignment::Vertical::Top,
182            shaping: Shaping::Advanced,
183        }
184    }
185}
186
187impl From<String> for Text {
188    fn from(content: String) -> Text {
189        Text {
190            content,
191            ..Default::default()
192        }
193    }
194}
195
196impl From<&str> for Text {
197    fn from(content: &str) -> Text {
198        String::from(content).into()
199    }
200}