1418 lines
43 KiB
Rust
1418 lines
43 KiB
Rust
//! Platform-independent text layout tests.
|
|
use anyhow::Result;
|
|
use itertools::Itertools;
|
|
use pathfinder_color::ColorU;
|
|
|
|
use crate::elements::DEFAULT_UI_LINE_HEIGHT_RATIO;
|
|
use crate::fonts::{FamilyId, Properties, Style, Weight};
|
|
#[cfg(target_os = "macos")]
|
|
use crate::platform::mac::fonts::FontDB;
|
|
use crate::platform::{FontDB as _, LineStyle};
|
|
use crate::text_layout::{
|
|
ClipConfig, Line, StyleAndFont, TextAlignment, TextFrame, TextStyle, DEFAULT_TOP_BOTTOM_RATIO,
|
|
};
|
|
#[cfg(not(target_os = "macos"))]
|
|
use crate::windowing::winit::fonts::FontDB;
|
|
|
|
const FONT_SIZE: f32 = 16.;
|
|
const FRAME_WIDTH: f32 = 80.;
|
|
const FRAME_HEIGHT: f32 = f32::MAX;
|
|
|
|
#[test]
|
|
fn test_fixed_width_tab_size_matches_spaces_width() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let tabbed = "\tX";
|
|
let spaced = " X";
|
|
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: Some(4),
|
|
};
|
|
|
|
let tabbed_line = font_db.text_layout_system().layout_line(
|
|
tabbed,
|
|
line_style,
|
|
&[(
|
|
0..tabbed.chars().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
f32::MAX,
|
|
ClipConfig::default(),
|
|
);
|
|
|
|
let spaced_line = font_db.text_layout_system().layout_line(
|
|
spaced,
|
|
line_style,
|
|
&[(
|
|
0..spaced.chars().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
f32::MAX,
|
|
ClipConfig::default(),
|
|
);
|
|
|
|
let error = (tabbed_line.width - spaced_line.width).abs();
|
|
assert!(
|
|
error < 1.0,
|
|
"expected tab width ~= 4 spaces; got tabbed {}, spaced {} (error {})",
|
|
tabbed_line.width,
|
|
spaced_line.width,
|
|
error
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Read the bundled Roboto font's bytes from the filesystem.
|
|
fn load_roboto_bytes() -> Vec<Vec<u8>> {
|
|
use std::fs::read;
|
|
use std::path::PathBuf;
|
|
let root = env!("CARGO_MANIFEST_DIR");
|
|
let typeface_files = ["Roboto-Italic.ttf", "Roboto-Bold.ttf", "Roboto-Regular.ttf"];
|
|
typeface_files
|
|
.iter()
|
|
.map(|font_file| {
|
|
let path = [
|
|
root, "..", "..", "app", "assets", "bundled", "fonts", "roboto", font_file,
|
|
]
|
|
.iter()
|
|
.collect::<PathBuf>();
|
|
Ok(read(path)?)
|
|
})
|
|
.collect::<Result<Vec<_>>>()
|
|
.expect("should be able to read roboto font bytes from filesystem")
|
|
}
|
|
|
|
pub(crate) fn init_fonts() -> (FontDB, FamilyId) {
|
|
let mut font_db = FontDB::new();
|
|
let font_bytes = load_roboto_bytes();
|
|
let roboto = font_db
|
|
.load_from_bytes("Roboto", font_bytes)
|
|
.expect("should be able to load Roboto font for test");
|
|
(font_db, roboto)
|
|
}
|
|
|
|
pub(crate) fn collect_glyph_indices(frame: &TextFrame) -> Vec<Vec<usize>> {
|
|
frame
|
|
.lines()
|
|
.iter()
|
|
.map(|line| {
|
|
line.runs
|
|
.iter()
|
|
.flat_map(|run| run.glyphs.iter())
|
|
.map(|glyph| glyph.index)
|
|
.collect_vec()
|
|
})
|
|
.collect_vec()
|
|
}
|
|
|
|
fn collect_caret_position_start_offsets(frame: &TextFrame) -> Vec<Vec<usize>> {
|
|
frame
|
|
.lines()
|
|
.iter()
|
|
.map(collect_line_caret_position_starts)
|
|
.collect_vec()
|
|
}
|
|
|
|
fn collect_caret_position_last_offsets(frame: &TextFrame) -> Vec<Vec<usize>> {
|
|
frame
|
|
.lines()
|
|
.iter()
|
|
.map(|line| {
|
|
line.caret_positions
|
|
.iter()
|
|
.map(|caret_position| caret_position.last_offset)
|
|
.collect_vec()
|
|
})
|
|
.collect_vec()
|
|
}
|
|
|
|
pub(crate) fn collect_line_caret_position_starts(line: &Line) -> Vec<usize> {
|
|
line.caret_positions
|
|
.iter()
|
|
.map(|pos| pos.start_offset)
|
|
.collect_vec()
|
|
}
|
|
|
|
/// Checks that the head indent and first line's width don't exceed the frame's width.
|
|
fn first_line_bounded(frame: &TextFrame, first_line_indent: f32, frame_width: f32) -> bool {
|
|
let first_line_width = frame.lines().first().unwrap().width;
|
|
first_line_width + first_line_indent.min(frame_width) <= frame_width
|
|
}
|
|
|
|
fn all_lines_bounded(frame: &TextFrame, frame_width: f32) -> bool {
|
|
frame.lines().iter().fold(true, |all_bounded, line| {
|
|
let current_bounded = line.width <= frame_width;
|
|
all_bounded && current_bounded
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn test_leading_newline_caret_positions() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "\nstart on the\nsecond line";
|
|
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..26,
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
10000.0,
|
|
10000.0,
|
|
TextAlignment::Left,
|
|
None,
|
|
);
|
|
|
|
let caret_position_starts = collect_caret_position_start_offsets(&frame);
|
|
|
|
assert_eq!(
|
|
caret_position_starts,
|
|
vec![
|
|
(0..1).collect_vec(),
|
|
(1..14).collect_vec(),
|
|
(14..25).collect_vec(),
|
|
],
|
|
);
|
|
|
|
let caret_position_lasts = collect_caret_position_last_offsets(&frame);
|
|
|
|
assert_eq!(
|
|
caret_position_lasts,
|
|
vec![
|
|
(0..1).collect_vec(),
|
|
(1..14).collect_vec(),
|
|
(14..25).collect_vec(),
|
|
],
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiline_caret_positions() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
let text = "eel\n\nivy\nthesaurus\n\nclingy";
|
|
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..26,
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
10000.0,
|
|
10000.0,
|
|
TextAlignment::Left,
|
|
None,
|
|
);
|
|
|
|
let caret_position_starts = collect_caret_position_start_offsets(&frame);
|
|
|
|
assert_eq!(
|
|
caret_position_starts,
|
|
vec![
|
|
(0..4).collect_vec(),
|
|
(4..5).collect_vec(),
|
|
(5..9).collect_vec(),
|
|
(9..19).collect_vec(),
|
|
(19..20).collect_vec(),
|
|
(20..26).collect_vec(),
|
|
],
|
|
);
|
|
|
|
let caret_position_lasts = collect_caret_position_last_offsets(&frame);
|
|
|
|
assert_eq!(
|
|
caret_position_lasts,
|
|
vec![
|
|
(0..4).collect_vec(),
|
|
(4..5).collect_vec(),
|
|
(5..9).collect_vec(),
|
|
(9..19).collect_vec(),
|
|
(19..20).collect_vec(),
|
|
(20..26).collect_vec(),
|
|
],
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_str_infinite_height() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
// Ensure layout_text terminates even if there's an unbounded `max_height`.
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
"hello world",
|
|
LineStyle {
|
|
font_size: 14.,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..12,
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
0.,
|
|
f32::INFINITY,
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
assert_eq!(frame.lines().len(), 10);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_layout_str() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
font_db.text_layout_system().layout_line(
|
|
"hello world 😃",
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[
|
|
(
|
|
0..2,
|
|
StyleAndFont::new(
|
|
font_family,
|
|
Properties::default().weight(Weight::Bold),
|
|
TextStyle::new(),
|
|
),
|
|
),
|
|
(
|
|
2..6,
|
|
StyleAndFont::new(
|
|
font_family,
|
|
Properties::default().style(Style::Italic),
|
|
TextStyle::new(),
|
|
),
|
|
),
|
|
(
|
|
6..13,
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
),
|
|
],
|
|
100000.0,
|
|
ClipConfig::default(),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(windows, ignore = "TODO(CORE-3626)")]
|
|
#[test]
|
|
fn test_layout_str_with_style() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let line = font_db.text_layout_system().layout_line(
|
|
"hello world 😃",
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[
|
|
(
|
|
0..2,
|
|
StyleAndFont::new(
|
|
font_family,
|
|
Properties::default().weight(Weight::Bold),
|
|
TextStyle::new().with_foreground_color(ColorU::from_u32(0xFF0000FF)),
|
|
),
|
|
),
|
|
(
|
|
2..6,
|
|
StyleAndFont::new(
|
|
font_family,
|
|
Properties::default().style(Style::Italic),
|
|
TextStyle::new(),
|
|
),
|
|
),
|
|
(
|
|
6..13,
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
),
|
|
],
|
|
100000.0,
|
|
ClipConfig::default(),
|
|
);
|
|
|
|
// The first run should have a red foreground color, while the rest of the runs should not
|
|
// have a foreground color set.
|
|
assert_eq!(line.runs.len(), 4);
|
|
|
|
let foreground_colors: Vec<_> = line
|
|
.runs
|
|
.into_iter()
|
|
.map(|run| run.styles.foreground_color)
|
|
.collect();
|
|
|
|
assert_eq!(
|
|
foreground_colors,
|
|
vec![Some(ColorU::from_u32(0xFF0000FF)), None, None, None]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_multiline_glyph_indices() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "eel\n\nivy\nthesaurus\n\nclingy";
|
|
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..26,
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
10000.0,
|
|
10000.0,
|
|
TextAlignment::Left,
|
|
None,
|
|
);
|
|
|
|
let glyph_indices = collect_glyph_indices(&frame);
|
|
assert_eq!(
|
|
glyph_indices,
|
|
vec![
|
|
(0..4).collect_vec(),
|
|
(4..5).collect_vec(),
|
|
(5..9).collect_vec(),
|
|
(9..19).collect_vec(),
|
|
(19..20).collect_vec(),
|
|
(20..26).collect_vec(),
|
|
],
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_layout_mixed_ltr_rtl_text() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "Help! Is this عربي?";
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
f32::MAX, /* max_width */
|
|
f32::MAX, /* max_height */
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
// Assert that we successfully laid everything out in a single line
|
|
// without panicking.
|
|
assert_eq!(frame.lines().len(), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_char_indices() -> Result<()> {
|
|
let (font_db, ligatured_font) = init_fonts();
|
|
|
|
let text = "fluffing pillows";
|
|
let line = font_db.text_layout_system().layout_line(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(ligatured_font, Properties::default(), TextStyle::new()),
|
|
)],
|
|
10000.0,
|
|
ClipConfig::default(),
|
|
);
|
|
|
|
// It's easiest to understand what's happening here by visualizing the text and seeing which
|
|
// characters get combined to become a single glyph. At a high level, what this is testing
|
|
// is that after laying out the string, we see some characters get combined into a single
|
|
// glyph. For example, the text "Zapfino" gets combined into a single glyph, which is why
|
|
// there is a jump from 23 to 30 in the list of glyph indices below.
|
|
// See https://docs.google.com/drawings/d/18qOKhzA5rWaMuxKVeWFDXh7ebrDjxongarAckkm0qnE/edit
|
|
// for a full diagram of what's happening here.
|
|
assert_eq!(
|
|
line.runs
|
|
.iter()
|
|
.flat_map(|r| r.glyphs.iter())
|
|
.map(|g| g.index)
|
|
.collect::<Vec<_>>(),
|
|
vec![0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_caret_positions() -> Result<()> {
|
|
let (font_db, ligatured_font) = init_fonts();
|
|
|
|
// This string has 16 characters, but 14 UTF-16 code points.
|
|
// Each 'fl' or 'fi' character encodes as 2 UTF-16 code points.
|
|
let text: &str = "fluffing pillows";
|
|
// 0123456789012345
|
|
// 0 12 3456789012
|
|
|
|
let line = font_db.text_layout_system().layout_line(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(ligatured_font, Properties::default(), TextStyle::new()),
|
|
)],
|
|
10000.0,
|
|
ClipConfig::default(),
|
|
);
|
|
|
|
// There are only 13 glyphs because 'fl' and 'ffi' each have ligatures.
|
|
assert_eq!(
|
|
line.runs.iter().map(|run| run.glyphs.len()).sum::<usize>(),
|
|
13
|
|
);
|
|
|
|
// On MacOS, there should be a caret position for each character.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_line_caret_position_starts(&line),
|
|
(0..16).collect::<Vec<usize>>()
|
|
);
|
|
|
|
// With cosmic-text, we only get one caret position per visual glyph.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_line_caret_position_starts(&line),
|
|
[0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
|
);
|
|
|
|
// On MacOS, there is a caret for the 3rd character at the 3rd position, even though
|
|
// the first 2 characters ("fl") are represented with 1 glyph.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
line.caret_position_for_index(3),
|
|
line.caret_positions[3].position_in_line
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "This is a sample text layout!";
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
100., /* max_width */
|
|
f32::MAX, /* max_height */
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
// The text should contain multiple lines since it can't fit in 100 pixels on the first
|
|
// line.
|
|
assert_eq!(frame.lines().len(), 3);
|
|
|
|
// The text should be wrapped over 4 lines and look like this:
|
|
// "This is a"
|
|
// "sample text"
|
|
// "layout!"
|
|
assert_eq!(
|
|
collect_glyph_indices(&frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21],
|
|
vec![22, 23, 24, 25, 26, 27, 28],
|
|
]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text_first_line_head_indent() -> Result<()> {
|
|
// Similar test to above, except we add in a left head indent (with reduced max width)!
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "This is a sample text layout!";
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
80., /* max_width */
|
|
f32::MAX, /* max_height */
|
|
Default::default(),
|
|
Some(50.), /* first_line_head_indent */
|
|
);
|
|
|
|
// The text should contain multiple lines since we have a 50px left head indent on the first
|
|
// line and then each line only has 80px.
|
|
assert_eq!(frame.lines().len(), 4);
|
|
|
|
assert_eq!(
|
|
collect_glyph_indices(&frame),
|
|
vec![
|
|
vec![0, 1, 2],
|
|
vec![3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16],
|
|
vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
|
|
]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text_large_first_line_head_indent() -> Result<()> {
|
|
// Similar test to above, except we have a large first line head indent which goes beyond the
|
|
// max_width of the first line! We expect an empty line at the start to account for this (post-layout).
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "This is a sample text layout!";
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
80., /* max_width */
|
|
f32::MAX, /* max_height */
|
|
Default::default(),
|
|
Some(80.), /* first_line_head_indent */
|
|
);
|
|
|
|
// We expect 1 empty line at the start and then 7 lines of content.
|
|
assert_eq!(frame.lines().len(), 4);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&frame),
|
|
vec![
|
|
vec![], // first line head indent takes up entire line!
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16],
|
|
vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&frame),
|
|
vec![
|
|
vec![], // first line head indent takes up entire line!
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
vec![10, 11, 12, 13, 14, 15],
|
|
vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
|
|
]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text_last_line_clipped() -> Result<()> {
|
|
let (font_db, font_family) = init_fonts();
|
|
|
|
let text = "This text doesn't fit in one line";
|
|
|
|
let max_width = 100.;
|
|
let frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
LineStyle {
|
|
font_size: 16.0,
|
|
line_height_ratio: 1.2,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
},
|
|
&[(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(font_family, Properties::default(), TextStyle::new()),
|
|
)],
|
|
max_width,
|
|
30., /* max_height */
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
// The text should only fit one line.
|
|
assert_eq!(frame.lines().len(), 1);
|
|
|
|
// The text is one line long and should be clipped like so: "this text doesn't...".
|
|
// Note that the contents are not clipped, but the width exceeding the max width
|
|
// and the non-none clip direction indicate that when we paint, this is clipped.
|
|
assert_eq!(
|
|
collect_glyph_indices(&frame),
|
|
vec![[
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24,
|
|
25, 26, 27, 28, 29, 30, 31, 32
|
|
],]
|
|
);
|
|
let first_line = frame.lines().first().unwrap();
|
|
assert!(first_line.width > max_width);
|
|
assert!(first_line.clip_config.is_some());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_layout_text_first_line_indent_small() -> Result<()> {
|
|
let (font_db, roboto) = init_fonts();
|
|
|
|
let text = "Let's lay out s𐍈me Roboto text.";
|
|
// 0123456789012345678901234567890
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
};
|
|
let style_runs = [(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
|
|
)];
|
|
|
|
// First, lay out the text with no head indent.
|
|
let no_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
// The text should contain multiple lines.
|
|
// The first line has about the same amount of content as the others,
|
|
// since there's no head indent.
|
|
assert_eq!(no_indent_frame.lines().len(), 4);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&no_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], // 9 is whitespace.
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18], // 18 is whitespace.
|
|
vec![19, 20, 21, 22, 23, 24, 25], // 25 is whitespace.
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&no_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8], // 9 is whitespace.
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17], // 18 is whitespace.
|
|
vec![19, 20, 21, 22, 23, 24], // 25 is whitespace.
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
|
|
assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a 5px head indent.
|
|
let small_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(5.),
|
|
);
|
|
|
|
// The first line has about the same amount of content as the others,
|
|
// since the head indent is small.
|
|
assert_eq!(small_indent_frame.lines().len(), 4);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&small_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&small_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
assert!(first_line_bounded(&small_indent_frame, 5., FRAME_WIDTH));
|
|
assert!(all_lines_bounded(&small_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a 40px head indent,
|
|
// which is half the width of the frame.
|
|
let half_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH / 2.),
|
|
);
|
|
|
|
// The text contains an additional line to accommodate the indent.
|
|
assert_eq!(half_indent_frame.lines().len(), 5);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&half_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5], // Fewer glyphs fit on this line. 5 is whitespace.
|
|
vec![6, 7, 8, 9, 10, 11, 12, 13], // 13 is whitespace.
|
|
vec![14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&half_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4], // Fewer glyphs fit on this line. 5 is whitespace.
|
|
vec![6, 7, 8, 9, 10, 11, 12], // 13 is whitespace.
|
|
vec![14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
assert!(first_line_bounded(
|
|
&half_indent_frame,
|
|
FRAME_WIDTH / 2.,
|
|
FRAME_WIDTH,
|
|
));
|
|
assert!(all_lines_bounded(&half_indent_frame, FRAME_WIDTH));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_layout_text_first_line_indent_medium() -> Result<()> {
|
|
let (font_db, roboto) = init_fonts();
|
|
|
|
let text = "Let's lay out s𐍈me Roboto text.";
|
|
// 0123456789012345678901234567890
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
};
|
|
let style_runs = [(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
|
|
)];
|
|
|
|
// First, lay out the text with no head indent.
|
|
let no_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(0.),
|
|
);
|
|
|
|
// The text should contain multiple lines.
|
|
// The first line has about the same amount of content as the others,
|
|
// since there's no head indent.
|
|
assert_eq!(no_indent_frame.lines().len(), 4);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&no_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&no_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
|
|
assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a head indent that's 15px smaller than
|
|
// the width of the frame.
|
|
let overflow_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH - 20.),
|
|
);
|
|
|
|
// The first line should have some glyphs on it, but not the whole
|
|
// first word.
|
|
assert_eq!(overflow_indent_frame.lines().len(), 5);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&overflow_indent_frame),
|
|
vec![
|
|
vec![0, 1], // Only a few glyphs fit.
|
|
vec![2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
|
|
vec![14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&overflow_indent_frame),
|
|
vec![
|
|
vec![0, 1], // Only a few glyphs fit.
|
|
vec![2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
|
vec![14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
assert!(first_line_bounded(
|
|
&overflow_indent_frame,
|
|
FRAME_WIDTH - 20.,
|
|
FRAME_WIDTH,
|
|
));
|
|
assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_layout_text_first_line_indent_large() -> Result<()> {
|
|
let (font_db, roboto) = init_fonts();
|
|
|
|
let text = "Let's lay out s𐍈me Roboto text.";
|
|
// 0123456789012345678901234567890
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
};
|
|
let style_runs = [(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
|
|
)];
|
|
|
|
// First, lay out the text with no head indent.
|
|
let no_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(0.),
|
|
);
|
|
|
|
// The text should contain multiple lines.
|
|
// The first line has about the same amount of content as the others,
|
|
// since there's no head indent.
|
|
assert_eq!(no_indent_frame.lines().len(), 4);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&no_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&no_indent_frame),
|
|
vec![
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
|
|
assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a head indent that's 5px bigger than the width of the frame.
|
|
let overflow_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH + 5.),
|
|
);
|
|
|
|
// The first line is left entirely blank since no glyphs fit on it.
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&overflow_indent_frame),
|
|
vec![
|
|
vec![], // No glyphs fit on this line.
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&overflow_indent_frame),
|
|
vec![
|
|
vec![], // No glyphs fit on this line.
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
assert!(first_line_bounded(
|
|
&overflow_indent_frame,
|
|
FRAME_WIDTH + 5.,
|
|
FRAME_WIDTH,
|
|
));
|
|
assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a 79px head indent,
|
|
// which spans almost the entire width of the frame.
|
|
let big_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH - 0.1),
|
|
);
|
|
|
|
// The first line is left entirely blank since no glyphs fit on it.
|
|
assert_eq!(big_indent_frame.lines().len(), 5);
|
|
|
|
// CoreText leaves newline glyphs in the laid-out lines.
|
|
#[cfg(target_os = "macos")]
|
|
assert_eq!(
|
|
collect_glyph_indices(&big_indent_frame),
|
|
vec![
|
|
vec![], // No glyphs fit on this line.
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17, 18],
|
|
vec![19, 20, 21, 22, 23, 24, 25],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
// cosmic-text strips newline glyphs from the laid-out lines.
|
|
#[cfg(not(target_os = "macos"))]
|
|
assert_eq!(
|
|
collect_glyph_indices(&big_indent_frame),
|
|
vec![
|
|
vec![], // No glyphs fit on this line.
|
|
vec![0, 1, 2, 3, 4, 5, 6, 7, 8],
|
|
vec![10, 11, 12, 13, 14, 15, 16, 17],
|
|
vec![19, 20, 21, 22, 23, 24],
|
|
vec![26, 27, 28, 29, 30],
|
|
]
|
|
);
|
|
|
|
assert!(first_line_bounded(
|
|
&big_indent_frame,
|
|
FRAME_WIDTH - 0.1,
|
|
FRAME_WIDTH,
|
|
));
|
|
assert!(all_lines_bounded(&big_indent_frame, FRAME_WIDTH));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// TODO(PLAT-779): check all line bounds once bidirectional wrapping is fixed in cosmic-text.
|
|
// See https://github.com/pop-os/cosmic-text/issues/252.
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text_first_line_indent_small_bidirectional() -> Result<()> {
|
|
let (font_db, roboto) = init_fonts();
|
|
|
|
let text = "brekkie, إفطار, lunch (غداء) and dinner - عشاء";
|
|
// 0123456783210945678901265437890123456789015432
|
|
// RTL spans: |-----| |----| |----|
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
};
|
|
let style_runs = [(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
|
|
)];
|
|
|
|
// First, lay out the text with no head indent.
|
|
let no_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
// The text should contain multiple lines.
|
|
// The first line has about the same amount of content as the others,
|
|
// since there's no head indent.
|
|
assert_eq!(no_indent_frame.lines().len(), 5);
|
|
assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
|
|
// assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a 5px head indent.
|
|
let small_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(5.),
|
|
);
|
|
|
|
// The first line has about the same amount of content as the others,
|
|
// since the head indent is small.
|
|
assert_eq!(small_indent_frame.lines().len(), 5);
|
|
assert!(first_line_bounded(&small_indent_frame, 5., FRAME_WIDTH));
|
|
// assert!(all_lines_bounded(&small_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a 40px head indent,
|
|
// which is half the width of the frame.
|
|
let half_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH / 2.),
|
|
);
|
|
|
|
// The text contains an additional line to accommodate the indent.
|
|
assert_eq!(half_indent_frame.lines().len(), 5);
|
|
assert!(first_line_bounded(
|
|
&half_indent_frame,
|
|
FRAME_WIDTH / 2.,
|
|
FRAME_WIDTH,
|
|
));
|
|
// assert!(all_lines_bounded(&half_indent_frame, FRAME_WIDTH));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// TODO(PLAT-779): check all line bounds once bidirectional wrapping is fixed in cosmic-text.
|
|
// See https://github.com/pop-os/cosmic-text/issues/252.
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text_first_line_indent_medium_bidirectional() -> Result<()> {
|
|
let (font_db, roboto) = init_fonts();
|
|
|
|
let text = "brekkie, إفطار, lunch (غداء) and dinner - عشاء";
|
|
// 0123456783210945678901265437890123456789015432
|
|
// RTL spans: |-----| |----| |----|
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
};
|
|
let style_runs = [(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
|
|
)];
|
|
|
|
// First, lay out the text with no head indent.
|
|
let no_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
None,
|
|
);
|
|
|
|
// The text should contain multiple lines.
|
|
// The first line has about the same amount of content as the others,
|
|
// since there's no head indent.
|
|
assert_eq!(no_indent_frame.lines().len(), 5);
|
|
assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
|
|
// assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a head indent that's 15px smaller than
|
|
// the width of the frame.
|
|
let overflow_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH - 20.),
|
|
);
|
|
|
|
// The first line should have some glyphs on it, but not the whole
|
|
// first word.
|
|
assert_eq!(overflow_indent_frame.lines().len(), 5);
|
|
assert!(first_line_bounded(
|
|
&overflow_indent_frame,
|
|
FRAME_WIDTH - 20.,
|
|
FRAME_WIDTH,
|
|
));
|
|
// assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// TODO(PLAT-779): check all line bounds once bidirectional wrapping is fixed in cosmic-text.
|
|
// See https://github.com/pop-os/cosmic-text/issues/252.
|
|
#[cfg_attr(
|
|
not(macos),
|
|
ignore = "discrepancy in winit vs. MacOS text layout implementation: glyph indices do not match"
|
|
)]
|
|
#[test]
|
|
fn test_layout_text_first_line_indent_large_bidirectional() -> Result<()> {
|
|
let (font_db, roboto) = init_fonts();
|
|
|
|
let text = "brekkie, إفطار, lunch (غداء) and dinner - عشاء";
|
|
// 0123456783210945678901265437890123456789015432
|
|
// RTL spans: |-----| |----| |----|
|
|
let line_style = LineStyle {
|
|
font_size: FONT_SIZE,
|
|
line_height_ratio: DEFAULT_UI_LINE_HEIGHT_RATIO,
|
|
baseline_ratio: DEFAULT_TOP_BOTTOM_RATIO,
|
|
fixed_width_tab_size: None,
|
|
};
|
|
let style_runs = [(
|
|
0..text.encode_utf16().count(),
|
|
StyleAndFont::new(roboto, Properties::default(), TextStyle::new()),
|
|
)];
|
|
|
|
// First, lay out the text with no head indent.
|
|
let no_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(0.),
|
|
);
|
|
|
|
// The text should contain multiple lines.
|
|
// The first line has about the same amount of content as the others,
|
|
// since there's no head indent.
|
|
assert_eq!(no_indent_frame.lines().len(), 5);
|
|
assert!(first_line_bounded(&no_indent_frame, 0., FRAME_WIDTH));
|
|
// assert!(all_lines_bounded(&no_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a head indent that's 5px bigger than the width of the frame.
|
|
let overflow_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH + 5.),
|
|
);
|
|
|
|
// The first line is left entirely blank since no glyphs fit on it.
|
|
assert_eq!(overflow_indent_frame.lines().len(), 6);
|
|
assert!(collect_glyph_indices(&overflow_indent_frame)
|
|
.first()
|
|
.unwrap()
|
|
.is_empty(),);
|
|
assert!(first_line_bounded(
|
|
&overflow_indent_frame,
|
|
FRAME_WIDTH + 5.,
|
|
FRAME_WIDTH,
|
|
));
|
|
// assert!(all_lines_bounded(&overflow_indent_frame, FRAME_WIDTH));
|
|
|
|
// Lay out the text with a 79px head indent,
|
|
// which spans almost the entire width of the frame.
|
|
let big_indent_frame = font_db.text_layout_system().layout_text(
|
|
text,
|
|
line_style,
|
|
&style_runs,
|
|
FRAME_WIDTH,
|
|
FRAME_HEIGHT,
|
|
Default::default(),
|
|
Some(FRAME_WIDTH - 0.1),
|
|
);
|
|
|
|
// The first line is left entirely blank since no glyphs fit on it.
|
|
assert_eq!(big_indent_frame.lines().len(), 6);
|
|
assert!(collect_glyph_indices(&big_indent_frame)
|
|
.first()
|
|
.unwrap()
|
|
.is_empty(),);
|
|
assert!(first_line_bounded(
|
|
&big_indent_frame,
|
|
FRAME_WIDTH - 0.1,
|
|
FRAME_WIDTH,
|
|
));
|
|
// assert!(all_lines_bounded(&big_indent_frame, FRAME_WIDTH));
|
|
|
|
Ok(())
|
|
}
|