iced_graphics/geometry/
text.rs

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