///|
pub fn fixture_aseprite_json() -> String {
"{" +
"\"frames\":[" +
"{" +
"\"filename\":\"hero_idle_0\"," +
"\"frame\":{\"x\":0,\"y\":0,\"w\":16,\"h\":16}," +
"\"rotated\":false,\"trimmed\":false," +
"\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":16,\"h\":16}," +
"\"sourceSize\":{\"w\":16,\"h\":16}," +
"\"duration\":120" +
"}," +
"{" +
"\"filename\":\"hero_idle_1\"," +
"\"frame\":{\"x\":16,\"y\":0,\"w\":16,\"h\":16}," +
"\"rotated\":false,\"trimmed\":false," +
"\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":16,\"h\":16}," +
"\"sourceSize\":{\"w\":16,\"h\":16}," +
"\"duration\":120" +
"}," +
"{" +
"\"filename\":\"hero_run_0\"," +
"\"frame\":{\"x\":0,\"y\":16,\"w\":16,\"h\":16}," +
"\"rotated\":false,\"trimmed\":true," +
"\"spriteSourceSize\":{\"x\":1,\"y\":0,\"w\":14,\"h\":16}," +
"\"sourceSize\":{\"w\":16,\"h\":16}," +
"\"duration\":80" +
"}," +
"{" +
"\"filename\":\"hero_run_1\"," +
"\"frame\":{\"x\":16,\"y\":16,\"w\":16,\"h\":16}," +
"\"rotated\":false,\"trimmed\":true," +
"\"spriteSourceSize\":{\"x\":1,\"y\":0,\"w\":14,\"h\":16}," +
"\"sourceSize\":{\"w\":16,\"h\":16}," +
"\"duration\":80" +
"}" +
"]," +
"\"meta\":{" +
"\"app\":\"aseprite\",\"version\":\"1.3-fixture\",\"image\":\"hero.png\",\"format\":\"RGBA8888\"," +
"\"size\":{\"w\":32,\"h\":32},\"scale\":\"1\"," +
"\"frameTags\":[" +
"{\"name\":\"idle\",\"from\":0,\"to\":1,\"direction\":\"forward\",\"color\":\"#7ec8e3\"}," +
"{\"name\":\"run\",\"from\":2,\"to\":3,\"direction\":\"pingpong\",\"color\":\"#f9c74f\"}" +
"]," +
"\"layers\":[" +
"{\"name\":\"body\",\"opacity\":255,\"blendMode\":\"normal\",\"visible\":true}," +
"{\"name\":\"weapon\",\"opacity\":220,\"blendMode\":\"normal\",\"visible\":true}" +
"]," +
"\"slices\":[" +
"{\"name\":\"hitbox_body\",\"color\":\"#ff0000\",\"keys\":[{\"frame\":0,\"bounds\":{\"x\":4,\"y\":2,\"w\":8,\"h\":12},\"pivot\":{\"x\":8,\"y\":14}}]}," +
"{\"name\":\"hurtbox_body\",\"color\":\"#00ff00\",\"keys\":[{\"frame\":0,\"bounds\":{\"x\":3,\"y\":1,\"w\":10,\"h\":14}}]}," +
"{\"name\":\"collision_feet\",\"color\":\"#0000ff\",\"keys\":[{\"frame\":0,\"bounds\":{\"x\":5,\"y\":13,\"w\":6,\"h\":3}}]}" +
"]" +
"}" +
"}"
}
///|
pub fn fixture_object_frames_json() -> String {
"{" +
"\"frames\":{" +
"\"coin_0\":{\"frame\":{\"x\":0,\"y\":0,\"w\":8,\"h\":8},\"rotated\":false,\"trimmed\":false,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":8,\"h\":8},\"sourceSize\":{\"w\":8,\"h\":8},\"duration\":90}," +
"\"coin_1\":{\"frame\":{\"x\":8,\"y\":0,\"w\":8,\"h\":8},\"rotated\":false,\"trimmed\":false,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":8,\"h\":8},\"sourceSize\":{\"w\":8,\"h\":8},\"duration\":90}" +
"}," +
"\"meta\":{\"image\":\"coin.png\",\"format\":\"RGBA8888\",\"size\":{\"w\":16,\"h\":8},\"scale\":\"1\",\"frameTags\":[{\"name\":\"spin\",\"from\":0,\"to\":1,\"direction\":\"forward\"}],\"slices\":[]}" +
"}"
}
///|
pub fn fixture_invalid_sprite_json() -> String {
"{" +
"\"frames\":[" +
"{\"filename\":\"bad\",\"frame\":{\"x\":0,\"y\":0,\"w\":0,\"h\":16},\"duration\":0}," +
"{\"filename\":\"bad\",\"frame\":{\"x\":40,\"y\":0,\"w\":16,\"h\":16},\"duration\":100}" +
"]," +
"\"meta\":{\"image\":\"bad.png\",\"size\":{\"w\":32,\"h\":16},\"frameTags\":[{\"name\":\"broken\",\"from\":0,\"to\":5,\"direction\":\"forward\"}],\"slices\":[{\"name\":\"hitbox\",\"keys\":[{\"frame\":99,\"bounds\":{\"x\":0,\"y\":0,\"w\":8,\"h\":8}}]}]}" +
"}"
}
///|
pub fn fixture_malformed_json() -> String {
"{\"frames\":"
}
///|
pub fn fixture_not_object_json() -> String {
"[1,2,3]"
}
///|
fn push_byte(bytes : Array[Int], value : Int) -> Unit {
bytes.push(value & 0xff)
}
///|
fn push_u16_le(bytes : Array[Int], value : Int) -> Unit {
push_byte(bytes, value)
push_byte(bytes, value >> 8)
}
///|
fn push_u32_le(bytes : Array[Int], value : Int) -> Unit {
push_byte(bytes, value)
push_byte(bytes, value >> 8)
push_byte(bytes, value >> 16)
push_byte(bytes, value >> 24)
}
///|
fn set_u32_le(bytes : Array[Int], offset : Int, value : Int) -> Unit {
bytes[offset] = value & 0xff
bytes[offset + 1] = (value >> 8) & 0xff
bytes[offset + 2] = (value >> 16) & 0xff
bytes[offset + 3] = (value >> 24) & 0xff
}
///|
fn push_ascii(bytes : Array[Int], text : String) -> Unit {
for i in 0.. Unit {
while bytes.length() < target {
bytes.push(0)
}
}
///|
fn push_ase_header(bytes : Array[Int], file_size : Int) -> Unit {
push_u32_le(bytes, file_size)
push_u16_le(bytes, 0xA5E0)
push_u16_le(bytes, 1)
push_u16_le(bytes, 32)
push_u16_le(bytes, 32)
push_u16_le(bytes, 32)
push_u32_le(bytes, 1)
push_u16_le(bytes, 100)
push_u32_le(bytes, 0)
push_u32_le(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_u16_le(bytes, 256)
push_byte(bytes, 1)
push_byte(bytes, 1)
pad_to(bytes, 128)
}
///|
fn push_layer_chunk(bytes : Array[Int], name : String) -> Unit {
let start = bytes.length()
push_u32_le(bytes, 0)
push_u16_le(bytes, 0x2004)
push_u16_le(bytes, 1)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_byte(bytes, 255)
push_byte(bytes, 0)
push_u16_le(bytes, name.length())
push_ascii(bytes, name)
set_u32_le(bytes, start, bytes.length() - start)
}
///|
fn push_cel_chunk(bytes : Array[Int]) -> Unit {
let start = bytes.length()
push_u32_le(bytes, 0)
push_u16_le(bytes, 0x2005)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_byte(bytes, 255)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
set_u32_le(bytes, start, bytes.length() - start)
}
///|
fn push_tag_chunk(bytes : Array[Int], name : String) -> Unit {
let start = bytes.length()
push_u32_le(bytes, 0)
push_u16_le(bytes, 0x2016)
push_u16_le(bytes, 1)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 0)
push_u16_le(bytes, 0)
push_u16_le(bytes, 0)
push_byte(bytes, 0)
push_byte(bytes, 255)
push_byte(bytes, 255)
push_byte(bytes, 255)
push_byte(bytes, 0)
push_u16_le(bytes, name.length())
push_ascii(bytes, name)
set_u32_le(bytes, start, bytes.length() - start)
}
///|
fn push_slice_chunk(bytes : Array[Int], name : String) -> Unit {
let start = bytes.length()
push_u32_le(bytes, 0)
push_u16_le(bytes, 0x2020)
push_u32_le(bytes, 1)
push_u32_le(bytes, 1)
push_u32_le(bytes, 0)
push_u32_le(bytes, 0)
push_u16_le(bytes, name.length())
push_ascii(bytes, name)
set_u32_le(bytes, start, bytes.length() - start)
}
///|
pub fn fixture_aseprite_binary() -> Array[Int] {
let bytes : Array[Int] = []
push_ase_header(bytes, 0)
let frame_start = bytes.length()
push_u32_le(bytes, 0)
push_u16_le(bytes, 0xF1FA)
push_u16_le(bytes, 4)
push_u16_le(bytes, 100)
push_u16_le(bytes, 0)
push_u32_le(bytes, 4)
push_layer_chunk(bytes, "Body")
push_cel_chunk(bytes)
push_tag_chunk(bytes, "idle")
push_slice_chunk(bytes, "hitbox")
let file_size = bytes.length()
let frame_size = file_size - frame_start
set_u32_le(bytes, 0, file_size)
set_u32_le(bytes, frame_start, frame_size)
bytes
}
///|
pub fn fixture_truncated_aseprite_binary() -> Array[Int] {
[0x10, 0x00, 0x00, 0x00, 0xE0, 0xA5]
}
///|
pub fn fixture_not_aseprite_binary() -> Array[Int] {
Array::makei(128, i => {
if i == 4 {
0x00
} else if i == 5 {
0x00
} else {
i & 0xff
}
})
}
///|
pub fn fixture_catalog_passed() -> Bool {
let parsed = parse_sprite_sheet_json(fixture_aseprite_json())
let object = parse_sprite_sheet_json(fixture_object_frames_json())
let invalid = parse_sprite_sheet_json(fixture_invalid_sprite_json())
let binary = parse_aseprite_binary(fixture_aseprite_binary())
parsed.ok &&
object.ok &&
invalid.ok &&
!validate_sprite_sheet(invalid.sheet).ok &&
binary.ok &&
validate_ase_file(binary.file).ok
}