1use super::{BuiltInLineBreaker, GlyphPositioner, LineBreaker, SectionGeometry, ToSectionText};
2use crate::{characters::Characters, GlyphChange, SectionGlyph};
3use ab_glyph::*;
4
5#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
16pub enum Layout<L: LineBreaker> {
17 SingleLine {
20 line_breaker: L,
21 h_align: HorizontalAlign,
22 v_align: VerticalAlign,
23 },
24 Wrap {
28 line_breaker: L,
29 h_align: HorizontalAlign,
30 v_align: VerticalAlign,
31 },
32}
33
34impl Default for Layout<BuiltInLineBreaker> {
35 #[inline]
36 fn default() -> Self {
37 Layout::default_wrap()
38 }
39}
40
41impl Layout<BuiltInLineBreaker> {
42 #[inline]
43 pub fn default_single_line() -> Self {
44 Layout::SingleLine {
45 line_breaker: BuiltInLineBreaker::default(),
46 h_align: HorizontalAlign::Left,
47 v_align: VerticalAlign::Top,
48 }
49 }
50
51 #[inline]
52 pub fn default_wrap() -> Self {
53 Layout::Wrap {
54 line_breaker: BuiltInLineBreaker::default(),
55 h_align: HorizontalAlign::Left,
56 v_align: VerticalAlign::Top,
57 }
58 }
59}
60
61impl<L: LineBreaker> Layout<L> {
62 pub fn h_align(self, h_align: HorizontalAlign) -> Self {
64 use crate::Layout::*;
65 match self {
66 SingleLine {
67 line_breaker,
68 v_align,
69 ..
70 } => SingleLine {
71 line_breaker,
72 v_align,
73 h_align,
74 },
75 Wrap {
76 line_breaker,
77 v_align,
78 ..
79 } => Wrap {
80 line_breaker,
81 v_align,
82 h_align,
83 },
84 }
85 }
86
87 pub fn v_align(self, v_align: VerticalAlign) -> Self {
89 use crate::Layout::*;
90 match self {
91 SingleLine {
92 line_breaker,
93 h_align,
94 ..
95 } => SingleLine {
96 line_breaker,
97 v_align,
98 h_align,
99 },
100 Wrap {
101 line_breaker,
102 h_align,
103 ..
104 } => Wrap {
105 line_breaker,
106 v_align,
107 h_align,
108 },
109 }
110 }
111
112 pub fn line_breaker<L2: LineBreaker>(self, line_breaker: L2) -> Layout<L2> {
114 use crate::Layout::*;
115 match self {
116 SingleLine {
117 h_align, v_align, ..
118 } => SingleLine {
119 line_breaker,
120 v_align,
121 h_align,
122 },
123 Wrap {
124 h_align, v_align, ..
125 } => Wrap {
126 line_breaker,
127 v_align,
128 h_align,
129 },
130 }
131 }
132}
133
134impl<L: LineBreaker> GlyphPositioner for Layout<L> {
135 fn calculate_glyphs<F, S>(
136 &self,
137 fonts: &[F],
138 geometry: &SectionGeometry,
139 sections: &[S],
140 ) -> Vec<SectionGlyph>
141 where
142 F: Font,
143 S: ToSectionText,
144 {
145 use crate::Layout::{SingleLine, Wrap};
146
147 let SectionGeometry {
148 screen_position,
149 bounds: (bound_w, bound_h),
150 ..
151 } = *geometry;
152
153 match *self {
154 SingleLine {
155 h_align,
156 v_align,
157 line_breaker,
158 } => Characters::new(
159 fonts,
160 sections.iter().map(|s| s.to_section_text()),
161 line_breaker,
162 )
163 .words()
164 .lines(bound_w)
165 .next()
166 .map(|line| line.aligned_on_screen(screen_position, h_align, v_align))
167 .unwrap_or_default(),
168
169 Wrap {
170 h_align,
171 v_align,
172 line_breaker,
173 } => {
174 let mut out = vec![];
175 let mut caret = screen_position;
176 let v_align_top = v_align == VerticalAlign::Top;
177
178 let lines = Characters::new(
179 fonts,
180 sections.iter().map(|s| s.to_section_text()),
181 line_breaker,
182 )
183 .words()
184 .lines(bound_w);
185
186 for line in lines {
187 if v_align_top && caret.1 >= screen_position.1 + bound_h {
189 break;
190 }
191
192 let line_height = line.line_height();
193 out.extend(line.aligned_on_screen(caret, h_align, VerticalAlign::Top));
194 caret.1 += line_height;
195 }
196
197 if !out.is_empty() {
198 match v_align {
199 VerticalAlign::Top => {}
201 VerticalAlign::Center | VerticalAlign::Bottom => {
203 let shift_up = if v_align == VerticalAlign::Center {
204 (caret.1 - screen_position.1) / 2.0
205 } else {
206 caret.1 - screen_position.1
207 };
208
209 let (min_x, max_x) = h_align.x_bounds(screen_position.0, bound_w);
210 let (min_y, max_y) = v_align.y_bounds(screen_position.1, bound_h);
211
212 out = out
213 .drain(..)
214 .filter_map(|mut sg| {
215 sg.glyph.position.y -= shift_up;
217
218 let sfont = fonts[sg.font_id].as_scaled(sg.glyph.scale);
220 let h_advance = sfont.h_advance(sg.glyph.id);
221 let h_side_bearing = sfont.h_side_bearing(sg.glyph.id);
222 let height = sfont.height();
223
224 Some(sg).filter(|sg| {
225 sg.glyph.position.x - h_side_bearing <= max_x
226 && sg.glyph.position.x + h_advance >= min_x
227 && sg.glyph.position.y - height <= max_y
228 && sg.glyph.position.y + height >= min_y
229 })
230 })
231 .collect();
232 }
233 }
234 }
235
236 out
237 }
238 }
239 }
240
241 fn bounds_rect(&self, geometry: &SectionGeometry) -> Rect {
242 use crate::Layout::{SingleLine, Wrap};
243
244 let SectionGeometry {
245 screen_position: (screen_x, screen_y),
246 bounds: (bound_w, bound_h),
247 } = *geometry;
248
249 let (h_align, v_align) = match *self {
250 Wrap {
251 h_align, v_align, ..
252 }
253 | SingleLine {
254 h_align, v_align, ..
255 } => (h_align, v_align),
256 };
257
258 let (x_min, x_max) = h_align.x_bounds(screen_x, bound_w);
259 let (y_min, y_max) = v_align.y_bounds(screen_y, bound_h);
260
261 Rect {
262 min: point(x_min, y_min),
263 max: point(x_max, y_max),
264 }
265 }
266
267 #[allow(clippy::float_cmp)]
268 fn recalculate_glyphs<F, S, P>(
269 &self,
270 previous: P,
271 change: GlyphChange,
272 fonts: &[F],
273 geometry: &SectionGeometry,
274 sections: &[S],
275 ) -> Vec<SectionGlyph>
276 where
277 F: Font,
278 S: ToSectionText,
279 P: IntoIterator<Item = SectionGlyph>,
280 {
281 match change {
282 GlyphChange::Geometry(old) if old.bounds == geometry.bounds => {
283 let adjustment = point(
285 geometry.screen_position.0 - old.screen_position.0,
286 geometry.screen_position.1 - old.screen_position.1,
287 );
288
289 let mut glyphs: Vec<_> = previous.into_iter().collect();
290 glyphs
291 .iter_mut()
292 .for_each(|sg| sg.glyph.position += adjustment);
293 glyphs
294 }
295 _ => self.calculate_glyphs(fonts, geometry, sections),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
302pub enum HorizontalAlign {
303 Left,
306 Center,
309 Right,
312}
313
314impl HorizontalAlign {
315 #[inline]
316 pub(crate) fn x_bounds(self, screen_x: f32, bound_w: f32) -> (f32, f32) {
317 let (min, max) = match self {
318 HorizontalAlign::Left => (screen_x, screen_x + bound_w),
319 HorizontalAlign::Center => (screen_x - bound_w / 2.0, screen_x + bound_w / 2.0),
320 HorizontalAlign::Right => (screen_x - bound_w, screen_x),
321 };
322
323 (min.floor(), max.ceil())
324 }
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
330pub enum VerticalAlign {
331 Top,
333 Center,
335 Bottom,
337}
338
339impl VerticalAlign {
340 #[inline]
341 pub(crate) fn y_bounds(self, screen_y: f32, bound_h: f32) -> (f32, f32) {
342 let (min, max) = match self {
343 VerticalAlign::Top => (screen_y, screen_y + bound_h),
344 VerticalAlign::Center => (screen_y - bound_h / 2.0, screen_y + bound_h / 2.0),
345 VerticalAlign::Bottom => (screen_y - bound_h, screen_y),
346 };
347
348 (min.floor(), max.ceil())
349 }
350}
351
352#[cfg(test)]
353mod bounds_test {
354 use super::*;
355
356 const fn inf() -> f32 {
357 f32::INFINITY
358 }
359
360 #[test]
361 fn v_align_y_bounds_inf() {
362 assert_eq!(VerticalAlign::Top.y_bounds(0.0, inf()), (0.0, inf()));
363 assert_eq!(VerticalAlign::Center.y_bounds(0.0, inf()), (-inf(), inf()));
364 assert_eq!(VerticalAlign::Bottom.y_bounds(0.0, inf()), (-inf(), 0.0));
365 }
366
367 #[test]
368 fn h_align_x_bounds_inf() {
369 assert_eq!(HorizontalAlign::Left.x_bounds(0.0, inf()), (0.0, inf()));
370 assert_eq!(
371 HorizontalAlign::Center.x_bounds(0.0, inf()),
372 (-inf(), inf())
373 );
374 assert_eq!(HorizontalAlign::Right.x_bounds(0.0, inf()), (-inf(), 0.0));
375 }
376}
377
378#[cfg(test)]
379mod layout_test {
380 use super::*;
381 use crate::{BuiltInLineBreaker::*, FontId, SectionText};
382 use approx::assert_relative_eq;
383 use once_cell::sync::Lazy;
384 use ordered_float::OrderedFloat;
385 use std::{collections::*, f32};
386
387 static A_FONT: Lazy<FontRef<'static>> = Lazy::new(|| {
388 FontRef::try_from_slice(include_bytes!("../../fonts/DejaVuSansMono.ttf")).unwrap()
389 });
390 static CJK_FONT: Lazy<FontRef<'static>> = Lazy::new(|| {
391 FontRef::try_from_slice(include_bytes!("../../fonts/WenQuanYiMicroHei.ttf")).unwrap()
392 });
393 static FONT_MAP: Lazy<[&'static FontRef<'static>; 2]> = Lazy::new(|| [&*A_FONT, &*CJK_FONT]);
394
395 const TEST_CHARS: &[char] = &[
397 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
398 'S', 'T', 'U', 'V', 'W', 'X', 'Q', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
399 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ', ',',
400 '.', '提', '高', '代', '碼', '執', '行', '率', '❤', 'é', 'ß', '\'', '_',
401 ];
402
403 fn glyphs_to_common_string<F>(glyphs: &[SectionGlyph], font: &F) -> String
405 where
406 F: Font,
407 {
408 glyphs
409 .iter()
410 .map(|sg| {
411 TEST_CHARS
412 .iter()
413 .find(|cc| font.glyph_id(**cc) == sg.glyph.id)
414 .unwrap_or(&'☐')
415 })
416 .collect()
417 }
418
419 macro_rules! assert_glyph_order {
423 ($glyphs:expr, $string:expr) => {
424 assert_glyph_order!($glyphs, $string, font = &*A_FONT)
425 };
426 ($glyphs:expr, $string:expr, font = $font:expr) => {{
427 assert_eq!($string, glyphs_to_common_string(&$glyphs, $font));
428 }};
429 }
430
431 #[allow(unused)]
433 #[derive(Hash)]
434 enum SimpleCustomGlyphPositioner {}
435
436 impl GlyphPositioner for SimpleCustomGlyphPositioner {
437 fn calculate_glyphs<F, S>(
438 &self,
439 _fonts: &[F],
440 _geometry: &SectionGeometry,
441 _sections: &[S],
442 ) -> Vec<SectionGlyph>
443 where
444 F: Font,
445 S: ToSectionText,
446 {
447 <_>::default()
448 }
449
450 fn bounds_rect(&self, _: &SectionGeometry) -> Rect {
453 Rect {
454 min: point(0.0, 0.0),
455 max: point(0.0, 0.0),
456 }
457 }
458 }
459
460 #[test]
461 fn zero_scale_glyphs() {
462 let glyphs = Layout::default_single_line()
463 .line_breaker(AnyCharLineBreaker)
464 .calculate_glyphs(
465 &*FONT_MAP,
466 &SectionGeometry::default(),
467 &[SectionText {
468 text: "hello world",
469 scale: 0.0.into(),
470 ..<_>::default()
471 }],
472 );
473
474 assert!(glyphs.is_empty(), "{:?}", glyphs);
475 }
476
477 #[test]
478 fn negative_scale_glyphs() {
479 let glyphs = Layout::default_single_line()
480 .line_breaker(AnyCharLineBreaker)
481 .calculate_glyphs(
482 &*FONT_MAP,
483 &SectionGeometry::default(),
484 &[SectionText {
485 text: "hello world",
486 scale: PxScale::from(-20.0),
487 ..<_>::default()
488 }],
489 );
490
491 assert!(glyphs.is_empty(), "{:?}", glyphs);
492 }
493
494 #[test]
495 fn single_line_chars_left_simple() {
496 let glyphs = Layout::default_single_line()
497 .line_breaker(AnyCharLineBreaker)
498 .calculate_glyphs(
499 &*FONT_MAP,
500 &SectionGeometry::default(),
501 &[SectionText {
502 text: "hello world",
503 scale: PxScale::from(20.0),
504 ..SectionText::default()
505 }],
506 );
507
508 assert_glyph_order!(glyphs, "hello world");
509
510 assert_relative_eq!(glyphs[0].glyph.position.x, 0.0);
511 let last_glyph = &glyphs.last().unwrap().glyph;
512 assert!(
513 last_glyph.position.x > 0.0,
514 "unexpected last position {:?}",
515 last_glyph.position
516 );
517 }
518
519 #[test]
520 fn single_line_chars_right() {
521 let glyphs = Layout::default_single_line()
522 .line_breaker(AnyCharLineBreaker)
523 .h_align(HorizontalAlign::Right)
524 .calculate_glyphs(
525 &*FONT_MAP,
526 &SectionGeometry::default(),
527 &[SectionText {
528 text: "hello world",
529 scale: PxScale::from(20.0),
530 ..SectionText::default()
531 }],
532 );
533
534 assert_glyph_order!(glyphs, "hello world");
535 let last_glyph = &glyphs.last().unwrap().glyph;
536 assert!(glyphs[0].glyph.position.x < last_glyph.position.x);
537 assert!(
538 last_glyph.position.x <= 0.0,
539 "unexpected last position {:?}",
540 last_glyph.position
541 );
542
543 let sfont = A_FONT.as_scaled(20.0);
544 let rightmost_x = last_glyph.position.x + sfont.h_advance(last_glyph.id);
545 assert_relative_eq!(rightmost_x, 0.0, epsilon = 1e-1);
546 }
547
548 #[test]
549 fn single_line_chars_center() {
550 let glyphs = Layout::default_single_line()
551 .line_breaker(AnyCharLineBreaker)
552 .h_align(HorizontalAlign::Center)
553 .calculate_glyphs(
554 &*FONT_MAP,
555 &SectionGeometry::default(),
556 &[SectionText {
557 text: "hello world",
558 scale: PxScale::from(20.0),
559 ..SectionText::default()
560 }],
561 );
562
563 assert_glyph_order!(glyphs, "hello world");
564 assert!(
565 glyphs[0].glyph.position.x < 0.0,
566 "unexpected first glyph position {:?}",
567 glyphs[0].glyph.position
568 );
569
570 let last_glyph = &glyphs.last().unwrap().glyph;
571 assert!(
572 last_glyph.position.x > 0.0,
573 "unexpected last glyph position {:?}",
574 last_glyph.position
575 );
576
577 let leftmost_x = glyphs[0].glyph.position.x;
578 let sfont = A_FONT.as_scaled(20.0);
579 let rightmost_x = last_glyph.position.x + sfont.h_advance(last_glyph.id);
580 assert_relative_eq!(rightmost_x, -leftmost_x, epsilon = 1e-1);
581 }
582
583 #[test]
584 fn single_line_chars_left_finish_at_newline() {
585 let glyphs = Layout::default_single_line()
586 .line_breaker(AnyCharLineBreaker)
587 .calculate_glyphs(
588 &*FONT_MAP,
589 &SectionGeometry::default(),
590 &[SectionText {
591 text: "hello\nworld",
592 scale: PxScale::from(20.0),
593 ..SectionText::default()
594 }],
595 );
596
597 assert_glyph_order!(glyphs, "hello");
598 assert_relative_eq!(glyphs[0].glyph.position.x, 0.0);
599 assert!(
600 glyphs[4].glyph.position.x > 0.0,
601 "unexpected last position {:?}",
602 glyphs[4].glyph.position
603 );
604 }
605
606 #[test]
607 fn wrap_word_left() {
608 let glyphs = Layout::default_single_line().calculate_glyphs(
609 &*FONT_MAP,
610 &SectionGeometry {
611 bounds: (85.0, f32::INFINITY), ..SectionGeometry::default()
613 },
614 &[SectionText {
615 text: "hello what's _happening_?",
616 scale: PxScale::from(20.0),
617 ..SectionText::default()
618 }],
619 );
620
621 assert_glyph_order!(glyphs, "hello ");
622 assert_relative_eq!(glyphs[0].glyph.position.x, 0.0);
623 let last_glyph = &glyphs.last().unwrap().glyph;
624 assert!(
625 last_glyph.position.x > 0.0,
626 "unexpected last position {:?}",
627 last_glyph.position
628 );
629
630 let glyphs = Layout::default_single_line().calculate_glyphs(
631 &*FONT_MAP,
632 &SectionGeometry {
633 bounds: (125.0, f32::INFINITY),
634 ..SectionGeometry::default()
635 },
636 &[SectionText {
637 text: "hello what's _happening_?",
638 scale: PxScale::from(20.0),
639 ..SectionText::default()
640 }],
641 );
642
643 assert_glyph_order!(glyphs, "hello what's ");
644 assert_relative_eq!(glyphs[0].glyph.position.x, 0.0);
645 let last_glyph = &glyphs.last().unwrap().glyph;
646 assert!(
647 last_glyph.position.x > 0.0,
648 "unexpected last position {:?}",
649 last_glyph.position
650 );
651 }
652
653 #[test]
654 fn single_line_limited_horizontal_room() {
655 let glyphs = Layout::default_single_line()
656 .line_breaker(AnyCharLineBreaker)
657 .calculate_glyphs(
658 &*FONT_MAP,
659 &SectionGeometry {
660 bounds: (50.0, f32::INFINITY),
661 ..SectionGeometry::default()
662 },
663 &[SectionText {
664 text: "hello world",
665 scale: PxScale::from(20.0),
666 ..SectionText::default()
667 }],
668 );
669
670 assert_glyph_order!(glyphs, "hell");
671 assert_relative_eq!(glyphs[0].glyph.position.x, 0.0);
672 }
673
674 #[test]
675 fn wrap_layout_with_new_lines() {
676 let test_str = "Autumn moonlight\n\
677 a worm digs silently\n\
678 into the chestnut.";
679
680 let glyphs = Layout::default_wrap().calculate_glyphs(
681 &*FONT_MAP,
682 &SectionGeometry::default(),
683 &[SectionText {
684 text: test_str,
685 scale: PxScale::from(20.0),
686 ..SectionText::default()
687 }],
688 );
689
690 assert_glyph_order!(
692 glyphs,
693 "Autumn moonlighta worm digs silentlyinto the chestnut."
694 );
695 assert!(
696 glyphs[16].glyph.position.y > glyphs[0].glyph.position.y,
697 "second line should be lower than first"
698 );
699 assert!(
700 glyphs[36].glyph.position.y > glyphs[16].glyph.position.y,
701 "third line should be lower than second"
702 );
703 }
704
705 #[test]
706 fn leftover_max_vmetrics() {
707 let glyphs = Layout::default_single_line().calculate_glyphs(
708 &*FONT_MAP,
709 &SectionGeometry {
710 bounds: (750.0, f32::INFINITY),
711 ..SectionGeometry::default()
712 },
713 &[
714 SectionText {
715 text: "Autumn moonlight, ",
716 scale: PxScale::from(30.0),
717 ..SectionText::default()
718 },
719 SectionText {
720 text: "a worm digs silently ",
721 scale: PxScale::from(40.0),
722 ..SectionText::default()
723 },
724 SectionText {
725 text: "into the chestnut.",
726 scale: PxScale::from(10.0),
727 ..SectionText::default()
728 },
729 ],
730 );
731
732 for g in glyphs {
733 println!("{:?}", (g.glyph.scale, g.glyph.position));
734 let y_pos = g.glyph.position.y;
736 assert_relative_eq!(y_pos, A_FONT.as_scaled(40.0).ascent());
737 }
738 }
739
740 #[test]
741 fn eol_new_line_hard_breaks() {
742 let glyphs = Layout::default_wrap().calculate_glyphs(
743 &*FONT_MAP,
744 &SectionGeometry::default(),
745 &[
746 SectionText {
747 text: "Autumn moonlight, \n",
748 ..SectionText::default()
749 },
750 SectionText {
751 text: "a worm digs silently ",
752 ..SectionText::default()
753 },
754 SectionText {
755 text: "\n",
756 ..SectionText::default()
757 },
758 SectionText {
759 text: "into the chestnut.",
760 ..SectionText::default()
761 },
762 ],
763 );
764
765 let y_ords: HashSet<OrderedFloat<f32>> = glyphs
766 .iter()
767 .map(|g| OrderedFloat(g.glyph.position.y))
768 .collect();
769
770 println!("Y ords: {y_ords:?}");
771 assert_eq!(y_ords.len(), 3, "expected 3 distinct lines");
772
773 assert_glyph_order!(
774 glyphs,
775 "Autumn moonlight, a worm digs silently into the chestnut."
776 );
777
778 let line_2_glyph = &glyphs[18].glyph;
779 let line_3_glyph = &&glyphs[39].glyph;
780 assert_eq!(line_2_glyph.id, A_FONT.glyph_id('a'));
781 assert!(line_2_glyph.position.y > glyphs[0].glyph.position.y);
782
783 assert_eq!(line_3_glyph.id, A_FONT.glyph_id('i'));
784 assert!(line_3_glyph.position.y > line_2_glyph.position.y);
785 }
786
787 #[test]
788 fn single_line_multibyte_chars_finish_at_break() {
789 let unicode_str = "❤❤é❤❤\n❤ß❤";
790 assert_eq!(
791 unicode_str, "\u{2764}\u{2764}\u{e9}\u{2764}\u{2764}\n\u{2764}\u{df}\u{2764}",
792 "invisible char funny business",
793 );
794 assert_eq!(unicode_str.len(), 23);
795 assert_eq!(
796 xi_unicode::LineBreakIterator::new(unicode_str).find(|n| n.1),
797 Some((15, true)),
798 );
799
800 let glyphs = Layout::default_single_line().calculate_glyphs(
801 &*FONT_MAP,
802 &SectionGeometry::default(),
803 &[SectionText {
804 text: unicode_str,
805 scale: PxScale::from(20.0),
806 ..SectionText::default()
807 }],
808 );
809
810 assert_glyph_order!(glyphs, "\u{2764}\u{2764}\u{e9}\u{2764}\u{2764}");
811 assert_relative_eq!(glyphs[0].glyph.position.x, 0.0);
812 assert!(
813 glyphs[4].glyph.position.x > 0.0,
814 "unexpected last position {:?}",
815 glyphs[4].glyph.position
816 );
817 }
818
819 #[test]
820 fn no_inherent_section_break() {
821 let glyphs = Layout::default_wrap().calculate_glyphs(
822 &*FONT_MAP,
823 &SectionGeometry {
824 bounds: (50.0, f32::INFINITY),
825 ..SectionGeometry::default()
826 },
827 &[
828 SectionText {
829 text: "The ",
830 ..SectionText::default()
831 },
832 SectionText {
833 text: "moon",
834 ..SectionText::default()
835 },
836 SectionText {
837 text: "light",
838 ..SectionText::default()
839 },
840 ],
841 );
842
843 assert_glyph_order!(glyphs, "The moonlight");
844
845 let y_ords: HashSet<OrderedFloat<f32>> = glyphs
846 .iter()
847 .map(|g| OrderedFloat(g.glyph.position.y))
848 .collect();
849
850 assert_eq!(y_ords.len(), 2, "Y ords: {y_ords:?}");
851
852 let first_line_y = y_ords.iter().min().unwrap();
853 let second_line_y = y_ords.iter().max().unwrap();
854
855 assert_relative_eq!(glyphs[0].glyph.position.y, first_line_y);
856 assert_relative_eq!(glyphs[4].glyph.position.y, second_line_y);
857 }
858
859 #[test]
860 fn recalculate_identical() {
861 let glyphs = Layout::default().calculate_glyphs(
862 &*FONT_MAP,
863 &SectionGeometry::default(),
864 &[SectionText {
865 text: "hello world",
866 scale: PxScale::from(20.0),
867 ..SectionText::default()
868 }],
869 );
870
871 let recalc = Layout::default().recalculate_glyphs(
872 glyphs,
873 GlyphChange::Unknown,
874 &*FONT_MAP,
875 &SectionGeometry::default(),
876 &[SectionText {
877 text: "hello world",
878 scale: PxScale::from(20.0),
879 ..SectionText::default()
880 }],
881 );
882
883 assert_glyph_order!(recalc, "hello world");
884
885 assert_relative_eq!(recalc[0].glyph.position.x, 0.0);
886 let last_glyph = &recalc.last().unwrap().glyph;
887 assert!(
888 last_glyph.position.x > 0.0,
889 "unexpected last position {:?}",
890 last_glyph.position
891 );
892 }
893
894 #[test]
895 fn recalculate_position() {
896 let geometry_1 = SectionGeometry {
897 screen_position: (0.0, 0.0),
898 ..<_>::default()
899 };
900
901 let glyphs = Layout::default().calculate_glyphs(
902 &*FONT_MAP,
903 &geometry_1,
904 &[SectionText {
905 text: "hello world",
906 scale: PxScale::from(20.0),
907 font_id: FontId(0),
908 }],
909 );
910
911 let original_y = glyphs[0].glyph.position.y;
912
913 let recalc = Layout::default().recalculate_glyphs(
914 glyphs,
915 GlyphChange::Geometry(geometry_1),
916 &*FONT_MAP,
917 &SectionGeometry {
918 screen_position: (0.0, 50.0),
919 ..geometry_1
920 },
921 &[SectionText {
922 text: "hello world",
923 scale: PxScale::from(20.0),
924 ..SectionText::default()
925 }],
926 );
927
928 assert_glyph_order!(recalc, "hello world");
929
930 assert_relative_eq!(recalc[0].glyph.position.x, 0.0);
931 assert_relative_eq!(recalc[0].glyph.position.y, original_y + 50.0);
932 let last_glyph = &recalc.last().unwrap().glyph;
933 assert!(
934 last_glyph.position.x > 0.0,
935 "unexpected last position {:?}",
936 last_glyph.position
937 );
938 }
939
940 #[test]
943 fn wrap_word_chinese() {
944 let glyphs = Layout::default().calculate_glyphs(
945 &*FONT_MAP,
946 &SectionGeometry {
947 bounds: (25.0, f32::INFINITY),
948 ..<_>::default()
949 },
950 &[SectionText {
951 text: "提高代碼執行率",
952 scale: PxScale::from(20.0),
953 font_id: FontId(1),
954 }],
955 );
956
957 assert_glyph_order!(glyphs, "提高代碼執行率", font = &*CJK_FONT);
958
959 let x_positions: HashSet<_> = glyphs
960 .iter()
961 .map(|g| OrderedFloat(g.glyph.position.x))
962 .collect();
963 assert_eq!(x_positions, std::iter::once(OrderedFloat(0.0)).collect());
964
965 let y_positions: HashSet<_> = glyphs
966 .iter()
967 .map(|g| OrderedFloat(g.glyph.position.y))
968 .collect();
969
970 assert_eq!(y_positions.len(), 7, "{y_positions:?}");
971 }
972
973 #[test]
976 fn include_spaces_in_layout_width_preceded_hard_break() {
977 let glyphs_no_newline = Layout::default()
979 .h_align(HorizontalAlign::Right)
980 .calculate_glyphs(
981 &*FONT_MAP,
982 &SectionGeometry {
983 bounds: (50.0, f32::INFINITY),
984 ..<_>::default()
985 },
986 &[SectionText {
987 text: "Foo bar",
988 ..<_>::default()
989 }],
990 );
991
992 let y_positions: HashSet<_> = glyphs_no_newline
993 .iter()
994 .map(|g| OrderedFloat(g.glyph.position.y))
995 .collect();
996 assert_eq!(y_positions.len(), 2, "{y_positions:?}");
997
998 let glyphs_newline = Layout::default()
1000 .h_align(HorizontalAlign::Right)
1001 .calculate_glyphs(
1002 &*FONT_MAP,
1003 &SectionGeometry {
1004 bounds: (50.0, f32::INFINITY),
1005 ..<_>::default()
1006 },
1007 &[SectionText {
1008 text: "Foo \nbar",
1009 ..<_>::default()
1010 }],
1011 );
1012
1013 let y_positions: HashSet<_> = glyphs_newline
1014 .iter()
1015 .map(|g| OrderedFloat(g.glyph.position.y))
1016 .collect();
1017 assert_eq!(y_positions.len(), 2, "{y_positions:?}");
1018
1019 let newline_f = &glyphs_newline[0];
1022 let no_newline_f = &glyphs_no_newline[0];
1023 assert!(
1024 newline_f.glyph.position.x < no_newline_f.glyph.position.x,
1025 "explicit newline `F` ({}) should be 1 space to the left of no-newline `F` ({})",
1026 newline_f.glyph.position.x,
1027 no_newline_f.glyph.position.x,
1028 );
1029 }
1030
1031 #[test]
1034 fn include_spaces_in_layout_width_preceded_end() {
1035 let glyphs_no_newline = Layout::default()
1036 .h_align(HorizontalAlign::Right)
1037 .calculate_glyphs(
1038 &*FONT_MAP,
1039 &<_>::default(),
1040 &[SectionText {
1041 text: "Foo",
1042 ..<_>::default()
1043 }],
1044 );
1045
1046 let glyphs_space = Layout::default()
1047 .h_align(HorizontalAlign::Right)
1048 .calculate_glyphs(
1049 &*FONT_MAP,
1050 &<_>::default(),
1051 &[SectionText {
1052 text: "Foo ",
1053 ..<_>::default()
1054 }],
1055 );
1056
1057 let space_f = &glyphs_space[0];
1058 let no_space_f = &glyphs_no_newline[0];
1059 assert!(
1060 space_f.glyph.position.x < no_space_f.glyph.position.x,
1061 "with-space `F` ({}) should be 3 spaces to the left of no-space `F` ({})",
1062 space_f.glyph.position.x,
1063 no_space_f.glyph.position.x,
1064 );
1065 }
1066}