mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 05:42:38 +00:00
svg import: bound the layer stack a filter chain builds (#2252)
This commit is contained in:
1 parent
6e9582c0a0
commit
64e91207f1
2 files changed
+25
-5
No files matched your search
@@ -70,10 +70,15 @@ impl Img {
|
||||
}
|
||||
}
|
||||
|
||||
fn stack(bottom: Img, top: Img) -> Img {
|
||||
/// The most layers a stack may hold. Results that name earlier results can double a stack at each
|
||||
/// of up to 64 primitives; real shadow chains hold a handful of layers, and a longer stack isn't
|
||||
/// an effect list anyone wrote.
|
||||
const MAX_STACK: usize = 64;
|
||||
|
||||
fn stack(bottom: Img, top: Img) -> Option<Img> {
|
||||
let mut v = bottom.into_layers();
|
||||
v.extend(top.into_layers());
|
||||
Img::Stack(v)
|
||||
(v.len() <= MAX_STACK).then_some(Img::Stack(v))
|
||||
}
|
||||
|
||||
/// The effects `filters` amount to on a layer whose user space maps to the page by `world`; `None`
|
||||
@@ -148,13 +153,16 @@ fn primitive(k: &Kind, get: &dyn Fn(&Input) -> Option<Img>) -> Option<Img> {
|
||||
let mut v = Vec::new();
|
||||
for i in m.inputs() {
|
||||
v.extend(get(i)?.into_layers());
|
||||
if v.len() > MAX_STACK {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Img::Stack(v)
|
||||
}
|
||||
Kind::Blend(b) => {
|
||||
let (top, bottom) = (get(b.input1())?, get(b.input2())?);
|
||||
match b.mode() {
|
||||
usvg::BlendMode::Normal => stack(bottom, top),
|
||||
usvg::BlendMode::Normal => stack(bottom, top)?,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
@@ -162,7 +170,7 @@ fn primitive(k: &Kind, get: &dyn Fn(&Input) -> Option<Img>) -> Option<Img> {
|
||||
let sigma = (f64::from(d.std_dev_x().get()) + f64::from(d.std_dev_y().get())) / 2.0;
|
||||
let s = Sh { dx: f64::from(d.dx()), dy: f64::from(d.dy()), sigma, color: color(d.color(), d.opacity().get()), ..Sh::alpha() };
|
||||
match get(d.input())? {
|
||||
Img::Layer(b) => stack(Img::Shadow(s), Img::Layer(b)),
|
||||
Img::Layer(b) => stack(Img::Shadow(s), Img::Layer(b))?,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
@@ -190,7 +198,7 @@ fn tint(v: &[f32], s: Sh) -> Option<Img> {
|
||||
fn composite(a: Img, b: Img, op: CompositeOperator) -> Option<Img> {
|
||||
let tinted = |s: Sh, c: Color| Sh { color: Color::new(c.r, c.g, c.b, (c.a * s.color.a).clamp(0.0, 1.0)), ..s };
|
||||
Some(match (op, a, b) {
|
||||
(CompositeOperator::Over, a, b) => stack(b, a),
|
||||
(CompositeOperator::Over, a, b) => stack(b, a)?,
|
||||
(CompositeOperator::In, Img::Flood(c), Img::Shadow(s)) | (CompositeOperator::In, Img::Shadow(s), Img::Flood(c)) => Img::Shadow(tinted(s, c)),
|
||||
// A flood cut to everything but a shadow, then cut to the layer: an inner shadow.
|
||||
(CompositeOperator::Out, Img::Flood(c), Img::Shadow(s)) if !s.inner && !s.inverted => Img::Shadow(Sh { inverted: true, ..tinted(s, c) }),
|
||||
|
||||
@@ -228,3 +228,15 @@ fn coloured_copies_of_a_blurred_layer_arent_shadows() {
|
||||
assert!(!notes.is_empty(), "{filter}: reported");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_doubling_chain_of_merges_is_refused_not_expanded() {
|
||||
// Each merge holds the previous result twice: 2^40 layers if expanded.
|
||||
let mut f = String::from(r##"<feMerge result="r0"><feMergeNode in="SourceGraphic"/><feMergeNode in="SourceGraphic"/></feMerge>"##);
|
||||
for i in 1..40 {
|
||||
f.push_str(&format!(r##"<feMerge result="r{i}"><feMergeNode in="r{}"/><feMergeNode in="r{}"/></feMerge>"##, i - 1, i - 1));
|
||||
}
|
||||
let (fx, notes) = effects(&f, "");
|
||||
assert!(fx.is_empty(), "{fx:?}");
|
||||
assert!(!notes.is_empty());
|
||||
}
|
||||
Reference in new issue
Block a user