mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 10:17:02 -06:00
The characters' stroke takes Align Stroke like an object stroke: CharStyle keeps stroke_align (written only when not centred), stroke.set and text.setRangeStyle strokeOptions apply align to type, and the Stroke panel enables the buttons for type. Canvas, PDF and EPS draw it around the glyphs through stroke_layer(); SVG keeps the text live and writes the aligned stroke on the glyph outlines, clipped or masked. Type's visual bounds grow with its character strokes.
114 lines
5.1 KiB
Rust
114 lines
5.1 KiB
Rust
//! Stroke panel reach: `stroke.set` on groups, what the panel shows for a selection (mixed values,
|
|
//! Align Stroke), Units > Stroke, and the stroke options `document.inspect` reports.
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_doc::{NodeId, NodeKind, Unit};
|
|
|
|
use super::*;
|
|
|
|
fn session() -> Session {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
|
s
|
|
}
|
|
|
|
fn run(s: &mut Session, id: &str, p: Value) -> Value {
|
|
s.execute(id, &p).unwrap_or_else(|e| panic!("{id}: {e}"))
|
|
}
|
|
|
|
fn rect(s: &mut Session, x: f64) -> NodeId {
|
|
NodeId(run(s, "shape.rectangle", json!({"x": x, "y": 10, "width": 40, "height": 40}))["id"].as_u64().unwrap())
|
|
}
|
|
|
|
fn select(s: &mut Session, ids: &[NodeId]) {
|
|
run(s, "select.set", json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>() }));
|
|
}
|
|
|
|
#[test]
|
|
fn stroke_set_on_a_group_skips_images_and_symbol_instances() {
|
|
let mut s = session();
|
|
let (a, b, c) = (rect(&mut s, 10.0), rect(&mut s, 60.0), rect(&mut s, 110.0));
|
|
select(&mut s, &[b]);
|
|
let image = NodeId(run(&mut s, "object.rasterize", json!({}))["id"].as_u64().unwrap());
|
|
select(&mut s, &[c]);
|
|
let instance = NodeId(run(&mut s, "symbol.new", json!({"name": "Box"}))["id"].as_u64().unwrap());
|
|
select(&mut s, &[a, image, instance]);
|
|
run(&mut s, "object.group", json!({}));
|
|
run(&mut s, "stroke.set", json!({"weight": 5}));
|
|
let d = &s.doc().unwrap().doc;
|
|
assert_eq!(d.node(a).unwrap().appearance.stroke_width(), 5.0);
|
|
for id in [image, instance] {
|
|
let n = d.node(id).unwrap();
|
|
assert!(matches!(n.kind, NodeKind::Image(_) | NodeKind::SymbolInstance { .. }));
|
|
assert!(n.appearance.stroke().is_none(), "{:?} gains no stroke", n.kind_label());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn mixed_values_and_align_stroke_follow_the_selection() {
|
|
let mut s = session();
|
|
let (a, b) = (rect(&mut s, 10.0), rect(&mut s, 60.0));
|
|
run(&mut s, "stroke.set", json!({"ids": [b.0], "weight": 3, "join": "round"}));
|
|
select(&mut s, &[a, b]);
|
|
let m = s.active().unwrap().stroke_mixed();
|
|
assert!(m.weight && m.join && !m.cap && !m.miter_limit && !m.align && !m.dash && m.can_align, "{m:?}");
|
|
// An open path in the selection can't take an inside or outside stroke; type can (its
|
|
// characters' stroke goes around the glyphs).
|
|
let line = NodeId(run(&mut s, "shape.line", json!({"x1": 10, "y1": 100, "x2": 90, "y2": 100}))["id"].as_u64().unwrap());
|
|
select(&mut s, &[a, line]);
|
|
assert!(!s.active().unwrap().stroke_mixed().can_align);
|
|
let text = NodeId(run(&mut s, "text.create", json!({"x": 10, "y": 200, "text": "Hi"}))["id"].as_u64().unwrap());
|
|
select(&mut s, &[text]);
|
|
assert!(s.active().unwrap().stroke_mixed().can_align);
|
|
select(&mut s, &[text, line]);
|
|
assert!(!s.active().unwrap().stroke_mixed().can_align);
|
|
// Grouped closed paths can.
|
|
select(&mut s, &[a, b]);
|
|
run(&mut s, "object.group", json!({}));
|
|
let m = s.active().unwrap().stroke_mixed();
|
|
assert!(m.can_align && m.weight);
|
|
assert_eq!(s.shown_stroke().map(|st| st.width), Some(1.0), "a group shows its first painted object's stroke");
|
|
}
|
|
|
|
#[test]
|
|
fn stroke_unit_follows_the_preference() {
|
|
let mut s = session();
|
|
assert_eq!(s.stroke_unit(), Unit::Points);
|
|
s.prefs.units_stroke = "millimeters".into();
|
|
assert_eq!(s.stroke_unit(), Unit::Millimeters);
|
|
assert_eq!(s.stroke_unit().format(1.0), "0.3528 mm");
|
|
}
|
|
|
|
#[test]
|
|
fn inspect_reports_stroke_options() {
|
|
let mut s = session();
|
|
let line = run(&mut s, "shape.line", json!({"x1": 10, "y1": 100, "x2": 90, "y2": 100}))["id"].as_u64().unwrap();
|
|
run(
|
|
&mut s,
|
|
"stroke.set",
|
|
json!({"ids": [line], "cap": "round", "join": "bevel", "miterLimit": 4, "dash": [6, 3], "alignDashes": true, "endArrow": "Triangle", "arrowAlign": "tip", "profile": "lens"}),
|
|
);
|
|
let doc = run(&mut s, "document.inspect", json!({}));
|
|
let o = &doc["layers"][0]["children"][0]["strokeOptions"];
|
|
assert_eq!(o["cap"], "round");
|
|
assert_eq!(o["join"], "bevel");
|
|
assert_eq!(o["miterLimit"], 4.0);
|
|
assert_eq!(o["align"], "center");
|
|
assert_eq!(o["dash"], json!([6.0, 3.0]));
|
|
assert_eq!(o["alignDashes"], true);
|
|
assert_eq!((o["startArrow"].clone(), o["endArrow"].clone(), o["arrowAlign"].clone()), (Value::Null, json!("Triangle"), json!("tip")));
|
|
assert_eq!(o["profile"], "lens");
|
|
// The options read back into stroke.set unchanged.
|
|
let mut q = o.as_object().unwrap().clone();
|
|
q.insert("ids".into(), json!([line]));
|
|
let before = s.doc().unwrap().doc.node(NodeId(line)).unwrap().appearance.clone();
|
|
run(&mut s, "stroke.set", Value::Object(q));
|
|
assert_eq!(s.doc().unwrap().doc.node(NodeId(line)).unwrap().appearance, before);
|
|
// Type reports its characters' stroke.
|
|
run(&mut s, "text.create", json!({"x": 10, "y": 200, "text": "Hi"}));
|
|
run(&mut s, "stroke.set", json!({"join": "round"}));
|
|
let doc = run(&mut s, "document.inspect", json!({}));
|
|
let text = doc["layers"][0]["children"].as_array().unwrap().iter().find(|c| c["kind"] == "Text" || c.get("text").is_some()).unwrap().clone();
|
|
assert_eq!(text["strokeOptions"]["join"], "round", "{text}");
|
|
}
|