diff --git a/bezier-rs/docs/interactive-docs/src/App.vue b/bezier-rs/docs/interactive-docs/src/App.vue
index ed1750ad..9340d028 100644
--- a/bezier-rs/docs/interactive-docs/src/App.vue
+++ b/bezier-rs/docs/interactive-docs/src/App.vue
@@ -2,7 +2,8 @@
Bezier-rs Interactive Documentation
This is the interactive documentation for the bezier-rs library. Click and drag on the endpoints of the example curves to visualize the various Bezier utilities and functions.
-
+
Beziers
+
+
Subpaths
+
+
+
@@ -21,10 +26,11 @@
import { defineComponent, markRaw } from "vue";
import { drawText, drawPoint, drawBezier, drawLine, getContextFromCanvas, drawBezierHelper, COLORS } from "@/utils/drawing";
-import { BezierCurveType, Point, WasmBezierInstance } from "@/utils/types";
+import { BezierCurveType, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
import ExamplePane from "@/components/ExamplePane.vue";
import SliderExample from "@/components/SliderExample.vue";
+import SubpathExamplePane from "@/components/SubpathExamplePane.vue";
const tSliderOptions = {
min: 0,
@@ -37,13 +43,9 @@ const tSliderOptions = {
const SCALE_UNIT_VECTOR_FACTOR = 50;
export default defineComponent({
- name: "App",
- components: {
- ExamplePane,
- },
data() {
return {
- features: [
+ bezierFeatures: [
{
name: "Constructor",
// eslint-disable-next-line
@@ -355,8 +357,22 @@ export default defineComponent({
},
},
],
+ subpathFeatures: [
+ {
+ name: "Constructor",
+ callback: (subpath: WasmSubpathInstance): string => subpath.to_svg(),
+ },
+ {
+ name: "Length",
+ callback: (subpath: WasmSubpathInstance): string => subpath.length(),
+ },
+ ],
};
},
+ components: {
+ ExamplePane,
+ SubpathExamplePane,
+ },
});
diff --git a/bezier-rs/docs/interactive-docs/src/components/Example.vue b/bezier-rs/docs/interactive-docs/src/components/Example.vue
index ec5dce8a..b5411bca 100644
--- a/bezier-rs/docs/interactive-docs/src/components/Example.vue
+++ b/bezier-rs/docs/interactive-docs/src/components/Example.vue
@@ -12,12 +12,6 @@ import BezierDrawing from "@/components/BezierDrawing";
import { BezierCallback, WasmBezierInstance } from "@/utils/types";
export default defineComponent({
- name: "ExampleComponent",
- data() {
- return {
- bezierDrawing: new BezierDrawing(this.bezier, this.callback, this.options, this.createThroughPoints),
- };
- },
props: {
title: String,
bezier: {
@@ -37,6 +31,11 @@ export default defineComponent({
default: false,
},
},
+ data() {
+ return {
+ bezierDrawing: new BezierDrawing(this.bezier, this.callback, this.options, this.createThroughPoints),
+ };
+ },
mounted() {
const drawing = this.$refs.drawing as HTMLElement;
drawing.appendChild(this.bezierDrawing.getCanvas());
diff --git a/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue b/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue
index 1cded33d..8b919503 100644
--- a/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue
+++ b/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue
@@ -1,6 +1,6 @@
-
+
@@ -58,10 +58,6 @@ const CurveTypeMapping = {
};
export default defineComponent({
- name: "ExamplePane",
- components: {
- Example,
- },
props: {
name: {
type: String as PropType
,
@@ -118,12 +114,15 @@ export default defineComponent({
});
});
},
+ components: {
+ Example,
+ },
});
diff --git a/bezier-rs/docs/interactive-docs/src/components/SubpathExamplePane.vue b/bezier-rs/docs/interactive-docs/src/components/SubpathExamplePane.vue
new file mode 100644
index 00000000..11337e4f
--- /dev/null
+++ b/bezier-rs/docs/interactive-docs/src/components/SubpathExamplePane.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
diff --git a/bezier-rs/docs/interactive-docs/src/utils/drawing.ts b/bezier-rs/docs/interactive-docs/src/utils/drawing.ts
index 2644fbb0..1b9655d1 100644
--- a/bezier-rs/docs/interactive-docs/src/utils/drawing.ts
+++ b/bezier-rs/docs/interactive-docs/src/utils/drawing.ts
@@ -7,7 +7,7 @@ export const COLORS = {
CANVAS: "white",
INTERACTIVE: {
STROKE_1: "black",
- STROKE_2: "grey",
+ STROKE_2: "gray",
SELECTED: "blue",
},
NON_INTERACTIVE: {
diff --git a/bezier-rs/docs/interactive-docs/src/utils/types.ts b/bezier-rs/docs/interactive-docs/src/utils/types.ts
index 9a8ac92c..013bf674 100644
--- a/bezier-rs/docs/interactive-docs/src/utils/types.ts
+++ b/bezier-rs/docs/interactive-docs/src/utils/types.ts
@@ -5,6 +5,9 @@ export type WasmBezierKey = keyof WasmBezierInstance;
export type WasmBezierConstructorKey = "new_linear" | "new_quadratic" | "new_cubic";
export type WasmBezierManipulatorKey = "set_start" | "set_handle_start" | "set_handle_end" | "set_end";
+export type WasmSubpathInstance = InstanceType;
+export type WasmSubpathManipulatorKey = "set_anchor" | "set_in_handle" | "set_out_handle";
+
export enum BezierCurveType {
Linear = "Linear",
Quadratic = "Quadratic",
@@ -12,6 +15,7 @@ export enum BezierCurveType {
}
export type BezierCallback = (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record, mouseLocation?: Point) => void;
+export type SubpathCallback = (subpath: WasmSubpathInstance) => string;
export type SliderOption = {
min: number;
diff --git a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs
index d690a0fb..577a004c 100644
--- a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs
+++ b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs
@@ -1,3 +1,6 @@
+pub mod subpath;
+mod svg_drawing;
+
use bezier_rs::{Bezier, ProjectionOptions};
use glam::DVec2;
use serde::{Deserialize, Serialize};
@@ -31,7 +34,7 @@ fn to_js_value(data: T) -> JsValue {
#[wasm_bindgen]
impl WasmBezier {
- /// Expect js_points to be a list of 3 pairs.
+ /// Expect js_points to be a list of 2 pairs.
pub fn new_linear(js_points: &JsValue) -> WasmBezier {
let points: [DVec2; 2] = js_points.into_serde().unwrap();
WasmBezier(Bezier::from_linear_dvec2(points[0], points[1]))
diff --git a/bezier-rs/docs/interactive-docs/wasm/src/subpath.rs b/bezier-rs/docs/interactive-docs/wasm/src/subpath.rs
new file mode 100644
index 00000000..dac05807
--- /dev/null
+++ b/bezier-rs/docs/interactive-docs/wasm/src/subpath.rs
@@ -0,0 +1,47 @@
+use bezier_rs::subpath::{ManipulatorGroup, Subpath, ToSVGOptions};
+use glam::DVec2;
+use wasm_bindgen::prelude::*;
+
+use crate::svg_drawing::*;
+
+/// Wrapper of the `Subpath` struct to be used in JS.
+#[wasm_bindgen]
+pub struct WasmSubpath(Subpath);
+
+#[wasm_bindgen]
+impl WasmSubpath {
+ /// Expects js_points to be an unbounded list of triples, where each item is a tuple of floats.
+ pub fn from_triples(js_points: &JsValue, closed: bool) -> WasmSubpath {
+ let point_triples: Vec<[Option; 3]> = js_points.into_serde().unwrap();
+ let manipulator_groups = point_triples
+ .into_iter()
+ .map(|point_triple| ManipulatorGroup {
+ anchor: point_triple[0].unwrap(),
+ in_handle: point_triple[1],
+ out_handle: point_triple[2],
+ })
+ .collect();
+ WasmSubpath(Subpath::new(manipulator_groups, closed))
+ }
+
+ pub fn set_anchor(&mut self, index: usize, x: f64, y: f64) {
+ self.0[index].anchor = DVec2::new(x, y);
+ }
+
+ pub fn set_in_handle(&mut self, index: usize, x: f64, y: f64) {
+ self.0[index].in_handle = Some(DVec2::new(x, y));
+ }
+
+ pub fn set_out_handle(&mut self, index: usize, x: f64, y: f64) {
+ self.0[index].out_handle = Some(DVec2::new(x, y));
+ }
+
+ pub fn to_svg(&self) -> String {
+ format!("{}{}{}", SVG_OPEN_TAG, self.0.to_svg(ToSVGOptions::default()), SVG_CLOSE_TAG)
+ }
+
+ pub fn length(&self) -> String {
+ let length_text = draw_text(format!("Length: {:.2}", self.0.length(None)), 5., 193., BLACK);
+ format!("{}{}{}{}", SVG_OPEN_TAG, self.0.to_svg(ToSVGOptions::default()), length_text, SVG_CLOSE_TAG)
+ }
+}
diff --git a/bezier-rs/docs/interactive-docs/wasm/src/svg_drawing.rs b/bezier-rs/docs/interactive-docs/wasm/src/svg_drawing.rs
new file mode 100644
index 00000000..479146c1
--- /dev/null
+++ b/bezier-rs/docs/interactive-docs/wasm/src/svg_drawing.rs
@@ -0,0 +1,11 @@
+// SVG drawing constants
+pub const SVG_OPEN_TAG: &str = r#""#;
+pub const SVG_CLOSE_TAG: &str = " ";
+
+// Sylistic constants
+pub const BLACK: &str = "black";
+
+/// Helper function to create an SVG text entitty.
+pub fn draw_text(text: String, x_pos: f64, y_pos: f64, fill: &str) -> String {
+ format!(r#"{text} "#)
+}
diff --git a/bezier-rs/lib/src/consts.rs b/bezier-rs/lib/src/consts.rs
index d605140f..c6e5dfdd 100644
--- a/bezier-rs/lib/src/consts.rs
+++ b/bezier-rs/lib/src/consts.rs
@@ -19,3 +19,10 @@ pub const DEFAULT_LUT_STEP_SIZE: i32 = 10;
pub const DEFAULT_LENGTH_SUBDIVISIONS: i32 = 1000;
/// Default step size for `reduce` function.
pub const DEFAULT_REDUCE_STEP_SIZE: f64 = 0.01;
+
+// SVG constants
+pub const SVG_ARG_CUBIC: &str = "C";
+pub const SVG_ARG_LINEAR: &str = "L";
+pub const SVG_ARG_MOVE: &str = "M";
+pub const SVG_ARG_QUADRATIC: &str = "Q";
+pub const SVG_ARG_CLOSED: &str = "Z";
diff --git a/bezier-rs/lib/src/lib.rs b/bezier-rs/lib/src/lib.rs
index 1073bb1f..f4fdc67b 100644
--- a/bezier-rs/lib/src/lib.rs
+++ b/bezier-rs/lib/src/lib.rs
@@ -1,9 +1,11 @@
//! Bezier-rs: A Bezier Math Library for Rust
mod consts;
+pub mod subpath;
mod utils;
use consts::*;
+pub use subpath::*;
use glam::{DMat2, DVec2};
@@ -164,23 +166,49 @@ impl Bezier {
Bezier::from_cubic_dvec2(start, handle_start, handle_end, end)
}
- /// Convert to SVG.
- pub fn to_svg(&self) -> String {
- // TODO: Allow modifying the viewport, width and height
- let m_path = format!("M {} {}", self.start.x, self.start.y);
- let handles_path = match self.handles {
- BezierHandles::Linear => "L".to_string(),
+ /// Return the string argument used to create a curve in an SVG `path`, excluding the start point.
+ pub(crate) fn svg_curve_argument(&self) -> String {
+ let handle_args = match self.handles {
+ BezierHandles::Linear => SVG_ARG_LINEAR.to_string(),
BezierHandles::Quadratic { handle } => {
- format!("Q {} {},", handle.x, handle.y)
+ format!("{SVG_ARG_QUADRATIC}{} {}", handle.x, handle.y)
}
BezierHandles::Cubic { handle_start, handle_end } => {
- format!("C {} {}, {} {},", handle_start.x, handle_start.y, handle_end.x, handle_end.y)
+ format!("{SVG_ARG_CUBIC}{} {} {} {}", handle_start.x, handle_start.y, handle_end.x, handle_end.y)
}
};
- let curve_path = format!("{} {} {}", handles_path, self.end.x, self.end.y);
+ format!("{handle_args} {} {}", self.end.x, self.end.y)
+ }
+
+ /// Return the string argument used to create the lines connecting handles to endpoints in an SVG `path`
+ pub(crate) fn svg_handle_line_argument(&self) -> Option {
+ match self.handles {
+ BezierHandles::Linear => None,
+ BezierHandles::Quadratic { handle } => {
+ let handle_line = format!("{SVG_ARG_LINEAR}{} {}", handle.x, handle.y);
+ Some(format!(
+ "{SVG_ARG_MOVE}{} {} {handle_line} {SVG_ARG_MOVE}{} {} {handle_line}",
+ self.start.x, self.start.y, self.end.x, self.end.y
+ ))
+ }
+ BezierHandles::Cubic { handle_start, handle_end } => {
+ let handle_start_line = format!("{SVG_ARG_LINEAR}{} {}", handle_start.x, handle_start.y);
+ let handle_end_line = format!("{SVG_ARG_LINEAR}{} {}", handle_end.x, handle_end.y);
+ Some(format!(
+ "{SVG_ARG_MOVE}{} {} {handle_start_line} {SVG_ARG_MOVE}{} {} {handle_end_line}",
+ self.start.x, self.start.y, self.end.x, self.end.y
+ ))
+ }
+ }
+ }
+
+ /// Convert `Bezier` to SVG `path`.
+ pub fn to_svg(&self) -> String {
format!(
- r#" "#,
- 0, 0, 100, 100, 100, 100, "\n", m_path, curve_path
+ r#" "#,
+ self.start.x,
+ self.start.y,
+ self.svg_curve_argument()
)
}
diff --git a/bezier-rs/lib/src/subpath/core.rs b/bezier-rs/lib/src/subpath/core.rs
new file mode 100644
index 00000000..359c0104
--- /dev/null
+++ b/bezier-rs/lib/src/subpath/core.rs
@@ -0,0 +1,88 @@
+use super::*;
+use crate::consts::*;
+
+/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
+impl Subpath {
+ /// Create a new `Subpath` using a list of [ManipulatorGroup]s.
+ /// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
+ pub fn new(manipulator_groups: Vec, closed: bool) -> Subpath {
+ assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup.");
+ Subpath { manipulator_groups, closed }
+ }
+
+ /// Create a `Subpath` consisting of 2 manipulator groups from a `Bezier`.
+ pub fn from_bezier(bezier: Bezier) -> Self {
+ Subpath::new(
+ vec![
+ ManipulatorGroup {
+ anchor: bezier.start(),
+ in_handle: None,
+ out_handle: bezier.handle_start(),
+ },
+ ManipulatorGroup {
+ anchor: bezier.end(),
+ in_handle: bezier.handle_end(),
+ out_handle: None,
+ },
+ ],
+ false,
+ )
+ }
+
+ /// Returns true if the `Subpath` contains no [ManipulatorGroup].
+ pub fn is_empty(&self) -> bool {
+ self.manipulator_groups.is_empty()
+ }
+
+ /// Returns the number of [ManipulatorGroup]s contained within the `Subpath`.
+ pub fn len(&self) -> usize {
+ self.manipulator_groups.len()
+ }
+
+ /// Returns an iterator of the [Bezier]s along the `Subpath`.
+ pub fn iter(&self) -> SubpathIter {
+ SubpathIter { sub_path: self, index: 0 }
+ }
+
+ /// Returns an SVG representation of the `Subpath`.
+ pub fn to_svg(&self, options: ToSVGOptions) -> String {
+ if self.is_empty() {
+ return String::new();
+ }
+
+ let curve_start_argument = format!("{SVG_ARG_MOVE}{} {}", self[0].anchor.x, self[0].anchor.y);
+ let mut curve_arguments: Vec = self.iter().map(|bezier| bezier.svg_curve_argument()).collect();
+ if self.closed {
+ curve_arguments.push(String::from(SVG_ARG_CLOSED));
+ }
+
+ let anchor_arguments = options.formatted_anchor_arguments();
+ let anchor_circles = self
+ .manipulator_groups
+ .iter()
+ .map(|point| format!(r#" "#, point.anchor.x, point.anchor.y, anchor_arguments))
+ .collect::>();
+
+ let handle_point_arguments = options.formatted_handle_point_arguments();
+ let handle_circles: Vec = self
+ .manipulator_groups
+ .iter()
+ .flat_map(|group| [group.in_handle, group.out_handle])
+ .flatten()
+ .map(|handle| format!(r#" "#, handle.x, handle.y, handle_point_arguments))
+ .collect();
+
+ let handle_pieces: Vec = self.iter().filter_map(|bezier| bezier.svg_handle_line_argument()).collect();
+
+ format!(
+ r#" {}{}"#,
+ curve_start_argument,
+ curve_arguments.join(" "),
+ options.formatted_curve_arguments(),
+ handle_pieces.join(" "),
+ options.formatted_handle_line_arguments(),
+ handle_circles.join(""),
+ anchor_circles.join(""),
+ )
+ }
+}
diff --git a/bezier-rs/lib/src/subpath/lookup.rs b/bezier-rs/lib/src/subpath/lookup.rs
new file mode 100644
index 00000000..d1051eaf
--- /dev/null
+++ b/bezier-rs/lib/src/subpath/lookup.rs
@@ -0,0 +1,95 @@
+use super::*;
+
+/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
+impl Subpath {
+ /// Return the sum of the approximation of the length of each `Bezier` curve along the `Subpath`.
+ /// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is `1000`.
+ pub fn length(&self, num_subdivisions: Option) -> f64 {
+ self.iter().fold(0., |accumulator, bezier| accumulator + bezier.length(num_subdivisions))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::Bezier;
+ use glam::DVec2;
+
+ #[test]
+ fn length_quadratic() {
+ let start = DVec2::new(20., 30.);
+ let middle = DVec2::new(80., 90.);
+ let end = DVec2::new(60., 45.);
+ let handle1 = DVec2::new(75., 85.);
+ let handle2 = DVec2::new(40., 30.);
+ let handle3 = DVec2::new(10., 10.);
+
+ let bezier1 = Bezier::from_quadratic_dvec2(start, handle1, middle);
+ let bezier2 = Bezier::from_quadratic_dvec2(middle, handle2, end);
+ let bezier3 = Bezier::from_quadratic_dvec2(end, handle3, start);
+
+ let mut subpath = Subpath::new(
+ vec![
+ ManipulatorGroup {
+ anchor: start,
+ in_handle: None,
+ out_handle: Some(handle1),
+ },
+ ManipulatorGroup {
+ anchor: middle,
+ in_handle: None,
+ out_handle: Some(handle2),
+ },
+ ManipulatorGroup {
+ anchor: end,
+ in_handle: None,
+ out_handle: Some(handle3),
+ },
+ ],
+ false,
+ );
+ assert_eq!(subpath.length(None), bezier1.length(None) + bezier2.length(None));
+
+ subpath.closed = true;
+ assert_eq!(subpath.length(None), bezier1.length(None) + bezier2.length(None) + bezier3.length(None));
+ }
+
+ #[test]
+ fn length_mixed() {
+ let start = DVec2::new(20., 30.);
+ let middle = DVec2::new(70., 70.);
+ let end = DVec2::new(60., 45.);
+ let handle1 = DVec2::new(75., 85.);
+ let handle2 = DVec2::new(40., 30.);
+ let handle3 = DVec2::new(10., 10.);
+
+ let linear_bezier = Bezier::from_linear_dvec2(start, middle);
+ let quadratic_bezier = Bezier::from_quadratic_dvec2(middle, handle1, end);
+ let cubic_bezier = Bezier::from_cubic_dvec2(end, handle2, handle3, start);
+
+ let mut subpath = Subpath::new(
+ vec![
+ ManipulatorGroup {
+ anchor: start,
+ in_handle: Some(handle3),
+ out_handle: None,
+ },
+ ManipulatorGroup {
+ anchor: middle,
+ in_handle: None,
+ out_handle: Some(handle1),
+ },
+ ManipulatorGroup {
+ anchor: end,
+ in_handle: None,
+ out_handle: Some(handle2),
+ },
+ ],
+ false,
+ );
+ assert_eq!(subpath.length(None), linear_bezier.length(None) + quadratic_bezier.length(None));
+
+ subpath.closed = true;
+ assert_eq!(subpath.length(None), linear_bezier.length(None) + quadratic_bezier.length(None) + cubic_bezier.length(None));
+ }
+}
diff --git a/bezier-rs/lib/src/subpath/mod.rs b/bezier-rs/lib/src/subpath/mod.rs
new file mode 100644
index 00000000..bf6e7e5d
--- /dev/null
+++ b/bezier-rs/lib/src/subpath/mod.rs
@@ -0,0 +1,68 @@
+mod core;
+mod lookup;
+mod structs;
+pub use structs::*;
+
+use crate::Bezier;
+
+use std::ops::{Index, IndexMut};
+
+/// Structure used to represent a path composed of [Bezier] curves.
+pub struct Subpath {
+ manipulator_groups: Vec,
+ closed: bool,
+}
+
+/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
+pub struct SubpathIter<'a> {
+ index: usize,
+ sub_path: &'a Subpath,
+}
+
+impl Index for Subpath {
+ type Output = ManipulatorGroup;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
+ &self.manipulator_groups[index]
+ }
+}
+
+impl IndexMut for Subpath {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
+ &mut self.manipulator_groups[index]
+ }
+}
+
+impl Iterator for SubpathIter<'_> {
+ type Item = Bezier;
+
+ // Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.
+ fn next(&mut self) -> Option {
+ let len = self.sub_path.len() - 1
+ + match self.sub_path.closed {
+ true => 1,
+ false => 0,
+ };
+ if self.index >= len {
+ return None;
+ }
+ let start_index = self.index;
+ let end_index = (self.index + 1) % self.sub_path.len();
+ self.index += 1;
+
+ let start = self.sub_path[start_index].anchor;
+ let end = self.sub_path[end_index].anchor;
+ let out_handle = self.sub_path[start_index].out_handle;
+ let in_handle = self.sub_path[end_index].in_handle;
+
+ if let (Some(handle1), Some(handle2)) = (out_handle, in_handle) {
+ Some(Bezier::from_cubic_dvec2(start, handle1, handle2, end))
+ } else if let Some(handle) = out_handle.or(in_handle) {
+ Some(Bezier::from_quadratic_dvec2(start, handle, end))
+ } else {
+ Some(Bezier::from_linear_dvec2(start, end))
+ }
+ }
+}
diff --git a/bezier-rs/lib/src/subpath/structs.rs b/bezier-rs/lib/src/subpath/structs.rs
new file mode 100644
index 00000000..f2edb960
--- /dev/null
+++ b/bezier-rs/lib/src/subpath/structs.rs
@@ -0,0 +1,83 @@
+use glam::DVec2;
+
+/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
+pub struct ManipulatorGroup {
+ pub anchor: DVec2,
+ pub in_handle: Option,
+ pub out_handle: Option,
+}
+
+/// Structure to represent optional parameters that can be passed to the `into_svg` function.
+pub struct ToSVGOptions {
+ /// Color of the line segments along the `Subpath`. Defaulted to `black`.
+ pub curve_stroke_color: String,
+ /// Width of the line segments along the `Subpath`. Defaulted to `2.`.
+ pub curve_stroke_width: f64,
+ /// Stroke color outlining circles marking anchors on the `Subpath`. Defaulted to `black`.
+ pub anchor_stroke_color: String,
+ /// Stroke width outlining circles marking anchors on the `Subpath`. Defaulted to `2.`.
+ pub anchor_stroke_width: f64,
+ /// Radius of the circles marking anchors on the `Subpath`. Defaulted to `4.`.
+ pub anchor_radius: f64,
+ /// Fill color of the circles marking anchors on the `Subpath`. Defaulted to `white`.
+ pub anchor_fill: String,
+ /// Color of the line segments connecting anchors to handle points. Defaulted to `gray`.
+ pub handle_line_stroke_color: String,
+ /// Width of the line segments connecting anchors to handle points. Defaulted to `1.`.
+ pub handle_line_stroke_width: f64,
+ /// Stroke color outlining circles marking the handles of `Subpath`. Defaulted to `gray`.
+ pub handle_point_stroke_color: String,
+ /// Stroke color outlining circles marking the handles of `Subpath`. Defaulted to `1.5`.
+ pub handle_point_stroke_width: f64,
+ /// Radius of the circles marking the handles of `Subpath`. Defaulted to `3.`.
+ pub handle_point_radius: f64,
+ /// Fill color of the circles marking the handles of `Subpath`. Defaulted to `white`.
+ pub handle_point_fill: String,
+}
+
+impl ToSVGOptions {
+ /// Combine and format curve styling options for an SVG path.
+ pub(crate) fn formatted_curve_arguments(&self) -> String {
+ format!(r#"stroke="{}" stroke-width="{}" fill="none""#, self.curve_stroke_color, self.curve_stroke_width)
+ }
+
+ /// Combine and format anchor styling options an SVG circle.
+ pub(crate) fn formatted_anchor_arguments(&self) -> String {
+ format!(
+ r#"r="{}", stroke="{}" stroke-width="{}" fill="{}""#,
+ self.anchor_radius, self.anchor_stroke_color, self.anchor_stroke_width, self.anchor_fill
+ )
+ }
+
+ /// Combine and format handle point styling options for an SVG circle.
+ pub(crate) fn formatted_handle_point_arguments(&self) -> String {
+ format!(
+ r#"r="{}", stroke="{}" stroke-width="{}" fill="{}""#,
+ self.handle_point_radius, self.handle_point_stroke_color, self.handle_point_stroke_width, self.handle_point_fill
+ )
+ }
+
+ /// Combine and format handle line styling options an SVG path.
+ pub(crate) fn formatted_handle_line_arguments(&self) -> String {
+ format!(r#"stroke="{}" stroke-width="{}" fill="none""#, self.handle_line_stroke_color, self.handle_line_stroke_width)
+ }
+}
+
+impl Default for ToSVGOptions {
+ fn default() -> Self {
+ ToSVGOptions {
+ curve_stroke_color: String::from("black"),
+ curve_stroke_width: 2.,
+ anchor_stroke_color: String::from("black"),
+ anchor_stroke_width: 2.,
+ anchor_radius: 4.,
+ anchor_fill: String::from("white"),
+ handle_line_stroke_color: String::from("gray"),
+ handle_line_stroke_width: 1.,
+ handle_point_stroke_color: String::from("gray"),
+ handle_point_stroke_width: 1.5,
+ handle_point_radius: 3.,
+ handle_point_fill: String::from("white"),
+ }
+ }
+}