Part of #868: legacy .ai defines annotatepage in a prolog of its own

The page trailer the version 7 specification gives (section 3, Script Setup) is
`gsave annotatepage grestore showpage`, and annotatepage is a procedure of the app's own
prolog, which the file didn't have. The file now carries a procedure set of the writer's own
(VectorCraft_Legacy 1 0, listed in %%DocumentSuppliedResources) in a dictionary the setup begins
and the trailer ends, which defines annotatepage as marking nothing.
This commit is contained in:
Dan Green
2026-10-16 01:36:25 +07:00
committed by bflatastic
parent 37cebc47ee
commit 06221726fb
2 changed files with 31 additions and 6 deletions
+25 -4
View File
@@ -51,8 +51,10 @@
//! Windows-1252, vertical type, inline graphics, gradient or pattern fills) is written as its
//! outlines, and all type with [`LegacyAiOptions::outline_text`].
//!
//! No prolog is written: the file names none, so PostScript printers can't print it, as the `.ai`
//! files other apps write in this format.
//! The prolog defines the one procedure the script calls that isn't an operator of the format,
//! the page trailer's `annotatepage` ([`PROLOG`]); the format's operators are left to the app that
//! reads the file, so PostScript printers can't print it, as the `.ai` files other apps write in
//! this format.
use std::fmt::Write;
use std::sync::Arc;
@@ -196,7 +198,11 @@ pub fn export_legacy_ai(doc: &Document, o: &LegacyAiOptions) -> Result<LegacyAiO
let _ = writeln!(s, "%AI5_ArtSize: {} {}", ps::num(width), ps::num(height));
s.push_str("%AI5_RulerUnits: 2\n");
let _ = writeln!(s, "%AI5_NumLayers: {layers}");
s.push_str("%%EndComments\n%%BeginProlog\n%%EndProlog\n%%BeginSetup\n");
s.push_str("%%DocumentSuppliedResources: procset VectorCraft_Legacy 1 0\n");
s.push_str("%%EndComments\n%%BeginProlog\n");
s.push_str(PROLOG);
s.push_str("%%EndProlog\n%%BeginSetup\n");
s.push_str("VCLegacy begin\n");
fonts(&mut s, &w.fonts);
if !w.gradients.is_empty() {
let _ = writeln!(s, "{} Bn", w.gradients.len());
@@ -206,7 +212,9 @@ pub fn export_legacy_ai(doc: &Document, o: &LegacyAiOptions) -> Result<LegacyAiO
}
s.push_str("%%EndSetup\n");
s.push_str(&w.body);
s.push_str("%%PageTrailer\ngsave annotatepage grestore showpage\n%%Trailer\n%%EOF\n");
// The page trailer as the specification's script has it (§3); the document trailer takes the
// prolog's dictionary off the dictionary stack (its procedure set termination, §3).
s.push_str("%%PageTrailer\ngsave annotatepage grestore showpage\n%%Trailer\nend\n%%EOF\n");
Ok(LegacyAiOutput { bytes: s.into_bytes(), warnings: w.warnings })
}
@@ -234,6 +242,19 @@ fn fonts(s: &mut String, fonts: &[(String, bool)]) {
}
}
/// The prolog: a procedure set of this writer's own (`VectorCraft_Legacy 1 0`), in a dictionary
/// (`VCLegacy`) the setup begins and the trailer ends (§1.2: the prolog holds definitions only,
/// the setup activates them, the trailer deactivates them). The page trailer of the
/// specification's script (§3, Script Setup: `gsave annotatepage grestore showpage`) calls
/// `annotatepage`, which marks nothing here.
const PROLOG: &str = "%%BeginResource: procset VectorCraft_Legacy 1 0
/VCLegacy 8 dict def
VCLegacy begin
/annotatepage {} bind def
end
%%EndResource
";
/// The first line of a PostScript file (the DSC version comment).
const PS_HEADER: &str = "%!PS-Adobe-3.0"; // brand-ok: the PostScript header comment
+6 -2
View File
@@ -103,10 +103,14 @@ fn writes_the_documented_operators() {
"\nq\n",
"\n1 A\n",
"XI\n",
"%%EOF\n",
"%%PageTrailer\ngsave annotatepage grestore showpage\n%%Trailer\nend\n%%EOF\n",
] {
assert!(text.contains(needle), "missing {needle:?} in\n{text}");
}
// The prolog defines the page trailer's `annotatepage` (§3), in the dictionary the setup begins.
let prolog = text.split("%%BeginProlog\n").nth(1).and_then(|t| t.split("%%EndProlog\n").next()).unwrap();
assert!(prolog.contains("/VCLegacy 8 dict def\nVCLegacy begin\n/annotatepage {} bind def\nend\n"), "{prolog}");
assert!(text.contains("%%DocumentSuppliedResources: procset VectorCraft_Legacy 1 0\n"), "{text}");
// The hidden layer is written hidden: visible 0.
assert!(text.contains("%AI5_BeginLayer\n0 1 1 1 0 0 1 "), "{text}");
// Art space is y up from the artboard's bottom-left: the red square (10..40 down from the top)
@@ -119,7 +123,7 @@ fn writes_the_documented_operators() {
// The clipping path: closed by `h`, then `W n` (§5.6.2).
assert!(text.contains(" L\nh\nW\nn\n"), "{text}");
// Gradients: counted (`Bn`), each stop's operands before `%_Bs` (§3.5).
assert!(text.contains("%%BeginSetup\n1 Bn\n%AI5_BeginGradient: (Gradient)\n(Gradient) 0 2 Bd\n[\n"), "{text}");
assert!(text.contains("%%BeginSetup\nVCLegacy begin\n1 Bn\n%AI5_BeginGradient: (Gradient)\n(Gradient) 0 2 Bd\n[\n"), "{text}");
assert!(text.contains(" 2 50 100 %_Bs\n") && !text.contains("Np\n"), "{text}");
assert!(text.contains("1 0 0 1 0 0 Bg\nf\n0 BB\n"), "{text}");
// The image: operands and `XI` on a line, its samples on comment lines (§8).