mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 18:27:02 -06:00
Merge pull request #2198 from marcoarnulfo/fix/pdf-title-document-name
PDF: an untitled document saved to a file exports titled after it
This commit is contained in:
@@ -157,7 +157,9 @@ fn source<'a>(s: &'a mut Session, f: &Format, p: &Value, cmd: &str) -> Result<Co
|
||||
if f.id == "txt" && bool_or(p, "selectionOnly", false) {
|
||||
return Ok(Cow::Owned(selection(s, cmd)?.0));
|
||||
}
|
||||
export_source(s.doc()?, p)
|
||||
let st = s.doc()?;
|
||||
let doc = export_source(st, p)?;
|
||||
Ok(super::titled_for_export(&doc, st.path.as_deref(), f.id).map_or(doc, Cow::Owned))
|
||||
}
|
||||
|
||||
/// The selected objects (not on template layers) alone on one layer, with one artboard: their
|
||||
|
||||
@@ -712,6 +712,18 @@ pub fn file_stem(name: &str) -> String {
|
||||
std::path::Path::new(name).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| name.to_string())
|
||||
}
|
||||
|
||||
/// What a `format` export of `doc`, whose file is `path`, writes when it isn't `doc` itself: a
|
||||
/// document that only has the Untitled-N name New gave it is titled after its file, as its tab is
|
||||
/// (the title PDF, PNG and SVG metadata carry). A title of its own stays, and native files keep
|
||||
/// the stored title.
|
||||
pub fn titled_for_export(doc: &vectorcraft_doc::Document, path: Option<&str>, format: &str) -> Option<vectorcraft_doc::Document> {
|
||||
let untitled = doc.title.strip_prefix("Untitled-").is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit()));
|
||||
let path = path.filter(|_| untitled && !matches!(format, "vectorcraft" | "template" | "ai"))?;
|
||||
let mut d = doc.clone();
|
||||
d.title = file_stem(path);
|
||||
Some(d)
|
||||
}
|
||||
|
||||
/// The format a file name's extension names.
|
||||
pub fn format_for_name(name: &str) -> Option<&'static Format> {
|
||||
Some(extension(name)).filter(|e| !e.is_empty()).and_then(|e| format(&e))
|
||||
|
||||
@@ -365,7 +365,9 @@ fn export_pdf(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let expanded = expand_preset(s, C, p)?;
|
||||
let p = &*expanded;
|
||||
let (bytes, warnings) = encode(C, &s.doc()?.doc, p)?;
|
||||
let st = s.doc()?;
|
||||
let titled = super::titled_for_export(&st.doc, st.path.as_deref(), "pdf");
|
||||
let (bytes, warnings) = encode(C, titled.as_ref().unwrap_or(&st.doc), p)?;
|
||||
write_or_return(str_param(p, "path"), &bytes, json!({ "warnings": warnings }))
|
||||
}
|
||||
|
||||
|
||||
@@ -446,6 +446,11 @@ pub(crate) fn job_for(s: &Session, st: &DocState, plan: SavePlan) -> Result<Save
|
||||
fn job_for_stamped(s: &Session, st: &DocState, plan: SavePlan, stamped: Option<i64>) -> Result<SaveJob> {
|
||||
let cmd = plan.mode.command();
|
||||
let mut own = doc_to_save(st, plan.format);
|
||||
// The file the document is saved as (else its own) titles it if New's name is all it has.
|
||||
let file = plan.path.as_deref().filter(|_| plan.retargets()).or(st.path.as_deref());
|
||||
if let Some(d) = super::titled_for_export(&own, file, plan.format.id) {
|
||||
own = Arc::new(d);
|
||||
}
|
||||
let mut snapshot = st.doc.clone();
|
||||
if let Some(at) = stamped {
|
||||
stamp_doc_dates(Arc::make_mut(&mut own), at);
|
||||
|
||||
@@ -161,3 +161,47 @@ fn svg_metadata_carries_file_info_as_dublin_core() {
|
||||
let plain = String::from_utf8(b64(&s.execute("document.export", &json!({"format": "svg"})).unwrap())).unwrap();
|
||||
assert!(!plain.contains("<metadata>") && plain.contains("<desc>"));
|
||||
}
|
||||
|
||||
/// The PDF `document.exportPdf` and `document.export` write of the active document (text
|
||||
/// uncompressed, so its metadata reads).
|
||||
fn pdf_texts(s: &mut Session) -> [String; 2] {
|
||||
let p = json!({"compression": {"compressText": false}, "preserveEditing": false});
|
||||
let a = b64(&s.execute("document.exportPdf", &p).unwrap());
|
||||
let b = b64(&s.execute("document.export", &json!({"format": "pdf", "compression": {"compressText": false}, "preserveEditing": false})).unwrap());
|
||||
[a, b].map(|pdf| String::from_utf8_lossy(&pdf).into_owned())
|
||||
}
|
||||
|
||||
/// The XMP `dc:title` of a PDF's text.
|
||||
fn xmp_title(pdf: &str) -> &str {
|
||||
let from = pdf.find("<dc:title>").expect("XMP title");
|
||||
&pdf[from..from + pdf[from..].find("</dc:title>").unwrap()]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_untitled_document_saved_to_a_file_exports_titled_after_it() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-pdftitle-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("Document C.vectorcraft");
|
||||
// Saved as a file, then exported: Untitled-1 was only the name New gave it.
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 80})).unwrap();
|
||||
s.execute("document.save", &json!({"path": path.to_str().unwrap()})).unwrap();
|
||||
for pdf in pdf_texts(&mut s) {
|
||||
assert!(pdf.contains("/Title(Document C)") && xmp_title(&pdf).contains("Document C"), "{}", xmp_title(&pdf));
|
||||
}
|
||||
// Opened from that file, in another session.
|
||||
let mut s = Session::new();
|
||||
s.execute("document.open", &json!({"path": path.to_str().unwrap()})).unwrap();
|
||||
for pdf in pdf_texts(&mut s) {
|
||||
assert!(pdf.contains("/Title(Document C)") && !pdf.contains("Untitled"), "{}", xmp_title(&pdf));
|
||||
}
|
||||
// The document's own title is left as it was.
|
||||
assert_eq!(s.doc().unwrap().doc.title, "Untitled-1");
|
||||
// A title of its own (File Info, New Document's Name) stays.
|
||||
let mut s = session();
|
||||
s.execute("document.save", &json!({"path": dir.join("Document D.vectorcraft").to_str().unwrap()})).unwrap();
|
||||
for pdf in pdf_texts(&mut s) {
|
||||
assert!(pdf.contains("/Title(Poster)") && xmp_title(&pdf).contains("Poster"));
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
@@ -618,6 +618,7 @@ pub fn export(app: &mut VectorcraftApp, format: Option<&str>, path: Option<Strin
|
||||
std::borrow::Cow::Borrowed(_) => st.doc.clone(),
|
||||
std::borrow::Cow::Owned(d) => std::sync::Arc::new(d),
|
||||
};
|
||||
let doc = fileio::titled_for_export(&doc, st.path.as_deref(), f.id).map_or(doc, std::sync::Arc::new);
|
||||
let path = target_path(app, path, f.extensions[0])?;
|
||||
let mut again = params.clone();
|
||||
if let Some(o) = again.as_object_mut() {
|
||||
|
||||
Reference in New Issue
Block a user