Bezier-rs: Convert to_svg used in subpaths to match style used in beziers (#854)

Converted subpath::to_svg to Bezier style
This commit is contained in:
Rob Nadal 2022-11-14 22:37:06 -08:00 committed by Keavon Chambers
parent 9c87658ae4
commit 00c8fa83c2
5 changed files with 65 additions and 145 deletions

View File

@ -3,9 +3,7 @@
mod bezier; mod bezier;
mod consts; mod consts;
mod subpath; mod subpath;
mod svg;
mod utils; mod utils;
pub use bezier::*; pub use bezier::*;
pub use subpath::*; pub use subpath::*;
pub use svg::*;

View File

@ -1,6 +1,6 @@
use super::*; use super::*;
use crate::consts::*; use crate::consts::*;
use crate::ToSVGOptions; use std::fmt::Write;
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`. /// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
impl Subpath { impl Subpath {
@ -45,45 +45,59 @@ impl Subpath {
SubpathIter { sub_path: self, index: 0 } SubpathIter { sub_path: self, index: 0 }
} }
/// Returns an SVG representation of the `Subpath`. /// Appends to the `svg` mutable string with an SVG shape representation of the curve.
pub fn to_svg(&self, options: ToSVGOptions) -> String { pub fn curve_to_svg(&self, svg: &mut String, attributes: 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 curve_start_argument = format!("{SVG_ARG_MOVE}{} {}", self[0].anchor.x, self[0].anchor.y);
let mut curve_arguments: Vec<String> = self.iter().map(|bezier| bezier.svg_curve_argument()).collect(); let mut curve_arguments: Vec<String> = self.iter().map(|bezier| bezier.svg_curve_argument()).collect();
if self.closed { if self.closed {
curve_arguments.push(String::from(SVG_ARG_CLOSED)); curve_arguments.push(String::from(SVG_ARG_CLOSED));
} }
let anchor_arguments = options.formatted_anchor_arguments(); let _ = write!(svg, r#"<path d="{} {}" {attributes}/>"#, curve_start_argument, curve_arguments.join(" "));
let anchor_circles = self }
/// 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 handle_lines: Vec<String> = self.iter().filter_map(|bezier| bezier.svg_handle_line_argument()).collect();
let _ = write!(svg, r#"<path d="{}" {attributes}/>"#, handle_lines.join(" "));
}
/// 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 anchors = self
.manipulator_groups .manipulator_groups
.iter() .iter()
.map(|point| format!(r#"<circle cx="{}" cy="{}" {}/>"#, point.anchor.x, point.anchor.y, anchor_arguments)) .map(|point| format!(r#"<circle cx="{}" cy="{}" {attributes}/>"#, point.anchor.x, point.anchor.y))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let _ = write!(svg, "{}", anchors.concat());
}
let handle_point_arguments = options.formatted_handle_point_arguments(); /// Appends to the `svg` mutable string with an SVG shape representation of the handles.
let handle_circles: Vec<String> = self pub fn handles_to_svg(&self, svg: &mut String, attributes: String) {
let handles = self
.manipulator_groups .manipulator_groups
.iter() .iter()
.flat_map(|group| [group.in_handle, group.out_handle]) .flat_map(|group| [group.in_handle, group.out_handle])
.flatten() .flatten()
.map(|handle| format!(r#"<circle cx="{}" cy="{}" {}/>"#, handle.x, handle.y, handle_point_arguments)) .map(|handle| format!(r#"<circle cx="{}" cy="{}" {attributes}/>"#, handle.x, handle.y))
.collect(); .collect::<Vec<String>>();
let _ = write!(svg, "{}", handles.concat());
}
let handle_pieces: Vec<String> = self.iter().filter_map(|bezier| bezier.svg_handle_line_argument()).collect(); /// Returns an SVG representation of the `Subpath`.
/// Appends to the `svg` mutable string with an SVG shape representation that includes the curve, the handle lines, the anchors, and the handles.
format!( pub fn to_svg(&self, svg: &mut String, curve_attributes: String, anchor_attributes: String, handle_attributes: String, handle_line_attributes: String) {
r#"<path d="{} {}" {}/><path d="{}" {}/>{}{}"#, if !curve_attributes.is_empty() {
curve_start_argument, self.curve_to_svg(svg, curve_attributes);
curve_arguments.join(" "), }
options.formatted_curve_arguments(), if !handle_line_attributes.is_empty() {
handle_pieces.join(" "), self.handle_lines_to_svg(svg, handle_line_attributes);
options.formatted_handle_line_arguments(), }
handle_circles.join(""), if !anchor_attributes.is_empty() {
anchor_circles.join(""), self.anchors_to_svg(svg, anchor_attributes);
) }
if !handle_attributes.is_empty() {
self.handles_to_svg(svg, handle_attributes);
}
} }
} }

View File

@ -1,74 +0,0 @@
/// 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 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 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 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 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"),
}
}
}

View File

@ -383,15 +383,8 @@ impl WasmBezier {
pub fn rotate(&self, angle: f64, pivot_x: f64, pivot_y: f64) -> String { pub fn rotate(&self, angle: f64, pivot_x: f64, pivot_y: f64) -> String {
let original_bezier_svg = self.get_bezier_path(); let original_bezier_svg = self.get_bezier_path();
let rotated_bezier = self.0.rotate_about_point(angle, DVec2::new(pivot_x, pivot_y)); let rotated_bezier = self.0.rotate_about_point(angle, DVec2::new(pivot_x, pivot_y));
let empty_string = String::new();
let mut rotated_bezier_svg = String::new(); let mut rotated_bezier_svg = String::new();
rotated_bezier.to_svg( rotated_bezier.to_svg(&mut rotated_bezier_svg, CURVE_ATTRIBUTES.to_string().replace(BLACK, RED), String::new(), String::new(), String::new());
&mut rotated_bezier_svg,
CURVE_ATTRIBUTES.to_string().replace(BLACK, RED),
empty_string.clone(),
empty_string.clone(),
empty_string,
);
let pivot = draw_circle(pivot_x, pivot_y, 3., GRAY, 1.5, WHITE); let pivot = draw_circle(pivot_x, pivot_y, 3., GRAY, 1.5, WHITE);
// Line between pivot and start point on curve // Line between pivot and start point on curve
@ -433,15 +426,8 @@ impl WasmBezier {
let bezier_curve_svg = self.get_bezier_path(); let bezier_curve_svg = self.get_bezier_path();
let empty_string = String::new();
let mut line_svg = String::new(); let mut line_svg = String::new();
line.to_svg( line.to_svg(&mut line_svg, CURVE_ATTRIBUTES.to_string().replace(BLACK, RED), String::new(), String::new(), String::new());
&mut line_svg,
CURVE_ATTRIBUTES.to_string().replace(BLACK, RED),
empty_string.clone(),
empty_string.clone(),
empty_string,
);
let intersections_svg = self let intersections_svg = self
.intersect(&line, None) .intersect(&line, None)
@ -460,15 +446,8 @@ impl WasmBezier {
let bezier_curve_svg = self.get_bezier_path(); let bezier_curve_svg = self.get_bezier_path();
let empty_string = String::new();
let mut quadratic_svg = String::new(); let mut quadratic_svg = String::new();
quadratic.to_svg( quadratic.to_svg(&mut quadratic_svg, CURVE_ATTRIBUTES.to_string().replace(BLACK, RED), String::new(), String::new(), String::new());
&mut quadratic_svg,
CURVE_ATTRIBUTES.to_string().replace(BLACK, RED),
empty_string.clone(),
empty_string.clone(),
empty_string,
);
let intersections_svg = self let intersections_svg = self
.intersect(&quadratic, Some(error)) .intersect(&quadratic, Some(error))
@ -487,15 +466,8 @@ impl WasmBezier {
let bezier_curve_svg = self.get_bezier_path(); let bezier_curve_svg = self.get_bezier_path();
let empty_string = String::new();
let mut cubic_svg = String::new(); let mut cubic_svg = String::new();
cubic.to_svg( cubic.to_svg(&mut cubic_svg, CURVE_ATTRIBUTES.to_string().replace(BLACK, RED), String::new(), String::new(), String::new());
&mut cubic_svg,
CURVE_ATTRIBUTES.to_string().replace(BLACK, RED),
empty_string.clone(),
empty_string.clone(),
empty_string,
);
let intersections_svg = self let intersections_svg = self
.intersect(&cubic, Some(error)) .intersect(&cubic, Some(error))
@ -526,7 +498,6 @@ impl WasmBezier {
} }
pub fn reduce(&self) -> String { pub fn reduce(&self) -> String {
let empty_string = String::new();
let original_curve_svg = self.get_bezier_path(); let original_curve_svg = self.get_bezier_path();
let bezier_curves_svg: String = self let bezier_curves_svg: String = self
.0 .0
@ -538,9 +509,9 @@ impl WasmBezier {
bezier_curve.to_svg( bezier_curve.to_svg(
&mut curve_svg, &mut curve_svg,
CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * index))), CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * index))),
empty_string.clone(), String::new(),
empty_string.clone(), String::new(),
empty_string.clone(), String::new(),
); );
curve_svg curve_svg
}) })
@ -549,7 +520,6 @@ impl WasmBezier {
} }
pub fn offset(&self, distance: f64) -> String { pub fn offset(&self, distance: f64) -> String {
let empty_string = String::new();
let original_curve_svg = self.get_bezier_path(); let original_curve_svg = self.get_bezier_path();
let bezier_curves_svg = self let bezier_curves_svg = self
.0 .0
@ -561,9 +531,9 @@ impl WasmBezier {
bezier_curve.to_svg( bezier_curve.to_svg(
&mut curve_svg, &mut curve_svg,
CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * index))), CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * index))),
empty_string.clone(), String::new(),
empty_string.clone(), String::new(),
empty_string.clone(), String::new(),
); );
curve_svg curve_svg
}) })

View File

@ -1,4 +1,4 @@
use bezier_rs::{ManipulatorGroup, Subpath, ToSVGOptions}; use bezier_rs::{ManipulatorGroup, Subpath};
use glam::DVec2; use glam::DVec2;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -37,11 +37,23 @@ impl WasmSubpath {
} }
pub fn to_svg(&self) -> String { pub fn to_svg(&self) -> String {
format!("{}{}{}", SVG_OPEN_TAG, self.0.to_svg(ToSVGOptions::default()), SVG_CLOSE_TAG) format!("{}{}{}", SVG_OPEN_TAG, self.to_default_svg(), SVG_CLOSE_TAG)
}
fn to_default_svg(&self) -> String {
let mut subpath_svg = String::new();
self.0.to_svg(
&mut subpath_svg,
CURVE_ATTRIBUTES.to_string(),
ANCHOR_ATTRIBUTES.to_string(),
HANDLE_ATTRIBUTES.to_string(),
HANDLE_LINE_ATTRIBUTES.to_string(),
);
subpath_svg
} }
pub fn length(&self) -> String { pub fn length(&self) -> String {
let length_text = draw_text(format!("Length: {:.2}", self.0.length(None)), 5., 193., BLACK); 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) format!("{}{}{}{}", SVG_OPEN_TAG, self.to_default_svg(), length_text, SVG_CLOSE_TAG)
} }
} }