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,
24 pub color: Color,
26 pub size: Pixels,
28 pub line_height: LineHeight,
30 pub font: Font,
32 pub align_x: alignment::Horizontal,
34 pub align_y: alignment::Vertical,
36 pub shaping: Shaping,
38}
39
40impl Text {
41 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 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}