Merge pull request #6917 from dotCooCoo/fix/ai-look-stroke-warning

.ai save: a stroke the object's art style keeps no longer warns
This commit is contained in:
BFlat
2026-11-13 05:24:11 +07:00
committed by GitHub
3 changed files with 70 additions and 5 deletions
+1 -1
View File
@@ -172,7 +172,7 @@
//! symbols past 4,096 as their art (symbol art past [`vectorcraft_doc::MAX_SYMBOL_NODES`] objects
//! in all left out), instances of symbols the document doesn't have as empty groups, symbols' 9-slice
//! guides, export types and dynamic type left out, live objects (placed documents included) as their art,
//! strokes a plain line can't draw
//! strokes a plain line can't draw and the object's art style doesn't hold
//! as their filled outlines, several fills or strokes as a group of a path for each, freeform
//! gradient swatches, gradient swatches of more than 256 stops, swatches of patterns written as
//! tiles and the placement of pattern swatches that move, turn or scale their pattern, the opacity and blending modes of single fills and strokes, opacity masks on
+64
View File
@@ -6758,6 +6758,70 @@ fn every_effect_setting_written_on_an_object_is_read_back() {
assert!(matches!(&spot.appearance.fill().unwrap().paint, Paint::Solid { swatch: Some(s), .. } if s == "Ink"), "{spot:?}");
}
/// A drawn look's strokes warn only where its art style leaves them out: a path with a drop shadow and
/// a stroke with a width profile is written with a style that holds the profile, and doesn't warn;
/// a group with a drop shadow whose member has a stroke inside the path still warns.
#[test]
fn a_drawn_looks_strokes_warn_only_where_its_style_leaves_them_out() {
let profile = vectorcraft_doc::WidthProfile { points: vec![(0.0, 1.0, 0.5), (0.5, 0.25, 0.75), (1.0, 0.0, 1.0)] };
let (n, warnings, p) = read_one(|doc| {
let mut stroke = StrokeLayer::new(black_fill(), 4.0);
stroke.profile = Some(profile.clone());
let ap = Appearance { items: vec![AppearanceItem::Stroke(stroke)], effects: vec![drop_shadow()], contents_index: None };
Node::path(doc.alloc_id(), shapes::rectangle(Rect::new(10.0, 10.0, 50.0, 40.0)), ap)
});
assert!(p.contains("%AI9_BeginArtStyles"), "{p}");
assert!(!warnings.iter().any(|w| w == writer::STROKES), "{warnings:?}");
assert_eq!(n.appearance.stroke().and_then(|s| s.profile.clone()), Some(profile));
let (_, warnings, p) = read_one(|doc| {
let mut n =
Node::path(doc.alloc_id(), shapes::rectangle(Rect::new(10.0, 10.0, 50.0, 40.0)), Appearance::basic(Paint::None, black_fill(), 2.0));
n.appearance.stroke_mut().unwrap().align = vectorcraft_doc::StrokeAlign::Inside;
let mut g = group(doc, vec![n], false);
g.appearance.effects = vec![drop_shadow()];
g
});
assert!(p.contains("%AI9_BeginArtStyles"), "{p}");
assert!(warnings.iter().any(|w| w == writer::STROKES), "{warnings:?}");
}
/// A path linked to a graphic style with a drop shadow and a stroke with a width profile is written
/// with that style, which holds the profile, and doesn't warn; it reads back linked, with its
/// profile. A linked path whose fill is half opaque in Screen mode isn't written with its style, which
/// the reader reads on no object, and warns that the fill's transparency is left out.
#[test]
fn a_linked_paths_drawn_look_warns_only_for_what_its_style_leaves_out() {
let profile = vectorcraft_doc::WidthProfile { points: vec![(0.0, 1.0, 0.5), (0.5, 0.25, 0.75), (1.0, 0.0, 1.0)] };
let linked = |doc: &mut Document, fill: FillLayer| {
let mut stroke = StrokeLayer::new(black_fill(), 4.0);
stroke.profile = Some(profile.clone());
let ap = Appearance {
items: vec![AppearanceItem::Fill(fill), AppearanceItem::Stroke(stroke)],
effects: vec![drop_shadow()],
contents_index: None,
};
let mut g = vectorcraft_doc::GraphicStyle::new("Linked", ap.clone());
g.id = 7;
doc.graphic_styles.push(g);
let mut n = Node::path(doc.alloc_id(), shapes::rectangle(Rect::new(10.0, 10.0, 50.0, 40.0)), ap);
n.extras_mut().graphic_style = Some(7);
n
};
let (n, warnings, p) = read_one(|doc| linked(doc, FillLayer::new(solid(Color::rgb(0.2, 0.2, 0.9)))));
assert!(p.contains("1 (Linked) XW"), "{warnings:?}");
assert!(!warnings.iter().any(|w| w == writer::STROKES), "{warnings:?}");
assert!(n.extras().graphic_style.is_some(), "{n:?}");
assert_eq!(n.appearance.stroke().and_then(|s| s.profile.clone()), Some(profile.clone()));
let (_, warnings, p) = read_one(|doc| {
let mut fill = FillLayer::new(solid(Color::rgb(0.2, 0.2, 0.9)));
fill.opacity = 0.5;
fill.blend = BlendMode::Screen;
linked(doc, fill)
});
assert!(!p.contains("1 (Linked) XW"), "{p}");
assert!(warnings.iter().any(|w| w == writer::ITEM_TRANSPARENCY), "{warnings:?}");
}
/// Each effect a graphic style can hold, in a style of its own, reads back with its settings, whatever
/// the writer's conversions to the file's units.
#[test]
+5 -4
View File
@@ -1037,10 +1037,11 @@ impl<'a> Writer<'a> {
AppearanceItem::Fill(f) => self.fill_object(path, rule, f, xy),
AppearanceItem::Stroke(s) if self.direct_stroke(s) => self.path_object(path, rule, None, Some(s), xy),
AppearanceItem::Stroke(s) => {
// In a drawn look a brushed stroke is kept by the object's art style, which is written
// only when the reader reads the brush back from it; a styled group's members are
// written again after the look, and warn there.
if !(self.in_look && s.brush.is_some()) {
// In a styled path's or compound path's drawn look the stroke, brushed or not, is kept
// by the object's art style, which is written only when the reader reads the object's
// appearance (and its brush) back from it. A styled group's style holds no fills or
// strokes: its members are written again after the look, and warn there.
if !self.in_look {
self.warn(STROKES);
}
self.wrote_fixed = true;