//! 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"))); }