mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #2597 from dotCooCoo/fix/save-as-template-extension
Templates: Save as Template always writes a file with a template extension
This commit is contained in:
@@ -185,6 +185,9 @@ fn parent_folder(path: &str) -> Option<String> {
|
||||
fn save_format_of(f: &str) -> std::result::Result<&'static Format, String> {
|
||||
match format(f) {
|
||||
Some(f) if SAVE_FORMATS.contains(&f.id) => Ok(f),
|
||||
Some(f) if TEMPLATE_FORMATS.contains(&f.id) => {
|
||||
Err(format!("Save writes {}, not {}: use file.saveAsTemplate to write it", SAVE_FORMATS.join(", "), f.label))
|
||||
}
|
||||
Some(f) => Err(format!("Save writes {}, not {}: use document.export for other formats", SAVE_FORMATS.join(", "), f.label)),
|
||||
None => Err(format!("unknown format `{f}` (Save writes {})", SAVE_FORMATS.join(", "))),
|
||||
}
|
||||
@@ -196,6 +199,18 @@ pub fn save_format(format: Option<&str>, path: Option<&str>) -> std::result::Res
|
||||
save_format_of(format.or_else(|| path.and_then(format_for_name).map(|f| f.id)).unwrap_or("vectorcraft"))
|
||||
}
|
||||
|
||||
/// `path` for a template in format `f`: kept when its extension names `f`, with the extension
|
||||
/// replaced when it names another format (`card.png` → `card.vctemplate`), else with `f`'s
|
||||
/// extension added (`card`, `card v1.2`).
|
||||
fn template_path(path: &str, f: &Format) -> String {
|
||||
let ext = f.extensions[0];
|
||||
match format_for_name(path) {
|
||||
Some(g) if g.id == f.id => path.to_string(),
|
||||
Some(_) => std::path::Path::new(path).with_extension(ext).to_string_lossy().into_owned(),
|
||||
None => format!("{path}.{ext}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`save_format_of`] for command `cmd`.
|
||||
fn checked(cmd: &str, f: &str) -> Result<&'static Format> {
|
||||
save_format_of(f).map_err(|e| bad(cmd, e))
|
||||
@@ -217,6 +232,7 @@ pub fn save_plan(s: &Session, mode: SaveMode, p: &Value) -> Result<SavePlan> {
|
||||
(_, None, Some(f)) => checked(cmd, f.id)?,
|
||||
(_, None, None) => checked(cmd, st.format)?,
|
||||
};
|
||||
let path = if mode == SaveMode::Template { path.map(|p| template_path(&p, format)) } else { path };
|
||||
let mut given: Map<String, Value> = match p.get("options") {
|
||||
Some(Value::Object(o)) => o.iter().filter(|(k, _)| reads_option(format, k)).map(|(k, v)| (k.clone(), v.clone())).collect(),
|
||||
None | Some(Value::Null) => Map::new(),
|
||||
@@ -755,7 +771,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), 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 (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)
|
||||
),
|
||||
@@ -773,7 +789,7 @@ pub(super) fn specs() -> Vec<CommandSpec> {
|
||||
"Save as Template…",
|
||||
["File"],
|
||||
None,
|
||||
"{…as document.save} {path?, format?: template (default) | ait (also when format is not given and the path ends in .ait), compress?, version?, preview?} a template that opens as a new untitled document: a native template (.vctemplate), or a PDF-compatible .ait written as a .ai file is (the .ai options and the PDF options, as document.save takes them for .ai); the document is unchanged → {path, format, bytes, warnings}; no path → {dataBase64, format, name: \"<name> template.<ext>\", folder: the Templates folder (preference templatesFolder), warnings}",
|
||||
"{…as document.save} {path? (saved with the format's extension: one naming another format is replaced, and one is added to a name without it), format?: template (default) | ait (also when format is not given and the path ends in .ait), compress?, version?, preview?} a template that opens as a new untitled document: a native template (.vctemplate), or a PDF-compatible .ait written as a .ai file is (the .ai options and the PDF options, as document.save takes them for .ai); the document is unchanged → {path, format, bytes, warnings}; no path → {dataBase64, format, name: \"<name> template.<ext>\", folder: the Templates folder (preference templatesFolder), warnings}",
|
||||
has_doc,
|
||||
|s, p| save(s, SaveMode::Template, p)
|
||||
),
|
||||
|
||||
@@ -223,7 +223,8 @@ fn templates_open_untitled() {
|
||||
let mut s = session(100.0, 100.0, 1);
|
||||
let dir = tmp_dir("tpl");
|
||||
let path = dir.join("t.vectorcraft").to_string_lossy().to_string();
|
||||
s.execute("file.saveAsTemplate", &json!({"path": path})).unwrap();
|
||||
// A template under a native file name still opens untitled.
|
||||
std::fs::write(&path, b64(&s.execute("file.saveAsTemplate", &json!({})).unwrap())).unwrap();
|
||||
let r = s.execute("document.open", &json!({"path": path})).unwrap();
|
||||
assert!(r["title"].as_str().unwrap().starts_with("Untitled-"), "{r}");
|
||||
assert_eq!(s.doc().unwrap().path, None);
|
||||
|
||||
@@ -201,6 +201,37 @@ fn save_as_template_writes_an_ait_that_new_from_template_opens_untitled() {
|
||||
assert!(vectorcraft_pdf::editing(&b64(&r)).is_some_and(|e| e.intact));
|
||||
}
|
||||
|
||||
/// Save as Template writes a file named with its template format's extension: another format's
|
||||
/// extension is replaced and a name without one gets it; Save and Save As name the command that
|
||||
/// writes a `.ait`.
|
||||
#[test]
|
||||
fn save_as_template_names_its_file_after_the_template_format() {
|
||||
let mut s = rich();
|
||||
for (name, format, saved) in [
|
||||
("card.png", None, "card.vctemplate"),
|
||||
("card.vectorcraft", None, "card.vctemplate"),
|
||||
("card.ait", Some("template"), "card.vctemplate"),
|
||||
("card.pdf", Some("ait"), "card.ait"),
|
||||
("card v1.2", None, "card v1.2.vctemplate"),
|
||||
] {
|
||||
let (typed, want) = (tmp(&format!("named/{name}")), tmp(&format!("named/{saved}")));
|
||||
let _ = std::fs::create_dir_all(std::path::Path::new(&typed).parent().unwrap());
|
||||
let _ = std::fs::remove_file(&typed);
|
||||
let _ = std::fs::remove_file(&want);
|
||||
let r = s.execute("file.saveAsTemplate", &json!({"path": typed, "format": format})).unwrap();
|
||||
assert_eq!(r["path"], want.as_str(), "{name}: {r}");
|
||||
let bytes = std::fs::read(&want).unwrap_or_else(|e| panic!("{name}: {saved} not written: {e}"));
|
||||
let ait = saved.ends_with(".ait");
|
||||
assert_eq!(bytes.starts_with(b"%PDF"), ait, "{name}");
|
||||
assert!(ait || vectorcraft_format::load(&bytes).is_ok_and(|d| d.template), "{name}: a native template");
|
||||
assert!(!std::path::Path::new(&typed).exists(), "{name}: nothing written under the typed name");
|
||||
}
|
||||
for (cmd, p) in [("file.saveAs", json!({"format": "ait"})), ("document.save", json!({"path": tmp("card.ait")}))] {
|
||||
let e = s.execute(cmd, &p).unwrap_err().to_string();
|
||||
assert!(e.contains("file.saveAsTemplate") && !e.contains("document.export"), "{cmd}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standards_and_screens_leave_the_editing_data_out() {
|
||||
let mut s = rich();
|
||||
|
||||
@@ -338,8 +338,9 @@ fn template_opens_as_untitled() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-tpl-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("letterhead.vectorcraft");
|
||||
s.execute("file.saveAsTemplate", &json!({"path": path.to_str().unwrap()})).unwrap();
|
||||
s.execute("document.open", &json!({"path": path.to_str().unwrap()})).unwrap();
|
||||
let r = s.execute("file.saveAsTemplate", &json!({"path": path.to_str().unwrap()})).unwrap();
|
||||
assert_eq!(r["path"], dir.join("letterhead.vctemplate").to_str().unwrap(), "saved with the template's extension");
|
||||
s.execute("document.open", &json!({"path": r["path"]})).unwrap();
|
||||
let st = s.doc().unwrap();
|
||||
assert!(st.path.is_none() && !st.doc.template);
|
||||
assert!(st.doc.title.starts_with("Untitled-"), "{}", st.doc.title);
|
||||
|
||||
@@ -43,8 +43,9 @@ fn headless_opens_pdf_ai_and_templates() {
|
||||
std::fs::write(&ait, &pdf).unwrap();
|
||||
let r = text(&call_tool(&mut h, "open_file", &json!({"path": ait})));
|
||||
assert!(r["title"].as_str().unwrap().starts_with("Untitled-"), "{r}");
|
||||
let tpl = tmp("t.vectorcraft");
|
||||
h.call("engine.execute", json!({"command": "file.saveAsTemplate", "params": {"path": tpl}})).unwrap();
|
||||
let saved = h.call("engine.execute", json!({"command": "file.saveAsTemplate", "params": {"path": tmp("t.vectorcraft")}})).unwrap();
|
||||
let tpl = tmp("t.vctemplate");
|
||||
assert_eq!(saved["path"], tpl.as_str(), "saved with the template's extension: {saved}");
|
||||
let r = text(&call_tool(&mut h, "open_file", &json!({"path": tpl})));
|
||||
assert!(r["title"].as_str().unwrap().starts_with("Untitled-"), "{r}");
|
||||
assert_eq!(h.session.doc().unwrap().path, None, "Save asks for a new name");
|
||||
|
||||
@@ -697,7 +697,7 @@ mod tests {
|
||||
assert_eq!(pick.name, "Untitled-1 template.vctemplate");
|
||||
assert_eq!(pick.folder.as_deref().map(std::path::Path::new), Some(folder.as_path()));
|
||||
assert_eq!(pick.filters, [("VectorCraft Template", &["vctemplate"][..]), ("PDF-compatible .ait template", &["ait"][..])]);
|
||||
assert_eq!(written.borrow()[1].0, "copy.vectorcraft", "the picked name is kept");
|
||||
assert_eq!(written.borrow()[1].0, "copy.vectorcraft.vctemplate", "the picked name, with the template's extension");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -726,6 +726,22 @@ mod tests {
|
||||
assert!(bytes.starts_with(b"%PDF"));
|
||||
}
|
||||
|
||||
/// A name typed in the Save as Template panel with a Save As format's extension gets the
|
||||
/// template's extension added, as Save As adds its own to a name it doesn't write.
|
||||
#[test]
|
||||
fn save_as_template_adds_its_extension_to_a_save_formats_name() {
|
||||
for (picked, saved) in
|
||||
[("card.pdf", "card.pdf.vctemplate"), ("card.vectorcraft", "card.vectorcraft.vctemplate"), ("card.svg", "card.svg.vctemplate")]
|
||||
{
|
||||
let (mut app, written, _) = desktop(picked);
|
||||
app.session.prefs.templates_folder = vectorcraft_testkit::temp_dir("save-template-ext").to_string_lossy().into_owned();
|
||||
app.run("file.saveAsTemplate", json!({})).unwrap();
|
||||
let (path, bytes) = written.borrow()[0].clone();
|
||||
assert_eq!(path, saved, "typed {picked}");
|
||||
assert!(vectorcraft_format::load(&bytes).is_ok_and(|d| d.template), "typed {picked}: a native template");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_save_as_names_the_file_and_picks_the_format() {
|
||||
let (mut app, written) = web();
|
||||
|
||||
@@ -3576,6 +3576,7 @@ axis Both И двете
|
||||
@msg SVG parse error: {0} Грешка при анализ на SVG: {0}
|
||||
@msg Save for Web: the image is empty Запис за уеб: изображението е празно
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Записът записва {_1}, а не {_2}: използвайте document.export за други формати
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Записът записва {_1}, а не {_2}: използвайте file.saveAsTemplate, за да го запишете
|
||||
@msg Saved recovery data for {title} Запазени данни за възстановяване за {title}
|
||||
@msg Saved to User Defined: {_1} Запазено в Потребителски: {_1}
|
||||
@msg Saved {path} Записано: {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3948,6 +3948,7 @@ imageTrace Outline Obrys
|
||||
@msg SVG parse error: {0} chyba při analýze SVG: {0}
|
||||
@msg Save for Web: the image is empty Uložit pro web: obraz je prázdný
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Uložení zapisuje {_1}, ne {_2}: pro jiné formáty použijte document.export
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Uložení zapisuje {_1}, ne {_2}: k jeho zápisu použijte file.saveAsTemplate
|
||||
@msg Saved recovery data for {title} Data pro obnovení uložena pro {title}
|
||||
@msg Saved to User Defined: {_1} Uloženo do uživatelských knihoven: {_1}
|
||||
@msg Saved {path} Uloženo: {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3550,6 +3550,7 @@ theme Appearance Erscheinungsbild
|
||||
@msg SVG parse error: {0} SVG-Analysefehler: {0}
|
||||
@msg Save for Web: the image is empty Für Web speichern: Das Bild ist leer
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats „Speichern“ schreibt {_1}, nicht {_2}: Verwenden Sie document.export für andere Formate
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it „Speichern“ schreibt {_1}, nicht {_2}: Verwenden Sie dafür file.saveAsTemplate
|
||||
@msg Saved recovery data for {title} Wiederherstellungsdaten für {title} gespeichert
|
||||
@msg Saved to User Defined: {_1} In „Benutzerdefiniert“ gespeichert: {_1}
|
||||
@msg Saved {path} {path} gespeichert
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1959,6 +1959,7 @@ axis Both Και τα δύο
|
||||
@msg SVG parse error: {0} Σφάλμα ανάλυσης SVG: {0}
|
||||
@msg Save for Web: the image is empty Αποθήκευση για ιστό: η εικόνα είναι κενή
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Η αποθήκευση γράφει {_1}, όχι {_2}: χρησιμοποιήστε το document.export για άλλες μορφές
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Η αποθήκευση γράφει {_1}, όχι {_2}: χρησιμοποιήστε το file.saveAsTemplate για να το γράψετε
|
||||
@msg Saved recovery data for {title} Αποθηκεύτηκαν δεδομένα ανάκτησης για το {title}
|
||||
@msg Saved to User Defined: {_1} Αποθηκεύτηκε στις προσαρμοσμένες ρυθμίσεις: {_1}
|
||||
@msg Saved {path} Αποθηκεύτηκε το {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 1902 and column 19.
|
@@ -3647,6 +3647,7 @@ axis Both Ambos
|
||||
@msg SVG parse error: {0} error al analizar el SVG: {0}
|
||||
@msg Save for Web: the image is empty Guardar para web: la imagen está vacía
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Guardar escribe {_1}, no {_2}: usa document.export para otros formatos
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Guardar escribe {_1}, no {_2}: usa file.saveAsTemplate para escribirlo
|
||||
@msg Saved recovery data for {title} Se guardaron los datos de recuperación de {title}
|
||||
@msg Saved to User Defined: {_1} Se guardó en Definido por el usuario: {_1}
|
||||
@msg Saved {path} Se guardó {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3875,6 +3875,7 @@ imageTrace Outline Ääriviiva
|
||||
@msg SVG parse error: {0} SVG-jäsennysvirhe: {0}
|
||||
@msg Save for Web: the image is empty Tallenna verkkoon: kuva on tyhjä
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Tallenna kirjoittaa muotoa {_1}, ei {_2}: käytä muille muodoille komentoa document.export
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Tallenna kirjoittaa muotoa {_1}, ei {_2}: kirjoita se komennolla file.saveAsTemplate
|
||||
@msg Saved recovery data for {title} Tallennettiin palautustiedot: {title}
|
||||
@msg Saved to User Defined: {_1} Tallennettiin kohteeseen Käyttäjän määrittämä: {_1}
|
||||
@msg Saved {path} Tallennettiin {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3644,6 +3644,7 @@ axis Both Les deux
|
||||
@msg SVG parse error: {0} Erreur d’analyse SVG : {0}
|
||||
@msg Save for Web: the image is empty Enregistrer pour le Web : l’image est vide
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Enregistrer écrit du {_1}, pas du {_2} : utilisez document.export pour les autres formats
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Enregistrer écrit du {_1}, pas du {_2} : utilisez file.saveAsTemplate pour l’écrire
|
||||
@msg Saved recovery data for {title} Données de récupération enregistrées pour {title}
|
||||
@msg Saved to User Defined: {_1} Enregistré dans Défini par l’utilisateur : {_1}
|
||||
@msg Saved {path} {path} enregistré
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4590,6 +4590,7 @@ imageTrace Outline Körvonal
|
||||
@msg the SVG's elements are nested more than {0} deep az SVG elemei több mint {0} szint mélyen vannak egymásba ágyazva
|
||||
@msg Save for Web: the image is empty Mentés webre: a kép üres
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats a Mentés ezt írja: {_1}, ezt nem: {_2}; más formátumhoz használd a document.export parancsot
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it a Mentés ezt írja: {_1}, ezt nem: {_2}; ennek írásához használd a file.saveAsTemplate parancsot
|
||||
@msg saved panel layout is invalid; reset the workspace first a mentett panelelrendezés érvénytelen; előbb állítsd vissza a munkaterületet
|
||||
@msg Saved recovery data for {title} Helyreállítási adatok mentve: {title}
|
||||
@msg Saved to User Defined: {_1} Mentve a felhasználóiak közé: {_1}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3650,6 +3650,7 @@ axis Both Entrambi
|
||||
@msg SVG parse error: {0} Errore di analisi SVG: {0}
|
||||
@msg Save for Web: the image is empty Salva per il web: l'immagine è vuota
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Salva scrive {_1}, non {_2}: usa document.export per gli altri formati
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Salva scrive {_1}, non {_2}: usa file.saveAsTemplate per scriverlo
|
||||
@msg Saved recovery data for {title} Dati di recupero salvati per {title}
|
||||
@msg Saved to User Defined: {_1} Salvato in Definito dall'utente: {_1}
|
||||
@msg Saved {path} Salvato {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3745,6 +3745,7 @@ swatch Bright Violet ビビッドバイオレット
|
||||
@msg {_1} files can be opened but not written (see document.formats) {_1} ファイルは開けますが、保存や書き出しはできません (document.formats を参照してください)
|
||||
@msg {path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64) {path}: この環境にはファイルシステムがありません (開くには dataBase64 を渡し、dataBase64 を受け取るにはパスを省略してください)
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats 保存できる形式は {_1} のみで、{_2} 形式では保存できません: ほかの形式には document.export を使用してください
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it 保存できる形式は {_1} のみで、{_2} 形式では保存できません: この形式で保存するには file.saveAsTemplate を使用してください
|
||||
@msg no changes since the last save 前回の保存以降、変更はありません
|
||||
@msg unknown format `{f}` (Save writes {_1}) `{f}` は不明な形式です (保存できる形式: {_1})
|
||||
@msg `svg` must be an object of SVG options (see document.formats) `svg` には SVG オプションのオブジェクトを指定してください (document.formats を参照)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3677,6 +3677,7 @@ theme Appearance 외관
|
||||
@msg {_1} files can be opened but not written (see document.formats) {_1} 파일은 열 수 있지만 쓸 수 없습니다(document.formats 참조)
|
||||
@msg {path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64) {path}: 여기에는 파일 시스템이 없습니다(열려면 dataBase64를 전달하고, dataBase64를 얻으려면 경로를 생략하세요)
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats 저장은 {_2}이(가) 아닌 {_1}(으)로 기록합니다. 다른 형식은 document.export를 사용하세요
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it 저장은 {_2}이(가) 아닌 {_1}(으)로 기록합니다. 이 형식으로 기록하려면 file.saveAsTemplate를 사용하세요
|
||||
@msg no changes since the last save 마지막 저장 이후 변경 내용이 없습니다
|
||||
@msg unknown format `{f}` (Save writes {_1}) 알 수 없는 형식 `{f}`(저장은 {_1}(으)로 기록)
|
||||
@msg `svg` must be an object of SVG options (see document.formats) `svg`는 SVG 옵션 개체여야 합니다(document.formats 참조)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3763,6 +3763,7 @@ imageTrace Outline Kontur
|
||||
@msg SVG parse error: {0} Błąd analizy SVG: {0}
|
||||
@msg Save for Web: the image is empty Zapisz dla Internetu: obraz jest pusty
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Zapis tworzy {_1}, nie {_2}: dla innych formatów użyj document.export
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Zapis tworzy {_1}, nie {_2}: aby go zapisać, użyj file.saveAsTemplate
|
||||
@msg Saved recovery data for {title} Zapisano dane odzyskiwania dla {title}
|
||||
@msg Saved to User Defined: {_1} Zapisano w zdefiniowanych przez użytkownika: {_1}
|
||||
@msg Saved {path} Zapisano {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3247,6 +3247,7 @@ axis Both Ambos
|
||||
@msg SVG parse error: {0} Erro de análise do SVG: {0}
|
||||
@msg Save for Web: the image is empty Salvar para Web: a imagem está vazia
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Salvar grava {_1}, não {_2}: use document.export para outros formatos
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Salvar grava {_1}, não {_2}: use file.saveAsTemplate para gravá-lo
|
||||
@msg Saved recovery data for {title} Dados de recuperação de {title} salvos
|
||||
@msg Saved to User Defined: {_1} Salvo em Definido pelo usuário: {_1}
|
||||
@msg Saved {path} {path} salvo
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3646,6 +3646,7 @@ axis Both Оба
|
||||
@msg SVG parse error: {0} ошибка разбора SVG: {0}
|
||||
@msg Save for Web: the image is empty Сохранить для web: изображение пусто
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Сохранение записывает {_1}, а не {_2}: используйте document.export для других форматов
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Сохранение записывает {_1}, а не {_2}: чтобы записать его, используйте file.saveAsTemplate
|
||||
@msg Saved recovery data for {title} Сохранены данные восстановления для {title}
|
||||
@msg Saved to User Defined: {_1} Сохранено в «Пользовательский»: {_1}
|
||||
@msg Saved {path} Сохранено: {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3576,6 +3576,7 @@ axis Both Båda
|
||||
@msg SVG parse error: {0} SVG-tolkningsfel: {0}
|
||||
@msg Save for Web: the image is empty Spara för webb: bilden är tom
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Spara skriver {_1}, inte {_2}: använd document.export för andra format
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Spara skriver {_1}, inte {_2}: använd file.saveAsTemplate för att skriva det
|
||||
@msg Saved recovery data for {title} Sparade återställningsdata för {title}
|
||||
@msg Saved to User Defined: {_1} Sparat i Användardefinierat: {_1}
|
||||
@msg Saved {path} Sparade {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3634,6 +3634,7 @@ axis Both Обидві
|
||||
@msg SVG parse error: {0} Помилка розбору SVG: {0}
|
||||
@msg Save for Web: the image is empty Зберегти для Інтернету: зображення пусте
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Збереження записує {_1}, а не {_2}: використовуйте document.export для інших форматів
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Збереження записує {_1}, а не {_2}: щоб записати його, використовуйте file.saveAsTemplate
|
||||
@msg Saved recovery data for {title} Збережено дані відновлення для {title}
|
||||
@msg Saved to User Defined: {_1} Збережено в «Користувацькі»: {_1}
|
||||
@msg Saved {path} Збережено: {path}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 721 and column 40.
|
@@ -4223,6 +4223,7 @@ theme Appearance Giao diện
|
||||
@msg the SVG's elements are nested more than {0} deep các phần tử SVG lồng nhau sâu quá {0} cấp
|
||||
@msg Save for Web: the image is empty Lưu cho Web: ảnh trống
|
||||
@msg Save writes {_1}, not {_2}: use document.export for other formats Lưu ghi {_1}, không phải {_2}: dùng document.export cho các định dạng khác
|
||||
@msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it Lưu ghi {_1}, không phải {_2}: dùng file.saveAsTemplate để ghi định dạng này
|
||||
@msg saved panel layout is invalid; reset the workspace first bố cục bảng đã lưu không hợp lệ; hãy đặt lại không gian làm việc trước
|
||||
@msg Saved recovery data for {title} Đã lưu dữ liệu phục hồi cho {title}
|
||||
@msg Saved to User Defined: {_1} Đã lưu vào Người dùng tùy chỉnh: {_1}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3780 and column 10.
|
@@ -349,11 +349,12 @@ pub fn remember_view(app: &mut VectorcraftApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// A path typed in a save panel: kept when its extension names a save format, or, for Save as
|
||||
/// Template (`mode`), when it names a template format (the panel's file type); otherwise `f`'s
|
||||
/// extension is added.
|
||||
/// A path typed in a save panel: kept when its extension names one of the panel's file types (a
|
||||
/// save format, or a template format for Save as Template, `mode`); otherwise `f`'s extension is
|
||||
/// added.
|
||||
fn with_save_extension(path: &str, f: &Format, mode: SaveMode) -> String {
|
||||
with_extension(path, f.extensions[0], |g| SAVE_FORMATS.contains(&g.id) || (mode == SaveMode::Template && TEMPLATE_FORMATS.contains(&g.id)))
|
||||
let formats = if mode == SaveMode::Template { TEMPLATE_FORMATS } else { SAVE_FORMATS };
|
||||
with_extension(path, f.extensions[0], |g| formats.contains(&g.id))
|
||||
}
|
||||
|
||||
/// Write every file of an export (one per artboard, linked images) for the destination `path`.
|
||||
|
||||
+6
-3
@@ -473,10 +473,13 @@ markup around it: if another app changed the SVG since, `document.open` reads it
|
||||
`warnings`.
|
||||
|
||||
Saving beyond `save_file` goes through `run_command`: `file.saveAs {path?, format?, options?, svg?}` (the document takes
|
||||
on the new file and format), `file.saveCopy` (the document keeps its path, title and modified state),
|
||||
on the new file and format; for `format: "ait"`, or a `.ait` path without `format`, it fails with an error that names
|
||||
`file.saveAsTemplate`), `file.saveCopy` (the document keeps its path, title and modified state),
|
||||
`file.saveAsTemplate {path?, format?}` (it writes a `.vctemplate`, or, for `format: "ait"` or a `.ait` path, a
|
||||
PDF-compatible `.ait` written as a `.ai` file is; both open untitled; the suggested name and the Templates folder come
|
||||
back without a path), `file.newFromTemplate {path}`, `file.revert` and `file.formatOptions {format?}` (a format's options
|
||||
PDF-compatible `.ait` written as a `.ai` file is; both open untitled; a path whose extension names another format has
|
||||
it replaced by the template's extension, and any other path that does not end in that extension has it added, so
|
||||
`card.png` is saved as `card.vctemplate` and `card` as `card.vctemplate`; the suggested name and the Templates folder
|
||||
come back without a path), `file.newFromTemplate {path}`, `file.revert` and `file.formatOptions {format?}` (a format's options
|
||||
with the values a save would use). Without a path the save commands return `{dataBase64, name, folder?, warnings}`. A document saved as SVG or PDF
|
||||
remembers its options, so the next save reuses them. A PDF opened with `pages`, `cropTo` or `password` is only part
|
||||
of the file, so Save asks for a new name instead of writing it back.
|
||||
|
||||
@@ -612,6 +612,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- `.vectorcraft` (lossless JSON, compressed, atomic saves, save down to v1), Save As with a format chooser, Save a Copy, Revert, templates, Data Recovery after a crash (browser tabs hold their copies with Web Locks too, so a background tab's copies are never taken for a crash's, and a tab repairs copies another removed when it resumes: #367), background save and export. The desktop app writes its warnings and errors (graphics adapter, a lost graphics device, panics the guard recovered from) to standard error and to `logs/vectorcraft.log` next to the preferences, the two previous runs kept and each file capped at 16 MiB, so a launch without a terminal still leaves something to attach to a bug report (#465). Native documents with a lot of path data (traced or generated art, in layers, symbols or patterns) are saved as compact JSON, about a fifth of the size; small documents stay pretty-printed (#1492). Saving to a network drive on macOS falls back to a plain fsync when the share refuses a full flush (#1625, part of #1620; not confirmed on a real share), and a save still succeeds when a folder refuses the temporary file, the rename over the document or copying permissions: the content is written elsewhere first and copied in, and a failed copy keeps the temporary file and names it (#1649, part of #1620). A document opened by a relative path finds its relatively linked images, and `vectorcraft-cli convert` lists what reading the input left out as `importWarnings` (#1606, fixes #1604).
|
||||
- macOS: the save sheet of Save As, Save a Copy, Save as Template and the first Save of a new document has a Format menu that lists each format the save can write, with the suggested file's format selected. Choosing a format changes the extension in the name field, and a name typed without an extension gets the chosen format's. A name typed with another listed extension is saved in that format. The menu is set up before the sheet opens; a save panel with one file type is shown by rfd as before (#866, `apps/vectorcraft/src/mac_save_format.rs`).
|
||||
- Save as Template writes a PDF-compatible `.ait` as well as a `.vctemplate`, which stays the default: the save panel and the web build's Save as Template dialog list both, and New from Template opens the `.ait` as an untitled document with its layers and art (`file.saveAsTemplate {format: "ait"}`, or a path ending in `.ait`).
|
||||
Save as Template always writes a file named with its template type's extension: `file.saveAsTemplate` saves a path ending in another format's extension (`card.png`) as `card.vctemplate` (or `card.ait`), the desktop save panel adds `.vctemplate` to a name typed with a Save As format's extension (`card.pdf.vctemplate`), and `file.saveAs` and `document.save` refuse `ait` with an error that names `file.saveAsTemplate`.
|
||||
- SVG/SVGZ in and out with SVG Options, Preserve Editing, symbols, filters, rich text and physical units (SVG type under a transform or `viewBox` scale opens at the size it draws at, the scale moved into its size, leading, baseline shift and strokes, a stretch, skew or rotation kept as its transform: #396; a top-level group with a clip path, such as a clipped Inkscape layer, opens as a layer with a clipping mask, its name kept: #420; hidden objects written hidden on Save and `convert`, and `data-*` attributes kept as the objects' own data: #522; top-level groups open as layers named from `data-name`, else the id, also when one more group (a transform on all the art, a `<switch>`) wraps them, hidden ones hidden, and a file whose art sits loose under a root `<svg>` with an id (or `data-name`), as one-layer exports write it, opens with its one layer named from it; nested groups stay groups, except the sublayers VectorCraft writes, marked `data-vc-layer`, and Inkscape sublayers, which open as sublayers in their layer's colour: #372); PDF and PDF-compatible `.ai` in and out (layers, with hidden ones kept hidden with their art and nested ones as sublayers, on pages of up to 64 million operators, art whose layer can't be told going to a hidden, non-printing layer rather than showing or printing: #419; strokes kept live, a fill and a stroke of one outline as one object; a type object's stroke written as each glyph's outline stroked back on the type, so it stays one editable object; lines wrapped in a frame (each wrapped line ending in a space, one left edge, leading and font) back as one area type object, left aligned or justified with its paragraph spacing, where laying it out breaks the lines where the file does; glyphs set apart by gaps wider than a space (a barcode's digit groups) keeping their places; layers stacked as the pages mark them even when one first paints on a later page, art ending a layer's marked content or a clip closing in a later layer kept in its own layer, and layers with no art kept empty: #508; art reaching past a plain PDF's page clipped to its crop box, as PDF viewers show it, each such object in a clip group of its own (a PDF-compatible `.ai` opens whole: #675); a `.ai` on its trim boxes with the bleed boxes round them as the document's bleed, as Crop To › Trim opens a PDF (#1266); masks, editable text with type set along a curve coming back as type on a path, each artboard's art on its own page only, an `.ai`'s white page under its layers left out rather than coming in as art, spot colours, presets, security, marks and bleed, ICC output intents, raster effects, subset fonts, PDF/X, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3). CMYK images keep their ink amounts from PDF import (and from placed CMYK TIFFs) to PDF export: a CMYK JPEG passes through unchanged, others stay CMYK (DeviceCMYK, or ICC-based CMYK in PDF/X-3/X-4) unless the Output settings convert them; the canvas shows them through the working CMYK profile (#320). Overprint is kept from PDF import (opened, the PDF part of an `.ai`, placed) to PDF export: the graphics state's `/OP` and `/op` become the overprint of fills, strokes and type, so a spot colour over a process colour still overprints; a page whose overprint can't be read for certain knocks out with a note, as does `/OPM 0` being read as nonzero overprint (#1388; hand-written fixtures, `pdf/src/tests_import_overprint.rs`). A masked CMYK image (`/SMask`, `/Mask` or a colour key) keeps its inks too, its alpha coming in as a greyscale opacity mask.
|
||||
- SVG/SVGZ in and out with SVG Options, Preserve Editing, symbols, filters, rich text and physical units (SVG type under a transform or `viewBox` scale opens at the size it draws at, the scale moved into its size, leading, baseline shift and strokes, a stretch, skew or rotation kept as its transform: #396; a top-level group with a clip path, such as a clipped Inkscape layer, opens as a layer with a clipping mask, its name kept: #420; hidden objects written hidden on Save and `convert`, and `data-*` attributes kept as the objects' own data: #522; top-level groups open as layers named from `data-name`, else the id, also when one more group (a transform on all the art, a `<switch>`) wraps them, hidden ones hidden, and a file whose art sits loose under a root `<svg>` with an id (or `data-name`), as one-layer exports write it, opens with its one layer named from it; nested groups stay groups, except the sublayers VectorCraft writes, marked `data-vc-layer`, and Inkscape sublayers, which open as sublayers in their layer's colour: #372); PDF and PDF-compatible `.ai` in and out (layers, with hidden ones kept hidden with their art and nested ones as sublayers, on pages of up to 64 million operators, art whose layer can't be told going to a hidden, non-printing layer rather than showing or printing: #419; strokes kept live, a fill and a stroke of one outline as one object; a type object's stroke written as each glyph's outline stroked back on the type, so it stays one editable object; lines wrapped in a frame (each wrapped line ending in a space, one left edge, leading and font) back as one area type object, left aligned or justified with its paragraph spacing, where laying it out breaks the lines where the file does; glyphs set apart by gaps wider than a space (a barcode's digit groups) keeping their places; layers stacked as the pages mark them even when one first paints on a later page, art ending a layer's marked content or a clip closing in a later layer kept in its own layer, and layers with no art kept empty: #508; art reaching past a plain PDF's page clipped to its crop box, as PDF viewers show it, each such object in a clip group of its own (a PDF-compatible `.ai` opens whole: #675); a `.ai` on its trim boxes with the bleed boxes round them as the document's bleed, as Crop To › Trim opens a PDF (#1266); masks, editable text with type set along a curve coming back as type on a path, each artboard's art on its own page only, an `.ai`'s white page under its layers left out rather than coming in as art, spot colours, presets, security, marks and bleed, ICC output intents, raster effects, subset fonts, PDF/X, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3). CMYK images keep their ink amounts from PDF import (and from placed CMYK TIFFs and the images in an `.ai` or EPS file's editing data, an alpha channel coming in as a grayscale opacity mask) to PDF export: a CMYK JPEG passes through unchanged, others stay CMYK (DeviceCMYK, or ICC-based CMYK in PDF/X-3/X-4) unless the Output settings convert them; the canvas shows them through the working CMYK profile (#320). Overprint is kept from PDF import (opened, the PDF part of an `.ai`, placed) to PDF export: the graphics state's `/OP` and `/op` become the overprint of fills, strokes and type, so a spot colour over a process colour still overprints; a page whose overprint can't be read for certain knocks out with a note, as does `/OPM 0` being read as nonzero overprint (#1388; hand-written fixtures, `pdf/src/tests_import_overprint.rs`). A masked CMYK image (`/SMask`, `/Mask` or a colour key) keeps its inks too, its alpha coming in as a greyscale opacity mask.
|
||||
SVG import's style cascade, used for hidden objects, non-scaling strokes and type, tests each element only against the `<style>` rules for its id, its classes and its element name, and the rules whose selector ends in none of these (`*`, `[x]`).
|
||||
|
||||
Reference in New Issue
Block a user