diff --git a/crates/eps/src/import/ai/brush.rs b/crates/eps/src/import/ai/brush.rs index bd5a1628a..8e1f27bad 100644 --- a/crates/eps/src/import/ai/brush.rs +++ b/crates/eps/src/import/ai/brush.rs @@ -28,10 +28,11 @@ //! [`vectorcraft_brush::ArtBrush`]'s `overlap: false`). //! - A Scatter, Art or Pattern brush's parameters are fields split by `/`: a number, the art's //! section names (one for Scatter, five for Art and Pattern, empty where there is none, each -//! after a space for Art and Pattern), more fields, and last 15 numbers. Brushes whose first -//! number is 1, as older files write them, have one field fewer before the numbers and 8 numbers; -//! an Art brush in that form is read from its art's name alone (its numbers aren't known, so it -//! keeps the defaults and its paths their drawn look). +//! after a space for Art and Pattern), more fields, and last 15 numbers. Older files write +//! brushes with first number 1, one field fewer before the numbers and 8 numbers; an Art brush +//! with 8 numbers, whatever its first number, is read from its art's name alone (its numbers +//! aren't known, so it keeps the defaults and its paths their drawn look). A Pattern brush needs +//! only its first 3 numbers (its scale is the third). //! - Scatter: the size (2, 3), spacing (5, 6) and scatter (8, 9) as (low, high) factors of the //! art, the rotation in degrees (11, 12), how each varies (4, 7, 10, 13) and whether the //! rotation follows the path (14), and 1 Tints (1). @@ -339,7 +340,8 @@ pub(super) fn parse_tool_object(kind: Tool, name: &str, params: &str) -> Result< } Tool::Art => { // The older form's 8 numbers aren't known: its brush keeps the defaults and its - // paths their drawn look. Any other count isn't a form this reads. + // paths their drawn look. The count tells the form, not the first number. + // Any other count isn't a form this reads. let (colorization, plain) = if let Ok(n15) = <&[f64; 15]>::try_from(n.as_slice()) { (if n15[1] == 1.0 { Colorization::Tints } else { Colorization::None }, art_is_plain(n15)) } else if n.len() == 8 { diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index c69728434..9a3227113 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -8225,6 +8225,10 @@ fn an_older_art_brush_comes_in_with_its_art() { assert_eq!(brush::parse_tool_object(brush::Tool::Art, "Ribbon", bad), Err(brush::BrushError::Values), "{bad}"); } assert_eq!(brush::parse_tool_object(brush::Tool::Scatter, "Spray", "1 /Dot art/ 0 1 / 1 0 1 0 1 0 0 0"), Err(brush::BrushError::Values)); + // 8 numbers are the older form whatever the first number says: read with the defaults. + let d = brush::parse_tool_object(brush::Tool::Art, "Ribbon", "0 / Ribbon art/ / / / / 0 1 / 1 0 1 0 1 0 0 0").unwrap(); + let brush::DraftKind::Art { ref brush, ref art, plain } = d.kind else { panic!("art") }; + assert_eq!((art.as_str(), plain, brush.colorization), ("Ribbon art", false, vectorcraft_brush::ArtBrush::default().colorization)); let objects = [plugin_object(ART_BRUSH, "Ribbon", OLD_RIBBON), plugin_object(ART_BRUSH, "Bare", "1 / / / / / / 0 1 / 1 0 1 0 1 0 0 0")].concat(); let r = read_ok(&format!("{}{objects}{}", brush_art("Ribbon art", 4.0), layer(""))); let lib = vectorcraft_brush::library(&r.doc); @@ -8234,6 +8238,26 @@ fn an_older_art_brush_comes_in_with_its_art() { assert!(r.warnings.iter().any(|w| w.ends_with("Brushes panel (1)")), "{:?}", r.warnings); } +/// A Pattern brush needs only its first 3 numbers, its scale the third (as a percentage), so the +/// older form's 8 numbers read too; fewer than 3 aren't read. +#[test] +fn a_pattern_brush_reads_its_scale_from_three_or_more_numbers() { + const SLOTS: &str = "1 / Tile one/ Tile two/ / / Tile five/ 0 1 / "; + for numbers in ["1 0 0.5", "1 0 0.5 0 1 0 0 0"] { + let d = brush::parse_tool_object(brush::Tool::Pattern, "Fence", &format!("{SLOTS}{numbers}")).unwrap(); + let brush::DraftKind::Pattern { tiles, scale } = d.kind else { panic!("{d:?}") }; + assert_eq!(tiles[1].as_deref(), Some("Tile two"), "{numbers}"); + assert!((scale - 50.0).abs() < 1e-9, "{numbers}: {scale}"); + } + for numbers in ["", "1", "1 0"] { + assert_eq!( + brush::parse_tool_object(brush::Tool::Pattern, "Fence", &format!("{SLOTS}{numbers}")), + Err(brush::BrushError::Values), + "{numbers:?}" + ); + } +} + /// A brush's art filled with a pattern the file defines before it is read, with that pattern. #[test] fn a_brushs_art_takes_the_patterns_defined_before_it() {