mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
Automation: exported layers keep linked smart objects as pixels in workspace sessions
`Headless::export_layers` calls `export_layers_with` outside `Session::execute`, so no session gate was installed when `encode` chose whether a PSD/PSB embeds a linked smart object's file. `export_layers_with` enters the session's gate itself, as `Session::execute` does, and a workspace session's layer export writes such a layer as pixels like `doc.save`.
This commit is contained in:
@@ -935,6 +935,43 @@ mod tests {
|
||||
std::fs::remove_dir_all(base).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_layer_exports_keep_linked_smart_objects_as_pixels() {
|
||||
use photocraft_engine::doc::{LayerContent, SmartSource};
|
||||
|
||||
const MARKER: &[u8] = b"PHOTOCRAFT-LINKED-0002";
|
||||
let has_marker = |bytes: &[u8]| bytes.windows(MARKER.len()).any(|w| w == MARKER);
|
||||
let base = std::env::temp_dir().join(format!("pc-export-layers-linked-{}", std::process::id()));
|
||||
let root = base.join("workspace");
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
let outside = base.join("outside.txt");
|
||||
std::fs::write(&outside, MARKER).unwrap();
|
||||
// A document whose smart object names a file outside the roots as its source.
|
||||
let mut session = photocraft_engine::Session::new();
|
||||
session.execute("file.new", json!({"width": 8, "height": 8, "background": "white"})).unwrap();
|
||||
session.execute("layer.smartObjects.convertToSmartObject", json!({})).unwrap();
|
||||
let state = session.active_mut().unwrap();
|
||||
let LayerContent::Smart(smart) = &mut std::sync::Arc::make_mut(&mut state.doc).layers[0].content else { panic!("smart object") };
|
||||
smart.source = SmartSource::Linked { path: outside.to_string_lossy().into_owned() };
|
||||
std::fs::write(root.join("in.pcraft"), photocraft_engine::smart_cmds::encode_source(&state.doc).unwrap()).unwrap();
|
||||
|
||||
let mut h = Headless::with_workspace(crate::AuthorizedWorkspace::new(Some(&root), Some(&root)).unwrap());
|
||||
h.handle("doc.open", json!({"path": "in.pcraft"})).unwrap();
|
||||
let out = root.join("layers");
|
||||
for format in ["psd", "psb"] {
|
||||
let exported = h.handle("doc.exportLayers", json!({"dir": out.to_string_lossy(), "format": format})).unwrap();
|
||||
assert_eq!(exported["count"], 1, "{exported}");
|
||||
}
|
||||
let files: Vec<_> = std::fs::read_dir(&out).unwrap().map(|e| e.unwrap().path()).collect();
|
||||
assert_eq!(files.len(), 2, "{files:?}");
|
||||
for file in files {
|
||||
assert!(!has_marker(&std::fs::read(&file).unwrap()), "{} embeds no file outside the roots", file.display());
|
||||
}
|
||||
// Release directory capabilities before cleanup on Windows.
|
||||
drop(h);
|
||||
std::fs::remove_dir_all(base).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_local_render_keeps_full_size_behavior() {
|
||||
let mut h = Headless::trusted_local();
|
||||
|
||||
@@ -1681,6 +1681,9 @@ pub fn export_layers_with(s: &Session, p: &Value, mut write: impl FnMut(&str, &[
|
||||
let cmd = "file.export.layersToFiles";
|
||||
let format = p.get("format").and_then(Value::as_str).unwrap_or("png").trim_start_matches('.').to_ascii_lowercase();
|
||||
let visible_only = p.get("visibleOnly").and_then(Value::as_bool).unwrap_or(true);
|
||||
// Callers outside `Session::execute` (headless workspace exports) carry the session's gate
|
||||
// into `encode`, so a PSD embeds no file a linked smart object names.
|
||||
let _authorization = crate::jobs::render_authorization_scope(s.authorize);
|
||||
let save = SaveOpts::from_params(s, p, cmd)?;
|
||||
let d = s.active().ok_or(EngineError::NoDocument)?;
|
||||
let prefix = p.get("prefix").and_then(Value::as_str).map(str::to_string).unwrap_or_else(|| stem(&d.doc.name));
|
||||
|
||||
Reference in New Issue
Block a user