mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 07:42:42 +00:00
Live Paint: Gap Options fits an edge's dashes and arrowheads to each piece it cuts
When Gap Options cut an edge in two, each piece took the old edge's stroke rows unchanged: both arrowheads, the dash phase of the edge's start, and a brush or width profile spread over the whole edge. Each piece now takes those rows fitted to its part of the old edge, as Make fits a source path's rows to the edges cut from it.
This commit is contained in:
@@ -749,8 +749,9 @@ fn build_children(d: &mut Document, sources: Vec<Arc<Node>>, max_gap: Option<f64
|
||||
/// Give the faces and edges of `g`, a Live Paint group made again from the sources of `old`, the
|
||||
/// look of `old`'s face or edge where they lie: each face takes everything but the outline (its
|
||||
/// appearance, none included, its transparency and the rest) of the old face its inside point is
|
||||
/// in, and each edge that of the old edge its middle lies on. Faces and edges where `old` had none
|
||||
/// keep the paint of their sources.
|
||||
/// in, and each edge that of the old edge its middle lies on, with the old edge's stroke rows fitted
|
||||
/// to the part of it the new edge runs, as [`fit_to_edge`] fits a source's rows. Faces and edges
|
||||
/// where `old` had none keep the paint of their sources.
|
||||
fn repaint_from(g: &mut Node, old: &Node) {
|
||||
/// How far an edge's middle may be from the old edge it lies on.
|
||||
const ON_EDGE: f64 = 1e-3;
|
||||
@@ -773,6 +774,20 @@ fn repaint_from(g: &mut Node, old: &Node) {
|
||||
n.id = c.id;
|
||||
n.kind = c.kind.clone();
|
||||
n.bbox_angle = c.bbox_angle;
|
||||
// An edge cut from the old one takes the old edge's dashes, arrowheads, width profile
|
||||
// and brush as they fall on its part of the old edge.
|
||||
if c.name.as_deref() == Some(EDGE_NAME)
|
||||
&& n.appearance.items.iter().any(depends_on_place)
|
||||
&& let Some(old_path) = was.path_data()
|
||||
{
|
||||
let tracks: Vec<Track> = old_path.subpaths.iter().map(Track::new).collect();
|
||||
let run = n.path_data_mut().and_then(|path| edge_run(&tracks, path));
|
||||
let mut a = (*n.appearance).clone();
|
||||
for r in &mut a.items {
|
||||
fit_to_edge(r, run);
|
||||
}
|
||||
n.appearance = a.into();
|
||||
}
|
||||
*c = Arc::new(n);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2753,6 +2753,50 @@ fn live_paint_make_fits_dashes_and_arrowheads_to_each_edge() {
|
||||
assert_eq!(edges, vec![rows]);
|
||||
}
|
||||
|
||||
/// When Gap Options cuts an edge in two, each piece keeps the edge's dashes going on from where it
|
||||
/// starts along the edge, an arrowhead only at the end of the edge it ends at, and no brush.
|
||||
#[test]
|
||||
fn live_paint_gap_options_fits_an_edge_look_to_each_piece() {
|
||||
use vectorcraft_doc::{Arrowhead, Dash};
|
||||
let mut s = session();
|
||||
let l = line(&mut s, (0.0, 50.0), (100.0, 50.0));
|
||||
let stub = line(&mut s, (50.0, 0.0), (50.0, 48.0));
|
||||
let row = stroke_row(Paint::solid(Color::BLACK), 2.0, |st| {
|
||||
st.dash = Some(Dash { pattern: vec![10.0, 5.0], offset: 2.0, align_corners: false });
|
||||
st.start_arrow = Some(Arrowhead::Triangle);
|
||||
st.end_arrow = Some(Arrowhead::Circle);
|
||||
st.brush = Some("Charcoal".into());
|
||||
});
|
||||
let rows = set_rows(&mut s, l, vec![row]);
|
||||
s.execute("select.set", &json!({"ids": [l.0, stub.0]})).unwrap();
|
||||
let g = NodeId(s.execute("livePaint.make", &json!({})).unwrap()["id"].as_u64().unwrap());
|
||||
let dashed = |s: &Session| {
|
||||
let mut out: Vec<(f64, f64, vectorcraft_doc::StrokeLayer)> = vectorcraft_tools::builder::edges(&node(s, g))
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
let st = e.appearance.stroke().filter(|st| st.dash.is_some())?.clone();
|
||||
let sp = e.path_data()?.subpaths.first()?;
|
||||
Some(((sp.anchors.first()?.p.x * 1e3).round() / 1e3, (sp.anchors.last()?.p.x * 1e3).round() / 1e3, st))
|
||||
})
|
||||
.collect();
|
||||
out.sort_by(|p, q| p.0.total_cmp(&q.0));
|
||||
out
|
||||
};
|
||||
let before = dashed(&s);
|
||||
assert_eq!(before.len(), 1, "nothing cuts the line yet");
|
||||
assert_eq!(vec![AppearanceItem::Stroke(before[0].2.clone())], rows);
|
||||
let r = s.execute("livePaint.gapOptions", &json!({"id": g.0, "closeGaps": true})).unwrap();
|
||||
assert_eq!(r["closed"], json!(1));
|
||||
let pieces = dashed(&s);
|
||||
let ends: Vec<(f64, f64)> = pieces.iter().map(|p| (p.0, p.1)).collect();
|
||||
assert_eq!(ends, vec![(0.0, 50.0), (50.0, 100.0)], "the line is cut where the gap closes");
|
||||
let offsets: Vec<f64> = pieces.iter().map(|p| (p.2.dash.as_ref().unwrap().offset * 1e3).round() / 1e3).collect();
|
||||
assert_eq!(offsets, vec![2.0, 52.0], "the dashes go on along the line");
|
||||
let arrows: Vec<_> = pieces.iter().map(|p| (p.2.start_arrow.clone(), p.2.end_arrow.clone())).collect();
|
||||
assert_eq!(arrows, vec![(Some(Arrowhead::Triangle), None), (None, Some(Arrowhead::Circle))]);
|
||||
assert!(pieces.iter().all(|p| p.2.brush.is_none()), "a brush spreads over the whole line");
|
||||
}
|
||||
|
||||
/// The edge Make builds from a closed path that nothing cuts starts at the anchor the path starts
|
||||
/// at and runs the way the path does, whichever anchor that is, so its dashes and brush lie where
|
||||
/// they do on the path, and it keeps the path's stroke row as it is.
|
||||
|
||||
+2
-1
@@ -3614,7 +3614,8 @@ already walks Image Trace. Short agent entry points:
|
||||
the path (Align Dashes to Corners is turned off on it), the arrowheads stay only at the path's own ends, and a width
|
||||
profile or brush stays only on an edge that runs the whole path. A gradient applied along a stroke starts again on
|
||||
each cut edge, and an inside or outside stroke is drawn centered on an edge that is an open piece of a closed path.
|
||||
A face or edge given no such row has one unpainted row. Rows that do not draw stay with the stored sources, which
|
||||
When `livePaint.gapOptions` cuts an edge into pieces, each piece takes the edge's rows fitted to its part of the
|
||||
edge in the same way. A face or edge given no such row has one unpainted row. Rows that do not draw stay with the stored sources, which
|
||||
`livePaint.release` returns unchanged. The source's object effects and the strokes' pen pressure are not carried.
|
||||
Expand keeps those face fills.
|
||||
`livePaint.expand` keeps each face and edge that draws, with all its fill and stroke rows: it is shown, its opacity
|
||||
|
||||
Reference in New Issue
Block a user