ifc_lite_geometry/
contour_composition.rs1use crate::{BooleanOp2D, ContourFillRule, ContourSet, Ring2D};
8use i_overlay::{
9 core::{fill_rule::FillRule, overlay::Overlay, overlay_rule::OverlayRule},
10 i_float::{adapter::FloatPointAdapter, float::rect::FloatRect, int::point::IntPoint},
11};
12type Ring = Vec<IntPoint<i64>>;
13struct Group {
14 rings: Vec<Ring>,
15 shape_offsets: Vec<usize>,
16 classified: bool,
17}
18pub struct FixedGridComposition {
21 adapter: FloatPointAdapter<[f64; 2], i64>,
22 groups: Vec<Group>,
23 vertices: usize,
24}
25impl FixedGridComposition {
26 pub fn new(groups: &[Vec<Ring2D>], grid: f64) -> Result<Self, String> {
29 if groups.len() > 257 || !grid.is_finite() || grid <= 0. || !(1. / grid).is_finite() {
30 return Err("Fixed-grid composition has invalid group count or grid".into());
31 }
32 let n = groups
33 .iter()
34 .flatten()
35 .try_fold(0usize, |n, r| n.checked_add(r.len()))
36 .ok_or("Fixed-grid source vertex overflow")?;
37 if n > 1024
38 || groups
39 .iter()
40 .flatten()
41 .flatten()
42 .flatten()
43 .any(|v| !v.is_finite() || v.abs() > 1e12)
44 {
45 return Err("Fixed-grid composition exceeds 1024 finite source vertices".into());
46 }
47 let rings: Vec<_> = groups.iter().flatten().cloned().collect();
48 let adapter =
49 FloatPointAdapter::with_iter_and_scale_checked(rings.iter().flatten(), 1. / grid)
50 .map_err(|e| format!("Fixed-grid composition numeric range: {e:?}"))?;
51 crate::contour_grid_guard::validate_with_adapter(&rings, &[], grid, &adapter)
52 .map_err(|e| format!("PDF original lattice qualification: {e}"))?;
53 let mut result = Self {
54 adapter,
55 groups: vec![],
56 vertices: n,
57 };
58 for group in groups {
59 let sanitized = crate::contour_bool2d::sanitize_fixed_grid(group)?;
60 let rings: Vec<Ring> = sanitized
61 .iter()
62 .map(|r| r.iter().map(|p| result.adapter.float_to_int(p)).collect())
63 .collect();
64 exact_range(&rings)?;
65 result.groups.push(Group {
66 rings,
67 shape_offsets: vec![],
68 classified: false,
69 });
70 }
71 result.groups.push(Group {
72 rings: vec![],
73 shape_offsets: vec![],
74 classified: true,
75 });
76 Ok(result)
77 }
78 pub fn vertex_count(&self, id: usize) -> Result<usize, String> {
80 Ok(self.group(id)?.rings.iter().map(Vec::len).sum())
81 }
82 fn group(&self, id: usize) -> Result<&Group, String> {
83 self.groups
84 .get(id)
85 .ok_or_else(|| "Unknown fixed-grid composition group".into())
86 }
87 pub fn overlay(
89 &mut self,
90 a: usize,
91 b: usize,
92 op: BooleanOp2D,
93 fill: ContourFillRule,
94 ) -> Result<usize, String> {
95 let a = self.group(a)?;
96 let b = self.group(b)?;
97 let n = a.rings.iter().chain(&b.rings).map(Vec::len).sum::<usize>();
98 if n > 1024 || self.groups.len() >= 2048 {
99 return Err("Fixed-grid composition stage exceeds bounds".into());
100 }
101 let floats = |rings: &[Ring]| {
105 rings
106 .iter()
107 .map(|r| r.iter().map(|p| [p.x as f64, p.y as f64]).collect())
108 .collect::<Vec<Ring2D>>()
109 };
110 let af = floats(&a.rings);
111 let bf = floats(&b.rings);
112 let radius = (1_u64 << 51) as f64;
113 let unit =
114 FloatPointAdapter::try_with_scale(FloatRect::new(-radius, radius, -radius, radius), 1.)
115 .map_err(|e| format!("Fixed-grid composition unit range: {e:?}"))?;
116 crate::contour_grid_guard::validate_with_adapter(&af, &bf, 1., &unit)
117 .map_err(|e| format!("PDF integer stage qualification: {e}"))?;
118 let rule = match op {
119 BooleanOp2D::Union => OverlayRule::Union,
120 BooleanOp2D::Difference => OverlayRule::Difference,
121 BooleanOp2D::Intersection => OverlayRule::Intersect,
122 };
123 let fill = match fill {
124 ContourFillRule::NonZero => FillRule::NonZero,
125 ContourFillRule::EvenOdd => FillRule::EvenOdd,
126 };
127 let shapes = Overlay::with_contours(&a.rings, &b.rings).overlay(rule, fill);
128 let mut group = Group {
129 rings: vec![],
130 shape_offsets: vec![],
131 classified: true,
132 };
133 for shape in shapes {
134 if shape.first().is_none_or(|r| r.len() < 3) {
135 continue;
136 }
137 group.shape_offsets.push(group.rings.len());
138 group
139 .rings
140 .extend(shape.into_iter().filter(|r| r.len() >= 3));
141 }
142 exact_range(&group.rings)?;
143 let output = group.rings.iter().map(Vec::len).sum::<usize>();
144 self.vertices = self
145 .vertices
146 .checked_add(output)
147 .ok_or("Fixed-grid composition vertex overflow")?;
148 if self.vertices > 16_384 {
149 return Err("Fixed-grid composition exceeds cumulative vertex budget".into());
150 }
151 let id = self.groups.len();
152 self.groups.push(group);
153 Ok(id)
154 }
155 pub fn contours(&self, id: usize) -> Result<ContourSet, String> {
158 let group = self.group(id)?;
159 if !group.classified {
160 return Err("Cannot export an unclassified fixed-grid source group".into());
161 }
162 Ok(ContourSet {
163 rings: group
164 .rings
165 .iter()
166 .map(|r| r.iter().map(|p| self.adapter.int_to_float(p)).collect())
167 .collect(),
168 shape_offsets: group.shape_offsets.clone(),
169 })
170 }
171}
172fn exact_range(rings: &[Ring]) -> Result<(), String> {
173 if rings
174 .iter()
175 .flatten()
176 .any(|p| p.x.unsigned_abs() > (1_u64 << 50) || p.y.unsigned_abs() > (1_u64 << 50))
177 {
178 return Err("Fixed-grid composition exceeds exact diagnostic coordinate range".into());
179 }
180 Ok(())
181}
182
183#[cfg(test)]
184#[path = "contour_composition_tests.rs"]
185mod tests;