mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Export All Open Documents: every page and artboard, linked data included
Each format's copy options are given in full, so no remembered option applies: PDF and .ai copies write every page (range all), SVG copies the art of every artboard in one file (useArtboards false), native, .ai and EPS copies include linked files' data.
This commit is contained in:
@@ -36,7 +36,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Export All Open Documents",
|
||||
["Object", "Plug-ins", "Document Tools"],
|
||||
None,
|
||||
"{folder, format?: vectorcraft|pdf|svg|svgz|ai|eps (default: pdf)} write a copy of every open document, in tab order, to `folder` as File › Save a Copy writes it, each named after its document (<name>.<ext>; -2, -3… added when that name is taken, so no file is replaced), one file each (images embedded, artboards together): every document keeps its path, title and modified state, and no undo step is made. A document that can't be written is listed in `failed` and the others are still written; the active document stays active → {files: [{title, path, warnings}], failed: [{title, error}], format}",
|
||||
"{folder, format?: vectorcraft|pdf|svg|svgz|ai|eps (default: pdf)} write a copy of every open document, in tab order, to `folder` as File › Save a Copy writes it, each named after its document (<name>.<ext>; -2, -3… added when that name is taken, so no file is replaced), one self-contained file each with all of its art (images embedded or included, every artboard; no remembered save options apply): every document keeps its path, title and modified state, and no undo step is made. A document that can't be written is listed in `failed` and the others are still written; the active document stays active → {files: [{title, path, warnings}], failed: [{title, error}], format}",
|
||||
has_doc,
|
||||
export_all_open
|
||||
),
|
||||
@@ -98,6 +98,20 @@ fn save_timestamped_copy(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fileio::save_document(s, SaveMode::Copy, &json!({ "path": path, "format": format.id }))
|
||||
}
|
||||
|
||||
/// The save options of every copy, given in full so that no document's remembered options (linked
|
||||
/// SVG images, a PDF page range, separate artboard files) apply: one self-contained file per
|
||||
/// document with all of its art. Each format reads only the options it has, and `range` is left
|
||||
/// out of SVG, where it would pick artboards over `useArtboards`.
|
||||
fn copy_options(format: &str) -> Value {
|
||||
match format {
|
||||
"svg" | "svgz" => json!({ "images": "embed", "useArtboards": false }),
|
||||
"pdf" => json!({ "range": "all" }),
|
||||
"eps" => json!({ "includeLinkedFiles": true, "useArtboards": false }),
|
||||
"ai" | "ait" => json!({ "range": "all", "includeLinked": true, "separateArtboards": false }),
|
||||
_ => json!({ "includeLinked": true, "separateArtboards": false }),
|
||||
}
|
||||
}
|
||||
|
||||
fn export_all_open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "addon.document.exportAllOpen";
|
||||
let format = fileio::save_format(Some(str_param(p, "format").unwrap_or("pdf")), None).map_err(|e| bad(C, e))?;
|
||||
@@ -117,10 +131,7 @@ fn export_all_open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
failed.push(json!({ "title": title, "error": "every numbered name for the copy is taken" }));
|
||||
continue;
|
||||
};
|
||||
// One file per document: the remembered options of a document saved before in this format
|
||||
// (linked SVG images, separate artboard files) would write others beside it. Each format
|
||||
// reads only the options it has.
|
||||
let options = json!({ "images": "embed", "separateArtboards": false });
|
||||
let options = copy_options(format.id);
|
||||
match fileio::save_document(s, SaveMode::Copy, &json!({ "path": path, "format": format.id, "options": options })) {
|
||||
Ok(r) => {
|
||||
written.push(path.clone());
|
||||
|
||||
@@ -240,3 +240,39 @@ fn export_all_open_documents_writes_one_file_each() {
|
||||
assert!(std::fs::read_to_string(out.join("pic.svg")).unwrap().contains("data:image/png;base64,"), "the image is embedded");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A PDF document last saved with a page range exports with every page, and an SVG document with
|
||||
/// two artboards exports as one SVG holding the art of both (#4920 review).
|
||||
#[test]
|
||||
fn export_all_open_documents_keeps_every_page_and_artboard() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-export-all-pages-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(dir.join("out")).unwrap();
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100, "artboards": 2, "name": "pages"})).unwrap();
|
||||
s.execute("document.save", &json!({"path": dir.join("pages.pdf"), "format": "pdf", "options": {"range": "1"}})).unwrap();
|
||||
let pages = |b: &[u8]| {
|
||||
String::from_utf8_lossy(b).matches("/Type /Page\n").count()
|
||||
+ String::from_utf8_lossy(b).matches("/Type/Page/").count()
|
||||
+ String::from_utf8_lossy(b).matches("/Type /Page/").count()
|
||||
+ String::from_utf8_lossy(b).matches("/Type /Page ").count()
|
||||
};
|
||||
let first = pages(&std::fs::read(dir.join("pages.pdf")).unwrap());
|
||||
let out = dir.join("out");
|
||||
s.execute("addon.document.exportAllOpen", &json!({"folder": out, "format": "pdf"})).unwrap();
|
||||
let copy = pages(&std::fs::read(out.join("pages.pdf")).unwrap());
|
||||
assert!(copy > first, "the copy has every page: {first} saved, {copy} exported");
|
||||
// SVG: one file with the art on both artboards.
|
||||
let a = s.doc().unwrap().doc.artboards[1].rect;
|
||||
s.execute("shape.rectangle", &json!({"x": a.x0 + 10.0, "y": a.y0 + 10.0, "width": 20, "height": 20})).unwrap();
|
||||
s.execute("addon.document.exportAllOpen", &json!({"folder": out, "format": "svg"})).unwrap();
|
||||
let svgs: Vec<String> =
|
||||
std::fs::read_dir(&out).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().into_owned()).filter(|n| n.ends_with(".svg")).collect();
|
||||
assert_eq!(svgs, ["pages.svg"]);
|
||||
let svg = std::fs::read_to_string(out.join("pages.svg")).unwrap();
|
||||
// Not the first artboard alone (its 100 × 100 box at the origin): the art's own bounds.
|
||||
let view = svg.split("viewBox=\"").nth(1).and_then(|v| v.split('"').next()).unwrap();
|
||||
let w: f64 = view.split_whitespace().nth(2).unwrap().parse().unwrap();
|
||||
assert!(w > 19.0 && w < 30.0, "the SVG shows the art (the square on the second artboard), not the first artboard: viewBox {view}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user