Merge pull request #4293 from mockydevs/fix/eps-original-format-save

Support EPS Save and Save As with editable data
This commit is contained in:
BFlat
2026-10-29 13:49:38 +07:00
committed by GitHub
11 changed files with 280 additions and 22 deletions
+27 -4
View File
@@ -13,13 +13,16 @@ use vectorcraft_eps::{EpsOptions, Level, Overprint, Preview, Raster};
use vectorcraft_geom::{Point, Rect};
use super::super::flatten::{FlattenOptions, flatten_document};
use super::super::{CommandSpec, EngineError, Result, Session, bad, cmd, has_doc, str_param};
use super::super::{CommandSpec, EngineError, Result, Session, bad, bool_or, cmd, has_doc, str_param};
use super::{ArtboardPick, Encoded, FormatOption};
const C: &str = "document.export";
/// Most pixels along a side of a preview.
const MAX_PREVIEW: f64 = 2048.0;
/// Save needs a lightweight compatibility preview, not an uncompressed 16 MiB image beside
/// a small vector. TIFF format choices are unchanged; there is no public resolution option.
const SAVE_PREVIEW: f64 = 1024.0;
/// Most pixels along a side of a thumbnail.
const THUMBNAIL: f64 = 128.0;
@@ -260,7 +263,20 @@ fn native(doc: &Document, board: Option<usize>, include_linked: bool) -> Vec<u8>
/// artboard (after a central file of the whole document when there are several and art shows).
/// Transparency is flattened first (once for all of them); the previews show `doc` itself.
pub(super) fn encode(doc: &Document, p: &Value) -> Result<Encoded> {
encode_document(doc, p, false)
}
/// Save keeps a single page's original frame and all native editing data, unlike Export's
/// optional art-bound crop or separate file per artboard. Existing EPS loss warnings still apply.
pub(super) fn encode_save(doc: &Document, p: &Value) -> Result<Encoded> {
encode_document(doc, p, true)
}
fn encode_document(doc: &Document, p: &Value, saving: bool) -> Result<Encoded> {
let set = parse(p)?;
if saving && (set.use_artboards == Some(false) || bool_or(p, "selectedOnly", false)) {
return Err(bad(C, "EPS Save retains the artboard and all artwork: use document.export for cropped or selected-only EPS"));
}
let original = doc;
let (full, warnings) = crate::cmd::place::document::full_documents(doc);
let doc = &*full;
@@ -272,7 +288,13 @@ pub(super) fn encode(doc: &Document, p: &Value) -> Result<Encoded> {
} else {
Cow::Borrowed(original)
};
let pages = pages(doc, &set)?;
let pages = if saving {
let board = set.boards.one(doc.artboards.len()).map_err(|e| bad(C, e))?;
let rect = doc.artboards.get(board).ok_or_else(|| bad(C, "the document has no artboard"))?.rect;
vec![(Some(board), rect, Point::new(rect.x0, rect.y1))]
} else {
pages(doc, &set)?
};
if pages.is_empty() {
return Err(bad(C, "the document has no artboard"));
}
@@ -284,13 +306,14 @@ pub(super) fn encode(doc: &Document, p: &Value) -> Result<Encoded> {
if transparent && flat.is_some() {
enc.warnings.push("transparency is flattened into opaque art and images (EPS has none): see the flattener preset".into());
}
let preview_limit = if saving { SAVE_PREVIEW } else { MAX_PREVIEW };
let created = vectorcraft_doc::metadata::output_time();
let mut renderer = vectorcraft_render::Renderer::new();
for (board, region, origin) in pages {
let rect = vectorcraft_eps::preview_rect(region, origin);
let preview = match set.eps.preview {
Preview::None => None,
kind => Some(render(&mut renderer, doc, rect, MAX_PREVIEW, set.eps.transparent_preview && kind == Preview::TiffColor)?),
kind => Some(render(&mut renderer, doc, rect, preview_limit, set.eps.transparent_preview && kind == Preview::TiffColor)?),
};
let thumbnail = set.thumbnails.then(|| render(&mut renderer, doc, rect, THUMBNAIL, false)).transpose()?;
let o = EpsOptions {
@@ -298,7 +321,7 @@ pub(super) fn encode(doc: &Document, p: &Value) -> Result<Encoded> {
origin,
title: doc.title.clone(),
created,
native: Some(native(&editing, board, set.include_linked)),
native: Some(native(&editing, if saving { None } else { board }, set.include_linked)),
..set.eps.clone()
};
let out = vectorcraft_eps::export(drawn, &o, preview.as_ref(), thumbnail.as_ref()).map_err(|e| bad(C, e))?;
+13 -2
View File
@@ -430,8 +430,19 @@ pub fn open_template(s: &mut Session, name: &str, bytes: &[u8], path: Option<&st
/// `opts` are the options it was read with.
fn open_loaded(s: &mut Session, loaded: Loaded, links: Resolved, path: Option<String>, opts: &LoadOptions, as_template: bool) -> Result<Value> {
let Loaded { mut doc, format, warnings, restored, converted, .. } = loaded.with_grid_prefs(&s.prefs);
// What reading the file left out of the document, if anything.
let losses: Vec<String> = warnings.iter().filter(|w| is_loss(w)).cloned().collect();
// EPS now has an ordinary Save path. Its preview, partial-program and damaged-editing
// fallbacks must ask before overwriting the richer source they could not fully read.
let losses: Vec<String> = warnings
.iter()
.filter(|w| {
is_loss(w)
|| (format.id == "eps"
&& (matches!(w.as_str(), EDITING_DAMAGED | EDITING_STALE)
|| w.starts_with("this file's PostScript couldn't be read")
|| w.starts_with("the file was read up to an error")))
})
.cloned()
.collect();
let source_path = path.clone().filter(|_| !losses.is_empty());
// A template (saved by Save as Template, or an .ait/.vctemplate file) opens as a new untitled
// document.
+12 -8
View File
@@ -21,7 +21,7 @@ use crate::{DocState, Prefs};
/// The formats Save As offers, in menu order (append-only). The other writable formats are exports:
/// they never become the document's own file.
pub const SAVE_FORMATS: &[&str] = &["vectorcraft", "template", "pdf", "svg", "svgz", "ai"];
pub const SAVE_FORMATS: &[&str] = &["vectorcraft", "template", "pdf", "svg", "svgz", "ai", "eps"];
/// The formats Save as Template writes, the default first: a native template, or a
/// PDF-compatible `.ait` (written as a `.ai` file is).
@@ -431,9 +431,9 @@ pub fn reads_option(f: &Format, key: &str) -> bool {
}
/// Formats that carry the whole native document: they lose nothing, and record links relative to
/// where they are written. A `.ai` or `.ait` file is a PDF carrying it.
/// where they are written. A `.ai` or `.ait` file is a PDF carrying it; EPS carries it in comments.
fn is_native(f: &Format) -> bool {
matches!(f.id, "vectorcraft" | "template" | "ai" | "ait")
matches!(f.id, "vectorcraft" | "template" | "ai" | "ait" | "eps")
}
fn is_pdf(f: &Format) -> bool {
@@ -520,7 +520,7 @@ impl<W: std::io::Write> std::io::Write for Counted<W> {
/// `separateArtboards`, those `range` names (default all).
fn separate_boards(cmd: &str, f: &Format, doc: &Document, p: &Value) -> Result<Vec<usize>> {
// A legacy .ai file is a file per artboard already.
if !is_native(f) || !bool_or(p, "separateArtboards", false) || super::legacy_ai::wanted(cmd, f, p)? {
if !is_native(f) || f.id == "eps" || !bool_or(p, "separateArtboards", false) || super::legacy_ai::wanted(cmd, f, p)? {
return Ok(vec![]);
}
let range = str_param(p, "range").filter(|r| !r.trim().is_empty()).map(str::to_string);
@@ -735,8 +735,12 @@ impl SaveJob {
/// Encode one file of `doc` in the job's format.
fn encode_one(&self, doc: &Document) -> Result<Encoded> {
let (cmd, f) = (self.plan.mode.command(), self.plan.format);
let enc = if matches!(f.id, "ai" | "ait") { encode_ai(cmd, f, doc, &self.params) } else { encode_all(doc, f.id, &self.params) }
.map_err(|e| named_for_save(cmd, e))?;
let enc = match f.id {
"ai" | "ait" => encode_ai(cmd, f, doc, &self.params),
"eps" => super::eps::encode_save(doc, &self.params),
_ => encode_all(doc, f.id, &self.params),
}
.map_err(|e| named_for_save(cmd, e))?;
// A legacy .ai file per artboard.
let per_artboard = f.id == "ai" && super::legacy_ai::wanted(cmd, f, &self.params)?;
if enc.files.len() != 1 && !per_artboard {
@@ -950,7 +954,7 @@ pub(super) fn specs() -> Vec<CommandSpec> {
"Save Document",
[],
None,
"{path?, format?: vectorcraft|template|pdf|svg|svgz|ai (default: the path's extension, else the document's own format), options?: {…the format's options, see file.formatOptions; default: as last saved}, svg?: {…SVG options} (an SVG save keeps hidden layers, display:none, unless hiddenLayers is false), …the SVG and PDF options given flat instead (an option given flat or in svg and in options with different values is an error), compress?: bool (native, also in options: gzip; default: the useCompression preference; warnings say when the format doesn't read compress, version or preview given flat), version?: 4 (3, 2 or 1: for older VectorCraft versions; 1 and 2 are never compressed), preview?: false (embed a PNG of the first artboard, at most 256 px), separateArtboards?: false (native and .ai: also save each artboard of `range` to <name>-<artboard>.<ext> beside the file, holding that artboard and the art touching it; the result's files lists every file written), range?: \"1-3, 5\"|\"all\" (default; with separateArtboards), includeLinked?: false (keep linked files' own pixels, not just their previews; they stay linked), embedProfiles?: true (carry the ICC profiles loaded from files that the document is tagged with; opening the file installs them where missing), pdfCompatible?: false for native (also carry a PDF of every artboard) | true for .ai (false: blank pages, only the native document; a .ai file's compress, default true, is its compression.compressText), modified?: Unix seconds|null (the File Info modified date, and created date when there is none, a save to a file stamps; default now, recorded in the journal so a replay matches; null: leave the dates)} → {path, format, bytes, warnings, files?: [path…] (the file and its artboards' files), linked?: [path…] (images an SVG links to)}. Save writes one artboard, except a .ai file: a PDF-compatible file of every artboard carrying the native document (preserveEditing always on; PDF options flat or in options), which document.open restores exactly; separateArtboards writes more files, one per artboard. Without a path it writes the document's own file in its own format: a document opened from or saved as SVG/PDF saves as that again (warnings name what the format loses). No path known (never saved, converted from an older version, restored by Data Recovery, or another format) → {dataBase64, format, name, folder?, warnings} and the document stays modified",
"{path?, format?: vectorcraft|template|pdf|svg|svgz|ai|eps (default: the path's extension, else the document's own format), options?: {…the format's options, see file.formatOptions; default: as last saved}, svg?: {…SVG options} (an SVG save keeps hidden layers, display:none, unless hiddenLayers is false), …the SVG and PDF options given flat instead (an option given flat or in svg and in options with different values is an error), compress?: bool (native, also in options: gzip; default: the useCompression preference; warnings say when the format doesn't read compress, version or preview given flat), version?: 4 (3, 2 or 1: for older VectorCraft versions; 1 and 2 are never compressed), preview?: false (embed a PNG of the first artboard, at most 256 px), separateArtboards?: false (native and .ai: also save each artboard of `range` to <name>-<artboard>.<ext> beside the file, holding that artboard and the art touching it; the result's files lists every file written), range?: \"1-3, 5\"|\"all\" (default; with separateArtboards), includeLinked?: false (keep linked files' own pixels, not just their previews; they stay linked), embedProfiles?: true (carry the ICC profiles loaded from files that the document is tagged with; opening the file installs them where missing), pdfCompatible?: false for native (also carry a PDF of every artboard) | true for .ai (false: blank pages, only the native document; a .ai file's compress, default true, is its compression.compressText), modified?: Unix seconds|null (the File Info modified date, and created date when there is none, a save to a file stamps; default now, recorded in the journal so a replay matches; null: leave the dates)} → {path, format, bytes, warnings, files?: [path…] (the file and its artboards' files), linked?: [path…] (images an SVG links to)}. Save writes one artboard, except a .ai file: a PDF-compatible file of every artboard carrying the native document (preserveEditing always on; PDF options flat or in options), which document.open restores exactly; separateArtboards writes more files, one per artboard. EPS saves the chosen artboard (first by default), retaining its page margins and the whole editable document; its PostScript appearance flattens transparency, as EPS requires. Without a path it writes the document's own file in its own format: a document opened from or saved as SVG/PDF/EPS saves as that again (warnings name what the format loses). No path known (never saved, converted from an older version, restored by Data Recovery, or another format) → {dataBase64, format, name, folder?, warnings} and the document stays modified",
has_doc,
|s, p| save(s, SaveMode::Save, p)
),
@@ -959,7 +963,7 @@ pub(super) fn specs() -> Vec<CommandSpec> {
"Save As…",
["File"],
Some("Cmd+Shift+S"),
"{…as document.save} {path?, format?: vectorcraft|template|pdf|svg|svgz|ai (default: the path's extension, else the document's own; ait, a .ait template, is written by file.saveAsTemplate), options?, svg?, modified?} the document takes on the new path, name and format (except a template, which is always a copy) → {path, format, bytes, warnings}; no path → {dataBase64, format, name, folder?, warnings}",
"{…as document.save} {path?, format?: vectorcraft|template|pdf|svg|svgz|ai|eps (default: the path's extension, else the document's own; ait, a .ait template, is written by file.saveAsTemplate), options?, svg?, modified?} the document takes on the new path, name and format (except a template, which is always a copy) → {path, format, bytes, warnings}; no path → {dataBase64, format, name, folder?, warnings}",
has_doc,
|s, p| save(s, SaveMode::SaveAs, p)
),
+139 -1
View File
@@ -84,7 +84,7 @@ fn save_as_takes_on_path_format_and_options() {
let svg = s.execute("file.formatOptions", &json!({"format": "svg"})).unwrap();
assert_eq!(svg["options"]["outlineText"]["value"], false, "another format shows its defaults");
let ids: Vec<&str> = o["saveFormats"].as_array().unwrap().iter().map(|f| f["id"].as_str().unwrap()).collect();
assert_eq!(ids, ["vectorcraft", "template", "pdf", "svg", "svgz", "ai"]);
assert_eq!(ids, ["vectorcraft", "template", "pdf", "svg", "svgz", "ai", "eps"]);
// SVGZ is gzip-compressed SVG and reopens as SVGZ.
let svgz = path(&d, "doc.svgz");
s.execute("file.saveAs", &json!({"path": svgz})).unwrap();
@@ -405,3 +405,141 @@ fn transparency_grid_round_trips_without_dirtying() {
assert!(!s.doc().unwrap().transparency_grid);
let _ = std::fs::remove_dir_all(d);
}
#[test]
fn opened_eps_saves_to_its_own_file_with_editable_data_and_page_margins() {
let d = dir("eps-original-format");
let eps_path = path(&d, "Artwork.eps");
// Original, contributor-authored PostScript: the artwork occupies only part of the page.
let source = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 200 100\n0 0 1 setrgbcolor 30 20 40 30 rectfill\nshowpage\n%%EOF\n";
std::fs::write(&eps_path, source).unwrap();
let mut s = Session::new();
s.execute("document.open", &json!({"path": eps_path})).unwrap();
assert_eq!(s.doc().unwrap().format, "eps");
assert_eq!(s.doc().unwrap().path.as_deref(), Some(eps_path.as_str()));
let board = s.doc().unwrap().doc.artboards[0].rect;
assert_eq!((board.width(), board.height()), (200.0, 100.0));
rect(&mut s);
let edited_layers = s.doc().unwrap().doc.layers.clone();
let r = s.execute("document.save", &json!({"options": {"previewFormat": "none", "thumbnails": false}})).unwrap();
assert_eq!((r["path"].as_str(), r["format"].as_str()), (Some(eps_path.as_str()), Some("eps")));
let bytes = std::fs::read(&eps_path).unwrap();
assert!(bytes.starts_with(b"%!PS-Adobe-3.0 EPSF-3.0"));
assert!(std::str::from_utf8(vectorcraft_eps::sections(&bytes).unwrap().0).unwrap().contains("%%BoundingBox: 0 0 200 100"));
let editing = vectorcraft_format::load(&vectorcraft_eps::native(&bytes).unwrap()).unwrap();
assert_eq!(editing.artboards[0].rect, board);
assert_eq!(editing.layers, edited_layers);
assert!(!s.doc().unwrap().is_dirty());
let opened = s.execute("document.open", &json!({"path": eps_path})).unwrap();
assert_eq!(opened["restored"], true);
assert_eq!(s.doc().unwrap().doc.layers, edited_layers);
assert_eq!(s.doc().unwrap().doc.artboards[0].rect, board);
rect(&mut s);
s.execute("document.save", &json!({})).unwrap();
assert_eq!(s.doc().unwrap().path.as_deref(), Some(eps_path.as_str()));
std::fs::remove_dir_all(d).unwrap();
}
#[test]
fn eps_save_keeps_all_editing_artboards_while_printing_one_page() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 100, "artboards": 2})).unwrap();
rect(&mut s);
let r = s.execute("document.save", &json!({"format": "eps", "artboard": 1, "previewFormat": "none", "thumbnails": false})).unwrap();
let bytes = b64(&r);
let native = vectorcraft_format::load(&vectorcraft_eps::native(&bytes).unwrap()).unwrap();
assert_eq!(native.artboards.len(), 2);
assert_eq!(native.layers, s.doc().unwrap().doc.layers);
assert!(s.execute("document.save", &json!({"format": "eps", "range": "all"})).is_err(), "EPS is one printed page");
for options in [json!({"useArtboards": false}), json!({"selectedOnly": true})] {
assert!(s.execute("document.save", &json!({"format": "eps", "options": options})).is_err(), "cropping is Export's job");
}
}
#[test]
fn eps_save_retains_lossy_import_overwrite_protection_and_transparency_warning() {
let d = dir("eps-import-warning");
let eps_path = path(&d, "Artwork.eps");
let mut s = session();
rect(&mut s);
s.execute("transparency.set", &json!({"opacity": 50})).unwrap();
let r = s.execute("document.save", &json!({"path": eps_path, "previewFormat": "none", "thumbnails": false})).unwrap();
assert!(r["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("flatten")), "{r}");
let original = std::fs::read(&eps_path).unwrap();
let st = s.doc_mut().unwrap();
st.imported_from = Some(eps_path.clone());
st.import_losses.push("test import omitted artwork".into());
rect(&mut s);
assert!(s.execute("document.save", &json!({})).unwrap_err().to_string().contains("acknowledgeLoss"));
assert_eq!(std::fs::read(&eps_path).unwrap(), original);
s.execute("document.save", &json!({"acknowledgeLoss": true})).unwrap();
assert_ne!(std::fs::read(&eps_path).unwrap(), original);
std::fs::remove_dir_all(d).unwrap();
}
#[test]
fn eps_save_supports_an_empty_artboard_without_cropping_its_page() {
let mut s = session();
let r = s.execute("document.save", &json!({"format": "eps", "previewFormat": "none", "thumbnails": false})).unwrap();
let bytes = b64(&r);
let native = vectorcraft_format::load(&vectorcraft_eps::native(&bytes).unwrap()).unwrap();
assert_eq!(native.artboards[0].rect, s.doc().unwrap().doc.artboards[0].rect);
assert_eq!(native.node_count(), s.doc().unwrap().doc.node_count());
assert!(std::str::from_utf8(vectorcraft_eps::sections(&bytes).unwrap().0).unwrap().contains("%%BoundingBox: 0 0 200 100"));
}
#[test]
fn partially_read_eps_requires_confirmation_before_save_back() {
let d = dir("eps-partial-import");
let eps_path = path(&d, "partial.eps");
let original = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 200 100\n0 0 1 setrgbcolor 30 20 40 30 rectfill\nunsupportedMockyOperator\n1 0 0 setrgbcolor 80 20 40 30 rectfill\nshowpage\n%%EOF\n";
std::fs::write(&eps_path, original).unwrap();
let mut s = Session::new();
let opened = s.execute("document.open", &json!({"path": eps_path})).unwrap();
assert!(opened["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().starts_with("the file was read up to an error")), "{opened}");
rect(&mut s);
assert!(s.execute("document.save", &json!({})).unwrap_err().to_string().contains("acknowledgeLoss"));
assert_eq!(std::fs::read(&eps_path).unwrap(), original);
s.execute("document.save", &json!({"acknowledgeLoss": true})).unwrap();
assert!(vectorcraft_eps::native(&std::fs::read(&eps_path).unwrap()).is_some());
std::fs::remove_dir_all(d).unwrap();
}
#[test]
fn eps_with_damaged_editing_data_requires_confirmation_before_save_back() {
let d = dir("eps-damaged-editing");
let eps_path = path(&d, "damaged.eps");
let mut s = session();
rect(&mut s);
let r = s.execute("document.exportEps", &json!({"previewFormat": "none", "thumbnails": false, "cmykPostScript": false})).unwrap();
let mut bytes = b64(&r);
let at = bytes.windows(9).position(|w| w == b"native\n% ").unwrap() + 12;
bytes[at] = b'z';
std::fs::write(&eps_path, &bytes).unwrap();
let opened = s.execute("document.open", &json!({"path": eps_path})).unwrap();
assert_eq!(opened["restored"], false);
assert!(opened["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("editing data can't be read")), "{opened}");
rect(&mut s);
assert!(s.execute("document.save", &json!({})).unwrap_err().to_string().contains("acknowledgeLoss"));
assert_eq!(std::fs::read(&eps_path).unwrap(), bytes);
std::fs::remove_dir_all(d).unwrap();
}
#[test]
fn eps_save_bounds_large_page_preview_without_cropping_vector_or_editing_data() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 4000, "height": 4000})).unwrap();
rect(&mut s);
// Explicit colour preview remains a colour preview; Save bounds its resolution, not the art.
let r = s.execute("document.save", &json!({"format": "eps", "previewFormat": "tiffColor"})).unwrap();
let bytes = b64(&r);
let (ps, tiff) = vectorcraft_eps::sections(&bytes).unwrap();
let tiff = tiff.unwrap();
assert!(tiff.len() < 4 * 1024 * 1024 + 64 * 1024, "compatibility preview grew to {} bytes", tiff.len());
let preview = image::load_from_memory_with_format(tiff, image::ImageFormat::Tiff).unwrap();
assert_eq!((preview.width(), preview.height()), (1024, 1024));
assert!(std::str::from_utf8(ps).unwrap().contains("%%BoundingBox: 0 0 4000 4000"));
let native = vectorcraft_format::load(&vectorcraft_eps::native(&bytes).unwrap()).unwrap();
assert_eq!(native.artboards, s.doc().unwrap().doc.artboards);
assert_eq!(native.layers, s.doc().unwrap().doc.layers);
}
@@ -87,6 +87,9 @@ fn shown(f: &Format) -> impl Iterator<Item = &'static FormatOption> {
|| (range && o.name == "artboard")
|| (separate && o.name == "range")
|| (matches!(f.id, "ai" | "ait") && o.name == "useArtboards")
// EPS Save keeps a page and the complete editing document; cropped and
// selected-only output belongs to Export, never this save dialog.
|| (f.id == "eps" && matches!(o.name, "useArtboards" | "selectedOnly"))
|| (f.id == "ait" && o.name == "aiVersion"))
})
}
@@ -72,7 +72,7 @@ fn save_as_svg_opens_svg_options_then_writes_svg() {
// The save panel offers every save format, the document's own first.
let pick = &picks.borrow()[0];
let labels: Vec<&str> = pick.filters.iter().map(|f| f.0.as_str()).collect();
assert_eq!(labels, ["VectorCraft", "VectorCraft Template", "PDF", "SVG", "SVG Compressed", "PDF-compatible .ai"]);
assert_eq!(labels, ["VectorCraft", "VectorCraft Template", "PDF", "SVG", "SVG Compressed", "PDF-compatible .ai", "EPS"]);
assert_eq!(pick.name, "Untitled-1.vectorcraft");
let d = dialog(&app);
assert_eq!((d.kind.as_str(), d.str("path").as_str(), d.str("mode").as_str()), ("svgOptions", "art.svg", "save"));
@@ -332,3 +332,18 @@ fn save_as_pdf_asks_with_the_save_pdf_dialog_and_remembers_its_options() {
assert_eq!(dialog(&app).str("compatibility"), "1.5");
assert_eq!(dialog(&app).str("__save"), "file.saveCopy");
}
#[test]
fn eps_save_as_keeps_page_options_and_writes_eps_without_export_only_switches() {
let (mut app, written, _) = desktop("art.eps");
app.run("file.saveAs", json!({})).unwrap();
let names: Vec<_> = shown(fileio::format("eps").unwrap()).map(|option| option.name).collect();
assert!(!names.contains(&"useArtboards") && !names.contains(&"selectedOnly"));
assert!(names.contains(&"previewFormat"));
super::super::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
let saved = written.borrow();
assert_eq!(saved[0].0, "art.eps");
assert!(vectorcraft_eps::native(&saved[0].1).is_some());
assert_eq!(app.session.active().unwrap().format, "eps");
}
+19
View File
@@ -303,3 +303,22 @@ fn native_saves_stream_into_the_file() {
assert_eq!(*streamed.lock().unwrap(), [native]);
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn file_save_writes_opened_eps_back_without_another_save_as_dialog() {
let d = dir("eps-original-format");
let path = d.join("Artwork.eps").to_string_lossy().to_string();
std::fs::write(&path, b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 200 100\n0 0 1 setrgbcolor 30 20 40 30 rectfill\nshowpage\n%%EOF\n").unwrap();
let mut app = app();
app.run("document.open", json!({"path":path})).unwrap();
rect(&mut app);
app.run("file.save", json!({})).unwrap();
crate::background::wait_all(&mut app);
assert!(app.ui.dialog.is_none(), "ordinary EPS Save does not require another format dialog");
let bytes = std::fs::read(&path).unwrap();
assert!(vectorcraft_eps::sections(&bytes).unwrap().0.starts_with(b"%!PS-Adobe-3.0 EPSF-3.0"));
assert!(vectorcraft_eps::native(&bytes).is_some());
assert_eq!(app.session.active().unwrap().format, "eps");
assert_eq!(app.session.active().unwrap().path.as_deref(), Some(path.as_str()));
std::fs::remove_dir_all(d).unwrap();
}
File diff suppressed because one or more lines are too long
+8 -1
View File
File diff suppressed because one or more lines are too long
+7 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long