// Port of path-data-parser 0.1.0 src/normalize.ts.
///|
/// `normalize(segments)`: turns absolute segments (see `absolutize`) into
/// `M`, `L`, `C` and `Z` only. `H`/`V` become lines; `S`, `Q`, `T` and arcs
/// become cubic Béziers.
pub fn normalize(segments : Array[Segment]) -> Array[Segment] {
let out = []
let mut last_type = ' '
let mut cx = 0.0
let mut cy = 0.0
let mut subx = 0.0
let mut suby = 0.0
let mut lcx = 0.0
let mut lcy = 0.0
for segment in segments {
let data = segment.data
match segment.key {
'M' => {
out.push({ key: 'M', data: data.copy(), })
cx = data[0]
cy = data[1]
subx = data[0]
suby = data[1]
}
'C' => {
out.push({ key: 'C', data: data.copy(), })
cx = data[4]
cy = data[5]
lcx = data[2]
lcy = data[3]
}
'L' => {
out.push({ key: 'L', data: data.copy(), })
cx = data[0]
cy = data[1]
}
'H' => {
cx = data[0]
out.push({ key: 'L', data: [cx, cy], })
}
'V' => {
cy = data[0]
out.push({ key: 'L', data: [cx, cy], })
}
'S' => {
let (cx1, cy1) = if last_type == 'C' || last_type == 'S' {
(cx + (cx - lcx), cy + (cy - lcy))
} else {
(cx, cy)
}
out.push({
key: 'C',
data: [cx1, cy1, data[0], data[1], data[2], data[3]],
})
lcx = data[0]
lcy = data[1]
cx = data[2]
cy = data[3]
}
'T' => {
let x = data[0]
let y = data[1]
let (x1, y1) = if last_type == 'Q' || last_type == 'T' {
(cx + (cx - lcx), cy + (cy - lcy))
} else {
(cx, cy)
}
let cx1 = cx + 2.0 * (x1 - cx) / 3.0
let cy1 = cy + 2.0 * (y1 - cy) / 3.0
let cx2 = x + 2.0 * (x1 - x) / 3.0
let cy2 = y + 2.0 * (y1 - y) / 3.0
out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y], })
lcx = x1
lcy = y1
cx = x
cy = y
}
'Q' => {
let x1 = data[0]
let y1 = data[1]
let x = data[2]
let y = data[3]
let cx1 = cx + 2.0 * (x1 - cx) / 3.0
let cy1 = cy + 2.0 * (y1 - cy) / 3.0
let cx2 = x + 2.0 * (x1 - x) / 3.0
let cy2 = y + 2.0 * (y1 - y) / 3.0
out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y], })
lcx = x1
lcy = y1
cx = x
cy = y
}
'A' => {
let r1 = data[0].abs()
let r2 = data[1].abs()
let angle = data[2]
let large_arc_flag = data[3]
let sweep_flag = data[4]
let x = data[5]
let y = data[6]
if r1 == 0.0 || r2 == 0.0 {
out.push({ key: 'C', data: [cx, cy, x, y, x, y], })
cx = x
cy = y
} else if cx != x || cy != y {
let curves = arc_to_cubic_curves(
cx, cy, x, y, r1, r2, angle, large_arc_flag, sweep_flag,
)
for curve in curves {
out.push({ key: 'C', data: curve, })
}
cx = x
cy = y
}
}
'Z' => {
out.push({ key: 'Z', data: [], })
cx = subx
cy = suby
}
_ => ()
}
last_type = segment.key
}
out
}
///|
fn deg_to_rad(degrees : Double) -> Double {
@jsmath.PI * degrees / 180.0
}
///|
fn rotate(x : Double, y : Double, angle_rad : Double) -> (Double, Double) {
let cos = @jsmath.cos(angle_rad)
let sin = @jsmath.sin(angle_rad)
(x * cos - y * sin, x * sin + y * cos)
}
///|
/// JavaScript truthiness of a number used as an arc flag.
fn truthy(flag : Double) -> Bool {
flag != 0.0 && !flag.is_nan()
}
///|
/// `parseFloat(x.toFixed(9))`.
fn round9(x : Double) -> Double {
@jsmath.parse_float(@jsmath.to_fixed(x, 9))
}
///|
/// `arcToCubicCurves` without the `recursive` argument: the endpoint
/// parameterization of an SVG arc, split into pieces of at most 120 degrees.
fn arc_to_cubic_curves(
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
r1 : Double,
r2 : Double,
angle : Double,
large_arc_flag : Double,
sweep_flag : Double,
) -> Array[Array[Double]] {
let angle_rad = deg_to_rad(angle)
let (x1, y1) = rotate(x1, y1, -angle_rad)
let (x2, y2) = rotate(x2, y2, -angle_rad)
let x = (x1 - x2) / 2.0
let y = (y1 - y2) / 2.0
let mut h = x * x / (r1 * r1) + y * y / (r2 * r2)
let mut r1 = r1
let mut r2 = r2
if h > 1.0 {
h = h.sqrt()
r1 = h * r1
r2 = h * r2
}
let sign = if large_arc_flag == sweep_flag { -1.0 } else { 1.0 }
let r1_pow = r1 * r1
let r2_pow = r2 * r2
let left = r1_pow * r2_pow - r1_pow * y * y - r2_pow * x * x
let right = r1_pow * y * y + r2_pow * x * x
let k = sign * (left / right).abs().sqrt()
let cx = k * r1 * y / r2 + (x1 + x2) / 2.0
let cy = k * -r2 * x / r1 + (y1 + y2) / 2.0
let mut f1 = @jsmath.asin(round9((y1 - cy) / r2))
let mut f2 = @jsmath.asin(round9((y2 - cy) / r2))
if x1 < cx {
f1 = @jsmath.PI - f1
}
if x2 < cx {
f2 = @jsmath.PI - f2
}
if f1 < 0.0 {
f1 = @jsmath.PI * 2.0 + f1
}
if f2 < 0.0 {
f2 = @jsmath.PI * 2.0 + f2
}
if truthy(sweep_flag) && f1 > f2 {
f1 = f1 - @jsmath.PI * 2.0
}
if !truthy(sweep_flag) && f2 > f1 {
f2 = f2 - @jsmath.PI * 2.0
}
let params = arc_pieces(x1, y1, x2, y2, r1, r2, sweep_flag, f1, f2, cx, cy)
let curves = []
for i = 0; i < params.length(); i = i + 3 {
let (ax, ay) = rotate(params[i].0, params[i].1, angle_rad)
let (bx, by) = rotate(params[i + 1].0, params[i + 1].1, angle_rad)
let (ex, ey) = rotate(params[i + 2].0, params[i + 2].1, angle_rad)
curves.push([ax, ay, bx, by, ex, ey])
}
curves
}
///|
/// The part of `arcToCubicCurves` shared by the outer and the `recursive`
/// calls: control points `[m2, m3, m4, ...]` of the pieces from angle `f1`
/// to `f2`, in the arc's unrotated frame.
fn arc_pieces(
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
r1 : Double,
r2 : Double,
sweep_flag : Double,
f1 : Double,
f2 : Double,
cx : Double,
cy : Double,
) -> Array[(Double, Double)] {
let mut f2 = f2
let mut x2 = x2
let mut y2 = y2
let mut params = []
let mut df = f2 - f1
if df.abs() > @jsmath.PI * 120.0 / 180.0 {
let f2old = f2
let x2old = x2
let y2old = y2
if truthy(sweep_flag) && f2 > f1 {
f2 = f1 + @jsmath.PI * 120.0 / 180.0 * 1.0
} else {
f2 = f1 + @jsmath.PI * 120.0 / 180.0 * -1.0
}
x2 = cx + r1 * @jsmath.cos(f2)
y2 = cy + r2 * @jsmath.sin(f2)
params = arc_pieces(
x2, y2, x2old, y2old, r1, r2, sweep_flag, f2, f2old, cx, cy,
)
}
df = f2 - f1
let c1 = @jsmath.cos(f1)
let s1 = @jsmath.sin(f1)
let c2 = @jsmath.cos(f2)
let s2 = @jsmath.sin(f2)
let t = @jsmath.tan(df / 4.0)
let hx = 4.0 / 3.0 * r1 * t
let hy = 4.0 / 3.0 * r2 * t
let m2x = x1 + hx * s1
let m2y = y1 - hy * c1
let m3 = (x2 + hx * s2, y2 - hy * c2)
let m4 = (x2, y2)
let m2 = (2.0 * x1 - m2x, 2.0 * y1 - m2y)
[m2, m3, m4, ..params]
}