Lottie: import every Dash/Gap pair of a stroke, not only the first dash and the last gap

This commit is contained in:
Marco Arnulfo
2026-10-24 17:37:03 +02:00
parent eb5108786b
commit f17ff8bff5
2 changed files with 51 additions and 12 deletions
+27 -12
View File
@@ -3,8 +3,8 @@
use effectcraft_color::BlendMode;
use effectcraft_keyframe::{Gradient, ShapePath, Value};
use effectcraft_project::build::{self, Ids};
use effectcraft_project::{Node, PropGroup};
use effectcraft_project::build::{self, EXTRA_DASHES, Ids};
use effectcraft_project::{Node, ParamUi, PropGroup};
use serde_json::{Map, Value as Json, json};
use crate::anim::{self, Ex, Im, export_prop, import_prop};
@@ -356,7 +356,7 @@ fn import_blend(j: &Json, g: &mut PropGroup) {
}
}
fn import_stroke(im: &Im, j: &Json, g: &mut PropGroup) {
fn import_stroke(im: &Im, ids: &mut Ids, j: &Json, g: &mut PropGroup) {
set(im, g, "width", j.get("w"), &anim::to_scalar);
if let Some(lc) = int(j, "lc") {
set_enum(g, "cap", lc.saturating_sub(1).clamp(0, 2) as u32);
@@ -374,17 +374,32 @@ fn import_stroke(im: &Im, j: &Json, g: &mut PropGroup) {
if let Some(list) = j.get("d").and_then(Json::as_array)
&& let Some(d) = g.sub_mut("dashes")
{
let mut seen_dash = false;
// The n-th `d` and `g` are the n-th Dash/Gap pair; the extra pairs are created as the Dashes "+" does.
let (mut dashes, mut gaps) = (0, 0);
for e in list {
let m = match e.get("n").and_then(Json::as_str) {
Some("d") if !seen_dash => {
seen_dash = true;
"dash"
let (n, pair) = match e.get("n").and_then(Json::as_str) {
Some("d") => (&mut dashes, 0),
Some("g") => (&mut gaps, 1),
Some("o") => {
set(im, d, "offset", e.get("v"), &anim::to_scalar);
continue;
}
Some("g") => "gap",
Some("o") => "offset",
_ => continue,
};
let k = *n;
*n += 1;
let m = match k {
0 => ["dash", "gap"][pair],
_ => {
let Some(&(dm, gm)) = EXTRA_DASHES.get(k - 1) else { continue };
if d.get(dm).is_none() {
let at = d.children.iter().position(|c| c.match_id() == "offset").unwrap_or(d.children.len());
d.children.insert(at, ids.prop(gm, &format!("Gap {}", k + 1), Value::Scalar(0.0)).with_ui(ParamUi::Pixels).into());
d.children.insert(at, ids.prop(dm, &format!("Dash {}", k + 1), Value::Scalar(0.0)).with_ui(ParamUi::Pixels).into());
}
[dm, gm][pair]
}
};
set(im, d, m, e.get("v"), &anim::to_scalar);
}
}
@@ -474,7 +489,7 @@ pub(crate) fn import_items(im: &mut Im, ids: &mut Ids, items: &[Json]) -> Vec<Pr
let mut g = build::shape_stroke(ids, [1.0, 1.0, 1.0, 1.0], 2.0);
set(im, &mut g, "color", j.get("c"), &anim::to_color);
set(im, &mut g, "opacity", j.get("o"), &anim::to_scalar);
import_stroke(im, j, &mut g);
import_stroke(im, ids, j, &mut g);
import_blend(j, &mut g);
g
}
@@ -501,7 +516,7 @@ pub(crate) fn import_items(im: &mut Im, ids: &mut Ids, items: &[Json]) -> Vec<Pr
set_enum(&mut g, "rule", 1);
}
} else {
import_stroke(im, j, &mut g);
import_stroke(im, ids, j, &mut g);
}
import_blend(j, &mut g);
g
+24
View File
@@ -407,6 +407,30 @@ fn offset_paths_miter_uses_animatable_lottie_value() {
assert_eq!(second.get("miter").unwrap().value, Value::Scalar(6.0));
}
#[test]
fn extra_dash_gap_pairs_survive_a_roundtrip() {
let (mut p, cid) = setup();
let l = shape_layer(&mut p, cid, |ids| {
let mut stroke = build::shape_stroke(ids, [0.0, 0.0, 1.0, 1.0], 3.0);
let d = stroke.sub_mut("dashes").unwrap();
d.get_mut("dash").unwrap().value = Value::Scalar(10.0);
d.get_mut("gap").unwrap().value = Value::Scalar(5.0);
let at = d.children.iter().position(|n| n.match_id() == "offset").unwrap();
d.children.insert(at, ids.prop("gap2", "Gap 2", Value::Scalar(3.0)).into());
d.children.insert(at, ids.prop("dash2", "Dash 2", Value::Scalar(20.0)).into());
let rect = build::shape_rect(ids, [60.0, 40.0], [0.0, 0.0], 0.0);
vec![build::shape_group(ids, "G", vec![rect, stroke])]
});
push(&mut p, cid, l);
let (q, nc, _, _) = roundtrip(&p, cid, &ExportOptions::default());
let c = q.comp(nc).unwrap();
let g = c.layers[0].props.sub("contents").unwrap().groups().next().unwrap();
let stroke = g.sub("contents").unwrap().groups().find(|g| g.match_id == "stroke").unwrap();
let d = stroke.sub("dashes").unwrap();
let v = |m: &str| d.get(m).map(|p| p.value.as_f64());
assert_eq!((v("dash"), v("gap"), v("dash2"), v("gap2")), (Some(10.0), Some(5.0), Some(20.0), Some(3.0)));
}
// ---------------------------------------------------------------- layers
#[test]