mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-03 06:30:00 -06:00
Effects: effect art redraws when fonts arrive or the color settings change
The renderer kept the art it made for an object's effects while the object stayed the same. In the web build, type that a geometry effect outlines, on its own, in a group or in a symbol's art, kept the substitute font's outlines after its own font loaded. On the desktop, Refresh Font List left them on a symbol instance whose art holds the type. Objects with Revolve, Extrude & Bevel, Inflate or Rotate, and objects and groups with a color adjustment, kept the colors they were shaded or recolored with after the color settings changed. Live objects already followed both. The renderer now keeps that art with the inputs it keeps for the art of live objects: the font generation when the art holds type, the art of the symbols it shows, and the color settings. A temporary release test timed the added key at 11 microseconds a frame for 400 type objects with a drop shadow and 0.77 ms for 400 symbol instances of 100 paths each with a drop shadow, against warm frames of 5.3 and 72 ms. Docs: docs/target-app-parity.md, docs/gaps.md.
This commit is contained in:
+21
-3
@@ -108,6 +108,23 @@ fn evaluates_live(n: &Node) -> bool {
|
||||
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), or an object with a 3D effect or a color
|
||||
/// adjustment (shaded or recolored through the color settings), in it or in the opacity masks of
|
||||
/// the objects in it.
|
||||
fn reads_outside(n: &Node) -> bool {
|
||||
let mut reads = false;
|
||||
n.walk(&mut |c| {
|
||||
reads |= matches!(c.kind, NodeKind::Text(_) | NodeKind::SymbolInstance { .. })
|
||||
|| effects::has_revolve(c)
|
||||
|| effects::has_solid(c)
|
||||
|| effects::has_adjustment(c)
|
||||
|| c.extras().mask.as_ref().is_some_and(|m| reads_outside(&m.art))
|
||||
});
|
||||
reads
|
||||
}
|
||||
|
||||
/// What [`Renderer::raster_fx`] paints effects around.
|
||||
struct Content<'a> {
|
||||
/// The object the effects belong to (cached shadows are checked against it).
|
||||
@@ -1358,8 +1375,9 @@ impl Renderer {
|
||||
/// as their evaluated art (groups and layers: [`effects::evaluate_container`], keeping their
|
||||
/// knockout setting), recoloured by the colour adjustments in it ([`Self::adjusted_art`]);
|
||||
/// without the object's transparency and effects (a path keeps its geometry effects). Art that
|
||||
/// is made from live objects it evaluated ([`evaluates_live`]) is kept only while the inputs of
|
||||
/// their evaluation stay the same ([`Self::live_found`]).
|
||||
/// is made from live objects it evaluated ([`evaluates_live`]), or from type, symbol art or
|
||||
/// colors shaded through the color settings ([`reads_outside`]), is kept only while what it
|
||||
/// reads from outside `a` stays the same ([`Self::live_found`]).
|
||||
fn fx_art(&mut self, doc: &vectorcraft_doc::Document, a: &Arc<Node>, cache: bool) -> Arc<Node> {
|
||||
let key = Arc::as_ptr(a) as usize;
|
||||
let mut kept = None;
|
||||
@@ -1372,7 +1390,7 @@ impl Renderer {
|
||||
Some(was) => kept = Some((art.clone(), was.0)),
|
||||
}
|
||||
}
|
||||
let found = (kept.is_some() || (cache && evaluates_live(a))).then(|| self.live_found(doc, a));
|
||||
let found = (kept.is_some() || (cache && (evaluates_live(a) || reads_outside(a)))).then(|| self.live_found(doc, a));
|
||||
if let Some((art, was)) = kept
|
||||
&& found.as_ref().is_some_and(|f| f.0 == was)
|
||||
{
|
||||
|
||||
@@ -410,8 +410,9 @@ type ClipEntry = (Arc<Node>, Option<Arc<(BezPath, FillRule)>>);
|
||||
/// A clipping path (kept alive) and what it paints: its fills and its strokes ([`Node::clip_paint`]).
|
||||
type ClipPaintEntry = (Arc<Node>, Option<Arc<Node>>, Option<Arc<Node>>);
|
||||
/// An object with effects (kept alive), the art drawn for it ([`Renderer::fx_art`]), and, when that
|
||||
/// art can be made from evaluated live objects (`fx::evaluates_live`), the inputs of their
|
||||
/// evaluation ([`live::Found`]).
|
||||
/// art can be made from evaluated live objects (`fx::evaluates_live`) or from type, symbol art or
|
||||
/// colors shaded through the color settings (`fx::reads_outside`), the inputs it was made with
|
||||
/// ([`live::Found`]).
|
||||
type FxArtEntry = (Arc<Node>, Arc<Node>, Option<live::Found>);
|
||||
/// What symbol art placed by [`Renderer::placed_art`] is kept under: the art's address, the bits
|
||||
/// of the transform's coefficients, whether strokes scale with it, and the stain ([`stain_bits`]).
|
||||
|
||||
@@ -68,8 +68,8 @@ pub fn expand_live(n: &Node) -> Vec<Node> {
|
||||
crate::effects::expand_live(None, n)
|
||||
}
|
||||
|
||||
/// What the evaluation of `n` (a live node, or an object whose effect art can be made from
|
||||
/// evaluated live objects, see [`Renderer::live_found`]) reads from the document, placed files,
|
||||
/// What the evaluation of `n` (a live node, or an object whose effect art the renderer keeps with
|
||||
/// its inputs, see [`Renderer::live_found`]) reads from the document, placed files,
|
||||
/// the fonts, the plug-ins and the color settings, hashed: the art of the symbols, the tilings of
|
||||
/// the patterns and the reads of the placed files it shows ([`hash_resources`]), the font
|
||||
/// generation ([`vectorcraft_text::FontDb::generation`]) when it shows type or a placed file, the
|
||||
@@ -343,7 +343,7 @@ fn spread_to_edges(px: &mut [vello_cpu::color::PremulRgba8], filled: Vec<bool>,
|
||||
impl Renderer {
|
||||
/// The [`resources_key`] of `n` in `doc`, with the frame's [`DocInputs`] ([`Found`]): `n` is a
|
||||
/// live node, or an object with effects whose art ([`Renderer::fx_art`]) can be made from
|
||||
/// evaluated live objects.
|
||||
/// evaluated live objects, type, symbol art or colors shaded through the color settings.
|
||||
pub(crate) fn live_found(&mut self, doc: &Document, n: &Node) -> Found {
|
||||
let inputs = self.doc_inputs(doc);
|
||||
let (key, reads) = resources_key(doc, inputs.cms, n);
|
||||
|
||||
@@ -155,6 +155,60 @@ fn symbol_type_redraws_when_its_font_becomes_available() {
|
||||
assert_eq!(after, Renderer::new().render(&d, 300, 150, Affine::IDENTITY, &opts).pixels, "as a new renderer draws it");
|
||||
}
|
||||
|
||||
/// Type that a geometry effect outlines redraws in a font that becomes available: the art drawn for
|
||||
/// the effects of the type, of a group that holds it and of a symbol instance whose art holds it
|
||||
/// is made again when the fonts change.
|
||||
#[test]
|
||||
fn type_with_geometry_effects_redraws_when_its_font_becomes_available() {
|
||||
let _adding = adding_fonts();
|
||||
use vectorcraft_doc::Effect;
|
||||
let moved = || Effect { id: "distort.transform".into(), params: serde_json::json!({"moveH": 0.0}), visible: true };
|
||||
let cases = [
|
||||
("Moving Serif 4", "the type with a Transform effect"),
|
||||
("Shadow Serif 5", "the type with a Transform effect and a drop shadow"),
|
||||
("Massed Serif 4", "the group with a Transform effect"),
|
||||
("Stamps Serif 4", "the symbol instance with a Transform effect"),
|
||||
("Darker Serif 4", "the type with a Transform effect in Multiply"),
|
||||
];
|
||||
for (i, (family, what)) in cases.into_iter().enumerate() {
|
||||
let mut d = Document::new(300.0, 150.0);
|
||||
let style = CharStyle { size: 50.0, font_family: family.into(), ..Default::default() };
|
||||
let at = if i == 3 { Point::new(-140.0, 20.0) } else { Point::new(10.0, 80.0) };
|
||||
let mut object = Node::new(d.alloc_id(), NodeKind::Text(Box::new(TextObject::point(at, "Hamburg", style))));
|
||||
match i {
|
||||
0 => object.appearance.effects.push(moved()),
|
||||
1 => {
|
||||
object.appearance.effects.push(moved());
|
||||
let params = serde_json::json!({"x": 4, "y": 4, "blur": 1, "opacity": 100});
|
||||
object.appearance.effects.push(Effect { id: "stylize.dropShadow".into(), params, visible: true });
|
||||
}
|
||||
2 => {
|
||||
object = Node::group(d.alloc_id(), vec![Arc::new(object)]);
|
||||
object.appearance.effects.push(moved());
|
||||
}
|
||||
3 => {
|
||||
let art = Node::group(d.alloc_id(), vec![Arc::new(object)]);
|
||||
d.symbols.push(vectorcraft_doc::Symbol { name: "Word".into(), art: Arc::new(art) });
|
||||
object = Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "Word".into(), xf: Affine::translate((150.0, 75.0)) });
|
||||
object.appearance.effects.push(moved());
|
||||
}
|
||||
_ => {
|
||||
object.appearance.effects.push(moved());
|
||||
object.blend = vectorcraft_color::BlendMode::Multiply;
|
||||
}
|
||||
}
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, object).unwrap();
|
||||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() };
|
||||
let mut r = Renderer::new();
|
||||
let before = r.render(&d, 300, 150, Affine::IDENTITY, &opts).pixels;
|
||||
assert!(vectorcraft_text::FontDb::global().add_font(serif_as(family)) > 0);
|
||||
let after = r.render(&d, 300, 150, Affine::IDENTITY, &opts).pixels;
|
||||
assert_ne!(before, after, "{what}: drawn in the font that arrived");
|
||||
assert_eq!(after, Renderer::new().render(&d, 300, 150, Affine::IDENTITY, &opts).pixels, "{what}: as a new renderer draws it");
|
||||
}
|
||||
}
|
||||
|
||||
/// Type that an envelope bends redraws in a font that becomes available, in the envelope and in
|
||||
/// the art of a symbol whose instance an envelope bends: the envelopes are evaluated again when the
|
||||
/// fonts change.
|
||||
|
||||
Reference in New Issue
Block a user