.ai import: version 7 pattern fills in brush art use their operands

A brush's art is read by a reader of its own, which was given the
patterns defined before it but not the record of which of them use the
version 7 form. A fill in that art with such a pattern then ignored its
offset, scale, rotation, reflection and shear operands, and placed the
tiles by the matrix alone. The brush-art reader now receives that record
as well, so those operands place the tiles. A brush whose art has such a
fill without all nine operands is now not read, as other art the reader
can't read whole isn't; before, the brush was kept.
This commit is contained in:
dotCooCoo
2026-11-05 23:42:07 +07:00
committed by bflatastic
parent 934e6cba53
commit 95df6982ee
2 changed files with 38 additions and 1 deletions
+3 -1
View File
@@ -575,9 +575,10 @@ impl<'a> Reader<'a> {
// In a space of its own (y down), with no head to take.
sub.pattern = Some(PatternHead { taken: true, ..PatternHead::default() });
sub.subreader_depth = self.subreader_depth + 1;
// The gradients and patterns defined so far, lent.
// The gradients and patterns defined so far, and which patterns are in the version 7 form, lent.
sub.gradients = std::mem::take(&mut self.gradients);
sub.pattern_tiles = std::mem::take(&mut self.pattern_tiles);
sub.v7_patterns = std::mem::take(&mut self.v7_patterns);
sub.global_names = std::mem::take(&mut self.global_names);
sub.nodes = self.nodes;
sub.template = self.template;
@@ -585,6 +586,7 @@ impl<'a> Reader<'a> {
self.nodes = sub.nodes;
self.gradients = std::mem::take(&mut sub.gradients);
self.pattern_tiles = std::mem::take(&mut sub.pattern_tiles);
self.v7_patterns = std::mem::take(&mut sub.v7_patterns);
self.global_names = std::mem::take(&mut sub.global_names);
// Images aren't kept: the document's images don't count a brush's art among their users. Art
// read with a note (something left out of it) isn't kept either.
+35
View File
@@ -6419,6 +6419,41 @@ fn a_brushs_art_takes_the_patterns_defined_before_it() {
assert!(matches!(fill(&s.art), Paint::Pattern { ref pattern, .. } if pattern == "Dots"), "{:?}", r.warnings);
}
/// A brush's art filled with a pattern defined in the version 7 form places the tiles with the
/// fill's offset, scale, rotation, reflection and shear operands, and a brush whose art has such a
/// fill without all nine of them isn't read.
#[test]
fn a_brushs_art_places_a_version_7_patterns_tiles_with_its_operands() {
let def = pattern7("Grid", "(0 g) @ _ &\n");
let read_with = |ops: &str| {
let section = format!("%AI8_BeginBrushPattern\n(Dot art)\n0 A\n(Grid) {ops} p\n{}f\n%AI8_EndBrushPattern\n", rect_path(0.0, 0.0, 4.0, 4.0));
read_ok(&format!("{def}{section}{}{}", plugin_object(SCATTER, "Spray", SPRAY), layer("")))
};
let placed = |ops: &str| {
let r = read_with(ops);
let lib = vectorcraft_brush::library(&r.doc);
let vectorcraft_brush::BrushKind::Scatter(s) = brush_named(&lib, "Spray") else { panic!("scatter") };
match fill(&s.art) {
Paint::Pattern { xf, .. } => xf,
p => panic!("{ops}: {p:?} {:?}", r.warnings),
}
};
let plain = placed("0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0]");
// The operands and the move they make from the plain placement (a b c d e f): those of
// `a_pattern_fill_applies_its_offset_scale_rotation_reflection_and_shear` with y down, as the
// art's space has it.
let cases = [
("10 15 1 1 0 0 0 0 0 [1 0 0 1 0 0]", [1.0, 0.0, 0.0, 1.0, 10.0, -15.0]),
("-5 7 2 .5 -45 0 0 -15 45 [1 0 0 1 0 0]", [1.793151, 0.353553, -1.035276, 0.353553, -5.0, -7.0]),
];
for (ops, want) in cases {
let got = (placed(ops) * plain.inverse()).as_coeffs();
assert!(got.iter().zip(want).all(|(g, w)| (g - w).abs() < 1e-6), "{ops}: {got:?}");
}
let r = read_with("0 0 1 1 0 0 0 0 [1 0 0 1 0 0]");
assert!(!vectorcraft_brush::library(&r.doc).iter().any(|b| b.name == "Spray"), "{:?}", r.warnings);
}
/// A file without brush objects keeps the default brushes.
#[test]
fn a_file_without_brushes_keeps_the_default_ones() {