1#![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 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 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 if let Some(last) = self.types.last_mut() {
184 *last |= PathPointTypeCloseSubpath;
185 }
186 self.in_shape = false;
187 }
188 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 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 pub fn set_rasterization_truncates(&mut self, rasterization_truncates: bool) {
214 self.rasterization_truncates = rasterization_truncates;
215 }
216
217 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 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
249pub 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 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!(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!(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!(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!(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!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfafad659db9a2efd);
391
392 }
393
394 #[test]
395 fn range() {
396 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 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 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 p.curve_to(-24., -10., -300., 119., 0.0, 0.0);
553 let result = p.rasterize_to_tri_list(0, 0, 100, 100);
554 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 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}