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