mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 05:22:22 +00:00
Effects: hidden layer children and edited freeform gradients follow the Transform and Free Distort effects
A path that keeps its shape because the effect would pass the geometry budget holds a group's own placed gradients in place. A hidden path in a layer, which the layer's bounds leave out, no longer holds them, so the layer's gradient moves with its visible art. The art's own gradients follow the same rule. A freeform gradient whose points were edited keeps its vector fitted to the bounds, so it did not count as placed and its points stayed behind under a Transform effect and Free Distort unless another placed gradient was in the same appearance. Its points now move like a placed gradient.
This commit is contained in:
@@ -376,6 +376,11 @@ impl Appearance {
|
||||
pub fn has_placed_gradient(&self) -> bool {
|
||||
self.has_gradient(true)
|
||||
}
|
||||
/// Does a fill or stroke carry a gradient that is drawn at a place in document space: a placed
|
||||
/// one, or a freeform gradient with points of its own whose vector is fitted to the bounds?
|
||||
pub fn has_positioned_gradient(&self) -> bool {
|
||||
self.gradients().any(|g| g.is_placed() || g.has_freeform_points())
|
||||
}
|
||||
/// Fix unplaced gradients to their fit on `bounds` (the object's geometric bounds; strokes fit
|
||||
/// the stroke-inflated box), so they can follow transforms that refitting wouldn't reproduce.
|
||||
pub fn pin_gradients(&mut self, bounds: vectorcraft_geom::Rect) {
|
||||
|
||||
@@ -236,10 +236,11 @@ impl Carry {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Does it move any of the gradients of `ap`? Callers check first, so an appearance shared
|
||||
/// with other objects is not copied for nothing.
|
||||
/// Does it move any of the gradients of `ap` (those drawn at a place in document space, a
|
||||
/// freeform gradient's own points included)? Callers check first, so an appearance shared with
|
||||
/// other objects is not copied for nothing.
|
||||
pub fn moves(&self, ap: &Appearance) -> bool {
|
||||
!self.0.is_empty() && ap.has_placed_gradient()
|
||||
!self.0.is_empty() && ap.has_positioned_gradient()
|
||||
}
|
||||
|
||||
/// Move the placed gradients of `ap` along, through each step in turn.
|
||||
|
||||
@@ -670,8 +670,7 @@ fn reshape_leaves_apart(art: &mut Node, fx: &[&Effect], budget: Budget, apart: b
|
||||
if follows {
|
||||
group_marks(art, &mut vec![], &HashSet::new(), &mut groups);
|
||||
}
|
||||
// Whether a path kept its shape (past the budget), and the ids of those that did.
|
||||
let mut kept = false;
|
||||
// The ids of the paths that kept their shape (past the budget).
|
||||
let mut kept_ids = HashSet::new();
|
||||
let transform = (e.id == "distort.transform").then(|| {
|
||||
let (m, copies) = crate::distort::transform_map(bounds, ¶ms, seed);
|
||||
@@ -697,7 +696,6 @@ fn reshape_leaves_apart(art: &mut Node, fx: &[&Effect], budget: Budget, apart: b
|
||||
let (made, moved) = transform_leaf(leaf, &path, map, copies, options, room);
|
||||
used = used.minus(before).plus(made);
|
||||
if !moved {
|
||||
kept = true;
|
||||
kept_ids.insert(leaf.id);
|
||||
}
|
||||
return;
|
||||
@@ -709,12 +707,11 @@ fn reshape_leaves_apart(art: &mut Node, fx: &[&Effect], budget: Budget, apart: b
|
||||
}
|
||||
}
|
||||
let Some(out) = apply_one(&e.id, ¶ms, &path, bounds, &GeomContext { seed, ..GeomContext::of(leaf) }, room) else {
|
||||
kept = true;
|
||||
kept_ids.insert(leaf.id);
|
||||
return;
|
||||
};
|
||||
used = used.minus(before).plus(Budget::of(&out));
|
||||
if follows && !boxes && leaf.appearance.has_placed_gradient() {
|
||||
if follows && !boxes && leaf.appearance.has_positioned_gradient() {
|
||||
crate::Carry::of(&e.id, ¶ms, bounds, None, None, seed).apply(&mut leaf.appearance);
|
||||
}
|
||||
set_geometry(leaf, out, rule);
|
||||
@@ -732,13 +729,13 @@ fn reshape_leaves_apart(art: &mut Node, fx: &[&Effect], budget: Budget, apart: b
|
||||
}
|
||||
// What paints the art as a whole, and the own fills and strokes of the groups inside it,
|
||||
// follow their bounds under Free Distort (they stay when no path changed them). Each stays
|
||||
// when a path under it kept its shape (past the budget).
|
||||
// when a path under it that its bounds measure kept its shape (past the budget).
|
||||
let mut after = vec![];
|
||||
let held = (!groups.is_empty() || !kept_ids.is_empty()) && group_marks(art, &mut vec![], &kept_ids, &mut after).1;
|
||||
if !groups.is_empty() {
|
||||
let mut after = vec![];
|
||||
group_marks(art, &mut vec![], &kept_ids, &mut after);
|
||||
carry_groups(art, groups, after, &|from, to| crate::Carry::of(&e.id, ¶ms, bounds, from, to, seed));
|
||||
}
|
||||
if follows && !kept {
|
||||
if follows && !held {
|
||||
whole.push(crate::Carry::of(&e.id, ¶ms, bounds, Some(bounds), art.geometric_bounds(), seed));
|
||||
}
|
||||
}
|
||||
@@ -1020,7 +1017,7 @@ fn rebase_type_outlines(n: &mut Node, before: &mut std::vec::IntoIter<Option<Rec
|
||||
&& from != to
|
||||
{
|
||||
for_each_leaf(n, &mut |leaf| {
|
||||
if leaf.appearance.has_placed_gradient() {
|
||||
if leaf.appearance.has_positioned_gradient() {
|
||||
follow_boxes(&mut leaf.appearance, from, to);
|
||||
}
|
||||
});
|
||||
@@ -1049,11 +1046,11 @@ fn is_member_group(n: &Node) -> bool {
|
||||
|
||||
/// A group below the art that [`is_member_group`] takes and whose own fills or strokes hold a
|
||||
/// placed gradient: the child indices that lead to it from the art, its geometric bounds, and
|
||||
/// whether a path below it kept its shape.
|
||||
/// whether a path below it that its bounds measure kept its shape.
|
||||
type GroupMark = (Vec<usize>, Option<Rect>, bool);
|
||||
|
||||
/// The geometric bounds of `n` (as [`Node::geometric_bounds`] gives them) and whether a path or
|
||||
/// compound path below it is in `kept`, measured in one walk. Each group below `n` that
|
||||
/// compound path below it that those bounds measure is in `kept`, measured in one walk. Each group below `n` that
|
||||
/// [`GroupMark`] describes goes into `out`, reached from `n` through `at`.
|
||||
fn group_marks(n: &Node, at: &mut Vec<usize>, kept: &HashSet<NodeId>, out: &mut Vec<GroupMark>) -> (Option<Rect>, bool) {
|
||||
match &n.kind {
|
||||
@@ -1065,14 +1062,15 @@ fn group_marks(n: &Node, at: &mut Vec<usize>, kept: &HashSet<NodeId>, out: &mut
|
||||
for (i, c) in children.iter().enumerate() {
|
||||
at.push(i);
|
||||
let (b, h) = group_marks(c, at, kept, out);
|
||||
if is_member_group(c) && c.appearance.has_placed_gradient() {
|
||||
if is_member_group(c) && c.appearance.has_positioned_gradient() {
|
||||
out.push((at.clone(), b, h));
|
||||
}
|
||||
at.pop();
|
||||
held |= h;
|
||||
// Only members measured in the bounds hold the group's gradients in place.
|
||||
let counts = if *clip { i == 0 } else { i >= skip && (c.visible || !layer) };
|
||||
if counts {
|
||||
bounds = vectorcraft_geom::union_opt(bounds, b);
|
||||
held |= h;
|
||||
}
|
||||
}
|
||||
(bounds, held)
|
||||
@@ -1126,7 +1124,7 @@ fn rebase_leaves(n: &mut Node, before: &mut std::vec::IntoIter<Option<Rect>>) {
|
||||
if let Some(Some(from)) = before.next()
|
||||
&& let Some(to) = n.geometric_bounds()
|
||||
&& from != to
|
||||
&& n.appearance.has_placed_gradient()
|
||||
&& n.appearance.has_positioned_gradient()
|
||||
{
|
||||
follow_boxes(&mut n.appearance, from, to);
|
||||
}
|
||||
@@ -1224,7 +1222,7 @@ fn copy_placed(n: &mut Node, m: Affine, copies: usize, room: &mut usize) {
|
||||
_ => {}
|
||||
}
|
||||
// The placed gradients of its own fills and strokes move with it, as a path's do.
|
||||
if xf != Affine::IDENTITY && one.appearance.has_placed_gradient() {
|
||||
if xf != Affine::IDENTITY && one.appearance.has_positioned_gradient() {
|
||||
one.appearance.transform_gradients(xf);
|
||||
}
|
||||
one
|
||||
|
||||
@@ -455,6 +455,92 @@ fn art_past_the_budget_keeps_its_own_gradients_in_place() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A hidden path in a layer that keeps its shape past the budget does not hold the layer's
|
||||
/// gradients in place: the layer's bounds leave it out, so the layer's own gradient moves with
|
||||
/// the visible art under a Transform effect and Free Distort, as does the art's when the layer is
|
||||
/// the art.
|
||||
#[test]
|
||||
fn a_hidden_path_past_the_budget_leaves_a_layers_gradients_free_to_move() {
|
||||
use std::sync::Arc;
|
||||
|
||||
use vectorcraft_color::{Gradient, GradientGeom, GradientKind, GradientPaint, GradientStop};
|
||||
use vectorcraft_doc::{AppearanceItem, FillLayer, LayerColor, NodeId, NodeKind, color::Paint};
|
||||
let stops = vec![GradientStop::new(0.0, vectorcraft_color::Color::BLACK), GradientStop::new(1.0, vectorcraft_color::Color::WHITE)];
|
||||
let geom = GradientGeom { start: (0.0, 50.0).into(), end: (100.0, 50.0).into(), aspect: 1.0, focal: None };
|
||||
let placed = Paint::Gradient(Box::new(GradientPaint { geom: Some(geom), ..GradientPaint::new(Gradient::new(GradientKind::Linear, stops)) }));
|
||||
let end = |ap: &Appearance| match ap.fill_paint() {
|
||||
Paint::Gradient(g) => g.geom.map(|g| g.end.x),
|
||||
_ => None,
|
||||
};
|
||||
// A layer with a visible square and a hidden circle, both 0..100, and a gradient fill of its own.
|
||||
let layer = || {
|
||||
let square = Arc::new(Node::path(NodeId(3), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), Appearance::default()));
|
||||
let mut circle = Node::path(NodeId(4), shapes::ellipse(Rect::new(0.0, 0.0, 100.0, 100.0)), Appearance::default());
|
||||
circle.visible = false;
|
||||
let mut layer = Node::layer(NodeId(2), "Layer 1", LayerColor::Preset(0));
|
||||
if let NodeKind::Layer { children, .. } = &mut layer.kind {
|
||||
*children = vec![square, Arc::new(circle)];
|
||||
}
|
||||
layer.appearance.items.push(AppearanceItem::Fill(FillLayer::new(placed.clone())));
|
||||
layer
|
||||
};
|
||||
// No room beyond what the paths cost: the square takes each effect, the circle would be drawn
|
||||
// with more lines and keeps its shape.
|
||||
let budget = Budget { anchors: usize::MAX, lines: 0 };
|
||||
// Each scales the art ten times about its center: 0..100 becomes -450..550.
|
||||
let corners = json!({"corners": [[-4.5, -4.5], [5.5, -4.5], [5.5, 5.5], [-4.5, 5.5]]});
|
||||
for grow in [fx("distort.transform", json!({"scaleH": 1000, "scaleV": 1000})), fx("distort.freeDistort", corners)] {
|
||||
let mut art = layer();
|
||||
let carry = crate::reshape::reshape_leaves_within(&mut art, &[&grow], budget);
|
||||
let circle = art.children().unwrap()[1].geometric_bounds().unwrap();
|
||||
assert!(circle.width() < 101.0, "{}: the hidden circle keeps its shape: {circle:?}", grow.id);
|
||||
let square = art.children().unwrap()[0].geometric_bounds().unwrap();
|
||||
assert!((square.x1 - 550.0).abs() < 1e-6, "{}: the square takes the effect: {square:?}", grow.id);
|
||||
let mut own = Appearance::clone(&art.appearance);
|
||||
carry.apply(&mut own);
|
||||
assert_eq!(end(&own), Some(550.0), "{}: the layer's gradient, as the art", grow.id);
|
||||
let mut art = Node::group(NodeId(9), vec![Arc::new(layer())]);
|
||||
crate::reshape::reshape_leaves_within(&mut art, &[&grow], budget);
|
||||
assert_eq!(end(&art.children().unwrap()[0].appearance), Some(550.0), "{}: the layer's gradient, inside the art", grow.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// A freeform gradient whose points were edited (its vector still fitted to the bounds) moves its
|
||||
/// points with a Transform effect and Free Distort, on a path, on a group inside the art and on the
|
||||
/// art itself, as a placed gradient does.
|
||||
#[test]
|
||||
fn edited_freeform_points_follow_a_transform_effect_and_free_distort() {
|
||||
use std::sync::Arc;
|
||||
|
||||
use vectorcraft_color::{Color, Freeform, FreeformPoint, Gradient, GradientKind, GradientPaint};
|
||||
use vectorcraft_doc::{AppearanceItem, FillLayer, NodeId, color::Paint};
|
||||
let mut ramp = GradientPaint::new(Gradient { kind: GradientKind::Freeform, ..Gradient::default() });
|
||||
let pt = |x: f64, c| FreeformPoint::new(Point::new(x, 50.0), c);
|
||||
ramp.set_freeform(Freeform { points: vec![pt(0.0, Color::BLACK), pt(100.0, Color::WHITE)], ..Default::default() });
|
||||
assert!(ramp.geom.is_none() && !ramp.is_placed());
|
||||
let freeform = Paint::Gradient(Box::new(ramp));
|
||||
let xs = |ap: &Appearance| match ap.fill_paint() {
|
||||
Paint::Gradient(g) => g.freeform.as_ref().map(|f| f.points.iter().map(|p| p.at.x).collect::<Vec<_>>()),
|
||||
_ => None,
|
||||
};
|
||||
let square = |ap: Appearance| Arc::new(Node::path(NodeId(3), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), ap));
|
||||
// Each moves the art 30 pt right.
|
||||
let corners = json!({"corners": [[0.3, 0.0], [1.3, 0.0], [1.3, 1.0], [0.3, 1.0]]});
|
||||
for e in [fx("distort.transform", json!({"moveH": 30})), fx("distort.freeDistort", corners)] {
|
||||
let mut inner = Node::group(NodeId(2), vec![square(Appearance::default())]);
|
||||
inner.appearance.items.push(AppearanceItem::Fill(FillLayer::new(freeform.clone())));
|
||||
let mut art = Node::group(NodeId(9), vec![square(Appearance::basic(freeform.clone(), Paint::None, 0.0)), Arc::new(inner)]);
|
||||
art.appearance.items.push(AppearanceItem::Fill(FillLayer::new(freeform.clone())));
|
||||
let carry = crate::reshape::reshape_leaves_within(&mut art, &[&e], Budget::EFFECTS);
|
||||
let members = art.children().unwrap();
|
||||
assert_eq!(xs(&members[0].appearance), Some(vec![30.0, 130.0]), "{}: the path's points", e.id);
|
||||
assert_eq!(xs(&members[1].appearance), Some(vec![30.0, 130.0]), "{}: the inner group's points", e.id);
|
||||
let mut own = Appearance::clone(&art.appearance);
|
||||
carry.apply(&mut own);
|
||||
assert_eq!(xs(&own), Some(vec![30.0, 130.0]), "{}: the art's own points", e.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// #3578: a large Roughen draws each of its points as many lines; they stay within the budget,
|
||||
/// the Roughen built again with fewer points where they wouldn't.
|
||||
#[test]
|
||||
|
||||
@@ -549,7 +549,7 @@ since 2026-10-01.
|
||||
- **Transform effect options (#885):** Effect › Distort & Transform › Transform… has visible Transform Objects, Transform Patterns and Scale Strokes & Effects checkboxes (`distort.transform` params `objects`, `patterns`, `strokes`; files without them draw as before). With Transform Objects off the geometry stays and makes no copies; Scale Strokes & Effects scales stroke weights, dashes and the fills' and strokes' effect distances by the mean scale, and Transform Patterns moves pattern tiles, each copy `k` by the transformation `k` times. Such objects draw, export and expand through the same per-copy art, so the canvas and Expand Appearance agree (engine `tests_transformfx`, effects `tests_transformfx`, the dialog test). A Transform effect in a fill's or stroke's own effects applies the same options to that fill or stroke: its weight and dashes, the distances of its later effects and its pattern follow the transformation, and with copies each copy is an object of its own; the canvas, export, Expand Appearance and Use Preview Bounds use the same art (effects `tests_transformfx`). Not yet: object effects after a Transform effect keep their distances. Public reference: [Scale objects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/scale-objects.html), [How to apply effects](https://helpx.adobe.com/illustrator/using/effects.html), [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html). Scores and hours unchanged.
|
||||
- **Warp Options (#1760):** Effect > Warp and existing Appearance effect links share the fifteen existing styles, original schematic previews, orientation choices and percent Bend/Distortion controls. Focused tests passed for real numeric entry and slider input, Preview/Cancel, immediate Enter/OK, mixed-style edits, target/slot/visibility preservation and Undo/Redo. Each applied style stays unchanged until an explicit Style choice. Original style-menu and live-preview captures were reviewed; their numeric values were supplied programmatically. Public references: [Reshaping with effects](https://helpx.adobe.com/illustrator/using/reshaping-objects-effects.html) and [Using effects](https://helpx.adobe.com/illustrator/using/effects.html). Warp geometry, optional Properties integration and remembered defaults are unchanged; no broader parity claim.
|
||||
- **Distort Appearance with Transform effect options:** with Envelope Options › Distort Appearance on, a path whose Transform effect has Scale Strokes & Effects or Transform Patterns on, or copies it with a gradient, is bent as the same per-copy art it draws and expands as outside the envelope: each copy's own stroke weight, dashes, pattern placement and gradient (effects `bake::tests`). Scores and hours unchanged.
|
||||
- **Placed gradients follow Transform and Free Distort effects:** a gradient placed with the Gradient tool moves, turns and scales with the object when the object's Transform effect moves its outline, and each copy of the Transform effect takes it as many times as it takes the transform. Under Free Distort it moves and scales from the path's bounds before the effect to its bounds after it. This holds on the canvas, in export, in Expand Appearance, in Pathfinder and compound shapes, and in Object › Expand of the fill, for an object's own effects, a group's (the own fills of the group, of the groups inside it, of a symbol's art, and of the images and symbol instances it moves, included) and a fill's or stroke's own. A gradient on a path inside a group with a Transform effect now moves with the path (#2214 moved only the gradients of type), and so does a gradient on an image's or symbol instance's own fill. When a path keeps its shape because the effect would pass the geometry budget, the group's own gradients stay in place. How gradients follow each effect is VectorCraft's own rule; what each effect does to the object is described in [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html) (effects `bake::tests::a_baked_transform_moves_a_placed_gradient_with_the_path`, `bake::tests::a_strokes_own_transform_and_free_distort_move_its_placed_gradient`, `bake::tests::a_fills_gradient_follows_the_effects_around_its_transform_with_options`, `bake::tests::baked_free_distort_moves_placed_gradients_from_the_bounds_to_the_distorted_bounds`, `group::tests::a_groups_transform_and_free_distort_move_the_gradients_placed_on_its_paths`, `group::tests::a_groups_transform_moves_the_gradient_of_its_own_fill`, `group::tests::a_nested_groups_own_gradient_follows_the_outer_groups_transform_and_free_distort`, `group::tests::a_groups_transform_moves_the_gradients_placed_on_an_images_own_fills`, `group::tests::shape_art_moves_gradients_with_a_transform_effect`, `group::tests::shape_art_moves_the_gradient_of_a_symbol_instances_art_with_its_transform`, `tests::art_past_the_budget_keeps_its_own_gradients_in_place`; render `objectfx::a_paths_gradient_moves_with_its_transform_and_free_distort`; engine `objexpand::a_placed_gradient_moved_by_a_transform_or_free_distort_expands_as_the_canvas_draws_it`). Scores and hours unchanged.
|
||||
- **Placed gradients follow Transform and Free Distort effects:** a gradient placed with the Gradient tool moves, turns and scales with the object when the object's Transform effect moves its outline, and each copy of the Transform effect takes it as many times as it takes the transform. Under Free Distort it moves and scales from the path's bounds before the effect to its bounds after it. This holds on the canvas, in export, in Expand Appearance, in Pathfinder and compound shapes, and in Object › Expand of the fill, for an object's own effects, a group's (the own fills of the group, of the groups inside it, of a symbol's art, and of the images and symbol instances it moves, included) and a fill's or stroke's own. A gradient on a path inside a group with a Transform effect now moves with the path (#2214 moved only the gradients of type), and so does a gradient on an image's or symbol instance's own fill. When a path keeps its shape because the effect would pass the geometry budget, the group's own gradients stay in place; a hidden path in a layer, which the layer's bounds leave out, does not hold them. A freeform gradient whose points were edited moves its points in the same way. How gradients follow each effect is VectorCraft's own rule; what each effect does to the object is described in [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html) (effects `bake::tests::a_baked_transform_moves_a_placed_gradient_with_the_path`, `bake::tests::a_strokes_own_transform_and_free_distort_move_its_placed_gradient`, `bake::tests::a_fills_gradient_follows_the_effects_around_its_transform_with_options`, `bake::tests::baked_free_distort_moves_placed_gradients_from_the_bounds_to_the_distorted_bounds`, `group::tests::a_groups_transform_and_free_distort_move_the_gradients_placed_on_its_paths`, `group::tests::a_groups_transform_moves_the_gradient_of_its_own_fill`, `group::tests::a_nested_groups_own_gradient_follows_the_outer_groups_transform_and_free_distort`, `group::tests::a_groups_transform_moves_the_gradients_placed_on_an_images_own_fills`, `group::tests::shape_art_moves_gradients_with_a_transform_effect`, `group::tests::shape_art_moves_the_gradient_of_a_symbol_instances_art_with_its_transform`, `tests::art_past_the_budget_keeps_its_own_gradients_in_place`, `tests::a_hidden_path_past_the_budget_leaves_a_layers_gradients_free_to_move`, `tests::edited_freeform_points_follow_a_transform_effect_and_free_distort`; render `objectfx::a_paths_gradient_moves_with_its_transform_and_free_distort`; engine `objexpand::a_placed_gradient_moved_by_a_transform_or_free_distort_expands_as_the_canvas_draws_it`). Scores and hours unchanged.
|
||||
- **Constrain Angle (#394):** General › Constrain Angle turns the arrow keys' axes (guides stay on the page's), holds Shift drags of the Selection, Direct Selection, Free Transform, Slice Selection, Reshape and Measure tools and the Pen's handles to the turned axes, and new rectangles, rounded rectangles, ellipses, grids, point and area type, Shift-drawn polygons and stars and the shape size dialogs are drawn along it with their bounding box turned; 0° and whole turns are exactly as before, quarter turns exact (<https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rotate-objects-by-specific-angles.html>). Tests: `tests_constrain_tools`, `tests_constrain_new` (engine), `geom` `constrain_axes` / `rotate_deg`, the shape, grid and type tool tests.
|
||||
|
||||
- **Clipping paths on their outline (G1):** the selection tools (Selection, Direct Selection, Group Selection), Object Highlighting and a right-click take a clipping path on its outline before the art it clips; Select Behind lists the same; the eyedropper, the tools that act on what is drawn and the selection tools' double-clicks, loaded text and guide release take the art. Doc and tool regression tests; score unchanged.
|
||||
|
||||
Reference in New Issue
Block a user