1use crate::{
22 brush::Brush,
23 core::{
24 algebra::Vector2, color::Color, math::Rect, reflect::prelude::*, uuid_provider,
25 variable::InheritableVariable, visitor::prelude::*,
26 },
27 font::{Font, FontGlyph, FontResource},
28 style::StyledProperty,
29 HorizontalAlignment, VerticalAlignment,
30};
31use std::ops::Range;
32use strum_macros::{AsRefStr, EnumString, VariantNames};
33
34mod textwrapper;
35use textwrapper::*;
36
37#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, Visit, Reflect)]
39pub struct Position {
40 pub line: usize,
42
43 pub offset: usize,
45}
46
47#[derive(Debug, Clone, Default)]
48pub struct TextGlyph {
49 pub bounds: Rect<f32>,
50 pub tex_coords: [Vector2<f32>; 4],
51 pub atlas_page_index: usize,
52}
53
54#[derive(Copy, Clone, Debug, Default)]
55pub struct TextLine {
56 pub begin: usize,
58 pub end: usize,
60 pub width: f32,
62 pub height: f32,
64 pub x_offset: f32,
66 pub y_offset: f32,
68}
69
70impl TextLine {
71 fn new() -> TextLine {
72 TextLine {
73 begin: 0,
74 end: 0,
75 width: 0.0,
76 height: 0.0,
77 x_offset: 0.0,
78 y_offset: 0.0,
79 }
80 }
81
82 pub fn len(&self) -> usize {
83 self.end - self.begin
84 }
85
86 pub fn is_empty(&self) -> bool {
87 self.end == self.begin
88 }
89
90 pub fn y_distance(&self, y: f32) -> f32 {
91 (self.y_offset + self.height / 2.0 - y).abs()
92 }
93}
94
95#[derive(
97 Default,
98 Copy,
99 Clone,
100 PartialOrd,
101 PartialEq,
102 Hash,
103 Debug,
104 Eq,
105 Visit,
106 Reflect,
107 AsRefStr,
108 EnumString,
109 VariantNames,
110)]
111pub enum WrapMode {
112 #[default]
114 NoWrap,
115
116 Letter,
118
119 Word,
121}
122
123uuid_provider!(WrapMode = "f1290ceb-3fee-461f-a1e9-f9450bd06805");
124
125struct GlyphMetrics<'a> {
126 font: &'a mut Font,
127 size: f32,
128}
129
130impl GlyphMetrics<'_> {
131 fn ascender(&self) -> f32 {
132 self.font.ascender(self.size)
133 }
134
135 fn descender(&self) -> f32 {
136 self.font.descender(self.size)
137 }
138
139 fn newline_advance(&self) -> f32 {
140 self.size / 2.0
141 }
142
143 fn horizontal_kerning(&self, left: char, right: char) -> Option<f32> {
144 self.font.horizontal_kerning(self.size, left, right)
145 }
146
147 fn advance(&mut self, c: char) -> f32 {
148 match c {
149 '\n' => self.newline_advance(),
150 _ => self.font.glyph_advance(c, self.size),
151 }
152 }
153
154 fn glyph(&mut self, c: char, super_sampling_scale: f32) -> Option<&FontGlyph> {
155 self.font.glyph(c, self.size * super_sampling_scale)
156 }
157}
158
159fn build_glyph(
160 metrics: &mut GlyphMetrics,
161 mut x: f32,
162 mut y: f32,
163 character: char,
164 prev_character: Option<char>,
165 super_sampling_scale: f32,
166) -> (TextGlyph, f32) {
167 let ascender = metrics.ascender();
168 let font_size = metrics.size;
169
170 x = x.floor();
171 y = y.floor();
172
173 match metrics.glyph(character, super_sampling_scale) {
175 Some(glyph) => {
176 let k = 1.0 / super_sampling_scale;
179 let rect = Rect::new(
181 x + glyph.bitmap_left * k,
182 y + ascender.floor() - glyph.bitmap_top * k - (glyph.bitmap_height * k),
183 glyph.bitmap_width * k,
184 glyph.bitmap_height * k,
185 );
186 let text_glyph = TextGlyph {
187 bounds: rect,
188 tex_coords: glyph.tex_coords,
189 atlas_page_index: glyph.page_index,
190 };
191 let advance = glyph.advance
192 + prev_character
193 .and_then(|prev| metrics.horizontal_kerning(prev, character))
194 .unwrap_or_default();
195 (text_glyph, advance * k)
196 }
197 None => {
198 let rect = Rect::new(x, y + ascender, font_size, font_size);
200 let text_glyph = TextGlyph {
201 bounds: rect,
202 tex_coords: [Vector2::default(); 4],
203 atlas_page_index: 0,
204 };
205 (text_glyph, rect.w())
206 }
207 }
208}
209
210struct WrapSink<'a> {
211 lines: &'a mut Vec<TextLine>,
212 max_width: f32,
213}
214
215impl LineSink for WrapSink<'_> {
216 fn push_line(&mut self, range: Range<usize>, width: f32) {
217 let mut line = TextLine::new();
218 line.begin = range.start;
219 line.end = range.end;
220 line.width = width;
221 self.lines.push(line);
222 }
223
224 fn max_width(&self) -> f32 {
225 self.max_width
226 }
227}
228
229#[derive(Default, Clone, Debug, Visit, Reflect)]
230pub struct FormattedText {
231 font: InheritableVariable<FontResource>,
232 text: InheritableVariable<Vec<char>>,
233 #[reflect(hidden)]
237 #[visit(skip)]
238 lines: Vec<TextLine>,
239 #[visit(skip)]
241 #[reflect(hidden)]
242 glyphs: Vec<TextGlyph>,
243 vertical_alignment: InheritableVariable<VerticalAlignment>,
244 horizontal_alignment: InheritableVariable<HorizontalAlignment>,
245 brush: InheritableVariable<Brush>,
246 #[visit(skip)]
247 #[reflect(hidden)]
248 constraint: Vector2<f32>,
249 wrap: InheritableVariable<WrapMode>,
250 mask_char: InheritableVariable<Option<char>>,
251 #[visit(skip)]
252 #[reflect(hidden)]
253 pub(crate) super_sampling_scale: f32,
254 #[visit(rename = "Height")]
255 font_size: InheritableVariable<StyledProperty<f32>>,
256 pub shadow: InheritableVariable<bool>,
257 pub shadow_brush: InheritableVariable<Brush>,
258 pub shadow_dilation: InheritableVariable<f32>,
259 pub shadow_offset: InheritableVariable<Vector2<f32>>,
260}
261
262impl FormattedText {
263 pub fn nearest_valid_position(&self, start: Position) -> Position {
264 if self.lines.is_empty() {
265 return Position::default();
266 }
267 let mut pos = start;
268 pos.line = usize::min(pos.line, self.lines.len() - 1);
269 pos.offset = usize::min(pos.offset, self.lines[pos.line].len());
270 pos
271 }
272 pub fn get_relative_position_x(&self, start: Position, offset: isize) -> Position {
273 if self.lines.is_empty() {
274 return Position::default();
275 }
276 let mut pos = self.nearest_valid_position(start);
277 let distance = offset.abs();
278 for _ in 0..distance {
279 if offset < 0 {
280 if pos.offset > 0 {
281 pos.offset -= 1
282 } else if pos.line > 0 {
283 pos.line -= 1;
284 pos.offset = self.lines[pos.line].len().saturating_sub(1);
285 } else {
286 pos.offset = 0;
287 break;
288 }
289 } else {
290 let line = &self.lines[pos.line];
291 if pos.offset + 1 < line.len() {
292 pos.offset += 1;
293 } else if pos.line + 1 < self.lines.len() {
294 pos.line += 1;
295 pos.offset = 0;
296 } else {
297 pos.offset = line.len();
298 break;
299 }
300 }
301 }
302 pos
303 }
304
305 pub fn get_relative_position_y(&self, start: Position, offset: isize) -> Position {
306 let mut pos = self.nearest_valid_position(start);
307 pos.line = pos.line.saturating_add_signed(offset);
308 self.nearest_valid_position(pos)
309 }
310
311 pub fn get_line_range(&self, line: usize) -> Range<Position> {
312 let length = self.lines.get(line).map(TextLine::len).unwrap_or(0);
313 Range {
314 start: Position { line, offset: 0 },
315 end: Position {
316 line,
317 offset: length,
318 },
319 }
320 }
321
322 pub fn iter_line_ranges_within(
323 &self,
324 range: Range<Position>,
325 ) -> impl Iterator<Item = Range<Position>> + '_ {
326 (range.start.line..=range.end.line).map(move |i| {
327 let r = self.get_line_range(i);
328 Range {
329 start: Position::max(range.start, r.start),
330 end: Position::min(range.end, r.end),
331 }
332 })
333 }
334
335 pub fn end_position(&self) -> Position {
336 match self.lines.iter().enumerate().last() {
337 Some((i, line)) => Position {
338 line: i,
339 offset: line.len(),
340 },
341 None => Position::default(),
342 }
343 }
344
345 fn position_to_char_index_internal(&self, position: Position, clamp: bool) -> Option<usize> {
346 self.lines.get(position.line).map(|line| {
347 line.begin
348 + position.offset.min(if clamp {
349 line.len().saturating_sub(1)
350 } else {
351 line.len()
352 })
353 })
354 }
355 pub fn position_range_to_char_index_range(&self, range: Range<Position>) -> Range<usize> {
356 let start = self
357 .position_to_char_index_unclamped(range.start)
358 .unwrap_or(0);
359 let end = self
360 .position_to_char_index_unclamped(range.end)
361 .unwrap_or(self.text.len());
362 start..end
363 }
364 pub fn position_to_char_index_unclamped(&self, position: Position) -> Option<usize> {
369 self.position_to_char_index_internal(position, false)
370 }
371
372 pub fn position_to_char_index_clamped(&self, position: Position) -> Option<usize> {
379 self.position_to_char_index_internal(position, true)
380 }
381
382 pub fn char_index_to_position(&self, i: usize) -> Option<Position> {
384 self.lines
385 .iter()
386 .enumerate()
387 .find_map(|(line_index, line)| {
388 if (line.begin..line.end).contains(&i) {
389 Some(Position {
390 line: line_index,
391 offset: i - line.begin,
392 })
393 } else {
394 None
395 }
396 })
397 .or(Some(self.end_position()))
398 }
399
400 pub fn position_to_local(&self, position: Position) -> Vector2<f32> {
401 let mut state = self.font.state();
402 let Some(font) = state.data() else {
403 return Default::default();
404 };
405 let mut metrics = GlyphMetrics {
406 font,
407 size: **self.font_size,
408 };
409 let mut caret_pos = Vector2::default();
410 let position = self.nearest_valid_position(position);
411
412 let line = self.lines[position.line];
413 let raw_text = self.get_raw_text();
414 caret_pos += Vector2::new(line.x_offset, line.y_offset);
415 for (offset, char_index) in (line.begin..line.end).enumerate() {
416 if offset >= position.offset {
417 break;
418 }
419 if let Some(advance) = raw_text.get(char_index).map(|c| metrics.advance(*c)) {
420 caret_pos.x += advance;
421 } else {
422 caret_pos.x += metrics.size;
423 }
424 }
425 caret_pos
426 }
427
428 pub fn local_to_position(&self, point: Vector2<f32>) -> Position {
429 let font_size = **self.font_size();
430 let font = self.get_font();
431 let mut state = font.state();
432 let Some(font) = state.data() else {
433 return Position::default();
434 };
435 let mut metrics = GlyphMetrics {
436 font,
437 size: font_size,
438 };
439 let y = point.y;
440
441 let Some(line_index) = self
442 .lines
443 .iter()
444 .enumerate()
445 .map(|(i, a)| (i, a.y_distance(y)))
446 .min_by(|a, b| f32::total_cmp(&a.1, &b.1))
447 .map(|(i, _)| i)
448 else {
449 return Position::default();
450 };
451 let line = self.lines[line_index];
452 let x = point.x - line.x_offset;
453 let mut glyph_x: f32 = 0.0;
454 let mut min_dist: f32 = x.abs();
455 let mut min_index: usize = 0;
456 let raw_text = self.get_raw_text();
457 for (offset, char_index) in (line.begin..line.end).enumerate() {
458 if let Some(advance) = raw_text.get(char_index).map(|c| metrics.advance(*c)) {
459 glyph_x += advance;
460 } else {
461 glyph_x += font_size;
462 }
463 let dist = (x - glyph_x).abs();
464 if dist < min_dist {
465 min_dist = dist;
466 min_index = offset + 1;
467 }
468 }
469 Position {
470 line: line_index,
471 offset: min_index,
472 }
473 }
474
475 pub fn get_glyphs(&self) -> &[TextGlyph] {
476 &self.glyphs
477 }
478
479 pub fn get_font(&self) -> FontResource {
480 (*self.font).clone()
481 }
482
483 pub fn set_font(&mut self, font: FontResource) -> &mut Self {
484 self.font.set_value_and_mark_modified(font);
485 self
486 }
487
488 pub fn font_size(&self) -> &StyledProperty<f32> {
489 &self.font_size
490 }
491
492 pub fn super_sampled_font_size(&self) -> f32 {
493 **self.font_size * self.super_sampling_scale
494 }
495
496 pub fn set_font_size(&mut self, font_size: StyledProperty<f32>) -> &mut Self {
497 self.font_size.set_value_and_mark_modified(font_size);
498 self
499 }
500
501 pub fn get_lines(&self) -> &[TextLine] {
502 &self.lines
503 }
504
505 pub fn set_vertical_alignment(&mut self, vertical_alignment: VerticalAlignment) -> &mut Self {
506 self.vertical_alignment
507 .set_value_and_mark_modified(vertical_alignment);
508 self
509 }
510
511 pub fn vertical_alignment(&self) -> VerticalAlignment {
512 *self.vertical_alignment
513 }
514
515 pub fn set_horizontal_alignment(
516 &mut self,
517 horizontal_alignment: HorizontalAlignment,
518 ) -> &mut Self {
519 self.horizontal_alignment
520 .set_value_and_mark_modified(horizontal_alignment);
521 self
522 }
523
524 pub fn horizontal_alignment(&self) -> HorizontalAlignment {
525 *self.horizontal_alignment
526 }
527
528 pub fn set_brush(&mut self, brush: Brush) -> &mut Self {
529 self.brush.set_value_and_mark_modified(brush);
530 self
531 }
532
533 pub fn brush(&self) -> Brush {
534 (*self.brush).clone()
535 }
536
537 pub fn set_super_sampling_scale(&mut self, scale: f32) -> &mut Self {
538 self.super_sampling_scale = scale;
539 self
540 }
541
542 pub fn set_constraint(&mut self, constraint: Vector2<f32>) -> &mut Self {
543 self.constraint = constraint;
544 self
545 }
546
547 pub fn get_raw_text(&self) -> &[char] {
548 &self.text
549 }
550
551 pub fn text(&self) -> String {
552 self.text.iter().collect()
553 }
554
555 pub fn text_range(&self, range: Range<usize>) -> String {
556 self.text[range].iter().collect()
557 }
558
559 pub fn get_range_width<T: IntoIterator<Item = usize>>(&self, range: T) -> f32 {
560 let mut width = 0.0;
561 if let Some(font) = self.font.state().data() {
562 let mut metrics = GlyphMetrics {
563 font,
564 size: **self.font_size(),
565 };
566 for index in range {
567 if let Some(glyph) = self.text.get(index) {
569 width += metrics.advance(*glyph);
570 }
571 }
572 }
573 width
574 }
575
576 pub fn set_text<P: AsRef<str>>(&mut self, text: P) -> &mut Self {
577 self.text
578 .set_value_and_mark_modified(text.as_ref().chars().collect());
579 self
580 }
581
582 pub fn set_wrap(&mut self, wrap: WrapMode) -> &mut Self {
583 self.wrap.set_value_and_mark_modified(wrap);
584 self
585 }
586
587 pub fn set_shadow(&mut self, shadow: bool) -> &mut Self {
589 self.shadow.set_value_and_mark_modified(shadow);
590 self
591 }
592
593 pub fn set_shadow_brush(&mut self, brush: Brush) -> &mut Self {
595 self.shadow_brush.set_value_and_mark_modified(brush);
596 self
597 }
598
599 pub fn set_shadow_dilation(&mut self, thickness: f32) -> &mut Self {
602 self.shadow_dilation.set_value_and_mark_modified(thickness);
603 self
604 }
605
606 pub fn set_shadow_offset(&mut self, offset: Vector2<f32>) -> &mut Self {
608 self.shadow_offset.set_value_and_mark_modified(offset);
609 self
610 }
611
612 pub fn wrap_mode(&self) -> WrapMode {
613 *self.wrap
614 }
615
616 pub fn insert_char(&mut self, code: char, index: usize) -> &mut Self {
617 self.text.insert(index, code);
618 self
619 }
620
621 pub fn insert_str(&mut self, str: &str, position: usize) -> &mut Self {
622 for (i, code) in str.chars().enumerate() {
623 self.text.insert(position + i, code);
624 }
625
626 self
627 }
628
629 pub fn remove_range(&mut self, range: Range<usize>) -> &mut Self {
630 self.text.drain(range);
631 self
632 }
633
634 pub fn remove_at(&mut self, index: usize) -> &mut Self {
635 self.text.remove(index);
636 self
637 }
638
639 pub fn build(&mut self) -> Vector2<f32> {
640 let mut font_state = self.font.state();
641 let Some(font) = font_state.data() else {
642 return Default::default();
643 };
644 let mut metrics = GlyphMetrics {
645 font,
646 size: **self.font_size(),
647 };
648 let line_height: f32 = metrics.ascender();
649
650 self.lines.clear();
651 let sink = WrapSink {
652 lines: &mut self.lines,
653 max_width: self.constraint.x,
654 };
655 if let Some(mask) = *self.mask_char {
656 let advance = metrics.advance(mask);
657 match *self.wrap {
658 WrapMode::NoWrap => wrap_mask(NoWrap::new(sink), self.text.len(), mask, advance),
659 WrapMode::Letter => wrap_mask(
660 LetterWrap::new(sink),
661 self.text.len(),
662 mask,
663 **self.font_size,
664 ),
665 WrapMode::Word => wrap_mask(WordWrap::new(sink), self.text.len(), mask, advance),
666 }
667 } else {
668 match *self.wrap {
669 WrapMode::NoWrap => wrap(NoWrap::new(sink), &mut metrics, self.text.as_slice()),
670 WrapMode::Letter => wrap(LetterWrap::new(sink), &mut metrics, self.text.as_slice()),
671 WrapMode::Word => wrap(WordWrap::new(sink), &mut metrics, self.text.as_slice()),
672 }
673 }
674
675 let total_height = line_height * self.lines.len() as f32;
676 for line in self.lines.iter_mut() {
678 match *self.horizontal_alignment {
679 HorizontalAlignment::Left => line.x_offset = 0.0,
680 HorizontalAlignment::Center => {
681 if self.constraint.x.is_infinite() {
682 line.x_offset = 0.0;
683 } else {
684 line.x_offset = 0.5 * (self.constraint.x - line.width).max(0.0);
685 }
686 }
687 HorizontalAlignment::Right => {
688 if self.constraint.x.is_infinite() {
689 line.x_offset = 0.0;
690 } else {
691 line.x_offset = (self.constraint.x - line.width).max(0.0)
692 }
693 }
694 HorizontalAlignment::Stretch => line.x_offset = 0.0,
695 }
696 }
697
698 self.glyphs.clear();
700
701 let cursor_y_start = match *self.vertical_alignment {
702 VerticalAlignment::Top => 0.0,
703 VerticalAlignment::Center => {
704 if self.constraint.y.is_infinite() {
705 0.0
706 } else {
707 (self.constraint.y - total_height).max(0.0) * 0.5
708 }
709 }
710 VerticalAlignment::Bottom => {
711 if self.constraint.y.is_infinite() {
712 0.0
713 } else {
714 (self.constraint.y - total_height).max(0.0)
715 }
716 }
717 VerticalAlignment::Stretch => 0.0,
718 };
719
720 let mut y: f32 = cursor_y_start.floor();
721 for line in self.lines.iter_mut() {
722 let mut x = line.x_offset.floor();
723 if let Some(mask) = *self.mask_char {
724 let mut prev = None;
725 for c in std::iter::repeat::<char>(mask).take(line.len()) {
726 let (glyph, advance) =
727 build_glyph(&mut metrics, x, y, c, prev, self.super_sampling_scale);
728 self.glyphs.push(glyph);
729 x += advance;
730 prev = Some(c);
731 }
732 } else {
733 let mut prev = None;
734 for c in self.text.iter().take(line.end).skip(line.begin).cloned() {
735 match c {
736 '\n' => {
737 x += metrics.newline_advance();
738 }
739 _ => {
740 let (glyph, advance) =
741 build_glyph(&mut metrics, x, y, c, prev, self.super_sampling_scale);
742 self.glyphs.push(glyph);
743 x += advance;
744 }
745 }
746 prev = Some(c);
747 }
748 }
749 line.height = line_height;
750 line.y_offset = y;
751 y += line_height;
752 }
753
754 let size_x = self
755 .lines
756 .iter()
757 .map(|line| line.width)
758 .max_by(f32::total_cmp)
759 .unwrap_or_default();
760 let size_y = total_height - metrics.descender();
762 Vector2::new(size_x, size_y)
763 }
764}
765
766fn wrap<W: TextWrapper>(mut wrapper: W, metrics: &mut GlyphMetrics, text: &[char]) {
767 for &character in text.iter() {
768 let advance = metrics.advance(character);
769 wrapper.push(character, advance);
770 }
771 wrapper.finish();
772}
773
774fn wrap_mask<W: TextWrapper>(mut wrapper: W, length: usize, mask_char: char, advance: f32) {
775 for _ in 0..length {
776 wrapper.push(mask_char, advance);
777 }
778 wrapper.finish();
779}
780
781pub struct FormattedTextBuilder {
782 font: FontResource,
783 brush: Brush,
784 constraint: Vector2<f32>,
785 text: String,
786 vertical_alignment: VerticalAlignment,
787 horizontal_alignment: HorizontalAlignment,
788 wrap: WrapMode,
789 mask_char: Option<char>,
790 shadow: bool,
791 shadow_brush: Brush,
792 shadow_dilation: f32,
793 shadow_offset: Vector2<f32>,
794 font_size: StyledProperty<f32>,
795 super_sampling_scaling: f32,
796}
797
798impl FormattedTextBuilder {
799 pub fn new(font: FontResource) -> FormattedTextBuilder {
801 FormattedTextBuilder {
802 font,
803 text: "".to_owned(),
804 horizontal_alignment: HorizontalAlignment::Left,
805 vertical_alignment: VerticalAlignment::Top,
806 brush: Brush::Solid(Color::WHITE),
807 constraint: Vector2::new(128.0, 128.0),
808 wrap: WrapMode::NoWrap,
809 mask_char: None,
810 shadow: false,
811 shadow_brush: Brush::Solid(Color::BLACK),
812 shadow_dilation: 1.0,
813 shadow_offset: Vector2::new(1.0, 1.0),
814 font_size: 14.0f32.into(),
815 super_sampling_scaling: 1.0,
816 }
817 }
818
819 pub fn with_vertical_alignment(mut self, vertical_alignment: VerticalAlignment) -> Self {
820 self.vertical_alignment = vertical_alignment;
821 self
822 }
823
824 pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
825 self.wrap = wrap;
826 self
827 }
828
829 pub fn with_horizontal_alignment(mut self, horizontal_alignment: HorizontalAlignment) -> Self {
830 self.horizontal_alignment = horizontal_alignment;
831 self
832 }
833
834 pub fn with_text(mut self, text: String) -> Self {
835 self.text = text;
836 self
837 }
838
839 pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
840 self.font_size = font_size;
841 self
842 }
843
844 pub fn with_constraint(mut self, constraint: Vector2<f32>) -> Self {
845 self.constraint = constraint;
846 self
847 }
848
849 pub fn with_brush(mut self, brush: Brush) -> Self {
850 self.brush = brush;
851 self
852 }
853
854 pub fn with_mask_char(mut self, mask_char: Option<char>) -> Self {
855 self.mask_char = mask_char;
856 self
857 }
858
859 pub fn with_shadow(mut self, shadow: bool) -> Self {
861 self.shadow = shadow;
862 self
863 }
864
865 pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
867 self.shadow_brush = brush;
868 self
869 }
870
871 pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
874 self.shadow_dilation = thickness;
875 self
876 }
877
878 pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
880 self.shadow_offset = offset;
881 self
882 }
883
884 pub fn with_super_sampling_scaling(mut self, scaling: f32) -> Self {
886 self.super_sampling_scaling = scaling;
887 self
888 }
889
890 pub fn build(self) -> FormattedText {
891 FormattedText {
892 text: self.text.chars().collect::<Vec<char>>().into(),
893 lines: Vec::new(),
894 glyphs: Vec::new(),
895 vertical_alignment: self.vertical_alignment.into(),
896 horizontal_alignment: self.horizontal_alignment.into(),
897 brush: self.brush.into(),
898 constraint: self.constraint,
899 wrap: self.wrap.into(),
900 mask_char: self.mask_char.into(),
901 super_sampling_scale: self.super_sampling_scaling,
902 font_size: self.font_size.into(),
903 shadow: self.shadow.into(),
904 shadow_brush: self.shadow_brush.into(),
905 font: self.font.into(),
906 shadow_dilation: self.shadow_dilation.into(),
907 shadow_offset: self.shadow_offset.into(),
908 }
909 }
910}