wpf_gpu_raster/
lib.rs

1/*!
2Converts a 2D path into a set of vertices of a triangle strip mesh that represents the antialiased fill of that path.
3
4```rust
5    use wpf_gpu_raster::PathBuilder;
6    let mut p = PathBuilder::new();
7    p.move_to(10., 10.);
8    p.line_to(40., 10.);
9    p.line_to(40., 40.);
10    let result = p.rasterize_to_tri_list(0, 0, 100, 100);
11```
12
13*/
14#![allow(unused_parens)]
15#![allow(overflowing_literals)]
16#![allow(non_snake_case)]
17#![allow(non_camel_case_types)]
18#![allow(non_upper_case_globals)]
19#![allow(dead_code)]
20#![allow(unused_macros)]
21
22#[macro_use]
23mod fix;
24#[macro_use]
25mod helpers;
26#[macro_use]
27mod real;
28mod bezier;
29#[macro_use]
30mod aarasterizer;
31mod hwrasterizer;
32mod aacoverage;
33mod hwvertexbuffer;
34
35mod types;
36mod geometry_sink;
37mod matrix;
38
39mod nullable_ref;
40
41#[cfg(feature = "c_bindings")]
42pub mod c_bindings;
43
44#[cfg(test)]
45mod tri_rasterize;
46
47use aarasterizer::CheckValidRange28_4;
48use hwrasterizer::CHwRasterizer;
49use hwvertexbuffer::{CHwVertexBuffer, CHwVertexBufferBuilder};
50use real::CFloatFPU;
51use types::{MilFillMode, PathPointTypeStart, MilPoint2F, MilPointAndSizeL, PathPointTypeLine, MilVertexFormat, MilVertexFormatAttribute, DynArray, BYTE, PathPointTypeBezier, PathPointTypeCloseSubpath, CMILSurfaceRect, POINT};
52
53#[repr(C)]
54#[derive(Clone, Debug, Default)]
55pub struct OutputVertex {
56    pub x: f32,
57    pub y: f32,
58    pub coverage: f32
59}
60
61#[repr(C)]
62#[derive(Copy, Clone)]
63pub enum FillMode {
64    EvenOdd = 0,
65    Winding = 1,
66}
67
68impl Default for FillMode {
69    fn default() -> Self {
70        FillMode::EvenOdd
71    }
72}
73
74#[derive(Clone, Default)]
75pub struct OutputPath {
76    fill_mode: FillMode,
77    points: Box<[POINT]>,
78    types: Box<[BYTE]>,
79}
80
81impl std::hash::Hash for OutputVertex {
82    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
83        self.x.to_bits().hash(state);
84        self.y.to_bits().hash(state);
85        self.coverage.to_bits().hash(state);
86    }
87}
88
89pub struct PathBuilder {
90    points: DynArray<POINT>,
91    types: DynArray<BYTE>,
92    initial_point: Option<MilPoint2F>,
93    current_point: Option<MilPoint2F>,
94    in_shape: bool,
95    fill_mode: FillMode,
96    outside_bounds: Option<CMILSurfaceRect>,
97    need_inside: bool,
98    valid_range: bool,
99    rasterization_truncates: bool,
100}
101
102impl PathBuilder {
103    pub fn new() -> Self {
104        Self {
105            points: Vec::new(),
106            types: Vec::new(),
107            initial_point: None,
108            current_point: None,
109            in_shape: false,
110            fill_mode: FillMode::EvenOdd,
111            outside_bounds: None,
112            need_inside: true,
113            valid_range: true,
114            rasterization_truncates: false,
115        }
116    }
117    fn add_point(&mut self, x: f32, y: f32) {
118        self.current_point = Some(MilPoint2F{X: x, Y: y});
119        // Transform from pixel corner at 0.0 to pixel center at 0.0. Scale into 28.4 range.
120        // Validate that the point before rounding is within expected bounds for the rasterizer.
121        let (x, y) = ((x - 0.5) * 16.0, (y - 0.5) * 16.0);
122        self.valid_range = self.valid_range && CheckValidRange28_4(x, y);
123        self.points.push(POINT {
124            x: CFloatFPU::Round(x),
125            y: CFloatFPU::Round(y),
126        });
127    }
128    pub fn line_to(&mut self, x: f32, y: f32) {
129        if let Some(initial_point) = self.initial_point {
130            if !self.in_shape {
131                self.types.push(PathPointTypeStart);
132                self.add_point(initial_point.X, initial_point.Y);
133                self.in_shape = true;
134            }
135            self.types.push(PathPointTypeLine);
136            self.add_point(x, y);
137        } else {
138            self.initial_point = Some(MilPoint2F{X: x, Y: y})
139        }
140    }
141    pub fn move_to(&mut self, x: f32, y: f32) {
142        self.in_shape = false;
143        self.initial_point = Some(MilPoint2F{X: x, Y: y});
144        self.current_point = self.initial_point;
145    }
146    pub fn curve_to(&mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) {
147        let initial_point = match self.initial_point {
148            Some(initial_point) => initial_point,
149            None => MilPoint2F{X:c1x, Y:c1y}
150        };
151        if !self.in_shape {
152            self.types.push(PathPointTypeStart);
153            self.add_point(initial_point.X, initial_point.Y);
154            self.initial_point = Some(initial_point);
155            self.in_shape = true;
156        }
157        self.types.push(PathPointTypeBezier);
158        self.add_point(c1x, c1y);
159        self.add_point(c2x, c2y);
160        self.add_point(x, y);
161    }
162    pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
163        // For now we just implement quad_to on top of curve_to.
164        // Long term we probably want to support quad curves
165        // directly.
166        let c0 = match self.current_point {
167            Some(current_point) => current_point,
168            None => MilPoint2F{X:cx, Y:cy}
169        };
170
171        let c1x = c0.X + (2./3.) * (cx - c0.X);
172        let c1y = c0.Y + (2./3.) * (cy - c0.Y);
173
174        let c2x = x + (2./3.) * (cx - x);
175        let c2y = y + (2./3.) * (cy - y);
176
177        self.curve_to(c1x, c1y, c2x, c2y, x, y);
178    }
179    pub fn close(&mut self) {
180        if self.in_shape {
181          // Only close the path if we are inside a shape. Otherwise, the point
182          // should be safe to elide.
183          if let Some(last) = self.types.last_mut() {
184              *last |= PathPointTypeCloseSubpath;
185          }
186          self.in_shape = false;
187        }
188        // Close must install a new initial point that is the same as the
189        // initial point of the just-closed sub-path. Thus, just leave the
190        // initial point unchanged.
191        self.current_point = self.initial_point;
192    }
193    pub fn set_fill_mode(&mut self, fill_mode: FillMode) {
194        self.fill_mode = fill_mode;
195    }
196    /// Enables rendering geometry for areas outside the shape but
197    /// within the bounds.  These areas will be created with
198    /// zero alpha.
199    ///
200    /// This is useful for creating geometry for other blend modes.
201    /// For example:
202    /// - `IN(dest, geometry)` can be done with `outside_bounds` and `need_inside = false`
203    /// - `IN(dest, geometry, alpha)` can be done with `outside_bounds` and `need_inside = true`
204    ///
205    /// Note: trapezoidal areas won't be clipped to outside_bounds
206    pub fn set_outside_bounds(&mut self, outside_bounds: Option<(i32, i32, i32, i32)>, need_inside: bool) {
207        self.outside_bounds = outside_bounds.map(|r| CMILSurfaceRect { left: r.0, top: r.1, right: r.2, bottom: r.3 });
208        self.need_inside = need_inside;
209    }
210
211    /// Set this to true if post vertex shader coordinates are converted to fixed point
212    /// via truncation. This has been observed with OpenGL on AMD GPUs on macOS. 
213    pub fn set_rasterization_truncates(&mut self, rasterization_truncates: bool) {
214        self.rasterization_truncates = rasterization_truncates;
215    }
216
217    /// Note: trapezoidal areas won't necessarily be clipped to the clip rect
218    pub fn rasterize_to_tri_list(&self, clip_x: i32, clip_y: i32, clip_width: i32, clip_height: i32) -> Box<[OutputVertex]> {
219        if !self.valid_range {
220            // If any of the points are outside of valid 28.4 range, then just return an empty triangle list.
221            return Box::new([]);
222        }
223        let (x, y, width, height, need_outside) = if let Some(CMILSurfaceRect { left, top, right, bottom }) = self.outside_bounds {
224            let x0 = clip_x.max(left);
225            let y0 = clip_y.max(top);
226            let x1 = (clip_x + clip_width).min(right);
227            let y1 = (clip_y + clip_height).min(bottom);
228            (x0, y0, x1 - x0, y1 - y0, true)
229        } else {
230            (clip_x, clip_y, clip_width, clip_height, false)
231        };
232        rasterize_to_tri_list(self.fill_mode, &self.types, &self.points, x, y, width, height, self.need_inside, need_outside, self.rasterization_truncates, None)
233            .flush_output()
234    }
235
236    pub fn get_path(&mut self) -> Option<OutputPath> {
237        if self.valid_range && !self.points.is_empty() && !self.types.is_empty() {
238            Some(OutputPath {
239                fill_mode: self.fill_mode,
240                points: std::mem::take(&mut self.points).into_boxed_slice(),
241                types: std::mem::take(&mut self.types).into_boxed_slice(),
242            })
243        } else {
244            None
245        }
246    }
247}
248
249// Converts a path that is specified as an array of edge types, each associated with a fixed number
250// of points that are serialized to the points array. Edge types are specified via PathPointType
251// masks, whereas points must be supplied in 28.4 signed fixed-point format. By default, users can
252// fill the inside of the path excluding the outside. It may alternatively be desirable to fill the
253// outside the path out to the clip boundary, optionally keeping the inside. PathBuilder may be
254// used instead as a simpler interface to this function that handles building the path arrays.
255pub fn rasterize_to_tri_list<'a>(
256    fill_mode: FillMode,
257    types: &[BYTE],
258    points: &[POINT],
259    clip_x: i32,
260    clip_y: i32,
261    clip_width: i32,
262    clip_height: i32,
263    need_inside: bool,
264    need_outside: bool,
265    rasterization_truncates: bool,
266    output_buffer: Option<&'a mut [OutputVertex]>,
267) -> CHwVertexBuffer<'a> {
268    let clipRect = MilPointAndSizeL {
269        X: clip_x,
270        Y: clip_y,
271        Width: clip_width,
272        Height: clip_height,
273    };
274
275    let mil_fill_mode = match fill_mode {
276        FillMode::EvenOdd => MilFillMode::Alternate,
277        FillMode::Winding => MilFillMode::Winding,
278    };
279
280    let m_mvfIn: MilVertexFormat = MilVertexFormatAttribute::MILVFAttrXY as MilVertexFormat;
281    let m_mvfGenerated: MilVertexFormat  = MilVertexFormatAttribute::MILVFAttrNone as MilVertexFormat;
282    //let mvfaAALocation  = MILVFAttrNone;
283    const HWPIPELINE_ANTIALIAS_LOCATION: MilVertexFormatAttribute = MilVertexFormatAttribute::MILVFAttrDiffuse;
284    let mvfaAALocation = HWPIPELINE_ANTIALIAS_LOCATION;
285
286    let outside_bounds = if need_outside {
287        Some(CMILSurfaceRect {
288            left: clip_x,
289            top: clip_y,
290            right: clip_x + clip_width,
291            bottom: clip_y + clip_height,
292        })
293    } else {
294        None
295    };
296
297    let mut vertexBuffer = CHwVertexBuffer::new(rasterization_truncates, output_buffer);
298    {
299        let mut vertexBuilder = CHwVertexBufferBuilder::Create(
300            m_mvfIn, m_mvfIn | m_mvfGenerated, mvfaAALocation, &mut vertexBuffer);
301        vertexBuilder.SetOutsideBounds(outside_bounds.as_ref(), need_inside);
302        vertexBuilder.BeginBuilding();
303        {
304            let mut rasterizer = CHwRasterizer::new(
305                &mut vertexBuilder, mil_fill_mode, None, clipRect);
306            rasterizer.SendGeometry(points, types);
307        }
308        vertexBuilder.EndBuilding();
309    }
310
311    vertexBuffer
312}
313
314#[cfg(test)]
315mod tests {
316    use std::{hash::{Hash, Hasher}, collections::hash_map::DefaultHasher};
317    use crate::{*, tri_rasterize::rasterize_to_mask};
318    fn calculate_hash<T: Hash>(t: &T) -> u64 {
319        let mut s = DefaultHasher::new();
320        t.hash(&mut s);
321        s.finish()
322    }
323    #[test]
324    fn basic() {
325        let mut p = PathBuilder::new();
326        p.move_to(10., 10.);
327        p.line_to(10., 30.);
328        p.line_to(30., 30.);
329        p.line_to(30., 10.);
330        p.close();
331        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
332        assert_eq!(result.len(), 18);
333        //assert_eq!(dbg!(calculate_hash(&result)), 0x5851570566450135);
334        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfbb7c3932059e240);
335    }
336
337    #[test]
338    fn simple() {
339        let mut p = PathBuilder::new();
340        p.move_to(10., 10.);
341        p.line_to(40., 10.);
342        p.line_to(40., 40.);
343        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
344        //assert_eq!(dbg!(calculate_hash(&result)), 0x81a9af7769f88e68);
345        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6d1595533d40ef92);
346    }
347
348    #[test]
349    fn rust() {
350        let mut p = PathBuilder::new();
351        p.move_to(10., 10.);
352        p.line_to(40., 10.);
353        p.line_to(40., 40.);
354        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
355        //assert_eq!(dbg!(calculate_hash(&result)), 0x81a9af7769f88e68);
356        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6d1595533d40ef92);
357    }
358
359    #[test]
360    fn fill_mode() {
361        let mut p = PathBuilder::new();
362        p.move_to(10., 10.);
363        p.line_to(40., 10.);
364        p.line_to(40., 40.);
365        p.line_to(10., 40.);
366        p.close();
367        p.move_to(15., 15.);
368        p.line_to(35., 15.);
369        p.line_to(35., 35.);
370        p.line_to(15., 35.);
371        p.close();
372        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
373        //assert_eq!(dbg!(calculate_hash(&result)), 0xb34344234f2f75a8);
374        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xc7bf999c56ccfc34);
375
376        let mut p = PathBuilder::new();
377        p.move_to(10., 10.);
378        p.line_to(40., 10.);
379        p.line_to(40., 40.);
380        p.line_to(10., 40.);
381        p.close();
382        p.move_to(15., 15.);
383        p.line_to(35., 15.);
384        p.line_to(35., 35.);
385        p.line_to(15., 35.);
386        p.close();
387        p.set_fill_mode(FillMode::Winding);
388        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
389        //assert_eq!(dbg!(calculate_hash(&result)), 0xee4ecd8a738fc42c);
390        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfafad659db9a2efd);
391
392    }
393
394    #[test]
395    fn range() {
396        // test for a start point out of range
397        let mut p = PathBuilder::new();
398        p.curve_to(8.872974e16, 0., 0., 0., 0., 0.);
399        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
400        assert_eq!(result.len(), 0);
401
402        // test for a subsequent point out of range
403        let mut p = PathBuilder::new();
404        p.curve_to(0., 0., 8.872974e16, 0., 0., 0.);
405        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
406        assert_eq!(result.len(), 0);
407    }
408
409    #[test]
410    fn multiple_starts() {
411        let mut p = PathBuilder::new();
412        p.line_to(10., 10.);
413        p.move_to(0., 0.);
414        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
415        assert_eq!(result.len(), 0);
416    }
417
418    #[test]
419    fn path_closing() {
420        let mut p = PathBuilder::new();
421        p.curve_to(0., 0., 0., 0., 0., 32.0);
422        p.close();
423        p.curve_to(0., 0., 0., 0., 0., 32.0);
424        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
425        assert_eq!(result.len(), 0);
426    }
427
428    #[test]
429    fn curve() {
430        let mut p = PathBuilder::new();
431        p.move_to(10., 10.);
432        p.curve_to(40., 10., 40., 10., 40., 40.);
433        p.close();
434        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
435        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xa92aae8dba7b8cd4);
436        assert_eq!(dbg!(calculate_hash(&result)), 0x8dbc4d23f9bba38d);
437    }
438
439    #[test]
440    fn partial_coverage_last_line() {
441        let mut p = PathBuilder::new();
442
443        p.move_to(10., 10.);
444        p.line_to(40., 10.);
445        p.line_to(40., 39.6);
446        p.line_to(10., 39.6);
447
448        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
449        assert_eq!(result.len(), 21);
450        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfa200c3bae144952);
451        assert_eq!(dbg!(calculate_hash(&result)), 0xf90cb6afaadfb559);
452    }
453
454    #[test]
455    fn delta_upper_bound() {
456        let mut p = PathBuilder::new();
457        p.move_to(-122.3 + 200.,84.285);
458        p.curve_to(-122.3 + 200., 84.285, -122.2 + 200.,86.179, -123.03 + 200., 86.16);
459        p.curve_to(-123.85 + 200., 86.141, -140.3 + 200., 38.066, -160.83 + 200., 40.309);
460        p.curve_to(-160.83 + 200., 40.309, -143.05 + 200., 32.956,  -122.3 + 200., 84.285);
461        p.close();
462
463        let result = p.rasterize_to_tri_list(0, 0, 400, 400);
464        assert_eq!(result.len(), 429);
465        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x5e82d98fdb47a796);
466        assert_eq!(dbg!(calculate_hash(&result)), 0x52d52992e249587a);
467    }
468
469
470    #[test]
471    fn self_intersect() {
472        let mut p = PathBuilder::new();
473        p.move_to(10., 10.);
474        p.line_to(40., 10.);
475        p.line_to(10., 40.);
476        p.line_to(40., 40.);
477        p.close();
478        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
479        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x49ecc769e1d4ec01);
480        assert_eq!(dbg!(calculate_hash(&result)), 0xf10babef5c619d19);
481    }
482
483    #[test]
484    fn grid() {
485        let mut p = PathBuilder::new();
486
487        for i in 0..200 {
488            let offset = i as f32 * 1.3;
489            p.move_to(0. + offset, -8.);
490            p.line_to(0.5 + offset, -8.);
491            p.line_to(0.5 + offset, 40.);
492            p.line_to(0. + offset, 40.);
493            p.close();
494        }
495        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
496        assert_eq!(result.len(), 12000);
497        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x5a7df39d9e9292f0);
498    }
499
500    #[test]
501    fn outside() {
502        let mut p = PathBuilder::new();
503        p.move_to(10., 10.);
504        p.line_to(40., 10.);
505        p.line_to(10., 40.);
506        p.line_to(40., 40.);
507        p.close();
508        p.set_outside_bounds(Some((0, 0, 50, 50)), false);
509        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
510        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x59403ddbb7e1d09a);
511        assert_eq!(dbg!(calculate_hash(&result)), 0x805fd385e47e6f2);
512
513        // ensure that adjusting the outside bounds changes the results
514        p.set_outside_bounds(Some((5, 5, 50, 50)), false);
515        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
516        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x59403ddbb7e1d09a);
517        assert_eq!(dbg!(calculate_hash(&result)), 0xcec2ed688999c966);
518    }
519
520    #[test]
521    fn outside_inside() {
522        let mut p = PathBuilder::new();
523        p.move_to(10., 10.);
524        p.line_to(40., 10.);
525        p.line_to(10., 40.);
526        p.line_to(40., 40.);
527        p.close();
528        p.set_outside_bounds(Some((0, 0, 50, 50)), true);
529        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
530        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x49ecc769e1d4ec01);
531        assert_eq!(dbg!(calculate_hash(&result)), 0xaf76b42a5244d1ec);
532    }
533
534    #[test]
535    fn outside_clipped() {
536        let mut p = PathBuilder::new();
537        p.move_to(10., 10.);
538        p.line_to(10., 40.);
539        p.line_to(90., 40.);
540        p.line_to(40., 10.);
541        p.close();
542        p.set_outside_bounds(Some((0, 0, 50, 50)), false);
543        let result = p.rasterize_to_tri_list(0, 0, 50, 50);
544        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x3d2a08f5d0bac999);
545        assert_eq!(dbg!(calculate_hash(&result)), 0xbd42b934ab52be39);
546    }
547
548    #[test]
549    fn clip_edge() {
550        let mut p = PathBuilder::new();
551        // tests the bigNumerator < 0 case of aarasterizer::ClipEdge
552        p.curve_to(-24., -10., -300., 119., 0.0, 0.0);
553        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
554        // The edge merging only happens between points inside the enumerate buffer. This means
555        // that the vertex output can depend on the size of the enumerate buffer because there
556        // the number of edges and positions of vertices will change depending on edge merging.
557        if ENUMERATE_BUFFER_NUMBER!() == 32 {
558            assert_eq!(result.len(), 111);
559        } else {
560            assert_eq!(result.len(), 171);
561        }
562        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x50b887b09a4c16e);
563    }
564
565    #[test]
566    fn enum_buffer_num() {
567        let mut p = PathBuilder::new();
568        p.curve_to(0.0, 0.0, 0.0, 12.0, 0.0, 44.919434);
569        p.line_to(64.0, 36.0 );
570        p.line_to(0.0, 80.0,);
571        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
572        assert_eq!(result.len(), 300);
573        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x659cc742f16b42f2);
574    }
575
576    #[test]
577    fn fill_alternating_empty_interior_pairs() {
578        let mut p = PathBuilder::new();
579        p.line_to( 0., 2. );
580        p.curve_to(0.0, 0.0,1., 6., 0.0, 0.0);
581        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
582        assert_eq!(result.len(), 9);
583        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x726606a662fe46a0);
584    }
585
586    #[test]
587    fn fill_winding_empty_interior_pairs() {
588        let mut p = PathBuilder::new();
589        p.curve_to(45., 61., 0.09, 0., 0., 0.);
590        p.curve_to(45., 61., 0.09, 0., 0., 0.);
591        p.curve_to(0., 0., 0., 38., 0.09, 15.);
592        p.set_fill_mode(FillMode::Winding);
593        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
594        assert_eq!(result.len(), 462);
595        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x651ea4ade5543087);
596    }
597
598    #[test]
599    fn empty_fill() {
600        let mut p = PathBuilder::new();
601        p.move_to(0., 0.);
602        p.line_to(10., 100.);
603        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
604        assert_eq!(result.len(), 0);
605    }
606
607    #[test]
608    fn rasterize_line() {
609        let mut p = PathBuilder::new();
610        p.move_to(1., 1.);
611        p.line_to(2., 1.);
612        p.line_to(2., 2.);
613        p.line_to(1., 2.);
614        p.close();
615        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
616        let mask = rasterize_to_mask(&result, 3, 3);
617        assert_eq!(&mask[..], &[0,   0, 0,
618                                0, 255, 0,
619                                0,   0, 0][..]);
620    }
621
622    #[test]
623    fn triangle() {
624        let mut p = PathBuilder::new();
625        p.move_to(1., 10.);
626        p.line_to(100., 13.);
627        p.line_to(1., 16.);
628        p.close();
629        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
630        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x4757b0c5a19b02f0);
631    }
632
633    #[test]
634    fn single_pixel() {
635        let mut p = PathBuilder::new();
636        p.move_to(1.5, 1.5);
637        p.line_to(2., 1.5);
638        p.line_to(2., 2.);
639        p.line_to(1.5, 2.);
640        p.close();
641        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
642        assert_eq!(result.len(), 3);
643        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 4, 4)), 0x9f481fe5588e341c);
644    }
645
646    #[test]
647    fn traps_outside_bounds() {
648        let mut p = PathBuilder::new();
649        p.move_to(10., 10.0);
650        p.line_to(30., 10.);
651        p.line_to(50., 20.);
652        p.line_to(30., 30.);
653        p.line_to(10., 30.);
654        p.close();
655        // The generated trapezoids are not necessarily clipped to the outside bounds rect
656        // and in this case the outside bounds geometry ends up drawing on top of the
657        // edge geometry which could be considered a bug.
658        p.set_outside_bounds(Some((0, 0, 50, 30)), true);
659        let result = p.rasterize_to_tri_list(0, 0, 100, 100);
660        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6514e3d79d641f09);
661
662    }
663
664    #[test]
665    fn quad_to() {
666        let mut p = PathBuilder::new();
667        p.move_to(10., 10.0);
668        p.quad_to(30., 10., 30., 30.);
669        p.quad_to(10., 30., 30., 30.);
670        p.quad_to(60., 30., 60., 10.);
671        p.close();
672        let result = p.rasterize_to_tri_list(0, 0, 70, 40);
673        assert_eq!(result.len(), 279);
674        assert_eq!(calculate_hash(&rasterize_to_mask(&result, 70, 40)), 0xbd2eec3cfe9bd30b);
675    }
676
677    #[test]
678    fn close_after_move_to() {
679        let mut p = PathBuilder::new();
680        p.move_to(10., 0.);
681        p.close();
682        p.move_to(0., 0.);
683        p.line_to(0., 10.);
684        p.line_to(10., 10.);
685        p.move_to(10., 0.);
686        p.close();
687        let result = p.rasterize_to_tri_list(0, 0, 20, 20);
688        assert_eq!(result.len(), 27);
689        assert_eq!(dbg!(calculate_hash(&result)), 0xecfdf5bdfa25a1dd);
690    }
691}