M4.38: native and .ai save options: each artboard to its own file, linked files, ICC profiles, PDF content

- document.save / Save As / Save a Copy to a native or .ai file take
  separateArtboards + range (also write <name>-<artboard>.<ext> per artboard
  beside the master file, holding that artboard and the art touching it; the
  result lists every file), includeLinked (linked images keep their files' own
  pixels), embedProfiles (ICC profiles loaded from files that the document is
  tagged with; opening installs them where missing) and pdfCompatible (native:
  also carry a PDF of every artboard; .ai: false writes blank pages around the
  native document). .ai files also take compress (compression.compressText).
- File > Save As to a native or .ai file shows the options dialog (VectorCraft
  Options) after the save panel: Save each artboard to a separate file (All or a
  range), Include Linked Files, Embed ICC Profiles, Create PDF-Compatible File,
  Use Compression, Preview; the web's Save As dialog shows them too. Save, Save a
  Copy and Save as Template reuse the options as last saved.
- Format: optional "profiles" and "pdf" members (load_file, pdf_content),
  SaveOptions.include_linked/profiles/pdf; files without them are unchanged.
- Changed test: tests_aisave::save_as_ai_writes_a_pdf_that_reopens_editable now
  confirms the options dialog Save As .ai shows before writing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-05 15:24:19 +07:00
co-authored by Claude Opus 5.5
parent 975067b44a
commit bf4614669e
19 changed files with 883 additions and 72 deletions
+13 -8
View File
@@ -85,8 +85,9 @@ impl Encoded {
}
/// Every file to write for the destination `path` (a path or a file name): the file itself
/// when there is one, else `{stem}-{artboard name}.{ext}` beside it, then the linked images
/// beside it under their own names, then the image maps as `{image stem}.html` or `.map`.
/// when there is one, else `{stem}-{artboard name}.{ext}` beside it (see `joiner`; a file of no artboard
/// among several, as Save's master file, is the file itself), then the linked images beside it
/// under their own names, then the image maps as `{image stem}.html` or `.map`.
pub fn named<'a>(&'a self, doc: &Document, path: &str) -> Vec<(String, Cow<'a, [u8]>)> {
// Siblings keep the path's own separators (this runs on every platform and on the web).
let dir = &path[..path.rfind(['/', '\\']).map_or(0, |i| i + 1)];
@@ -94,15 +95,19 @@ impl Encoded {
let mut out: Vec<(String, Cow<[u8]>)> = match self.files.as_slice() {
[(_, bytes)] => vec![(path.to_string(), Cow::Borrowed(bytes.as_slice()))],
files => {
let boards: Vec<usize> = files.iter().map(|(b, _)| b.unwrap_or(0)).collect();
let boards: Vec<usize> = files.iter().filter_map(|(b, _)| *b).collect();
let file = std::path::Path::new(&path[dir.len()..]);
let stem = file.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
let ext = file.extension().map(|e| format!(".{}", e.to_string_lossy())).unwrap_or_default();
super::artboard_file_names(doc, &boards)
.into_iter()
.zip(files)
.map(|(board, (_, bytes))| {
(sibling(&format!("{stem}{}{board}{ext}", self.joiner.unwrap_or("-"))), Cow::Borrowed(bytes.as_slice()))
let mut names = super::artboard_file_names(doc, &boards).into_iter();
files
.iter()
.map(|(b, bytes)| {
let name = match b {
None => path.to_string(),
Some(_) => sibling(&format!("{stem}{}{}{ext}", self.joiner.unwrap_or("-"), names.next().unwrap_or_default())),
};
(name, Cow::Borrowed(bytes.as_slice()))
})
.collect()
}