mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 13:36:53 +00:00
Merge pull request #7046 from dotCooCoo/fix/issue-3349
.ai import: graphic styles of one paint open with their fill and stroke
This commit is contained in:
@@ -424,25 +424,45 @@ fn a_graphic_style_library_in_a_ai_file_without_layers_loads_its_styles() {
|
||||
/BasicFilter :\n(Fill Style Filter) 0 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n/FillStyle : 0 O\n{xa}0 1 Xd\n/Def ;\n (FillStyle) ,\n; /Dict ;\n /Part ,\n;\n"
|
||||
)
|
||||
};
|
||||
// Styles of one paint (`/SimpleStyle`): a red 2 pt stroke and no fill, and a gradient fill,
|
||||
// which isn't read, so it is left out rather than listed with no paint.
|
||||
let one_paint = |name: &str, paint: &str| format!("/KnownStyle :\n({name}) /Name ,\n/SimpleStyle :\n{paint}/Paint ;\n/Def ;\n");
|
||||
let styles = format!(
|
||||
"%AI9_BeginArtStyles\n{}{}{}%AI9_EndArtStyles\n%AI9_BeginArtStyleList\n(Red)\n(Green)\n(Blue)\n%AI9_EndArtStyleList\n",
|
||||
"%AI9_BeginArtStyles\n{}{}{}{}{}%AI9_EndArtStyles\n\
|
||||
%AI9_BeginArtStyleList\n(Red)\n(Green)\n(Blue)\n(Red outline)\n(Sky fill)\n%AI9_EndArtStyleList\n",
|
||||
style("Blue", "0 0 0 0 0 0 1 Xa\n"),
|
||||
style("Green", "0 0 0 0 0 1 0 Xa\n"),
|
||||
style("Red", "0 0 0 0 1 0 0 Xa\n")
|
||||
style("Red", "0 0 0 0 1 0 0 Xa\n"),
|
||||
one_paint("Red outline", "0 R\n0 1 1 0 K\n0 J 0 j 2 w 10 M []0 d\n1 0 Xd\n"),
|
||||
one_paint("Sky fill", "0 O\n0 Bb\n2 (Sky) 0 0 0 100 1 0 0 1 0 0 Bg\n0 BB\n0 1 Xd\n")
|
||||
);
|
||||
let data = ai::editing_data(200.0, 100.0, &styles);
|
||||
let file = ai::ai(&compressed(data.as_bytes()), "1 0 0 rg 10 50 30 40 re f");
|
||||
let mut s = Session::new();
|
||||
let p = json!({"dataBase64": vectorcraft_format::base64_encode(&file), "name": "fills.ai"});
|
||||
let r = s.execute("graphicStyle.loadLibrary", &p).unwrap();
|
||||
assert_eq!((&r["name"], &r["count"]), (&json!("fills"), &json!(3)), "{r}");
|
||||
let lib = s.execute("graphicStyle.library", &json!({"library": r["library"]})).unwrap();
|
||||
assert_eq!((&r["name"], &r["count"]), (&json!("fills"), &json!(4)), "{r}");
|
||||
let library = r["library"].clone();
|
||||
let lib = s.execute("graphicStyle.library", &json!({"library": library})).unwrap();
|
||||
let names: Vec<&str> = lib["styles"].as_array().unwrap().iter().map(|g| g["name"].as_str().unwrap()).collect();
|
||||
assert_eq!(names, ["Red", "Green", "Blue"], "{lib}");
|
||||
assert_eq!(names, ["Red", "Green", "Blue", "Red outline"], "{lib}");
|
||||
// The outline style strokes a rectangle red, 2 pt wide, and leaves it unfilled.
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
let id = NodeId(s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap());
|
||||
let r = s.execute("graphicStyle.addFromLibrary", &json!({"library": library, "names": ["Red outline"], "apply": true, "ids": [id.0]})).unwrap();
|
||||
assert_eq!(r["added"], json!(["Red outline"]), "{r}");
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
let look = &doc.node(id).unwrap().appearance;
|
||||
assert!(look.fill().is_none(), "{look:?}");
|
||||
let strokes: Vec<_> = look.items.iter().filter(|i| matches!(i, vectorcraft_doc::AppearanceItem::Stroke(_))).collect();
|
||||
assert_eq!(strokes.len(), 1, "{look:?}");
|
||||
let stroke = look.stroke().unwrap();
|
||||
assert_eq!((stroke.paint.color(), stroke.width), (Some(vectorcraft_color::Color::cmyk(0.0, 1.0, 1.0, 0.0)), 2.0), "{look:?}");
|
||||
assert_eq!(doc.graphic_style("Red outline").map(|g| &g.appearance), Some(&**look));
|
||||
let r = open(&mut s, "fills.ai", &file, json!({})).unwrap();
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
let styles: Vec<(&str, u32)> = doc.graphic_styles.iter().map(|g| (g.name.as_str(), g.id)).collect();
|
||||
assert_eq!(styles, [("Red", 1), ("Green", 2), ("Blue", 3)], "{r}");
|
||||
assert_eq!(styles, [("Red", 1), ("Green", 2), ("Blue", 3), ("Red outline", 4)], "{r}");
|
||||
}
|
||||
|
||||
/// A `.ai` symbol library (symbols and their list, no layers) opens with its symbols, and Other
|
||||
|
||||
@@ -4318,6 +4318,63 @@ proptest! {
|
||||
survive("style library in an EPS", || vectorcraft_engine::cmd::fileio::load("x.eps", &eps).ok().map(|l| l.doc))?;
|
||||
}
|
||||
|
||||
/// Graphic style libraries whose styles are one paint (`/SimpleStyle`): any paint operators in
|
||||
/// any order, with any operands or none, `Xd` missing or repeated, and the paint left open. The
|
||||
/// library is also loaded in the Graphic Style Libraries panel and its styles applied to artwork.
|
||||
#[test]
|
||||
fn ai_style_libraries_of_one_paint_styles_never_panic(
|
||||
// Each operator, and whether it is a paint operator rather than any text.
|
||||
ops in prop::collection::vec(prop_oneof![
|
||||
prop::sample::select(vec![
|
||||
"0 R", "0 O", "0 1 1 0 K", "1 0 0 0 k", "0 0 0 0 1 0 0 Xa", "0 0 0 0 1 0 0 XA", "0.5 G", "0 g", "K", "Xa",
|
||||
"0 J 0 j 2 w 10 M []0 d", "-1 w", "1e400 w", "0 M", "[3 -1]0 d", "[]0 d",
|
||||
"1 0 Xd", "0 1 Xd", "1 1 Xd", "0 0 Xd", "1 Xd", "Xd", "0 1 0 0 0 Xy", "1 0.5 1 1 0 Xy", "Xy",
|
||||
"0 Bb", "2 (Sky) 0 0 0 100 1 0 0 1 0 0 Bg", "0 BB", "(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p",
|
||||
"(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] P", "0 XR",
|
||||
]).prop_map(|op| (op.to_string(), true)),
|
||||
"[ -~]{0,12}".prop_map(|text| (text, false)),
|
||||
], 0..12),
|
||||
closed in any::<bool>(),
|
||||
) {
|
||||
use vectorcraft_testkit::ai;
|
||||
let close = if closed { "/Paint ;\n/Def ;\n" } else { "" };
|
||||
// C, a red 2 pt stroke, comes first, so that it reads whatever A holds.
|
||||
let header = format!(
|
||||
"%AI9_BeginArtStyles\n/KnownStyle :\n(C) /Name ,\n/SimpleStyle :\n0 R\n0 1 1 0 K\n2 w\n1 0 Xd\n/Paint ;\n/Def ;\n\
|
||||
/KnownStyle :\n(A) /Name ,\n/SimpleStyle :\n{}\n{close}/KnownStyle :\n(B) /Name ,\n/SimpleStyle :\n1 0 Xd\n/Paint ;\n/Def ;\n\
|
||||
%AI9_EndArtStyles\n%AI9_BeginArtStyleList\n(A)\n(B)\n(C)\n%AI9_EndArtStyleList\n",
|
||||
ops.iter().map(|(op, _)| op.as_str()).collect::<Vec<_>>().join("\n")
|
||||
);
|
||||
let library = ai::editing_data(200.0, 100.0, &header);
|
||||
survive("one-paint style library", || Some(vectorcraft_eps::layered_ai(library.as_bytes(), Document::new(200.0, 100.0), vec![], false, false, 1.0).0))?;
|
||||
let file = ai::ai(library.as_bytes(), ai::page_pdf());
|
||||
// How many objects C's look reached, what applying it answered, and whether that look is
|
||||
// C's red 2 pt stroke with no fill.
|
||||
let (mut styled, mut applied, mut red_stroke) = (0, Value::Null, false);
|
||||
survive("one-paint styles applied", || {
|
||||
let mut s = rich_session();
|
||||
let p = json!({"dataBase64": vectorcraft_format::base64_encode(&file), "name": "fuzz.ai"});
|
||||
let library = no_panic(s.execute("graphicStyle.loadLibrary", &p)).ok()?["library"].clone();
|
||||
no_panic(s.execute("select.all", &json!({}))).ok()?;
|
||||
// Every style is added and the first one, A when it reads, applied; then C is applied.
|
||||
let _ = no_panic(s.execute("graphicStyle.addFromLibrary", &json!({"library": library, "apply": true})));
|
||||
applied = no_panic(s.execute("graphicStyle.addFromLibrary", &json!({"library": library, "names": ["C"], "apply": true}))).ok()?;
|
||||
let d = (*s.doc().ok()?.doc).clone();
|
||||
if let Some(look) = applied["applied"].as_str().and_then(|name| d.graphic_style(name)).map(|g| &g.appearance) {
|
||||
let strokes = look.items.iter().filter(|i| matches!(i, vectorcraft_doc::AppearanceItem::Stroke(_))).count();
|
||||
red_stroke = look.fill().is_none()
|
||||
&& strokes == 1
|
||||
&& look.stroke().is_some_and(|s| (s.paint.color(), s.width) == (Some(vectorcraft_color::Color::cmyk(0.0, 1.0, 1.0, 0.0)), 2.0));
|
||||
d.walk(|n| styled += usize::from(n.appearance == *look));
|
||||
}
|
||||
Some(d)
|
||||
})?;
|
||||
// Text that isn't a paint operator can leave a string or a comment open over the style list.
|
||||
if ops.iter().all(|(_, paint)| *paint) {
|
||||
prop_assert!(applied["applied"].is_string() && red_stroke && styled > 0, "the stroke style wasn't applied: {applied}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Objects on Path records of any shape: ids in any order, repeated or missing, any number of
|
||||
/// objects, pivots and angles out of range or not finite, spacings, order tags that disagree,
|
||||
/// groups nested or cut short, and versioned content left open.
|
||||
|
||||
@@ -1231,13 +1231,18 @@ fn styled(object: &Node, look: &Node, st: &crate::import::ai::style::ArtStyle, p
|
||||
|
||||
/// A graphic style's appearance from art style `st`, on no object: `None` when the style isn't one
|
||||
/// this reads, or paints with a gradient or a pattern (placed on each object as its drawn look has them).
|
||||
/// A style that is one paint is its fill and stroke ([`crate::import::ai::style::ArtStyle::one_paint`]).
|
||||
pub(super) fn appearance_from_art_style(st: &crate::import::ai::style::ArtStyle) -> Option<Appearance> {
|
||||
use crate::import::ai::style::StyleItem;
|
||||
if !st.plain {
|
||||
return None;
|
||||
}
|
||||
let style_items = match &st.one_paint {
|
||||
Some(one) => one.as_ref().ok()?,
|
||||
None => &st.items,
|
||||
};
|
||||
let mut items = vec![];
|
||||
for (n, item) in st.items.iter().enumerate() {
|
||||
for (n, item) in style_items.iter().enumerate() {
|
||||
let mut layer = match item {
|
||||
StyleItem::Fill(paint) => AppearanceItem::Fill(FillLayer::new(paint.clone())),
|
||||
// A graphic style's brush isn't checked against the file's brushes: it is left out.
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
//! gradient (`Bb` … `flag (name) x y angle length … Bg` … `BB`); `stroke fill Xd` says whether it
|
||||
//! paints at all. A blend filter's `mode opacity isolate knockout shape Xy` is its transparency.
|
||||
//!
|
||||
//! A style with no filters is one paint (`/SimpleStyle :` … `/Paint ;` in place of `/ActiveStyle`):
|
||||
//! the fill's and stroke's colors and the stroke's style as an object's own paint operators write
|
||||
//! them, `stroke fill Xd` turning each on, and an `Xy` for its transparency.
|
||||
//!
|
||||
//! A stroke with a Calligraphic or Art brush is its own basic filter, `(Adobe Calligraphic Brush
|
||||
//! Tool)` or `(Adobe ArtOnPath Brush Tool)`, with its stroke's style and the brush's settings for
|
||||
//! the object as entries (see [`super::brush`]).
|
||||
@@ -206,6 +210,11 @@ pub(super) struct ArtStyle {
|
||||
/// paint is a pattern: its [`StyleItem::Pattern`]. A text document's characters name such
|
||||
/// styles for their fills and strokes (`ate`). `None` for any other paint or style.
|
||||
pub simple_pattern: Option<StyleItem>,
|
||||
/// A style that is one paint: its fill and stroke, bottom first, each there when its
|
||||
/// `stroke fill Xd` turns it on (none when neither is on). `Err` when it turns on a fill or
|
||||
/// stroke whose paint isn't read here (a gradient, a pattern, a color that isn't read) or has
|
||||
/// no `Xd`. `None` for a style with filters, whose paint is [`Self::items`].
|
||||
pub one_paint: Option<Result<Vec<StyleItem>, ()>>,
|
||||
}
|
||||
|
||||
impl ArtStyle {
|
||||
@@ -412,12 +421,22 @@ pub(super) fn art_styles_in(data: &[u8], names: &mut Names) -> HashMap<String, A
|
||||
let mut in_basic = false;
|
||||
// A style that is one paint being read, and its pattern so far.
|
||||
let mut simple: Option<Option<StyleItem>> = None;
|
||||
let mut finish = |name: Option<String>, filters: &mut Vec<Filter>, simple_pattern: Option<StyleItem>| {
|
||||
// The same style's paint operators so far.
|
||||
let mut one_paint: Option<OnePaint> = None;
|
||||
let mut finish = |name: Option<String>, filters: &mut Vec<Filter>, simple_pattern: Option<StyleItem>, one_paint: Option<OnePaint>| {
|
||||
if let Some(name) = name {
|
||||
// Only a style that is one paint holds a pattern for type: one with filters as well
|
||||
// paints with more than that.
|
||||
let simple_pattern = simple_pattern.filter(|_| filters.is_empty());
|
||||
styles.insert(name, ArtStyle { simple_pattern, ..style(filters) });
|
||||
let mut st = ArtStyle { simple_pattern, ..style(filters) };
|
||||
if let Some(one) = one_paint.filter(|_| filters.is_empty()) {
|
||||
if let Some((mode, opacity)) = one.paint.xy {
|
||||
let (isolate, knockout) = one.paint.xy_group.unwrap_or((false, Knockout::Off));
|
||||
st.transparency = Some((opacity, super::read::blend_mode(mode), isolate, knockout));
|
||||
}
|
||||
st.one_paint = Some(one.items());
|
||||
}
|
||||
styles.insert(name, st);
|
||||
}
|
||||
filters.clear();
|
||||
};
|
||||
@@ -454,12 +473,12 @@ pub(super) fn art_styles_in(data: &[u8], names: &mut Names) -> HashMap<String, A
|
||||
}
|
||||
Tok::Name(n) => match n {
|
||||
b"KnownStyle" => {
|
||||
finish(name.take(), &mut filters, simple.take().flatten());
|
||||
finish(name.take(), &mut filters, simple.take().flatten(), one_paint.take());
|
||||
operands.clear();
|
||||
open.clear();
|
||||
(pending, in_basic, style_entry, other_entry) = (None, false, None, None);
|
||||
}
|
||||
b"SimpleStyle" if filters.is_empty() => simple = Some(None),
|
||||
b"SimpleStyle" if filters.is_empty() => (simple, one_paint) = (Some(None), Some(OnePaint::default())),
|
||||
b"FillStyle" => style_entry = Some(Kind::Fill),
|
||||
b"StrokeStyle" | b"BlendStyle" => style_entry = Some(Kind::Other),
|
||||
b"Name" if name.is_none() && filters.is_empty() => name = operands.iter().rev().find_map(|v| names.text(v)),
|
||||
@@ -559,7 +578,7 @@ pub(super) fn art_styles_in(data: &[u8], names: &mut Names) -> HashMap<String, A
|
||||
f.repeated |= f.kinds.insert(key, kind).is_some();
|
||||
}
|
||||
}
|
||||
style_paint_op(&mut filters, &mut simple, op, &operands, names);
|
||||
style_paint_op(&mut filters, &mut simple, &mut one_paint, op, &operands, names);
|
||||
if op == "," || op == ";" || op.starts_with('X') || op.starts_with('B') || op.len() <= 2 {
|
||||
operands.clear();
|
||||
}
|
||||
@@ -571,7 +590,7 @@ pub(super) fn art_styles_in(data: &[u8], names: &mut Names) -> HashMap<String, A
|
||||
operands.clear();
|
||||
}
|
||||
}
|
||||
finish(name, &mut filters, simple.take().flatten());
|
||||
finish(name, &mut filters, simple.take().flatten(), one_paint.take());
|
||||
styles
|
||||
}
|
||||
|
||||
@@ -729,13 +748,99 @@ fn pattern_operands(vals: &[V], names: &mut Names) -> Option<(String, Option<[f6
|
||||
}
|
||||
|
||||
/// A paint operator in a style being read: one of the last filter's (`filters`), else one of a style
|
||||
/// that is one paint (`simple`, while one is read).
|
||||
fn style_paint_op(filters: &mut [Filter], simple: &mut Option<Option<StyleItem>>, op: &str, vals: &[V], names: &mut Names) {
|
||||
/// that is one paint (`simple` and `one_paint`, while one is read).
|
||||
fn style_paint_op(
|
||||
filters: &mut [Filter],
|
||||
simple: &mut Option<Option<StyleItem>>,
|
||||
one_paint: &mut Option<OnePaint>,
|
||||
op: &str,
|
||||
vals: &[V],
|
||||
names: &mut Names,
|
||||
) {
|
||||
if let Some(f) = filters.last_mut() {
|
||||
paint_op(f, op, vals, names);
|
||||
} else if let Some(paint) = simple.as_mut() {
|
||||
return;
|
||||
}
|
||||
if let Some(paint) = simple.as_mut() {
|
||||
simple_paint_op(paint, op, vals, names);
|
||||
}
|
||||
if let Some(one) = one_paint.as_mut() {
|
||||
one.op(op, vals, names);
|
||||
}
|
||||
}
|
||||
|
||||
/// The paint operators of a style that is one paint (`/SimpleStyle :` … `/Paint ;`), read as a
|
||||
/// fill's or stroke's style reads them.
|
||||
#[derive(Default)]
|
||||
struct OnePaint {
|
||||
paint: Filter,
|
||||
/// Its `stroke fill Xd` was read.
|
||||
xd: bool,
|
||||
/// Its last fill paint is a pattern (`p`) or may be a gradient (`Bg`).
|
||||
fill_unread: bool,
|
||||
/// Its last stroke paint is a pattern (`P`) or may be a gradient (`Bg`).
|
||||
stroke_unread: bool,
|
||||
/// Inside a gradient instance (`flag Bb` … `BB`): whether it paints the stroke (flag 1).
|
||||
instance: Option<bool>,
|
||||
}
|
||||
|
||||
impl OnePaint {
|
||||
fn op(&mut self, op: &str, vals: &[V], names: &mut Names) {
|
||||
match op {
|
||||
"Xd" => self.xd |= vals.iter().filter_map(V::num).count() >= 2,
|
||||
"p" => self.fill_unread = true,
|
||||
"P" => self.stroke_unread = true,
|
||||
// The flag of the instance it is in says which one it paints, as an object's does
|
||||
// (`Instance::begin` in `paint.rs`); outside one it is read as either one's.
|
||||
"Bb" => self.instance = Some(vals.iter().filter_map(V::num).next_back() == Some(1.0)),
|
||||
"BB" => self.instance = None,
|
||||
"Bg" => match self.instance {
|
||||
Some(true) => self.stroke_unread = true,
|
||||
Some(false) => self.fill_unread = true,
|
||||
None => (self.fill_unread, self.stroke_unread) = (true, true),
|
||||
},
|
||||
// A color replaces what came before it; one that isn't read leaves none.
|
||||
"Xa" | "k" | "g" | "x" | "Xx" | "Xk" | "Xs" | "Xz" | "xa" | "xx" | "xk" | "xs" | "xz" => {
|
||||
(self.fill_unread, self.paint.fill) = (false, None);
|
||||
}
|
||||
"XA" | "K" | "G" | "X" | "XX" | "XK" | "XS" | "XZ" => (self.stroke_unread, self.paint.stroke) = (false, None),
|
||||
_ => {}
|
||||
}
|
||||
paint_op(&mut self.paint, op, vals, names);
|
||||
}
|
||||
|
||||
/// Its fill and stroke ([`ArtStyle::one_paint`]).
|
||||
fn items(&self) -> Result<Vec<StyleItem>, ()> {
|
||||
let f = &self.paint;
|
||||
if !self.xd {
|
||||
return Err(());
|
||||
}
|
||||
let (stroked, filled) = f.paints;
|
||||
let mut items = vec![];
|
||||
if filled {
|
||||
match &f.fill {
|
||||
Some(p) if !self.fill_unread => items.push(StyleItem::Fill(p.clone())),
|
||||
_ => return Err(()),
|
||||
}
|
||||
}
|
||||
if stroked {
|
||||
match &f.stroke {
|
||||
Some(p) if !self.stroke_unread => items.push(StyleItem::Stroke {
|
||||
paint: p.clone(),
|
||||
width: f.width,
|
||||
cap: f.cap,
|
||||
join: f.join,
|
||||
miter: f.miter,
|
||||
dash: f.dash.clone(),
|
||||
profile: None,
|
||||
align: StrokeAlign::Center,
|
||||
brush: None,
|
||||
}),
|
||||
_ => return Err(()),
|
||||
}
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
/// A paint operator of a style that is one paint: a pattern (`p`) is kept as its
|
||||
|
||||
@@ -1512,3 +1512,48 @@ fn only_a_style_of_one_pattern_paint_holds_a_pattern_for_type() {
|
||||
assert!(styles(&body).get("S").is_none_or(|s| s.simple_pattern.is_none()), "{body}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A style that is one paint reads as the fill and stroke its `Xd` turns on, in the paint's colors
|
||||
/// and the stroke's style; one that turns on a paint not read here, or has no `Xd`, isn't read.
|
||||
#[test]
|
||||
fn a_style_of_one_paint_reads_as_the_fill_and_stroke_its_xd_turns_on() {
|
||||
let one = |paint: &str| {
|
||||
let data = format!("/KnownStyle :\n(S) /Name ,\n/SimpleStyle :\n{paint}/Paint ;\n/Def ;\n");
|
||||
art_styles_in(data.as_bytes(), &mut Names::default()).remove("S").and_then(|s| s.one_paint)
|
||||
};
|
||||
let stroke = "0 R\n0 1 1 0 K\n1 J 2 j 3 w 4 M [2 1]0 d\n";
|
||||
let Some(Ok(items)) = one(&format!("0 O\n0 0 1 0 k\n{stroke}1 1 Xd\n")) else { panic!("{:?}", one(stroke)) };
|
||||
let [StyleItem::Fill(fill), StyleItem::Stroke { paint, width, cap, join, miter, dash, profile: None, align: StrokeAlign::Center, brush: None }] =
|
||||
items.as_slice()
|
||||
else {
|
||||
panic!("{items:?}")
|
||||
};
|
||||
assert_eq!((fill.color(), paint.color()), (Some(Color::cmyk(0.0, 0.0, 1.0, 0.0)), Some(Color::cmyk(0.0, 1.0, 1.0, 0.0))));
|
||||
assert_eq!((*width, *cap, *join, *miter), (3.0, LineCap::Round, LineJoin::Bevel, 4.0));
|
||||
assert_eq!(dash.as_ref().map(|d| d.pattern.clone()), Some(vec![2.0, 1.0]));
|
||||
// Only what `Xd` turns on; nothing when it turns on neither.
|
||||
assert!(matches!(one(&format!("0 0 1 0 k\n{stroke}1 0 Xd\n")), Some(Ok(i)) if matches!(i.as_slice(), [StyleItem::Stroke { .. }])));
|
||||
assert_eq!(one(&format!("0 0 1 0 k\n{stroke}0 0 Xd\n")), Some(Ok(vec![])));
|
||||
// The last paint of each one counts: a solid color after a pattern or a gradient replaces it.
|
||||
let solid_fill = |before: &str| one(&format!("{before}\n0 0 1 0 k\n0 1 Xd\n"));
|
||||
for before in ["(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p", "0 Bb\n2 (Sky) 0 0 0 100 1 0 0 1 0 0 Bg\n0 BB"] {
|
||||
assert!(matches!(solid_fill(before), Some(Ok(i)) if matches!(i.as_slice(), [StyleItem::Fill(_)])), "{before}");
|
||||
}
|
||||
let solid_stroke = one("(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] P\n0 1 1 0 K\n1 0 Xd\n");
|
||||
assert!(matches!(solid_stroke, Some(Ok(i)) if matches!(i.as_slice(), [StyleItem::Stroke { .. }])));
|
||||
// A gradient instance's flag says which one it paints: a stroke gradient leaves a solid fill read.
|
||||
let fill_and_stroke_gradient = one("0 0 1 0 k\n1 Bb\n2 (Ramp) 0 0 90 1 1 0 0 1 0 0 1 Bg\n0 BB\n0 1 Xd\n");
|
||||
assert!(matches!(fill_and_stroke_gradient, Some(Ok(i)) if matches!(i.as_slice(), [StyleItem::Fill(_)])));
|
||||
// Not read: no `Xd`, a gradient or pattern fill, a pattern stroke, a gradient stroke after a solid one, a paint
|
||||
// turned on with no color.
|
||||
for paint in [
|
||||
format!("0 0 1 0 k\n{stroke}"),
|
||||
"0 Bb\n2 (Sky) 0 0 0 100 1 0 0 1 0 0 Bg\n0 BB\n0 1 Xd\n".to_string(),
|
||||
"(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p\n0 1 Xd\n".to_string(),
|
||||
"(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] P\n1 0 Xd\n".to_string(),
|
||||
"0 R\n0 1 1 0 K\n1 Bb\n0 0 0 0 Bh\n2 (Ramp) 0 0 90 1 1 0 0 1 0 0 1 Bg\n0 BB\n2 w\n1 0 Xd\n".to_string(),
|
||||
"0 0 1 0 k\n1 1 Xd\n".to_string(),
|
||||
] {
|
||||
assert_eq!(one(&paint), Some(Err(())), "{paint}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user