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 hint_factor: None,
60 });
61
62 let translation_x = match self.align_x {
63 Alignment::Default | Alignment::Left | Alignment::Justified => self.position.x,
64 Alignment::Center => self.position.x - paragraph.min_width() / 2.0,
65 Alignment::Right => self.position.x - paragraph.min_width(),
66 };
67
68 let translation_y = {
69 match self.align_y {
70 alignment::Vertical::Top => self.position.y,
71 alignment::Vertical::Center => self.position.y - paragraph.min_height() / 2.0,
72 alignment::Vertical::Bottom => self.position.y - paragraph.min_height(),
73 }
74 };
75
76 let buffer = paragraph.buffer();
77 let mut swash_cache = cosmic_text::SwashCache::new();
78
79 let mut font_system = text::font_system().write().expect("Write font system");
80
81 for run in buffer.layout_runs() {
82 for glyph in run.glyphs.iter() {
83 let physical_glyph = glyph.physical((0.0, 0.0), 1.0);
84
85 let start_x = translation_x + glyph.x + glyph.x_offset;
86 let start_y = translation_y + glyph.y_offset + run.line_y;
87 let offset = Vector::new(start_x, start_y);
88
89 if let Some(commands) =
90 swash_cache.get_outline_commands(font_system.raw(), physical_glyph.cache_key)
91 {
92 let glyph = Path::new(|path| {
93 use cosmic_text::Command;
94
95 for command in commands {
96 match command {
97 Command::MoveTo(p) => {
98 path.move_to(Point::new(p.x, -p.y) + offset);
99 }
100 Command::LineTo(p) => {
101 path.line_to(Point::new(p.x, -p.y) + offset);
102 }
103 Command::CurveTo(control_a, control_b, to) => {
104 path.bezier_curve_to(
105 Point::new(control_a.x, -control_a.y) + offset,
106 Point::new(control_b.x, -control_b.y) + offset,
107 Point::new(to.x, -to.y) + offset,
108 );
109 }
110 Command::QuadTo(control, to) => {
111 path.quadratic_curve_to(
112 Point::new(control.x, -control.y) + offset,
113 Point::new(to.x, -to.y) + offset,
114 );
115 }
116 Command::Close => {
117 path.close();
118 }
119 }
120 }
121 });
122
123 f(glyph, self.color);
124 } else {
125 let [r, g, b, a] = self.color.into_rgba8();
127
128 swash_cache.with_pixels(
129 font_system.raw(),
130 physical_glyph.cache_key,
131 cosmic_text::Color::rgba(r, g, b, a),
132 |x, y, color| {
133 f(
134 Path::rectangle(
135 Point::new(x as f32, y as f32) + offset,
136 Size::new(1.0, 1.0),
137 ),
138 Color::from_rgba8(
139 color.r(),
140 color.g(),
141 color.b(),
142 color.a() as f32 / 255.0,
143 ),
144 );
145 },
146 );
147 }
148 }
149 }
150 }
151}
152
153impl Default for Text {
154 fn default() -> Text {
155 Text {
156 content: String::new(),
157 position: Point::ORIGIN,
158 max_width: f32::INFINITY,
159 color: Color::BLACK,
160 size: Pixels(16.0),
161 line_height: LineHeight::Relative(1.2),
162 font: Font::default(),
163 align_x: Alignment::Default,
164 align_y: alignment::Vertical::Top,
165 shaping: Shaping::default(),
166 }
167 }
168}
169
170impl From<String> for Text {
171 fn from(content: String) -> Text {
172 Text {
173 content,
174 ..Default::default()
175 }
176 }
177}
178
179impl From<&str> for Text {
180 fn from(content: &str) -> Text {
181 String::from(content).into()
182 }
183}