mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
.ai import: a Drop Shadow with Darkness comes in with its Darkness
An object whose art style had a Drop Shadow with Darkness (a csrc other than 0 in the shadow filter's dictionary) came in as its drawn look, with a warning that its appearance isn't editable yet. With a Color shadow instead, the same object comes in as itself with its fill, stroke and shadow. The reader now reads such a shadow as Drop Shadow with shade "darkness" and darkness set to the filter's dark value, a percentage kept within 0 to 100, or 100 when the value is missing or not a finite number. The shadow's other keys are read as for a shadow with Color, and the object comes in as itself wherever it would with a shadow with Color. A shadow with Color comes in with the same params as before. The import fuzz's .ai tokens now include csrc and dark. Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -2543,6 +2543,8 @@ fn arb_ai_token() -> impl Strategy<Value = String> {
|
||||
"1 /Bool (BSDashAdj) ,",
|
||||
"0.75 /Real (opac) ,",
|
||||
"1e308 /Real (blur) ,",
|
||||
"1 /Int (csrc) ,",
|
||||
"-1e308 /Real (dark) ,",
|
||||
"0 /Real ,",
|
||||
"; (BSVariableWidthTValuesArray) ,",
|
||||
"; (BSCenteredWidthsArray) ,",
|
||||
|
||||
@@ -330,9 +330,8 @@ 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,
|
||||
}),
|
||||
SHADOW if param("csrc").unwrap_or(0.0) == 0.0 => effects.push(Effect {
|
||||
id: "stylize.dropShadow".into(),
|
||||
params: json!({
|
||||
SHADOW => {
|
||||
let mut params = json!({
|
||||
"mode": mode(1),
|
||||
"opacity": param("opac").unwrap_or(0.75) * 100.0,
|
||||
"blur": 2.0 * param("blur").unwrap_or(5.0),
|
||||
@@ -341,11 +340,15 @@ fn style(filters: &[Filter]) -> ArtStyle {
|
||||
"x": param("horz").unwrap_or(7.0),
|
||||
"y": param("vert").unwrap_or(7.0),
|
||||
"color": color,
|
||||
}),
|
||||
visible: true,
|
||||
}),
|
||||
// A shadow darkening the object's own colors is not read as Drop Shadow's Darkness.
|
||||
SHADOW => plain = false,
|
||||
});
|
||||
// A `csrc` of 1 is Darkness, the object's own colors with black added; any value other than 0 is read as
|
||||
// Darkness. `dark` is the percent of black (0 to 100, where `opac` is a fraction), and 100 when it is missing or not finite.
|
||||
if param("csrc").unwrap_or(0.0) != 0.0 {
|
||||
params["shade"] = json!("darkness");
|
||||
params["darkness"] = json!(param("dark").filter(|v| v.is_finite()).map_or(100.0, |v| v.clamp(0.0, 100.0)));
|
||||
}
|
||||
effects.push(Effect { id: "stylize.dropShadow".into(), params, visible: true });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -393,6 +396,50 @@ mod tests {
|
||||
assert_eq!((shadow.params["x"].as_f64(), shadow.params["y"].as_f64()), (Some(7.0), Some(7.0)));
|
||||
}
|
||||
|
||||
/// The style `Shade`: a Drop Shadow whose dictionary holds `keys`.
|
||||
fn shadow_style(keys: &str) -> ArtStyle {
|
||||
let data = format!(
|
||||
"/KnownStyle :\n(Shade) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\
|
||||
/BasicFilter :\n(Adobe Drop Shadow) 1 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n{keys}; /Dict ;\n /Part ,\n;\n"
|
||||
);
|
||||
art_styles(data.as_bytes()).remove("Shade").unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_drop_shadow_with_darkness_reads_its_darkness() {
|
||||
// GUARDS: a `csrc` other than 0 made the style not plain, and the object came in as its drawn look.
|
||||
let style = shadow_style("1 /Int (blnd) ,\n0.75 /Real (opac) ,\n5 /Real (blur) ,\n1 /Int (csrc) ,\n50 /Real (dark) ,\n");
|
||||
assert!(style.plain, "{style:?}");
|
||||
let [shadow] = style.effects.as_slice() else { panic!("{style:?}") };
|
||||
let p = &shadow.params;
|
||||
assert_eq!(
|
||||
(shadow.id.as_str(), &p["shade"], &p["darkness"], &p["opacity"], &p["blur"]),
|
||||
("stylize.dropShadow", &json!("darkness"), &json!(50.0), &json!(75.0), &json!(10.0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_drop_shadow_darkness_is_100_percent_when_missing_and_kept_within_0_to_100() {
|
||||
for (dark, want) in [("", 100.0), ("(fifty) /Real (dark) ,\n", 100.0), ("250 /Real (dark) ,\n", 100.0), ("-5 /Real (dark) ,\n", 0.0)] {
|
||||
let style = shadow_style(&format!("1 /Int (csrc) ,\n{dark}"));
|
||||
assert!(style.plain, "{dark:?}: {style:?}");
|
||||
let [shadow] = style.effects.as_slice() else { panic!("{dark:?}: {style:?}") };
|
||||
assert_eq!((&shadow.params["shade"], &shadow.params["darkness"]), (&json!("darkness"), &json!(want)), "{dark:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_drop_shadow_with_color_keeps_its_params() {
|
||||
// GUARDS: a `csrc` of 0, or none, is Color; its shadow has neither `shade` nor `darkness`.
|
||||
for csrc in ["", "0 /Int (csrc) ,\n50 /Real (dark) ,\n"] {
|
||||
let style = shadow_style(&format!("2 /Int (blnd) ,\n0.5 /Real (opac) ,\n{csrc}"));
|
||||
assert!(style.plain, "{csrc:?}: {style:?}");
|
||||
let [shadow] = style.effects.as_slice() else { panic!("{csrc:?}: {style:?}") };
|
||||
let want = json!({"mode": "Screen", "opacity": 50.0, "blur": 10.0, "x": 7.0, "y": 7.0, "color": vectorcraft_color::Color::BLACK});
|
||||
assert_eq!(shadow.params, want, "{csrc:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_dash_offsets_and_width_profiles_are_tamed() {
|
||||
let mut f = Filter::default();
|
||||
|
||||
@@ -1176,6 +1176,26 @@ fn a_styled_object_inside_a_drawn_look_survives_it() {
|
||||
assert_eq!((shadow.id.as_str(), shadow.params["x"].as_f64(), shadow.params["y"].as_f64()), ("stylize.dropShadow", Some(3.0), Some(4.0)));
|
||||
}
|
||||
|
||||
/// An object whose style's Drop Shadow has Darkness (`csrc` 1, `dark` 50) comes in as itself with
|
||||
/// that shadow, not as its drawn look.
|
||||
#[test]
|
||||
fn an_object_with_a_darkness_shadow_comes_in_as_itself() {
|
||||
let styles = ART_STYLES.replace("3 /Real (horz) ,\n", "1 /Int (csrc) ,\n50 /Real (dark) ,\n3 /Real (horz) ,\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%_{hidden_rect}f\n1 (Blue) XW\n", rect_path(0.0, 0.0, 10.0, 10.0));
|
||||
let r = read_ok(&format!("{styles}{}", layer(&art_)));
|
||||
assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings);
|
||||
let a = art(&r.doc);
|
||||
assert_eq!(a.len(), 1, "{a:#?}");
|
||||
assert!(matches!(a[0].kind, NodeKind::Path { .. }), "the object, not its look: {a:#?}");
|
||||
assert_eq!(fill(&a[0]).color(), Some(Color::rgb(0.0, 0.0, 1.0)));
|
||||
let [shadow] = a[0].appearance.effects.as_slice() else { panic!("{:?}", a[0].appearance.effects) };
|
||||
assert_eq!(
|
||||
(shadow.id.as_str(), shadow.params["shade"].as_str(), shadow.params["darkness"].as_f64(), shadow.params["x"].as_f64()),
|
||||
("stylize.dropShadow", Some("darkness"), Some(50.0), Some(3.0))
|
||||
);
|
||||
}
|
||||
|
||||
/// A styled object read in place of its drawn look keeps the transparency written on the look (the
|
||||
/// object's own, after the look's group ends): here 60 % in Multiply.
|
||||
#[test]
|
||||
|
||||
@@ -683,7 +683,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
|
||||
- **Raster effects after document and view changes:** a cached drop shadow, outer glow or filtered content is made again when something its content reads from outside the object changes: a pattern's art or tiling, a symbol shown inside another symbol, a brush definition, an image's pixels, the raster resolution (De-Interlace), Page Knockout Group, Document Setup's substitution highlights, a layer's Dim Images, the color settings, the fonts, Pixel Preview, Anti-aliased Artwork, Overprint Preview and isolation mode. A thumbnail under Color Halftone or Mezzotint is made again when the document's color mode changes. Raster effects (blur, shadow, glow, grain) drawn after a pattern, radial or freeform gradient fill export at their place and size (#1541, fixes #1536).
|
||||
|
||||
- **Drop Shadow's Darkness:** Effect › Stylize › Drop Shadow… has Color and Darkness as radio buttons, each with its field, and the Appearance panel's inline editor has the two radio buttons in its Shade row (`shade`: `color` or `darkness`; `darkness`: 0 to 100%, 100 by default). Color is the default, and a shadow saved without these keys draws as before. With Darkness the shadow is the object's own colors with black added: in a CMYK document their cyan, magenta and yellow stay and their black K becomes K + darkness × (1 − K), and in an RGB document each channel is multiplied by 1 − darkness. Darkness 0% gives the object's color; 100% gives black in an RGB document and keeps the colors under 100% black in a CMYK one. SVG export writes the object blurred, moved and darkened with `feComponentTransfer` (screen colors, in a CMYK document too), and SVG import reads it back; CSS export leaves the shadow out and marks the object unsupported. Behavior reference: Illustrator CC Help (2014, [PDF](https://help.adobe.com/archive/en/illustrator/cc/2014/illustrator_reference.pdf)), page 428 (PDF page 434).
|
||||
- **Drop Shadow's Darkness:** Effect › Stylize › Drop Shadow… has Color and Darkness as radio buttons, each with its field, and the Appearance panel's inline editor has the two radio buttons in its Shade row (`shade`: `color` or `darkness`; `darkness`: 0 to 100%, 100 by default). Color is the default, and a shadow saved without these keys draws as before. With Darkness the shadow is the object's own colors with black added: in a CMYK document their cyan, magenta and yellow stay and their black K becomes K + darkness × (1 − K), and in an RGB document each channel is multiplied by 1 − darkness. Darkness 0% gives the object's color; 100% gives black in an RGB document and keeps the colors under 100% black in a CMYK one. SVG export writes the object blurred, moved and darkened with `feComponentTransfer` (screen colors, in a CMYK document too), and SVG import reads it back; CSS export leaves the shadow out and marks the object unsupported. When a `.ai`, EPS or PDF opens from its editing data, a Drop Shadow with Darkness in an object's art style is read as one with Color is, with the shadow's Darkness percentage (100% when the style gives none). Behavior reference: Illustrator CC Help (2014, [PDF](https://help.adobe.com/archive/en/illustrator/cc/2014/illustrator_reference.pdf)), page 428 (PDF page 434).
|
||||
|
||||
- **Live objects after document changes:** a blend, envelope or repeat, with or without an effect of its own, is evaluated again when one of these changes: the tiling of a pattern whose tiles an envelope bends (Distort Pattern Fills), a symbol shown inside a symbol, a pattern or an opacity mask, the fonts of its type, a placed document's file once the file is read, and the color settings. A gradient mesh, with or without an effect of its own, is colored again when the color settings change.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user