diff --git a/libraries/bezier-rs/src/bezier/core.rs b/libraries/bezier-rs/src/bezier/core.rs index 43572ecb..2fb48d97 100644 --- a/libraries/bezier-rs/src/bezier/core.rs +++ b/libraries/bezier-rs/src/bezier/core.rs @@ -1,4 +1,5 @@ use super::*; +use std::fmt::Write; /// Functionality relating to core `Bezier` operations, such as constructors and `abs_diff_eq`. impl Bezier { @@ -142,14 +143,44 @@ impl Bezier { } } - /// Convert `Bezier` to SVG `path`. - pub fn to_svg(&self) -> String { - format!( - r#""#, - self.start.x, - self.start.y, - self.svg_curve_argument() - ) + /// Appends to the `svg` mutable string with an SVG shape representation of the curve. + pub fn curve_to_svg(&self, svg: &mut String, attributes: String) { + let _ = write!(svg, r#""#, self.start.x, self.start.y, self.svg_curve_argument(), attributes); + } + + /// Appends to the `svg` mutable string with an SVG shape representation of the handle lines. + pub fn handle_lines_to_svg(&self, svg: &mut String, attributes: String) { + let _ = write!(svg, r#""#, self.svg_handle_line_argument().unwrap_or_else(|| "".to_string()), attributes); + } + + /// Appends to the `svg` mutable string with an SVG shape representation of the anchors. + pub fn anchors_to_svg(&self, svg: &mut String, attributes: String) { + let _ = write!( + svg, + r#""#, + self.start.x, self.start.y, self.end.x, self.end.y + ); + } + + /// Appends to the `svg` mutable string with an SVG shape representation of the handles. + pub fn handles_to_svg(&self, svg: &mut String, attributes: String) { + if let BezierHandles::Quadratic { handle } = self.handles { + let _ = write!(svg, r#""#, handle.x, handle.y); + } else if let BezierHandles::Cubic { handle_start, handle_end } = self.handles { + let _ = write!( + svg, + r#""#, + handle_start.x, handle_start.y, handle_end.x, handle_end.y + ); + }; + } + + /// Appends to the `svg` mutable string with an SVG shape representation that includes the curve, the handle lines, the anchors, and the handles. + pub fn to_svg(&self, svg: &mut String, curve_attributes: String, anchor_attributes: String, handle_attributes: String, handle_line_attributes: String) { + self.curve_to_svg(svg, curve_attributes); + self.handle_lines_to_svg(svg, handle_line_attributes); + self.anchors_to_svg(svg, anchor_attributes); + self.handles_to_svg(svg, handle_attributes); } /// Returns true if the corresponding points of the two `Bezier`s are within the provided absolute value difference from each other. diff --git a/libraries/bezier-rs/src/lib.rs b/libraries/bezier-rs/src/lib.rs index 3b0d26b2..0c2482af 100644 --- a/libraries/bezier-rs/src/lib.rs +++ b/libraries/bezier-rs/src/lib.rs @@ -3,7 +3,9 @@ mod bezier; mod consts; mod subpath; +mod svg; mod utils; pub use bezier::*; pub use subpath::*; +pub use svg::*; diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 7dc43f35..d466d1ac 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -1,5 +1,6 @@ use super::*; use crate::consts::*; +use crate::ToSVGOptions; /// Functionality relating to core `Subpath` operations, such as constructors and `iter`. impl Subpath { diff --git a/libraries/bezier-rs/src/subpath/structs.rs b/libraries/bezier-rs/src/subpath/structs.rs index f2edb960..1ad55dac 100644 --- a/libraries/bezier-rs/src/subpath/structs.rs +++ b/libraries/bezier-rs/src/subpath/structs.rs @@ -6,78 +6,3 @@ pub struct ManipulatorGroup { 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"), - } - } -} diff --git a/libraries/bezier-rs/src/svg.rs b/libraries/bezier-rs/src/svg.rs new file mode 100644 index 00000000..d7352c01 --- /dev/null +++ b/libraries/bezier-rs/src/svg.rs @@ -0,0 +1,74 @@ +/// 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"), + } + } +} diff --git a/website/other/bezier-rs-demos/vue.config.js b/website/other/bezier-rs-demos/vue.config.js index a29ed969..dc251f5a 100644 --- a/website/other/bezier-rs-demos/vue.config.js +++ b/website/other/bezier-rs-demos/vue.config.js @@ -23,7 +23,7 @@ module.exports = defineConfig({ crateDirectory: path.resolve(__dirname, "wasm"), // Remove when this issue is resolved: https://github.com/wasm-tool/wasm-pack-plugin/issues/93 outDir: path.resolve(__dirname, "wasm/pkg"), - watchDirectories: ["../../lib"].map((folder) => path.resolve(__dirname, folder)), + watchDirectories: ["../../../libraries/bezier-rs"].map((folder) => path.resolve(__dirname, folder)), }) ) .end(); diff --git a/website/other/bezier-rs-demos/wasm/src/bezier.rs b/website/other/bezier-rs-demos/wasm/src/bezier.rs new file mode 100644 index 00000000..9d4deaf9 --- /dev/null +++ b/website/other/bezier-rs-demos/wasm/src/bezier.rs @@ -0,0 +1,264 @@ +use crate::svg_drawing::*; +use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, ProjectionOptions, ToSVGOptions}; +use glam::DVec2; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +#[derive(Serialize, Deserialize)] +struct CircleSector { + center: Point, + radius: f64, + #[serde(rename = "startAngle")] + start_angle: f64, + #[serde(rename = "endAngle")] + end_angle: f64, +} + +#[derive(Serialize, Deserialize)] +struct Point { + x: f64, + y: f64, +} + +#[wasm_bindgen] +pub enum WasmMaximizeArcs { + Automatic, // 0 + On, // 1 + Off, // 2 +} + +/// Wrapper of the `Bezier` struct to be used in JS. +#[wasm_bindgen] +#[derive(Clone)] +pub struct WasmBezier(Bezier); + +/// Convert a `DVec2` into a `Point`. +fn vec_to_point(p: &DVec2) -> Point { + Point { x: p.x, y: p.y } +} + +/// Convert a bezier to a list of points. +fn bezier_to_points(bezier: Bezier) -> Vec { + bezier.get_points().map(|point| Point { x: point.x, y: point.y }).collect() +} + +/// Serialize some data and then convert it to a JsValue. +fn to_js_value(data: T) -> JsValue { + JsValue::from_serde(&serde_json::to_string(&data).unwrap()).unwrap() +} + +fn convert_wasm_maximize_arcs(wasm_enum_value: WasmMaximizeArcs) -> ArcStrategy { + match wasm_enum_value { + WasmMaximizeArcs::Automatic => ArcStrategy::Automatic, + WasmMaximizeArcs::On => ArcStrategy::FavorLargerArcs, + WasmMaximizeArcs::Off => ArcStrategy::FavorCorrectness, + } +} + +#[wasm_bindgen] +impl WasmBezier { + /// 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])) + } + + /// Expect js_points to be a list of 3 pairs. + pub fn new_quadratic(js_points: &JsValue) -> WasmBezier { + let points: [DVec2; 3] = js_points.into_serde().unwrap(); + WasmBezier(Bezier::from_quadratic_dvec2(points[0], points[1], points[2])) + } + + /// Expect js_points to be a list of 4 pairs. + pub fn new_cubic(js_points: &JsValue) -> WasmBezier { + let points: [DVec2; 4] = js_points.into_serde().unwrap(); + WasmBezier(Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3])) + } + + pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> WasmBezier { + let points: [DVec2; 3] = js_points.into_serde().unwrap(); + WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], Some(t))) + } + + pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> WasmBezier { + let points: [DVec2; 3] = js_points.into_serde().unwrap(); + WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], Some(t), Some(midpoint_separation))) + } + + pub fn set_start(&mut self, x: f64, y: f64) { + self.0.set_start(DVec2::new(x, y)); + } + + pub fn set_end(&mut self, x: f64, y: f64) { + self.0.set_end(DVec2::new(x, y)); + } + + pub fn set_handle_start(&mut self, x: f64, y: f64) { + self.0.set_handle_start(DVec2::new(x, y)); + } + + pub fn set_handle_end(&mut self, x: f64, y: f64) { + self.0.set_handle_end(DVec2::new(x, y)); + } + + /// The wrapped return type is `Vec`. + pub fn get_points(&self) -> JsValue { + let points: Vec = self.0.get_points().map(|point| vec_to_point(&point)).collect(); + to_js_value(points) + } + + pub fn to_svg(&self) -> String { + let mut bezier = String::new(); + self.0.to_svg( + &mut bezier, + CURVE_ATTRIBUTES.to_string(), + ANCHOR_ATTRIBUTES.to_string(), + HANDLE_ATTRIBUTES.to_string(), + HANDLE_LINE_ATTRIBUTES.to_string(), + ); + format!("{}{}{}", SVG_OPEN_TAG, bezier, SVG_CLOSE_TAG) + } + + pub fn length(&self) -> f64 { + self.0.length(None) + } + + /// The wrapped return type is `Point`. + pub fn evaluate(&self, t: f64) -> JsValue { + let point: Point = vec_to_point(&self.0.evaluate(t)); + to_js_value(point) + } + + /// The wrapped return type is `Vec`. + pub fn compute_lookup_table(&self, steps: usize) -> JsValue { + let table_values: Vec = self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect(); + to_js_value(table_values) + } + + pub fn derivative(&self) -> Option { + self.0.derivative().map(WasmBezier) + } + + /// The wrapped return type is `Point`. + pub fn tangent(&self, t: f64) -> JsValue { + let tangent_point: Point = vec_to_point(&self.0.tangent(t)); + to_js_value(tangent_point) + } + + /// The wrapped return type is `Point`. + pub fn normal(&self, t: f64) -> JsValue { + let normal_point: Point = vec_to_point(&self.0.normal(t)); + to_js_value(normal_point) + } + + pub fn curvature(&self, t: f64) -> f64 { + self.0.curvature(t) + } + + /// The wrapped return type is `[Vec; 2]`. + pub fn split(&self, t: f64) -> JsValue { + let bezier_points: [Vec; 2] = self.0.split(t).map(bezier_to_points); + to_js_value(bezier_points) + } + + pub fn trim(&self, t1: f64, t2: f64) -> WasmBezier { + WasmBezier(self.0.trim(t1, t2)) + } + + pub fn project(&self, x: f64, y: f64) -> f64 { + self.0.project(DVec2::new(x, y), ProjectionOptions::default()) + } + + /// The wrapped return type is `[Vec; 2]`. + pub fn local_extrema(&self) -> JsValue { + let local_extrema: [Vec; 2] = self.0.local_extrema(); + to_js_value(local_extrema) + } + + /// The wrapped return type is `[Point; 2]`. + pub fn bounding_box(&self) -> JsValue { + let bbox_points: [Point; 2] = self.0.bounding_box().map(|p| Point { x: p.x, y: p.y }); + to_js_value(bbox_points) + } + + /// The wrapped return type is `Vec`. + pub fn inflections(&self) -> JsValue { + let inflections: Vec = self.0.inflections(); + to_js_value(inflections) + } + + /// The wrapped return type is `Vec>`. + pub fn de_casteljau_points(&self, t: f64) -> JsValue { + let points: Vec> = self + .0 + .de_casteljau_points(t) + .iter() + .map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::>()) + .collect(); + to_js_value(points) + } + + pub fn rotate(&self, angle: f64) -> WasmBezier { + WasmBezier(self.0.rotate(angle)) + } + + fn intersect(&self, curve: &Bezier, error: Option) -> Vec { + self.0.intersections(curve, error) + } + + pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec { + let points: [DVec2; 2] = js_points.into_serde().unwrap(); + let line = Bezier::from_linear_dvec2(points[0], points[1]); + self.intersect(&line, None) + } + + pub fn intersect_quadratic_segment(&self, js_points: &JsValue, error: f64) -> Vec { + let points: [DVec2; 3] = js_points.into_serde().unwrap(); + let quadratic = Bezier::from_quadratic_dvec2(points[0], points[1], points[2]); + self.intersect(&quadratic, Some(error)) + } + + pub fn intersect_cubic_segment(&self, js_points: &JsValue, error: f64) -> Vec { + let points: [DVec2; 4] = js_points.into_serde().unwrap(); + let cubic = Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3]); + self.intersect(&cubic, Some(error)) + } + + /// The wrapped return type is `Vec<[f64; 2]>`. + pub fn intersect_self(&self, error: f64) -> JsValue { + let points: Vec<[f64; 2]> = self.0.self_intersections(Some(error)); + to_js_value(points) + } + + pub fn reduce(&self) -> JsValue { + let bezier_points: Vec> = self.0.reduce(None).into_iter().map(bezier_to_points).collect(); + to_js_value(bezier_points) + } + + /// The wrapped return type is `Vec>`. + pub fn offset(&self, distance: f64) -> JsValue { + let bezier_points: Vec> = self.0.offset(distance).into_iter().map(bezier_to_points).collect(); + to_js_value(bezier_points) + } + + /// The wrapped return type is `Vec`. + pub fn arcs(&self, error: f64, max_iterations: usize, maximize_arcs: WasmMaximizeArcs) -> JsValue { + let strategy = convert_wasm_maximize_arcs(maximize_arcs); + let options = ArcsOptions { error, max_iterations, strategy }; + let circle_sectors: Vec = self + .0 + .arcs(options) + .iter() + .map(|sector| CircleSector { + center: Point { + x: sector.center.x, + y: sector.center.y, + }, + radius: sector.radius, + start_angle: sector.start_angle, + end_angle: sector.end_angle, + }) + .collect(); + to_js_value(circle_sectors) + } +} diff --git a/website/other/bezier-rs-demos/wasm/src/lib.rs b/website/other/bezier-rs-demos/wasm/src/lib.rs index 3bae2ee3..0ac7850c 100644 --- a/website/other/bezier-rs-demos/wasm/src/lib.rs +++ b/website/other/bezier-rs-demos/wasm/src/lib.rs @@ -1,265 +1,3 @@ +pub mod bezier; pub mod subpath; mod svg_drawing; - -use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, ProjectionOptions}; -use glam::DVec2; -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; - -#[derive(Serialize, Deserialize)] -struct CircleSector { - center: Point, - radius: f64, - #[serde(rename = "startAngle")] - start_angle: f64, - #[serde(rename = "endAngle")] - end_angle: f64, -} - -#[derive(Serialize, Deserialize)] -struct Point { - x: f64, - y: f64, -} - -#[wasm_bindgen] -pub enum WasmMaximizeArcs { - Automatic, // 0 - On, // 1 - Off, // 2 -} - -/// Wrapper of the `Bezier` struct to be used in JS. -#[wasm_bindgen] -#[derive(Clone)] -pub struct WasmBezier(Bezier); - -impl Drop for WasmBezier { - fn drop(&mut self) { - // Is it correct to keep this empty? - // Consider removing after https://github.com/rustwasm/wasm-bindgen/pull/2984 is merged and released - } -} - -/// Convert a `DVec2` into a `Point`. -fn vec_to_point(p: &DVec2) -> Point { - Point { x: p.x, y: p.y } -} - -/// Convert a bezier to a list of points. -fn bezier_to_points(bezier: Bezier) -> Vec { - bezier.get_points().map(|point| Point { x: point.x, y: point.y }).collect() -} - -/// Serialize some data and then convert it to a JsValue. -fn to_js_value(data: T) -> JsValue { - JsValue::from_serde(&serde_json::to_string(&data).unwrap()).unwrap() -} - -fn convert_wasm_maximize_arcs(wasm_enum_value: WasmMaximizeArcs) -> ArcStrategy { - match wasm_enum_value { - WasmMaximizeArcs::Automatic => ArcStrategy::Automatic, - WasmMaximizeArcs::On => ArcStrategy::FavorLargerArcs, - WasmMaximizeArcs::Off => ArcStrategy::FavorCorrectness, - } -} - -#[wasm_bindgen] -impl WasmBezier { - /// 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])) - } - - /// Expect js_points to be a list of 3 pairs. - pub fn new_quadratic(js_points: &JsValue) -> WasmBezier { - let points: [DVec2; 3] = js_points.into_serde().unwrap(); - WasmBezier(Bezier::from_quadratic_dvec2(points[0], points[1], points[2])) - } - - /// Expect js_points to be a list of 4 pairs. - pub fn new_cubic(js_points: &JsValue) -> WasmBezier { - let points: [DVec2; 4] = js_points.into_serde().unwrap(); - WasmBezier(Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3])) - } - - pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> WasmBezier { - let points: [DVec2; 3] = js_points.into_serde().unwrap(); - WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], Some(t))) - } - - pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> WasmBezier { - let points: [DVec2; 3] = js_points.into_serde().unwrap(); - WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], Some(t), Some(midpoint_separation))) - } - - pub fn set_start(&mut self, x: f64, y: f64) { - self.0.set_start(DVec2::new(x, y)); - } - - pub fn set_end(&mut self, x: f64, y: f64) { - self.0.set_end(DVec2::new(x, y)); - } - - pub fn set_handle_start(&mut self, x: f64, y: f64) { - self.0.set_handle_start(DVec2::new(x, y)); - } - - pub fn set_handle_end(&mut self, x: f64, y: f64) { - self.0.set_handle_end(DVec2::new(x, y)); - } - - /// The wrapped return type is `Vec`. - pub fn get_points(&self) -> JsValue { - let points: Vec = self.0.get_points().map(|point| vec_to_point(&point)).collect(); - to_js_value(points) - } - - pub fn to_svg(&self) -> String { - self.0.to_svg() - } - - pub fn length(&self) -> f64 { - self.0.length(None) - } - - /// The wrapped return type is `Point`. - pub fn evaluate(&self, t: f64) -> JsValue { - let point: Point = vec_to_point(&self.0.evaluate(t)); - to_js_value(point) - } - - /// The wrapped return type is `Vec`. - pub fn compute_lookup_table(&self, steps: usize) -> JsValue { - let table_values: Vec = self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect(); - to_js_value(table_values) - } - - pub fn derivative(&self) -> Option { - self.0.derivative().map(WasmBezier) - } - - /// The wrapped return type is `Point`. - pub fn tangent(&self, t: f64) -> JsValue { - let tangent_point: Point = vec_to_point(&self.0.tangent(t)); - to_js_value(tangent_point) - } - - /// The wrapped return type is `Point`. - pub fn normal(&self, t: f64) -> JsValue { - let normal_point: Point = vec_to_point(&self.0.normal(t)); - to_js_value(normal_point) - } - - pub fn curvature(&self, t: f64) -> f64 { - self.0.curvature(t) - } - - /// The wrapped return type is `[Vec; 2]`. - pub fn split(&self, t: f64) -> JsValue { - let bezier_points: [Vec; 2] = self.0.split(t).map(bezier_to_points); - to_js_value(bezier_points) - } - - pub fn trim(&self, t1: f64, t2: f64) -> WasmBezier { - WasmBezier(self.0.trim(t1, t2)) - } - - pub fn project(&self, x: f64, y: f64) -> f64 { - self.0.project(DVec2::new(x, y), ProjectionOptions::default()) - } - - /// The wrapped return type is `[Vec; 2]`. - pub fn local_extrema(&self) -> JsValue { - let local_extrema: [Vec; 2] = self.0.local_extrema(); - to_js_value(local_extrema) - } - - /// The wrapped return type is `[Point; 2]`. - pub fn bounding_box(&self) -> JsValue { - let bbox_points: [Point; 2] = self.0.bounding_box().map(|p| Point { x: p.x, y: p.y }); - to_js_value(bbox_points) - } - - /// The wrapped return type is `Vec`. - pub fn inflections(&self) -> JsValue { - let inflections: Vec = self.0.inflections(); - to_js_value(inflections) - } - - /// The wrapped return type is `Vec>`. - pub fn de_casteljau_points(&self, t: f64) -> JsValue { - let points: Vec> = self - .0 - .de_casteljau_points(t) - .iter() - .map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::>()) - .collect(); - to_js_value(points) - } - - pub fn rotate(&self, angle: f64) -> WasmBezier { - WasmBezier(self.0.rotate(angle)) - } - - fn intersect(&self, curve: &Bezier, error: Option) -> Vec { - self.0.intersections(curve, error) - } - - pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec { - let points: [DVec2; 2] = js_points.into_serde().unwrap(); - let line = Bezier::from_linear_dvec2(points[0], points[1]); - self.intersect(&line, None) - } - - pub fn intersect_quadratic_segment(&self, js_points: &JsValue, error: f64) -> Vec { - let points: [DVec2; 3] = js_points.into_serde().unwrap(); - let quadratic = Bezier::from_quadratic_dvec2(points[0], points[1], points[2]); - self.intersect(&quadratic, Some(error)) - } - - pub fn intersect_cubic_segment(&self, js_points: &JsValue, error: f64) -> Vec { - let points: [DVec2; 4] = js_points.into_serde().unwrap(); - let cubic = Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3]); - self.intersect(&cubic, Some(error)) - } - - /// The wrapped return type is `Vec<[f64; 2]>`. - pub fn intersect_self(&self, error: f64) -> JsValue { - let points: Vec<[f64; 2]> = self.0.self_intersections(Some(error)); - to_js_value(points) - } - - pub fn reduce(&self) -> JsValue { - let bezier_points: Vec> = self.0.reduce(None).into_iter().map(bezier_to_points).collect(); - to_js_value(bezier_points) - } - - /// The wrapped return type is `Vec>`. - pub fn offset(&self, distance: f64) -> JsValue { - let bezier_points: Vec> = self.0.offset(distance).into_iter().map(bezier_to_points).collect(); - to_js_value(bezier_points) - } - - /// The wrapped return type is `Vec`. - pub fn arcs(&self, error: f64, max_iterations: usize, maximize_arcs: WasmMaximizeArcs) -> JsValue { - let strategy = convert_wasm_maximize_arcs(maximize_arcs); - let options = ArcsOptions { error, max_iterations, strategy }; - let circle_sectors: Vec = self - .0 - .arcs(options) - .iter() - .map(|sector| CircleSector { - center: Point { - x: sector.center.x, - y: sector.center.y, - }, - radius: sector.radius, - start_angle: sector.start_angle, - end_angle: sector.end_angle, - }) - .collect(); - to_js_value(circle_sectors) - } -} diff --git a/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs b/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs index 5b78bd76..f65b0de0 100644 --- a/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs +++ b/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs @@ -5,6 +5,12 @@ pub const SVG_CLOSE_TAG: &str = ""; // Stylistic constants pub const BLACK: &str = "black"; +// Default attributes +pub const CURVE_ATTRIBUTES: &str = "stroke=\"black\" stroke-width=\"2\" fill=\"none\""; +pub const HANDLE_LINE_ATTRIBUTES: &str = "stroke=\"gray\" stroke-width=\"1\" fill=\"none\""; +pub const ANCHOR_ATTRIBUTES: &str = "r=\"4\" stroke=\"black\" stroke-width=\"2\" fill=\"white\""; +pub const HANDLE_ATTRIBUTES: &str = "r=\"3\" stroke=\"gray\" stroke-width=\"1.5\" fill=\"white\""; + /// 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}"#)