use super::*; use crate::display_list::{Fragment, Glyph, GlyphRun}; use crate::geom::{Affine, Rect, Stroke}; use crate::paint::{Blob, Color, FontData}; fn font() -> FontData { // The bytes are never parsed here: `ink_bounds` reads glyph positions and // the run's size, both of which the display list already carries. FontData::new(Blob::new(std::sync::Arc::new(Vec::new())), 0) } fn run(size: f32, positions: &[(f32, f32)]) -> GlyphRun { GlyphRun { font: font(), size, brush: Color::BLACK.into(), glyphs: positions .iter() .map(|&(x, y)| Glyph { id: 0, x, y }) .collect(), normalized_coords: Vec::new(), hint: true, synthesis: Default::default(), } } #[test] fn a_run_bounds_its_glyphs_with_room_for_their_ink() { let bounds = run(10.0, &[(100.0, 200.0), (140.0, 200.0)]) .ink_bounds() .expect("a run with glyphs has bounds"); // The placements alone span (100,200)–(140,200); the reach is what turns // that into a box the outlines cannot escape. assert!(bounds.x0 <= 100.0 && bounds.x1 >= 140.0); assert!(bounds.y0 < 200.0 && bounds.y1 > 200.0); assert!(bounds.x1 - 140.0 >= 10.0, "an em of advance at the least"); } #[test] fn an_empty_run_has_no_bounds() { assert!(run(10.0, &[]).ink_bounds().is_none()); } #[test] fn scope_markers_and_children_report_no_bounds() { let mut fragment = Fragment::new(); fragment.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0)); fragment.push_transform(Affine::translate((1.0, 1.0))); fragment.pop_transform(); fragment.pop_clip(); for item in &fragment.items { assert_eq!(item.draw_bounds(Affine::IDENTITY), None); } } #[test] fn a_fill_reports_its_shape_under_the_transform() { let mut fragment = Fragment::new(); fragment.fill(Rect::new(0.0, 0.0, 10.0, 10.0), Color::BLACK); let bounds = fragment.items[0] .draw_bounds(Affine::translate((100.0, 0.0))) .expect("a fill draws"); assert_eq!(bounds, Rect::new(100.0, 0.0, 110.0, 10.0)); } #[test] fn a_stroke_reports_past_its_shape() { let mut fragment = Fragment::new(); fragment.stroke( Rect::new(0.0, 0.0, 10.0, 10.0), Color::BLACK, Stroke::new(4.0), ); let bounds = fragment.items[0] .draw_bounds(Affine::IDENTITY) .expect("a stroke draws"); assert!(bounds.x0 <= -2.0 && bounds.x1 >= 12.0); } #[test] fn an_unclipped_stack_keeps_everything() { let clip = ClipBounds::new(None); assert!(clip.any_visible()); assert!(clip.visible(Rect::new(-1e9, -1e9, -1e8, -1e8))); } #[test] fn a_clip_culls_what_lies_outside_it() { let clip = ClipBounds::new(Some(Rect::new(0.0, 0.0, 100.0, 100.0))); assert!(clip.visible(Rect::new(50.0, 50.0, 150.0, 150.0))); assert!(!clip.visible(Rect::new(200.0, 0.0, 300.0, 100.0))); // Sharing an edge covers no pixels. assert!(!clip.visible(Rect::new(100.0, 0.0, 200.0, 100.0))); } #[test] fn nested_clips_intersect_and_unwind() { let mut clip = ClipBounds::new(Some(Rect::new(0.0, 0.0, 100.0, 100.0))); clip.push(Rect::new(50.0, 0.0, 200.0, 100.0)); assert_eq!(clip.current(), Some(Rect::new(50.0, 0.0, 100.0, 100.0))); assert!(!clip.visible(Rect::new(0.0, 0.0, 40.0, 40.0))); clip.pop(); assert!(clip.visible(Rect::new(0.0, 0.0, 40.0, 40.0))); } #[test] fn a_clip_closed_to_nothing_stays_closed() { let mut clip = ClipBounds::new(Some(Rect::new(0.0, 0.0, 100.0, 100.0))); clip.push(Rect::new(500.0, 500.0, 600.0, 600.0)); assert!(!clip.any_visible()); // A scope opened inside it cannot bring anything back. clip.push(Rect::new(0.0, 0.0, 100.0, 100.0)); assert!(!clip.any_visible()); assert!(!clip.visible(Rect::new(0.0, 0.0, 100.0, 100.0))); clip.pop(); clip.pop(); assert!(clip.any_visible()); } #[test] fn an_unclipped_outer_scope_is_restored_as_unclipped() { let mut clip = ClipBounds::new(None); clip.push(Rect::new(0.0, 0.0, 10.0, 10.0)); assert!(!clip.visible(Rect::new(100.0, 100.0, 200.0, 200.0))); clip.pop(); assert_eq!(clip.current(), None); assert!(clip.visible(Rect::new(100.0, 100.0, 200.0, 200.0))); } #[test] fn the_transform_is_what_places_an_item_against_the_clip() { let clip = ClipBounds::new(Some(Rect::new(0.0, 0.0, 100.0, 100.0))); let mut fragment = Fragment::new(); fragment.fill(Rect::new(0.0, 0.0, 10.0, 10.0), Color::BLACK); let item = &fragment.items[0]; assert!(clip.item_visible(item, Affine::IDENTITY)); assert!(!clip.item_visible(item, Affine::translate((1000.0, 0.0)))); }