From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/wpf-gpu-raster/src/lib.rs | 700 +++++++++++++++++++++++++++++ 1 file changed, 700 insertions(+) create mode 100644 third_party/rust/wpf-gpu-raster/src/lib.rs (limited to 'third_party/rust/wpf-gpu-raster/src/lib.rs') diff --git a/third_party/rust/wpf-gpu-raster/src/lib.rs b/third_party/rust/wpf-gpu-raster/src/lib.rs new file mode 100644 index 0000000000..a5905336eb --- /dev/null +++ b/third_party/rust/wpf-gpu-raster/src/lib.rs @@ -0,0 +1,700 @@ +/*! +Converts a 2D path into a set of vertices of a triangle strip mesh that represents the antialiased fill of that path. + +```rust + use wpf_gpu_raster::PathBuilder; + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(40., 40.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); +``` + +*/ +#![allow(unused_parens)] +#![allow(overflowing_literals)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(dead_code)] +#![allow(unused_macros)] + +#[macro_use] +mod fix; +#[macro_use] +mod helpers; +#[macro_use] +mod real; +mod bezier; +#[macro_use] +mod aarasterizer; +mod hwrasterizer; +mod aacoverage; +mod hwvertexbuffer; + +mod types; +mod geometry_sink; +mod matrix; + +mod nullable_ref; + +#[cfg(feature = "c_bindings")] +pub mod c_bindings; + +#[cfg(test)] +mod tri_rasterize; + +use aarasterizer::CheckValidRange28_4; +use hwrasterizer::CHwRasterizer; +use hwvertexbuffer::{CHwVertexBuffer, CHwVertexBufferBuilder}; +use real::CFloatFPU; +use types::{MilFillMode, PathPointTypeStart, MilPoint2F, MilPointAndSizeL, PathPointTypeLine, MilVertexFormat, MilVertexFormatAttribute, DynArray, BYTE, PathPointTypeBezier, PathPointTypeCloseSubpath, CMILSurfaceRect, POINT}; + +#[repr(C)] +#[derive(Clone, Debug, Default)] +pub struct OutputVertex { + pub x: f32, + pub y: f32, + pub coverage: f32 +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub enum FillMode { + EvenOdd = 0, + Winding = 1, +} + +impl Default for FillMode { + fn default() -> Self { + FillMode::EvenOdd + } +} + +#[derive(Clone, Default)] +pub struct OutputPath { + fill_mode: FillMode, + points: Box<[POINT]>, + types: Box<[BYTE]>, +} + +impl std::hash::Hash for OutputVertex { + fn hash(&self, state: &mut H) { + self.x.to_bits().hash(state); + self.y.to_bits().hash(state); + self.coverage.to_bits().hash(state); + } +} + +pub struct PathBuilder { + points: DynArray, + types: DynArray, + initial_point: Option, + current_point: Option, + in_shape: bool, + fill_mode: FillMode, + outside_bounds: Option, + need_inside: bool, + valid_range: bool, + rasterization_truncates: bool, +} + +impl PathBuilder { + pub fn new() -> Self { + Self { + points: Vec::new(), + types: Vec::new(), + initial_point: None, + current_point: None, + in_shape: false, + fill_mode: FillMode::EvenOdd, + outside_bounds: None, + need_inside: true, + valid_range: true, + rasterization_truncates: false, + } + } + fn reset(&mut self) { + *self = Self { + points: std::mem::take(&mut self.points), + types: std::mem::take(&mut self.types), + ..Self::new() + }; + self.points.clear(); + self.types.clear(); + } + fn add_point(&mut self, x: f32, y: f32) { + self.current_point = Some(MilPoint2F{X: x, Y: y}); + // Transform from pixel corner at 0.0 to pixel center at 0.0. Scale into 28.4 range. + // Validate that the point before rounding is within expected bounds for the rasterizer. + let (x, y) = ((x - 0.5) * 16.0, (y - 0.5) * 16.0); + self.valid_range = self.valid_range && CheckValidRange28_4(x, y); + self.points.push(POINT { + x: CFloatFPU::Round(x), + y: CFloatFPU::Round(y), + }); + } + pub fn line_to(&mut self, x: f32, y: f32) { + if let Some(initial_point) = self.initial_point { + if !self.in_shape { + self.types.push(PathPointTypeStart); + self.add_point(initial_point.X, initial_point.Y); + self.in_shape = true; + } + self.types.push(PathPointTypeLine); + self.add_point(x, y); + } else { + self.initial_point = Some(MilPoint2F{X: x, Y: y}) + } + } + pub fn move_to(&mut self, x: f32, y: f32) { + self.in_shape = false; + self.initial_point = Some(MilPoint2F{X: x, Y: y}); + self.current_point = self.initial_point; + } + pub fn curve_to(&mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) { + let initial_point = match self.initial_point { + Some(initial_point) => initial_point, + None => MilPoint2F{X:c1x, Y:c1y} + }; + if !self.in_shape { + self.types.push(PathPointTypeStart); + self.add_point(initial_point.X, initial_point.Y); + self.initial_point = Some(initial_point); + self.in_shape = true; + } + self.types.push(PathPointTypeBezier); + self.add_point(c1x, c1y); + self.add_point(c2x, c2y); + self.add_point(x, y); + } + pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) { + // For now we just implement quad_to on top of curve_to. + // Long term we probably want to support quad curves + // directly. + let c0 = match self.current_point { + Some(current_point) => current_point, + None => MilPoint2F{X:cx, Y:cy} + }; + + let c1x = c0.X + (2./3.) * (cx - c0.X); + let c1y = c0.Y + (2./3.) * (cy - c0.Y); + + let c2x = x + (2./3.) * (cx - x); + let c2y = y + (2./3.) * (cy - y); + + self.curve_to(c1x, c1y, c2x, c2y, x, y); + } + pub fn close(&mut self) { + if self.in_shape { + // Only close the path if we are inside a shape. Otherwise, the point + // should be safe to elide. + if let Some(last) = self.types.last_mut() { + *last |= PathPointTypeCloseSubpath; + } + self.in_shape = false; + } + // Close must install a new initial point that is the same as the + // initial point of the just-closed sub-path. Thus, just leave the + // initial point unchanged. + self.current_point = self.initial_point; + } + pub fn set_fill_mode(&mut self, fill_mode: FillMode) { + self.fill_mode = fill_mode; + } + /// Enables rendering geometry for areas outside the shape but + /// within the bounds. These areas will be created with + /// zero alpha. + /// + /// This is useful for creating geometry for other blend modes. + /// For example: + /// - `IN(dest, geometry)` can be done with `outside_bounds` and `need_inside = false` + /// - `IN(dest, geometry, alpha)` can be done with `outside_bounds` and `need_inside = true` + /// + /// Note: trapezoidal areas won't be clipped to outside_bounds + pub fn set_outside_bounds(&mut self, outside_bounds: Option<(i32, i32, i32, i32)>, need_inside: bool) { + self.outside_bounds = outside_bounds.map(|r| CMILSurfaceRect { left: r.0, top: r.1, right: r.2, bottom: r.3 }); + self.need_inside = need_inside; + } + + /// Set this to true if post vertex shader coordinates are converted to fixed point + /// via truncation. This has been observed with OpenGL on AMD GPUs on macOS. + pub fn set_rasterization_truncates(&mut self, rasterization_truncates: bool) { + self.rasterization_truncates = rasterization_truncates; + } + + /// Note: trapezoidal areas won't necessarily be clipped to the clip rect + pub fn rasterize_to_tri_list(&self, clip_x: i32, clip_y: i32, clip_width: i32, clip_height: i32) -> Box<[OutputVertex]> { + if !self.valid_range { + // If any of the points are outside of valid 28.4 range, then just return an empty triangle list. + return Box::new([]); + } + let (x, y, width, height, need_outside) = if let Some(CMILSurfaceRect { left, top, right, bottom }) = self.outside_bounds { + let x0 = clip_x.max(left); + let y0 = clip_y.max(top); + let x1 = (clip_x + clip_width).min(right); + let y1 = (clip_y + clip_height).min(bottom); + (x0, y0, x1 - x0, y1 - y0, true) + } else { + (clip_x, clip_y, clip_width, clip_height, false) + }; + rasterize_to_tri_list(self.fill_mode, &self.types, &self.points, x, y, width, height, self.need_inside, need_outside, self.rasterization_truncates, None) + .flush_output() + } + + pub fn get_path(&mut self) -> Option { + if self.valid_range && !self.points.is_empty() && !self.types.is_empty() { + Some(OutputPath { + fill_mode: self.fill_mode, + points: Box::from(self.points.as_slice()), + types: Box::from(self.types.as_slice()), + }) + } else { + None + } + } +} + +// Converts a path that is specified as an array of edge types, each associated with a fixed number +// of points that are serialized to the points array. Edge types are specified via PathPointType +// masks, whereas points must be supplied in 28.4 signed fixed-point format. By default, users can +// fill the inside of the path excluding the outside. It may alternatively be desirable to fill the +// outside the path out to the clip boundary, optionally keeping the inside. PathBuilder may be +// used instead as a simpler interface to this function that handles building the path arrays. +pub fn rasterize_to_tri_list<'a>( + fill_mode: FillMode, + types: &[BYTE], + points: &[POINT], + clip_x: i32, + clip_y: i32, + clip_width: i32, + clip_height: i32, + need_inside: bool, + need_outside: bool, + rasterization_truncates: bool, + output_buffer: Option<&'a mut [OutputVertex]>, +) -> CHwVertexBuffer<'a> { + let clipRect = MilPointAndSizeL { + X: clip_x, + Y: clip_y, + Width: clip_width, + Height: clip_height, + }; + + let mil_fill_mode = match fill_mode { + FillMode::EvenOdd => MilFillMode::Alternate, + FillMode::Winding => MilFillMode::Winding, + }; + + let m_mvfIn: MilVertexFormat = MilVertexFormatAttribute::MILVFAttrXY as MilVertexFormat; + let m_mvfGenerated: MilVertexFormat = MilVertexFormatAttribute::MILVFAttrNone as MilVertexFormat; + //let mvfaAALocation = MILVFAttrNone; + const HWPIPELINE_ANTIALIAS_LOCATION: MilVertexFormatAttribute = MilVertexFormatAttribute::MILVFAttrDiffuse; + let mvfaAALocation = HWPIPELINE_ANTIALIAS_LOCATION; + + let outside_bounds = if need_outside { + Some(CMILSurfaceRect { + left: clip_x, + top: clip_y, + right: clip_x + clip_width, + bottom: clip_y + clip_height, + }) + } else { + None + }; + + let mut vertexBuffer = CHwVertexBuffer::new(rasterization_truncates, output_buffer); + { + let mut vertexBuilder = CHwVertexBufferBuilder::Create( + m_mvfIn, m_mvfIn | m_mvfGenerated, mvfaAALocation, &mut vertexBuffer); + vertexBuilder.SetOutsideBounds(outside_bounds.as_ref(), need_inside); + vertexBuilder.BeginBuilding(); + { + let mut rasterizer = CHwRasterizer::new( + &mut vertexBuilder, mil_fill_mode, None, clipRect); + rasterizer.SendGeometry(points, types); + } + vertexBuilder.EndBuilding(); + } + + vertexBuffer +} + +#[cfg(test)] +mod tests { + use std::{hash::{Hash, Hasher}, collections::hash_map::DefaultHasher}; + use crate::{*, tri_rasterize::rasterize_to_mask}; + fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + #[test] + fn basic() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(10., 30.); + p.line_to(30., 30.); + p.line_to(30., 10.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 18); + //assert_eq!(dbg!(calculate_hash(&result)), 0x5851570566450135); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfbb7c3932059e240); + } + + #[test] + fn simple() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(40., 40.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + //assert_eq!(dbg!(calculate_hash(&result)), 0x81a9af7769f88e68); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6d1595533d40ef92); + } + + #[test] + fn rust() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(40., 40.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + //assert_eq!(dbg!(calculate_hash(&result)), 0x81a9af7769f88e68); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6d1595533d40ef92); + } + + #[test] + fn fill_mode() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(40., 40.); + p.line_to(10., 40.); + p.close(); + p.move_to(15., 15.); + p.line_to(35., 15.); + p.line_to(35., 35.); + p.line_to(15., 35.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + //assert_eq!(dbg!(calculate_hash(&result)), 0xb34344234f2f75a8); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xc7bf999c56ccfc34); + + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(40., 40.); + p.line_to(10., 40.); + p.close(); + p.move_to(15., 15.); + p.line_to(35., 15.); + p.line_to(35., 35.); + p.line_to(15., 35.); + p.close(); + p.set_fill_mode(FillMode::Winding); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + //assert_eq!(dbg!(calculate_hash(&result)), 0xee4ecd8a738fc42c); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfafad659db9a2efd); + + } + + #[test] + fn range() { + // test for a start point out of range + let mut p = PathBuilder::new(); + p.curve_to(8.872974e16, 0., 0., 0., 0., 0.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 0); + + // test for a subsequent point out of range + let mut p = PathBuilder::new(); + p.curve_to(0., 0., 8.872974e16, 0., 0., 0.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 0); + } + + #[test] + fn multiple_starts() { + let mut p = PathBuilder::new(); + p.line_to(10., 10.); + p.move_to(0., 0.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 0); + } + + #[test] + fn path_closing() { + let mut p = PathBuilder::new(); + p.curve_to(0., 0., 0., 0., 0., 32.0); + p.close(); + p.curve_to(0., 0., 0., 0., 0., 32.0); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 0); + } + + #[test] + fn curve() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.curve_to(40., 10., 40., 10., 40., 40.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xa92aae8dba7b8cd4); + assert_eq!(dbg!(calculate_hash(&result)), 0x8dbc4d23f9bba38d); + } + + #[test] + fn partial_coverage_last_line() { + let mut p = PathBuilder::new(); + + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(40., 39.6); + p.line_to(10., 39.6); + + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 21); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfa200c3bae144952); + assert_eq!(dbg!(calculate_hash(&result)), 0xf90cb6afaadfb559); + } + + #[test] + fn delta_upper_bound() { + let mut p = PathBuilder::new(); + p.move_to(-122.3 + 200.,84.285); + p.curve_to(-122.3 + 200., 84.285, -122.2 + 200.,86.179, -123.03 + 200., 86.16); + p.curve_to(-123.85 + 200., 86.141, -140.3 + 200., 38.066, -160.83 + 200., 40.309); + p.curve_to(-160.83 + 200., 40.309, -143.05 + 200., 32.956, -122.3 + 200., 84.285); + p.close(); + + let result = p.rasterize_to_tri_list(0, 0, 400, 400); + assert_eq!(result.len(), 429); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x5e82d98fdb47a796); + assert_eq!(dbg!(calculate_hash(&result)), 0x52d52992e249587a); + } + + + #[test] + fn self_intersect() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(10., 40.); + p.line_to(40., 40.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x49ecc769e1d4ec01); + assert_eq!(dbg!(calculate_hash(&result)), 0xf10babef5c619d19); + } + + #[test] + fn grid() { + let mut p = PathBuilder::new(); + + for i in 0..200 { + let offset = i as f32 * 1.3; + p.move_to(0. + offset, -8.); + p.line_to(0.5 + offset, -8.); + p.line_to(0.5 + offset, 40.); + p.line_to(0. + offset, 40.); + p.close(); + } + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 12000); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x5a7df39d9e9292f0); + } + + #[test] + fn outside() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(10., 40.); + p.line_to(40., 40.); + p.close(); + p.set_outside_bounds(Some((0, 0, 50, 50)), false); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x59403ddbb7e1d09a); + assert_eq!(dbg!(calculate_hash(&result)), 0x805fd385e47e6f2); + + // ensure that adjusting the outside bounds changes the results + p.set_outside_bounds(Some((5, 5, 50, 50)), false); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x59403ddbb7e1d09a); + assert_eq!(dbg!(calculate_hash(&result)), 0xcec2ed688999c966); + } + + #[test] + fn outside_inside() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(40., 10.); + p.line_to(10., 40.); + p.line_to(40., 40.); + p.close(); + p.set_outside_bounds(Some((0, 0, 50, 50)), true); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x49ecc769e1d4ec01); + assert_eq!(dbg!(calculate_hash(&result)), 0xaf76b42a5244d1ec); + } + + #[test] + fn outside_clipped() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.); + p.line_to(10., 40.); + p.line_to(90., 40.); + p.line_to(40., 10.); + p.close(); + p.set_outside_bounds(Some((0, 0, 50, 50)), false); + let result = p.rasterize_to_tri_list(0, 0, 50, 50); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x3d2a08f5d0bac999); + assert_eq!(dbg!(calculate_hash(&result)), 0xbd42b934ab52be39); + } + + #[test] + fn clip_edge() { + let mut p = PathBuilder::new(); + // tests the bigNumerator < 0 case of aarasterizer::ClipEdge + p.curve_to(-24., -10., -300., 119., 0.0, 0.0); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + // The edge merging only happens between points inside the enumerate buffer. This means + // that the vertex output can depend on the size of the enumerate buffer because there + // the number of edges and positions of vertices will change depending on edge merging. + if ENUMERATE_BUFFER_NUMBER!() == 32 { + assert_eq!(result.len(), 111); + } else { + assert_eq!(result.len(), 171); + } + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x50b887b09a4c16e); + } + + #[test] + fn enum_buffer_num() { + let mut p = PathBuilder::new(); + p.curve_to(0.0, 0.0, 0.0, 12.0, 0.0, 44.919434); + p.line_to(64.0, 36.0 ); + p.line_to(0.0, 80.0,); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 300); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x659cc742f16b42f2); + } + + #[test] + fn fill_alternating_empty_interior_pairs() { + let mut p = PathBuilder::new(); + p.line_to( 0., 2. ); + p.curve_to(0.0, 0.0,1., 6., 0.0, 0.0); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 9); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x726606a662fe46a0); + } + + #[test] + fn fill_winding_empty_interior_pairs() { + let mut p = PathBuilder::new(); + p.curve_to(45., 61., 0.09, 0., 0., 0.); + p.curve_to(45., 61., 0.09, 0., 0., 0.); + p.curve_to(0., 0., 0., 38., 0.09, 15.); + p.set_fill_mode(FillMode::Winding); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 462); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x651ea4ade5543087); + } + + #[test] + fn empty_fill() { + let mut p = PathBuilder::new(); + p.move_to(0., 0.); + p.line_to(10., 100.); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 0); + } + + #[test] + fn rasterize_line() { + let mut p = PathBuilder::new(); + p.move_to(1., 1.); + p.line_to(2., 1.); + p.line_to(2., 2.); + p.line_to(1., 2.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + let mask = rasterize_to_mask(&result, 3, 3); + assert_eq!(&mask[..], &[0, 0, 0, + 0, 255, 0, + 0, 0, 0][..]); + } + + #[test] + fn triangle() { + let mut p = PathBuilder::new(); + p.move_to(1., 10.); + p.line_to(100., 13.); + p.line_to(1., 16.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x4757b0c5a19b02f0); + } + + #[test] + fn single_pixel() { + let mut p = PathBuilder::new(); + p.move_to(1.5, 1.5); + p.line_to(2., 1.5); + p.line_to(2., 2.); + p.line_to(1.5, 2.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(result.len(), 3); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 4, 4)), 0x9f481fe5588e341c); + } + + #[test] + fn traps_outside_bounds() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.0); + p.line_to(30., 10.); + p.line_to(50., 20.); + p.line_to(30., 30.); + p.line_to(10., 30.); + p.close(); + // The generated trapezoids are not necessarily clipped to the outside bounds rect + // and in this case the outside bounds geometry ends up drawing on top of the + // edge geometry which could be considered a bug. + p.set_outside_bounds(Some((0, 0, 50, 30)), true); + let result = p.rasterize_to_tri_list(0, 0, 100, 100); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6514e3d79d641f09); + + } + + #[test] + fn quad_to() { + let mut p = PathBuilder::new(); + p.move_to(10., 10.0); + p.quad_to(30., 10., 30., 30.); + p.quad_to(10., 30., 30., 30.); + p.quad_to(60., 30., 60., 10.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 70, 40); + assert_eq!(result.len(), 279); + assert_eq!(calculate_hash(&rasterize_to_mask(&result, 70, 40)), 0xbd2eec3cfe9bd30b); + } + + #[test] + fn close_after_move_to() { + let mut p = PathBuilder::new(); + p.move_to(10., 0.); + p.close(); + p.move_to(0., 0.); + p.line_to(0., 10.); + p.line_to(10., 10.); + p.move_to(10., 0.); + p.close(); + let result = p.rasterize_to_tri_list(0, 0, 20, 20); + assert_eq!(result.len(), 27); + assert_eq!(dbg!(calculate_hash(&result)), 0xecfdf5bdfa25a1dd); + } +} -- cgit v1.2.3