Refactor bezier-rs' `to_svg` functionality (#758)
* Change Bezier to_svg to include handles and endpoints * Move params into brackets of format macro, remove unused comment * Use write macro instead of format * Fix path for watched directory * Refactor functions to remove ToSVGOptions
This commit is contained in:
parent
1e109dc552
commit
f74b6ed111
|
|
@ -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#"<path d="{SVG_ARG_MOVE}{} {} {}" stroke="black" fill="none"/>"#,
|
||||
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#"<path d="{SVG_ARG_MOVE}{} {} {}" {}/>"#, 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#"<path d="{}" {}/>"#, 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#"<circle cx="{}" cy="{}" {attributes}/><circle cx="{}" cy="{}" {attributes}/>"#,
|
||||
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#"<circle cx="{}" cy="{}" {attributes}/>"#, handle.x, handle.y);
|
||||
} else if let BezierHandles::Cubic { handle_start, handle_end } = self.handles {
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<circle cx="{}" cy="{}" {attributes}/><circle cx="{}" cy="{}" {attributes}/>"#,
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
mod bezier;
|
||||
mod consts;
|
||||
mod subpath;
|
||||
mod svg;
|
||||
mod utils;
|
||||
|
||||
pub use bezier::*;
|
||||
pub use subpath::*;
|
||||
pub use svg::*;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -6,78 +6,3 @@ pub struct ManipulatorGroup {
|
|||
pub in_handle: Option<DVec2>,
|
||||
pub out_handle: Option<DVec2>,
|
||||
}
|
||||
|
||||
/// 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<Point> {
|
||||
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<T: Serialize>(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<Point>`.
|
||||
pub fn get_points(&self) -> JsValue {
|
||||
let points: Vec<Point> = 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<Point>`.
|
||||
pub fn compute_lookup_table(&self, steps: usize) -> JsValue {
|
||||
let table_values: Vec<Point> = self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect();
|
||||
to_js_value(table_values)
|
||||
}
|
||||
|
||||
pub fn derivative(&self) -> Option<WasmBezier> {
|
||||
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<Point>; 2]`.
|
||||
pub fn split(&self, t: f64) -> JsValue {
|
||||
let bezier_points: [Vec<Point>; 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<f64>; 2]`.
|
||||
pub fn local_extrema(&self) -> JsValue {
|
||||
let local_extrema: [Vec<f64>; 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<f64>`.
|
||||
pub fn inflections(&self) -> JsValue {
|
||||
let inflections: Vec<f64> = self.0.inflections();
|
||||
to_js_value(inflections)
|
||||
}
|
||||
|
||||
/// The wrapped return type is `Vec<Vec<Point>>`.
|
||||
pub fn de_casteljau_points(&self, t: f64) -> JsValue {
|
||||
let points: Vec<Vec<Point>> = self
|
||||
.0
|
||||
.de_casteljau_points(t)
|
||||
.iter()
|
||||
.map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::<Vec<Point>>())
|
||||
.collect();
|
||||
to_js_value(points)
|
||||
}
|
||||
|
||||
pub fn rotate(&self, angle: f64) -> WasmBezier {
|
||||
WasmBezier(self.0.rotate(angle))
|
||||
}
|
||||
|
||||
fn intersect(&self, curve: &Bezier, error: Option<f64>) -> Vec<f64> {
|
||||
self.0.intersections(curve, error)
|
||||
}
|
||||
|
||||
pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec<f64> {
|
||||
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<f64> {
|
||||
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<f64> {
|
||||
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<Vec<Point>> = self.0.reduce(None).into_iter().map(bezier_to_points).collect();
|
||||
to_js_value(bezier_points)
|
||||
}
|
||||
|
||||
/// The wrapped return type is `Vec<Vec<Point>>`.
|
||||
pub fn offset(&self, distance: f64) -> JsValue {
|
||||
let bezier_points: Vec<Vec<Point>> = self.0.offset(distance).into_iter().map(bezier_to_points).collect();
|
||||
to_js_value(bezier_points)
|
||||
}
|
||||
|
||||
/// The wrapped return type is `Vec<CircleSector>`.
|
||||
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<CircleSector> = 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Point> {
|
||||
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<T: Serialize>(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<Point>`.
|
||||
pub fn get_points(&self) -> JsValue {
|
||||
let points: Vec<Point> = 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<Point>`.
|
||||
pub fn compute_lookup_table(&self, steps: usize) -> JsValue {
|
||||
let table_values: Vec<Point> = self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect();
|
||||
to_js_value(table_values)
|
||||
}
|
||||
|
||||
pub fn derivative(&self) -> Option<WasmBezier> {
|
||||
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<Point>; 2]`.
|
||||
pub fn split(&self, t: f64) -> JsValue {
|
||||
let bezier_points: [Vec<Point>; 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<f64>; 2]`.
|
||||
pub fn local_extrema(&self) -> JsValue {
|
||||
let local_extrema: [Vec<f64>; 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<f64>`.
|
||||
pub fn inflections(&self) -> JsValue {
|
||||
let inflections: Vec<f64> = self.0.inflections();
|
||||
to_js_value(inflections)
|
||||
}
|
||||
|
||||
/// The wrapped return type is `Vec<Vec<Point>>`.
|
||||
pub fn de_casteljau_points(&self, t: f64) -> JsValue {
|
||||
let points: Vec<Vec<Point>> = self
|
||||
.0
|
||||
.de_casteljau_points(t)
|
||||
.iter()
|
||||
.map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::<Vec<Point>>())
|
||||
.collect();
|
||||
to_js_value(points)
|
||||
}
|
||||
|
||||
pub fn rotate(&self, angle: f64) -> WasmBezier {
|
||||
WasmBezier(self.0.rotate(angle))
|
||||
}
|
||||
|
||||
fn intersect(&self, curve: &Bezier, error: Option<f64>) -> Vec<f64> {
|
||||
self.0.intersections(curve, error)
|
||||
}
|
||||
|
||||
pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec<f64> {
|
||||
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<f64> {
|
||||
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<f64> {
|
||||
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<Vec<Point>> = self.0.reduce(None).into_iter().map(bezier_to_points).collect();
|
||||
to_js_value(bezier_points)
|
||||
}
|
||||
|
||||
/// The wrapped return type is `Vec<Vec<Point>>`.
|
||||
pub fn offset(&self, distance: f64) -> JsValue {
|
||||
let bezier_points: Vec<Vec<Point>> = self.0.offset(distance).into_iter().map(bezier_to_points).collect();
|
||||
to_js_value(bezier_points)
|
||||
}
|
||||
|
||||
/// The wrapped return type is `Vec<CircleSector>`.
|
||||
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<CircleSector> = 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ pub const SVG_CLOSE_TAG: &str = "</svg>";
|
|||
// 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 x="{x_pos}" y="{y_pos}" fill="{fill}">{text}</text>"#)
|
||||
|
|
|
|||
Loading…
Reference in New Issue