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#[derive(Debug, Clone)]
10pub struct Text {
11 pub content: String,
13 pub position: Point,
25 pub max_width: f32,
29 pub color: Color,
31 pub size: Pixels,
33 pub line_height: LineHeight,
35 pub font: Font,
37 pub align_x: Alignment,
39 pub align_y: alignment::Vertical,
41 pub shaping: Shaping,
43}
44
45impl Text {
46 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 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}