use tiny_skia_path::{NormalizedF32, NormalizedF32Exclusive, Point};
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use tiny_skia_path::NoStdFloat;
pub use tiny_skia_path::path_geometry::{
chop_cubic_at2, chop_quad_at, find_cubic_max_curvature, find_unit_quad_roots, new_t_values,
CubicCoeff, QuadCoeff,
};
use tiny_skia_path::path_geometry::valid_unit_divide;
pub fn chop_quad_at_x_extrema(src: &[Point; 3], dst: &mut [Point; 5]) -> usize {
let a = src[0].x;
let mut b = src[1].x;
let c = src[2].x;
if is_not_monotonic(a, b, c) {
if let Some(t_value) = valid_unit_divide(a - b, a - b - b + c) {
chop_quad_at(src, t_value, dst);
dst[1].x = dst[2].x;
dst[3].x = dst[2].x;
return 1;
}
b = if (a - b).abs() < (b - c).abs() { a } else { c };
}
dst[0] = Point::from_xy(a, src[0].y);
dst[1] = Point::from_xy(b, src[1].y);
dst[2] = Point::from_xy(c, src[2].y);
0
}
pub fn chop_quad_at_y_extrema(src: &[Point; 3], dst: &mut [Point; 5]) -> usize {
let a = src[0].y;
let mut b = src[1].y;
let c = src[2].y;
if is_not_monotonic(a, b, c) {
if let Some(t_value) = valid_unit_divide(a - b, a - b - b + c) {
chop_quad_at(src, t_value, dst);
dst[1].y = dst[2].y;
dst[3].y = dst[2].y;
return 1;
}
b = if (a - b).abs() < (b - c).abs() { a } else { c };
}
dst[0] = Point::from_xy(src[0].x, a);
dst[1] = Point::from_xy(src[1].x, b);
dst[2] = Point::from_xy(src[2].x, c);
0
}
fn is_not_monotonic(a: f32, b: f32, c: f32) -> bool {
let ab = a - b;
let mut bc = b - c;
if ab < 0.0 {
bc = -bc;
}
ab == 0.0 || bc < 0.0
}
pub fn chop_cubic_at_x_extrema(src: &[Point; 4], dst: &mut [Point; 10]) -> usize {
let mut t_values = new_t_values();
let t_values = find_cubic_extrema(src[0].x, src[1].x, src[2].x, src[3].x, &mut t_values);
chop_cubic_at(src, t_values, dst);
if !t_values.is_empty() {
dst[2].x = dst[3].x;
dst[4].x = dst[3].x;
if t_values.len() == 2 {
dst[5].x = dst[6].x;
dst[7].x = dst[6].x;
}
}
t_values.len()
}
pub fn chop_cubic_at_y_extrema(src: &[Point; 4], dst: &mut [Point; 10]) -> usize {
let mut t_values = new_t_values();
let t_values = find_cubic_extrema(src[0].y, src[1].y, src[2].y, src[3].y, &mut t_values);
chop_cubic_at(src, t_values, dst);
if !t_values.is_empty() {
dst[2].y = dst[3].y;
dst[4].y = dst[3].y;
if t_values.len() == 2 {
dst[5].y = dst[6].y;
dst[7].y = dst[6].y;
}
}
t_values.len()
}
fn find_cubic_extrema(
a: f32,
b: f32,
c: f32,
d: f32,
t_values: &mut [NormalizedF32Exclusive; 3],
) -> &[NormalizedF32Exclusive] {
let na = d - a + 3.0 * (b - c);
let nb = 2.0 * (a - b - b + c);
let nc = b - a;
let roots = find_unit_quad_roots(na, nb, nc, t_values);
&t_values[0..roots]
}
pub fn chop_cubic_at(src: &[Point; 4], t_values: &[NormalizedF32Exclusive], dst: &mut [Point]) {
if t_values.is_empty() {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
} else {
let mut t = t_values[0];
let mut tmp = [Point::zero(); 4];
let mut src = src;
let mut dst_offset = 0;
for i in 0..t_values.len() {
chop_cubic_at2(src, t, &mut dst[dst_offset..]);
if i == t_values.len() - 1 {
break;
}
dst_offset += 3;
tmp[0] = dst[dst_offset + 0];
tmp[1] = dst[dst_offset + 1];
tmp[2] = dst[dst_offset + 2];
tmp[3] = dst[dst_offset + 3];
src = &tmp;
let n = valid_unit_divide(
t_values[i + 1].get() - t_values[i].get(),
1.0 - t_values[i].get(),
);
match n {
Some(n) => t = n,
None => {
dst[dst_offset + 4] = src[3];
dst[dst_offset + 5] = src[3];
dst[dst_offset + 6] = src[3];
break;
}
}
}
}
}
pub fn chop_cubic_at_max_curvature(
src: &[Point; 4],
t_values: &mut [NormalizedF32Exclusive; 3],
dst: &mut [Point],
) -> usize {
let mut roots = [NormalizedF32::ZERO; 3];
let roots = find_cubic_max_curvature(src, &mut roots);
let mut count = 0;
for root in roots {
if 0.0 < root.get() && root.get() < 1.0 {
t_values[count] = NormalizedF32Exclusive::new_bounded(root.get());
count += 1;
}
}
if count == 0 {
dst[0..4].copy_from_slice(src);
} else {
chop_cubic_at(src, &t_values[0..count], dst);
}
count + 1
}
pub fn chop_mono_cubic_at_x(src: &[Point; 4], x: f32, dst: &mut [Point; 7]) -> bool {
cubic_dchop_at_intercept(src, x, true, dst)
}
pub fn chop_mono_cubic_at_y(src: &[Point; 4], y: f32, dst: &mut [Point; 7]) -> bool {
cubic_dchop_at_intercept(src, y, false, dst)
}
fn cubic_dchop_at_intercept(
src: &[Point; 4],
intercept: f32,
is_vertical: bool,
dst: &mut [Point; 7],
) -> bool {
use crate::path64::{cubic64::Cubic64, line_cubic_intersections, point64::Point64};
let src = [
Point64::from_point(src[0]),
Point64::from_point(src[1]),
Point64::from_point(src[2]),
Point64::from_point(src[3]),
];
let cubic = Cubic64::new(src);
let mut roots = [0.0; 3];
let count = if is_vertical {
line_cubic_intersections::vertical_intersect(&cubic, f64::from(intercept), &mut roots)
} else {
line_cubic_intersections::horizontal_intersect(&cubic, f64::from(intercept), &mut roots)
};
if count > 0 {
let pair = cubic.chop_at(roots[0]);
for i in 0..7 {
dst[i] = pair.points[i].to_point();
}
true
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chop_cubic_at_y_extrema_1() {
let src = [
Point::from_xy(10.0, 20.0),
Point::from_xy(67.0, 437.0),
Point::from_xy(298.0, 213.0),
Point::from_xy(401.0, 214.0),
];
let mut dst = [Point::zero(); 10];
let n = chop_cubic_at_y_extrema(&src, &mut dst);
assert_eq!(n, 2);
assert_eq!(dst[0], Point::from_xy(10.0, 20.0));
assert_eq!(dst[1], Point::from_xy(37.508274, 221.24475));
assert_eq!(dst[2], Point::from_xy(105.541855, 273.19803));
assert_eq!(dst[3], Point::from_xy(180.15599, 273.19803));
assert_eq!(dst[4], Point::from_xy(259.80502, 273.19803));
assert_eq!(dst[5], Point::from_xy(346.9527, 213.99666));
assert_eq!(dst[6], Point::from_xy(400.30844, 213.99666));
assert_eq!(dst[7], Point::from_xy(400.53958, 213.99666));
assert_eq!(dst[8], Point::from_xy(400.7701, 213.99777));
assert_eq!(dst[9], Point::from_xy(401.0, 214.0));
}
}