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#[derive(Debug, Clone)]
9pub struct Text {
10 pub content: String,
12 pub position: Point,
21 pub color: Color,
23 pub size: Pixels,
25 pub line_height: LineHeight,
27 pub font: Font,
29 pub horizontal_alignment: alignment::Horizontal,
31 pub vertical_alignment: alignment::Vertical,
33 pub shaping: Shaping,
35}
36
37impl Text {
38 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 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}