Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion packages/psd/src/sections/LayerAndMaskInformation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function parseLayerAndMaskInformation(

// Skip over Global layer mask info
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115
cursor.pass(cursor.read("u32"));
skipGlobalLayerMaskInfo(cursor);

const globalAdditionalLayerInformation = readGlobalAdditionalLayerInformation(
cursor,
Expand Down Expand Up @@ -140,3 +140,22 @@ export function parseLayerAndMaskInformation(

return {layers, groups, orders, globalAdditionalLayerInformation};
}

function skipGlobalLayerMaskInfo(cursor: Cursor): void {
// Some PSD writers omit the Global Layer Mask Info block entirely and leave
// only alignment padding after LayerInfo. In those cases, treat the block as
// empty instead of forcing a 4-byte length read past the end of the section.
if (cursor.position + 4 > cursor.length) {
return;
}

const length = cursor.read("u32");
const remaining = cursor.length - cursor.position;

if (length > remaining) {
cursor.unpass(4);
return;
}

cursor.pass(length);
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export function readGlobalAdditionalLayerInformation(
fileVersionSpec: FileVersionSpec
): AdditionalLayerProperties {
const additionalLayerInfos = [];
while (cursor.position < cursor.length) {
while (cursor.position + 12 <= cursor.length) {
try {
additionalLayerInfos.push(
readAdditionalLayerInfo(cursor, fileVersionSpec, /* padding */ 4)
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @webtoon/psd
// Copyright 2021-present NAVER WEBTOON
// MIT License

import * as fs from "fs";
import * as path from "path";
import {beforeAll, describe, expect, it} from "vitest";

import type Psd from "../../src/index";
import PSD from "../../src/index";

const FIXTURE_DIR = path.join(__dirname, "fixtures");

describe("Procreate PSD without global mask info", () => {
let psd: Psd;

beforeAll(() => {
const data = fs.readFileSync(
path.resolve(FIXTURE_DIR, "procreate-missing-global-mask-info.psd")
);
psd = PSD.parse(data.buffer);
});

it("parses successfully", () => {
expect(psd.width).toBe(128);
expect(psd.height).toBe(128);
expect(psd.layers.length).toBeGreaterThan(0);
});
});
37 changes: 37 additions & 0 deletions packages/psd/tests/unit/layerAndMaskInformation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @webtoon/psd
// Copyright 2021-present NAVER WEBTOON
// MIT License

import {describe, expect, it} from "vitest";

import {PsdSpec} from "../../src/interfaces/FileVersionSpec";
import {parseLayerAndMaskInformation} from "../../src/sections/LayerAndMaskInformation";

describe("parseLayerAndMaskInformation", () => {
it("accepts PSD sections without a readable global layer mask info block", () => {
const data = new Uint8Array([
0x00,
0x00,
0x00,
0x08, // Layer and Mask Information section length
0x00,
0x00,
0x00,
0x02, // LayerInfo length
0x00,
0x00, // layer count = 0
0x00,
0x00, // alignment padding; no room for GlobalLayerMaskInfo length field
]);

const section = parseLayerAndMaskInformation(
new DataView(data.buffer),
PsdSpec
);

expect(section.layers).toStrictEqual([]);
expect(section.groups).toStrictEqual([]);
expect(section.orders).toStrictEqual([]);
expect(section.globalAdditionalLayerInformation).toStrictEqual({});
});
});