#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
pub x: f64,
pub y: f64,
}
impl Point {
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PathCmd {
MoveTo = 0,
LineTo = 1,
QuadTo = 2,
ConicTo = 3,
CubicTo = 4,
Close = 5,
}
pub struct Path {
cmds: Vec<PathCmd>,
points: Vec<Point>,
conic_weights: Vec<f64>,
cur: Point,
sub_start: Point,
last_quad_cp: Option<Point>,
last_cubic_cp2: Option<Point>,
}
const KAPPA: f64 = 0.552_284_749_831;
impl Path {
pub fn new() -> Self {
Self {
cmds: Vec::new(),
points: Vec::new(),
conic_weights: Vec::new(),
cur: Point::new(0.0, 0.0),
sub_start: Point::new(0.0, 0.0),
last_quad_cp: None,
last_cubic_cp2: None,
}
}
pub fn conic_weights(&self) -> &[f64] {
&self.conic_weights
}
pub fn clear(&mut self) {
self.cmds.clear();
self.points.clear();
self.conic_weights.clear();
self.last_quad_cp = None;
self.last_cubic_cp2 = None;
}
pub fn is_empty(&self) -> bool {
self.cmds.is_empty()
}
pub fn len(&self) -> usize {
self.cmds.len()
}
pub fn cmds(&self) -> &[PathCmd] {
&self.cmds
}
pub fn points(&self) -> &[Point] {
&self.points
}
pub fn move_to(&mut self, x: f64, y: f64) {
self.cmds.push(PathCmd::MoveTo);
self.points.push(Point::new(x, y));
let p = Point::new(x, y);
self.cur = p;
self.sub_start = p;
self.last_quad_cp = None;
self.last_cubic_cp2 = None;
}
pub fn line_to(&mut self, x: f64, y: f64) {
self.cmds.push(PathCmd::LineTo);
let p = Point::new(x, y);
self.points.push(p);
self.cur = p;
self.last_quad_cp = None;
self.last_cubic_cp2 = None;
}
pub fn cubic_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
self.cmds.push(PathCmd::CubicTo);
self.points.push(Point::new(cp1x, cp1y));
self.points.push(Point::new(cp2x, cp2y));
let end = Point::new(x, y);
self.points.push(end);
self.last_cubic_cp2 = Some(Point::new(cp2x, cp2y));
self.last_quad_cp = None;
self.cur = end;
}
pub fn quad_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
self.cmds.push(PathCmd::QuadTo);
let cp = Point::new(cpx, cpy);
self.points.push(cp);
let end = Point::new(x, y);
self.points.push(end);
self.last_quad_cp = Some(cp);
self.last_cubic_cp2 = None;
self.cur = end;
}
pub fn smooth_quad_to(&mut self, x: f64, y: f64) {
let cp = self
.last_quad_cp
.expect("smooth_quad_to requires a preceding quad_to");
let cpx = 2.0 * self.cur.x - cp.x;
let cpy = 2.0 * self.cur.y - cp.y;
self.quad_to(cpx, cpy, x, y);
}
pub fn smooth_cubic_to(&mut self, cp2x: f64, cp2y: f64, x: f64, y: f64) {
let cp2_prev = self
.last_cubic_cp2
.expect("smooth_cubic_to requires a preceding cubic_to");
let cp1x = 2.0 * self.cur.x - cp2_prev.x;
let cp1y = 2.0 * self.cur.y - cp2_prev.y;
self.cubic_to(cp1x, cp1y, cp2x, cp2y, x, y);
}
pub fn conic_to(&mut self, cx: f64, cy: f64, ex: f64, ey: f64, w: f64) {
assert!(w > 0.0, "conic weight must be positive");
self.cmds.push(PathCmd::ConicTo);
self.points.push(Point::new(cx, cy));
let end = Point::new(ex, ey);
self.points.push(end);
self.conic_weights.push(w);
self.last_quad_cp = None;
self.last_cubic_cp2 = None;
self.cur = end;
}
pub fn arc_to(
&mut self,
cx: f64,
cy: f64,
rx: f64,
ry: f64,
start: f64,
sweep: f64,
force_move_to: bool,
) {
if rx <= 0.0 || ry <= 0.0 || sweep == 0.0 {
return;
}
let sx = cx + rx * start.cos();
let sy = cy + ry * start.sin();
if force_move_to {
self.move_to(sx, sy);
} else {
self.line_to(sx, sy);
}
add_arc_segments(self, cx, cy, rx, ry, start, sweep);
}
pub fn close(&mut self) {
self.cmds.push(PathCmd::Close);
self.cur = self.sub_start;
self.last_quad_cp = None;
self.last_cubic_cp2 = None;
}
pub fn add_pie(&mut self, cx: f64, cy: f64, rx: f64, ry: f64, start: f64, sweep: f64) {
if rx <= 0.0 || ry <= 0.0 || sweep == 0.0 {
return;
}
let start_x = cx + rx * start.cos();
let start_y = cy + ry * start.sin();
self.move_to(cx, cy);
self.line_to(start_x, start_y);
add_arc_segments(self, cx, cy, rx, ry, start, sweep);
self.close();
}
pub fn translate(&mut self, dx: f64, dy: f64) {
for p in self.points.iter_mut() {
p.x += dx;
p.y += dy;
}
self.cur.x += dx;
self.cur.y += dy;
self.sub_start.x += dx;
self.sub_start.y += dy;
if let Some(ref mut p) = self.last_quad_cp {
p.x += dx;
p.y += dy;
}
if let Some(ref mut p) = self.last_cubic_cp2 {
p.x += dx;
p.y += dy;
}
}
pub fn transform(&mut self, m: &crate::api::matrix::Matrix2D) {
for p in self.points.iter_mut() {
let (x, y) = m.map_point(p.x, p.y);
p.x = x;
p.y = y;
}
let (cx, cy) = m.map_point(self.cur.x, self.cur.y);
self.cur = Point::new(cx, cy);
let (sx, sy) = m.map_point(self.sub_start.x, self.sub_start.y);
self.sub_start = Point::new(sx, sy);
if let Some(p) = self.last_quad_cp {
let (x, y) = m.map_point(p.x, p.y);
self.last_quad_cp = Some(Point::new(x, y));
}
if let Some(p) = self.last_cubic_cp2 {
let (x, y) = m.map_point(p.x, p.y);
self.last_cubic_cp2 = Some(Point::new(x, y));
}
}
pub fn add_path(&mut self, other: &Path) {
if other.is_empty() {
return;
}
let mut pi = 0usize;
let mut wi = 0usize;
for &cmd in &other.cmds {
match cmd {
PathCmd::MoveTo => {
let p = other.points[pi];
pi += 1;
self.move_to(p.x, p.y);
}
PathCmd::LineTo => {
let p = other.points[pi];
pi += 1;
self.line_to(p.x, p.y);
}
PathCmd::QuadTo => {
let cp = other.points[pi];
let end = other.points[pi + 1];
pi += 2;
self.quad_to(cp.x, cp.y, end.x, end.y);
}
PathCmd::ConicTo => {
let cp = other.points[pi];
let end = other.points[pi + 1];
pi += 2;
let w = other.conic_weights[wi];
wi += 1;
self.conic_to(cp.x, cp.y, end.x, end.y, w);
}
PathCmd::CubicTo => {
let cp1 = other.points[pi];
let cp2 = other.points[pi + 1];
let end = other.points[pi + 2];
pi += 3;
self.cubic_to(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
}
PathCmd::Close => {
self.close();
}
}
}
}
pub fn add_path_translated(&mut self, other: &Path, dx: f64, dy: f64) {
let mut pi = 0usize;
let mut wi = 0usize;
for &cmd in &other.cmds {
match cmd {
PathCmd::MoveTo => {
let p = other.points[pi];
pi += 1;
self.move_to(p.x + dx, p.y + dy);
}
PathCmd::LineTo => {
let p = other.points[pi];
pi += 1;
self.line_to(p.x + dx, p.y + dy);
}
PathCmd::QuadTo => {
let cp = other.points[pi];
let end = other.points[pi + 1];
pi += 2;
self.quad_to(cp.x + dx, cp.y + dy, end.x + dx, end.y + dy);
}
PathCmd::ConicTo => {
let cp = other.points[pi];
let end = other.points[pi + 1];
pi += 2;
let w = other.conic_weights[wi];
wi += 1;
self.conic_to(cp.x + dx, cp.y + dy, end.x + dx, end.y + dy, w);
}
PathCmd::CubicTo => {
let cp1 = other.points[pi];
let cp2 = other.points[pi + 1];
let end = other.points[pi + 2];
pi += 3;
self.cubic_to(
cp1.x + dx,
cp1.y + dy,
cp2.x + dx,
cp2.y + dy,
end.x + dx,
end.y + dy,
);
}
PathCmd::Close => {
self.close();
}
}
}
}
pub fn add_path_transformed(&mut self, other: &Path, m: &crate::api::matrix::Matrix2D) {
let mut pi = 0usize;
let mut wi = 0usize;
let map = |p: Point| {
let (x, y) = m.map_point(p.x, p.y);
(x, y)
};
for &cmd in &other.cmds {
match cmd {
PathCmd::MoveTo => {
let (x, y) = map(other.points[pi]);
pi += 1;
self.move_to(x, y);
}
PathCmd::LineTo => {
let (x, y) = map(other.points[pi]);
pi += 1;
self.line_to(x, y);
}
PathCmd::QuadTo => {
let (cx, cy) = map(other.points[pi]);
let (ex, ey) = map(other.points[pi + 1]);
pi += 2;
self.quad_to(cx, cy, ex, ey);
}
PathCmd::ConicTo => {
let (cx, cy) = map(other.points[pi]);
let (ex, ey) = map(other.points[pi + 1]);
pi += 2;
let w = other.conic_weights[wi];
wi += 1;
self.conic_to(cx, cy, ex, ey, w);
}
PathCmd::CubicTo => {
let (c1x, c1y) = map(other.points[pi]);
let (c2x, c2y) = map(other.points[pi + 1]);
let (ex, ey) = map(other.points[pi + 2]);
pi += 3;
self.cubic_to(c1x, c1y, c2x, c2y, ex, ey);
}
PathCmd::Close => {
self.close();
}
}
}
}
pub fn control_box(&self) -> Option<crate::api::context::Rect> {
if self.points.is_empty() {
return None;
}
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for p in &self.points {
if p.x < min_x {
min_x = p.x;
}
if p.y < min_y {
min_y = p.y;
}
if p.x > max_x {
max_x = p.x;
}
if p.y > max_y {
max_y = p.y;
}
}
Some(crate::api::context::Rect::new(
min_x,
min_y,
max_x - min_x,
max_y - min_y,
))
}
pub fn bounding_box(&self) -> Option<crate::api::context::Rect> {
self.control_box()
}
pub fn add_triangle(&mut self, x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) {
self.move_to(x0, y0);
self.line_to(x1, y1);
self.line_to(x2, y2);
self.close();
}
pub fn add_polygon(&mut self, points: &[Point]) {
if points.len() < 3 {
return;
}
self.move_to(points[0].x, points[0].y);
for p in &points[1..] {
self.line_to(p.x, p.y);
}
self.close();
}
pub fn add_polyline(&mut self, points: &[Point]) {
if points.len() < 2 {
return;
}
self.move_to(points[0].x, points[0].y);
for p in &points[1..] {
self.line_to(p.x, p.y);
}
}
pub fn add_round_rect(&mut self, x: f64, y: f64, w: f64, h: f64, rx: f64, ry: f64) {
if w <= 0.0 || h <= 0.0 {
return;
}
let rx = rx.max(0.0).min(w * 0.5);
let ry = ry.max(0.0).min(h * 0.5);
if rx == 0.0 || ry == 0.0 {
self.move_to(x, y);
self.line_to(x + w, y);
self.line_to(x + w, y + h);
self.line_to(x, y + h);
self.close();
return;
}
let kx = rx * KAPPA;
let ky = ry * KAPPA;
let x1 = x + w;
let y1 = y + h;
self.move_to(x + rx, y);
self.line_to(x1 - rx, y);
self.cubic_to(x1 - rx + kx, y, x1, y + ry - ky, x1, y + ry);
self.line_to(x1, y1 - ry);
self.cubic_to(x1, y1 - ry + ky, x1 - rx + kx, y1, x1 - rx, y1);
self.line_to(x + rx, y1);
self.cubic_to(x + rx - kx, y1, x, y1 - ry + ky, x, y1 - ry);
self.line_to(x, y + ry);
self.cubic_to(x, y + ry - ky, x + rx - kx, y, x + rx, y);
self.close();
}
pub fn add_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) {
let kx = rx * KAPPA;
let ky = ry * KAPPA;
self.move_to(cx + rx, cy);
self.cubic_to(cx + rx, cy + ky, cx + kx, cy + ry, cx, cy + ry);
self.cubic_to(cx - kx, cy + ry, cx - rx, cy + ky, cx - rx, cy);
self.cubic_to(cx - rx, cy - ky, cx - kx, cy - ry, cx, cy - ry);
self.cubic_to(cx + kx, cy - ry, cx + rx, cy - ky, cx + rx, cy);
self.close();
}
pub fn add_circle(&mut self, cx: f64, cy: f64, r: f64) {
let kx = r * KAPPA;
let ky = r * KAPPA;
self.move_to(cx + r, cy);
self.cubic_to(cx + r, cy + ky, cx + kx, cy + r, cx, cy + r);
self.cubic_to(cx - kx, cy + r, cx - r, cy + ky, cx - r, cy);
self.cubic_to(cx - r, cy - ky, cx - kx, cy - r, cx, cy - r);
self.cubic_to(cx + kx, cy - r, cx + r, cy - ky, cx + r, cy);
self.close();
}
}
impl Default for Path {
fn default() -> Self {
Self::new()
}
}
fn add_arc_segments(path: &mut Path, cx: f64, cy: f64, rx: f64, ry: f64, start: f64, sweep: f64) {
let half_pi = std::f64::consts::FRAC_PI_2;
let abs_sweep = sweep.abs();
let n = (abs_sweep / half_pi).ceil() as usize;
let n = n.max(1);
let segment_sweep = sweep / n as f64;
let mut angle = start;
for _ in 0..n {
add_arc_cubic(path, cx, cy, rx, ry, angle, segment_sweep);
angle += segment_sweep;
}
}
fn add_arc_cubic(path: &mut Path, cx: f64, cy: f64, rx: f64, ry: f64, start: f64, sweep: f64) {
let k = (4.0 / 3.0) * (sweep / 4.0).tan();
let (sin0, cos0) = start.sin_cos();
let end = start + sweep;
let (sin1, cos1) = end.sin_cos();
let cp1x = cx + rx * (cos0 - k * sin0);
let cp1y = cy + ry * (sin0 + k * cos0);
let cp2x = cx + rx * (cos1 + k * sin1);
let cp2y = cy + ry * (sin1 - k * cos1);
let ex = cx + rx * cos1;
let ey = cy + ry * sin1;
path.cubic_to(cp1x, cp1y, cp2x, cp2y, ex, ey);
}