Effects: Effect › Path › Outline Object turns type into live, editable outlines

Effect › Path had Offset Path and Outline Stroke but not Outline Object.
The new effect (path.outlineObject) has no options: the Effect menu
applies it at once, and effect.apply refuses any parameter. It is a
geometry effect that returns its input. Type already reaches geometry
effects as its glyph outlines, so type that carries it is drawn,
exported (PDF and SVG as paths, with no font) and expanded as those
outlines, stays editable type, and the effects after it reshape the
outlines. Paths, compound paths and images draw as before, and no
bounds change: with Use Preview Bounds on, type is still measured by
its layout box.

Every complete catalog gets a row for the label.

Docs: docs/effects-parity.md, docs/target-app-parity.md (an evidence
line), docs/mcp.md (the effect, and what Expand Appearance does to
type).
This commit is contained in:
dotCooCoo
2026-10-16 15:27:54 +07:00
committed by bflatastic
parent bc8e42d8c7
commit dfe215ca30
30 changed files with 262 additions and 5 deletions
+10
View File
@@ -291,6 +291,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"{offset: pt (10; negative insets), joins: \"miter\"|\"round\"|\"bevel\", miterLimit: (4)}",
json!({"offset": 10.0, "joins": "miter", "miterLimit": 4.0}),
),
g(
"path.outlineObject",
"Outline Object",
PATH,
"{} type is drawn, exported and expanded as its glyph outlines and stays editable, and the effects after it reshape those outlines; paths and images do not change",
json!({}),
),
g(
"path.outlineStroke",
"Outline Stroke",
@@ -874,6 +881,9 @@ pub(crate) fn apply_one(id: &str, p: &Value, path: &PathData, b: Rect, ctx: &Geo
}
vectorcraft_pathops::offset_path(path, off, join(p, "joins"), num(p, "miterLimit", 4.0).clamp(1.0, 500.0))
}
// Outline Object returns the geometry unchanged. Type reaches every geometry effect as its
// glyph outlines (`reshape::reshape`), so type that carries it is drawn as those outlines.
"path.outlineObject" => path.clone(),
"path.outlineStroke" => {
let fallback;
let base = match ctx.stroke {
+12
View File
@@ -572,6 +572,18 @@ fn outline_stroke_uses_context_stroke() {
assert!(close(out.bounds().unwrap().width(), 101.0, 0.1));
}
#[test]
fn outline_object_is_a_geometry_effect_that_keeps_the_path() {
let info = effect_info("path.outlineObject").unwrap();
assert_eq!((info.label, info.menu, &info.defaults, info.raster), ("Outline Object", &["Effect", "Path"][..], &json!({}), false));
assert!(is_geometry("path.outlineObject"));
assert_eq!(run("path.outlineObject", json!({}), &square()), square());
// The effects after it reshape the same geometry.
let b = square().bounds().unwrap();
let offset = fx("path.offsetPath", json!({"offset": 5}));
assert_eq!(apply_geometry(&[fx("path.outlineObject", json!({})), offset.clone()], &square(), b), apply_geometry(&[offset], &square(), b));
}
#[test]
fn convert_to_shape_sizes() {
let r = run("convertToShape.rectangle", json!({"extraW": 20, "extraH": 10}), &square()).bounds().unwrap();
+2
View File
@@ -2016,6 +2016,8 @@ mod tests_objexpand;
#[cfg(test)]
mod tests_opacitymask;
#[cfg(test)]
mod tests_outlineobject;
#[cfg(test)]
mod tests_outlinestroke;
#[cfg(test)]
mod tests_overprint;
+96
View File
@@ -0,0 +1,96 @@
//! Outline Object (`path.outlineObject`, Effect › Path › Outline Object): type that carries it is
//! drawn, exported and expanded as its glyph outlines while it stays editable type.
use serde_json::json;
use vectorcraft_doc::{Node, NodeKind};
use super::*;
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
s
}
/// Selected point type "OUTLINE" (48 pt) at (40, 100).
fn text(s: &mut Session) -> NodeId {
let r = s.execute("text.create", &json!({"x": 40, "y": 100, "text": "OUTLINE", "size": 48})).unwrap();
NodeId(r["id"].as_u64().unwrap())
}
fn apply(s: &mut Session, id: NodeId) {
s.execute("effect.apply", &json!({"effect": "path.outlineObject", "ids": [id.0], "item": null})).unwrap();
}
fn node(s: &Session, id: NodeId) -> Node {
s.doc().unwrap().doc.node(id).unwrap().clone()
}
fn count_text(n: &Node) -> usize {
let mut k = 0;
n.walk(&mut |m| k += usize::from(matches!(m.kind, NodeKind::Text(_))));
k
}
#[test]
fn outline_object_is_applied_in_one_undo_step_and_takes_no_parameters() {
let mut s = session();
let id = text(&mut s);
apply(&mut s, id);
let ids: Vec<String> = node(&s, id).appearance.effects.iter().map(|e| e.id.clone()).collect();
assert_eq!(ids, ["path.outlineObject"]);
assert_eq!(s.doc().unwrap().history.undo.last().map(|e| e.label.as_str()), Some("Outline Object"));
s.execute("edit.undo", &json!({})).unwrap();
assert!(node(&s, id).appearance.effects.is_empty());
let err = s.execute("effect.apply", &json!({"effect": "path.outlineObject", "ids": [id.0], "params": {"width": 1}})).unwrap_err().to_string();
assert!(err.contains("`width`") && err.contains("takes no parameters"), "{err}");
assert!(node(&s, id).appearance.effects.is_empty());
}
#[test]
fn type_with_outline_object_stays_editable_type_and_keeps_its_bounds() {
let mut s = session();
let id = text(&mut s);
let before = [false, true].map(|preview| {
s.prefs.use_preview_bounds = preview;
s.transform_bounds(&[id])
});
apply(&mut s, id);
let after = [false, true].map(|preview| {
s.prefs.use_preview_bounds = preview;
s.transform_bounds(&[id])
});
assert_eq!(after, before);
// A native save keeps the effect.
let back = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap();
let ids: Vec<String> = back.node(id).unwrap().appearance.effects.iter().map(|e| e.id.clone()).collect();
assert_eq!(ids, ["path.outlineObject"]);
// The type is still type, drawn as its outlines, and a text edit changes them.
let n = node(&s, id);
assert!(matches!(n.kind, NodeKind::Text(_)));
let drawn = vectorcraft_render::effects::reshape(&n, None).unwrap();
assert_eq!(count_text(&drawn), 0);
s.execute("text.setText", &json!({"id": id.0, "text": "OUTLINED TYPE"})).unwrap();
let longer = vectorcraft_render::effects::reshape(&node(&s, id), None).unwrap();
assert!(longer.geometric_bounds().unwrap().width() > drawn.geometric_bounds().unwrap().width() + 50.0);
}
#[test]
fn type_with_outline_object_exports_and_expands_as_paths() {
let mut s = session();
let id = text(&mut s);
apply(&mut s, id);
let doc = s.doc().unwrap().doc.clone();
let svg = vectorcraft_svg::export(&doc, &Default::default());
assert!(!svg.contains("<text") && !svg.contains("OUTLINE"), "{svg}");
// Expand Appearance leaves paths and no type, in one undo step.
let undo = s.doc().unwrap().history.undo.len();
s.execute("effect.expandAppearance", &json!({})).unwrap();
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1);
let doc = &s.doc().unwrap().doc;
let layer = &doc.layers[0];
assert_eq!(count_text(layer), 0);
let mut paths = 0;
layer.walk(&mut |m| paths += usize::from(matches!(m.kind, NodeKind::Path { .. } | NodeKind::Compound { .. })));
assert!(paths > 0);
}
+15
View File
@@ -268,6 +268,21 @@ fn fonts_whose_licence_forbids_embedding_fall_back_to_outlines() {
assert!(paths >= 2, "{paths} outlined words");
}
#[test]
fn type_with_outline_object_is_written_as_paths_with_no_font() {
let mut d = doc(vec![TextObject::point(Point::new(20.0, 60.0), "Hello World", style(24.0))]);
let id = d.layers[0].children().unwrap()[0].id;
d.node_mut(id).unwrap().appearance.effects.push(vectorcraft_doc::Effect { id: "path.outlineObject".into(), params: json!({}), visible: true });
let r = pdf(&d, false);
let text = String::from_utf8_lossy(&r.bytes);
assert!(!text.contains("/FontFile") && !text.contains("/Font"), "no font");
// Reopened, it is paths, even when type is asked for.
let back = import_as(&r.bytes, TextAs::Text);
assert!(texts(&back).is_empty());
let b = ink(&back);
assert!(b.width() > 50.0 && b.height() > 10.0, "{b:?}");
}
#[test]
fn subset_thresholds_below_100_percent_are_accepted_with_a_warning() {
let d = doc(vec![TextObject::point(Point::new(20.0, 60.0), "Subset", style(24.0))]);
+77
View File
@@ -602,6 +602,83 @@ fn geometry_effects_reshape_type_through_its_outlines() {
assert!(text == 0 && black > 0, "{art:?}");
}
/// Pixels that change by more than 8 levels in a channel when the first object of `d` gets
/// Outline Object.
fn changed_by_outline_object(d: &Document) -> usize {
let mut outlined = d.clone();
let id = outlined.layers[0].children().unwrap()[0].id;
outlined.node_mut(id).unwrap().appearance.effects.push(fx("path.outlineObject", json!({})));
differing(&render(d), &render(&outlined))
}
#[test]
fn outline_object_draws_type_as_its_outlines_and_leaves_its_look() {
// The type is drawn through its outlines: plain paths painted with the text's fill.
let d = text_doc(vec![fx("path.outlineObject", json!({}))]);
let n = &d.layers[0].children().unwrap()[0];
let art = vectorcraft_effects::reshape(n, None).unwrap();
let (mut text, mut black) = (0, 0);
art.walk(&mut |m| {
text += usize::from(matches!(m.kind, NodeKind::Text(_)));
black += usize::from(m.appearance.fill_paint() == Paint::solid(Color::BLACK));
});
assert!(text == 0 && black > 0, "{art:?}");
// Plain, underlined and scaled stroked type, and 50 % type in a group that carries the effect,
// look the same with it.
let underlined = CharStyle { size: 40.0, fill: Paint::solid(Color::BLACK), underline: true, ..Default::default() };
let red = Paint::solid(Color::rgb(1.0, 0.0, 0.0));
let stroked = CharStyle { size: 20.0, fill: Paint::solid(Color::BLACK), stroke: red, stroke_width: 2.0, ..Default::default() };
let mut t = text_doc(vec![]).layers[0].children().unwrap()[0].as_ref().clone();
t.id = NodeId(500);
t.opacity = 0.5;
let docs = [
text_doc(vec![]),
styled_text_doc(underlined, Affine::IDENTITY),
styled_text_doc(stroked, Affine::scale(2.0)),
doc_with(Document::new(100.0, 100.0), Node::group(NodeId(0), vec![Arc::new(t)])),
];
let changed: Vec<usize> = docs.iter().map(changed_by_outline_object).collect();
assert!(changed.iter().all(|&n| n < 10), "pixels changed: {changed:?}");
}
#[test]
fn outline_object_leaves_paths_and_images_as_they_are() {
let ap = Appearance::basic(Paint::solid(Color::rgb(0.0, 0.0, 1.0)), Paint::solid(Color::BLACK), 4.0);
let path = doc_with(Document::new(100.0, 100.0), Node::path(NodeId(0), shapes::ellipse(Rect::new(20.0, 20.0, 80.0, 70.0)), ap));
let changed: Vec<usize> = [path, image_doc(vec![])].iter().map(changed_by_outline_object).collect();
assert!(changed.iter().all(|&n| n < 10), "pixels changed: {changed:?}");
// The image is drawn through its frame, as under any geometry effect.
let d = image_doc(vec![fx("path.outlineObject", json!({}))]);
assert!(vectorcraft_effects::reshape(&d.layers[0].children().unwrap()[0], None).is_some());
}
#[test]
fn a_rectangle_on_a_fill_below_type_with_outline_object_fits_the_glyphs() {
// "IIII" with a red fill of its own below the characters that carries Convert to Shape ›
// Rectangle (the default 18 pt extra width and height).
let plain = render(&text_doc(vec![]));
let inked = inked_columns(&plain);
let gap = (inked[0]..*inked.last().unwrap()).find(|x| !inked.contains(x)).unwrap();
let mut d = text_doc(vec![fx("path.outlineObject", json!({}))]);
let id = d.layers[0].children().unwrap()[0].id;
let mut fill = FillLayer::new(Paint::solid(Color::rgb(1.0, 0.0, 0.0)));
fill.effects.push(fx("convertToShape.rectangle", json!({})));
let ap = &mut d.node_mut(id).unwrap().appearance;
ap.items.push(AppearanceItem::Fill(fill));
ap.set_contents_at(1);
let img = render(&d);
// The rectangle is under the glyphs and fills the gap between two of them.
assert_eq!(img.pixel(gap, 50), [255, 0, 0, 255], "column {gap}");
assert_eq!(lum(img.pixel(inked[0] + 1, 50)), 0);
// It reaches 9 pt past the glyph outlines on each side.
let glyphs = vectorcraft_effects::outline_text(d.node(id).unwrap()).unwrap().geometric_bounds().unwrap();
let (x, y) = (glyphs.center().x as u32, glyphs.center().y as u32);
assert_eq!(img.pixel(glyphs.x1 as u32 + 6, y), [255, 0, 0, 255]);
assert_eq!(lum(img.pixel(glyphs.x1 as u32 + 12, y)), WHITE);
assert_eq!(img.pixel(x, glyphs.y1 as u32 + 6), [255, 0, 0, 255]);
assert_eq!(lum(img.pixel(x, glyphs.y1 as u32 + 12)), WHITE);
}
#[test]
fn a_strokes_own_glow_paints_around_that_stroke_only() {
let mut d = Document::new(100.0, 100.0);
+1
View File
@@ -1948,6 +1948,7 @@
Out Tangent Handles Външни тангентни дръжки
Outer Glow… Външно разсейване…
Outline Контур
Outline Object Преобразуване на обекта в контур
Outline Others Контуриране на другите
Outline Stroke Преобразуване на щриха в контур
Outline Text Преобразуване на текста в контури
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -212,6 +212,7 @@
Join Spojit
Average… Zprůměrovat…
Outline Stroke Převést tah na obrys
Outline Object Převést objekt na obrys
Offset Path… Odsadit cestu…
Reverse Path Direction Obrátit směr cesty
Simplify… Zjednodušit…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1942,6 +1942,7 @@
Out Tangent Handles Ausgehende Tangentengriffe
Outer Glow… Schein nach außen…
Outline Pfadansicht
Outline Object Objekt in Pfade umwandeln
Outline Others Andere in Pfadansicht
Outline Stroke Konturlinie
Outline Text Text in Pfade umwandeln
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1517,6 +1517,7 @@
Other Άλλο
Outer Glow… Εξωτερική λάμψη…
Outline Περίγραμμα
Outline Object Μετατροπή αντικειμένου σε περιγράμματα
Outline Stroke Μετατροπή περιγράμματος σε διαδρομή
Output Έξοδος
Output: Έξοδος:
1 # Ελληνικά (el) — VectorCraft
1517
1518
1519
1520
1521
1522
1523
+1
View File
@@ -1991,6 +1991,7 @@
Out Tangent Handles Manejadores de tangente de salida
Outer Glow… Resplandor exterior…
Outline Contorno
Outline Object Contornear objeto
Outline Others Contornear otros
Outline Stroke Contornear trazo
Outline Text Contornear texto
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -209,6 +209,7 @@
Join Liitä
Average… Keskiarvota…
Outline Stroke Ääriviivaa viiva
Outline Object Muunna objekti ääriviivoiksi
Offset Path… Siirrä polkua…
Reverse Path Direction Käännä polun suunta
Simplify… Yksinkertaista…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1988,6 +1988,7 @@
Out Tangent Handles Poignées de tangente sortantes
Outer Glow… Lueur externe…
Outline Tracés
Outline Object Vectoriser l’objet
Outline Others Autres en mode Tracés
Outline Stroke Vectoriser le contour
Outline Text Vectoriser le texte
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2440,6 +2440,7 @@
Outer Corner Tile Külső sarokcsempe
Outer Glow… Külső ragyogás…
Outline Vázlat
Outline Object Objektum útvonallá alakítása
Outline Others Többi vázlatnézetben
Outline Stroke Körvonal útvonallá alakítása
Outline Text Szöveg útvonallá alakítása
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1993,6 +1993,7 @@
Out Tangent Handles Maniglie tangente in uscita
Outer Glow… Bagliore esterno…
Outline Contorno
Outline Object Contorna oggetto
Outline Others Altri in contorno
Outline Stroke Contorna traccia
Outline Text Contorna testo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -210,6 +210,7 @@
Join 連結
Average… 平均…
Outline Stroke パスのアウトライン
Outline Object オブジェクトのアウトライン
Offset Path… パスのオフセット…
Reverse Path Direction パスの方向反転
Simplify… 単純化…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -206,6 +206,7 @@
Join 결합
Average… 평균…
Outline Stroke 선 윤곽선화
Outline Object 개체 윤곽선화
Offset Path… 패스 오프셋…
Reverse Path Direction 패스 방향 반전
Simplify… 단순화…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -177,6 +177,7 @@
Join Połącz
Average… Uśrednij…
Outline Stroke Obrys konturu
Outline Object Kontur obiektu
Offset Path… Odsuń ścieżkę…
Reverse Path Direction Odwróć kierunek ścieżki
Simplify… Uprość…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1624,6 +1624,7 @@
Out Tangent Handles Alças de Tangente de Saída
Outer Glow… Brilho Externo…
Outline Contorno
Outline Object Contornar Objeto
Outline Stroke Contornar Traçado
Outline Text Contornar Texto
Outlines and Rules Contornos e Regras
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1989,6 +1989,7 @@
Out Tangent Handles Маркеры исходящей касательной
Outer Glow… Внешнее свечение…
Outline Контур
Outline Object Обвести объект
Outline Others Обвести остальные
Outline Stroke Обвести обводку
Outline Text Обвести текст
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1948,6 +1948,7 @@
Out Tangent Handles Utgående tangenthandtag
Outer Glow… Yttre glöd…
Outline Disposition
Outline Object Konvertera objekt till konturer
Outline Others Visa övriga som disposition
Outline Stroke Konvertera kontur till form
Outline Text Konvertera text till konturer
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -1985,6 +1985,7 @@
Out Tangent Handles Маркери вихідної дотичної
Outer Glow… Зовнішнє свічення…
Outline Контур
Outline Object Обвести об’єкт
Outline Others Обвести інші
Outline Stroke Обвести обведення
Outline Text Обвести текст
Can't render this file because it contains an unexpected character in line 720 and column 40.
+1
View File
@@ -2236,6 +2236,7 @@
Outer Corner Tile Ô góc ngoài
Outer Glow… Phát sáng ngoài…
Outline Đường bao
Outline Object Đường bao đối tượng
Outline Others Đường bao các mục khác
Outline Stroke Đường bao nét
Outline Text Chuyển chữ thành đường bao
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
2236
2237
2238
2239
2240
2241
2242
+1
View File
@@ -1876,6 +1876,7 @@
Out Tangent Handles 外切线控制手柄
Outer Glow… 外光晕…
Outline 轮廓
Outline Object 轮廓化对象
Outline Stroke 轮廓化描边
Outline Text 文字轮廓
Outlines and Rules 轮廓与规则线
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1965,6 +1965,7 @@
Out Tangent Handles 外切線控制把手
Outer Glow… 外光暈…
Outline 外框
Outline Object 外框物件
Outline Others 其他以外框檢視
Outline Stroke 外框筆畫
Outline Text 外框文字
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -999,6 +999,7 @@ mod tests {
assert_eq!(humanize("offsetX"), "Offset X");
assert_eq!(humanize("blur"), "Blur");
assert!(!effect_label("stylize.dropShadow").contains('…'));
assert_eq!(effect_label("path.outlineObject"), "Outline Object");
assert_eq!(effect_label("x.unknownThing"), "unknownThing");
// Only our own effects are interface labels; a plug-in's name is its own.
assert!(built_in_effect("stylize.dropShadow") && !built_in_effect("plugin.example.glow"));
+19 -1
View File
@@ -1,7 +1,7 @@
//! Neutral wording in menus and panels: what users read names VectorCraft's own features.
use egui::epaint::Shape;
use serde_json::json;
use serde_json::{Value, json};
use vectorcraft_engine::Session;
use crate::VectorcraftApp;
@@ -50,6 +50,24 @@ fn effect_menu_has_a_vector_effects_section() {
assert!(effect.iter().any(|i| matches!(i, Item::Header("Vector Effects"))));
}
/// Effect › Path: Offset Path… opens its dialog; Outline Object and Outline Stroke (no options)
/// apply.
#[test]
fn effect_menu_lists_outline_object_between_offset_path_and_outline_stroke() {
let app = app();
let entries = menus::menu_entries(&app);
let items: Vec<(&str, Option<&str>, &Value)> =
entries.iter().filter(|e| e.path == ["Effect", "Path"]).map(|e| (e.label.as_str(), e.command.as_deref(), &e.params)).collect();
assert_eq!(
items,
[
("Offset Path…", Some("effect.dialog"), &json!({"effect": "path.offsetPath"})),
("Outline Object", Some("effect.apply"), &json!({"effect": "path.outlineObject"})),
("Outline Stroke", Some("effect.apply"), &json!({"effect": "path.outlineStroke"})),
]
);
}
#[test]
fn effect_menu_lists_film_grain_in_artistic_before_blur() {
let app = app();
+2 -2
View File
@@ -11,7 +11,7 @@ Part of [target-app-parity.md](target-app-parity.md); open work is in [gaps.md](
| Group | Illustrator | VectorCraft | % | Remaining (h) |
|---|---:|---:|---:|---:|
| Illustrator effects (vector and stylize) | 50 | 44 | 88% | 6–10 |
| Illustrator effects (vector and stylize) | 50 | 45 | 90% | 6–10 |
| Photoshop effects (raster, Effect Gallery) | 57 | 30 | 53% | 22–38 |
| 3D and Materials | 4 (+3 Classic) | 4 (initial: Extrude & Bevel, Revolve, Inflate, Rotate) | ~25% | 25–45 |
| Beyond Illustrator: Effect › Color Adjustments (6), effect plug-ins (WebAssembly) | — | 6 + plug-ins | — | — |
@@ -23,7 +23,7 @@ Part of [target-app-parity.md](target-app-parity.md); open work is in [gaps.md](
| Convert to Shape | Rectangle, Rounded Rectangle, Ellipse | 3/3 | — |
| Crop Marks | Crop Marks | 1/1 | — |
| Distort & Transform | Free Distort, Pucker & Bloat, Roughen, Transform, Tweak, Twist, Zig Zag | 7/7 | Free Distort's dialog has a handle on each corner of the object's bounds, moved by dragging or with the arrow keys; Transform's Transform Objects/Patterns and Scale Strokes & Effects options, invisible Reflect checkboxes (#885); Zig Zag's ridges are evenly spaced along each segment (#1558); Roughen with Smooth keeps turn-backs smooth instead of cusps that draw as spikes (#1646) |
| Path | Offset Path, Outline Object, Outline Stroke | 2/3 | Outline Object |
| Path | Offset Path, Outline Object, Outline Stroke | 3/3 | With Use Preview Bounds on, Align, Distribute and the Transform panel still measure type that carries Outline Object by its layout box (preview bounds leave out effects) |
| Pathfinder | Add, Intersect, Exclude, Subtract, Minus Back, Divide, Trim, Merge, Crop, Outline, Hard Mix, Soft Mix, Trap | 10/13 | Hard Mix, Soft Mix, Trap |
| Rasterize | Rasterize… (as a live effect) | 0/1 | the live effect (Object › Rasterize exists) |
| Stylize | Drop Shadow, Feather, Inner Glow, Outer Glow, Round Corners, Scribble | 6/6 | Scribble Options' Settings presets |
+8 -2
View File
@@ -377,6 +377,10 @@ stroke instead of the whole object: `run_command effect.apply {"effect": "path.o
`effect.remove`, `effect.setParams` (`visible` toggles one) and `effect.duplicate` address that item's effects;
`apply_effect` uses the active item. `effect.list` reports each object's item effects under `applied[].items`.
`effect.apply {"effect": "path.outlineObject"}` (Effect › Path › Outline Object) takes no parameters. Type that carries
it is drawn, exported and expanded as its glyph outlines and stays editable type, and the effects after it reshape
those outlines. On paths, compound paths and images it does not change what is drawn.
Errors (unknown tool, bad arguments, a disabled or failing command) come back as a normal result with
`isError: true` and a message. The model can read the message and retry.
@@ -1479,8 +1483,10 @@ fill alone and each stroke its outline filled with its paint (a brushed stroke:
under the object's id; each piece takes its fill's or stroke's opacity and blend mode, and the group keeps the
object's transparency and opacity mask. Geometry effects are baked; raster effects become an embedded image at the
document's raster effects resolution (shadows and outer glows under the art; a blur, feather or inner glow replaces
the object with the image). Type with effects or fills and strokes of its own is outlined. A group's or layer's own
fills and strokes become objects among its members, and its members are expanded too.
the object with the image). Type with a geometry effect (Outline Object included) or with fills and strokes of its own
is outlined; type that has no fills or strokes of its own and whose only effects are shadows or outer glows stays
type, with the image under it. A group's or layer's own fills and strokes become objects among its members, and its
members are expanded too.
```json
{"name":"run_command","arguments":{"command":"effect.expandAppearance","params":{"ids":[12]}}}
+1
View File
@@ -535,6 +535,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Extrude & Bevel, Inflate and Rotate (Effect › 3D and Materials): live effects in the `three-d` crate with depth, twist, taper, solid or hollow caps, five original bevel shapes (width, height, repeat, spacing, inside, both sides), Inflate's volume, both sides, twist, taper and cap, Rotate's turned fills and strokes, Position presets, the rotation and light gizmos, SVG/PDF vector output and Expand Appearance; one 3D effect per object; covered by geometry, effect, canvas-versus-export, command and dialog tests ([three-d.md](three-d.md)).
- Initial live Revolve (#605, #846): a portable geometry and shading crate (`three-d`), editable path and compound profiles, compact options with artboard rotation and light gizmos, SVG/PDF vector output and Expand Appearance that can trim covered faces. Open ends are uncapped, shading is flat with one light, and intersecting surfaces' visibility is approximate ([revolve.md](revolve.md)).
- Live effects with previewing dialogs: Distort & Transform, Path, Convert to Shape, 15 Warp styles, Round Corners, Scribble, Effect → Pathfinder (all 10 operations, live on groups; several loose objects are grouped first, in one undo step), Color Adjustments (Brightness/Contrast, Curves, Levels, Hue/Saturation, Shift to Color, Temperature/Tint, on vectors, live type and embedded images), and raster drop shadow, glows and feather. SVG and PDF export keep live effects (geometry baked; SVG raster effects as filters). Zig Zag spaces its ridges evenly along each segment, so lines and curves get a regular wave in Corner and Smooth (#1558). Warp › Arc (the effect and Envelope › Make with Warp) bends in the object's real proportions: a circular arc with concentric edges and no fold-over at 100%, so Arc on art that isn't square draws differently from before (#1581, fixes #1578). `effect.apply` and `effect.setParams` give an error for a misspelt or wrongly typed effect parameter instead of running the effect with its default (#1623, #1592). Roughen with Smooth keeps turn-backs smooth instead of making cusps that draw as spikes (#1646, #1643). Scribble draws one zig-zag per region, with no lines across holes and notches; convex shapes are unchanged (#1651, #1648). On the canvas, in Expand Appearance and in exports, Effect → Pathfinder uses the glyph outlines of type in the group, and results painted like the type keep its opacity and blend mode.
- Effect › Path › Outline Object has no options. Type that carries it is drawn, exported (PDF and SVG as paths, with no font) and expanded as its glyph outlines while it stays editable, and the effects after it reshape those outlines. Paths and images draw as before. With Use Preview Bounds on, type is still measured by its layout box. Public reference: [Summary of effects](https://web.archive.org/web/20210901011839/https://helpx.adobe.com/illustrator/using/summary-effects.html) (archived copy).
- Initial live Revolve (#605, #846): a portable geometry and shading crate (`three-d`), editable path and compound profiles, compact options with artboard rotation and light gizmos, SVG/PDF vector output and Expand Appearance that can trim covered faces. Cap is on by default with continuous curved vector rims; shading uses vector gradients automatically, with sharp profile corners, uniform caps and round seam joins. One light and approximate visibility remain ([revolve.md](revolve.md)).
- Live effects with previewing dialogs: Distort & Transform, Path, Convert to Shape, 15 Warp styles, Round Corners, Scribble, Effect → Pathfinder (all 10 operations, live on groups; several loose objects are grouped first, in one undo step), Color Adjustments (Brightness/Contrast, Curves, Levels, Hue/Saturation, Shift to Color, Temperature/Tint, on vectors, live type and embedded images), and raster drop shadow, glows and feather. SVG and PDF export keep live effects (geometry baked; SVG raster effects as filters). Zig Zag spaces its ridges evenly along each segment, so lines and curves get a regular wave in Corner and Smooth (#1558). Warp › Arc (the effect and Envelope › Make with Warp) bends in the object's real proportions: a circular arc with concentric edges and no fold-over at 100%, so Arc on art that isn't square draws differently from before (#1581, fixes #1578). `effect.apply` and `effect.setParams` give an error for a misspelt or wrongly typed effect parameter instead of running the effect with its default (#1623, #1592). Roughen with Smooth keeps turn-backs smooth instead of making cusps that draw as spikes (#1646, #1643). Scribble draws one zig-zag per region, with no lines across holes and notches; convex shapes are unchanged (#1651, #1648).
- Scribble (Effect › Stylize › Scribble…) has a Variation for each of Path Overlap, Curviness and Spacing ([Scribble options](https://helpx.adobe.com/illustrator/using/creating-sketches-mosaics.html)) (`overlapVariation`, `curvinessVariation`, `spacingVariation`), shown in its dialog and in the Appearance panel. A Scribble saved with the earlier single `variation` reads it as the Path Overlap and Spacing variations and draws as before. The angle is stored above -180° and up to 180°, so 270° is stored as -90°.