mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 22:55:29 +00:00
A raster effect (Drop Shadow, Outer Glow, Gaussian Blur and the other effects that paint pixels) on a symbol instance's topmost fill now draws on each filled shape of the symbol's art, and one on its topmost stroke draws on each stroked shape, after any geometry effects listed before it. The same rules as for geometry effects apply: only a visible fill or stroke with an opacity above 0 counts, a group's own fills and strokes in the art take the effect, and an instance inside the art passes it on. The canvas, Use Preview Bounds and the drawn bounds, SVG and PDF export draw it, and Object > Expand Appearance is enabled for such an instance: a shadow or glow becomes an image under the instance, and a blur, feather or inner glow replaces it with the image, in one undo step. Existing documents with a raster effect on a symbol instance's fill or stroke draw differently: before, the effect drew nothing.
89 lines
4.7 KiB
Rust
89 lines
4.7 KiB
Rust
use super::bounds::drawn_bounds;
|
|
use super::{Affine, AppearanceItem, Effect, Frame, Node, NodeKind, RasterFx, Rect, SymbolBoxes, Vec2, effects};
|
|
|
|
fn visible(effects: &[Effect]) -> bool {
|
|
effects.iter().any(|e| e.visible)
|
|
}
|
|
|
|
/// Does this node need the effects path (a path or compound path with visible effects)?
|
|
pub(crate) fn has_fx(n: &Node) -> bool {
|
|
if !matches!(n.kind, NodeKind::Path { guide: false, .. } | NodeKind::Compound { .. }) {
|
|
return false;
|
|
}
|
|
visible(&n.appearance.effects)
|
|
|| n.appearance.items.iter().any(|i| match i {
|
|
AppearanceItem::Fill(f) => visible(&f.effects),
|
|
AppearanceItem::Stroke(s) => visible(&s.effects),
|
|
})
|
|
}
|
|
|
|
/// Does `n` carry visible object-level effects that only apply through its art (type, images,
|
|
/// symbol instances, live objects; Crop Marks and colour adjustments on anything), is it type with
|
|
/// a geometry effect on a fill or stroke of its own or an image with a fill or stroke of its own
|
|
/// that paints a shape ([`effects::has_item_geometry`]), a symbol instance whose active fill or
|
|
/// stroke has a raster effect ([`effects::has_instance_row_raster`]), is it a path whose Transform
|
|
/// effect copies it with a gradient or changes its fills and strokes ([`effects::has_transform_art`]),
|
|
/// or is it a group or layer with an appearance of its own (see [`Renderer::draw_object_fx`])?
|
|
pub(crate) fn has_object_fx(n: &Node) -> bool {
|
|
(effects::needs_outline(n) && (visible(&n.appearance.effects) || n.projection().is_some()))
|
|
|| effects::has_item_geometry(n)
|
|
|| effects::has_instance_row_raster(n)
|
|
|| effects::has_transform_art(n)
|
|
|| effects::has_container_appearance(n)
|
|
|| effects::has_crop_marks(n)
|
|
|| effects::addons::halftone::has_halftone(n)
|
|
|| effects::has_adjustment(n)
|
|
|| effects::has_revolve(n)
|
|
|| effects::has_solid(n)
|
|
}
|
|
|
|
/// Is `n` a group or layer (whose evaluated art keeps its knockout setting)?
|
|
pub(super) fn is_container(n: &Node) -> bool {
|
|
matches!(n.kind, NodeKind::Group { .. } | NodeKind::Layer { .. })
|
|
}
|
|
|
|
/// Can the art [`Renderer::fx_art`] makes for `n` be made from live objects it evaluates? It can
|
|
/// when `n` is a live object, and when `n` is a group or layer without a Pathfinder effect that
|
|
/// holds live objects and whose geometry effects reshape them or whose own fills and strokes paint
|
|
/// their evaluated outlines ([`effects::evaluate_container`]).
|
|
pub(super) fn evaluates_live(n: &Node) -> bool {
|
|
use vectorcraft_doc::live::{is_live, subtree_has_live};
|
|
let to_members = effects::has_geometry(&n.appearance.effects) || n.appearance.items.iter().any(effects::paints);
|
|
is_live(n) || (is_container(n) && to_members && !effects::has_pathfinder(n) && subtree_has_live(n))
|
|
}
|
|
|
|
/// Can the art [`Renderer::fx_art`] makes for `n` change while `n` stays the same, other than
|
|
/// through live objects it evaluates? It can when `n` holds type (laid out in the fonts installed),
|
|
/// a symbol instance (made from its symbol's art), an object with a 3D effect or a color
|
|
/// adjustment (shaded or recolored through the color settings), one with Vector Halftone (its
|
|
/// dots sampled from everything it draws), one with a live-effect plug-in that paints its pieces
|
|
/// (drawn only while the swatches and patterns they name are in the document), or an image with a
|
|
/// fill or stroke that follows the outline of its pixels ([`effects::follows_image_pixels`]: the
|
|
/// document's pixels for it can change), in it or in the opacity masks of the objects in it.
|
|
pub(super) fn reads_outside(n: &Node) -> bool {
|
|
let mut reads = false;
|
|
n.walk(&mut |c| {
|
|
reads |= matches!(c.kind, NodeKind::Text(_) | NodeKind::SymbolInstance { .. })
|
|
|| effects::follows_image_pixels(c)
|
|
|| effects::has_revolve(c)
|
|
|| effects::has_solid(c)
|
|
|| effects::has_adjustment(c)
|
|
|| effects::addons::halftone::has_halftone(c)
|
|
|| effects::has_painted_plugin_effect(c)
|
|
|| c.extras().mask.as_ref().is_some_and(|m| reads_outside(&m.art))
|
|
});
|
|
reads
|
|
}
|
|
|
|
/// The [`Content::bounds`] of `n` under raster effects `rfx`: its [`drawn_bounds`] when `rfx`
|
|
/// holds a Rasterize.
|
|
pub(super) fn rasterize_bounds(symbols: &SymbolBoxes, n: &Node, rfx: &[RasterFx]) -> Option<Rect> {
|
|
rfx.iter().any(|fx| matches!(fx, RasterFx::Rasterize(_))).then(|| drawn_bounds(symbols, n)).flatten()
|
|
}
|
|
|
|
/// `f` with the content moved by (`dx`, `dy`) document units (a shadow's offset).
|
|
pub(super) fn shifted<'a>(f: &Frame<'a>, dx: f64, dy: f64) -> Frame<'a> {
|
|
let moved = Affine::translate((dx, dy));
|
|
Frame { view: f.view * moved, visible: f.visible - Vec2::new(dx, dy), output: f.output * moved, ..*f }
|
|
}
|