use super::curve_hulls::{convex, qualify as qualify_hulls, sign};
use super::flatten::{bezier, charge, distance};
use ifc_lite_geometry::Ring2D;
pub(super) struct PathRings {
pub rings: Vec<Ring2D>,
pub curved: Vec<bool>,
}
pub(super) fn point(m: [f64; 6], p: [f64; 2]) -> Result<[f64; 2], String> {
let q = [
m[0] * p[0] + m[2] * p[1] + m[4],
m[1] * p[0] + m[3] * p[1] + m[5],
];
if q.iter().any(|v| !v.is_finite() || v.abs() > 1e8) {
return Err("PDF fill coordinate exceeds finite metric range".into());
}
Ok(q)
}
fn finish(
result: &mut PathRings,
ring: &mut Ring2D,
controls: &mut Vec<Ring2D>,
curved: &mut bool,
remaining: &mut u64,
) -> Result<(), String> {
if ring.len() >= 3 {
if *curved {
if ring.last() != ring.first() {
controls.push(vec![*ring.last().unwrap(), ring[0]]);
}
qualify_hulls(controls, remaining)?;
}
result.rings.push(std::mem::take(ring));
result.curved.push(*curved);
} else if *curved {
return Err("PDF curve collapses its filled contour".into());
}
ring.clear();
controls.clear();
*curved = false;
Ok(())
}
pub(super) fn rings(
commands: &[f64],
m: [f64; 6],
tolerance: f64,
remaining: &mut u64,
) -> Result<PathRings, String> {
let mut result = PathRings {
rings: vec![],
curved: vec![],
};
let mut ring = vec![];
let mut controls = vec![];
let mut curved = false;
let mut cursor = 0;
while cursor < commands.len() {
let op = commands[cursor] as u8;
cursor += 1;
match op {
0 => {
finish(
&mut result,
&mut ring,
&mut controls,
&mut curved,
remaining,
)?;
let p = point(m, [commands[cursor], commands[cursor + 1]])?;
cursor += 2;
ring.push(p);
}
1 => {
let p = point(m, [commands[cursor], commands[cursor + 1]])?;
cursor += 2;
if let Some(&last) = ring.last() {
if last != p {
controls.push(vec![last, p]);
}
}
ring.push(p);
}
2 | 3 => {
let a = *ring.last().ok_or("PDF curve has no current point")?;
let p = point(m, [commands[cursor], commands[cursor + 1]])?;
let q = point(m, [commands[cursor + 2], commands[cursor + 3]])?;
if op == 2 {
let end = point(m, [commands[cursor + 4], commands[cursor + 5]])?;
convex(&[a, p, q, end], remaining)?;
bezier([a, p, q, end], tolerance, remaining, &mut ring)?;
controls.push(vec![a, p, q, end]);
cursor += 6;
} else {
convex(&[a, p, q], remaining)?;
bezier([a, p, q], tolerance, remaining, &mut ring)?;
controls.push(vec![a, p, q]);
cursor += 4;
}
curved = true;
}
4 => {
let first = ring.first().copied();
finish(
&mut result,
&mut ring,
&mut controls,
&mut curved,
remaining,
)?;
if let Some(p) = first {
ring.push(p);
}
}
_ => return Err("Unknown PDF fill command".into()),
}
}
finish(
&mut result,
&mut ring,
&mut controls,
&mut curved,
remaining,
)?;
Ok(result)
}
pub(super) fn qualify(
paths: &[PathRings],
clip: &[Ring2D],
error: f64,
remaining: &mut u64,
) -> Result<(), String> {
let rings: Vec<_> = paths
.iter()
.flat_map(|p| p.rings.iter().zip(&p.curved).map(|(r, c)| (r, *c)))
.chain(clip.iter().map(|r| (r, false)))
.collect();
if !rings.iter().any(|(_, c)| *c) {
return Ok(());
}
let magnitude = rings
.iter()
.flat_map(|(r, _)| r.iter().flatten())
.fold(1_f64, |m, x| m.max(x.abs()));
if 128. * f64::EPSILON * magnitude >= error {
return Err("PDF curved boundary separation is below model-coordinate precision".into());
}
let total: usize = rings.iter().map(|(r, _)| r.len()).sum();
charge(remaining, (total as u64).pow(2) * 4)?;
for i in 0..rings.len() {
for j in i + 1..rings.len() {
let (a, ac) = rings[i];
let (b, bc) = rings[j];
if !ac && !bc {
continue;
}
for ai in 0..a.len() {
for bi in 0..b.len() {
let (p, q) = (a[ai], a[(ai + 1) % a.len()]);
let (r, s) = (b[bi], b[(bi + 1) % b.len()]);
let crosses = sign(p, q, r) != sign(p, q, s) && sign(r, s, p) != sign(r, s, q);
let gap = distance(p, r, s)
.min(distance(q, r, s))
.min(distance(r, p, q))
.min(distance(s, p, q));
if crosses || gap <= error * 4. {
return Err("PDF curved fill boundary intersects or approaches another paint, ring or CropBox within its error envelope".into());
}
}
}
}
}
Ok(())
}