mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 21:44:57 +00:00
SVG export: masked groups span their region only for effects that are written
A fill or stroke that the export leaves out (hidden, unpainted or zero width) no longer adds the hidden rectangle for an effect on it. An instance of a dynamic symbol is checked in its overridden art, so an override that paints a fill or stroke with an effect still adds it.
This commit is contained in:
@@ -449,17 +449,24 @@ impl Writer<'_> {
|
||||
|
||||
/// Do `n`, its fills and strokes, its members at any depth, its symbols' art or the art its
|
||||
/// live objects are written as have a visible raster effect (shadow, glow, blur, feather)?
|
||||
/// Objects that aren't written (hidden ones, unless the options keep them) are skipped. The
|
||||
/// walk keeps its own stack, and looks at each symbol's art once.
|
||||
/// Objects that aren't written (hidden ones, unless the options keep them) are skipped, and so
|
||||
/// are fills and strokes that [`Self::shape`] doesn't write (hidden, unpainted or zero-width).
|
||||
/// An instance with overrides is looked at in the art it is written as
|
||||
/// ([`vectorcraft_brush::instance_art`]). The walk keeps its own stack, and looks at each
|
||||
/// symbol's art once, and at each instance's overridden art once.
|
||||
fn holds_raster(&self, n: &Node) -> bool {
|
||||
let mut stack: Vec<std::sync::Arc<Node>> = vec![];
|
||||
let mut symbols = std::collections::HashSet::new();
|
||||
let mut symbols: std::collections::HashSet<(String, Option<vectorcraft_doc::NodeId>)> = std::collections::HashSet::new();
|
||||
let paints = |i: &AppearanceItem| match i {
|
||||
AppearanceItem::Fill(f) => f.visible && !f.paint.is_none(),
|
||||
AppearanceItem::Stroke(s) => s.visible && !s.paint.is_none() && s.width > 0.0,
|
||||
};
|
||||
// Whether `n` itself has one; else its members, symbol art or expanded art go on the stack.
|
||||
let mut visit = |n: &Node, stack: &mut Vec<std::sync::Arc<Node>>| {
|
||||
if !(n.visible || self.opts.hidden_layers) {
|
||||
return false;
|
||||
}
|
||||
if has_raster(&n.appearance.effects) || n.appearance.items.iter().any(|i| has_raster(i.effects())) {
|
||||
if has_raster(&n.appearance.effects) || n.appearance.items.iter().any(|i| paints(i) && has_raster(i.effects())) {
|
||||
return true;
|
||||
}
|
||||
// A live object is written as its expanded art ([`Self::node_body`]), which holds no
|
||||
@@ -471,11 +478,13 @@ impl Writer<'_> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind
|
||||
&& symbols.insert(symbol.clone())
|
||||
&& let Some(s) = self.doc.symbols.iter().find(|s| s.name == *symbol)
|
||||
{
|
||||
stack.push(s.art.clone());
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind {
|
||||
let custom = !n.extras().symbol_overrides.is_empty();
|
||||
if symbols.insert((symbol.clone(), custom.then_some(n.id)))
|
||||
&& let Some(s) = self.doc.symbols.iter().find(|s| s.name == *symbol)
|
||||
{
|
||||
stack.push(if custom { std::sync::Arc::new(vectorcraft_brush::instance_art(&s.art, n)) } else { s.art.clone() });
|
||||
}
|
||||
}
|
||||
stack.extend(n.children().into_iter().flatten().cloned());
|
||||
false
|
||||
|
||||
@@ -261,9 +261,12 @@ fn a_masked_group_spans_its_region_when_its_art_paints_past_its_shapes() {
|
||||
InGroup,
|
||||
InNestedGroup,
|
||||
FillEffect,
|
||||
HiddenFill,
|
||||
UnpaintedStroke,
|
||||
HiddenInGroup,
|
||||
Brushed,
|
||||
InSymbol,
|
||||
OverriddenSymbol,
|
||||
InBlend,
|
||||
}
|
||||
use Case::*;
|
||||
@@ -275,11 +278,17 @@ fn a_masked_group_spans_its_region_when_its_art_paints_past_its_shapes() {
|
||||
(InNestedGroup, true, true),
|
||||
(FillEffect, true, true),
|
||||
(FillEffect, false, false),
|
||||
// A fill or stroke that isn't written doesn't count.
|
||||
(HiddenFill, true, false),
|
||||
(UnpaintedStroke, true, false),
|
||||
(HiddenInGroup, true, false),
|
||||
(Brushed, false, false),
|
||||
(Brushed, true, true),
|
||||
(InSymbol, true, true),
|
||||
(InSymbol, false, false),
|
||||
// An unpainted fill in a dynamic symbol's art that the instance paints counts.
|
||||
(OverriddenSymbol, true, true),
|
||||
(OverriddenSymbol, false, false),
|
||||
(InBlend, true, true),
|
||||
(InBlend, false, false),
|
||||
] {
|
||||
@@ -290,15 +299,28 @@ fn a_masked_group_spans_its_region_when_its_art_paints_past_its_shapes() {
|
||||
stroke.brush = Some("Bristle Round".into());
|
||||
red.appearance.items.push(vectorcraft_doc::AppearanceItem::Stroke(stroke));
|
||||
}
|
||||
if matches!(case, UnpaintedStroke) {
|
||||
red.appearance.items.push(vectorcraft_doc::AppearanceItem::Stroke(vectorcraft_doc::StrokeLayer::new(Paint::None, 8.0)));
|
||||
}
|
||||
if shadowed {
|
||||
match case {
|
||||
FillEffect => red.appearance.items[0].effects_mut().push(drop_shadow()),
|
||||
FillEffect | HiddenFill | OverriddenSymbol => red.appearance.items[0].effects_mut().push(drop_shadow()),
|
||||
UnpaintedStroke => red.appearance.items.last_mut().unwrap().effects_mut().push(drop_shadow()),
|
||||
_ => red.appearance.effects.push(drop_shadow()),
|
||||
}
|
||||
}
|
||||
if matches!(case, HiddenFill)
|
||||
&& let vectorcraft_doc::AppearanceItem::Fill(f) = &mut red.appearance.items[0]
|
||||
{
|
||||
f.visible = false;
|
||||
}
|
||||
if matches!(case, OverriddenSymbol) {
|
||||
red.appearance.set_fill(Paint::None);
|
||||
}
|
||||
let group = |d: &mut Document, n: Node| Node::group(d.alloc_id(), vec![n.into()]);
|
||||
let mut masked = match case {
|
||||
Alone | FillEffect => red,
|
||||
HiddenFill | UnpaintedStroke => group(&mut d, red),
|
||||
InGroup | Brushed => group(&mut d, red),
|
||||
InNestedGroup => {
|
||||
let inner = group(&mut d, red);
|
||||
@@ -313,6 +335,14 @@ fn a_masked_group_spans_its_region_when_its_art_paints_past_its_shapes() {
|
||||
let instance = Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "Square".into(), xf: vectorcraft_geom::Affine::IDENTITY });
|
||||
group(&mut d, instance)
|
||||
}
|
||||
OverriddenSymbol => {
|
||||
d.symbols.push(vectorcraft_doc::Symbol { dynamic: true, name: "Square".into(), art: std::sync::Arc::new(red) });
|
||||
let mut instance =
|
||||
Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "Square".into(), xf: vectorcraft_geom::Affine::IDENTITY });
|
||||
let fill = Some(Paint::solid(Color::rgb8(255, 0, 0)));
|
||||
instance.extras_mut().symbol_overrides.push(vectorcraft_doc::symbol_overrides::SymbolOverride { fill, ..Default::default() });
|
||||
group(&mut d, instance)
|
||||
}
|
||||
InBlend => {
|
||||
let mut blue = rect(&mut d, Rect::new(60.0, 10.0, 80.0, 30.0), [0, 0, 255]);
|
||||
blue.appearance.effects = red.appearance.effects.clone();
|
||||
|
||||
Reference in New Issue
Block a user