1use std::collections::VecDeque;
18use std::convert::TryInto;
19use std::fmt::{Debug, Formatter};
20use std::hash::{Hash, Hasher};
21use std::iter::Peekable;
22use std::ops::Deref;
23use std::slice::Iter;
24use std::sync::atomic::{AtomicUsize, Ordering};
25use std::sync::Arc;
26use std::vec::IntoIter;
27
28use rusttype::Scale;
29use smallvec::{smallvec, SmallVec};
30use unicode_normalization::UnicodeNormalization;
31
32use crate::dimen::{Vec2, Vector2};
33use crate::error::{BacktraceError, ErrorMessage};
34use crate::shape::{Rect, Rectangle};
35
36static FONT_ID_GENERATOR: AtomicUsize = AtomicUsize::new(10000);
37
38pub type UserGlyphIndex = u32;
43
44pub type FontId = usize;
47
48type FormattedGlyphVec = SmallVec<[FormattedGlyph; 8]>;
49type FormattedTextLineVec = SmallVec<[FormattedTextLine; 1]>;
50
51#[derive(Debug, Hash, Eq, PartialEq, Clone)]
55pub struct Codepoint
56{
57 user_index: UserGlyphIndex,
58 codepoint: char
59}
60
61impl Codepoint
62{
63 pub const ZERO_WIDTH_SPACE: char = '\u{200B}';
67
68 #[inline]
72 #[must_use]
73 pub fn new(user_index: UserGlyphIndex, codepoint: char) -> Self
74 {
75 Codepoint {
76 user_index,
77 codepoint
78 }
79 }
80
81 fn from_unindexed_codepoints(unindexed_codepoints: &[char]) -> Vec<Self>
82 {
83 let mut codepoints = Vec::with_capacity(unindexed_codepoints.len());
84
85 for (i, codepoint) in unindexed_codepoints.iter().enumerate() {
86 codepoints.push(Codepoint::new(i.try_into().unwrap(), *codepoint));
87 }
88
89 codepoints
90 }
91}
92
93#[derive(Debug, Eq, PartialEq, Clone, Hash)]
94struct RenderableWord
95{
96 codepoints: Vec<Codepoint>,
97 is_whitespace: bool
98}
99
100impl RenderableWord
101{
102 fn starting_from_codepoint_location(mut self, location: usize) -> Self
103 {
104 self.codepoints.drain(0..location);
105
106 RenderableWord {
107 codepoints: self.codepoints,
108 is_whitespace: self.is_whitespace
109 }
110 }
111}
112
113#[derive(Debug, Eq, PartialEq, Clone, Hash)]
114enum Word
115{
116 Renderable(RenderableWord),
117 Newline
118}
119
120impl Word
121{
122 fn split_words(codepoints: &[Codepoint]) -> Vec<Word>
123 {
124 let mut reader = codepoints.iter().peekable();
125
126 let mut result = Vec::new();
127
128 while let Some(first_token) = reader.next() {
129 match first_token.codepoint {
130 Codepoint::ZERO_WIDTH_SPACE | '\r' => {
131 }
133
134 '\n' => result.push(Word::Newline),
135
136 ' ' | '\t' => {
137 result.push(Word::Renderable(RenderableWord {
138 codepoints: vec![first_token.clone()],
139 is_whitespace: true
140 }));
141 }
142
143 _ => {
144 let mut word_codepoints = Vec::with_capacity(16);
147 word_codepoints.push(first_token.clone());
148
149 while let Some(next) = reader.peek() {
150 match next.codepoint {
151 ' ' | '\t' | '\r' | '\n' | Codepoint::ZERO_WIDTH_SPACE => {
152 break
153 }
154 _ => word_codepoints.push(reader.next().unwrap().clone())
155 }
156 }
157
158 result.push(Word::Renderable(RenderableWord {
159 codepoints: word_codepoints,
160 is_whitespace: false
161 }));
162 }
163 }
164 }
165
166 result
167 }
168}
169
170pub struct FontGlyph
172{
173 glyph: rusttype::Glyph<'static>,
174 font: Font
175}
176
177struct WordsIterator
178{
179 words: Peekable<IntoIter<Word>>,
180 pending: VecDeque<Word>
181}
182
183impl WordsIterator
184{
185 fn from(words: Vec<Word>) -> Self
186 {
187 WordsIterator {
188 words: words.into_iter().peekable(),
189 pending: VecDeque::new()
190 }
191 }
192
193 #[inline]
194 #[must_use]
195 fn has_next(&self) -> bool
196 {
197 self.words.len() > 0 || !self.pending.is_empty()
198 }
199
200 #[inline]
201 #[must_use]
202 fn peek(&mut self) -> Option<&Word>
203 {
204 if let Some(word) = self.pending.front() {
205 return Some(word);
206 }
207
208 if let Some(word) = self.words.peek() {
209 return Some(word);
210 }
211
212 None
213 }
214
215 #[inline]
216 fn next(&mut self) -> Option<Word>
217 {
218 if let Some(word) = self.pending.pop_front() {
219 return Some(word);
220 }
221
222 if let Some(word) = self.words.next() {
223 return Some(word);
224 }
225
226 None
227 }
228
229 #[inline]
230 fn add_pending(&mut self, word: Word)
231 {
232 self.pending.push_back(word);
233 }
234}
235
236#[derive(Clone, Debug)]
237struct LineLayoutMetrics
238{
239 x_pos: f32,
240 max_ascent: f32,
241 min_descent: f32,
242 max_line_gap: f32,
243 last_glyph_id: Option<rusttype::GlyphId>,
244 last_font_id: Option<FontId>
245}
246
247impl LineLayoutMetrics
248{
249 fn new() -> Self
250 {
251 LineLayoutMetrics {
252 x_pos: 0.0,
253 max_ascent: 0.0,
254 min_descent: 0.0,
255 max_line_gap: 0.0,
256 last_glyph_id: None,
257 last_font_id: None
258 }
259 }
260
261 #[inline]
262 #[must_use]
263 fn height(&self) -> f32
264 {
265 self.max_ascent - self.min_descent
266 }
267
268 fn update_and_get_render_pos_x(
269 &mut self,
270 glyph: &rusttype::ScaledGlyph,
271 font_id: FontId,
272 scale: &Scale,
273 options: &TextOptions
274 ) -> f32
275 {
276 if let Some(last_glyph_id) = self.last_glyph_id {
277 if self.last_font_id == Some(font_id) {
278 self.x_pos +=
279 glyph.font().pair_kerning(*scale, last_glyph_id, glyph.id());
280 }
281
282 self.x_pos += options.tracking;
283 }
284
285 if self.last_font_id != Some(font_id) {
286 let v_metrics = glyph.font().v_metrics(*scale);
287
288 self.max_ascent = crate::numeric::max(self.max_ascent, v_metrics.ascent);
289 self.min_descent = crate::numeric::min(self.min_descent, v_metrics.descent);
290 self.max_line_gap =
291 crate::numeric::max(self.max_line_gap, v_metrics.line_gap);
292 }
293
294 let advance_width = glyph.h_metrics().advance_width;
295
296 let glyph_x_pos_start = self.x_pos;
297 self.x_pos += advance_width;
298
299 self.last_font_id = Some(font_id);
300 self.last_glyph_id = Some(glyph.id());
301
302 glyph_x_pos_start
303 }
304}
305
306enum WordLayoutResult
307{
308 Success(LineLayoutMetrics),
309 PartialWord(LineLayoutMetrics),
310 NotEnoughSpace
311}
312
313impl WordLayoutResult
314{
315 fn get_metrics(&self) -> Option<&LineLayoutMetrics>
316 {
317 match self {
318 WordLayoutResult::Success(metrics) => Some(metrics),
319 WordLayoutResult::PartialWord(metrics) => Some(metrics),
320 WordLayoutResult::NotEnoughSpace => None
321 }
322 }
323
324 fn end_of_line(&self) -> bool
325 {
326 match self {
327 WordLayoutResult::Success(_) => false,
328 WordLayoutResult::PartialWord(_) => true,
329 WordLayoutResult::NotEnoughSpace => true
330 }
331 }
332}
333
334#[allow(clippy::too_many_arguments)]
335fn try_layout_word_internal<T: TextLayout + ?Sized>(
336 layout_helper: &T,
337 word: RenderableWord,
338 remaining_words: &mut WordsIterator,
339 scale: &Scale,
340 options: &TextOptions,
341 pos_y_baseline: f32,
342 first_word_on_line: bool,
343 previous_metrics: &LineLayoutMetrics,
344 output: &mut FormattedGlyphVec
345) -> WordLayoutResult
346{
347 let mut new_word_metrics = previous_metrics.clone();
348 let pos_x_max = options.wrap_words_after_width;
349
350 let mut glyphs = FormattedGlyphVec::new();
351
352 for (
353 i,
354 Codepoint {
355 user_index,
356 codepoint: c
357 }
358 ) in word.codepoints.iter().enumerate()
359 {
360 let mut new_glyph_metrics = new_word_metrics.clone();
362
363 let glyph = match layout_helper.lookup_glyph_for_codepoint(*c) {
364 None => {
365 match layout_helper
366 .lookup_glyph_for_codepoint('□')
367 .or_else(|| layout_helper.lookup_glyph_for_codepoint('?'))
368 {
369 None => continue,
370 Some(glyph) => glyph
371 }
372 }
373 Some(glyph) => glyph
374 };
375
376 let scaled_glyph = glyph.glyph.scaled(*scale);
377
378 let glyph_x_pos_start = new_glyph_metrics.update_and_get_render_pos_x(
379 &scaled_glyph,
380 glyph.font.id(),
381 scale,
382 options
383 );
384
385 let formatted_glyph = FormattedGlyph {
386 user_index: *user_index,
387 glyph: scaled_glyph.positioned(rusttype::point(glyph_x_pos_start, 0.0)),
388 font_id: glyph.font.id()
389 };
390
391 if let Some(pos_x_max) = pos_x_max {
392 if new_glyph_metrics.x_pos > pos_x_max {
393 return if first_word_on_line {
394 if i == 0 {
395 glyphs.push(formatted_glyph);
398 new_word_metrics = new_glyph_metrics;
399
400 if word.codepoints.len() > 1 {
402 remaining_words.add_pending(Word::Renderable(
403 word.starting_from_codepoint_location(i + 1)
404 ));
405 }
406 } else {
407 remaining_words.add_pending(Word::Renderable(
408 word.starting_from_codepoint_location(i)
409 ));
410 }
411
412 glyphs.iter_mut().for_each(|glyph| {
413 glyph.reposition_y(pos_y_baseline + new_word_metrics.max_ascent);
414 });
415
416 output.append(&mut glyphs);
417 WordLayoutResult::PartialWord(new_word_metrics)
418 } else {
419 remaining_words.add_pending(Word::Renderable(word));
420 WordLayoutResult::NotEnoughSpace
421 };
422 }
423 }
424
425 glyphs.push(formatted_glyph);
426 new_word_metrics = new_glyph_metrics;
427 }
428
429 glyphs.iter_mut().for_each(|glyph| {
430 glyph.reposition_y(pos_y_baseline + new_word_metrics.max_ascent);
431 });
432
433 output.append(&mut glyphs);
434
435 WordLayoutResult::Success(new_word_metrics)
436}
437
438fn layout_line_internal<T: TextLayout + ?Sized>(
439 layout_helper: &T,
440 words: &mut WordsIterator,
441 scale: &Scale,
442 options: &TextOptions,
443 pos_y_baseline: f32
444) -> FormattedTextLine
445{
446 let mut line_metrics = LineLayoutMetrics::new();
447 let mut glyphs = SmallVec::new();
448
449 let mut first_word_on_line = true;
450
451 if options.trim_each_line {
452 while let Some(Word::Renderable(word)) = words.peek() {
454 if word.is_whitespace {
455 words.next().unwrap();
456 } else {
457 break;
458 }
459 }
460 }
461
462 while let Some(Word::Renderable(word)) = words.next() {
463 let result = try_layout_word_internal(
464 layout_helper,
465 word,
466 words,
467 scale,
468 options,
469 pos_y_baseline,
470 first_word_on_line,
471 &line_metrics,
472 &mut glyphs
473 );
474
475 if let Some(metrics) = result.get_metrics() {
476 line_metrics = metrics.clone();
477 }
478
479 if result.end_of_line() {
480 break;
481 }
482
483 first_word_on_line = false;
484 }
485
486 if glyphs.is_empty() {
487 let empty_metrics = layout_helper.empty_line_vertical_metrics(scale.y);
488 line_metrics.max_ascent = empty_metrics.ascent;
489 line_metrics.min_descent = empty_metrics.descent;
490 line_metrics.max_line_gap = empty_metrics.line_gap;
491 }
492
493 if let Some(max_width) = options.wrap_words_after_width {
494 let offset_x = match options.alignment {
495 TextAlignment::Left => None,
496 TextAlignment::Center => Some((max_width - line_metrics.x_pos) / 2.0),
497 TextAlignment::Right => Some(max_width - line_metrics.x_pos)
498 };
499
500 if let Some(offset_x) = offset_x {
501 for glyph in glyphs.iter_mut() {
502 glyph.add_offset_x(offset_x);
503 }
504 }
505 }
506
507 FormattedTextLine {
508 glyphs: Arc::new(glyphs),
509 baseline_vertical_position: pos_y_baseline,
510 width: line_metrics.x_pos,
511 height: line_metrics.height(),
512 ascent: line_metrics.max_ascent,
513 descent: line_metrics.min_descent,
514 line_gap: line_metrics.max_line_gap
515 }
516}
517
518fn layout_multiple_lines_internal<T: TextLayout + ?Sized>(
519 layout_helper: &T,
520 codepoints: &[Codepoint],
521 scale: f32,
522 options: TextOptions
523) -> FormattedTextBlock
524{
525 let scale = Scale::uniform(scale);
526
527 let mut iterator = WordsIterator::from(Word::split_words(codepoints));
528
529 let mut pos_y = 0.0;
530 let mut lines = SmallVec::new();
531
532 let mut width = 0.0;
533
534 while iterator.has_next() {
535 let line =
536 layout_line_internal(layout_helper, &mut iterator, &scale, &options, pos_y);
537
538 pos_y += line.height * options.line_spacing_multiplier;
539
540 if iterator.has_next() {
541 pos_y += line.line_gap * options.line_spacing_multiplier;
542 }
543
544 width = crate::numeric::max(width, line.width);
545
546 lines.push(line);
547 }
548
549 FormattedTextBlock {
550 lines: Arc::new(lines),
551 width,
552 height: pos_y
553 }
554}
555
556#[derive(Debug, Clone, PartialEq)]
558pub struct LineVerticalMetrics
559{
560 ascent: f32,
562 descent: f32,
564 line_gap: f32
566}
567
568impl LineVerticalMetrics
569{
570 pub fn height(&self) -> f32
572 {
573 self.ascent - self.descent
574 }
575}
576
577pub trait TextLayout
580{
581 fn lookup_glyph_for_codepoint(&self, codepoint: char) -> Option<FontGlyph>;
584
585 #[inline]
594 #[must_use]
595 fn layout_text(
596 &self,
597 text: &str,
598 scale: f32,
599 options: TextOptions
600 ) -> FormattedTextBlock
601 {
602 let codepoints: Vec<char> = text.nfc().collect();
603 self.layout_text_from_unindexed_codepoints(codepoints.as_slice(), scale, options)
604 }
605
606 #[inline]
613 #[must_use]
614 fn layout_text_from_unindexed_codepoints(
615 &self,
616 unindexed_codepoints: &[char],
617 scale: f32,
618 options: TextOptions
619 ) -> FormattedTextBlock
620 {
621 self.layout_text_from_codepoints(
622 Codepoint::from_unindexed_codepoints(unindexed_codepoints).as_slice(),
623 scale,
624 options
625 )
626 }
627
628 #[must_use]
634 fn layout_text_from_codepoints(
635 &self,
636 codepoints: &[Codepoint],
637 scale: f32,
638 options: TextOptions
639 ) -> FormattedTextBlock
640 {
641 layout_multiple_lines_internal(self, codepoints, scale, options)
642 }
643
644 #[must_use]
646 fn empty_line_vertical_metrics(&self, scale: f32) -> LineVerticalMetrics;
647}
648
649#[derive(Clone)]
651pub struct Font
652{
653 id: usize,
654 font: Arc<rusttype::Font<'static>>
655}
656
657impl Font
658{
659 pub fn new(bytes: &[u8]) -> Result<Font, BacktraceError<ErrorMessage>>
664 {
665 let font = rusttype::Font::try_from_vec(bytes.to_vec())
666 .ok_or_else(|| ErrorMessage::msg("Failed to load font"))?;
667
668 Ok(Font {
669 id: FONT_ID_GENERATOR.fetch_add(1, Ordering::SeqCst),
670 font: Arc::new(font)
671 })
672 }
673
674 #[inline]
675 fn id(&self) -> usize
676 {
677 self.id
678 }
679
680 #[inline]
681 fn font(&self) -> &rusttype::Font<'static>
682 {
683 &self.font
684 }
685}
686
687impl TextLayout for FontFamily
688{
689 fn lookup_glyph_for_codepoint(&self, codepoint: char) -> Option<FontGlyph>
690 {
691 for font in &*self.fonts {
692 if let Some(glyph) = font.lookup_glyph_for_codepoint(codepoint) {
693 return Some(glyph);
694 }
695 }
696
697 None
698 }
699
700 fn empty_line_vertical_metrics(&self, scale: f32) -> LineVerticalMetrics
701 {
702 match Arc::deref(&self.fonts).first() {
703 None => LineVerticalMetrics {
704 ascent: 0.0,
705 descent: 0.0,
706 line_gap: 0.0
707 },
708 Some(font) => {
709 let metrics = font.font.v_metrics(Scale::uniform(scale));
710 LineVerticalMetrics {
711 ascent: metrics.ascent,
712 descent: metrics.descent,
713 line_gap: metrics.line_gap
714 }
715 }
716 }
717 }
718}
719
720impl TextLayout for Font
721{
722 fn lookup_glyph_for_codepoint(&self, codepoint: char) -> Option<FontGlyph>
723 {
724 let glyph = self.font().glyph(codepoint);
725
726 if glyph.id().0 == 0 {
727 None
728 } else {
729 Some(FontGlyph {
730 glyph,
731 font: self.clone()
732 })
733 }
734 }
735
736 fn empty_line_vertical_metrics(&self, scale: f32) -> LineVerticalMetrics
737 {
738 let metrics = self.font.v_metrics(Scale::uniform(scale));
739 LineVerticalMetrics {
740 ascent: metrics.ascent,
741 descent: metrics.descent,
742 line_gap: metrics.line_gap
743 }
744 }
745}
746
747impl PartialEq for Font
748{
749 #[inline]
750 fn eq(&self, other: &Self) -> bool
751 {
752 self.id() == other.id()
753 }
754}
755
756impl Eq for Font {}
757
758impl Hash for Font
759{
760 #[inline]
761 fn hash<H: Hasher>(&self, state: &mut H)
762 {
763 self.id().hash(state);
764 }
765}
766
767impl Debug for Font
768{
769 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result
770 {
771 f.debug_struct("Font").field("id", &self.id()).finish()
772 }
773}
774
775#[derive(Clone, Debug, PartialEq, Eq, Hash)]
779pub struct FontFamily
780{
781 fonts: Arc<Vec<Font>>
782}
783
784impl FontFamily
785{
786 #[must_use]
789 pub fn new(fonts: Vec<Font>) -> Self
790 {
791 FontFamily {
792 fonts: Arc::new(fonts)
793 }
794 }
795}
796
797#[derive(Clone, Debug, Hash, Eq, PartialEq)]
800pub enum TextAlignment
801{
802 Left,
804 Center,
806 Right
808}
809
810pub struct TextOptions
812{
813 tracking: f32,
814 wrap_words_after_width: Option<f32>,
815 alignment: TextAlignment,
816 line_spacing_multiplier: f32,
817 trim_each_line: bool
818}
819
820impl TextOptions
821{
822 #[inline]
824 #[must_use]
825 pub fn new() -> Self
826 {
827 TextOptions {
828 tracking: 0.0,
829 wrap_words_after_width: None,
830 alignment: TextAlignment::Left,
831 line_spacing_multiplier: 1.0,
832 trim_each_line: true
833 }
834 }
835
836 #[inline]
841 #[must_use]
842 pub fn with_tracking(mut self, tracking: f32) -> Self
843 {
844 self.tracking = tracking;
845 self
846 }
847
848 #[inline]
855 #[must_use]
856 pub fn with_wrap_to_width(
857 mut self,
858 wrap_words_after_width_px: f32,
859 alignment: TextAlignment
860 ) -> Self
861 {
862 self.wrap_words_after_width = Some(wrap_words_after_width_px);
863 self.alignment = alignment;
864 self
865 }
866
867 #[inline]
872 #[must_use]
873 pub fn with_line_spacing_multiplier(mut self, line_spacing_multiplier: f32) -> Self
874 {
875 self.line_spacing_multiplier = line_spacing_multiplier;
876 self
877 }
878
879 #[inline]
884 #[must_use]
885 pub fn with_trim_each_line(mut self, trim_each_line: bool) -> Self
886 {
887 self.trim_each_line = trim_each_line;
888 self
889 }
890}
891
892impl Default for TextOptions
893{
894 fn default() -> Self
895 {
896 Self::new()
897 }
898}
899
900#[derive(Clone)]
902pub struct FormattedGlyph
903{
904 glyph: rusttype::PositionedGlyph<'static>,
905 font_id: FontId,
906 user_index: UserGlyphIndex
907}
908
909impl FormattedGlyph
910{
911 #[inline]
912 #[must_use]
913 pub(crate) fn glyph(&self) -> &rusttype::PositionedGlyph<'static>
914 {
915 &self.glyph
916 }
917
918 #[inline]
920 #[must_use]
921 pub fn font_id(&self) -> FontId
922 {
923 self.font_id
924 }
925
926 #[inline]
930 #[must_use]
931 pub fn user_index(&self) -> UserGlyphIndex
932 {
933 self.user_index
934 }
935
936 #[inline]
938 #[must_use]
939 pub fn position_x(&self) -> f32
940 {
941 self.glyph.position().x
942 }
943
944 #[inline]
949 #[must_use]
950 pub fn advance_width(&self) -> f32
951 {
952 self.glyph.unpositioned().h_metrics().advance_width
953 }
954
955 #[inline]
961 #[must_use]
962 pub fn pixel_bounding_box(&self) -> Option<Rect>
963 {
964 self.glyph.pixel_bounding_box().map(|r| {
965 Rect::from_tuples(
966 (r.min.x as f32, r.min.y as f32),
967 (r.max.x as f32, r.max.y as f32)
968 )
969 })
970 }
971
972 #[inline]
973 fn reposition_y(&mut self, y_pos: f32)
974 {
975 let existing_pos = self.glyph.position();
976 self.glyph
977 .set_position(rusttype::point(existing_pos.x, y_pos));
978 }
979
980 #[inline]
981 fn add_offset_x(&mut self, offset_x: f32)
982 {
983 let existing_pos = self.glyph.position();
984 self.glyph
985 .set_position(rusttype::point(existing_pos.x + offset_x, existing_pos.y));
986 }
987}
988
989#[derive(Clone)]
991pub struct FormattedTextBlock
992{
993 lines: Arc<FormattedTextLineVec>,
994 width: f32,
995 height: f32
996}
997
998impl FormattedTextBlock
999{
1000 #[inline]
1002 pub fn iter_lines(&self) -> Iter<'_, FormattedTextLine>
1003 {
1004 self.lines.iter()
1005 }
1006
1007 #[inline]
1009 #[must_use]
1010 pub fn width(&self) -> f32
1011 {
1012 self.width
1013 }
1014
1015 #[inline]
1017 #[must_use]
1018 pub fn height(&self) -> f32
1019 {
1020 self.height
1021 }
1022
1023 #[inline]
1025 #[must_use]
1026 pub fn size(&self) -> Vec2
1027 {
1028 Vec2::new(self.width, self.height)
1029 }
1030}
1031
1032#[derive(Clone)]
1034pub struct FormattedTextLine
1035{
1036 glyphs: Arc<FormattedGlyphVec>,
1037 baseline_vertical_position: f32,
1038 width: f32,
1039 height: f32,
1040 ascent: f32,
1041 descent: f32,
1042 line_gap: f32
1043}
1044
1045impl FormattedTextLine
1046{
1047 #[inline]
1049 pub fn iter_glyphs(&self) -> Iter<'_, FormattedGlyph>
1050 {
1051 self.glyphs.iter()
1052 }
1053
1054 #[inline]
1057 #[must_use]
1058 pub fn as_block(&self) -> FormattedTextBlock
1059 {
1060 FormattedTextBlock {
1061 lines: Arc::new(smallvec![self.clone()]),
1062 width: self.width,
1063 height: self.height
1064 }
1065 }
1066
1067 #[inline]
1069 #[must_use]
1070 pub fn width(&self) -> f32
1071 {
1072 self.width
1073 }
1074
1075 #[inline]
1078 #[must_use]
1079 pub fn height(&self) -> f32
1080 {
1081 self.height
1082 }
1083
1084 #[inline]
1087 #[must_use]
1088 pub fn ascent(&self) -> f32
1089 {
1090 self.ascent
1091 }
1092
1093 #[inline]
1099 #[must_use]
1100 pub fn descent(&self) -> f32
1101 {
1102 self.descent
1103 }
1104
1105 #[inline]
1108 #[must_use]
1109 pub fn line_gap(&self) -> f32
1110 {
1111 self.line_gap
1112 }
1113
1114 #[inline]
1116 #[must_use]
1117 pub fn baseline_position(&self) -> f32
1118 {
1119 self.baseline_vertical_position
1120 }
1121}
1122
1123impl<T: Copy> From<&rusttype::Rect<T>> for Rectangle<T>
1124{
1125 #[inline]
1126 fn from(rect: &rusttype::Rect<T>) -> Self
1127 {
1128 Rectangle::new(
1129 Vector2::new(rect.min.x, rect.min.y),
1130 Vector2::new(rect.max.x, rect.max.y)
1131 )
1132 }
1133}
1134
1135#[cfg(test)]
1136mod test
1137{
1138 use super::*;
1139
1140 #[test]
1141 fn test_word_split_1()
1142 {
1143 let codepoints = Codepoint::from_unindexed_codepoints(&['a', 'b', ' ', 'c', 'd']);
1144
1145 let words = Word::split_words(&codepoints);
1146
1147 assert_eq!(
1148 vec![
1149 Word::Renderable(RenderableWord {
1150 codepoints: vec![Codepoint::new(0, 'a'), Codepoint::new(1, 'b')],
1151 is_whitespace: false
1152 }),
1153 Word::Renderable(RenderableWord {
1154 codepoints: vec![Codepoint::new(2, ' ')],
1155 is_whitespace: true
1156 }),
1157 Word::Renderable(RenderableWord {
1158 codepoints: vec![Codepoint::new(3, 'c'), Codepoint::new(4, 'd')],
1159 is_whitespace: false
1160 })
1161 ],
1162 words
1163 )
1164 }
1165
1166 #[test]
1167 fn test_word_split_2()
1168 {
1169 let codepoints = Codepoint::from_unindexed_codepoints(&[
1170 'a', 'b', '\t', ' ', '\n', 'c', 'd', '\n', '\n', ' '
1171 ]);
1172
1173 let words = Word::split_words(&codepoints);
1174
1175 assert_eq!(
1176 vec![
1177 Word::Renderable(RenderableWord {
1178 codepoints: vec![Codepoint::new(0, 'a'), Codepoint::new(1, 'b')],
1179 is_whitespace: false
1180 }),
1181 Word::Renderable(RenderableWord {
1182 codepoints: vec![Codepoint::new(2, '\t'),],
1183 is_whitespace: true
1184 }),
1185 Word::Renderable(RenderableWord {
1186 codepoints: vec![Codepoint::new(3, ' '),],
1187 is_whitespace: true
1188 }),
1189 Word::Newline,
1190 Word::Renderable(RenderableWord {
1191 codepoints: vec![Codepoint::new(5, 'c'), Codepoint::new(6, 'd')],
1192 is_whitespace: false
1193 }),
1194 Word::Newline,
1195 Word::Newline,
1196 Word::Renderable(RenderableWord {
1197 codepoints: vec![Codepoint::new(9, ' ')],
1198 is_whitespace: true
1199 })
1200 ],
1201 words
1202 )
1203 }
1204}