psd 0.3.5

A Rust API for parsing and working with PSD files.
Documentation
use psd::{Psd, PsdGroup};

const GREEN_PIXEL: [u8; 4] = [0, 255, 0, 255];

/// cargo test --test layer_and_mask_information_section layer_and_mask_information_section -- --exact
#[test]
fn layer_and_mask_information_section() {
    let psd = include_bytes!("./fixtures/green-1x1.psd");

    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 1);

    let layer = psd.layer_by_name("First Layer").unwrap();

    assert_eq!(&layer.rgba()[..], &GREEN_PIXEL);
}

/// cargo test --test layer_and_mask_information_section layer_with_cyrillic_name -- --exact
#[test]
fn layer_with_cyrillic_name() {
    let psd = include_bytes!("fixtures/green-cyrillic-layer-name-1x1.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 1);
    psd.layer_by_name("привет").unwrap();
}

/// cargo test --test layer_and_mask_information_section layer_with_chinese_name -- --exact
#[test]
fn layer_with_chinese_name() {
    let psd = include_bytes!("fixtures/green-chinese-layer-name-1x1.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 1);
    psd.layer_by_name("圆角矩形").unwrap();
}

/// cargo test --test layer_and_mask_information_section layer_unicode_string -- --exact
#[test]
fn layer_unicode_string() {
    let psd = include_bytes!("fixtures/luni.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    let mut layer_names: Vec<&str> = psd.layers().iter().map(|l| l.name()).collect();
    layer_names.sort();
    assert_eq!(&layer_names[..], &["1\u{0}", "2 のコピー\u{0}", "3"]);
}

/// cargo test --test layer_and_mask_information_section layer_with_clipping -- --exact
#[test]
fn layer_with_clipping() {
    let psd = include_bytes!("fixtures/green-clipping-10x10.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 3);
    assert_eq!(
        psd.layer_by_name("Clipping base")
            .unwrap()
            .is_clipping_mask(),
        true
    );
    assert_eq!(
        psd.layer_by_name("First clipped layer")
            .unwrap()
            .is_clipping_mask(),
        false
    );
}

const TOP_LEVEL_ID: u32 = 1;

/// cargo test --test layer_and_mask_information_section one_group_one_layer_inside -- --exact
#[test]
fn one_group_one_layer_inside() {
    let psd = include_bytes!("fixtures/groups/green-1x1-one-group-one-layer-inside.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 1);
    assert_eq!(psd.groups().len(), 1);

    // Check group
    let group = group_by_name(&psd, "group");
    assert_eq!(group.id(), TOP_LEVEL_ID);

    let layer_parent_id = psd.layers().get(0).unwrap().parent_id().unwrap();

    assert_eq!(layer_parent_id, group.id());
}

/// cargo test --test layer_and_mask_information_section one_group_one_layer_inside_one_outside -- --exact
#[test]
fn one_group_one_layer_inside_one_outside() {
    let psd =
        include_bytes!("fixtures/groups/green-1x1-one-group-one-layer-inside-one-outside.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    // 1 layer outside + 1 layer inside
    assert_eq!(psd.layers().len(), 2);
    assert_eq!(psd.groups().len(), 1);

    // Check layer outside group
    let layer = psd.layer_by_name("Second Layer").unwrap();
    assert!(layer.parent_id().is_none());

    // Check group
    let group = group_by_name(&psd, "group");
    assert_eq!(group.id(), TOP_LEVEL_ID);

    // Check layer inside group
    let layer = psd.layer_by_name("First Layer").unwrap();
    assert_eq!(layer.parent_id().unwrap(), group.id());
}

/// cargo test --test layer_and_mask_information_section two_groups_two_layers_inside -- --exact
#[test]
fn two_groups_two_layers_inside() {
    let psd = include_bytes!("fixtures/groups/green-1x1-two-groups-two-layers-inside.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    // 2 group layer
    assert_eq!(psd.groups().len(), 2);

    // Check first group
    let group = group_by_name(&psd, "group");
    assert_eq!(group.id(), TOP_LEVEL_ID);

    // Check layer inside group
    let layer = psd.layer_by_name("First Layer").unwrap();
    assert_eq!(layer.parent_id().unwrap(), group.id());

    // Check second group
    let group = group_by_name(&psd, "group2");
    assert_eq!(group.id(), TOP_LEVEL_ID + 1);
}

///
/// group structure
/// +---------------+----------+---------+
/// | name          | group_id | parent  |
/// +---------------+----------+---------+
/// | group inside  | 2        | Some(1) | refers to 'group outside'
/// | group outside | 1        | None    |
/// +------------------------------------+
///
/// layer structure
/// +-------------+-----+---------+
/// | name        | idx | parent  |
/// +-------------+-----+---------+
/// | First Layer | 0   | Some(1) |  refers to 'group inside'
/// +-------------+-----+---------+
#[test]
fn one_group_inside_another() {
    let psd = include_bytes!("fixtures/groups/green-1x1-one-group-inside-another.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 1);
    // parent group + children group
    assert_eq!(psd.groups().len(), 2);

    // Check group
    let group = group_by_name(&psd, "group outside");
    assert_eq!(group.id(), TOP_LEVEL_ID);

    // Check subgroup
    let children_group = group_by_name(&psd, "group inside");
    assert_eq!(children_group.parent_id().unwrap(), group.id());

    let layer = psd.layer_by_name("First Layer").unwrap();
    assert_eq!(children_group.id(), layer.parent_id().unwrap());
}

///
/// PSD file structure
/// group: outside group, parent: `None`
/// 	group: first group inside, parent: `outside group`
/// 		layer: First Layer, parent: `first group inside`
///
/// 	group: second group inside, parent: `outside group`
/// 		group: sub sub group, parent: `second group inside`
/// 			layer: Second Layer, parent: `sub sub group`
///
/// 		layer: Third Layer, parent: `second group inside`
///
/// 	group: third group inside, parent: `outside group`
///
/// 	layer: Fourth Layer, parent: `outside group`
/// layer: Firth Layer, parent: `None`
///
/// group: outside group 2, parent: `None`
/// 	layer: Sixth Layer, parent: `outside group 2`
///
#[test]
fn one_group_with_two_subgroups() {
    let psd = include_bytes!("fixtures/groups/green-1x1-one-group-with-two-subgroups.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(6, psd.layers().len());
    assert_eq!(6, psd.groups().len());

    // Check first top-level group
    let outside_group = group_by_name(&psd, "outside group");
    assert_eq!(outside_group.id(), 1);

    // Check first subgroup
    let children_group = group_by_name(&psd, "first group inside");
    assert_eq!(children_group.parent_id().unwrap(), outside_group.id());

    let layer = psd.layer_by_name("First Layer").unwrap();
    assert_eq!(children_group.id(), layer.parent_id().unwrap());

    // Check second subgroup
    let children_group = group_by_name(&psd, "second group inside");
    assert_eq!(children_group.parent_id().unwrap(), outside_group.id());

    // Check `sub sub group`
    let sub_sub_group = group_by_name(&psd, "sub sub group");
    assert_eq!(sub_sub_group.parent_id().unwrap(), children_group.id());

    let layer = psd.layer_by_name("Second Layer").unwrap();
    assert_eq!(sub_sub_group.id(), layer.parent_id().unwrap());

    let layer = psd.layer_by_name("Third Layer").unwrap();
    assert_eq!(children_group.id(), layer.parent_id().unwrap());

    // Check third subgroup
    let children_group = group_by_name(&psd, "third group inside");
    assert_eq!(children_group.parent_id().unwrap(), outside_group.id());

    let layer = psd.layer_by_name("Fourth Layer").unwrap();
    assert_eq!(outside_group.id(), layer.parent_id().unwrap());

    // Check top-level Firth Group
    let layer = psd.layer_by_name("Firth Layer").unwrap();
    assert_eq!(layer.parent_id(), None);

    // Check second top-level group
    let outside_group = group_by_name(&psd, "outside group 2");
    assert_eq!(outside_group.id(), 6);

    let layer = psd.layer_by_name("Sixth Layer").unwrap();
    assert_eq!(layer.parent_id().unwrap(), outside_group.id());
}

fn group_by_name<'a>(psd: &'a Psd, name: &str) -> &'a PsdGroup {
    psd.groups()
        .iter()
        .find(|group| group.1.name() == name)
        .unwrap()
        .1
}

/// cargo test --test layer_and_mask_information_section layer_larger_than_psd -- --exact
#[test]
fn layer_larger_than_psd() {
    let psd = include_bytes!("./fixtures/layer-larger.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    let larger_layer = psd.layer_by_name("larger").unwrap();
    larger_layer.rgba();
}

/// cargo test --test layer_and_mask_information_section out_of_bounds_layer -- --exact
#[test]
fn out_of_bounds_layer() {
    let psd = include_bytes!("./fixtures/out-of-bounds-layer.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    let larger_layer = psd.layer_by_name("out-of-bounds").unwrap();
    larger_layer.rgba();
}