Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/fonts/text_layout_test.rs
StratoSDK / crates / strato-ui-renderer / src / fonts / text_layout_test.rs
1//! Platform-independent text layout tests.
2use crate::elements::DEFAULT_UI_LINE_HEIGHT_RATIO;
3use crate::fonts::{FamilyId, Properties, Style, Weight};
4use crate::platform::FontDB as _;
5use crate::platform::LineStyle;
6use crate::text_layout::{
7 ClipConfig, Line, StyleAndFont, TextAlignment, TextFrame, TextStyle, DEFAULT_TOP_BOTTOM_RATIO,
8};
9use anyhow::Result;
10use itertools::Itertools;
11use pathfinder_color::ColorU;
12 
13#[cfg(target_os = "macos")]
14use crate::platform::mac::fonts::FontDB;
15 
16#[cfg(not(target_os = "macos"))]
17use crate::windowing::winit::fonts::FontDB;
18 
19const FONT_SIZE: f32 = 16.;
20const FRAME_WIDTH: f32 = 80.;
21const FRAME_HEIGHT: f32 = f32::MAX;
22 
23#[test]
24fn test_fixed_width_tab_size_matches_spaces_width() -> Result<()> {
25 let (font_db, font_family) = init_fonts();
26 
27 let tabbed = "\tX";
28 let spaced = " X";
29 
30 let line_style = LineStyle {
31 font_size: FONT_SIZE,
32 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
33 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
34 fixed_width_tab_size: Some(4),
35 };
36 
37 let tabbed_line = font_db.text_layout_system().layout_line(
38 tabbed,
39 line_style,
40 &[(
41 0..tabbed.chars().count(),
42 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
43 )],
44 f32::MAX,
45 ClipConfig::default(),
46 );
47 
48 let spaced_line = font_db.text_layout_system().layout_line(
49 spaced,
50 line_style,
51 &[(
52 0..spaced.chars().count(),
53 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
54 )],
55 f32::MAX,
56 ClipConfig::default(),
57 );
58 
59 let error = (tabbed_line.width - spaced_line.width).abs();
60 assert!(
61 error < 1.0,
62 "expected tab width ~= 4 spaces; got tabbed {}, spaced {} (error {})",
63 tabbed_line.width,
64 spaced_line.width,
65 error
66 );
67 
68 Ok(())
69}
70 
71/// Read the bundled Roboto font's bytes from the filesystem.
72fn load_roboto_bytes() -> Vec<Vec<u8>> {
73 use std::{fs::read, path::PathBuf};
74 let root = env!("CARGO_MANIFEST_DIR");
75 let typeface_files = ["Roboto-Italic.ttf", "Roboto-Bold.ttf", "Roboto-Regular.ttf"];
76 typeface_files
77 .iter()
78 .map(|font_file| {
79 let path = [
80 root, "..", "..", "app", "assets", "bundled", "fonts", "roboto", font_file,
81 ]
82 .iter()
83 .collect::<PathBuf>();
84 Ok(read(path)?)
85 })
86 .collect::<Result<Vec<_>>>()
87 .expect("should be able to read roboto font bytes from filesystem")
88}
89 
90pub(crate) fn init_fonts() -> (FontDB, FamilyId) {
91 let mut font_db = FontDB::new();
92 let font_bytes = load_roboto_bytes();
93 let roboto = font_db
94 .load_from_bytes("Roboto", font_bytes)
95 .expect("should be able to load Roboto font for test");
96 (font_db, roboto)
97}
98 
99pub(crate) fn collect_glyph_indices(frame: &TextFrame) -> Vec<Vec<usize>> {
100 frame
101 .lines()
102 .iter()
103 .map(|line| {
104 line.runs
105 .iter()
106 .flat_map(|run| run.glyphs.iter())
107 .map(|glyph| glyph.index)
108 .collect_vec()
109 })
110 .collect_vec()
111}
112 
113fn collect_caret_position_start_offsets(frame: &TextFrame) -> Vec<Vec<usize>> {
114 frame
115 .lines()
116 .iter()
117 .map(collect_line_caret_position_starts)
118 .collect_vec()
119}
120 
121fn collect_caret_position_last_offsets(frame: &TextFrame) -> Vec<Vec<usize>> {
122 frame
123 .lines()
124 .iter()
125 .map(|line| {
126 line.caret_positions
127 .iter()
128 .map(|caret_position| caret_position.last_offset)
129 .collect_vec()
130 })
131 .collect_vec()
132}
133 
134pub(crate) fn collect_line_caret_position_starts(line: &Line) -> Vec<usize> {
135 line.caret_positions
136 .iter()
137 .map(|pos| pos.start_offset)
138 .collect_vec()
139}
140 
141/// Checks that the head indent and first line's width don't exceed the frame's width.
142fn first_line_bounded(frame: &TextFrame, first_line_indent: f32, frame_width: f32) -> bool {
143 let first_line_width = frame.lines().first().unwrap().width;
144 first_line_width + first_line_indent.min(frame_width) <= frame_width
145}
146 
147fn all_lines_bounded(frame: &TextFrame, frame_width: f32) -> bool {
148 frame.lines().iter().fold(true, |all_bounded, line| {
149 let current_bounded = line.width <= frame_width;
150 all_bounded && current_bounded
151 })
152}
153 
154#[test]
155fn test_leading_newline_caret_positions() -> Result<()> {
156 let (font_db, font_family) = init_fonts();
157 
158 let text = "\nstart on the\nsecond line";
159 
160 let frame = font_db.text_layout_system().layout_text(
161 text,
162 LineStyle {
163 font_size: 16.0,
164 line_height_ratio: 1.2,
165 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
166 fixed_width_tab_size: None,
167 },
168 &[(
169 0..26,
170 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
171 )],
172 10000.0,
173 10000.0,
174 TextAlignment::Left,
175 None,
176 );
177 
178 let caret_position_starts = collect_caret_position_start_offsets(&frame);
179 
180 assert_eq!(
181 caret_position_starts,
182 vec![
183 (0..1).collect_vec(),
184 (1..14).collect_vec(),
185 (14..25).collect_vec(),
186 ],
187 );
188 
189 let caret_position_lasts = collect_caret_position_last_offsets(&frame);
190 
191 assert_eq!(
192 caret_position_lasts,
193 vec![
194 (0..1).collect_vec(),
195 (1..14).collect_vec(),
196 (14..25).collect_vec(),
197 ],
198 );
199 
200 Ok(())
201}
202 
203#[test]
204fn test_multiline_caret_positions() -> Result<()> {
205 let (font_db, font_family) = init_fonts();
206 let text = "eel\n\nivy\nthesaurus\n\nclingy";
207 
208 let frame = font_db.text_layout_system().layout_text(
209 text,
210 LineStyle {
211 font_size: 16.0,
212 line_height_ratio: 1.2,
213 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
214 fixed_width_tab_size: None,
215 },
216 &[(
217 0..26,
218 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
219 )],
220 10000.0,
221 10000.0,
222 TextAlignment::Left,
223 None,
224 );
225 
226 let caret_position_starts = collect_caret_position_start_offsets(&frame);
227 
228 assert_eq!(
229 caret_position_starts,
230 vec![
231 (0..4).collect_vec(),
232 (4..5).collect_vec(),
233 (5..9).collect_vec(),
234 (9..19).collect_vec(),
235 (19..20).collect_vec(),
236 (20..26).collect_vec(),
237 ],
238 );
239 
240 let caret_position_lasts = collect_caret_position_last_offsets(&frame);
241 
242 assert_eq!(
243 caret_position_lasts,
244 vec![
245 (0..4).collect_vec(),
246 (4..5).collect_vec(),
247 (5..9).collect_vec(),
248 (9..19).collect_vec(),
249 (19..20).collect_vec(),
250 (20..26).collect_vec(),
251 ],
252 );
253 
254 Ok(())
255}
256 
257#[cfg_attr(
258 not(macos),
259 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
260)]
261#[test]
262fn test_layout_str_infinite_height() -> Result<()> {
263 let (font_db, font_family) = init_fonts();
264 
265 // Ensure layout_text terminates even if there's an unbounded `max_height`.
266 let frame = font_db.text_layout_system().layout_text(
267 "hello world",
268 LineStyle {
269 font_size: 14.,
270 line_height_ratio: 1.2,
271 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
272 fixed_width_tab_size: None,
273 },
274 &[(
275 0..12,
276 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
277 )],
278 0.,
279 f32::INFINITY,
280 Default::default(),
281 None,
282 );
283 
284 assert_eq!(frame.lines().len(), 10);
285 
286 Ok(())
287}
288 
289#[test]
290fn test_layout_str() -> Result<()> {
291 let (font_db, font_family) = init_fonts();
292 
293 font_db.text_layout_system().layout_line(
294 "hello world 😃",
295 LineStyle {
296 font_size: 16.0,
297 line_height_ratio: 1.2,
298 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
299 fixed_width_tab_size: None,
300 },
301 &[
302 (
303 0..2,
304 StyleAndFont::new(
305 font_family,
306 Properties::default().weight(Weight::Bold),
307 TextStyle::new(),
308 ),
309 ),
310 (
311 2..6,
312 StyleAndFont::new(
313 font_family,
314 Properties::default().style(Style::Italic),
315 TextStyle::new(),
316 ),
317 ),
318 (
319 6..13,
320 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
321 ),
322 ],
323 100000.0,
324 ClipConfig::default(),
325 );
326 
327 Ok(())
328}
329 
330#[cfg_attr(windows, ignore = "TODO(CORE-3626)")]
331#[test]
332fn test_layout_str_with_style() -> Result<()> {
333 let (font_db, font_family) = init_fonts();
334 
335 let line = font_db.text_layout_system().layout_line(
336 "hello world 😃",
337 LineStyle {
338 font_size: 16.0,
339 line_height_ratio: 1.2,
340 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
341 fixed_width_tab_size: None,
342 },
343 &[
344 (
345 0..2,
346 StyleAndFont::new(
347 font_family,
348 Properties::default().weight(Weight::Bold),
349 TextStyle::new().with_foreground_color(ColorU::from_u32(0xFF0000FF)),
350 ),
351 ),
352 (
353 2..6,
354 StyleAndFont::new(
355 font_family,
356 Properties::default().style(Style::Italic),
357 TextStyle::new(),
358 ),
359 ),
360 (
361 6..13,
362 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
363 ),
364 ],
365 100000.0,
366 ClipConfig::default(),
367 );
368 
369 // The first run should have a red foreground color, while the rest of the runs should not
370 // have a foreground color set.
371 assert_eq!(line.runs.len(), 4);
372 
373 let foreground_colors: Vec<_> = line
374 .runs
375 .into_iter()
376 .map(|run| run.styles.foreground_color)
377 .collect();
378 
379 assert_eq!(
380 foreground_colors,
381 vec![Some(ColorU::from_u32(0xFF0000FF)), None, None, None]
382 );
383 
384 Ok(())
385}
386 
387#[cfg_attr(
388 not(macos),
389 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
390)]
391#[test]
392fn test_multiline_glyph_indices() -> Result<()> {
393 let (font_db, font_family) = init_fonts();
394 
395 let text = "eel\n\nivy\nthesaurus\n\nclingy";
396 
397 let frame = font_db.text_layout_system().layout_text(
398 text,
399 LineStyle {
400 font_size: 16.0,
401 line_height_ratio: 1.2,
402 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
403 fixed_width_tab_size: None,
404 },
405 &[(
406 0..26,
407 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
408 )],
409 10000.0,
410 10000.0,
411 TextAlignment::Left,
412 None,
413 );
414 
415 let glyph_indices = collect_glyph_indices(&frame);
416 assert_eq!(
417 glyph_indices,
418 vec![
419 (0..4).collect_vec(),
420 (4..5).collect_vec(),
421 (5..9).collect_vec(),
422 (9..19).collect_vec(),
423 (19..20).collect_vec(),
424 (20..26).collect_vec(),
425 ],
426 );
427 
428 Ok(())
429}
430 
431#[test]
432fn test_layout_mixed_ltr_rtl_text() -> Result<()> {
433 let (font_db, font_family) = init_fonts();
434 
435 let text = "Help! Is this عربي?";
436 let frame = font_db.text_layout_system().layout_text(
437 text,
438 LineStyle {
439 font_size: 16.0,
440 line_height_ratio: 1.2,
441 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
442 fixed_width_tab_size: None,
443 },
444 &[(
445 0..text.encode_utf16().count(),
446 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
447 )],
448 f32::MAX, /* max_width */
449 f32::MAX, /* max_height */
450 Default::default(),
451 None,
452 );
453 
454 // Assert that we successfully laid everything out in a single line
455 // without panicking.
456 assert_eq!(frame.lines().len(), 1);
457 
458 Ok(())
459}
460 
461#[test]
462fn test_char_indices() -> Result<()> {
463 let (font_db, ligatured_font) = init_fonts();
464 
465 let text = "fluffing pillows";
466 let line = font_db.text_layout_system().layout_line(
467 text,
468 LineStyle {
469 font_size: 16.0,
470 line_height_ratio: 1.2,
471 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
472 fixed_width_tab_size: None,
473 },
474 &[(
475 0..text.encode_utf16().count(),
476 StyleAndFont::new(ligatured_font, Properties::default(), TextStyle::new()),
477 )],
478 10000.0,
479 ClipConfig::default(),
480 );
481 
482 // It's easiest to understand what's happening here by visualizing the text and seeing which
483 // characters get combined to become a single glyph. At a high level, what this is testing
484 // is that after laying out the string, we see some characters get combined into a single
485 // glyph. For example, the text "Zapfino" gets combined into a single glyph, which is why
486 // there is a jump from 23 to 30 in the list of glyph indices below.
487 // See https://docs.google.com/drawings/d/18qOKhzA5rWaMuxKVeWFDXh7ebrDjxongarAckkm0qnE/edit
488 // for a full diagram of what's happening here.
489 assert_eq!(
490 line.runs
491 .iter()
492 .flat_map(|r| r.glyphs.iter())
493 .map(|g| g.index)
494 .collect::<Vec<_>>(),
495 vec![0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
496 );
497 Ok(())
498}
499 
500#[test]
501fn test_caret_positions() -> Result<()> {
502 let (font_db, ligatured_font) = init_fonts();
503 
504 // This string has 16 characters, but 14 UTF-16 code points.
505 // Each 'fl' or 'fi' character encodes as 2 UTF-16 code points.
506 let text: &str = "fluffing pillows";
507 // 0123456789012345
508 // 0 12 3456789012
509 
510 let line = font_db.text_layout_system().layout_line(
511 text,
512 LineStyle {
513 font_size: 16.0,
514 line_height_ratio: 1.2,
515 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
516 fixed_width_tab_size: None,
517 },
518 &[(
519 0..text.encode_utf16().count(),
520 StyleAndFont::new(ligatured_font, Properties::default(), TextStyle::new()),
521 )],
522 10000.0,
523 ClipConfig::default(),
524 );
525 
526 // There are only 13 glyphs because 'fl' and 'ffi' each have ligatures.
527 assert_eq!(
528 line.runs.iter().map(|run| run.glyphs.len()).sum::<usize>(),
529 13
530 );
531 
532 // On MacOS, there should be a caret position for each character.
533 #[cfg(target_os = "macos")]
534 assert_eq!(
535 collect_line_caret_position_starts(&line),
536 (0..16).collect::<Vec<usize>>()
537 );
538 
539 // With cosmic-text, we only get one caret position per visual glyph.
540 #[cfg(not(target_os = "macos"))]
541 assert_eq!(
542 collect_line_caret_position_starts(&line),
543 [0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
544 );
545 
546 // On MacOS, there is a caret for the 3rd character at the 3rd position, even though
547 // the first 2 characters ("fl") are represented with 1 glyph.
548 #[cfg(target_os = "macos")]
549 assert_eq!(
550 line.caret_position_for_index(3),
551 line.caret_positions[3].position_in_line
552 );
553 
554 Ok(())
555}
556 
557#[cfg_attr(
558 not(macos),
559 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
560)]
561#[test]
562fn test_layout_text() -> Result<()> {
563 let (font_db, font_family) = init_fonts();
564 
565 let text = "This is a sample text layout!";
566 let frame = font_db.text_layout_system().layout_text(
567 text,
568 LineStyle {
569 font_size: 16.0,
570 line_height_ratio: 1.2,
571 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
572 fixed_width_tab_size: None,
573 },
574 &[(
575 0..text.encode_utf16().count(),
576 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
577 )],
578 100., /* max_width */
579 f32::MAX, /* max_height */
580 Default::default(),
581 None,
582 );
583 
584 // The text should contain multiple lines since it can't fit in 100 pixels on the first
585 // line.
586 assert_eq!(frame.lines().len(), 3);
587 
588 // The text should be wrapped over 4 lines and look like this:
589 // "This is a"
590 // "sample text"
591 // "layout!"
592 assert_eq!(
593 collect_glyph_indices(&frame),
594 vec![
595 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
596 vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21],
597 vec![22, 23, 24, 25, 26, 27, 28],
598 ]
599 );
600 
601 Ok(())
602}
603 
604#[cfg_attr(
605 not(macos),
606 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
607)]
608#[test]
609fn test_layout_text_first_line_head_indent() -> Result<()> {
610 // Similar test to above, except we add in a left head indent (with reduced max width)!
611 let (font_db, font_family) = init_fonts();
612 
613 let text = "This is a sample text layout!";
614 let frame = font_db.text_layout_system().layout_text(
615 text,
616 LineStyle {
617 font_size: 16.0,
618 line_height_ratio: 1.2,
619 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
620 fixed_width_tab_size: None,
621 },
622 &[(
623 0..text.encode_utf16().count(),
624 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
625 )],
626 80., /* max_width */
627 f32::MAX, /* max_height */
628 Default::default(),
629 Some(50.), /* first_line_head_indent */
630 );
631 
632 // The text should contain multiple lines since we have a 50px left head indent on the first
633 // line and then each line only has 80px.
634 assert_eq!(frame.lines().len(), 4);
635 
636 assert_eq!(
637 collect_glyph_indices(&frame),
638 vec![
639 vec![0, 1, 2],
640 vec![3, 4, 5, 6, 7, 8, 9],
641 vec![10, 11, 12, 13, 14, 15, 16],
642 vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
643 ]
644 );
645 
646 Ok(())
647}
648 
649#[cfg_attr(
650 not(macos),
651 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
652)]
653#[test]
654fn test_layout_text_large_first_line_head_indent() -> Result<()> {
655 // Similar test to above, except we have a large first line head indent which goes beyond the
656 // max_width of the first line! We expect an empty line at the start to account for this (post-layout).
657 let (font_db, font_family) = init_fonts();
658 
659 let text = "This is a sample text layout!";
660 let frame = font_db.text_layout_system().layout_text(
661 text,
662 LineStyle {
663 font_size: 16.0,
664 line_height_ratio: 1.2,
665 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
666 fixed_width_tab_size: None,
667 },
668 &[(
669 0..text.encode_utf16().count(),
670 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
671 )],
672 80., /* max_width */
673 f32::MAX, /* max_height */
674 Default::default(),
675 Some(80.), /* first_line_head_indent */
676 );
677 
678 // We expect 1 empty line at the start and then 7 lines of content.
679 assert_eq!(frame.lines().len(), 4);
680 
681 // CoreText leaves newline glyphs in the laid-out lines.
682 #[cfg(target_os = "macos")]
683 assert_eq!(
684 collect_glyph_indices(&frame),
685 vec![
686 vec![], // first line head indent takes up entire line!
687 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
688 vec![10, 11, 12, 13, 14, 15, 16],
689 vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
690 ]
691 );
692 
693 // cosmic-text strips newline glyphs from the laid-out lines.
694 #[cfg(not(target_os = "macos"))]
695 assert_eq!(
696 collect_glyph_indices(&frame),
697 vec![
698 vec![], // first line head indent takes up entire line!
699 vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
700 vec![10, 11, 12, 13, 14, 15],
701 vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
702 ]
703 );
704 
705 Ok(())
706}
707 
708#[cfg_attr(
709 not(macos),
710 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
711)]
712#[test]
713fn test_layout_text_last_line_clipped() -> Result<()> {
714 let (font_db, font_family) = init_fonts();
715 
716 let text = "This text doesn't fit in one line";
717 
718 let max_width = 100.;
719 let frame = font_db.text_layout_system().layout_text(
720 text,
721 LineStyle {
722 font_size: 16.0,
723 line_height_ratio: 1.2,
724 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
725 fixed_width_tab_size: None,
726 },
727 &[(
728 0..text.encode_utf16().count(),
729 StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
730 )],
731 max_width,
732 30., /* max_height */
733 Default::default(),
734 None,
735 );
736 
737 // The text should only fit one line.
738 assert_eq!(frame.lines().len(), 1);
739 
740 // The text is one line long and should be clipped like so: "this text doesn't...".
741 // Note that the contents are not clipped, but the width exceeding the max width
742 // and the non-none clip direction indicate that when we paint, this is clipped.
743 assert_eq!(
744 collect_glyph_indices(&frame),
745 vec![[
746 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24,
747 25, 26, 27, 28, 29, 30, 31, 32
748 ],]
749 );
750 let first_line = frame.lines().first().unwrap();
751 assert!(first_line.width > max_width);
752 assert!(first_line.clip_config.is_some());
753 
754 Ok(())
755}
756 
757#[test]
758fn test_layout_text_first_line_indent_small() -> Result<()> {
759 let (font_db, roboto) = init_fonts();
760 
761 let text = "Let's lay out s𐍈me Roboto text.";
762 // 0123456789012345678901234567890
763 let line_style = LineStyle {
764 font_size: FONT_SIZE,
765 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
766 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
767 fixed_width_tab_size: None,
768 };
769 let style_runs = [(
770 0..text.encode_utf16().count(),
771 StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
772 )];
773 
774 // First, lay out the text with no head indent.
775 let no_indent_frame = font_db.text_layout_system().layout_text(
776 text,
777 line_style,
778 &style_runs,
779 FRAME_WIDTH,
780 FRAME_HEIGHT,
781 Default::default(),
782 None,
783 );
784 
785 // The text should contain multiple lines.
786 // The first line has about the same amount of content as the others,
787 // since there's no head indent.
788 assert_eq!(no_indent_frame.lines().len(), 4);
789 
790 // CoreText leaves newline glyphs in the laid-out lines.
791 #[cfg(target_os = "macos")]
792 assert_eq!(
793 collect_glyph_indices(&no_indent_frame),
794 vec![
795 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], // 9 is whitespace.
796 vec![10, 11, 12, 13, 14, 15, 16, 17, 18], // 18 is whitespace.
797 vec![19, 20, 21, 22, 23, 24, 25], // 25 is whitespace.
798 vec![26, 27, 28, 29, 30],
799 ]
800 );
801 
802 // cosmic-text strips newline glyphs from the laid-out lines.
803 #[cfg(not(target_os = "macos"))]
804 assert_eq!(
805 collect_glyph_indices(&no_indent_frame),
806 vec![
807 vec![0, 1, 2, 3, 4, 5, 6, 7, 8], // 9 is whitespace.
808 vec![10, 11, 12, 13, 14, 15, 16, 17], // 18 is whitespace.
809 vec![19, 20, 21, 22, 23, 24], // 25 is whitespace.
810 vec![26, 27, 28, 29, 30],
811 ]
812 );
813 
814 assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
815 assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
816 
817 // Lay out the text with a 5px head indent.
818 let small_indent_frame = font_db.text_layout_system().layout_text(
819 text,
820 line_style,
821 &style_runs,
822 FRAME_WIDTH,
823 FRAME_HEIGHT,
824 Default::default(),
825 Some(5.),
826 );
827 
828 // The first line has about the same amount of content as the others,
829 // since the head indent is small.
830 assert_eq!(small_indent_frame.lines().len(), 4);
831 
832 // CoreText leaves newline glyphs in the laid-out lines.
833 #[cfg(target_os = "macos")]
834 assert_eq!(
835 collect_glyph_indices(&small_indent_frame),
836 vec![
837 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
838 vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
839 vec![19, 20, 21, 22, 23, 24, 25],
840 vec![26, 27, 28, 29, 30],
841 ]
842 );
843 
844 // cosmic-text strips newline glyphs from the laid-out lines.
845 #[cfg(not(target_os = "macos"))]
846 assert_eq!(
847 collect_glyph_indices(&small_indent_frame),
848 vec![
849 vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
850 vec![10, 11, 12, 13, 14, 15, 16, 17],
851 vec![19, 20, 21, 22, 23, 24],
852 vec![26, 27, 28, 29, 30],
853 ]
854 );
855 
856 assert!(first_line_bounded(&small_indent_frame, 5., FRAME_WIDTH));
857 assert!(all_lines_bounded(&small_indent_frame, FRAME_WIDTH));
858 
859 // Lay out the text with a 40px head indent,
860 // which is half the width of the frame.
861 let half_indent_frame = font_db.text_layout_system().layout_text(
862 text,
863 line_style,
864 &style_runs,
865 FRAME_WIDTH,
866 FRAME_HEIGHT,
867 Default::default(),
868 Some(FRAME_WIDTH / 2.),
869 );
870 
871 // The text contains an additional line to accommodate the indent.
872 assert_eq!(half_indent_frame.lines().len(), 5);
873 
874 // CoreText leaves newline glyphs in the laid-out lines.
875 #[cfg(target_os = "macos")]
876 assert_eq!(
877 collect_glyph_indices(&half_indent_frame),
878 vec![
879 vec![0, 1, 2, 3, 4, 5], // Fewer glyphs fit on this line. 5 is whitespace.
880 vec![6, 7, 8, 9, 10, 11, 12, 13], // 13 is whitespace.
881 vec![14, 15, 16, 17, 18],
882 vec![19, 20, 21, 22, 23, 24, 25],
883 vec![26, 27, 28, 29, 30],
884 ]
885 );
886 
887 // cosmic-text strips newline glyphs from the laid-out lines.
888 #[cfg(not(target_os = "macos"))]
889 assert_eq!(
890 collect_glyph_indices(&half_indent_frame),
891 vec![
892 vec![0, 1, 2, 3, 4], // Fewer glyphs fit on this line. 5 is whitespace.
893 vec![6, 7, 8, 9, 10, 11, 12], // 13 is whitespace.
894 vec![14, 15, 16, 17],
895 vec![19, 20, 21, 22, 23, 24],
896 vec![26, 27, 28, 29, 30],
897 ]
898 );
899 
900 assert!(first_line_bounded(
901 &half_indent_frame,
902 FRAME_WIDTH / 2.,
903 FRAME_WIDTH,
904 ));
905 assert!(all_lines_bounded(&half_indent_frame, FRAME_WIDTH));
906 
907 Ok(())
908}
909 
910#[test]
911fn test_layout_text_first_line_indent_medium() -> Result<()> {
912 let (font_db, roboto) = init_fonts();
913 
914 let text = "Let's lay out s𐍈me Roboto text.";
915 // 0123456789012345678901234567890
916 let line_style = LineStyle {
917 font_size: FONT_SIZE,
918 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
919 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
920 fixed_width_tab_size: None,
921 };
922 let style_runs = [(
923 0..text.encode_utf16().count(),
924 StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
925 )];
926 
927 // First, lay out the text with no head indent.
928 let no_indent_frame = font_db.text_layout_system().layout_text(
929 text,
930 line_style,
931 &style_runs,
932 FRAME_WIDTH,
933 FRAME_HEIGHT,
934 Default::default(),
935 Some(0.),
936 );
937 
938 // The text should contain multiple lines.
939 // The first line has about the same amount of content as the others,
940 // since there's no head indent.
941 assert_eq!(no_indent_frame.lines().len(), 4);
942 
943 // CoreText leaves newline glyphs in the laid-out lines.
944 #[cfg(target_os = "macos")]
945 assert_eq!(
946 collect_glyph_indices(&no_indent_frame),
947 vec![
948 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
949 vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
950 vec![19, 20, 21, 22, 23, 24, 25],
951 vec![26, 27, 28, 29, 30],
952 ]
953 );
954 
955 // cosmic-text strips newline glyphs from the laid-out lines.
956 #[cfg(not(target_os = "macos"))]
957 assert_eq!(
958 collect_glyph_indices(&no_indent_frame),
959 vec![
960 vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
961 vec![10, 11, 12, 13, 14, 15, 16, 17],
962 vec![19, 20, 21, 22, 23, 24],
963 vec![26, 27, 28, 29, 30],
964 ]
965 );
966 
967 assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
968 assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
969 
970 // Lay out the text with a head indent that's 15px smaller than
971 // the width of the frame.
972 let overflow_indent_frame = font_db.text_layout_system().layout_text(
973 text,
974 line_style,
975 &style_runs,
976 FRAME_WIDTH,
977 FRAME_HEIGHT,
978 Default::default(),
979 Some(FRAME_WIDTH - 20.),
980 );
981 
982 // The first line should have some glyphs on it, but not the whole
983 // first word.
984 assert_eq!(overflow_indent_frame.lines().len(), 5);
985 
986 // CoreText leaves newline glyphs in the laid-out lines.
987 #[cfg(target_os = "macos")]
988 assert_eq!(
989 collect_glyph_indices(&overflow_indent_frame),
990 vec![
991 vec![0, 1], // Only a few glyphs fit.
992 vec![2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
993 vec![14, 15, 16, 17, 18],
994 vec![19, 20, 21, 22, 23, 24, 25],
995 vec![26, 27, 28, 29, 30],
996 ]
997 );
998 
999 // cosmic-text strips newline glyphs from the laid-out lines.
1000 #[cfg(not(target_os = "macos"))]
1001 assert_eq!(
1002 collect_glyph_indices(&overflow_indent_frame),
1003 vec![
1004 vec![0, 1], // Only a few glyphs fit.
1005 vec![2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
1006 vec![14, 15, 16, 17],
1007 vec![19, 20, 21, 22, 23, 24],
1008 vec![26, 27, 28, 29, 30],
1009 ]
1010 );
1011 assert!(first_line_bounded(
1012 &overflow_indent_frame,
1013 FRAME_WIDTH - 20.,
1014 FRAME_WIDTH,
1015 ));
1016 assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
1017 
1018 Ok(())
1019}
1020 
1021#[test]
1022fn test_layout_text_first_line_indent_large() -> Result<()> {
1023 let (font_db, roboto) = init_fonts();
1024 
1025 let text = "Let's lay out s𐍈me Roboto text.";
1026 // 0123456789012345678901234567890
1027 let line_style = LineStyle {
1028 font_size: FONT_SIZE,
1029 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
1030 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
1031 fixed_width_tab_size: None,
1032 };
1033 let style_runs = [(
1034 0..text.encode_utf16().count(),
1035 StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
1036 )];
1037 
1038 // First, lay out the text with no head indent.
1039 let no_indent_frame = font_db.text_layout_system().layout_text(
1040 text,
1041 line_style,
1042 &style_runs,
1043 FRAME_WIDTH,
1044 FRAME_HEIGHT,
1045 Default::default(),
1046 Some(0.),
1047 );
1048 
1049 // The text should contain multiple lines.
1050 // The first line has about the same amount of content as the others,
1051 // since there's no head indent.
1052 assert_eq!(no_indent_frame.lines().len(), 4);
1053 
1054 // CoreText leaves newline glyphs in the laid-out lines.
1055 #[cfg(target_os = "macos")]
1056 assert_eq!(
1057 collect_glyph_indices(&no_indent_frame),
1058 vec![
1059 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1060 vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
1061 vec![19, 20, 21, 22, 23, 24, 25],
1062 vec![26, 27, 28, 29, 30],
1063 ]
1064 );
1065 
1066 // cosmic-text strips newline glyphs from the laid-out lines.
1067 #[cfg(not(target_os = "macos"))]
1068 assert_eq!(
1069 collect_glyph_indices(&no_indent_frame),
1070 vec![
1071 vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
1072 vec![10, 11, 12, 13, 14, 15, 16, 17],
1073 vec![19, 20, 21, 22, 23, 24],
1074 vec![26, 27, 28, 29, 30],
1075 ]
1076 );
1077 assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
1078 assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
1079 
1080 // Lay out the text with a head indent that's 5px bigger than the width of the frame.
1081 let overflow_indent_frame = font_db.text_layout_system().layout_text(
1082 text,
1083 line_style,
1084 &style_runs,
1085 FRAME_WIDTH,
1086 FRAME_HEIGHT,
1087 Default::default(),
1088 Some(FRAME_WIDTH + 5.),
1089 );
1090 
1091 // The first line is left entirely blank since no glyphs fit on it.
1092 
1093 // CoreText leaves newline glyphs in the laid-out lines.
1094 #[cfg(target_os = "macos")]
1095 assert_eq!(
1096 collect_glyph_indices(&overflow_indent_frame),
1097 vec![
1098 vec![], // No glyphs fit on this line.
1099 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1100 vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
1101 vec![19, 20, 21, 22, 23, 24, 25],
1102 vec![26, 27, 28, 29, 30],
1103 ]
1104 );
1105 
1106 // cosmic-text strips newline glyphs from the laid-out lines.
1107 #[cfg(not(target_os = "macos"))]
1108 assert_eq!(
1109 collect_glyph_indices(&overflow_indent_frame),
1110 vec![
1111 vec![], // No glyphs fit on this line.
1112 vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
1113 vec![10, 11, 12, 13, 14, 15, 16, 17],
1114 vec![19, 20, 21, 22, 23, 24],
1115 vec![26, 27, 28, 29, 30],
1116 ]
1117 );
1118 assert!(first_line_bounded(
1119 &overflow_indent_frame,
1120 FRAME_WIDTH + 5.,
1121 FRAME_WIDTH,
1122 ));
1123 assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
1124 
1125 // Lay out the text with a 79px head indent,
1126 // which spans almost the entire width of the frame.
1127 let big_indent_frame = font_db.text_layout_system().layout_text(
1128 text,
1129 line_style,
1130 &style_runs,
1131 FRAME_WIDTH,
1132 FRAME_HEIGHT,
1133 Default::default(),
1134 Some(FRAME_WIDTH - 0.1),
1135 );
1136 
1137 // The first line is left entirely blank since no glyphs fit on it.
1138 assert_eq!(big_indent_frame.lines().len(), 5);
1139 
1140 // CoreText leaves newline glyphs in the laid-out lines.
1141 #[cfg(target_os = "macos")]
1142 assert_eq!(
1143 collect_glyph_indices(&big_indent_frame),
1144 vec![
1145 vec![], // No glyphs fit on this line.
1146 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1147 vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
1148 vec![19, 20, 21, 22, 23, 24, 25],
1149 vec![26, 27, 28, 29, 30],
1150 ]
1151 );
1152 
1153 // cosmic-text strips newline glyphs from the laid-out lines.
1154 #[cfg(not(target_os = "macos"))]
1155 assert_eq!(
1156 collect_glyph_indices(&big_indent_frame),
1157 vec![
1158 vec![], // No glyphs fit on this line.
1159 vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
1160 vec![10, 11, 12, 13, 14, 15, 16, 17],
1161 vec![19, 20, 21, 22, 23, 24],
1162 vec![26, 27, 28, 29, 30],
1163 ]
1164 );
1165 
1166 assert!(first_line_bounded(
1167 &big_indent_frame,
1168 FRAME_WIDTH - 0.1,
1169 FRAME_WIDTH,
1170 ));
1171 assert!(all_lines_bounded(&big_indent_frame, FRAME_WIDTH));
1172 
1173 Ok(())
1174}
1175 
1176// TODO(PLAT-779): check all line bounds once bidirectional wrapping is fixed in cosmic-text.
1177// See https://github.com/pop-os/cosmic-text/issues/252.
1178#[cfg_attr(
1179 not(macos),
1180 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
1181)]
1182#[test]
1183fn test_layout_text_first_line_indent_small_bidirectional() -> Result<()> {
1184 let (font_db, roboto) = init_fonts();
1185 
1186 let text = "brekkie, إفطار, lunch (غداء) and dinner - عشاء";
1187 // 0123456783210945678901265437890123456789015432
1188 // RTL spans: |-----| |----| |----|
1189 let line_style = LineStyle {
1190 font_size: FONT_SIZE,
1191 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
1192 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
1193 fixed_width_tab_size: None,
1194 };
1195 let style_runs = [(
1196 0..text.encode_utf16().count(),
1197 StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
1198 )];
1199 
1200 // First, lay out the text with no head indent.
1201 let no_indent_frame = font_db.text_layout_system().layout_text(
1202 text,
1203 line_style,
1204 &style_runs,
1205 FRAME_WIDTH,
1206 FRAME_HEIGHT,
1207 Default::default(),
1208 None,
1209 );
1210 
1211 // The text should contain multiple lines.
1212 // The first line has about the same amount of content as the others,
1213 // since there's no head indent.
1214 assert_eq!(no_indent_frame.lines().len(), 5);
1215 assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
1216 // assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
1217 
1218 // Lay out the text with a 5px head indent.
1219 let small_indent_frame = font_db.text_layout_system().layout_text(
1220 text,
1221 line_style,
1222 &style_runs,
1223 FRAME_WIDTH,
1224 FRAME_HEIGHT,
1225 Default::default(),
1226 Some(5.),
1227 );
1228 
1229 // The first line has about the same amount of content as the others,
1230 // since the head indent is small.
1231 assert_eq!(small_indent_frame.lines().len(), 5);
1232 assert!(first_line_bounded(&small_indent_frame, 5., FRAME_WIDTH));
1233 // assert!(all_lines_bounded(&small_indent_frame, FRAME_WIDTH));
1234 
1235 // Lay out the text with a 40px head indent,
1236 // which is half the width of the frame.
1237 let half_indent_frame = font_db.text_layout_system().layout_text(
1238 text,
1239 line_style,
1240 &style_runs,
1241 FRAME_WIDTH,
1242 FRAME_HEIGHT,
1243 Default::default(),
1244 Some(FRAME_WIDTH / 2.),
1245 );
1246 
1247 // The text contains an additional line to accommodate the indent.
1248 assert_eq!(half_indent_frame.lines().len(), 5);
1249 assert!(first_line_bounded(
1250 &half_indent_frame,
1251 FRAME_WIDTH / 2.,
1252 FRAME_WIDTH,
1253 ));
1254 // assert!(all_lines_bounded(&half_indent_frame, FRAME_WIDTH));
1255 
1256 Ok(())
1257}
1258 
1259// TODO(PLAT-779): check all line bounds once bidirectional wrapping is fixed in cosmic-text.
1260// See https://github.com/pop-os/cosmic-text/issues/252.
1261#[cfg_attr(
1262 not(macos),
1263 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
1264)]
1265#[test]
1266fn test_layout_text_first_line_indent_medium_bidirectional() -> Result<()> {
1267 let (font_db, roboto) = init_fonts();
1268 
1269 let text = "brekkie, إفطار, lunch (غداء) and dinner - عشاء";
1270 // 0123456783210945678901265437890123456789015432
1271 // RTL spans: |-----| |----| |----|
1272 let line_style = LineStyle {
1273 font_size: FONT_SIZE,
1274 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
1275 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
1276 fixed_width_tab_size: None,
1277 };
1278 let style_runs = [(
1279 0..text.encode_utf16().count(),
1280 StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
1281 )];
1282 
1283 // First, lay out the text with no head indent.
1284 let no_indent_frame = font_db.text_layout_system().layout_text(
1285 text,
1286 line_style,
1287 &style_runs,
1288 FRAME_WIDTH,
1289 FRAME_HEIGHT,
1290 Default::default(),
1291 None,
1292 );
1293 
1294 // The text should contain multiple lines.
1295 // The first line has about the same amount of content as the others,
1296 // since there's no head indent.
1297 assert_eq!(no_indent_frame.lines().len(), 5);
1298 assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
1299 // assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
1300 
1301 // Lay out the text with a head indent that's 15px smaller than
1302 // the width of the frame.
1303 let overflow_indent_frame = font_db.text_layout_system().layout_text(
1304 text,
1305 line_style,
1306 &style_runs,
1307 FRAME_WIDTH,
1308 FRAME_HEIGHT,
1309 Default::default(),
1310 Some(FRAME_WIDTH - 20.),
1311 );
1312 
1313 // The first line should have some glyphs on it, but not the whole
1314 // first word.
1315 assert_eq!(overflow_indent_frame.lines().len(), 5);
1316 assert!(first_line_bounded(
1317 &overflow_indent_frame,
1318 FRAME_WIDTH - 20.,
1319 FRAME_WIDTH,
1320 ));
1321 // assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
1322 
1323 Ok(())
1324}
1325 
1326// TODO(PLAT-779): check all line bounds once bidirectional wrapping is fixed in cosmic-text.
1327// See https://github.com/pop-os/cosmic-text/issues/252.
1328#[cfg_attr(
1329 not(macos),
1330 ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
1331)]
1332#[test]
1333fn test_layout_text_first_line_indent_large_bidirectional() -> Result<()> {
1334 let (font_db, roboto) = init_fonts();
1335 
1336 let text = "brekkie, إفطار, lunch (غداء) and dinner - عشاء";
1337 // 0123456783210945678901265437890123456789015432
1338 // RTL spans: |-----| |----| |----|
1339 let line_style = LineStyle {
1340 font_size: FONT_SIZE,
1341 line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
1342 baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
1343 fixed_width_tab_size: None,
1344 };
1345 let style_runs = [(
1346 0..text.encode_utf16().count(),
1347 StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
1348 )];
1349 
1350 // First, lay out the text with no head indent.
1351 let no_indent_frame = font_db.text_layout_system().layout_text(
1352 text,
1353 line_style,
1354 &style_runs,
1355 FRAME_WIDTH,
1356 FRAME_HEIGHT,
1357 Default::default(),
1358 Some(0.),
1359 );
1360 
1361 // The text should contain multiple lines.
1362 // The first line has about the same amount of content as the others,
1363 // since there's no head indent.
1364 assert_eq!(no_indent_frame.lines().len(), 5);
1365 assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
1366 // assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
1367 
1368 // Lay out the text with a head indent that's 5px bigger than the width of the frame.
1369 let overflow_indent_frame = font_db.text_layout_system().layout_text(
1370 text,
1371 line_style,
1372 &style_runs,
1373 FRAME_WIDTH,
1374 FRAME_HEIGHT,
1375 Default::default(),
1376 Some(FRAME_WIDTH + 5.),
1377 );
1378 
1379 // The first line is left entirely blank since no glyphs fit on it.
1380 assert_eq!(overflow_indent_frame.lines().len(), 6);
1381 assert!(collect_glyph_indices(&overflow_indent_frame)
1382 .first()
1383 .unwrap()
1384 .is_empty(),);
1385 assert!(first_line_bounded(
1386 &overflow_indent_frame,
1387 FRAME_WIDTH + 5.,
1388 FRAME_WIDTH,
1389 ));
1390 // assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
1391 
1392 // Lay out the text with a 79px head indent,
1393 // which spans almost the entire width of the frame.
1394 let big_indent_frame = font_db.text_layout_system().layout_text(
1395 text,
1396 line_style,
1397 &style_runs,
1398 FRAME_WIDTH,
1399 FRAME_HEIGHT,
1400 Default::default(),
1401 Some(FRAME_WIDTH - 0.1),
1402 );
1403 
1404 // The first line is left entirely blank since no glyphs fit on it.
1405 assert_eq!(big_indent_frame.lines().len(), 6);
1406 assert!(collect_glyph_indices(&big_indent_frame)
1407 .first()
1408 .unwrap()
1409 .is_empty(),);
1410 assert!(first_line_bounded(
1411 &big_indent_frame,
1412 FRAME_WIDTH - 0.1,
1413 FRAME_WIDTH,
1414 ));
1415 // assert!(all_lines_bounded(&big_indent_frame, FRAME_WIDTH));
1416 
1417 Ok(())
1418}
1419