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