.ai save: the editing copy keeps artboard display marks and dense patterns

Each artboard's DisplayMark was written as 0. It now holds the center
mark (1), cross hairs (2) and video safe areas (4), as the reader reads
them.

A pattern fill that would place more tiles than a fill places came back
with no tiles and was written as an empty group, so the shape was
missing from the editing copy. Such a shape is now filled gray, as a
pattern that isn't found is, with a warning.
This commit is contained in:
dotCooCoo
2026-11-03 16:19:15 -08:00
parent f87ba9224c
commit 41477041ea
3 changed files with 47 additions and 1 deletions
+3 -1
View File
@@ -366,7 +366,9 @@ fn artboards(s: &mut String, doc: &Document, to_art: Affine, origin: (f64, f64))
s.push_str("/RealPoint\n%_ (RulerOrigin) ,\n%_");
let par = ab.video_ruler_pixel_aspect_ratio;
ps::push_num(s, if par.is_finite() && par > 0.0 { par } else { 1.0 });
let _ = writeln!(s, " /Real (PAR) ,\n%_0 /Int (DisplayMark) ,\n%_{} /Bool (IsArtboardSelected) ,", u8::from(i == 0));
// Display marks: 1 the center mark, 2 cross hairs, 4 video safe areas.
let mark = u8::from(ab.show_center_mark) | u8::from(ab.show_cross_hairs) << 1 | u8::from(ab.show_video_safe_areas) << 2;
let _ = writeln!(s, " /Real (PAR) ,\n%_{mark} /Int (DisplayMark) ,\n%_{} /Bool (IsArtboardSelected) ,", u8::from(i == 0));
let _ = writeln!(s, "%_{} /UnicodeString (Name) ,\n%_; ,", ps::bytes(ab.name.as_bytes()));
}
s.push_str("%_; (ArtboardArray) ,\n");
+36
View File
@@ -949,6 +949,42 @@ fn a_group_fill_in_a_pattern_tile_is_written() {
assert!(all.iter().any(|p| p.appearance.items.iter().any(|i| paint(i.paint()) == red)), "the tiles keep their group's fill");
}
/// A pattern fill that would place more tiles than a fill places is written as the shape filled
/// gray, as a pattern that isn't found is, instead of as nothing.
#[test]
fn a_pattern_fill_of_too_many_tiles_is_written_gray() {
let mut doc = Document::new(1200.0, 1200.0);
let layer = doc.layers[0].id;
let dot = Node::path(doc.alloc_id(), shapes::rectangle(Rect::new(0.0, 0.0, 0.5, 0.5)), Appearance::basic(solid(Color::BLACK), Paint::None, 0.0));
let tiny = vectorcraft_doc::PatternDef { tile: Rect::new(0.0, 0.0, 1.0, 1.0), ..vectorcraft_doc::PatternDef::new("Tiny", vec![Arc::new(dot)]) };
doc.patterns.push(tiny);
let filled = Node::path(
doc.alloc_id(),
shapes::rectangle(Rect::new(100.0, 100.0, 1100.0, 1100.0)),
Appearance::basic(Paint::Pattern { pattern: "Tiny".into(), xf: Affine::IDENTITY }, Paint::None, 0.0),
);
doc.insert(Some(layer), usize::MAX, filled).unwrap();
let back = read_back(&doc);
let mut all = vec![];
paths(back.layers[0].children().unwrap(), &mut all);
let gray = paint(&solid(Color::gray(0.5)));
assert!(all.iter().any(|p| p.appearance.items.iter().any(|i| paint(i.paint()) == gray)), "{}", all.len());
let out = export_ai_editing(&doc, &opts()).unwrap();
assert!(out.warnings.iter().any(|w| w == writer::PATTERN_TILES), "{:?}", out.warnings);
}
/// An artboard's center mark, cross hairs and video safe areas are written as the bits of its
/// `DisplayMark` and read back.
#[test]
fn artboard_display_marks_are_written() {
let mut doc = Document::new(200.0, 100.0);
(doc.artboards[0].show_center_mark, doc.artboards[0].show_video_safe_areas) = (true, true);
assert!(program(&doc).contains("%_5 /Int (DisplayMark) ,\n"));
let back = read_back(&doc);
let ab = &back.artboards[0];
assert!(ab.show_center_mark && !ab.show_cross_hairs && ab.show_video_safe_areas);
}
#[test]
fn effects_on_a_fill_are_reported_left_out() {
let mut doc = Document::new(200.0, 100.0);
+8
View File
@@ -27,6 +27,7 @@ const MAX_AVERAGED_PIXELS: u64 = 1 << 24;
pub(super) const TYPE: &str = "type is written as its outlines in the editing data for other apps";
pub(super) const GRADIENTS: &str = "gradients are written as their average color in the editing data for other apps";
const PATTERNS: &str = "pattern fills are written as their tiles cut to the shape in the editing data for other apps";
pub(super) const PATTERN_TILES: &str = "pattern fills with more tiles than a fill places are written filled gray in the editing data for other apps";
pub(super) const CLIPPING: &str =
"clipping groups and clipping layers are written as their art cut to the clipping path in the editing data for other apps";
pub(super) const IMAGES: &str = "images are written as their outlines filled with their average color in the editing data for other apps";
@@ -457,6 +458,13 @@ impl<'a> Writer<'a> {
return self.path_object(path, rule, Some(&flat), None);
};
let tiles = def.instances_in(*xf, path.to_bezpath().bounding_box());
// More tiles than a fill places come back as none: the shape is filled gray, as a
// pattern that isn't found is.
if tiles.is_empty() && !def.art.is_empty() {
self.warn(PATTERN_TILES);
let flat = FillLayer { paint: Paint::solid(Color::gray(0.5)), ..f.clone() };
return self.path_object(path, rule, Some(&flat), None);
}
let outer = self.clip.clone();
let region = self.cut(path, rule);
self.clip = Some(region);