mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 10:47:11 -06:00
Merge pull request #3064 from nrq6993-a11y/stream/vc-feat3-g9-ai-feather-art-style
G9: .ai import reads Adobe Feather art styles as live effects
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
//! ```
|
||||
//!
|
||||
//! A style is *plain* when it is one fill and one stroke at most, in solid colours (a fill's gradient
|
||||
//! is the object's own), and only effects read here (Outer Glow, Drop Shadow) after them. Such
|
||||
//! is the object's own), and only effects read here (Outer Glow, Drop Shadow, Feather) after them. Such
|
||||
//! objects are read as themselves with that fill, stroke and those effects; others keep their
|
||||
//! drawn look. A fill's or stroke's style says whether it paints with `stroke fill Xd`.
|
||||
|
||||
@@ -36,9 +36,10 @@ use super::obj::V;
|
||||
use super::paint;
|
||||
|
||||
/// The filters a plain style may chain.
|
||||
const PLAIN: [&str; 7] =
|
||||
["Chain Style Filter", "Stack Style Filter", "Stroke Style Filter", "Fill Style Filter", "Conduit Filter", "Blend Style Filter", GLOW];
|
||||
const PLAIN: [&str; 8] =
|
||||
["Chain Style Filter", "Stack Style Filter", "Stroke Style Filter", "Fill Style Filter", "Conduit Filter", "Blend Style Filter", GLOW, FEATHER];
|
||||
const GLOW: &str = "Adobe Outer Glow"; // brand-ok: the filter's name in the files
|
||||
const FEATHER: &str = "Adobe Feather"; // brand-ok: the filter's name in the files
|
||||
const SHADOW: &str = "Adobe Drop Shadow"; // brand-ok: the filter's name in the files
|
||||
|
||||
/// A plain style's paint, as the object comes in.
|
||||
@@ -307,7 +308,7 @@ fn style(filters: &[Filter]) -> ArtStyle {
|
||||
}
|
||||
}
|
||||
for (i, f) in filters.iter().enumerate() {
|
||||
let effect = f.name == GLOW || f.name == SHADOW;
|
||||
let effect = f.name == GLOW || f.name == FEATHER || f.name == SHADOW;
|
||||
if !PLAIN.contains(&f.name.as_str()) && f.name != SHADOW {
|
||||
plain = false;
|
||||
}
|
||||
@@ -330,6 +331,12 @@ fn style(filters: &[Filter]) -> ArtStyle {
|
||||
params: json!({"mode": mode(2), "opacity": param("opac").unwrap_or(0.75) * 100.0, "blur": 2.0 * param("blur").unwrap_or(5.0), "color": color}),
|
||||
visible: true,
|
||||
}),
|
||||
// LiveEffect XML uses `radius`; art styles abbreviate it `rad` (public scripting posts on applyEffect).
|
||||
FEATHER => effects.push(Effect {
|
||||
id: "stylize.feather".into(),
|
||||
params: json!({"radius": param("rad").or_else(|| param("radius")).filter(|v| v.is_finite()).unwrap_or(5.0)}),
|
||||
visible: true,
|
||||
}),
|
||||
SHADOW => {
|
||||
let mut params = json!({
|
||||
"mode": mode(1),
|
||||
@@ -440,6 +447,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_feather_reads_its_radius_from_rad() {
|
||||
let data = b"/KnownStyle :\n(Soft) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\
|
||||
/BasicFilter :\n(Adobe Feather) 1 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n12 /Real (rad) ,\n; /Dict ;\n /Part ,\n;\n";
|
||||
let styles = art_styles(data);
|
||||
let [feather] = styles["Soft"].effects.as_slice() else { panic!() };
|
||||
assert_eq!((feather.id.as_str(), feather.params["radius"].as_f64()), ("stylize.feather", Some(12.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_feather_falls_back_to_radius_and_defaults_to_five_points() {
|
||||
let with_name = b"/KnownStyle :\n(Soft) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\
|
||||
/BasicFilter :\n(Adobe Feather) 1 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n3 /Real (radius) ,\n; /Dict ;\n /Part ,\n;\n";
|
||||
let styles = art_styles(with_name);
|
||||
let [feather] = styles["Soft"].effects.as_slice() else { panic!() };
|
||||
assert_eq!(feather.params["radius"].as_f64(), Some(3.0));
|
||||
|
||||
let bare = b"/KnownStyle :\n(Soft) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\
|
||||
/BasicFilter :\n(Adobe Feather) 1 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n; /Dict ;\n /Part ,\n;\n";
|
||||
let styles = art_styles(bare);
|
||||
let [feather] = styles["Soft"].effects.as_slice() else { panic!() };
|
||||
assert_eq!(feather.params["radius"].as_f64(), Some(5.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_dash_offsets_and_width_profiles_are_tamed() {
|
||||
let mut f = Filter::default();
|
||||
|
||||
@@ -1243,6 +1243,25 @@ fn an_object_with_a_style_this_reads_comes_in_as_itself() {
|
||||
assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_object_with_a_feather_style_comes_in_with_a_live_effect() {
|
||||
let feather_style = "%AI9_BeginArtStyles\n\
|
||||
/KnownStyle :\n(Soft) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\
|
||||
/CompoundFilter :\n(Stack Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\
|
||||
/BasicFilter :\n(Fill Style Filter) 0 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n/FillStyle : 0 O\n0 0 0 0 0 0 1 Xa\n0 1 Xd\n/Def ;\n (FillStyle) ,\n; /Dict ;\n /Part ,\n\
|
||||
/BasicFilter :\n(Conduit Filter) 0 0 /Filter ,\n1 /Visible ,\n;\n /Part ,\n;\n /Part ,\n\
|
||||
/BasicFilter :\n(Adobe Feather) 1 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n8 /Real (rad) ,\n; /Dict ;\n /Part ,\n;\n\
|
||||
%AI9_EndArtStyles\n";
|
||||
let hidden_rect = rect_path(0.0, 0.0, 10.0, 10.0).replace('\n', "\n%_");
|
||||
let art_ = format!("0 Ae\nu\n{RED}{}f\nU\n%_Bb\n%_{hidden_rect}f\n%_0 BB\n1 (Soft) XW\n", rect_path(0.0, 0.0, 10.0, 10.0));
|
||||
let r = read_ok(&format!("{feather_style}{}", layer(&art_)));
|
||||
let a = art(&r.doc);
|
||||
assert_eq!(a.len(), 1);
|
||||
let [feather] = a[0].appearance.effects.as_slice() else { panic!("{:#?}", a[0].appearance) };
|
||||
assert_eq!((feather.id.as_str(), feather.params["radius"].as_f64()), ("stylize.feather", Some(8.0)));
|
||||
assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bare_comment_line_in_a_prolog_leaves_its_end_alone() {
|
||||
// GUARDS: a bare `%` line's empty marker matched the empty begin marker of a section that has none.
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
|
||||
- **Missing:** faithful layer and group structure from the editing data in real files (#951: 109 layers instead
|
||||
of 14), guides and non-printing construction
|
||||
lines (#779), open speed on large files (#758), live effects, brushes and symbols as live objects (#637; pattern fills and global colours are read since #1025, placed (linked) files since #1264). Writing Illustrator's own editing data is out of scope
|
||||
lines (#779), open speed on large files (#758), live effects, brushes and symbols as live objects (#637; plain art styles read Drop Shadow, Outer Glow and Feather as live effects; pattern fills and global colours are read since #1025, placed (linked) files since #1264). Writing Illustrator's own editing data is out of scope
|
||||
(undocumented): a PDF-compatible `.ai` opens in Illustrator 2018 without its layers and groups (#868). Save As
|
||||
`.ai` › Version: Legacy (version 7) writes the PostScript form from the published version 7 specification
|
||||
instead, keeping layers (names, order, visibility, lock), groups, compound paths, clipping groups, gradients and
|
||||
|
||||
Reference in New Issue
Block a user