//! Styles, swatches, stroke styles, colour groups and preferences.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use craft_idml::model::{
ColorSpace, GradientStop, StrokeStyleKind, StyleGroup, StyleKind, StyleNode, SwatchKind, color_groups, named, preference, stroke_styles,
style_groups, swatches,
};
use craft_idml::{El, parse_part, part_root, part_xml};
const STYLES: &str = r#"
CharacterStyle/$ID/[No character style]
$ID/[No paragraph style]22
"#;
fn top(xml: &str) -> Vec {
parse_part(xml.as_bytes()).unwrap().elements().cloned().collect()
}
#[test]
fn style_groups_give_their_styles_with_their_group_path() {
let groups = style_groups(&top(STYLES));
let kinds: Vec = groups.iter().map(|g| g.kind).collect();
assert_eq!(kinds, [StyleKind::Character, StyleKind::Paragraph, StyleKind::Object, StyleKind::Cell, StyleKind::Table]);
assert!(groups.iter().all(|g| g.root));
let chars = groups[0].styles();
assert_eq!(
chars.iter().map(|(path, s)| (path.join("/"), s.values.name().unwrap())).collect::>(),
[(String::new(), "$ID/[No character style]"), (String::new(), "Em"), ("Inline/Code".to_string(), "Inline:Code:Mono"),]
);
let em = chars[1].1;
assert_eq!(
(em.kind, em.based_on().as_deref(), em.values.get("FontStyle")),
(StyleKind::Character, Some("CharacterStyle/$ID/[No character style]"), Some("Italic"))
);
let head = groups[1].styles()[0].1;
assert_eq!((head.based_on().as_deref(), head.next_style().as_deref()), (Some("$ID/[No paragraph style]"), Some("ParagraphStyle/Body")));
assert_eq!((head.values.num("PointSize"), head.values.num("Leading")), (Some(18.0), Some(22.0)));
let frame = groups[2].styles()[0].1;
assert_eq!(frame.other("TextFramePreference").and_then(|p| p.attr("TextColumnCount")), Some("3"));
assert!(frame.other("TransparencySetting").is_some());
// A group inside a group is its own node, with its own values.
let StyleNode::Group(inline) = &groups[0].children[2] else { panic!() };
assert!(!inline.root);
assert_eq!(inline.styles()[0].0, ["Inline", "Code"], "a group's own name starts its path");
// Not a root style group.
assert_eq!(StyleGroup::from_el(&El::new("TOCStyle")), None);
}
#[test]
fn style_groups_written_and_read_again_are_the_same() {
let groups = style_groups(&top(STYLES));
let mut part = part_root("Styles");
for g in &groups {
part.push(g.to_el());
}
assert_eq!(style_groups(&top(&String::from_utf8(part_xml(&part)).unwrap())), groups);
}
const GRAPHIC: &str = r#"
"#;
#[test]
fn swatches_give_their_colours_tints_and_gradients() {
let t = top(GRAPHIC);
let sw = swatches(&t);
assert_eq!(
sw.iter().map(|s| s.kind).collect::>(),
[SwatchKind::Color, SwatchKind::Color, SwatchKind::Color, SwatchKind::Color, SwatchKind::Tint, SwatchKind::Gradient, SwatchKind::Swatch]
);
assert_eq!((sw[0].spot(), sw[0].space(), sw[0].color_value()), (false, ColorSpace::Cmyk, vec![0.0, 0.0, 0.0, 100.0]));
assert_eq!((sw[1].spot(), sw[1].space(), sw[1].color_value()), (true, ColorSpace::Rgb, vec![255.0, 128.0, 0.0]));
assert_eq!(sw[2].space(), ColorSpace::Lab);
assert_eq!(sw[3].color_value(), Vec::::new(), "a value that is not finite is no value");
assert_eq!((sw[4].base_color(), sw[4].tint_value()), (Some("Color/Brand"), Some(40.0)));
assert!(sw[5].radial());
assert_eq!(
sw[5].stops(),
[
GradientStop { color: "Color/Black".into(), location: 0.0, midpoint: 50.0 },
GradientStop { color: "Color/Brand".into(), location: 100.0, midpoint: 30.0 },
]
);
let again: Vec = top(&String::from_utf8(part_xml(&sw.iter().fold(part_root("Graphic"), |p, s| p.with_child(s.to_el())))).unwrap());
assert_eq!(swatches(&again), sw);
}
#[test]
fn inks_stroke_styles_and_colour_groups() {
let t = top(GRAPHIC);
let inks = named(&t, "Ink");
assert_eq!(inks.len(), 2);
assert_eq!((inks[1].boolean("ConvertToProcess"), inks[1].get("AliasInkName")), (Some(true), Some("Other")));
let strokes = stroke_styles(&t);
assert_eq!(strokes.iter().map(|s| s.kind).collect::>(), [StrokeStyleKind::Dashed, StrokeStyleKind::Striped, StrokeStyleKind::Dotted]);
assert_eq!(strokes[0].dash_array(), [4.0, 2.0, 1.0, 2.0]);
assert_eq!(strokes[1].stripe_array(), [0.0, 25.0, 75.0, 100.0]);
let groups = color_groups(&t);
assert!(groups[0].is_root());
assert_eq!(groups[0].members(), ["Swatch/None", "Color/Black"]);
let again = top(&String::from_utf8(part_xml(&part_root("Graphic").with_child(groups[0].to_el()).with_child(strokes[1].to_el()))).unwrap());
assert_eq!(color_groups(&again), groups);
assert_eq!(stroke_styles(&again), [strokes[1].clone()]);
}
#[test]
fn preferences_are_found_by_name() {
let prefs = top(
r#"Horizontal"#,
);
let doc = preference(&prefs, "DocumentPreference").unwrap();
assert_eq!(
(doc.num("PageWidth"), doc.boolean("FacingPages"), doc.value("ColumnDirection").as_deref()),
(Some(612.0), Some(true), Some("Horizontal"))
);
assert_eq!(preference(&prefs, "GridPreference").and_then(|g| g.num("BaselineDivision")), Some(12.0));
assert_eq!(preference(&prefs, "MarginPreference"), None);
assert_eq!(named(&prefs, "DocumentPreference").len(), 2);
}
/// Style groups nest as deep as the XML reader allows; their styles are gathered without
/// recursion.
#[test]
fn deeply_nested_style_groups_are_read() {
let depth = 200;
let xml = format!(
"{}{}",
"".repeat(depth),
"".repeat(depth)
);
let g = StyleGroup::from_el(&parse_part(xml.as_bytes()).unwrap()).unwrap();
let styles = g.styles();
assert_eq!((styles.len(), styles[0].0.len(), styles[0].1.values.name()), (1, depth, Some("deep")));
}