#[cfg(target_arch = "wasm32")]
extern crate wasm_bindgen_test;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
use kurbo::{Point, Size};
use piet_common::*;
macro_rules! assert_close {
($val:expr, $target:expr, $tolerance:expr) => {{
let min = $target - $tolerance;
let max = $target + $tolerance;
if $val < min || $val > max {
panic!(
"value {} outside target {} with tolerance {}",
$val, $target, $tolerance
);
}
}};
($val:expr, $target:expr, $tolerance:expr,) => {{ assert_close!($val, $target, $tolerance) }};
}
fn make_factory() -> PietText {
let mut device = Device::new().unwrap();
let mut target = device.bitmap_target(400, 400, 2.0).unwrap();
let mut ctx = target.render_context();
let text = ctx.text().to_owned();
let _ = ctx.finish();
text
}
trait FactoryHelpers {
type Layout: TextLayout;
fn make_layout(
&mut self,
text: &str,
font: FontFamily,
size: f64,
width: impl Into<Option<f64>>,
) -> Self::Layout;
fn make_mono_12pt(&mut self, text: &str) -> Self::Layout {
self.make_layout(text, FontFamily::MONOSPACE, 12.0, None)
}
fn measure_width(&mut self, text: &str, font: FontFamily, size: f64) -> Size {
self.make_layout(text, font, size, None).size()
}
fn get_mono_width(&mut self, size: f64) -> f64 {
self.measure_width("a", FontFamily::MONOSPACE, size).width
}
}
impl<T: Text> FactoryHelpers for T {
type Layout = T::TextLayout;
fn make_layout(
&mut self,
text: &str,
font: FontFamily,
size: f64,
width: impl Into<Option<f64>>,
) -> Self::Layout {
self.new_text_layout(text.to_owned())
.font(font, size)
.max_width(width.into().unwrap_or(f64::INFINITY))
.build()
.unwrap()
}
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn negative_width_doesnt_crash() {
let mut factory = make_factory();
let text = "oops";
let layout = factory.new_text_layout(text).max_width(-4.0).build();
assert!(layout.is_ok())
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn empty_layout() {
let mut factory = make_factory();
let text = "";
let layout = factory.new_text_layout(text).build().unwrap();
assert_eq!(layout.line_count(), 1);
assert!(layout.line_metric(0).is_some());
assert!(layout.line_text(0).is_some());
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn ws_only_layout() {
let mut factory = make_factory();
let text = " ";
let layout = factory.new_text_layout(text).build().unwrap();
assert_eq!(layout.line_count(), 1);
assert!(layout.line_metric(0).is_some());
assert!(layout.line_text(0).is_some());
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn rects_for_empty_range() {
let mut factory = make_factory();
let text = "";
let layout = factory.new_text_layout(text).build().unwrap();
assert!(layout.rects_for_range(0..0).is_empty());
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn empty_layout_size() {
let mut factory = make_factory();
let empty_layout = factory.make_layout("", FontFamily::SYSTEM_UI, 24.0, None);
let non_empty_layout = factory.make_layout("-", FontFamily::SYSTEM_UI, 24.0, None);
assert!(empty_layout.size().height > 0.0);
assert_close!(
empty_layout.size().height,
non_empty_layout.size().height,
2.0,
);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn hit_test_multibyte_grapheme_position() {
let mut factory = make_factory();
let input = "a£€💴\u{0023}\u{FE0F}";
assert_eq!(input.len(), 14);
assert_eq!(input.chars().count(), 6);
let layout = factory.new_text_layout(input).build().unwrap();
let p0 = layout.hit_test_text_position(0).point;
let p1 = layout.hit_test_text_position(1).point;
let p3 = layout.hit_test_text_position(3).point;
let p6 = layout.hit_test_text_position(6).point;
let p10 = layout.hit_test_text_position(10).point;
let p11 = layout.hit_test_text_position(11).point;
let p14 = layout.hit_test_text_position(14).point;
assert_close!(p0.x, 0.0, 1e-6);
assert!(p1.x > p0.x);
assert!(p3.x > p1.x);
assert!(p6.x > p3.x);
assert!(p10.x > p6.x);
assert!(p14.x > p11.x);
}
#[test]
#[should_panic(expected = "is_char_boundary")]
fn hit_test_interior_byte() {
let mut factory = make_factory();
let input = "a£€💴\u{0023}\u{FE0F}";
let layout = factory.new_text_layout(input).build().unwrap();
let _ = layout.hit_test_text_position(7).point;
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn hit_test_point_rounding() {
let mut factory = make_factory();
let unit_width = factory.get_mono_width(12.0);
let text = "aaaa";
let layout = factory.make_layout(text, FontFamily::MONOSPACE, 12.0, None);
let pt = layout.hit_test_point(Point::new(1.0, 5.0));
assert_eq!(pt.idx, 0);
let pt = layout.hit_test_point(Point::new(unit_width - 1.0, 5.0));
assert_eq!(pt.idx, 1);
let pt = layout.hit_test_point(Point::new(unit_width + 1.0, 5.0));
assert_eq!(pt.idx, 1);
}
#[test]
fn hit_test_point_outside() {
let mut factory = make_factory();
let unit_width = factory.get_mono_width(12.0);
let text = "aa aaaaa";
let wrap_width = unit_width * 5.5;
let layout = factory.make_layout(text, FontFamily::MONOSPACE, 12.0, wrap_width);
assert_eq!(layout.line_count(), 2);
let pt = layout.hit_test_point(Point::new(-1.0, 5.0));
assert_eq!(pt.idx, 0);
assert!(!pt.is_inside);
let pt = layout.hit_test_point(Point::new(1.0, -5.0));
assert_eq!(pt.idx, 0);
assert!(!pt.is_inside);
let pt = layout.hit_test_point(Point::new(unit_width * 4., 5.0));
assert_eq!(pt.idx, 3);
assert!(!pt.is_inside);
let y2 = layout.line_metric(1).unwrap().y_offset + 1.0;
let pt = layout.hit_test_point(Point::new(-1.0, y2));
assert_eq!(pt.idx, 3);
assert!(!pt.is_inside);
let pt = layout.hit_test_point(Point::new(unit_width * 6.0, y2));
assert_eq!(pt.idx, 8);
assert!(!pt.is_inside);
let pt = layout.hit_test_point(Point::new(1.0, y2 * 3.0));
assert_eq!(pt.idx, 3);
assert!(!pt.is_inside);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn hit_test_point_multibyte() {
let mut factory = make_factory();
let input = "a£€💴";
let unit_width = factory.get_mono_width(12.0);
let layout = factory.make_mono_12pt(input);
let pt = layout.hit_test_point(Point::new(1.0, 5.0));
assert_eq!(pt.idx, 0);
let pt = layout.hit_test_point(Point::new(unit_width + 1.0, 5.0));
assert_eq!(pt.idx, 1);
let pt = layout.hit_test_point(Point::new(unit_width * 2.0 + 1.0, 5.0));
assert_eq!(pt.idx, 3);
let pt = layout.hit_test_point(Point::new(unit_width * 3.0 + 1.0, 5.0));
assert_eq!(pt.idx, 6);
let emoji_width = layout.size().width - unit_width * 3.0;
let pt = layout.hit_test_point(Point::new(unit_width * 3.0 + emoji_width - 1.0, 5.0));
assert_eq!(pt.idx, 10);
assert!(pt.is_inside);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn newline_eof() {
let mut factory = make_factory();
let text = "A";
let layout = factory.new_text_layout(text).build().unwrap();
assert_eq!(layout.line_count(), 1);
let text = "\n";
let layout = factory.new_text_layout(text).build().unwrap();
assert_eq!(layout.line_count(), 2);
assert_eq!(layout.line_text(0), Some("\n"));
assert_eq!(layout.line_text(1), Some(""));
assert!(layout.line_text(2).is_none());
let hit0 = layout.hit_test_text_position(0);
let hit1 = layout.hit_test_text_position(1);
assert_close!(hit0.point.y * 2., hit1.point.y, 5.0);
assert_eq!(hit0.line, 0);
assert_eq!(hit1.line, 1);
let hit1 = layout.hit_test_point(Point::new(50., 50.));
assert_eq!(hit1.idx, 1);
let text = "AA";
let layout = factory.new_text_layout(text).build().unwrap();
assert_eq!(layout.line_count(), 1);
let text = "AA\n";
let layout_newline = factory.new_text_layout(text).build().unwrap();
assert_eq!(layout_newline.line_count(), 2);
assert_close!(
layout.size().height * 2.0,
layout_newline.size().height,
2.0
);
}
#[test]
fn eol_hit_testing() {
let mut factory = make_factory();
let text = "AA AA\nAA";
let line_size = factory.measure_width("AA", FontFamily::SYSTEM_UI, 12.0);
let layout = factory.make_layout(text, FontFamily::SYSTEM_UI, 12.0, line_size.width);
let metrics = layout.line_metric(0).unwrap();
let right_of_line = layout.hit_test_point(Point::new(line_size.width + 3.0, 5.0));
assert_eq!(right_of_line.idx, 3);
let hit = layout.hit_test_text_position(right_of_line.idx);
assert_close!(hit.point.x, 0.0, 1.0);
assert_close!(hit.point.y, metrics.height + metrics.baseline, 2.0);
let right_of_line =
layout.hit_test_point(Point::new(line_size.width + 3.0, metrics.height + 5.0));
assert_eq!(right_of_line.idx, 5);
let hit = layout.hit_test_text_position(right_of_line.idx);
assert_close!(hit.point.x, line_size.width, 2.0);
assert_close!(hit.point.y, metrics.height + metrics.baseline, 2.0);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn debug_impl_exists() {
let mut factory = make_factory();
let text = "";
let layout_builder = factory.new_text_layout(text);
let layout = factory.new_text_layout(text).build().unwrap();
let _args = format_args!("{text:?} {layout_builder:?} {layout:?}");
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn width_sanity() {
let mut factory = make_factory();
let text = "hello";
let ws = factory.new_text_layout(text).build().unwrap();
assert_eq!(ws.line_count(), 1);
let lm = ws.line_metric(0).unwrap();
assert_eq!(lm.start_offset, 0);
assert_eq!(lm.end_offset, text.len());
let width = ws.size().width;
assert_close!(width, 27.0, 5.0);
}
#[test]
#[cfg(not(target_os = "linux"))]
fn emergency_break_selections() {
let mut factory = make_factory();
let mono_width = factory.get_mono_width(16.0);
let text = "a".repeat(20);
let layout_width = mono_width * 6.5;
let layout = factory.make_layout(&text, FontFamily::MONOSPACE, 16.0, layout_width);
assert_eq!(layout.line_count(), 4);
let rects = layout.rects_for_range(..);
assert_eq!(rects.len(), 4);
let second_line = rects[1];
assert_close!(second_line.min_x(), 0.0, 1.0);
assert_close!(second_line.max_x(), 6.0 * mono_width, 1.0);
}
#[test]
fn trailing_whitespace_width() {
let mut factory = make_factory();
let text = "hello";
let text_ws = "hello ";
let text_ideographic = "mmm";
let text_ideographic_ws = "mmm ";
let non_ws = factory.make_mono_12pt(text);
let ws = factory.make_mono_12pt(text_ws);
let ideographic_non_ws = factory.make_mono_12pt(text_ideographic);
let ideographic_ws = factory.make_mono_12pt(text_ideographic_ws);
assert_close!(non_ws.size().width, ws.size().width, 0.1);
assert_close!(
ideographic_non_ws.size().width,
ideographic_ws.size().width,
0.1
);
assert_close!(non_ws.trailing_whitespace_width(), non_ws.size().width, 0.1);
assert_close!(
ideographic_non_ws.trailing_whitespace_width(),
ideographic_non_ws.size().width,
0.1
);
assert_close!(ws.trailing_whitespace_width() / ws.size().width, 2.0, 0.1);
assert_close!(
ideographic_ws.trailing_whitespace_width() / ideographic_ws.size().width,
2.0,
0.2
);
let text_ws_plus = "hello +";
let ws_plus = factory.make_mono_12pt(text_ws_plus);
assert!(
ws_plus.trailing_whitespace_width() > ws.trailing_whitespace_width(),
"trailing ws width is inclusive of other width"
);
}