1use crate::{
2 brush::Brush,
3 core::{algebra::Vector2, color::Color, math::Rect},
4 ttf::SharedFont,
5 Font, HorizontalAlignment, VerticalAlignment, DEFAULT_FONT,
6};
7use std::ops::Range;
8
9#[derive(Debug, Clone)]
10pub struct TextGlyph {
11 bounds: Rect<f32>,
12 tex_coords: [Vector2<f32>; 4],
13}
14
15impl TextGlyph {
16 pub fn get_bounds(&self) -> Rect<f32> {
17 self.bounds
18 }
19
20 pub fn get_tex_coords(&self) -> &[Vector2<f32>; 4] {
21 &self.tex_coords
22 }
23}
24
25#[derive(Copy, Clone, Debug)]
26pub struct TextLine {
27 pub begin: usize,
29 pub end: usize,
31 pub width: f32,
33 pub height: f32,
35 pub x_offset: f32,
37 pub y_offset: f32,
39}
40
41impl TextLine {
42 fn new() -> TextLine {
43 TextLine {
44 begin: 0,
45 end: 0,
46 width: 0.0,
47 height: 0.0,
48 x_offset: 0.0,
49 y_offset: 0.0,
50 }
51 }
52
53 pub fn len(&self) -> usize {
54 self.end - self.begin
55 }
56
57 pub fn is_empty(&self) -> bool {
58 self.end == self.begin
59 }
60}
61
62#[derive(Copy, Clone, PartialOrd, PartialEq, Hash, Debug)]
64pub enum WrapMode {
65 NoWrap,
67
68 Letter,
70
71 Word,
73}
74
75#[derive(Copy, Clone, Debug)]
76pub struct Character {
77 pub char_code: u32,
78 pub glyph_index: u32,
79}
80
81impl Character {
82 pub fn from_char_with_font(char_code: u32, font: &Font) -> Self {
83 Self {
84 char_code,
85 glyph_index: font.glyph_index(char_code).unwrap_or_default() as u32,
86 }
87 }
88}
89
90#[derive(Clone, Debug)]
91pub struct FormattedText {
92 font: SharedFont,
93 text: Vec<Character>,
94 lines: Vec<TextLine>,
98 glyphs: Vec<TextGlyph>,
100 vertical_alignment: VerticalAlignment,
101 horizontal_alignment: HorizontalAlignment,
102 brush: Brush,
103 constraint: Vector2<f32>,
104 wrap: WrapMode,
105 mask_char: Option<Character>,
106}
107
108#[derive(Copy, Clone, Debug)]
109struct Word {
110 width: f32,
111 length: usize,
112}
113
114impl FormattedText {
115 pub fn get_glyphs(&self) -> &[TextGlyph] {
116 &self.glyphs
117 }
118
119 pub fn get_font(&self) -> SharedFont {
120 self.font.clone()
121 }
122
123 pub fn set_font(&mut self, font: SharedFont) -> &mut Self {
124 self.font = font;
125 self
126 }
127
128 pub fn get_lines(&self) -> &[TextLine] {
129 &self.lines
130 }
131
132 pub fn set_vertical_alignment(&mut self, vertical_alignment: VerticalAlignment) -> &mut Self {
133 self.vertical_alignment = vertical_alignment;
134 self
135 }
136
137 pub fn vertical_alignment(&self) -> VerticalAlignment {
138 self.vertical_alignment
139 }
140
141 pub fn set_horizontal_alignment(
142 &mut self,
143 horizontal_alignment: HorizontalAlignment,
144 ) -> &mut Self {
145 self.horizontal_alignment = horizontal_alignment;
146 self
147 }
148
149 pub fn horizontal_alignment(&self) -> HorizontalAlignment {
150 self.horizontal_alignment
151 }
152
153 pub fn set_brush(&mut self, brush: Brush) -> &mut Self {
154 self.brush = brush;
155 self
156 }
157
158 pub fn brush(&self) -> Brush {
159 self.brush.clone()
160 }
161
162 pub fn set_constraint(&mut self, constraint: Vector2<f32>) -> &mut Self {
163 self.constraint = constraint;
164 self
165 }
166
167 pub fn get_raw_text(&self) -> &[Character] {
168 &self.text
169 }
170
171 pub fn text(&self) -> String {
172 self.text
173 .iter()
174 .filter_map(|c| std::char::from_u32(c.char_code))
175 .collect()
176 }
177
178 pub fn get_range_width<T: IntoIterator<Item = usize>>(&self, range: T) -> f32 {
179 let mut width = 0.0;
180 let font = self.font.0.lock().unwrap();
181 for index in range {
182 width += font.glyph_advance(self.text[index].glyph_index);
183 }
184 width
185 }
186
187 pub fn set_text<P: AsRef<str>>(&mut self, text: P) -> &mut Self {
188 self.text.clear();
190
191 let font = self.font.0.lock().unwrap();
192
193 for code in text.as_ref().chars().map(|c| c as u32) {
194 self.text.push(Character::from_char_with_font(code, &font));
195 }
196
197 drop(font);
198
199 self
200 }
201
202 pub fn set_wrap(&mut self, wrap: WrapMode) -> &mut Self {
203 self.wrap = wrap;
204 self
205 }
206
207 pub fn wrap_mode(&self) -> WrapMode {
208 self.wrap
209 }
210
211 pub fn insert_char(&mut self, code: char, index: usize) -> &mut Self {
212 let font = self.font.0.lock().unwrap();
213
214 self.text
215 .insert(index, Character::from_char_with_font(code as u32, &font));
216
217 drop(font);
218
219 self
220 }
221
222 pub fn insert_str(&mut self, str: &str, position: usize) -> &mut Self {
223 let font = self.font.0.lock().unwrap();
224
225 for (i, code) in str.chars().enumerate() {
226 self.text.insert(
227 position + i,
228 Character::from_char_with_font(code as u32, &font),
229 );
230 }
231
232 drop(font);
233
234 self
235 }
236
237 pub fn remove_range(&mut self, range: Range<usize>) -> &mut Self {
238 self.text.drain(range);
239 self
240 }
241
242 pub fn remove_at(&mut self, index: usize) -> &mut Self {
243 self.text.remove(index);
244 self
245 }
246
247 pub fn build(&mut self) -> Vector2<f32> {
248 let font = self.font.0.lock().unwrap();
249
250 let masked_text;
251 let text = if let Some(mask_char) = self.mask_char {
252 masked_text = (0..self.text.len()).map(|_| mask_char).collect();
253 &masked_text
254 } else {
255 &self.text
256 };
257
258 let mut total_height = 0.0;
260 let mut current_line = TextLine::new();
261 let mut word: Option<Word> = None;
262 self.lines.clear();
263 for (i, character) in text.iter().enumerate() {
264 let advance = match font.glyphs().get(character.glyph_index as usize) {
265 Some(glyph) => glyph.advance,
266 None => font.height(),
267 };
268 let is_new_line =
269 character.char_code == u32::from(b'\n') || character.char_code == u32::from(b'\r');
270 let new_width = current_line.width + advance;
271 let is_white_space =
272 char::from_u32(character.char_code).map_or(false, |c| c.is_whitespace());
273 let word_ended = word.is_some() && is_white_space || i == self.text.len() - 1;
274
275 if self.wrap == WrapMode::Word && !is_white_space {
276 match word.as_mut() {
277 Some(word) => {
278 word.width += advance;
279 word.length += 1;
280 }
281 None => {
282 word = Some(Word {
283 width: advance,
284 length: 1,
285 });
286 }
287 };
288 }
289
290 if is_new_line {
291 if let Some(word) = word.take() {
292 current_line.width += word.width;
293 current_line.end += word.length;
294 }
295 self.lines.push(current_line);
296 current_line.begin = if is_new_line { i + 1 } else { i };
297 current_line.end = current_line.begin;
298 current_line.width = advance;
299 total_height += font.ascender();
300 } else {
301 match self.wrap {
302 WrapMode::NoWrap => {
303 current_line.width = new_width;
304 current_line.end += 1;
305 }
306 WrapMode::Letter => {
307 if new_width > self.constraint.x {
308 self.lines.push(current_line);
309 current_line.begin = if is_new_line { i + 1 } else { i };
310 current_line.end = current_line.begin + 1;
311 current_line.width = advance;
312 total_height += font.ascender();
313 } else {
314 current_line.width = new_width;
315 current_line.end += 1;
316 }
317 }
318 WrapMode::Word => {
319 if word_ended {
320 let word = word.take().unwrap();
321 if word.width > self.constraint.x {
322 current_line.width += word.width;
325 current_line.end += word.length;
326 self.lines.push(current_line);
327 current_line.begin = current_line.end;
328 current_line.width = 0.0;
329 total_height += font.ascender();
330 } else if current_line.width + word.width > self.constraint.x {
331 self.lines.push(current_line);
334 current_line.begin = i - word.length;
335 current_line.end = i;
336 current_line.width = word.width;
337 total_height += font.ascender();
338 } else {
339 current_line.width += word.width;
342 current_line.end += word.length;
343 }
344 }
345
346 if is_white_space {
348 current_line.end += 1;
349 current_line.width += advance;
350 }
351 }
352 }
353 }
354 }
355 if current_line.begin != current_line.end {
357 for character in text.iter().skip(current_line.end) {
358 let advance = match font.glyphs().get(character.glyph_index as usize) {
359 Some(glyph) => glyph.advance,
360 None => font.height(),
361 };
362 current_line.width += advance;
363 }
364 current_line.end = self.text.len();
365 self.lines.push(current_line);
366 total_height += font.ascender();
367 }
368
369 for line in self.lines.iter_mut() {
371 match self.horizontal_alignment {
372 HorizontalAlignment::Left => line.x_offset = 0.0,
373 HorizontalAlignment::Center => {
374 if self.constraint.x.is_infinite() {
375 line.x_offset = 0.0;
376 } else {
377 line.x_offset = 0.5 * (self.constraint.x - line.width).max(0.0);
378 }
379 }
380 HorizontalAlignment::Right => {
381 if self.constraint.x.is_infinite() {
382 line.x_offset = 0.0;
383 } else {
384 line.x_offset = (self.constraint.x - line.width).max(0.0)
385 }
386 }
387 HorizontalAlignment::Stretch => line.x_offset = 0.0,
388 }
389 }
390
391 self.glyphs.clear();
393
394 let cursor_y_start = match self.vertical_alignment {
395 VerticalAlignment::Top => 0.0,
396 VerticalAlignment::Center => {
397 if self.constraint.y.is_infinite() {
398 0.0
399 } else {
400 (self.constraint.y - total_height).max(0.0) * 0.5
401 }
402 }
403 VerticalAlignment::Bottom => {
404 if self.constraint.y.is_infinite() {
405 0.0
406 } else {
407 (self.constraint.y - total_height).max(0.0)
408 }
409 }
410 VerticalAlignment::Stretch => 0.0,
411 };
412
413 let cursor_x_start = if self.constraint.x.is_infinite() {
414 0.0
415 } else {
416 self.constraint.x
417 };
418
419 let mut cursor = Vector2::new(cursor_x_start, cursor_y_start);
420 for line in self.lines.iter_mut() {
421 cursor.x = line.x_offset;
422
423 for &character in text.iter().take(line.end).skip(line.begin) {
424 match font.glyphs().get(character.glyph_index as usize) {
425 Some(glyph) => {
426 let rect = Rect::new(
428 cursor.x + glyph.left.floor(),
429 cursor.y + font.ascender().floor()
430 - glyph.top.floor()
431 - glyph.bitmap_height as f32,
432 glyph.bitmap_width as f32,
433 glyph.bitmap_height as f32,
434 );
435 let text_glyph = TextGlyph {
436 bounds: rect,
437 tex_coords: glyph.tex_coords,
438 };
439 self.glyphs.push(text_glyph);
440
441 cursor.x += glyph.advance;
442 }
443 None => {
444 let rect = Rect::new(
446 cursor.x,
447 cursor.y + font.ascender(),
448 font.height(),
449 font.height(),
450 );
451 self.glyphs.push(TextGlyph {
452 bounds: rect,
453 tex_coords: [Vector2::default(); 4],
454 });
455 cursor.x += rect.w();
456 }
457 }
458 }
459 line.height = font.ascender();
460 line.y_offset = cursor.y;
461 cursor.y += font.ascender();
462 }
463
464 let mut full_size = Vector2::new(0.0, total_height - font.descender());
466 for line in self.lines.iter() {
467 full_size.x = line.width.max(full_size.x);
468 }
469 full_size
470 }
471}
472
473pub struct FormattedTextBuilder {
474 font: SharedFont,
475 brush: Brush,
476 constraint: Vector2<f32>,
477 text: String,
478 vertical_alignment: VerticalAlignment,
479 horizontal_alignment: HorizontalAlignment,
480 wrap: WrapMode,
481 mask_char: Option<char>,
482}
483
484impl Default for FormattedTextBuilder {
485 fn default() -> Self {
486 Self::new()
487 }
488}
489
490impl FormattedTextBuilder {
491 pub fn new() -> FormattedTextBuilder {
493 FormattedTextBuilder {
494 font: DEFAULT_FONT.clone(),
495 text: "".to_owned(),
496 horizontal_alignment: HorizontalAlignment::Left,
497 vertical_alignment: VerticalAlignment::Top,
498 brush: Brush::Solid(Color::WHITE),
499 constraint: Vector2::new(128.0, 128.0),
500 wrap: WrapMode::NoWrap,
501 mask_char: None,
502 }
503 }
504
505 pub fn with_font(mut self, font: SharedFont) -> Self {
506 self.font = font;
507 self
508 }
509
510 pub fn with_vertical_alignment(mut self, vertical_alignment: VerticalAlignment) -> Self {
511 self.vertical_alignment = vertical_alignment;
512 self
513 }
514
515 pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
516 self.wrap = wrap;
517 self
518 }
519
520 pub fn with_horizontal_alignment(mut self, horizontal_alignment: HorizontalAlignment) -> Self {
521 self.horizontal_alignment = horizontal_alignment;
522 self
523 }
524
525 pub fn with_text(mut self, text: String) -> Self {
526 self.text = text;
527 self
528 }
529
530 pub fn with_constraint(mut self, constraint: Vector2<f32>) -> Self {
531 self.constraint = constraint;
532 self
533 }
534
535 pub fn with_brush(mut self, brush: Brush) -> Self {
536 self.brush = brush;
537 self
538 }
539
540 pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
541 self.mask_char = mask_char;
542 self
543 }
544
545 pub fn build(self) -> FormattedText {
546 let font = self.font.0.lock().unwrap();
547 FormattedText {
548 text: self
549 .text
550 .chars()
551 .map(|c| Character::from_char_with_font(c as u32, &font))
552 .collect(),
553 lines: Vec::new(),
554 glyphs: Vec::new(),
555 vertical_alignment: self.vertical_alignment,
556 horizontal_alignment: self.horizontal_alignment,
557 brush: self.brush,
558 constraint: self.constraint,
559 wrap: self.wrap,
560 mask_char: self
561 .mask_char
562 .map(|code| Character::from_char_with_font(u32::from(code), &font)),
563 font: {
564 drop(font);
565 self.font
566 },
567 }
568 }
569}