1use crate::draw::Color;
2use crate::layout::Rectangle;
3use std::borrow::Cow;
4use std::iter::Peekable;
5
6#[derive(Clone, Copy, Debug)]
8pub enum TextWrap {
9 NoWrap,
11 Wrap,
13 WordWrap,
15}
16
17#[derive(Clone)]
19pub struct Font {
20 pub(crate) inner: super::cache::Font,
21 pub(crate) id: super::cache::FontId,
22 pub(crate) tex_slot: usize,
23}
24
25impl std::fmt::Debug for Font {
26 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
27 formatter.debug_struct("Font").finish()
28 }
29}
30
31#[derive(Clone)]
33pub struct Text<'a> {
34 pub text: Cow<'a, str>,
36 pub font: Font,
38 pub size: f32,
40 pub wrap: TextWrap,
42 pub color: Color,
44}
45
46pub struct CharPositionIter<'a, 'b: 'a> {
48 font: &'b rusttype::Font<'static>,
49 scale: rusttype::Scale,
50 x: f32,
51 base: Peekable<std::str::Chars<'a>>,
52}
53
54impl<'a, 'b> Iterator for CharPositionIter<'a, 'b> {
55 type Item = (char, rusttype::ScaledGlyph<'static>, f32, f32);
56
57 fn next(&mut self) -> Option<Self::Item> {
58 let c = self.base.next()?;
59 let n = self.base.peek().cloned();
60 let g = self.font.glyph(c);
61 let g = g.scaled(self.scale);
62 let w = g.h_metrics().advance_width + n.map(|n| self.font.pair_kerning(self.scale, g.id(), n)).unwrap_or(0.0);
63 let elem = (c, g, self.x, self.x + w);
64 self.x += w;
65 Some(elem)
66 }
67}
68
69struct WordWrapper<'a, 'b: 'a> {
70 x: f32,
71 y: f32,
72 final_x: f32,
73 final_y: f32,
74 width: f32,
75 height: f32,
76 iter: CharPositionIter<'a, 'b>,
77 f: &'a mut dyn FnMut(rusttype::ScaledGlyph<'static>, f32, f32, f32),
78}
79
80impl<'a, 'b: 'a> WordWrapper<'a, 'b> {
81 fn layout_word(&mut self, glyph: rusttype::ScaledGlyph<'static>, a: f32, b: f32, c: f32, mut word: bool) {
82 if word {
83 self.x = self.final_x;
84 self.y = self.final_y;
85
86 if let Some((ch, glyph, b, c)) = self.iter.next() {
87 if ch.is_alphanumeric() {
88 if c - self.x > self.width {
89 self.x = a;
90 self.y += self.height;
91 word = false;
92 }
93 self.layout_word(glyph, a, b, c, word);
94 }
95 }
96
97 (self.f)(glyph, b - self.x, c - self.x, self.y);
98 } else {
99 self.final_x = self.x;
100 self.final_y = self.y;
101
102 if c - self.final_x > self.width {
103 self.final_x = b;
104 self.final_y += self.height;
105 }
106 (self.f)(glyph, b - self.final_x, c - self.final_x, self.final_y);
107
108 for (ch, glyph, b, c) in &mut self.iter {
109 if c - self.final_x > self.width {
110 self.final_x = b;
111 self.final_y += self.height;
112 }
113
114 (self.f)(glyph, b - self.final_x, c - self.final_x, self.final_y);
115
116 if !ch.is_alphanumeric() {
117 break;
118 }
119 }
120 }
121 }
122}
123
124impl<'t> Text<'t> {
125 pub(crate) fn char_positions<'a, 'b>(&'b self) -> CharPositionIter<'a, 'b> {
126 let scale = rusttype::Scale {
127 x: self.size,
128 y: self.size,
129 };
130 CharPositionIter {
131 font: &self.font.inner,
132 scale,
133 x: 0.0,
134 base: self.text.chars().peekable(),
135 }
136 }
137
138 pub(crate) fn layout<F: FnMut(rusttype::ScaledGlyph<'static>, f32, f32, f32)>(&self, rect: Rectangle, mut f: F) {
139 let line = self.font.inner.v_metrics(rusttype::Scale {
140 x: self.size,
141 y: self.size,
142 });
143
144 let width = rect.width();
145 let height = -line.descent + line.line_gap + line.ascent;
146
147 match self.wrap {
148 TextWrap::NoWrap => {
149 for (_, g, a, b) in self.char_positions() {
150 f(g, a, b, line.ascent);
151 }
152 }
153
154 TextWrap::Wrap => {
155 let mut x = 0.0;
156 let mut y = line.ascent;
157
158 for (_, g, a, b) in self.char_positions() {
159 if b - x > width {
160 x = a;
161 y += height;
162 }
163
164 f(g, a - x, b - x, y);
165 }
166 }
167
168 TextWrap::WordWrap => {
169 let mut wrapper = WordWrapper {
170 x: 0.0,
171 y: line.ascent,
172 final_x: 0.0,
173 final_y: line.ascent,
174 width,
175 height,
176 iter: self.char_positions(),
177 f: &mut f,
178 };
179
180 while let Some((ch, glyph, a, b)) = wrapper.iter.next() {
181 wrapper.layout_word(glyph, a, a, b, ch.is_alphanumeric());
182 }
183 }
184 }
185 }
186
187 pub fn measure(&self, rect: Option<Rectangle>) -> Rectangle {
190 let line = self.font.inner.v_metrics(rusttype::Scale {
191 x: self.size,
192 y: self.size,
193 });
194
195 match rect {
196 None => {
197 let mut w = 0.0;
198 self.layout(Rectangle::from_wh(f32::INFINITY, 0.0), |_, _, new_w, _| w = new_w);
199
200 Rectangle::from_wh(w.ceil(), (line.ascent - line.descent).ceil())
201 }
202 Some(r) => {
203 let mut w = 0.0;
204 let mut h = line.ascent;
205 match self.wrap {
206 TextWrap::NoWrap => self.layout(r, |_, _, new_w, _| w = new_w),
207 TextWrap::Wrap | TextWrap::WordWrap => {
208 w = rect.map_or(0.0, |r| r.width());
209 self.layout(r, |_, _, _, new_h| h = new_h);
210 }
211 }
212
213 Rectangle::from_xywh(r.left, r.top, w.ceil(), (h - line.descent).ceil())
214 }
215 }
216 }
217
218 pub fn measure_range(&self, from: usize, to: usize, rect: Rectangle) -> ((f32, f32), (f32, f32)) {
220 let mut from_result = (0.0, 0.0);
221 let mut to_result = (0.0, 0.0);
222
223 let mut index = 0;
224 self.layout(rect, |_, begin, end, y| {
225 if index == from {
226 from_result = (begin, y)
227 }
228 if index == to {
229 to_result = (begin, y)
230 }
231 if index + 1 == from {
232 from_result = (end, y)
233 }
234 if index + 1 == to {
235 to_result = (end, y)
236 }
237 index += 1;
238 });
239
240 (from_result, to_result)
241 }
242
243 pub fn hitdetect(&self, cursor: (f32, f32), rect: Rectangle) -> usize {
245 let dist = |(x, y)| x * x + y * y;
246
247 let mut nearest = (dist(cursor), 0);
248 let mut index = 0;
249
250 self.layout(rect, |_, begin, end, y| {
251 if dist((begin - cursor.0, y - cursor.1)) < nearest.0 {
252 nearest.0 = dist((begin - cursor.0, y - cursor.1));
253 nearest.1 = index;
254 }
255 if dist((end - cursor.0, y - cursor.1)) < nearest.0 {
256 nearest.0 = dist((end - cursor.0, y - cursor.1));
257 nearest.1 = index + 1;
258 }
259
260 index += 1;
261 });
262
263 nearest.1
264 }
265}
266
267impl<'a> Text<'a> {
268 pub fn to_owned(&self) -> Text<'static> {
270 Text {
271 text: Cow::Owned(self.text.clone().into_owned()),
272 font: self.font.clone(),
273 size: self.size,
274 wrap: self.wrap,
275 color: self.color,
276 }
277 }
278}