mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 04:37:29 -06:00
@@ -86,6 +86,7 @@ pub use solids::{
|
||||
EXTRUDE, INFLATE, ROTATE, ROTATION_PRESETS, SOLIDS, extrude_options, has_solid, inflate_options, is_three_d, solid_art, validate_solid,
|
||||
validate_three_d, view_options,
|
||||
};
|
||||
pub use stylize::{ScribbleSetting, scribble_setting_id, scribble_settings};
|
||||
pub use trap::TRAP;
|
||||
pub use warp::{WarpStyle, warp_point};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Convert to Shape, Round Corners and Scribble.
|
||||
|
||||
use serde_json::Value;
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_geom::kurbo;
|
||||
use vectorcraft_geom::shapes::{self, KAPPA};
|
||||
use vectorcraft_geom::{Affine, Anchor, PathData, Point, Rect, SubPath, corners};
|
||||
@@ -144,6 +144,54 @@ fn crosses_outline(p: Point, q: Point, polys: &[Vec<Point>]) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// One named Settings entry in Effect › Stylize › Scribble (public feature names; parameter sets
|
||||
/// are chosen for VectorCraft).
|
||||
pub struct ScribbleSetting {
|
||||
pub id: &'static str,
|
||||
pub label: &'static str,
|
||||
pub params: Value,
|
||||
}
|
||||
|
||||
/// The Scribble dialog's Settings presets, in menu order.
|
||||
pub fn scribble_settings() -> &'static [ScribbleSetting] {
|
||||
static PRESETS: std::sync::OnceLock<Vec<ScribbleSetting>> = std::sync::OnceLock::new();
|
||||
PRESETS.get_or_init(|| {
|
||||
[
|
||||
("default", "Default", json!({"angle": 30.0, "overlap": 0.0, "overlapVariation": 0.5, "strokeWidth": 3.0, "curviness": 5.0, "curvinessVariation": 0.0, "spacing": 5.0, "spacingVariation": 0.5})),
|
||||
("childlike", "Childlike", json!({"angle": 45.0, "overlap": 2.0, "overlapVariation": 2.0, "strokeWidth": 2.0, "curviness": 15.0, "curvinessVariation": 30.0, "spacing": 4.0, "spacingVariation": 1.5})),
|
||||
("dense", "Dense", json!({"angle": 30.0, "overlap": 0.0, "overlapVariation": 0.0, "strokeWidth": 3.0, "curviness": 0.0, "curvinessVariation": 0.0, "spacing": 2.0, "spacingVariation": 0.0})),
|
||||
("loose", "Loose", json!({"angle": 30.0, "overlap": 0.0, "overlapVariation": 1.0, "strokeWidth": 2.0, "curviness": 20.0, "curvinessVariation": 40.0, "spacing": 12.0, "spacingVariation": 3.0})),
|
||||
("moire", "Moire", json!({"angle": 0.0, "overlap": 0.0, "overlapVariation": 0.0, "strokeWidth": 1.0, "curviness": 80.0, "curvinessVariation": 10.0, "spacing": 3.0, "spacingVariation": 0.5})),
|
||||
("sharp", "Sharp", json!({"angle": 30.0, "overlap": 0.0, "overlapVariation": 0.0, "strokeWidth": 2.0, "curviness": 0.0, "curvinessVariation": 0.0, "spacing": 3.0, "spacingVariation": 0.0})),
|
||||
("sketch", "Sketch", json!({"angle": 30.0, "overlap": -1.0, "overlapVariation": 0.5, "strokeWidth": 2.0, "curviness": 10.0, "curvinessVariation": 15.0, "spacing": 6.0, "spacingVariation": 1.0})),
|
||||
("snarl", "Snarl", json!({"angle": 15.0, "overlap": 3.0, "overlapVariation": 3.0, "strokeWidth": 4.0, "curviness": 40.0, "curvinessVariation": 50.0, "spacing": 8.0, "spacingVariation": 4.0})),
|
||||
("swash", "Swash", json!({"angle": 60.0, "overlap": 5.0, "overlapVariation": 2.0, "strokeWidth": 5.0, "curviness": 30.0, "curvinessVariation": 20.0, "spacing": 10.0, "spacingVariation": 2.0})),
|
||||
("tight", "Tight", json!({"angle": 30.0, "overlap": 0.0, "overlapVariation": 0.0, "strokeWidth": 3.0, "curviness": 5.0, "curvinessVariation": 0.0, "spacing": 2.0, "spacingVariation": 0.2})),
|
||||
("zigzag", "Zigzag", json!({"angle": 90.0, "overlap": 0.0, "overlapVariation": 0.0, "strokeWidth": 3.0, "curviness": 0.0, "curvinessVariation": 0.0, "spacing": 8.0, "spacingVariation": 0.0})),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(id, label, params)| ScribbleSetting { id, label, params })
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// Which Settings preset matches `params` (seed ignored), if any.
|
||||
pub fn scribble_setting_id(params: &Value) -> Option<&'static str> {
|
||||
let obj = params.as_object()?;
|
||||
scribble_settings().iter().find(|p| p.params.as_object().is_some_and(|want| scribble_params_match(obj, want))).map(|p| p.id)
|
||||
}
|
||||
|
||||
fn scribble_params_match(have: &serde_json::Map<String, Value>, want: &serde_json::Map<String, Value>) -> bool {
|
||||
want.iter().all(|(k, v)| have.get(k).is_some_and(|a| scribble_param_eq(a, v)))
|
||||
}
|
||||
|
||||
fn scribble_param_eq(a: &Value, b: &Value) -> bool {
|
||||
match (a.as_f64(), b.as_f64()) {
|
||||
(Some(x), Some(y)) => (x - y).abs() < 1e-6,
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
/// Scribble (simplified): a zig-zag hatching across the filled area at `angle`, one per region
|
||||
/// of the shape, outlined to `strokeWidth` so the fill paints it. `overlapVariation` moves each
|
||||
/// line's ends along it by up to that many points, `spacingVariation` moves them across it, and
|
||||
|
||||
@@ -925,6 +925,17 @@ fn scribble_reads_its_earlier_single_variation() {
|
||||
assert!(default_params("stylize.scribble").unwrap().get("variation").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scribble_settings_presets_are_distinct_and_recognized() {
|
||||
let presets = crate::scribble_settings();
|
||||
assert_eq!(presets.len(), 11);
|
||||
assert_eq!(presets[0].id, "default");
|
||||
assert_eq!(crate::scribble_setting_id(&presets[0].params), Some("default"));
|
||||
assert_eq!(crate::scribble_setting_id(&presets[9].params), Some("tight"));
|
||||
let custom = json!({"angle": 30.0, "overlap": 0.0, "overlapVariation": 0.5, "strokeWidth": 3.0, "curviness": 5.0, "curvinessVariation": 0.0, "spacing": 4.0, "spacingVariation": 0.5});
|
||||
assert_eq!(crate::scribble_setting_id(&custom), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scribble_angle_is_stored_above_minus_180_and_up_to_180() {
|
||||
for (given, stored) in [(270.0, -90.0), (-180.0, 180.0), (180.0, 180.0), (540.0, 180.0), (-270.0, 90.0), (-200.0, 160.0)] {
|
||||
|
||||
Reference in New Issue
Block a user