Files
vectorcraft/crates/effects/src/live.rs
T
jmvergesandClaude Opus 5.5 543034987e Envelopes: type's strokes paint at their own weight along the bent outlines (#6644)
With Envelope Options > Distort Appearance on, VectorCraft turned the strokes
of type in an envelope into filled outlines and bent them with the art, so a
stroke got thinner where the grid squeezes the type and thicker where it
stretches it. The drawn art of users' .ai files keeps those strokes as stroked
paths at the type's own weights, and the ring they paint is as wide wherever
the grid squeezes or stretches the type. An envelope of stroked type in such a
grid therefore didn't draw as its drawn art and kept it instead of opening live.

Type's strokes (its characters' and those on its own appearance) now paint
along the bent glyph outlines at their own weight, with Distort Appearance on
or off, also in an envelope inside an envelope:

- doc: `map_node` maps the groups that hold type's glyph outlines with
  `Hooks::type_appearance` in place of `Hooks::appearance`, for type in the
  envelope's contents and for type that a live object in them (an inner
  envelope) outlined (`Warper::type_outlines`). Such a group's opacity mask
  is mapped with the ordinary hooks, so it bends as other art does. Groups
  marked as type's outlines in the contents themselves (art an earlier Expand
  made) stay plain art.
- effects: `bake_type_appearance`, `bake_appearance` with strokes kept as
  strokes: geometry effects on type's fills and strokes still bake in and bend.
- doc: `max_outset` counts type's character strokes as type's own visual
  bounds do (`char_stroke_outset`). It left them out, though they already
  painted past an envelope's grid where the glyphs reach its edge; this also
  grows the visual bounds of blends and repeats of stroked type and the reach
  of symbol art holding it.

Paths' strokes, geometry effects, opacity masks, linear gradients and pattern
fills bend as before. The canvas, hit testing, effect bounds, Expand, every
export and the .ai save take the evaluation from the same hooks. Expand of an
envelope of stroked type gives the bent outlines with the strokes on them.
This changes how VectorCraft's own envelopes of stroked type draw.

Tests: envelope_distort::type_strokes_paint_at_their_own_weight_in_a_squeezed_envelope,
a_stroke_on_types_appearance_paints_at_its_own_weight_in_a_squeezed_envelope,
type_strokes_paint_at_their_own_weight_in_an_envelope_inside_an_envelope,
a_paths_stroke_still_bends_in_a_squeezed_envelope,
an_opacity_mask_on_type_bends_as_other_art,
the_visual_bounds_of_an_envelope_hold_its_types_strokes,
the_visual_bounds_of_a_repeat_of_stroked_type_grow_by_half_the_stroke,
expanding_an_envelope_of_stroked_type_keeps_the_stroke_at_its_weight,
bake::tests::type_pieces_bake_their_geometry_effects_and_keep_their_strokes and
tests_native::an_envelope_of_stroked_type_whose_drawn_art_strokes_the_bent_outlines_at_the_weight_opens_live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:45:33 +00:00

67 lines
3.3 KiB
Rust

//! Live objects (blends, envelopes, gradient meshes, repeats) evaluated alike for the renderer and
//! every exporter, with what the pure evaluation in [`vectorcraft_doc::live`] can't do itself
//! ([`Hooks`]): type inside an envelope distorts as its glyph outlines, run by run
//! ([`outline_text`]); symbol instances as their symbol's art; with Distort Appearance strokes and
//! geometry effects bend with the art ([`bake_appearance`]), except type's strokes, which paint
//! along its bent outlines at their own weight ([`bake_type_appearance`], #6644); with Distort
//! Pattern Fills the pattern tiles do.
use std::sync::Arc;
use vectorcraft_doc::live::{self, Hooks, Outliner};
use vectorcraft_doc::{Document, Node, NodeKind};
use vectorcraft_geom::{Affine, Rect};
use crate::{bake_appearance, bake_type_appearance, outline_text};
/// The text outliner live evaluation uses: type as its glyph outlines, one path per run in the
/// run's paint ([`outline_text`]).
pub fn text_outliner() -> Outliner<'static> {
Some(&outline_text)
}
/// Type outlined, or a symbol instance as its symbol's art (from `doc`) placed by the instance,
/// through the symbol's 9-slice guides when it has them ([`Document::place_symbol_art`], `None`
/// past its object limit). As an [`OutlineHook`](crate::OutlineHook), it gives compound shapes and
/// Pathfinder operations the symbol instances among their members.
pub fn outline_object(doc: Option<&Document>, n: &Node) -> Option<Node> {
match &n.kind {
NodeKind::SymbolInstance { symbol, xf } => {
let doc = doc?;
let sym = doc.symbols.iter().find(|s| s.name == *symbol)?;
let art = vectorcraft_doc::symbol_overrides::dynamic_art(&sym.art, n);
let art = doc.place_symbol_art(symbol, &art, *xf)?;
Some(Node::group(n.id, vec![Arc::new(art)]))
}
_ => outline_text(n),
}
}
/// Run `f` with the hooks live evaluation needs; symbols and patterns come from `doc` (without a
/// document, symbol instances and pattern tiles stay as they are).
fn with_hooks<R>(doc: Option<&Document>, f: impl FnOnce(Hooks) -> R) -> R {
// Envelopes map type from its glyphs here too, without an engine session.
vectorcraft_text::register_envelope_ink();
let outline = |n: &Node| outline_object(doc, n);
let tiles = |name: &str, xf: Affine, region: Rect| Some(doc?.pattern(name)?.instances_in(xf, region));
let appearance = |n: &Node| bake_appearance(doc, n);
let type_appearance = |n: &Node| bake_type_appearance(doc, n);
f(Hooks { outline: Some(&outline), appearance: Some(&appearance), type_appearance: Some(&type_appearance), pattern: Some(&tiles), symbols: doc })
}
/// One level of evaluation of live object `n` ([`live::expand_live_hooks`]).
pub fn expand_live(doc: Option<&Document>, n: &Node) -> Vec<Node> {
with_hooks(doc, |h| live::expand_live_hooks(n, h))
}
/// Live object `n` evaluated into a plain group ([`live::expanded_group_hooks`]): Expand.
pub fn expanded_live_group(doc: Option<&Document>, n: &Node) -> Node {
with_hooks(doc, |h| live::expanded_group_hooks(n, h))
}
/// `n` with every live object in it replaced by its evaluated art ([`live::expand_deep_hooks`]):
/// what the exporters write for live objects.
pub fn expand_live_deep(doc: Option<&Document>, n: &Node) -> Node {
with_hooks(doc, |h| live::expand_deep_hooks(n, h))
}