mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-27 03:17:05 -06:00
* Load and save Photoshop Actions (.atn) (#219) - photocraft-psd: `atn` parser and writer for version-16 action sets, exact round trip, truncation/garbage safe, fuzz target and `atn_dump` example. Descriptor fix: `indx` and `Idnt` reference items carry a class before the number (real action files failed to parse). - photocraft-engine: `atn_cmds` maps 92 Photoshop events to commands and back (filters, adjustments per channel, image/canvas/crop/trim/rotate/flip/mode, make/set/select/delete/ hide/show/move/duplicate, fill/stroke, selection, clipboard, purge, profiles, play, stop). Other steps load as `actions.photoshopStep` (descriptor kept, written back by save, fails at playback by name); tool selections load switched off. Commands `actions.load`, `actions.save`, `actions.stopMessage`, `actions.photoshopStep`. `Action` gains `dialogs` and `set`. `layer` params accept a layer name, so loaded steps replay in any document. - UI: Actions panel menu › Load Actions… / Save Actions…; Photoshop steps labelled. - CLI: `batch --actions set.atn`. Automation policy covers the new path params. - Docs: file-format parity (.atn R/W), gaps G11, target-app parity, scorecard AUTO-219-2 partial, development guide. * Translate the Actions panel's Load/Save strings in every catalog The eight new panel strings (Load/Save Actions…, the loaded/saved counts, the kept-step label) were missing from all 19 catalogs; the languages that were complete are back at 100%. * Show the Actions entries in the default dock's panel menu The legacy dock renders its own group menu and only knew the Layers and Swatches entries, so Load Actions… and Save Actions… were missing there.
85 lines
3.8 KiB
Rust
85 lines
3.8 KiB
Rust
//! Print the actions and steps of `.atn` files, one event per line, with the top-level keys of
|
|
//! each step's descriptor: `cargo run -p photocraft-psd --example atn_dump -- set.atn…`
|
|
//!
|
|
//! Handy when adding a Photoshop event to the engine's `.atn` import table: it shows the event
|
|
//! id, the panel name and the parameter keys Photoshop recorded.
|
|
|
|
use photocraft_psd::atn;
|
|
use photocraft_psd::descriptor::{Descriptor, ReferenceItem, Value};
|
|
|
|
fn summary(v: &Value) -> String {
|
|
match v {
|
|
Value::Integer(i) => i.to_string(),
|
|
Value::Double(d) => format!("{d}"),
|
|
Value::UnitFloat { unit, value } => format!("{value}{}", String::from_utf8_lossy(unit)),
|
|
Value::Boolean(b) => b.to_string(),
|
|
Value::Text(t) => format!("{:?}", t.to_string_lossy()),
|
|
Value::Enumerated { type_id, value } => format!("{}.{}", String::from_utf8_lossy(type_id.as_bytes()), String::from_utf8_lossy(value.as_bytes())),
|
|
Value::Descriptor(d) | Value::GlobalObject(d) => format!("{{{}}}", keys(d)),
|
|
Value::List(l) => format!("[{}]", l.iter().map(summary).collect::<Vec<_>>().join(", ")),
|
|
Value::Reference(r) => format!("ref[{}]", r.iter().map(reference).collect::<Vec<_>>().join(" ")),
|
|
other => format!("{other:?}").chars().take(40).collect(),
|
|
}
|
|
}
|
|
|
|
fn id(i: &photocraft_psd::descriptor::Id) -> String {
|
|
String::from_utf8_lossy(i.as_bytes()).trim().to_string()
|
|
}
|
|
|
|
fn reference(item: &ReferenceItem) -> String {
|
|
match item {
|
|
ReferenceItem::Property { class, key } => format!("prop({},{})", id(&class.class_id), id(key)),
|
|
ReferenceItem::Class(c) => format!("Clss({})", id(&c.class_id)),
|
|
ReferenceItem::Enumerated { class, type_id, value } => format!("Enmr({},{}.{})", id(&class.class_id), id(type_id), id(value)),
|
|
ReferenceItem::Offset { class, offset } => format!("rele({},{offset})", id(&class.class_id)),
|
|
ReferenceItem::Identifier { class, id: v } => format!("Idnt({},{v})", id(&class.class_id)),
|
|
ReferenceItem::Index { class, index } => format!("indx({},{index})", id(&class.class_id)),
|
|
ReferenceItem::Name { class, name } => format!("name({},{:?})", id(&class.class_id), name.to_string_lossy()),
|
|
}
|
|
}
|
|
|
|
fn keys(d: &Descriptor) -> String {
|
|
d.items.iter().map(|(k, v)| format!("{}={}", String::from_utf8_lossy(k.as_bytes()), summary(v))).collect::<Vec<_>>().join(" ")
|
|
}
|
|
|
|
fn main() {
|
|
let mut failed = false;
|
|
for path in std::env::args().skip(1) {
|
|
let bytes = match std::fs::read(&path) {
|
|
Ok(b) => b,
|
|
Err(e) => {
|
|
eprintln!("{path}: {e}");
|
|
failed = true;
|
|
continue;
|
|
}
|
|
};
|
|
match atn::parse(&bytes) {
|
|
Ok(f) => {
|
|
println!(
|
|
"== {path}: set {:?}, {} actions{}",
|
|
f.name.to_string_lossy(),
|
|
f.actions.len(),
|
|
if f.warnings.is_empty() { String::new() } else { format!(" ({})", f.warnings.join("; ")) }
|
|
);
|
|
for a in &f.actions {
|
|
println!(" action {:?} ({} steps)", a.name.to_string_lossy(), a.items.len());
|
|
for item in &a.items {
|
|
let flags = format!("{}{}", if item.enabled { "" } else { " off" }, if item.with_dialog { " dialog" } else { "" });
|
|
match &item.descriptor {
|
|
Some(d) => println!(" {:<22} {:<28}{flags} {}", item.event.id(), item.dictionary, keys(d)),
|
|
None => println!(" {:<22} {:<28}{flags}", item.event.id(), item.dictionary),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("== {path}: {e}");
|
|
failed = true;
|
|
}
|
|
}
|
|
}
|
|
if failed {
|
|
std::process::exit(1);
|
|
}
|
|
}
|