mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-16 20:26:53 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be29e6bb54 |
No files matched your search
@@ -376,6 +376,9 @@ mod tests {
|
||||
});
|
||||
d.last_applied_comp = Some(7);
|
||||
let res = write_comps_resource(&d).unwrap();
|
||||
// `list` is a length-4 string id, as Photoshop writes it, never a code.
|
||||
assert!(res.windows(8).any(|w| w == b"\0\0\0\x04list"));
|
||||
assert!(!res.windows(8).any(|w| w == b"\0\0\0\0list"));
|
||||
let cm = write_cmls(&d.layer_comps, None, d.layer(lid).unwrap(), 3).unwrap();
|
||||
// Attach as Photoshop would: shmd with cmls plus another item.
|
||||
let mut raw = vec![(*b"shmd", write_shmd(&[MetadataItem::new(*b"cust", vec![1, 2])]))];
|
||||
|
||||
@@ -817,6 +817,28 @@ pub fn uuid_from(seed: &[u8]) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn count(hay: &[u8], needle: &[u8]) -> usize {
|
||||
hay.windows(needle.len()).filter(|w| *w == needle).count()
|
||||
}
|
||||
|
||||
/// A freshly built placed layer writes `warp` (key and class) as a length-4 string, as
|
||||
/// Photoshop does; the code form is a different key to Photoshop.
|
||||
#[test]
|
||||
fn fresh_placed_warp_is_a_string_id() {
|
||||
let spec =
|
||||
PlacedSpec { idnt: "id", placed: "id", transform: Affine::IDENTITY, perspective: None, size: (10.0, 10.0), dpi: 72.0, warp: None, filter_fx: None };
|
||||
let mut warnings = Vec::new();
|
||||
let sold = sold_bytes(None, &spec, &mut warnings);
|
||||
let plld = plld_bytes(&spec, &mut warnings);
|
||||
// `SoLd`: the `warp` key and the warp descriptor's class; `PlLd`: the descriptor's class.
|
||||
for (bytes, n) in [(&sold, 2), (&plld, 1)] {
|
||||
assert_eq!(count(bytes, b"\0\0\0\x04warp"), n);
|
||||
assert_eq!(count(bytes, b"\0\0\0\0warp"), 0);
|
||||
}
|
||||
let d = sold_descriptor(&sold).unwrap();
|
||||
assert!(matches!(d.get("warp"), Some(Value::Descriptor(w)) if w.class_id == Id::Str(b"warp".to_vec())));
|
||||
}
|
||||
|
||||
fn sf(command: &str, params: J) -> SmartFilter {
|
||||
SmartFilter { command: command.into(), params, blend: BlendMode::Normal, opacity: 1.0, visible: true }
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ impl UnicodeString {
|
||||
}
|
||||
}
|
||||
|
||||
/// Four-character class and key ids Photoshop writes as length-4 strings, never as codes
|
||||
/// (`00000004 'warp'`, not `00000000 'warp'`).
|
||||
pub const STRING_IDS: [&str; 3] = ["warp", "list", "time"];
|
||||
|
||||
/// A class / key / type identifier: a length-prefixed string, where a stored
|
||||
/// length of 0 means a 4-character code follows.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -57,10 +61,15 @@ pub enum Id {
|
||||
|
||||
impl Id {
|
||||
/// Convenience constructor: 4-byte strings become [`Id::Code`], others
|
||||
/// [`Id::Str`].
|
||||
/// [`Id::Str`]. The four-character names Photoshop stores as length-4 strings
|
||||
/// ([`STRING_IDS`]: `warp`, `list`, `time`) are [`Id::Str`] too: Photoshop treats the
|
||||
/// string and the code as different keys and doesn't recognise the code form.
|
||||
pub fn new(s: &str) -> Self {
|
||||
let b = s.as_bytes();
|
||||
if b.len() == 4 { Id::Code([b[0], b[1], b[2], b[3]]) } else { Id::Str(b.to_vec()) }
|
||||
match b {
|
||||
[a, b, c, d] if !STRING_IDS.contains(&s) => Id::Code([*a, *b, *c, *d]),
|
||||
_ => Id::Str(b.to_vec()),
|
||||
}
|
||||
}
|
||||
/// The identifier's bytes.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
@@ -609,6 +618,34 @@ mod tests {
|
||||
assert_eq!(p.to_bytes(), b);
|
||||
}
|
||||
|
||||
/// `warp`, `list` and `time` are written as length-4 strings, as Photoshop writes them, both
|
||||
/// as keys and as class ids; other four-character names stay codes. Reading accepts both
|
||||
/// forms, and a code read from a file is written back as a code.
|
||||
#[test]
|
||||
fn photoshop_string_ids_are_written_as_strings() {
|
||||
let str_id = |s: &str| [&[0, 0, 0, 4][..], s.as_bytes()].concat();
|
||||
let code_id = |s: &str| [&[0, 0, 0, 0][..], s.as_bytes()].concat();
|
||||
for name in STRING_IDS {
|
||||
assert_eq!(Id::new(name), Id::Str(name.as_bytes().to_vec()));
|
||||
let d = Descriptor::new(name).with(name, Value::Integer(1));
|
||||
let b = d.to_bytes();
|
||||
// Name (empty unicode string), then the class id; then the item count and the key.
|
||||
assert_eq!(&b[4..12], &str_id(name)[..], "class {name}");
|
||||
assert_eq!(&b[16..24], &str_id(name)[..], "key {name}");
|
||||
let back = Descriptor::from_bytes(&b).unwrap();
|
||||
assert_eq!(back, d);
|
||||
assert!(back.class_id.is(name) && back.get(name).is_some());
|
||||
// A file that wrote the code form keeps it, and lookups still find the key.
|
||||
let mut coded = b.clone();
|
||||
coded.splice(16..24, code_id(name));
|
||||
let back = Descriptor::from_bytes(&coded).unwrap();
|
||||
assert_eq!(back.get(name), Some(&Value::Integer(1)));
|
||||
assert_eq!(back.to_bytes(), coded);
|
||||
}
|
||||
assert_eq!(Id::new("view"), Id::Code(*b"view"));
|
||||
assert_eq!(Id::new("Txt "), Id::Code(*b"Txt "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versioned_roundtrip() {
|
||||
let v = VersionedDescriptor::new(sample());
|
||||
|
||||
Reference in new issue
Block a user