mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 02:30:09 +00:00
.ai save: write the artboards in the recorded part of the document data
The editing data put ArtboardArray and AIDocumentCanvasSize in the part of the /Document dictionary marked /NotRecorded. Apps that read the editing data do not read artboards from there: a file opened with one default artboard the size of the first, the other artboards and the names were lost, and the art landed one artboard's height above it. The two entries now close a /Recorded part, followed by an empty /NotRecorded one, as in the files users sent.
This commit is contained in:
@@ -19,7 +19,8 @@
|
||||
//! - Setup: `0 Bn` (no gradient definitions, §3), the color swatches (`%AI5_BeginPalette`,
|
||||
//! `0 0 Pb`, the Registration swatch, each swatch's color operator and `(name) Pc`, a color group as `0 (name) count Pg`
|
||||
//! before its swatches, `PB`), then the artboards as the `ArtboardArray` of the `/Document`
|
||||
//! dictionary and the canvas size as its `AIDocumentCanvasSize`, on `%_` lines.
|
||||
//! dictionary's `/Recorded` part and the canvas size as its `AIDocumentCanvasSize`, on `%_`
|
||||
//! lines.
|
||||
//! - Layers: `%AI5_BeginLayer`, `visible preview unlocked printing dimmed 0 1 colorIndex r g b 0
|
||||
//! dimPercent 0 Lb` (14 operands; a color that isn't one of the preset layer colors has the
|
||||
//! index -1), `(name) Ln`, the layer's name as an XML id in an `/ArtDictionary`, its art and
|
||||
@@ -304,7 +305,10 @@ fn ruler_units(u: Unit) -> u8 {
|
||||
/// the canvas (`RulerOrigin`, `origin` being art space's origin there), its pixel aspect ratio
|
||||
/// (`PAR`), `DisplayMark` 0 (as in the files users sent), whether it is the selected one (the
|
||||
/// first) and its name; then the canvas's size. Apps that read the editing data drop a record that
|
||||
/// lacks any of the first five.
|
||||
/// lacks any of the first five. Both sit in the dictionary the `/Document` marks `/Recorded`,
|
||||
/// followed by an empty `/NotRecorded` one, as in the files users sent: apps that read the editing
|
||||
/// data do not read artboards from the `/NotRecorded` dictionary, and then place the art one
|
||||
/// artboard's height above a default artboard.
|
||||
fn artboards(s: &mut String, doc: &Document, to_art: Affine, origin: (f64, f64)) {
|
||||
s.push_str("%AI9_BeginDocumentData\n%_/Document :\n%_/Dictionary :\n%_/Array :\n");
|
||||
for (i, ab) in doc.artboards.iter().enumerate() {
|
||||
@@ -323,7 +327,7 @@ fn artboards(s: &mut String, doc: &Document, to_art: Affine, origin: (f64, f64))
|
||||
}
|
||||
s.push_str("%_; (ArtboardArray) ,\n");
|
||||
let _ = writeln!(s, "%_{CANVAS_SIZE} /Int (AIDocumentCanvasSize) ,");
|
||||
s.push_str("%_; /NotRecorded ,\n%_;\n%AI9_EndDocumentData\n");
|
||||
s.push_str("%_; /Recorded ,\n%_/Dictionary : /NotRecorded ,\n%_; /NotRecorded ,\n%_;\n%AI9_EndDocumentData\n");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -342,6 +342,76 @@ fn art_lies_on_its_artboard_in_ruler_space() {
|
||||
assert_eq!(bounds, Rect::new(10.0, 20.0, 30.0, 40.0));
|
||||
}
|
||||
|
||||
/// A gray rectangle at `r` (document space) in `doc`'s first layer.
|
||||
fn add_rect(doc: &mut Document, r: Rect) {
|
||||
let rect = Node::path(doc.alloc_id(), shapes::rectangle(r), Appearance::basic(solid(Color::gray(0.5)), Paint::None, 0.0));
|
||||
let layer = doc.layers[0].id;
|
||||
doc.insert(Some(layer), usize::MAX, rect).unwrap();
|
||||
}
|
||||
|
||||
/// The end of the document data: the artboards and the canvas size in the `/Recorded` part of the
|
||||
/// `/Document` dictionary, then an empty `/NotRecorded` part.
|
||||
const RECORDED: &str = "%_; (ArtboardArray) ,\n%_16383 /Int (AIDocumentCanvasSize) ,\n%_; /Recorded ,\n%_/Dictionary : /NotRecorded ,\n%_; /NotRecorded ,\n%_;\n%AI9_EndDocumentData\n";
|
||||
|
||||
/// One letter-size artboard: the art 36 points in from its top-left corner, the artboard's record
|
||||
/// and the boxes, with the artboards in the `/Recorded` part of the `/Document` dictionary.
|
||||
#[test]
|
||||
fn one_artboard_is_recorded_with_its_art_in_place() {
|
||||
let mut doc = Document::new(612.0, 792.0);
|
||||
doc.artboards[0].name = "Page".into();
|
||||
add_rect(&mut doc, Rect::new(36.0, 36.0, 576.0, 756.0));
|
||||
let text = program(&doc);
|
||||
for line in [
|
||||
"%%BoundingBox: 36 -756 576 -36\n",
|
||||
"%AI3_Cropmarks: 0 -792 612 0\n",
|
||||
"%AI3_TemplateBox: 306.5 -396.5 306.5 -396.5\n",
|
||||
"\n36 -36 m\n",
|
||||
"%_0 0 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_612 -792 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_7885 7795 /RealPoint\n%_ (RulerOrigin) ,\n",
|
||||
"%_(Page) /UnicodeString (Name) ,\n%_; ,\n",
|
||||
RECORDED,
|
||||
] {
|
||||
assert!(text.contains(line), "{line}\n{text}");
|
||||
}
|
||||
let read = read_back(&doc);
|
||||
assert_eq!(read.artboards.iter().map(|a| (a.name.as_str(), a.rect)).collect::<Vec<_>>(), [("Page", Rect::new(0.0, 0.0, 612.0, 792.0))]);
|
||||
let bounds = read.layers.iter().filter_map(|l| l.visual_bounds()).reduce(|a, b| a.union(b)).unwrap();
|
||||
assert_eq!(bounds, Rect::new(36.0, 36.0, 576.0, 756.0));
|
||||
}
|
||||
|
||||
/// Two named artboards of different sizes, tops aligned: each record holds its corners in art
|
||||
/// space and its top-left corner on the canvas, where the two together are centered.
|
||||
#[test]
|
||||
fn artboards_of_different_sizes_keep_their_names_and_places() {
|
||||
let mut doc = Document::new(400.0, 300.0);
|
||||
doc.artboards[0].name = "Front".into();
|
||||
let mut back = doc.artboards[0].clone();
|
||||
(back.id, back.name, back.rect) = (2, "Back".into(), Rect::new(440.0, 0.0, 740.0, 500.0));
|
||||
doc.artboards.push(back);
|
||||
add_rect(&mut doc, Rect::new(50.0, 50.0, 150.0, 100.0));
|
||||
add_rect(&mut doc, Rect::new(490.0, 50.0, 590.0, 100.0));
|
||||
let text = program(&doc);
|
||||
for line in [
|
||||
"%AI3_Cropmarks: 0 -300 400 0\n",
|
||||
"%AI3_TemplateBox: 370.5 -250.5 370.5 -250.5\n",
|
||||
"\n50 -50 m\n",
|
||||
"\n490 -50 m\n",
|
||||
"%_0 0 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_400 -300 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_7821 7941 /RealPoint\n%_ (RulerOrigin) ,\n",
|
||||
"%_1 /Bool (IsArtboardSelected) ,\n%_(Front) /UnicodeString (Name) ,\n",
|
||||
"%_440 0 /RealPointRelToROrigin\n%_ (PositionPoint1) ,\n%_740 -500 /RealPointRelToROrigin\n%_ (PositionPoint2) ,\n%_8261 7941 /RealPoint\n%_ (RulerOrigin) ,\n",
|
||||
"%_0 /Bool (IsArtboardSelected) ,\n%_(Back) /UnicodeString (Name) ,\n",
|
||||
RECORDED,
|
||||
] {
|
||||
assert!(text.contains(line), "{line}\n{text}");
|
||||
}
|
||||
let read = read_back(&doc);
|
||||
assert_eq!(
|
||||
read.artboards.iter().map(|a| (a.name.as_str(), a.rect)).collect::<Vec<_>>(),
|
||||
[("Front", Rect::new(0.0, 0.0, 400.0, 300.0)), ("Back", Rect::new(440.0, 0.0, 740.0, 500.0))]
|
||||
);
|
||||
let bounds = read.layers.iter().filter_map(|l| l.visual_bounds()).reduce(|a, b| a.union(b)).unwrap();
|
||||
assert_eq!(bounds, Rect::new(50.0, 50.0, 590.0, 100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pdf_carries_the_data_on_its_first_page() {
|
||||
let doc = sample();
|
||||
|
||||
Reference in New Issue
Block a user