mirror of
https://gitee.com/onvia/ccc-tnt-psd2ui
synced 2025-11-02 04:16:37 +00:00
支持 mac
This commit is contained in:
540
npm-packages/mac-v2.4.x/ag-psd/src/abr.ts
Normal file
540
npm-packages/mac-v2.4.x/ag-psd/src/abr.ts
Normal file
@@ -0,0 +1,540 @@
|
||||
import { BlnM, DescriptorUnitsValue, parseAngle, parsePercent, parseUnitsToNumber, readVersionAndDescriptor } from './descriptor';
|
||||
import { BlendMode, PatternInfo } from './psd';
|
||||
import {
|
||||
checkSignature, createReader, readBytes, readDataRLE, readInt16, readInt32, readPascalString, readPattern,
|
||||
readSignature, readUint16, readUint32, readUint8, skipBytes
|
||||
} from './psdReader';
|
||||
|
||||
export interface Abr {
|
||||
brushes: Brush[];
|
||||
samples: SampleInfo[];
|
||||
patterns: PatternInfo[];
|
||||
}
|
||||
|
||||
export interface SampleInfo {
|
||||
id: string;
|
||||
bounds: { x: number; y: number; w: number; h: number; };
|
||||
alpha: Uint8Array;
|
||||
}
|
||||
|
||||
export interface BrushDynamics {
|
||||
control: 'off' | 'fade' | 'pen pressure' | 'pen tilt' | 'stylus wheel' | 'initial direction' | 'direction' | 'initial rotation' | 'rotation';
|
||||
steps: number; // for fade
|
||||
jitter: number;
|
||||
minimum: number;
|
||||
}
|
||||
|
||||
const dynamicsControl = ['off', 'fade', 'pen pressure', 'pen tilt', 'stylus wheel', 'initial direction', 'direction', 'initial rotation', 'rotation'];
|
||||
|
||||
export interface BrushShape {
|
||||
name?: string;
|
||||
size: number;
|
||||
angle: number;
|
||||
roundness: number;
|
||||
hardness?: number;
|
||||
spacingOn: boolean;
|
||||
spacing: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
sampledData?: string;
|
||||
}
|
||||
|
||||
export interface Brush {
|
||||
name: string;
|
||||
shape: BrushShape;
|
||||
shapeDynamics?: {
|
||||
sizeDynamics: BrushDynamics;
|
||||
minimumDiameter: number;
|
||||
tiltScale: number;
|
||||
angleDynamics: BrushDynamics; // jitter 0-1 -> 0-360 deg ?
|
||||
roundnessDynamics: BrushDynamics;
|
||||
minimumRoundness: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
brushProjection: boolean;
|
||||
};
|
||||
scatter?: {
|
||||
bothAxes: boolean;
|
||||
scatterDynamics: BrushDynamics;
|
||||
countDynamics: BrushDynamics;
|
||||
count: number;
|
||||
};
|
||||
texture?: {
|
||||
id: string;
|
||||
name: string;
|
||||
invert: boolean;
|
||||
scale: number;
|
||||
brightness: number;
|
||||
contrast: number;
|
||||
blendMode: BlendMode;
|
||||
depth: number;
|
||||
depthMinimum: number;
|
||||
depthDynamics: BrushDynamics;
|
||||
};
|
||||
dualBrush?: {
|
||||
flip: boolean;
|
||||
shape: BrushShape;
|
||||
blendMode: BlendMode;
|
||||
useScatter: boolean;
|
||||
spacing: number;
|
||||
count: number;
|
||||
bothAxes: boolean;
|
||||
countDynamics: BrushDynamics;
|
||||
scatterDynamics: BrushDynamics;
|
||||
};
|
||||
colorDynamics?: {
|
||||
foregroundBackground: BrushDynamics;
|
||||
hue: number;
|
||||
saturation: number;
|
||||
brightness: number;
|
||||
purity: number;
|
||||
perTip: boolean;
|
||||
};
|
||||
transfer?: {
|
||||
flowDynamics: BrushDynamics;
|
||||
opacityDynamics: BrushDynamics;
|
||||
wetnessDynamics: BrushDynamics;
|
||||
mixDynamics: BrushDynamics;
|
||||
};
|
||||
brushPose?: {
|
||||
overrideAngle: boolean;
|
||||
overrideTiltX: boolean;
|
||||
overrideTiltY: boolean;
|
||||
overridePressure: boolean;
|
||||
pressure: number;
|
||||
tiltX: number;
|
||||
tiltY: number;
|
||||
angle: number;
|
||||
};
|
||||
noise: boolean;
|
||||
wetEdges: boolean;
|
||||
// TODO: build-up
|
||||
// TODO: smoothing
|
||||
protectTexture?: boolean;
|
||||
spacing: number;
|
||||
brushGroup?: undefined; // ?
|
||||
interpretation?: boolean; // ?
|
||||
useBrushSize: boolean; // ?
|
||||
toolOptions?: {
|
||||
brushPreset: boolean;
|
||||
flow: number; // 0-100
|
||||
smooth: number; // ?
|
||||
mode: BlendMode;
|
||||
opacity: number; // 0-100
|
||||
smoothing: boolean;
|
||||
smoothingValue: number;
|
||||
smoothingRadiusMode: boolean;
|
||||
smoothingCatchup: boolean;
|
||||
smoothingCatchupAtEnd: boolean;
|
||||
smoothingZoomCompensation: boolean;
|
||||
pressureSmoothing: boolean;
|
||||
usePressureOverridesSize: boolean;
|
||||
usePressureOverridesOpacity: boolean;
|
||||
useLegacy: boolean;
|
||||
flowDynamics?: BrushDynamics;
|
||||
opacityDynamics?: BrushDynamics;
|
||||
sizeDynamics?: BrushDynamics;
|
||||
};
|
||||
}
|
||||
|
||||
// internal
|
||||
|
||||
interface PhryDescriptor {
|
||||
hierarchy: ({} | {
|
||||
'Nm ': string;
|
||||
zuid: string;
|
||||
})[];
|
||||
}
|
||||
|
||||
interface DynamicsDescriptor {
|
||||
bVTy: number;
|
||||
fStp: number;
|
||||
jitter: DescriptorUnitsValue;
|
||||
'Mnm ': DescriptorUnitsValue;
|
||||
}
|
||||
|
||||
interface BrushShapeDescriptor {
|
||||
Dmtr: DescriptorUnitsValue;
|
||||
Angl: DescriptorUnitsValue;
|
||||
Rndn: DescriptorUnitsValue;
|
||||
'Nm '?: string;
|
||||
Spcn: DescriptorUnitsValue;
|
||||
Intr: boolean;
|
||||
Hrdn?: DescriptorUnitsValue;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
sampledData?: string;
|
||||
}
|
||||
|
||||
interface DescDescriptor {
|
||||
Brsh: {
|
||||
'Nm ': string;
|
||||
Brsh: BrushShapeDescriptor;
|
||||
useTipDynamics: boolean;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
brushProjection: boolean;
|
||||
minimumDiameter: DescriptorUnitsValue;
|
||||
minimumRoundness: DescriptorUnitsValue;
|
||||
tiltScale: DescriptorUnitsValue;
|
||||
szVr: DynamicsDescriptor;
|
||||
angleDynamics: DynamicsDescriptor;
|
||||
roundnessDynamics: DynamicsDescriptor;
|
||||
useScatter: boolean;
|
||||
Spcn: DescriptorUnitsValue;
|
||||
'Cnt ': number;
|
||||
bothAxes: boolean;
|
||||
countDynamics: DynamicsDescriptor;
|
||||
scatterDynamics: DynamicsDescriptor;
|
||||
dualBrush: { useDualBrush: false; } | {
|
||||
useDualBrush: true;
|
||||
Flip: boolean;
|
||||
Brsh: BrushShapeDescriptor;
|
||||
BlnM: string;
|
||||
useScatter: boolean;
|
||||
Spcn: DescriptorUnitsValue;
|
||||
'Cnt ': number;
|
||||
bothAxes: boolean;
|
||||
countDynamics: DynamicsDescriptor;
|
||||
scatterDynamics: DynamicsDescriptor;
|
||||
};
|
||||
brushGroup: { useBrushGroup: false; };
|
||||
useTexture: boolean;
|
||||
TxtC: boolean;
|
||||
interpretation: boolean;
|
||||
textureBlendMode: string;
|
||||
textureDepth: DescriptorUnitsValue;
|
||||
minimumDepth: DescriptorUnitsValue;
|
||||
textureDepthDynamics: DynamicsDescriptor;
|
||||
Txtr?: {
|
||||
'Nm ': string;
|
||||
Idnt: string;
|
||||
};
|
||||
textureScale: DescriptorUnitsValue;
|
||||
InvT: boolean;
|
||||
protectTexture: boolean;
|
||||
textureBrightness: number;
|
||||
textureContrast: number;
|
||||
usePaintDynamics: boolean;
|
||||
prVr?: DynamicsDescriptor;
|
||||
opVr?: DynamicsDescriptor;
|
||||
wtVr?: DynamicsDescriptor;
|
||||
mxVr?: DynamicsDescriptor;
|
||||
useColorDynamics: boolean;
|
||||
clVr?: DynamicsDescriptor;
|
||||
'H '?: DescriptorUnitsValue;
|
||||
Strt?: DescriptorUnitsValue;
|
||||
Brgh?: DescriptorUnitsValue;
|
||||
purity?: DescriptorUnitsValue;
|
||||
colorDynamicsPerTip?: true;
|
||||
Wtdg: boolean;
|
||||
Nose: boolean;
|
||||
'Rpt ': boolean;
|
||||
useBrushSize: boolean;
|
||||
useBrushPose: boolean;
|
||||
overridePoseAngle?: boolean;
|
||||
overridePoseTiltX?: boolean;
|
||||
overridePoseTiltY?: boolean;
|
||||
overridePosePressure?: boolean;
|
||||
brushPosePressure?: DescriptorUnitsValue;
|
||||
brushPoseTiltX?: number;
|
||||
brushPoseTiltY?: number;
|
||||
brushPoseAngle?: number;
|
||||
toolOptions?: {
|
||||
brushPreset: boolean;
|
||||
flow?: number;
|
||||
Smoo?: number;
|
||||
'Md ': string;
|
||||
Opct?: number;
|
||||
smoothing?: boolean;
|
||||
smoothingValue?: number;
|
||||
smoothingRadiusMode?: boolean;
|
||||
smoothingCatchup?: boolean;
|
||||
smoothingCatchupAtEnd?: boolean;
|
||||
smoothingZoomCompensation?: boolean;
|
||||
pressureSmoothing?: boolean;
|
||||
usePressureOverridesSize?: boolean;
|
||||
usePressureOverridesOpacity?: boolean;
|
||||
useLegacy: boolean;
|
||||
'Prs '?: number; // TODO: ???
|
||||
MgcE?: boolean; // TODO: ???
|
||||
ErsB?: number; // TODO: ???
|
||||
prVr?: DynamicsDescriptor;
|
||||
opVr?: DynamicsDescriptor;
|
||||
szVr?: DynamicsDescriptor;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
function parseDynamics(desc: DynamicsDescriptor): BrushDynamics {
|
||||
return {
|
||||
control: dynamicsControl[desc.bVTy] as any,
|
||||
steps: desc.fStp,
|
||||
jitter: parsePercent(desc.jitter),
|
||||
minimum: parsePercent(desc['Mnm ']),
|
||||
};
|
||||
}
|
||||
|
||||
function parseBrushShape(desc: BrushShapeDescriptor): BrushShape {
|
||||
const shape: BrushShape = {
|
||||
size: parseUnitsToNumber(desc.Dmtr, 'Pixels'),
|
||||
angle: parseAngle(desc.Angl),
|
||||
roundness: parsePercent(desc.Rndn),
|
||||
spacingOn: desc.Intr,
|
||||
spacing: parsePercent(desc.Spcn),
|
||||
flipX: desc.flipX,
|
||||
flipY: desc.flipY,
|
||||
};
|
||||
|
||||
if (desc['Nm ']) shape.name = desc['Nm '];
|
||||
if (desc.Hrdn) shape.hardness = parsePercent(desc.Hrdn);
|
||||
if (desc.sampledData) shape.sampledData = desc.sampledData;
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
export function readAbr(buffer: ArrayBufferView, options: { logMissingFeatures?: boolean; } = {}): Abr {
|
||||
const reader = createReader(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
const version = readInt16(reader);
|
||||
const samples: SampleInfo[] = [];
|
||||
const brushes: Brush[] = [];
|
||||
const patterns: PatternInfo[] = [];
|
||||
|
||||
if (version === 1 || version === 2) {
|
||||
throw new Error(`Unsupported ABR version (${version})`); // TODO: ...
|
||||
} else if (version === 6 || version === 7 || version === 9 || version === 10) {
|
||||
const minorVersion = readInt16(reader);
|
||||
if (minorVersion !== 1 && minorVersion !== 2) throw new Error('Unsupported ABR minor version');
|
||||
|
||||
while (reader.offset < reader.view.byteLength) {
|
||||
checkSignature(reader, '8BIM');
|
||||
const type = readSignature(reader) as 'samp' | 'desc' | 'patt' | 'phry';
|
||||
let size = readUint32(reader);
|
||||
const end = reader.offset + size;
|
||||
|
||||
switch (type) {
|
||||
case 'samp': {
|
||||
while (reader.offset < end) {
|
||||
let brushLength = readUint32(reader);
|
||||
while (brushLength & 0b11) brushLength++; // pad to 4 byte alignment
|
||||
const brushEnd = reader.offset + brushLength;
|
||||
|
||||
const id = readPascalString(reader, 1);
|
||||
|
||||
// v1 - Skip the Int16 bounds rectangle and the unknown Int16.
|
||||
// v2 - Skip the unknown bytes.
|
||||
skipBytes(reader, minorVersion === 1 ? 10 : 264);
|
||||
|
||||
const y = readInt32(reader);
|
||||
const x = readInt32(reader);
|
||||
const h = readInt32(reader) - y;
|
||||
const w = readInt32(reader) - x;
|
||||
if (w <= 0 || h <= 0) throw new Error('Invalid bounds');
|
||||
|
||||
const depth = readInt16(reader);
|
||||
const compression = readUint8(reader); // 0 - raw, 1 - RLE
|
||||
const alpha = new Uint8Array(w * h);
|
||||
|
||||
if (depth === 8) {
|
||||
if (compression === 0) {
|
||||
alpha.set(readBytes(reader, alpha.byteLength));
|
||||
} else if (compression === 1) {
|
||||
readDataRLE(reader, { width: w, height: h, data: alpha }, w, h, 1, [0], false);
|
||||
} else {
|
||||
throw new Error('Invalid compression');
|
||||
}
|
||||
} else if (depth === 16) {
|
||||
if (compression === 0) {
|
||||
for (let i = 0; i < alpha.byteLength; i++) {
|
||||
alpha[i] = readUint16(reader) >> 8; // convert to 8bit values
|
||||
}
|
||||
} else if (compression === 1) {
|
||||
throw new Error('not implemented (16bit RLE)'); // TODO: ...
|
||||
} else {
|
||||
throw new Error('Invalid compression');
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid depth');
|
||||
}
|
||||
|
||||
samples.push({ id, bounds: { x, y, w, h }, alpha });
|
||||
reader.offset = brushEnd;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'desc': {
|
||||
const desc: DescDescriptor = readVersionAndDescriptor(reader);
|
||||
// console.log(require('util').inspect(desc, false, 99, true));
|
||||
|
||||
for (const brush of desc.Brsh) {
|
||||
const b: Brush = {
|
||||
name: brush['Nm '],
|
||||
shape: parseBrushShape(brush.Brsh),
|
||||
spacing: parsePercent(brush.Spcn),
|
||||
// TODO: brushGroup ???
|
||||
wetEdges: brush.Wtdg,
|
||||
noise: brush.Nose,
|
||||
// TODO: TxtC ??? smoothing / build-up ?
|
||||
// TODO: 'Rpt ' ???
|
||||
useBrushSize: brush.useBrushSize, // ???
|
||||
};
|
||||
|
||||
if (brush.interpretation != null) b.interpretation = brush.interpretation;
|
||||
if (brush.protectTexture != null) b.protectTexture = brush.protectTexture;
|
||||
|
||||
if (brush.useTipDynamics) {
|
||||
b.shapeDynamics = {
|
||||
tiltScale: parsePercent(brush.tiltScale),
|
||||
sizeDynamics: parseDynamics(brush.szVr),
|
||||
angleDynamics: parseDynamics(brush.angleDynamics),
|
||||
roundnessDynamics: parseDynamics(brush.roundnessDynamics),
|
||||
flipX: brush.flipX,
|
||||
flipY: brush.flipY,
|
||||
brushProjection: brush.brushProjection,
|
||||
minimumDiameter: parsePercent(brush.minimumDiameter),
|
||||
minimumRoundness: parsePercent(brush.minimumRoundness),
|
||||
};
|
||||
}
|
||||
|
||||
if (brush.useScatter) {
|
||||
b.scatter = {
|
||||
count: brush['Cnt '],
|
||||
bothAxes: brush.bothAxes,
|
||||
countDynamics: parseDynamics(brush.countDynamics),
|
||||
scatterDynamics: parseDynamics(brush.scatterDynamics),
|
||||
};
|
||||
}
|
||||
|
||||
if (brush.useTexture && brush.Txtr) {
|
||||
b.texture = {
|
||||
id: brush.Txtr.Idnt,
|
||||
name: brush.Txtr['Nm '],
|
||||
blendMode: BlnM.decode(brush.textureBlendMode),
|
||||
depth: parsePercent(brush.textureDepth),
|
||||
depthMinimum: parsePercent(brush.minimumDepth),
|
||||
depthDynamics: parseDynamics(brush.textureDepthDynamics),
|
||||
scale: parsePercent(brush.textureScale),
|
||||
invert: brush.InvT,
|
||||
brightness: brush.textureBrightness,
|
||||
contrast: brush.textureContrast,
|
||||
};
|
||||
}
|
||||
|
||||
const db = brush.dualBrush;
|
||||
if (db && db.useDualBrush) {
|
||||
b.dualBrush = {
|
||||
flip: db.Flip,
|
||||
shape: parseBrushShape(db.Brsh),
|
||||
blendMode: BlnM.decode(db.BlnM),
|
||||
useScatter: db.useScatter,
|
||||
spacing: parsePercent(db.Spcn),
|
||||
count: db['Cnt '],
|
||||
bothAxes: db.bothAxes,
|
||||
countDynamics: parseDynamics(db.countDynamics),
|
||||
scatterDynamics: parseDynamics(db.scatterDynamics),
|
||||
};
|
||||
}
|
||||
|
||||
if (brush.useColorDynamics) {
|
||||
b.colorDynamics = {
|
||||
foregroundBackground: parseDynamics(brush.clVr!),
|
||||
hue: parsePercent(brush['H ']!),
|
||||
saturation: parsePercent(brush.Strt!),
|
||||
brightness: parsePercent(brush.Brgh!),
|
||||
purity: parsePercent(brush.purity!),
|
||||
perTip: brush.colorDynamicsPerTip!,
|
||||
};
|
||||
}
|
||||
|
||||
if (brush.usePaintDynamics) {
|
||||
b.transfer = {
|
||||
flowDynamics: parseDynamics(brush.prVr!),
|
||||
opacityDynamics: parseDynamics(brush.opVr!),
|
||||
wetnessDynamics: parseDynamics(brush.wtVr!),
|
||||
mixDynamics: parseDynamics(brush.mxVr!),
|
||||
};
|
||||
}
|
||||
|
||||
if (brush.useBrushPose) {
|
||||
b.brushPose = {
|
||||
overrideAngle: brush.overridePoseAngle!,
|
||||
overrideTiltX: brush.overridePoseTiltX!,
|
||||
overrideTiltY: brush.overridePoseTiltY!,
|
||||
overridePressure: brush.overridePosePressure!,
|
||||
pressure: parsePercent(brush.brushPosePressure!),
|
||||
tiltX: brush.brushPoseTiltX!,
|
||||
tiltY: brush.brushPoseTiltY!,
|
||||
angle: brush.brushPoseAngle!,
|
||||
};
|
||||
}
|
||||
|
||||
const to = brush.toolOptions;
|
||||
if (to) {
|
||||
b.toolOptions = {
|
||||
brushPreset: to.brushPreset,
|
||||
flow: to.flow ?? 100,
|
||||
smooth: to.Smoo ?? 0,
|
||||
mode: BlnM.decode(to['Md '] || 'BlnM.Nrml'), // sometimes mode is missing
|
||||
opacity: to.Opct ?? 100,
|
||||
smoothing: !!to.smoothing,
|
||||
smoothingValue: to.smoothingValue || 0,
|
||||
smoothingRadiusMode: !!to.smoothingRadiusMode,
|
||||
smoothingCatchup: !!to.smoothingCatchup,
|
||||
smoothingCatchupAtEnd: !!to.smoothingCatchupAtEnd,
|
||||
smoothingZoomCompensation: !!to.smoothingZoomCompensation,
|
||||
pressureSmoothing: !!to.pressureSmoothing,
|
||||
usePressureOverridesSize: !!to.usePressureOverridesSize,
|
||||
usePressureOverridesOpacity: !!to.usePressureOverridesOpacity,
|
||||
useLegacy: !!to.useLegacy,
|
||||
};
|
||||
|
||||
if (to.prVr) {
|
||||
b.toolOptions.flowDynamics = parseDynamics(to.prVr);
|
||||
}
|
||||
|
||||
if (to.opVr) {
|
||||
b.toolOptions.opacityDynamics = parseDynamics(to.opVr);
|
||||
}
|
||||
|
||||
if (to.szVr) {
|
||||
b.toolOptions.sizeDynamics = parseDynamics(to.szVr);
|
||||
}
|
||||
}
|
||||
|
||||
brushes.push(b);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'patt': {
|
||||
if (reader.offset < end) { // TODO: check multiple patterns
|
||||
patterns.push(readPattern(reader));
|
||||
reader.offset = end;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'phry': {
|
||||
// TODO: what is this ?
|
||||
const desc: PhryDescriptor = readVersionAndDescriptor(reader);
|
||||
if (options.logMissingFeatures) {
|
||||
if (desc.hierarchy?.length) {
|
||||
console.log('unhandled phry section', desc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid brush type: ${type}`);
|
||||
}
|
||||
|
||||
// align to 4 bytes
|
||||
while (size % 4) {
|
||||
reader.offset++;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unsupported ABR version (${version})`);
|
||||
}
|
||||
|
||||
return { samples, patterns, brushes };
|
||||
}
|
||||
3019
npm-packages/mac-v2.4.x/ag-psd/src/additionalInfo.ts
Normal file
3019
npm-packages/mac-v2.4.x/ag-psd/src/additionalInfo.ts
Normal file
File diff suppressed because it is too large
Load Diff
44
npm-packages/mac-v2.4.x/ag-psd/src/csh.ts
Normal file
44
npm-packages/mac-v2.4.x/ag-psd/src/csh.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { readVectorMask } from './additionalInfo';
|
||||
import { LayerVectorMask } from './psd';
|
||||
import { readUint32, checkSignature, createReader, readPascalString, readUnicodeString } from './psdReader';
|
||||
|
||||
export interface Csh {
|
||||
shapes: (LayerVectorMask & {
|
||||
name: string;
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
})[];
|
||||
}
|
||||
|
||||
export function readCsh(buffer: ArrayBufferView): Csh {
|
||||
const reader = createReader(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
const csh: Csh = { shapes: [] };
|
||||
|
||||
checkSignature(reader, 'cush');
|
||||
if (readUint32(reader) !== 2) throw new Error('Invalid version');
|
||||
const count = readUint32(reader);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const name = readUnicodeString(reader);
|
||||
while (reader.offset % 4) reader.offset++; // pad to 4byte bounds
|
||||
if (readUint32(reader) !== 1) throw new Error('Invalid shape version');
|
||||
const size = readUint32(reader);
|
||||
const end = reader.offset + size;
|
||||
const id = readPascalString(reader, 1);
|
||||
// this might not be correct ???
|
||||
const y1 = readUint32(reader);
|
||||
const x1 = readUint32(reader);
|
||||
const y2 = readUint32(reader);
|
||||
const x2 = readUint32(reader);
|
||||
const width = x2 - x1;
|
||||
const height = y2 - y1;
|
||||
const mask: LayerVectorMask = { paths: [] };
|
||||
readVectorMask(reader, mask, width, height, end - reader.offset);
|
||||
csh.shapes.push({ name, id, width, height, ...mask });
|
||||
|
||||
reader.offset = end;
|
||||
}
|
||||
|
||||
return csh;
|
||||
}
|
||||
1843
npm-packages/mac-v2.4.x/ag-psd/src/descriptor.ts
Normal file
1843
npm-packages/mac-v2.4.x/ag-psd/src/descriptor.ts
Normal file
File diff suppressed because it is too large
Load Diff
305
npm-packages/mac-v2.4.x/ag-psd/src/effectsHelpers.ts
Normal file
305
npm-packages/mac-v2.4.x/ag-psd/src/effectsHelpers.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import { LayerEffectsInfo, BevelStyle, LayerEffectShadow } from './psd';
|
||||
import { toBlendMode, fromBlendMode } from './helpers';
|
||||
import {
|
||||
PsdReader, checkSignature, readSignature, skipBytes, readUint16, readUint8,
|
||||
readUint32, readFixedPoint32, readColor
|
||||
} from './psdReader';
|
||||
import {
|
||||
PsdWriter, writeSignature, writeUint16, writeZeros, writeFixedPoint32,
|
||||
writeUint8, writeUint32, writeColor
|
||||
} from './psdWriter';
|
||||
|
||||
const bevelStyles: BevelStyle[] = [
|
||||
undefined as any, 'outer bevel', 'inner bevel', 'emboss', 'pillow emboss', 'stroke emboss'
|
||||
];
|
||||
|
||||
function readBlendMode(reader: PsdReader) {
|
||||
checkSignature(reader, '8BIM');
|
||||
return toBlendMode[readSignature(reader)] || 'normal';
|
||||
}
|
||||
|
||||
function writeBlendMode(writer: PsdWriter, mode: string | undefined) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, fromBlendMode[mode!] || 'norm');
|
||||
}
|
||||
|
||||
function readFixedPoint8(reader: PsdReader) {
|
||||
return readUint8(reader) / 0xff;
|
||||
}
|
||||
|
||||
function writeFixedPoint8(writer: PsdWriter, value: number) {
|
||||
writeUint8(writer, Math.round(value * 0xff) | 0);
|
||||
}
|
||||
|
||||
export function readEffects(reader: PsdReader) {
|
||||
const version = readUint16(reader);
|
||||
if (version !== 0) throw new Error(`Invalid effects layer version: ${version}`);
|
||||
|
||||
const effectsCount = readUint16(reader);
|
||||
const effects: LayerEffectsInfo = <any>{};
|
||||
|
||||
for (let i = 0; i < effectsCount; i++) {
|
||||
checkSignature(reader, '8BIM');
|
||||
const type = readSignature(reader);
|
||||
|
||||
switch (type) {
|
||||
case 'cmnS': { // common state (see See Effects layer, common state info)
|
||||
const size = readUint32(reader);
|
||||
const version = readUint32(reader);
|
||||
const visible = !!readUint8(reader);
|
||||
skipBytes(reader, 2);
|
||||
|
||||
if (size !== 7 || version !== 0 || !visible) throw new Error(`Invalid effects common state`);
|
||||
break;
|
||||
}
|
||||
case 'dsdw': // drop shadow (see See Effects layer, drop shadow and inner shadow info)
|
||||
case 'isdw': { // inner shadow (see See Effects layer, drop shadow and inner shadow info)
|
||||
const blockSize = readUint32(reader);
|
||||
const version = readUint32(reader);
|
||||
|
||||
if (blockSize !== 41 && blockSize !== 51) throw new Error(`Invalid shadow size: ${blockSize}`);
|
||||
if (version !== 0 && version !== 2) throw new Error(`Invalid shadow version: ${version}`);
|
||||
|
||||
const size = readFixedPoint32(reader);
|
||||
readFixedPoint32(reader); // intensity
|
||||
const angle = readFixedPoint32(reader);
|
||||
const distance = readFixedPoint32(reader);
|
||||
const color = readColor(reader);
|
||||
const blendMode = readBlendMode(reader);
|
||||
const enabled = !!readUint8(reader);
|
||||
const useGlobalLight = !!readUint8(reader);
|
||||
const opacity = readFixedPoint8(reader);
|
||||
if (blockSize >= 51) readColor(reader); // native color
|
||||
const shadowInfo: LayerEffectShadow = {
|
||||
size: { units: 'Pixels', value: size },
|
||||
distance: { units: 'Pixels', value: distance },
|
||||
angle, color, blendMode, enabled, useGlobalLight, opacity
|
||||
};
|
||||
|
||||
if (type === 'dsdw') {
|
||||
effects.dropShadow = [shadowInfo];
|
||||
} else {
|
||||
effects.innerShadow = [shadowInfo];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'oglw': { // outer glow (see See Effects layer, outer glow info)
|
||||
const blockSize = readUint32(reader);
|
||||
const version = readUint32(reader);
|
||||
|
||||
if (blockSize !== 32 && blockSize !== 42) throw new Error(`Invalid outer glow size: ${blockSize}`);
|
||||
if (version !== 0 && version !== 2) throw new Error(`Invalid outer glow version: ${version}`);
|
||||
|
||||
const size = readFixedPoint32(reader);
|
||||
readFixedPoint32(reader); // intensity
|
||||
const color = readColor(reader);
|
||||
const blendMode = readBlendMode(reader);
|
||||
const enabled = !!readUint8(reader);
|
||||
const opacity = readFixedPoint8(reader);
|
||||
if (blockSize >= 42) readColor(reader); // native color
|
||||
|
||||
effects.outerGlow = {
|
||||
size: { units: 'Pixels', value: size },
|
||||
color, blendMode, enabled, opacity
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'iglw': { // inner glow (see See Effects layer, inner glow info)
|
||||
const blockSize = readUint32(reader);
|
||||
const version = readUint32(reader);
|
||||
|
||||
if (blockSize !== 32 && blockSize !== 43) throw new Error(`Invalid inner glow size: ${blockSize}`);
|
||||
if (version !== 0 && version !== 2) throw new Error(`Invalid inner glow version: ${version}`);
|
||||
|
||||
const size = readFixedPoint32(reader);
|
||||
readFixedPoint32(reader); // intensity
|
||||
const color = readColor(reader);
|
||||
const blendMode = readBlendMode(reader);
|
||||
const enabled = !!readUint8(reader);
|
||||
const opacity = readFixedPoint8(reader);
|
||||
|
||||
if (blockSize >= 43) {
|
||||
readUint8(reader); // inverted
|
||||
readColor(reader); // native color
|
||||
}
|
||||
|
||||
effects.innerGlow = {
|
||||
size: { units: 'Pixels', value: size },
|
||||
color, blendMode, enabled, opacity
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'bevl': { // bevel (see See Effects layer, bevel info)
|
||||
const blockSize = readUint32(reader);
|
||||
const version = readUint32(reader);
|
||||
|
||||
if (blockSize !== 58 && blockSize !== 78) throw new Error(`Invalid bevel size: ${blockSize}`);
|
||||
if (version !== 0 && version !== 2) throw new Error(`Invalid bevel version: ${version}`);
|
||||
|
||||
const angle = readFixedPoint32(reader);
|
||||
const strength = readFixedPoint32(reader);
|
||||
const size = readFixedPoint32(reader);
|
||||
const highlightBlendMode = readBlendMode(reader);
|
||||
const shadowBlendMode = readBlendMode(reader);
|
||||
const highlightColor = readColor(reader);
|
||||
const shadowColor = readColor(reader);
|
||||
const style = bevelStyles[readUint8(reader)] || 'inner bevel';
|
||||
const highlightOpacity = readFixedPoint8(reader);
|
||||
const shadowOpacity = readFixedPoint8(reader);
|
||||
const enabled = !!readUint8(reader);
|
||||
const useGlobalLight = !!readUint8(reader);
|
||||
const direction = readUint8(reader) ? 'down' : 'up';
|
||||
|
||||
if (blockSize >= 78) {
|
||||
readColor(reader); // real highlight color
|
||||
readColor(reader); // real shadow color
|
||||
}
|
||||
|
||||
effects.bevel = {
|
||||
size: { units: 'Pixels', value: size },
|
||||
angle, strength, highlightBlendMode, shadowBlendMode, highlightColor, shadowColor,
|
||||
style, highlightOpacity, shadowOpacity, enabled, useGlobalLight, direction,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'sofi': { // solid fill (Photoshop 7.0) (see See Effects layer, solid fill (added in Photoshop 7.0))
|
||||
const size = readUint32(reader);
|
||||
const version = readUint32(reader);
|
||||
|
||||
if (size !== 34) throw new Error(`Invalid effects solid fill info size: ${size}`);
|
||||
if (version !== 2) throw new Error(`Invalid effects solid fill info version: ${version}`);
|
||||
|
||||
const blendMode = readBlendMode(reader);
|
||||
const color = readColor(reader);
|
||||
const opacity = readFixedPoint8(reader);
|
||||
const enabled = !!readUint8(reader);
|
||||
readColor(reader); // native color
|
||||
|
||||
effects.solidFill = [{ blendMode, color, opacity, enabled }];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid effect type: '${type}'`);
|
||||
}
|
||||
}
|
||||
|
||||
return effects;
|
||||
}
|
||||
|
||||
function writeShadowInfo(writer: PsdWriter, shadow: LayerEffectShadow) {
|
||||
writeUint32(writer, 51);
|
||||
writeUint32(writer, 2);
|
||||
writeFixedPoint32(writer, shadow.size && shadow.size.value || 0);
|
||||
writeFixedPoint32(writer, 0); // intensity
|
||||
writeFixedPoint32(writer, shadow.angle || 0);
|
||||
writeFixedPoint32(writer, shadow.distance && shadow.distance.value || 0);
|
||||
writeColor(writer, shadow.color);
|
||||
writeBlendMode(writer, shadow.blendMode);
|
||||
writeUint8(writer, shadow.enabled ? 1 : 0);
|
||||
writeUint8(writer, shadow.useGlobalLight ? 1 : 0);
|
||||
writeFixedPoint8(writer, shadow.opacity ?? 1);
|
||||
writeColor(writer, shadow.color); // native color
|
||||
}
|
||||
|
||||
export function writeEffects(writer: PsdWriter, effects: LayerEffectsInfo) {
|
||||
const dropShadow = effects.dropShadow?.[0];
|
||||
const innerShadow = effects.innerShadow?.[0];
|
||||
const outerGlow = effects.outerGlow;
|
||||
const innerGlow = effects.innerGlow;
|
||||
const bevel = effects.bevel;
|
||||
const solidFill = effects.solidFill?.[0];
|
||||
|
||||
let count = 1;
|
||||
if (dropShadow) count++;
|
||||
if (innerShadow) count++;
|
||||
if (outerGlow) count++;
|
||||
if (innerGlow) count++;
|
||||
if (bevel) count++;
|
||||
if (solidFill) count++;
|
||||
|
||||
writeUint16(writer, 0);
|
||||
writeUint16(writer, count);
|
||||
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'cmnS');
|
||||
writeUint32(writer, 7); // size
|
||||
writeUint32(writer, 0); // version
|
||||
writeUint8(writer, 1); // visible
|
||||
writeZeros(writer, 2);
|
||||
|
||||
if (dropShadow) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'dsdw');
|
||||
writeShadowInfo(writer, dropShadow);
|
||||
}
|
||||
|
||||
if (innerShadow) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'isdw');
|
||||
writeShadowInfo(writer, innerShadow);
|
||||
}
|
||||
|
||||
if (outerGlow) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'oglw');
|
||||
writeUint32(writer, 42);
|
||||
writeUint32(writer, 2);
|
||||
writeFixedPoint32(writer, outerGlow.size?.value || 0);
|
||||
writeFixedPoint32(writer, 0); // intensity
|
||||
writeColor(writer, outerGlow.color);
|
||||
writeBlendMode(writer, outerGlow.blendMode);
|
||||
writeUint8(writer, outerGlow.enabled ? 1 : 0);
|
||||
writeFixedPoint8(writer, outerGlow.opacity || 0);
|
||||
writeColor(writer, outerGlow.color);
|
||||
}
|
||||
|
||||
if (innerGlow) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'iglw');
|
||||
writeUint32(writer, 43);
|
||||
writeUint32(writer, 2);
|
||||
writeFixedPoint32(writer, innerGlow.size?.value || 0);
|
||||
writeFixedPoint32(writer, 0); // intensity
|
||||
writeColor(writer, innerGlow.color);
|
||||
writeBlendMode(writer, innerGlow.blendMode);
|
||||
writeUint8(writer, innerGlow.enabled ? 1 : 0);
|
||||
writeFixedPoint8(writer, innerGlow.opacity || 0);
|
||||
writeUint8(writer, 0); // inverted
|
||||
writeColor(writer, innerGlow.color);
|
||||
}
|
||||
|
||||
if (bevel) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'bevl');
|
||||
writeUint32(writer, 78);
|
||||
writeUint32(writer, 2);
|
||||
writeFixedPoint32(writer, bevel.angle || 0);
|
||||
writeFixedPoint32(writer, bevel.strength || 0);
|
||||
writeFixedPoint32(writer, bevel.size?.value || 0);
|
||||
writeBlendMode(writer, bevel.highlightBlendMode);
|
||||
writeBlendMode(writer, bevel.shadowBlendMode);
|
||||
writeColor(writer, bevel.highlightColor);
|
||||
writeColor(writer, bevel.shadowColor);
|
||||
const style = bevelStyles.indexOf(bevel.style!);
|
||||
writeUint8(writer, style <= 0 ? 1 : style);
|
||||
writeFixedPoint8(writer, bevel.highlightOpacity || 0);
|
||||
writeFixedPoint8(writer, bevel.shadowOpacity || 0);
|
||||
writeUint8(writer, bevel.enabled ? 1 : 0);
|
||||
writeUint8(writer, bevel.useGlobalLight ? 1 : 0);
|
||||
writeUint8(writer, bevel.direction === 'down' ? 1 : 0);
|
||||
writeColor(writer, bevel.highlightColor);
|
||||
writeColor(writer, bevel.shadowColor);
|
||||
}
|
||||
|
||||
if (solidFill) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, 'sofi');
|
||||
writeUint32(writer, 34);
|
||||
writeUint32(writer, 2);
|
||||
writeBlendMode(writer, solidFill.blendMode);
|
||||
writeColor(writer, solidFill.color);
|
||||
writeFixedPoint8(writer, solidFill.opacity || 0);
|
||||
writeUint8(writer, solidFill.enabled ? 1 : 0);
|
||||
writeColor(writer, solidFill.color);
|
||||
}
|
||||
}
|
||||
359
npm-packages/mac-v2.4.x/ag-psd/src/engineData.ts
Normal file
359
npm-packages/mac-v2.4.x/ag-psd/src/engineData.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
function isWhitespace(char: number) {
|
||||
// ' ', '\n', '\r', '\t'
|
||||
return char === 32 || char === 10 || char === 13 || char === 9;
|
||||
}
|
||||
|
||||
function isNumber(char: number) {
|
||||
// 0123456789.-
|
||||
return (char >= 48 && char <= 57) || char === 46 || char === 45;
|
||||
}
|
||||
|
||||
export function parseEngineData(data: number[] | Uint8Array) {
|
||||
let index = 0;
|
||||
|
||||
function skipWhitespace() {
|
||||
while (index < data.length && isWhitespace(data[index])) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
function getTextByte() {
|
||||
let byte = data[index];
|
||||
index++;
|
||||
|
||||
if (byte === 92) { // \
|
||||
byte = data[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
function getText() {
|
||||
let result = '';
|
||||
|
||||
if (data[index] === 41) { // )
|
||||
index++;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Strings start with utf-16 BOM
|
||||
if (data[index] !== 0xFE || data[index + 1] !== 0xFF) {
|
||||
throw new Error('Invalid utf-16 BOM');
|
||||
}
|
||||
|
||||
index += 2;
|
||||
|
||||
// ), ( and \ characters are escaped in ascii manner, remove the escapes before interpreting
|
||||
// the bytes as utf-16
|
||||
while (index < data.length && data[index] !== 41) { // )
|
||||
const high = getTextByte();
|
||||
const low = getTextByte();
|
||||
const char = (high << 8) | low;
|
||||
result += String.fromCharCode(char);
|
||||
}
|
||||
|
||||
index++;
|
||||
return result;
|
||||
}
|
||||
|
||||
let root: any = null;
|
||||
const stack: any[] = [];
|
||||
|
||||
function pushContainer(value: any) {
|
||||
if (!stack.length) {
|
||||
stack.push(value);
|
||||
root = value;
|
||||
} else {
|
||||
pushValue(value);
|
||||
stack.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
function pushValue(value: any) {
|
||||
if (!stack.length) throw new Error('Invalid data');
|
||||
|
||||
const top = stack[stack.length - 1];
|
||||
|
||||
if (typeof top === 'string') {
|
||||
stack[stack.length - 2][top] = value;
|
||||
pop();
|
||||
} else if (Array.isArray(top)) {
|
||||
top.push(value);
|
||||
} else {
|
||||
throw new Error('Invalid data');
|
||||
}
|
||||
}
|
||||
|
||||
function pushProperty(name: string) {
|
||||
if (!stack.length) pushContainer({});
|
||||
|
||||
const top = stack[stack.length - 1];
|
||||
|
||||
if (top && typeof top === 'string') {
|
||||
if (name === 'nil') {
|
||||
pushValue(null);
|
||||
} else {
|
||||
pushValue(`/${name}`);
|
||||
}
|
||||
} else if (top && typeof top === 'object') {
|
||||
stack.push(name);
|
||||
} else {
|
||||
throw new Error('Invalid data');
|
||||
}
|
||||
}
|
||||
|
||||
function pop() {
|
||||
if (!stack.length) throw new Error('Invalid data');
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
skipWhitespace();
|
||||
|
||||
while (index < data.length) {
|
||||
const i = index;
|
||||
const char = data[i];
|
||||
|
||||
if (char === 60 && data[i + 1] === 60) { // <<
|
||||
index += 2;
|
||||
pushContainer({});
|
||||
} else if (char === 62 && data[i + 1] === 62) { // >>
|
||||
index += 2;
|
||||
pop();
|
||||
} else if (char === 47) { // /
|
||||
index += 1;
|
||||
const start = index;
|
||||
|
||||
while (index < data.length && !isWhitespace(data[index])) {
|
||||
index++;
|
||||
}
|
||||
|
||||
let name = '';
|
||||
|
||||
for (let i = start; i < index; i++) {
|
||||
name += String.fromCharCode(data[i]);
|
||||
}
|
||||
|
||||
pushProperty(name);
|
||||
} else if (char === 40) { // (
|
||||
index += 1;
|
||||
pushValue(getText());
|
||||
} else if (char === 91) { // [
|
||||
index += 1;
|
||||
pushContainer([]);
|
||||
} else if (char === 93) { // ]
|
||||
index += 1;
|
||||
pop();
|
||||
} else if (char === 110 && data[i + 1] === 117 && data[i + 2] === 108 && data[i + 3] === 108) { // null
|
||||
index += 4;
|
||||
pushValue(null);
|
||||
} else if (char === 116 && data[i + 1] === 114 && data[i + 2] === 117 && data[i + 3] === 101) { // true
|
||||
index += 4;
|
||||
pushValue(true);
|
||||
} else if (char === 102 && data[i + 1] === 97 && data[i + 2] === 108 && data[i + 3] === 115 && data[i + 4] === 101) { // false
|
||||
index += 5;
|
||||
pushValue(false);
|
||||
} else if (isNumber(char)) {
|
||||
let value = '';
|
||||
|
||||
while (index < data.length && isNumber(data[index])) {
|
||||
value += String.fromCharCode(data[index]);
|
||||
index++;
|
||||
}
|
||||
|
||||
pushValue(parseFloat(value));
|
||||
} else {
|
||||
index += 1;
|
||||
console.log(`Invalid token ${String.fromCharCode(char)} at ${index}`);
|
||||
// ` near ${String.fromCharCode.apply(null, data.slice(index - 10, index + 20) as any)}` +
|
||||
// `data [${Array.from(data.slice(index - 10, index + 20)).join(', ')}]`
|
||||
}
|
||||
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
const floatKeys = [
|
||||
'Axis', 'XY', 'Zone', 'WordSpacing', 'FirstLineIndent', 'GlyphSpacing', 'StartIndent', 'EndIndent', 'SpaceBefore',
|
||||
'SpaceAfter', 'LetterSpacing', 'Values', 'GridSize', 'GridLeading', 'PointBase', 'BoxBounds', 'TransformPoint0', 'TransformPoint1',
|
||||
'TransformPoint2', 'FontSize', 'Leading', 'HorizontalScale', 'VerticalScale', 'BaselineShift', 'Tsume',
|
||||
'OutlineWidth', 'AutoLeading',
|
||||
];
|
||||
|
||||
const intArrays = ['RunLengthArray'];
|
||||
|
||||
// TODO: handle /nil
|
||||
export function serializeEngineData(data: any, condensed = false) {
|
||||
let buffer = new Uint8Array(1024);
|
||||
let offset = 0;
|
||||
let indent = 0;
|
||||
|
||||
function write(value: number) {
|
||||
if (offset >= buffer.length) {
|
||||
const newBuffer = new Uint8Array(buffer.length * 2);
|
||||
newBuffer.set(buffer);
|
||||
buffer = newBuffer;
|
||||
}
|
||||
|
||||
buffer[offset] = value;
|
||||
offset++;
|
||||
}
|
||||
|
||||
function writeString(value: string) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
write(value.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function writeIndent() {
|
||||
if (condensed) {
|
||||
writeString(' ');
|
||||
} else {
|
||||
for (let i = 0; i < indent; i++) {
|
||||
writeString('\t');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeProperty(key: string, value: any) {
|
||||
writeIndent();
|
||||
writeString(`/${key}`);
|
||||
writeValue(value, key, true);
|
||||
if (!condensed) writeString('\n');
|
||||
}
|
||||
|
||||
function serializeInt(value: number) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
function serializeFloat(value: number) {
|
||||
return value.toFixed(5)
|
||||
.replace(/(\d)0+$/g, '$1')
|
||||
.replace(/^0+\.([1-9])/g, '.$1')
|
||||
.replace(/^-0+\.0(\d)/g, '-.0$1');
|
||||
}
|
||||
|
||||
function serializeNumber(value: number, key?: string) {
|
||||
const isFloat = (key && floatKeys.indexOf(key) !== -1) || (value | 0) !== value;
|
||||
return isFloat ? serializeFloat(value) : serializeInt(value);
|
||||
}
|
||||
|
||||
function getKeys(value: any) {
|
||||
const keys = Object.keys(value);
|
||||
|
||||
if (keys.indexOf('98') !== -1)
|
||||
keys.unshift(...keys.splice(keys.indexOf('99'), 1));
|
||||
|
||||
if (keys.indexOf('99') !== -1)
|
||||
keys.unshift(...keys.splice(keys.indexOf('99'), 1));
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
function writeStringByte(value: number) {
|
||||
if (value === 40 || value === 41 || value === 92) { // ( ) \
|
||||
write(92); // \
|
||||
}
|
||||
|
||||
write(value);
|
||||
}
|
||||
|
||||
function writeValue(value: any, key?: string, inProperty = false) {
|
||||
function writePrefix() {
|
||||
if (inProperty) {
|
||||
writeString(' ');
|
||||
} else {
|
||||
writeIndent();
|
||||
}
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
writePrefix();
|
||||
writeString(condensed ? '/nil' : 'null');
|
||||
} else if (typeof value === 'number') {
|
||||
writePrefix();
|
||||
writeString(serializeNumber(value, key));
|
||||
} else if (typeof value === 'boolean') {
|
||||
writePrefix();
|
||||
writeString(value ? 'true' : 'false');
|
||||
} else if (typeof value === 'string') {
|
||||
writePrefix();
|
||||
|
||||
if ((key === '99' || key === '98') && value.charAt(0) === '/') {
|
||||
writeString(value);
|
||||
} else {
|
||||
writeString('(');
|
||||
write(0xfe);
|
||||
write(0xff);
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const code = value.charCodeAt(i);
|
||||
writeStringByte((code >> 8) & 0xff);
|
||||
writeStringByte(code & 0xff);
|
||||
}
|
||||
|
||||
writeString(')');
|
||||
}
|
||||
} else if (Array.isArray(value)) {
|
||||
writePrefix();
|
||||
|
||||
if (value.every(x => typeof x === 'number')) {
|
||||
writeString('[');
|
||||
|
||||
const intArray = intArrays.indexOf(key!) !== -1;
|
||||
|
||||
for (const x of value) {
|
||||
writeString(' ');
|
||||
writeString(intArray ? serializeNumber(x) : serializeFloat(x));
|
||||
}
|
||||
|
||||
writeString(' ]');
|
||||
} else {
|
||||
writeString('[');
|
||||
if (!condensed) writeString('\n');
|
||||
|
||||
for (const x of value) {
|
||||
writeValue(x, key);
|
||||
if (!condensed) writeString('\n');
|
||||
}
|
||||
|
||||
writeIndent();
|
||||
writeString(']');
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
if (inProperty && !condensed) writeString('\n');
|
||||
|
||||
writeIndent();
|
||||
writeString('<<');
|
||||
|
||||
if (!condensed) writeString('\n');
|
||||
|
||||
indent++;
|
||||
|
||||
for (const key of getKeys(value)) {
|
||||
writeProperty(key, value[key]);
|
||||
}
|
||||
|
||||
indent--;
|
||||
writeIndent();
|
||||
writeString('>>');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (condensed) {
|
||||
if (typeof data === 'object') {
|
||||
for (const key of getKeys(data)) {
|
||||
writeProperty(key, data[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeString('\n\n');
|
||||
writeValue(data);
|
||||
}
|
||||
|
||||
return buffer.slice(0, offset);
|
||||
}
|
||||
387
npm-packages/mac-v2.4.x/ag-psd/src/helpers.ts
Normal file
387
npm-packages/mac-v2.4.x/ag-psd/src/helpers.ts
Normal file
@@ -0,0 +1,387 @@
|
||||
import { fromByteArray } from 'base64-js';
|
||||
import { deflate } from 'pako';
|
||||
import { Layer, BlendMode, LayerColor } from './psd';
|
||||
|
||||
export const MOCK_HANDLERS = false;
|
||||
export const RAW_IMAGE_DATA = false;
|
||||
|
||||
export const fromBlendMode: { [key: string]: string } = {};
|
||||
export const toBlendMode: { [key: string]: BlendMode } = {
|
||||
'pass': 'pass through',
|
||||
'norm': 'normal',
|
||||
'diss': 'dissolve',
|
||||
'dark': 'darken',
|
||||
'mul ': 'multiply',
|
||||
'idiv': 'color burn',
|
||||
'lbrn': 'linear burn',
|
||||
'dkCl': 'darker color',
|
||||
'lite': 'lighten',
|
||||
'scrn': 'screen',
|
||||
'div ': 'color dodge',
|
||||
'lddg': 'linear dodge',
|
||||
'lgCl': 'lighter color',
|
||||
'over': 'overlay',
|
||||
'sLit': 'soft light',
|
||||
'hLit': 'hard light',
|
||||
'vLit': 'vivid light',
|
||||
'lLit': 'linear light',
|
||||
'pLit': 'pin light',
|
||||
'hMix': 'hard mix',
|
||||
'diff': 'difference',
|
||||
'smud': 'exclusion',
|
||||
'fsub': 'subtract',
|
||||
'fdiv': 'divide',
|
||||
'hue ': 'hue',
|
||||
'sat ': 'saturation',
|
||||
'colr': 'color',
|
||||
'lum ': 'luminosity',
|
||||
};
|
||||
|
||||
Object.keys(toBlendMode).forEach(key => fromBlendMode[toBlendMode[key]] = key);
|
||||
|
||||
export const layerColors: LayerColor[] = [
|
||||
'none', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'gray'
|
||||
];
|
||||
|
||||
export const largeAdditionalInfoKeys = [
|
||||
// from documentation
|
||||
'LMsk', 'Lr16', 'Lr32', 'Layr', 'Mt16', 'Mt32', 'Mtrn', 'Alph', 'FMsk', 'lnk2', 'FEid', 'FXid', 'PxSD',
|
||||
// from guessing
|
||||
'cinf',
|
||||
];
|
||||
|
||||
export interface Dict {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export function revMap(map: Dict) {
|
||||
const result: Dict = {};
|
||||
Object.keys(map).forEach(key => result[map[key]] = key);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createEnum<T>(prefix: string, def: string, map: Dict) {
|
||||
const rev = revMap(map);
|
||||
const decode = (val: string): T => {
|
||||
const value = val.split('.')[1];
|
||||
if (value && !rev[value]) throw new Error(`Unrecognized value for enum: '${val}'`);
|
||||
return (rev[value] as any) || def;
|
||||
};
|
||||
const encode = (val: T | undefined): string => {
|
||||
if (val && !map[val as any]) throw new Error(`Invalid value for enum: '${val}'`);
|
||||
return `${prefix}.${map[val as any] || map[def]}`;
|
||||
};
|
||||
return { decode, encode };
|
||||
}
|
||||
|
||||
export const enum ColorSpace {
|
||||
RGB = 0,
|
||||
HSB = 1,
|
||||
CMYK = 2,
|
||||
Lab = 7,
|
||||
Grayscale = 8,
|
||||
}
|
||||
|
||||
export const enum LayerMaskFlags {
|
||||
PositionRelativeToLayer = 1,
|
||||
LayerMaskDisabled = 2,
|
||||
InvertLayerMaskWhenBlending = 4, // obsolete
|
||||
LayerMaskFromRenderingOtherData = 8,
|
||||
MaskHasParametersAppliedToIt = 16,
|
||||
}
|
||||
|
||||
export const enum MaskParams {
|
||||
UserMaskDensity = 1,
|
||||
UserMaskFeather = 2,
|
||||
VectorMaskDensity = 4,
|
||||
VectorMaskFeather = 8,
|
||||
}
|
||||
|
||||
export const enum ChannelID {
|
||||
Color0 = 0, // red (rgb) / cyan (cmyk)
|
||||
Color1 = 1, // green (rgb) / magenta (cmyk)
|
||||
Color2 = 2, // blue (rgb) / yellow (cmyk)
|
||||
Color3 = 3, // - (rgb) / black (cmyk)
|
||||
Transparency = -1,
|
||||
UserMask = -2,
|
||||
RealUserMask = -3,
|
||||
}
|
||||
|
||||
export const enum Compression {
|
||||
RawData = 0,
|
||||
RleCompressed = 1,
|
||||
ZipWithoutPrediction = 2,
|
||||
ZipWithPrediction = 3,
|
||||
}
|
||||
|
||||
export interface ChannelData {
|
||||
channelId: ChannelID;
|
||||
compression: Compression;
|
||||
buffer: Uint8Array | undefined;
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface Bounds {
|
||||
top: number;
|
||||
left: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface LayerChannelData {
|
||||
layer: Layer;
|
||||
channels: ChannelData[];
|
||||
top: number;
|
||||
left: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
mask?: Bounds;
|
||||
}
|
||||
|
||||
export type PixelArray = Uint8ClampedArray | Uint8Array;
|
||||
|
||||
export interface PixelData {
|
||||
data: PixelArray;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export function offsetForChannel(channelId: ChannelID, cmyk: boolean) {
|
||||
switch (channelId) {
|
||||
case ChannelID.Color0: return 0;
|
||||
case ChannelID.Color1: return 1;
|
||||
case ChannelID.Color2: return 2;
|
||||
case ChannelID.Color3: return cmyk ? 3 : channelId + 1;
|
||||
case ChannelID.Transparency: return cmyk ? 4 : 3;
|
||||
default: return channelId + 1;
|
||||
}
|
||||
}
|
||||
|
||||
export function clamp(value: number, min: number, max: number) {
|
||||
return value < min ? min : (value > max ? max : value);
|
||||
}
|
||||
|
||||
export function hasAlpha(data: PixelData) {
|
||||
const size = data.width * data.height * 4;
|
||||
|
||||
for (let i = 3; i < size; i += 4) {
|
||||
if (data.data[i] !== 255) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function resetImageData({ data }: PixelData) {
|
||||
const buffer = new Uint32Array(data.buffer);
|
||||
const size = buffer.length | 0;
|
||||
|
||||
for (let p = 0; p < size; p = (p + 1) | 0) {
|
||||
buffer[p] = 0xff000000;
|
||||
}
|
||||
}
|
||||
|
||||
export function decodeBitmap(input: PixelArray, output: PixelArray, width: number, height: number) {
|
||||
for (let y = 0, p = 0, o = 0; y < height; y++) {
|
||||
for (let x = 0; x < width;) {
|
||||
let b = input[o++];
|
||||
|
||||
for (let i = 0; i < 8 && x < width; i++, x++) {
|
||||
const v = b & 0x80 ? 0 : 255;
|
||||
b = b << 1;
|
||||
output[p++] = v;
|
||||
output[p++] = v;
|
||||
output[p++] = v;
|
||||
output[p++] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function writeDataRaw(data: PixelData, offset: number, width: number, height: number) {
|
||||
if (!width || !height)
|
||||
return undefined;
|
||||
|
||||
const array = new Uint8Array(width * height);
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
array[i] = data.data[i * 4 + offset];
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
export function writeDataRLE(buffer: Uint8Array, { data, width, height }: PixelData, offsets: number[], large: boolean) {
|
||||
if (!width || !height) return undefined;
|
||||
|
||||
const stride = (4 * width) | 0;
|
||||
|
||||
let ol = 0;
|
||||
let o = (offsets.length * (large ? 4 : 2) * height) | 0;
|
||||
|
||||
for (const offset of offsets) {
|
||||
for (let y = 0, p = offset | 0; y < height; y++) {
|
||||
const strideStart = (y * stride) | 0;
|
||||
const strideEnd = (strideStart + stride) | 0;
|
||||
const lastIndex = (strideEnd + offset - 4) | 0;
|
||||
const lastIndex2 = (lastIndex - 4) | 0;
|
||||
const startOffset = o;
|
||||
|
||||
for (p = (strideStart + offset) | 0; p < strideEnd; p = (p + 4) | 0) {
|
||||
if (p < lastIndex2) {
|
||||
let value1 = data[p];
|
||||
p = (p + 4) | 0;
|
||||
let value2 = data[p];
|
||||
p = (p + 4) | 0;
|
||||
let value3 = data[p];
|
||||
|
||||
if (value1 === value2 && value1 === value3) {
|
||||
let count = 3;
|
||||
|
||||
while (count < 128 && p < lastIndex && data[(p + 4) | 0] === value1) {
|
||||
count = (count + 1) | 0;
|
||||
p = (p + 4) | 0;
|
||||
}
|
||||
|
||||
buffer[o++] = 1 - count;
|
||||
buffer[o++] = value1;
|
||||
} else {
|
||||
const countIndex = o;
|
||||
let writeLast = true;
|
||||
let count = 1;
|
||||
buffer[o++] = 0;
|
||||
buffer[o++] = value1;
|
||||
|
||||
while (p < lastIndex && count < 128) {
|
||||
p = (p + 4) | 0;
|
||||
value1 = value2;
|
||||
value2 = value3;
|
||||
value3 = data[p];
|
||||
|
||||
if (value1 === value2 && value1 === value3) {
|
||||
p = (p - 12) | 0;
|
||||
writeLast = false;
|
||||
break;
|
||||
} else {
|
||||
count++;
|
||||
buffer[o++] = value1;
|
||||
}
|
||||
}
|
||||
|
||||
if (writeLast) {
|
||||
if (count < 127) {
|
||||
buffer[o++] = value2;
|
||||
buffer[o++] = value3;
|
||||
count += 2;
|
||||
} else if (count < 128) {
|
||||
buffer[o++] = value2;
|
||||
count++;
|
||||
p = (p - 4) | 0;
|
||||
} else {
|
||||
p = (p - 8) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[countIndex] = count - 1;
|
||||
}
|
||||
} else if (p === lastIndex) {
|
||||
buffer[o++] = 0;
|
||||
buffer[o++] = data[p];
|
||||
} else { // p === lastIndex2
|
||||
buffer[o++] = 1;
|
||||
buffer[o++] = data[p];
|
||||
p = (p + 4) | 0;
|
||||
buffer[o++] = data[p];
|
||||
}
|
||||
}
|
||||
|
||||
const length = o - startOffset;
|
||||
|
||||
if (large) {
|
||||
buffer[ol++] = (length >> 24) & 0xff;
|
||||
buffer[ol++] = (length >> 16) & 0xff;
|
||||
}
|
||||
|
||||
buffer[ol++] = (length >> 8) & 0xff;
|
||||
buffer[ol++] = length & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.slice(0, o);
|
||||
}
|
||||
|
||||
export function writeDataZipWithoutPrediction({ data, width, height }: PixelData, offsets: number[]) {
|
||||
const size = width * height;
|
||||
const channel = new Uint8Array(size);
|
||||
const buffers: Uint8Array[] = [];
|
||||
let totalLength = 0;
|
||||
|
||||
for (const offset of offsets) {
|
||||
for (let i = 0, o = offset; i < size; i++, o += 4) {
|
||||
channel[i] = data[o];
|
||||
}
|
||||
|
||||
const buffer = deflate(channel);
|
||||
buffers.push(buffer);
|
||||
totalLength += buffer.byteLength;
|
||||
}
|
||||
|
||||
if (buffers.length > 0) {
|
||||
const buffer = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
|
||||
for (const b of buffers) {
|
||||
buffer.set(b, offset);
|
||||
offset += b.byteLength;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
} else {
|
||||
return buffers[0];
|
||||
}
|
||||
}
|
||||
|
||||
export let createCanvas: (width: number, height: number) => HTMLCanvasElement = () => {
|
||||
throw new Error('Canvas not initialized, use initializeCanvas method to set up createCanvas method');
|
||||
};
|
||||
|
||||
export let createCanvasFromData: (data: Uint8Array) => HTMLCanvasElement = () => {
|
||||
throw new Error('Canvas not initialized, use initializeCanvas method to set up createCanvasFromData method');
|
||||
};
|
||||
|
||||
let tempCanvas: HTMLCanvasElement | undefined = undefined;
|
||||
|
||||
export let createImageData: (width: number, height: number) => ImageData = (width, height) => {
|
||||
if (!tempCanvas) tempCanvas = createCanvas(1, 1);
|
||||
return tempCanvas.getContext('2d')!.createImageData(width, height);
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
createCanvas = (width, height) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
};
|
||||
|
||||
createCanvasFromData = (data) => {
|
||||
const image = new Image();
|
||||
image.src = 'data:image/jpeg;base64,' + fromByteArray(data);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
canvas.getContext('2d')!.drawImage(image, 0, 0);
|
||||
return canvas;
|
||||
};
|
||||
}
|
||||
|
||||
export function initializeCanvas(
|
||||
createCanvasMethod: (width: number, height: number) => HTMLCanvasElement,
|
||||
createCanvasFromDataMethod?: (data: Uint8Array) => HTMLCanvasElement,
|
||||
createImageDataMethod?: (width: number, height: number) => ImageData
|
||||
) {
|
||||
createCanvas = createCanvasMethod;
|
||||
createCanvasFromData = createCanvasFromDataMethod || createCanvasFromData;
|
||||
createImageData = createImageDataMethod || createImageData;
|
||||
}
|
||||
1439
npm-packages/mac-v2.4.x/ag-psd/src/imageResources.ts
Normal file
1439
npm-packages/mac-v2.4.x/ag-psd/src/imageResources.ts
Normal file
File diff suppressed because it is too large
Load Diff
44
npm-packages/mac-v2.4.x/ag-psd/src/index.ts
Normal file
44
npm-packages/mac-v2.4.x/ag-psd/src/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Psd, ReadOptions, WriteOptions } from './psd';
|
||||
import { PsdWriter, writePsd as writePsdInternal, getWriterBuffer, createWriter, getWriterBufferNoCopy } from './psdWriter';
|
||||
import { PsdReader, readPsd as readPsdInternal, createReader } from './psdReader';
|
||||
export * from './abr';
|
||||
export * from './csh';
|
||||
export { initializeCanvas } from './helpers';
|
||||
export * from './psd';
|
||||
import { fromByteArray } from 'base64-js';
|
||||
export { PsdReader, PsdWriter };
|
||||
|
||||
interface BufferLike {
|
||||
buffer: ArrayBuffer;
|
||||
byteOffset: number;
|
||||
byteLength: number;
|
||||
}
|
||||
|
||||
export const byteArrayToBase64 = fromByteArray;
|
||||
|
||||
export function readPsd(buffer: ArrayBuffer | BufferLike, options?: ReadOptions): Psd {
|
||||
const reader = 'buffer' in buffer ?
|
||||
createReader(buffer.buffer, buffer.byteOffset, buffer.byteLength) :
|
||||
createReader(buffer);
|
||||
return readPsdInternal(reader, options);
|
||||
}
|
||||
|
||||
export function writePsd(psd: Psd, options?: WriteOptions): ArrayBuffer {
|
||||
const writer = createWriter();
|
||||
writePsdInternal(writer, psd, options);
|
||||
return getWriterBuffer(writer);
|
||||
}
|
||||
|
||||
export function writePsdUint8Array(psd: Psd, options?: WriteOptions): Uint8Array {
|
||||
const writer = createWriter();
|
||||
writePsdInternal(writer, psd, options);
|
||||
return getWriterBufferNoCopy(writer);
|
||||
}
|
||||
|
||||
export function writePsdBuffer(psd: Psd, options?: WriteOptions): Buffer {
|
||||
if (typeof Buffer === 'undefined') {
|
||||
throw new Error('Buffer not supported on this platform');
|
||||
}
|
||||
|
||||
return Buffer.from(writePsdUint8Array(psd, options));
|
||||
}
|
||||
25
npm-packages/mac-v2.4.x/ag-psd/src/initializeCanvas.ts
Normal file
25
npm-packages/mac-v2.4.x/ag-psd/src/initializeCanvas.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createCanvas } from 'canvas';
|
||||
import { initializeCanvas } from './index';
|
||||
import { decodeJpeg } from './jpeg';
|
||||
|
||||
function createCanvasFromData(data: Uint8Array) {
|
||||
const canvas = createCanvas(100, 100);
|
||||
|
||||
try {
|
||||
const context = canvas.getContext('2d')!;
|
||||
const imageData = decodeJpeg(data, (w, h) => context.createImageData(w, h));
|
||||
canvas.width = imageData.width;
|
||||
canvas.height = imageData.height;
|
||||
context.putImageData(imageData, 0, 0);
|
||||
} catch (e: any) {
|
||||
console.error('JPEG decompression error', e.message);
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
initializeCanvas(createCanvas, createCanvasFromData);
|
||||
|
||||
export function initialize() {
|
||||
initializeCanvas(createCanvas, createCanvasFromData);
|
||||
}
|
||||
1166
npm-packages/mac-v2.4.x/ag-psd/src/jpeg.ts
Normal file
1166
npm-packages/mac-v2.4.x/ag-psd/src/jpeg.ts
Normal file
File diff suppressed because it is too large
Load Diff
1243
npm-packages/mac-v2.4.x/ag-psd/src/psd.ts
Normal file
1243
npm-packages/mac-v2.4.x/ag-psd/src/psd.ts
Normal file
File diff suppressed because it is too large
Load Diff
1091
npm-packages/mac-v2.4.x/ag-psd/src/psdReader.ts
Normal file
1091
npm-packages/mac-v2.4.x/ag-psd/src/psdReader.ts
Normal file
File diff suppressed because it is too large
Load Diff
760
npm-packages/mac-v2.4.x/ag-psd/src/psdWriter.ts
Normal file
760
npm-packages/mac-v2.4.x/ag-psd/src/psdWriter.ts
Normal file
@@ -0,0 +1,760 @@
|
||||
import { Psd, Layer, LayerAdditionalInfo, ColorMode, SectionDividerType, WriteOptions, Color, GlobalLayerMaskInfo } from './psd';
|
||||
import {
|
||||
hasAlpha, createCanvas, writeDataRLE, PixelData, LayerChannelData, ChannelData,
|
||||
offsetForChannel, createImageData, fromBlendMode, ChannelID, Compression, clamp,
|
||||
LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction
|
||||
} from './helpers';
|
||||
import { ExtendedWriteOptions, hasMultiEffects, infoHandlers } from './additionalInfo';
|
||||
import { resourceHandlers } from './imageResources';
|
||||
|
||||
export interface PsdWriter {
|
||||
offset: number;
|
||||
buffer: ArrayBuffer;
|
||||
view: DataView;
|
||||
}
|
||||
|
||||
export function createWriter(size = 4096): PsdWriter {
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const view = new DataView(buffer);
|
||||
const offset = 0;
|
||||
return { buffer, view, offset };
|
||||
}
|
||||
|
||||
export function getWriterBuffer(writer: PsdWriter) {
|
||||
return writer.buffer.slice(0, writer.offset);
|
||||
}
|
||||
|
||||
export function getWriterBufferNoCopy(writer: PsdWriter) {
|
||||
return new Uint8Array(writer.buffer, 0, writer.offset);
|
||||
}
|
||||
|
||||
export function writeUint8(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 1);
|
||||
writer.view.setUint8(offset, value);
|
||||
}
|
||||
|
||||
export function writeInt16(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 2);
|
||||
writer.view.setInt16(offset, value, false);
|
||||
}
|
||||
|
||||
export function writeUint16(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 2);
|
||||
writer.view.setUint16(offset, value, false);
|
||||
}
|
||||
|
||||
export function writeInt32(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 4);
|
||||
writer.view.setInt32(offset, value, false);
|
||||
}
|
||||
|
||||
export function writeUint32(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 4);
|
||||
writer.view.setUint32(offset, value, false);
|
||||
}
|
||||
|
||||
export function writeFloat32(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 4);
|
||||
writer.view.setFloat32(offset, value, false);
|
||||
}
|
||||
|
||||
export function writeFloat64(writer: PsdWriter, value: number) {
|
||||
const offset = addSize(writer, 8);
|
||||
writer.view.setFloat64(offset, value, false);
|
||||
}
|
||||
|
||||
// 32-bit fixed-point number 16.16
|
||||
export function writeFixedPoint32(writer: PsdWriter, value: number) {
|
||||
writeInt32(writer, value * (1 << 16));
|
||||
}
|
||||
|
||||
// 32-bit fixed-point number 8.24
|
||||
export function writeFixedPointPath32(writer: PsdWriter, value: number) {
|
||||
writeInt32(writer, value * (1 << 24));
|
||||
}
|
||||
|
||||
export function writeBytes(writer: PsdWriter, buffer: Uint8Array | undefined) {
|
||||
if (buffer) {
|
||||
ensureSize(writer, writer.offset + buffer.length);
|
||||
const bytes = new Uint8Array(writer.buffer);
|
||||
bytes.set(buffer, writer.offset);
|
||||
writer.offset += buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeZeros(writer: PsdWriter, count: number) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
writeUint8(writer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function writeSignature(writer: PsdWriter, signature: string) {
|
||||
if (signature.length !== 4) throw new Error(`Invalid signature: '${signature}'`);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
writeUint8(writer, signature.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
export function writePascalString(writer: PsdWriter, text: string, padTo: number) {
|
||||
let length = text.length;
|
||||
writeUint8(writer, length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const code = text.charCodeAt(i);
|
||||
writeUint8(writer, code < 128 ? code : '?'.charCodeAt(0));
|
||||
}
|
||||
|
||||
while (++length % padTo) {
|
||||
writeUint8(writer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function writeUnicodeString(writer: PsdWriter, text: string) {
|
||||
writeUint32(writer, text.length);
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
writeUint16(writer, text.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
export function writeUnicodeStringWithPadding(writer: PsdWriter, text: string) {
|
||||
writeUint32(writer, text.length + 1);
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
writeUint16(writer, text.charCodeAt(i));
|
||||
}
|
||||
|
||||
writeUint16(writer, 0);
|
||||
}
|
||||
|
||||
function getLargestLayerSize(layers: Layer[] = []): number {
|
||||
let max = 0;
|
||||
|
||||
for (const layer of layers) {
|
||||
if (layer.canvas || layer.imageData) {
|
||||
const { width, height } = getLayerDimentions(layer);
|
||||
max = Math.max(max, 2 * height + 2 * width * height);
|
||||
}
|
||||
|
||||
if (layer.children) {
|
||||
max = Math.max(max, getLargestLayerSize(layer.children));
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
export function writeSection(writer: PsdWriter, round: number, func: () => void, writeTotalLength = false, large = false) {
|
||||
if (large) writeUint32(writer, 0);
|
||||
const offset = writer.offset;
|
||||
writeUint32(writer, 0);
|
||||
|
||||
func();
|
||||
|
||||
let length = writer.offset - offset - 4;
|
||||
let len = length;
|
||||
|
||||
while ((len % round) !== 0) {
|
||||
writeUint8(writer, 0);
|
||||
len++;
|
||||
}
|
||||
|
||||
if (writeTotalLength) {
|
||||
length = len;
|
||||
}
|
||||
|
||||
writer.view.setUint32(offset, length, false);
|
||||
}
|
||||
|
||||
export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}) {
|
||||
if (!(+psd.width > 0 && +psd.height > 0))
|
||||
throw new Error('Invalid document size');
|
||||
|
||||
if ((psd.width > 30000 || psd.height > 30000) && !options.psb)
|
||||
throw new Error('Document size is too large (max is 30000x30000, use PSB format instead)');
|
||||
|
||||
let imageResources = psd.imageResources || {};
|
||||
|
||||
const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map() };
|
||||
|
||||
if (opt.generateThumbnail) {
|
||||
imageResources = { ...imageResources, thumbnail: createThumbnail(psd) };
|
||||
}
|
||||
|
||||
let imageData = psd.imageData;
|
||||
|
||||
if (!imageData && psd.canvas) {
|
||||
imageData = psd.canvas.getContext('2d')!.getImageData(0, 0, psd.canvas.width, psd.canvas.height);
|
||||
}
|
||||
|
||||
if (imageData && (psd.width !== imageData.width || psd.height !== imageData.height))
|
||||
throw new Error('Document canvas must have the same size as document');
|
||||
|
||||
const globalAlpha = !!imageData && hasAlpha(imageData);
|
||||
const maxBufferSize = Math.max(getLargestLayerSize(psd.children), 4 * 2 * psd.width * psd.height + 2 * psd.height);
|
||||
const tempBuffer = new Uint8Array(maxBufferSize);
|
||||
|
||||
// header
|
||||
writeSignature(writer, '8BPS');
|
||||
writeUint16(writer, options.psb ? 2 : 1); // version
|
||||
writeZeros(writer, 6);
|
||||
writeUint16(writer, globalAlpha ? 4 : 3); // channels
|
||||
writeUint32(writer, psd.height);
|
||||
writeUint32(writer, psd.width);
|
||||
writeUint16(writer, 8); // bits per channel
|
||||
writeUint16(writer, ColorMode.RGB); // we only support saving RGB right now
|
||||
|
||||
// color mode data
|
||||
writeSection(writer, 1, () => {
|
||||
// TODO: implement
|
||||
});
|
||||
|
||||
// image resources
|
||||
writeSection(writer, 1, () => {
|
||||
for (const handler of resourceHandlers) {
|
||||
const has = handler.has(imageResources);
|
||||
const count = has === false ? 0 : (has === true ? 1 : has);
|
||||
for (let i = 0; i < count; i++) {
|
||||
writeSignature(writer, '8BIM');
|
||||
writeUint16(writer, handler.key);
|
||||
writePascalString(writer, '', 2);
|
||||
writeSection(writer, 2, () => handler.write(writer, imageResources, i));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// layer and mask info
|
||||
writeSection(writer, 2, () => {
|
||||
writeLayerInfo(tempBuffer, writer, psd, globalAlpha, opt);
|
||||
writeGlobalLayerMaskInfo(writer, psd.globalLayerMaskInfo);
|
||||
writeAdditionalLayerInfo(writer, psd, psd, opt);
|
||||
}, undefined, !!opt.psb);
|
||||
|
||||
// image data
|
||||
const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
|
||||
const width = imageData ? imageData.width : psd.width;
|
||||
const height = imageData ? imageData.height : psd.height;
|
||||
const data: PixelData = { data: new Uint8Array(width * height * 4), width, height };
|
||||
|
||||
writeUint16(writer, Compression.RleCompressed); // Photoshop doesn't support zip compression of composite image data
|
||||
|
||||
if (RAW_IMAGE_DATA && (psd as any).imageDataRaw) {
|
||||
console.log('writing raw image data');
|
||||
writeBytes(writer, (psd as any).imageDataRaw);
|
||||
} else {
|
||||
if (imageData) data.data.set(new Uint8Array(imageData.data.buffer, imageData.data.byteOffset, imageData.data.byteLength));
|
||||
|
||||
// add weird white matte
|
||||
if (globalAlpha) {
|
||||
const size = data.width * data.height * 4;
|
||||
const p = data.data;
|
||||
for (let i = 0; i < size; i += 4) {
|
||||
const pa = p[i + 3];
|
||||
if (pa != 0 && pa != 255) {
|
||||
const a = pa / 255;
|
||||
const ra = 255 * (1 - a);
|
||||
p[i + 0] = p[i + 0] * a + ra;
|
||||
p[i + 1] = p[i + 1] * a + ra;
|
||||
p[i + 2] = p[i + 2] * a + ra;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeBytes(writer, writeDataRLE(tempBuffer, data, channels, !!options.psb));
|
||||
}
|
||||
}
|
||||
|
||||
function writeLayerInfo(tempBuffer: Uint8Array, writer: PsdWriter, psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
|
||||
writeSection(writer, 4, () => {
|
||||
const layers: Layer[] = [];
|
||||
|
||||
addChildren(layers, psd.children);
|
||||
|
||||
if (!layers.length) layers.push({});
|
||||
|
||||
writeInt16(writer, globalAlpha ? -layers.length : layers.length);
|
||||
|
||||
const layersData = layers.map((l, i) => getChannels(tempBuffer, l, i === 0, options));
|
||||
|
||||
// layer records
|
||||
for (const layerData of layersData) {
|
||||
const { layer, top, left, bottom, right, channels } = layerData;
|
||||
|
||||
writeInt32(writer, top);
|
||||
writeInt32(writer, left);
|
||||
writeInt32(writer, bottom);
|
||||
writeInt32(writer, right);
|
||||
writeUint16(writer, channels.length);
|
||||
|
||||
for (const c of channels) {
|
||||
writeInt16(writer, c.channelId);
|
||||
if (options.psb) writeUint32(writer, 0);
|
||||
writeUint32(writer, c.length);
|
||||
}
|
||||
|
||||
writeSignature(writer, '8BIM');
|
||||
writeSignature(writer, fromBlendMode[layer.blendMode!] || 'norm');
|
||||
writeUint8(writer, Math.round(clamp(layer.opacity ?? 1, 0, 1) * 255));
|
||||
writeUint8(writer, layer.clipping ? 1 : 0);
|
||||
|
||||
let flags = 0x08; // 1 for Photoshop 5.0 and later, tells if bit 4 has useful information
|
||||
if (layer.transparencyProtected) flags |= 0x01;
|
||||
if (layer.hidden) flags |= 0x02;
|
||||
if (layer.vectorMask || (layer.sectionDivider && layer.sectionDivider.type !== SectionDividerType.Other)) {
|
||||
flags |= 0x10; // pixel data irrelevant to appearance of document
|
||||
}
|
||||
if (layer.effects && hasMultiEffects(layer.effects)) { // TODO: this is not correct
|
||||
flags |= 0x20; // just guessing this one, might be completely incorrect
|
||||
}
|
||||
// if ('_2' in layer) flags |= 0x20; // TEMP!!!
|
||||
|
||||
writeUint8(writer, flags);
|
||||
writeUint8(writer, 0); // filler
|
||||
writeSection(writer, 1, () => {
|
||||
writeLayerMaskData(writer, layer, layerData);
|
||||
writeLayerBlendingRanges(writer, psd);
|
||||
writePascalString(writer, layer.name || '', 4);
|
||||
writeAdditionalLayerInfo(writer, layer, psd, options);
|
||||
});
|
||||
}
|
||||
|
||||
// layer channel image data
|
||||
for (const layerData of layersData) {
|
||||
for (const channel of layerData.channels) {
|
||||
writeUint16(writer, channel.compression);
|
||||
|
||||
if (channel.buffer) {
|
||||
writeBytes(writer, channel.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, true, options.psb);
|
||||
}
|
||||
|
||||
function writeLayerMaskData(writer: PsdWriter, { mask }: Layer, layerData: LayerChannelData) {
|
||||
writeSection(writer, 1, () => {
|
||||
if (!mask) return;
|
||||
|
||||
const m = layerData.mask || {} as Partial<Bounds>;
|
||||
writeInt32(writer, m.top!);
|
||||
writeInt32(writer, m.left!);
|
||||
writeInt32(writer, m.bottom!);
|
||||
writeInt32(writer, m.right!);
|
||||
writeUint8(writer, mask.defaultColor!);
|
||||
|
||||
let params = 0;
|
||||
if (mask.userMaskDensity !== undefined) params |= MaskParams.UserMaskDensity;
|
||||
if (mask.userMaskFeather !== undefined) params |= MaskParams.UserMaskFeather;
|
||||
if (mask.vectorMaskDensity !== undefined) params |= MaskParams.VectorMaskDensity;
|
||||
if (mask.vectorMaskFeather !== undefined) params |= MaskParams.VectorMaskFeather;
|
||||
|
||||
let flags = 0;
|
||||
if (mask.disabled) flags |= LayerMaskFlags.LayerMaskDisabled;
|
||||
if (mask.positionRelativeToLayer) flags |= LayerMaskFlags.PositionRelativeToLayer;
|
||||
if (mask.fromVectorData) flags |= LayerMaskFlags.LayerMaskFromRenderingOtherData;
|
||||
if (params) flags |= LayerMaskFlags.MaskHasParametersAppliedToIt;
|
||||
|
||||
writeUint8(writer, flags);
|
||||
|
||||
if (params) {
|
||||
writeUint8(writer, params);
|
||||
|
||||
if (mask.userMaskDensity !== undefined) writeUint8(writer, Math.round(mask.userMaskDensity * 0xff));
|
||||
if (mask.userMaskFeather !== undefined) writeFloat64(writer, mask.userMaskFeather);
|
||||
if (mask.vectorMaskDensity !== undefined) writeUint8(writer, Math.round(mask.vectorMaskDensity * 0xff));
|
||||
if (mask.vectorMaskFeather !== undefined) writeFloat64(writer, mask.vectorMaskFeather);
|
||||
}
|
||||
|
||||
// TODO: handle rest of the fields
|
||||
|
||||
writeZeros(writer, 2);
|
||||
});
|
||||
}
|
||||
|
||||
function writeLayerBlendingRanges(writer: PsdWriter, psd: Psd) {
|
||||
writeSection(writer, 1, () => {
|
||||
writeUint32(writer, 65535);
|
||||
writeUint32(writer, 65535);
|
||||
|
||||
let channels = psd.channels || 0; // TODO: use always 4 instead ?
|
||||
// channels = 4; // TESTING
|
||||
|
||||
for (let i = 0; i < channels; i++) {
|
||||
writeUint32(writer, 65535);
|
||||
writeUint32(writer, 65535);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function writeGlobalLayerMaskInfo(writer: PsdWriter, info: GlobalLayerMaskInfo | undefined) {
|
||||
writeSection(writer, 1, () => {
|
||||
if (info) {
|
||||
writeUint16(writer, info.overlayColorSpace);
|
||||
writeUint16(writer, info.colorSpace1);
|
||||
writeUint16(writer, info.colorSpace2);
|
||||
writeUint16(writer, info.colorSpace3);
|
||||
writeUint16(writer, info.colorSpace4);
|
||||
writeUint16(writer, info.opacity * 0xff);
|
||||
writeUint8(writer, info.kind);
|
||||
writeZeros(writer, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function writeAdditionalLayerInfo(writer: PsdWriter, target: LayerAdditionalInfo, psd: Psd, options: ExtendedWriteOptions) {
|
||||
for (const handler of infoHandlers) {
|
||||
let key = handler.key;
|
||||
|
||||
if (key === 'Txt2' && options.invalidateTextLayers) continue;
|
||||
if (key === 'vmsk' && options.psb) key = 'vsms';
|
||||
|
||||
if (handler.has(target)) {
|
||||
const large = options.psb && largeAdditionalInfoKeys.indexOf(key) !== -1;
|
||||
|
||||
writeSignature(writer, large ? '8B64' : '8BIM');
|
||||
writeSignature(writer, key);
|
||||
|
||||
const fourBytes = key === 'Txt2' || key === 'luni' || key === 'vmsk' || key === 'artb' || key === 'artd' ||
|
||||
key === 'vogk' || key === 'SoLd' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
|
||||
key === 'lmfx' || key === 'lrFX' || key === 'cinf' || key === 'PlLd' || key === 'Anno';
|
||||
|
||||
writeSection(writer, fourBytes ? 4 : 2, () => {
|
||||
handler.write(writer, target, psd, options);
|
||||
}, key !== 'Txt2' && key !== 'cinf' && key !== 'extn', large);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addChildren(layers: Layer[], children: Layer[] | undefined) {
|
||||
if (!children) return;
|
||||
|
||||
for (const c of children) {
|
||||
if (c.children && c.canvas) throw new Error(`Invalid layer, cannot have both 'canvas' and 'children' properties`);
|
||||
if (c.children && c.imageData) throw new Error(`Invalid layer, cannot have both 'imageData' and 'children' properties`);
|
||||
|
||||
if (c.children) {
|
||||
layers.push({
|
||||
name: '</Layer group>',
|
||||
sectionDivider: {
|
||||
type: SectionDividerType.BoundingSectionDivider,
|
||||
},
|
||||
// TESTING
|
||||
// nameSource: 'lset',
|
||||
// id: [4, 0, 0, 8, 11, 0, 0, 0, 0, 14][layers.length] || 0,
|
||||
// layerColor: 'none',
|
||||
// timestamp: [1611346817.349021, 0, 0, 1611346817.349175, 1611346817.3491833, 0, 0, 0, 0, 1611346817.349832][layers.length] || 0,
|
||||
// protected: {},
|
||||
// referencePoint: { x: 0, y: 0 },
|
||||
});
|
||||
addChildren(layers, c.children);
|
||||
layers.push({
|
||||
sectionDivider: {
|
||||
type: c.opened === false ? SectionDividerType.ClosedFolder : SectionDividerType.OpenFolder,
|
||||
key: fromBlendMode[c.blendMode!] || 'pass',
|
||||
subType: 0,
|
||||
},
|
||||
...c,
|
||||
});
|
||||
} else {
|
||||
layers.push({ ...c });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resizeBuffer(writer: PsdWriter, size: number) {
|
||||
let newLength = writer.buffer.byteLength;
|
||||
|
||||
do {
|
||||
newLength *= 2;
|
||||
} while (size > newLength);
|
||||
|
||||
const newBuffer = new ArrayBuffer(newLength);
|
||||
const newBytes = new Uint8Array(newBuffer);
|
||||
const oldBytes = new Uint8Array(writer.buffer);
|
||||
newBytes.set(oldBytes);
|
||||
writer.buffer = newBuffer;
|
||||
writer.view = new DataView(writer.buffer);
|
||||
}
|
||||
|
||||
function ensureSize(writer: PsdWriter, size: number) {
|
||||
if (size > writer.buffer.byteLength) {
|
||||
resizeBuffer(writer, size);
|
||||
}
|
||||
}
|
||||
|
||||
function addSize(writer: PsdWriter, size: number) {
|
||||
const offset = writer.offset;
|
||||
ensureSize(writer, writer.offset += size);
|
||||
return offset;
|
||||
}
|
||||
|
||||
function createThumbnail(psd: Psd) {
|
||||
const canvas = createCanvas(10, 10);
|
||||
let scale = 1;
|
||||
|
||||
if (psd.width > psd.height) {
|
||||
canvas.width = 160;
|
||||
canvas.height = Math.floor(psd.height * (canvas.width / psd.width));
|
||||
scale = canvas.width / psd.width;
|
||||
} else {
|
||||
canvas.height = 160;
|
||||
canvas.width = Math.floor(psd.width * (canvas.height / psd.height));
|
||||
scale = canvas.height / psd.height;
|
||||
}
|
||||
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.scale(scale, scale);
|
||||
|
||||
if (psd.imageData) {
|
||||
const temp = createCanvas(psd.imageData.width, psd.imageData.height);
|
||||
temp.getContext('2d')!.putImageData(psd.imageData, 0, 0);
|
||||
context.drawImage(temp, 0, 0);
|
||||
} else if (psd.canvas) {
|
||||
context.drawImage(psd.canvas, 0, 0);
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function getChannels(
|
||||
tempBuffer: Uint8Array, layer: Layer, background: boolean, options: WriteOptions
|
||||
): LayerChannelData {
|
||||
const layerData = getLayerChannels(tempBuffer, layer, background, options);
|
||||
const mask = layer.mask;
|
||||
|
||||
if (mask) {
|
||||
let top = (mask.top as any) | 0;
|
||||
let left = (mask.left as any) | 0;
|
||||
let right = (mask.right as any) | 0;
|
||||
let bottom = (mask.bottom as any) | 0;
|
||||
let { width, height } = getLayerDimentions(mask);
|
||||
let imageData = mask.imageData;
|
||||
|
||||
if (!imageData && mask.canvas && width && height) {
|
||||
imageData = mask.canvas.getContext('2d')!.getImageData(0, 0, width, height);
|
||||
}
|
||||
|
||||
if (width && height && imageData) {
|
||||
right = left + width;
|
||||
bottom = top + height;
|
||||
|
||||
if (imageData.width !== width || imageData.height !== height) {
|
||||
throw new Error('Invalid imageData dimentions');
|
||||
}
|
||||
|
||||
let buffer: Uint8Array;
|
||||
let compression: Compression;
|
||||
|
||||
if (RAW_IMAGE_DATA && (layer as any).maskDataRaw) {
|
||||
// console.log('written raw layer image data');
|
||||
buffer = (layer as any).maskDataRaw;
|
||||
compression = Compression.RleCompressed;
|
||||
} else if (options.compress) {
|
||||
buffer = writeDataZipWithoutPrediction(imageData, [0]);
|
||||
compression = Compression.ZipWithoutPrediction;
|
||||
} else {
|
||||
buffer = writeDataRLE(tempBuffer, imageData, [0], !!options.psb)!;
|
||||
compression = Compression.RleCompressed;
|
||||
}
|
||||
|
||||
layerData.mask = { top, left, right, bottom };
|
||||
layerData.channels.push({ channelId: ChannelID.UserMask, compression, buffer, length: 2 + buffer.length });
|
||||
} else {
|
||||
layerData.mask = { top: 0, left: 0, right: 0, bottom: 0 };
|
||||
layerData.channels.push({ channelId: ChannelID.UserMask, compression: Compression.RawData, buffer: new Uint8Array(0), length: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
return layerData;
|
||||
}
|
||||
|
||||
function getLayerDimentions({ canvas, imageData }: Layer): { width: number; height: number; } {
|
||||
return imageData || canvas || { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
function cropImageData(data: ImageData, left: number, top: number, width: number, height: number) {
|
||||
const croppedData = createImageData(width, height);
|
||||
const srcData = data.data;
|
||||
const dstData = croppedData.data;
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
let src = ((x + left) + (y + top) * width) * 4;
|
||||
let dst = (x + y * width) * 4;
|
||||
dstData[dst] = srcData[src];
|
||||
dstData[dst + 1] = srcData[src + 1];
|
||||
dstData[dst + 2] = srcData[src + 2];
|
||||
dstData[dst + 3] = srcData[src + 3];
|
||||
}
|
||||
}
|
||||
|
||||
return croppedData;
|
||||
}
|
||||
|
||||
function getLayerChannels(
|
||||
tempBuffer: Uint8Array, layer: Layer, background: boolean, options: WriteOptions
|
||||
): LayerChannelData {
|
||||
let top = (layer.top as any) | 0;
|
||||
let left = (layer.left as any) | 0;
|
||||
let right = (layer.right as any) | 0;
|
||||
let bottom = (layer.bottom as any) | 0;
|
||||
let channels: ChannelData[] = [
|
||||
{ channelId: ChannelID.Transparency, compression: Compression.RawData, buffer: undefined, length: 2 },
|
||||
{ channelId: ChannelID.Color0, compression: Compression.RawData, buffer: undefined, length: 2 },
|
||||
{ channelId: ChannelID.Color1, compression: Compression.RawData, buffer: undefined, length: 2 },
|
||||
{ channelId: ChannelID.Color2, compression: Compression.RawData, buffer: undefined, length: 2 },
|
||||
];
|
||||
let { width, height } = getLayerDimentions(layer);
|
||||
|
||||
if (!(layer.canvas || layer.imageData) || !width || !height) {
|
||||
right = left;
|
||||
bottom = top;
|
||||
return { layer, top, left, right, bottom, channels };
|
||||
}
|
||||
|
||||
right = left + width;
|
||||
bottom = top + height;
|
||||
|
||||
let data = layer.imageData || layer.canvas!.getContext('2d')!.getImageData(0, 0, width, height);
|
||||
|
||||
if (options.trimImageData) {
|
||||
const trimmed = trimData(data);
|
||||
|
||||
if (trimmed.left !== 0 || trimmed.top !== 0 || trimmed.right !== data.width || trimmed.bottom !== data.height) {
|
||||
left += trimmed.left;
|
||||
top += trimmed.top;
|
||||
right -= (data.width - trimmed.right);
|
||||
bottom -= (data.height - trimmed.bottom);
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
|
||||
if (!width || !height) {
|
||||
return { layer, top, left, right, bottom, channels };
|
||||
}
|
||||
|
||||
if (layer.imageData) {
|
||||
data = cropImageData(data, trimmed.left, trimmed.top, width, height);
|
||||
} else {
|
||||
data = layer.canvas!.getContext('2d')!.getImageData(trimmed.left, trimmed.top, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const channelIds = [
|
||||
ChannelID.Color0,
|
||||
ChannelID.Color1,
|
||||
ChannelID.Color2,
|
||||
];
|
||||
|
||||
if (!background || options.noBackground || layer.mask || hasAlpha(data) || (RAW_IMAGE_DATA && (layer as any).imageDataRaw?.['-1'])) {
|
||||
channelIds.unshift(ChannelID.Transparency);
|
||||
}
|
||||
|
||||
channels = channelIds.map(channelId => {
|
||||
const offset = offsetForChannel(channelId, false); // TODO: psd.colorMode === ColorMode.CMYK);
|
||||
let buffer: Uint8Array;
|
||||
let compression: Compression;
|
||||
|
||||
if (RAW_IMAGE_DATA && (layer as any).imageDataRaw) {
|
||||
// console.log('written raw layer image data');
|
||||
buffer = (layer as any).imageDataRaw[channelId];
|
||||
compression = Compression.RleCompressed;
|
||||
} else if (options.compress) {
|
||||
buffer = writeDataZipWithoutPrediction(data, [offset]);
|
||||
compression = Compression.ZipWithoutPrediction;
|
||||
} else {
|
||||
buffer = writeDataRLE(tempBuffer, data, [offset], !!options.psb)!;
|
||||
compression = Compression.RleCompressed;
|
||||
}
|
||||
|
||||
return { channelId, compression, buffer, length: 2 + buffer.length };
|
||||
});
|
||||
|
||||
return { layer, top, left, right, bottom, channels };
|
||||
}
|
||||
|
||||
function isRowEmpty({ data, width }: PixelData, y: number, left: number, right: number) {
|
||||
const start = ((y * width + left) * 4 + 3) | 0;
|
||||
const end = (start + (right - left) * 4) | 0;
|
||||
|
||||
for (let i = start; i < end; i = (i + 4) | 0) {
|
||||
if (data[i] !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isColEmpty({ data, width }: PixelData, x: number, top: number, bottom: number) {
|
||||
const stride = (width * 4) | 0;
|
||||
const start = (top * stride + x * 4 + 3) | 0;
|
||||
|
||||
for (let y = top, i = start; y < bottom; y++, i = (i + stride) | 0) {
|
||||
if (data[i] !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function trimData(data: PixelData) {
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
let right = data.width;
|
||||
let bottom = data.height;
|
||||
|
||||
while (top < bottom && isRowEmpty(data, top, left, right))
|
||||
top++;
|
||||
while (bottom > top && isRowEmpty(data, bottom - 1, left, right))
|
||||
bottom--;
|
||||
while (left < right && isColEmpty(data, left, top, bottom))
|
||||
left++;
|
||||
while (right > left && isColEmpty(data, right - 1, top, bottom))
|
||||
right--;
|
||||
|
||||
return { top, left, right, bottom };
|
||||
}
|
||||
|
||||
export function writeColor(writer: PsdWriter, color: Color | undefined) {
|
||||
if (!color) {
|
||||
writeUint16(writer, ColorSpace.RGB);
|
||||
writeZeros(writer, 8);
|
||||
} else if ('r' in color) {
|
||||
writeUint16(writer, ColorSpace.RGB);
|
||||
writeUint16(writer, Math.round(color.r * 257));
|
||||
writeUint16(writer, Math.round(color.g * 257));
|
||||
writeUint16(writer, Math.round(color.b * 257));
|
||||
writeUint16(writer, 0);
|
||||
} else if ('fr' in color) {
|
||||
writeUint16(writer, ColorSpace.RGB);
|
||||
writeUint16(writer, Math.round(color.fr * 255 * 257));
|
||||
writeUint16(writer, Math.round(color.fg * 255 * 257));
|
||||
writeUint16(writer, Math.round(color.fb * 255 * 257));
|
||||
writeUint16(writer, 0);
|
||||
} else if ('l' in color) {
|
||||
writeUint16(writer, ColorSpace.Lab);
|
||||
writeInt16(writer, Math.round(color.l * 10000));
|
||||
writeInt16(writer, Math.round(color.a < 0 ? (color.a * 12800) : (color.a * 12700)));
|
||||
writeInt16(writer, Math.round(color.b < 0 ? (color.b * 12800) : (color.b * 12700)));
|
||||
writeUint16(writer, 0);
|
||||
} else if ('h' in color) {
|
||||
writeUint16(writer, ColorSpace.HSB);
|
||||
writeUint16(writer, Math.round(color.h * 0xffff));
|
||||
writeUint16(writer, Math.round(color.s * 0xffff));
|
||||
writeUint16(writer, Math.round(color.b * 0xffff));
|
||||
writeUint16(writer, 0);
|
||||
} else if ('c' in color) {
|
||||
writeUint16(writer, ColorSpace.CMYK);
|
||||
writeUint16(writer, Math.round(color.c * 257));
|
||||
writeUint16(writer, Math.round(color.m * 257));
|
||||
writeUint16(writer, Math.round(color.y * 257));
|
||||
writeUint16(writer, Math.round(color.k * 257));
|
||||
} else {
|
||||
writeUint16(writer, ColorSpace.Grayscale);
|
||||
writeUint16(writer, Math.round(color.k * 10000 / 255));
|
||||
writeZeros(writer, 6);
|
||||
}
|
||||
}
|
||||
752
npm-packages/mac-v2.4.x/ag-psd/src/text.ts
Normal file
752
npm-packages/mac-v2.4.x/ag-psd/src/text.ts
Normal file
@@ -0,0 +1,752 @@
|
||||
import { TextStyle, LayerTextData, ParagraphStyle, Font, AntiAlias, TextGridInfo, Justification, Color } from './psd';
|
||||
|
||||
interface Adjustments {
|
||||
Axis: number[];
|
||||
XY: number[];
|
||||
}
|
||||
|
||||
interface TypeValues {
|
||||
Type: number;
|
||||
Values: number[];
|
||||
}
|
||||
|
||||
interface ParagraphProperties {
|
||||
Justification?: number;
|
||||
FirstLineIndent?: number;
|
||||
StartIndent?: number;
|
||||
EndIndent?: number;
|
||||
SpaceBefore?: number;
|
||||
SpaceAfter?: number;
|
||||
AutoHyphenate?: boolean;
|
||||
HyphenatedWordSize?: number;
|
||||
PreHyphen?: number;
|
||||
PostHyphen?: number;
|
||||
ConsecutiveHyphens?: number;
|
||||
Zone?: number;
|
||||
WordSpacing?: number[];
|
||||
LetterSpacing?: number[];
|
||||
GlyphSpacing?: number[];
|
||||
AutoLeading?: number;
|
||||
LeadingType?: number;
|
||||
Hanging?: boolean;
|
||||
Burasagari?: boolean;
|
||||
KinsokuOrder?: number;
|
||||
EveryLineComposer?: boolean;
|
||||
}
|
||||
|
||||
interface ParagraphSheet {
|
||||
Name?: string;
|
||||
DefaultStyleSheet: number;
|
||||
Properties: ParagraphProperties;
|
||||
}
|
||||
|
||||
interface StyleSheetData {
|
||||
Font?: number;
|
||||
FontSize?: number;
|
||||
FauxBold?: boolean;
|
||||
FauxItalic?: boolean;
|
||||
AutoLeading?: boolean;
|
||||
Leading?: number;
|
||||
HorizontalScale?: number;
|
||||
VerticalScale?: number;
|
||||
Tracking?: number;
|
||||
AutoKerning?: boolean;
|
||||
Kerning?: number;
|
||||
BaselineShift?: number;
|
||||
FontCaps?: number;
|
||||
FontBaseline?: number;
|
||||
Underline?: boolean;
|
||||
Strikethrough?: boolean;
|
||||
Ligatures?: boolean;
|
||||
DLigatures?: boolean;
|
||||
BaselineDirection?: number;
|
||||
Tsume?: number;
|
||||
StyleRunAlignment?: number;
|
||||
Language?: number;
|
||||
NoBreak?: boolean;
|
||||
FillColor?: TypeValues;
|
||||
StrokeColor?: TypeValues;
|
||||
FillFlag?: boolean;
|
||||
StrokeFlag?: boolean;
|
||||
FillFirst?: boolean;
|
||||
YUnderline?: number;
|
||||
OutlineWidth?: number;
|
||||
CharacterDirection?: number;
|
||||
HindiNumbers?: boolean;
|
||||
Kashida?: number;
|
||||
DiacriticPos?: number;
|
||||
}
|
||||
|
||||
interface FontSet {
|
||||
Name: string;
|
||||
Script: number;
|
||||
FontType: number;
|
||||
Synthetic: number;
|
||||
}
|
||||
|
||||
interface ResourceDict {
|
||||
KinsokuSet: any[];
|
||||
MojiKumiSet: any[];
|
||||
TheNormalStyleSheet: number;
|
||||
TheNormalParagraphSheet: number;
|
||||
ParagraphSheetSet: ParagraphSheet[];
|
||||
StyleSheetSet: { Name: string; StyleSheetData: StyleSheetData; }[];
|
||||
FontSet: FontSet[];
|
||||
SuperscriptSize: number;
|
||||
SuperscriptPosition: number;
|
||||
SubscriptSize: number;
|
||||
SubscriptPosition: number;
|
||||
SmallCapSize: number;
|
||||
}
|
||||
|
||||
interface ParagraphRun {
|
||||
ParagraphSheet: ParagraphSheet;
|
||||
Adjustments: Adjustments;
|
||||
}
|
||||
|
||||
interface StyleRun {
|
||||
StyleSheet: { StyleSheetData: StyleSheetData; };
|
||||
}
|
||||
|
||||
interface PhotoshopNode {
|
||||
ShapeType?: number;
|
||||
PointBase?: number[];
|
||||
BoxBounds?: number[];
|
||||
Base?: {
|
||||
ShapeType: number;
|
||||
TransformPoint0: number[];
|
||||
TransformPoint1: number[];
|
||||
TransformPoint2: number[];
|
||||
};
|
||||
}
|
||||
|
||||
interface EngineData {
|
||||
EngineDict: {
|
||||
Editor: { Text: string; };
|
||||
ParagraphRun: {
|
||||
DefaultRunData: ParagraphRun;
|
||||
RunArray: ParagraphRun[];
|
||||
RunLengthArray: number[];
|
||||
IsJoinable: number;
|
||||
};
|
||||
StyleRun: {
|
||||
DefaultRunData: StyleRun;
|
||||
RunArray: StyleRun[];
|
||||
RunLengthArray: number[];
|
||||
IsJoinable: number;
|
||||
};
|
||||
GridInfo: {
|
||||
GridIsOn: boolean;
|
||||
ShowGrid: boolean;
|
||||
GridSize: number;
|
||||
GridLeading: number;
|
||||
GridColor: TypeValues;
|
||||
GridLeadingFillColor: TypeValues;
|
||||
AlignLineHeightToGridFlags: boolean;
|
||||
};
|
||||
AntiAlias: number;
|
||||
UseFractionalGlyphWidths: boolean;
|
||||
Rendered?: {
|
||||
Version: number;
|
||||
Shapes?: {
|
||||
WritingDirection: number;
|
||||
Children?: {
|
||||
ShapeType?: number;
|
||||
Procession: number;
|
||||
Lines: { WritingDirection: number; Children: any[]; };
|
||||
Cookie?: {
|
||||
Photoshop?: PhotoshopNode;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
};
|
||||
};
|
||||
ResourceDict: ResourceDict;
|
||||
DocumentResources: ResourceDict;
|
||||
}
|
||||
|
||||
const defaultFont: Font = {
|
||||
name: 'MyriadPro-Regular',
|
||||
script: 0,
|
||||
type: 0,
|
||||
synthetic: 0,
|
||||
};
|
||||
|
||||
const defaultParagraphStyle: ParagraphStyle = {
|
||||
justification: 'left',
|
||||
firstLineIndent: 0,
|
||||
startIndent: 0,
|
||||
endIndent: 0,
|
||||
spaceBefore: 0,
|
||||
spaceAfter: 0,
|
||||
autoHyphenate: true,
|
||||
hyphenatedWordSize: 6,
|
||||
preHyphen: 2,
|
||||
postHyphen: 2,
|
||||
consecutiveHyphens: 8,
|
||||
zone: 36,
|
||||
wordSpacing: [0.8, 1, 1.33],
|
||||
letterSpacing: [0, 0, 0],
|
||||
glyphSpacing: [1, 1, 1],
|
||||
autoLeading: 1.2,
|
||||
leadingType: 0,
|
||||
hanging: false,
|
||||
burasagari: false,
|
||||
kinsokuOrder: 0,
|
||||
everyLineComposer: false,
|
||||
};
|
||||
|
||||
const defaultStyle: TextStyle = {
|
||||
font: defaultFont,
|
||||
fontSize: 12,
|
||||
fauxBold: false,
|
||||
fauxItalic: false,
|
||||
autoLeading: true,
|
||||
leading: 0,
|
||||
horizontalScale: 1,
|
||||
verticalScale: 1,
|
||||
tracking: 0,
|
||||
autoKerning: true,
|
||||
kerning: 0,
|
||||
baselineShift: 0,
|
||||
fontCaps: 0,
|
||||
fontBaseline: 0,
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
ligatures: true,
|
||||
dLigatures: false,
|
||||
baselineDirection: 2,
|
||||
tsume: 0,
|
||||
styleRunAlignment: 2,
|
||||
language: 0,
|
||||
noBreak: false,
|
||||
fillColor: { r: 0, g: 0, b: 0 },
|
||||
strokeColor: { r: 0, g: 0, b: 0 },
|
||||
fillFlag: true,
|
||||
strokeFlag: false,
|
||||
fillFirst: true,
|
||||
yUnderline: 1,
|
||||
outlineWidth: 1,
|
||||
characterDirection: 0,
|
||||
hindiNumbers: false,
|
||||
kashida: 1,
|
||||
diacriticPos: 2,
|
||||
};
|
||||
|
||||
const defaultGridInfo: TextGridInfo = {
|
||||
isOn: false,
|
||||
show: false,
|
||||
size: 18,
|
||||
leading: 22,
|
||||
color: { r: 0, g: 0, b: 255 },
|
||||
leadingFillColor: { r: 0, g: 0, b: 255 },
|
||||
alignLineHeightToGridFlags: false,
|
||||
};
|
||||
|
||||
const paragraphStyleKeys: (keyof ParagraphStyle)[] = [
|
||||
'justification', 'firstLineIndent', 'startIndent', 'endIndent', 'spaceBefore', 'spaceAfter',
|
||||
'autoHyphenate', 'hyphenatedWordSize', 'preHyphen', 'postHyphen', 'consecutiveHyphens',
|
||||
'zone', 'wordSpacing', 'letterSpacing', 'glyphSpacing', 'autoLeading', 'leadingType',
|
||||
'hanging', 'burasagari', 'kinsokuOrder', 'everyLineComposer',
|
||||
];
|
||||
|
||||
const styleKeys: (keyof TextStyle)[] = [
|
||||
'font', 'fontSize', 'fauxBold', 'fauxItalic', 'autoLeading', 'leading', 'horizontalScale',
|
||||
'verticalScale', 'tracking', 'autoKerning', 'kerning', 'baselineShift', 'fontCaps', 'fontBaseline',
|
||||
'underline', 'strikethrough', 'ligatures', 'dLigatures', 'baselineDirection', 'tsume',
|
||||
'styleRunAlignment', 'language', 'noBreak', 'fillColor', 'strokeColor', 'fillFlag',
|
||||
'strokeFlag', 'fillFirst', 'yUnderline', 'outlineWidth', 'characterDirection', 'hindiNumbers',
|
||||
'kashida', 'diacriticPos',
|
||||
];
|
||||
|
||||
const antialias: AntiAlias[] = ['none', 'crisp', 'strong', 'smooth', 'sharp'];
|
||||
const justification: Justification[] = ['left', 'right', 'center'];
|
||||
|
||||
function upperFirst(value: string) {
|
||||
return value.substr(0, 1).toUpperCase() + value.substr(1);
|
||||
}
|
||||
|
||||
function decodeColor(color: { Type: number; Values: number[]; }): Color {
|
||||
const c = color.Values;
|
||||
|
||||
if (color.Type === 0) { // grayscale
|
||||
return { r: c[1] * 255, g: c[1] * 255, b: c[1] * 255 }; // , c[0] * 255];
|
||||
} else { // rgb
|
||||
return { r: c[1] * 255, g: c[2] * 255, b: c[3] * 255, a: c[0] }; // , c[0] * 255];
|
||||
}
|
||||
}
|
||||
|
||||
function encodeColor(color: Color | undefined) {
|
||||
if (color && 'r' in color) {
|
||||
return ['a' in color ? color.a : 1, color.r / 255, color.g / 255, color.b / 255];
|
||||
} else {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
function arraysEqual(a: any[], b: any[]) {
|
||||
if (!a || !b) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function objectsEqual(a: any, b: any) {
|
||||
if (!a || !b) return false;
|
||||
for (const key of Object.keys(a)) if (a[key] !== b[key]) return false;
|
||||
for (const key of Object.keys(b)) if (a[key] !== b[key]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function findOrAddFont(fonts: Font[], font: Font) {
|
||||
for (let i = 0; i < fonts.length; i++) {
|
||||
if (fonts[i].name === font.name) return i;
|
||||
}
|
||||
|
||||
fonts.push(font);
|
||||
return fonts.length - 1;
|
||||
}
|
||||
|
||||
function decodeObject(obj: any, keys: string[], fonts: Font[]) {
|
||||
const result: any = {};
|
||||
|
||||
for (const key of keys) {
|
||||
const Key = upperFirst(key);
|
||||
|
||||
if (obj[Key] === undefined) continue;
|
||||
|
||||
if (key === 'justification') {
|
||||
result[key] = justification[obj[Key]];
|
||||
} else if (key === 'font') {
|
||||
result[key] = fonts[obj[Key]];
|
||||
} else if (key === 'fillColor' || key === 'strokeColor') {
|
||||
result[key] = decodeColor(obj[Key]);
|
||||
} else {
|
||||
result[key] = obj[Key];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function encodeObject(obj: any, keys: string[], fonts: Font[]) {
|
||||
const result: any = {};
|
||||
|
||||
for (const key of keys) {
|
||||
const Key = upperFirst(key);
|
||||
|
||||
if (obj[key] === undefined) continue;
|
||||
|
||||
if (key === 'justification') {
|
||||
result[Key] = justification.indexOf(obj[key] ?? 'left');
|
||||
} else if (key === 'font') {
|
||||
result[Key] = findOrAddFont(fonts, obj[key]);
|
||||
} else if (key === 'fillColor' || key === 'strokeColor') {
|
||||
result[Key] = { Type: 1, Values: encodeColor(obj[key]) } as TypeValues;
|
||||
} else {
|
||||
result[Key] = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function decodeParagraphStyle(obj: ParagraphProperties, fonts: Font[]): ParagraphStyle {
|
||||
return decodeObject(obj, paragraphStyleKeys, fonts);
|
||||
}
|
||||
|
||||
function decodeStyle(obj: StyleSheetData, fonts: Font[]): TextStyle {
|
||||
return decodeObject(obj, styleKeys, fonts);
|
||||
}
|
||||
|
||||
function encodeParagraphStyle(obj: ParagraphStyle, fonts: Font[]): ParagraphProperties {
|
||||
return encodeObject(obj, paragraphStyleKeys, fonts);
|
||||
}
|
||||
|
||||
function encodeStyle(obj: TextStyle, fonts: Font[]): StyleSheetData {
|
||||
return encodeObject(obj, styleKeys, fonts);
|
||||
}
|
||||
|
||||
function deduplicateValues<T>(base: T, runs: { style: T; }[], keys: (keyof T)[]) {
|
||||
if (!runs.length) return;
|
||||
|
||||
for (const key of keys) {
|
||||
const value = runs[0].style[key];
|
||||
|
||||
if (value !== undefined) {
|
||||
let identical = false;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
identical = runs.every(r => arraysEqual(r.style[key] as any, value));
|
||||
} else if (typeof value === 'object') {
|
||||
identical = runs.every(r => objectsEqual(r.style[key] as any, value));
|
||||
} else {
|
||||
identical = runs.every(r => r.style[key] === value);
|
||||
}
|
||||
|
||||
if (identical) {
|
||||
base[key] = value as any;
|
||||
}
|
||||
}
|
||||
|
||||
const styleValue = base[key];
|
||||
|
||||
if (styleValue !== undefined) {
|
||||
for (const r of runs) {
|
||||
let same = false;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
same = arraysEqual(r.style[key] as any, value);
|
||||
} else if (typeof value === 'object') {
|
||||
same = objectsEqual(r.style[key] as any, value);
|
||||
} else {
|
||||
same = r.style[key] === value;
|
||||
}
|
||||
|
||||
if (same) delete r.style[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runs.every(x => Object.keys(x.style as any).length === 0)) {
|
||||
runs.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function decodeEngineData(engineData: EngineData) {
|
||||
// console.log('engineData', require('util').inspect(engineData, false, 99, true));
|
||||
const engineDict = engineData.EngineDict;
|
||||
const resourceDict = engineData.ResourceDict;
|
||||
|
||||
const fonts = resourceDict.FontSet.map<Font>(f => ({
|
||||
name: f.Name,
|
||||
script: f.Script,
|
||||
type: f.FontType,
|
||||
synthetic: f.Synthetic,
|
||||
}));
|
||||
|
||||
let text = engineDict.Editor.Text.replace(/\r/g, '\n');
|
||||
let removedCharacters = 0;
|
||||
|
||||
while (/\n$/.test(text)) {
|
||||
text = text.substr(0, text.length - 1);
|
||||
removedCharacters++;
|
||||
}
|
||||
|
||||
const result: LayerTextData = {
|
||||
text,
|
||||
antiAlias: antialias[engineDict.AntiAlias] ?? 'smooth',
|
||||
useFractionalGlyphWidths: !!engineDict.UseFractionalGlyphWidths,
|
||||
superscriptSize: resourceDict.SuperscriptSize,
|
||||
superscriptPosition: resourceDict.SuperscriptPosition,
|
||||
subscriptSize: resourceDict.SubscriptSize,
|
||||
subscriptPosition: resourceDict.SubscriptPosition,
|
||||
smallCapSize: resourceDict.SmallCapSize,
|
||||
};
|
||||
|
||||
// shape
|
||||
|
||||
const photoshop = engineDict.Rendered?.Shapes?.Children?.[0]?.Cookie?.Photoshop;
|
||||
|
||||
if (photoshop) {
|
||||
result.shapeType = photoshop.ShapeType === 1 ? 'box' : 'point';
|
||||
if (photoshop.PointBase) result.pointBase = photoshop.PointBase;
|
||||
if (photoshop.BoxBounds) result.boxBounds = photoshop.BoxBounds;
|
||||
}
|
||||
|
||||
// paragraph style
|
||||
|
||||
// const theNormalParagraphSheet = resourceDict.TheNormalParagraphSheet;
|
||||
// const paragraphSheetSet = resourceDict.ParagraphSheetSet;
|
||||
// const paragraphProperties = paragraphSheetSet[theNormalParagraphSheet].Properties;
|
||||
const paragraphRun = engineData.EngineDict.ParagraphRun;
|
||||
|
||||
result.paragraphStyle = {}; // decodeParagraphStyle(paragraphProperties, fonts);
|
||||
result.paragraphStyleRuns = [];
|
||||
|
||||
for (let i = 0; i < paragraphRun.RunArray.length; i++) {
|
||||
const run = paragraphRun.RunArray[i];
|
||||
const length = paragraphRun.RunLengthArray[i];
|
||||
const style = decodeParagraphStyle(run.ParagraphSheet.Properties, fonts);
|
||||
// const adjustments = {
|
||||
// axis: run.Adjustments.Axis,
|
||||
// xy: run.Adjustments.XY,
|
||||
// };
|
||||
result.paragraphStyleRuns.push({ length, style/*, adjustments*/ });
|
||||
}
|
||||
|
||||
for (let counter = removedCharacters; result.paragraphStyleRuns.length && counter > 0; counter--) {
|
||||
if (--result.paragraphStyleRuns[result.paragraphStyleRuns.length - 1].length === 0) {
|
||||
result.paragraphStyleRuns.pop();
|
||||
}
|
||||
}
|
||||
|
||||
deduplicateValues(result.paragraphStyle, result.paragraphStyleRuns, paragraphStyleKeys);
|
||||
|
||||
if (!result.paragraphStyleRuns.length) delete result.paragraphStyleRuns;
|
||||
|
||||
// style
|
||||
|
||||
// const theNormalStyleSheet = resourceDict.TheNormalStyleSheet;
|
||||
// const styleSheetSet = resourceDict.StyleSheetSet;
|
||||
// const styleSheetData = styleSheetSet[theNormalStyleSheet].StyleSheetData;
|
||||
const styleRun = engineData.EngineDict.StyleRun;
|
||||
|
||||
result.style = {}; // decodeStyle(styleSheetData, fonts);
|
||||
result.styleRuns = [];
|
||||
|
||||
for (let i = 0; i < styleRun.RunArray.length; i++) {
|
||||
const length = styleRun.RunLengthArray[i];
|
||||
const style = decodeStyle(styleRun.RunArray[i].StyleSheet.StyleSheetData, fonts);
|
||||
if (!style.font) style.font = fonts[0];
|
||||
result.styleRuns.push({ length, style });
|
||||
}
|
||||
|
||||
for (let counter = removedCharacters; result.styleRuns.length && counter > 0; counter--) {
|
||||
if (--result.styleRuns[result.styleRuns.length - 1].length === 0) {
|
||||
result.styleRuns.pop();
|
||||
}
|
||||
}
|
||||
|
||||
deduplicateValues(result.style, result.styleRuns, styleKeys);
|
||||
|
||||
if (!result.styleRuns.length) delete result.styleRuns;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function encodeEngineData(data: LayerTextData) {
|
||||
const text = `${(data.text || '').replace(/\r?\n/g, '\r')}\r`;
|
||||
|
||||
const fonts: Font[] = [
|
||||
{ name: 'AdobeInvisFont', script: 0, type: 0, synthetic: 0 },
|
||||
];
|
||||
|
||||
const defFont = data.style?.font || data.styleRuns?.find(s => s.style.font)?.style.font || defaultFont;
|
||||
const paragraphRunArray: ParagraphRun[] = [];
|
||||
const paragraphRunLengthArray: number[] = [];
|
||||
const paragraphRuns = data.paragraphStyleRuns;
|
||||
|
||||
if (paragraphRuns && paragraphRuns.length) {
|
||||
let leftLength = text.length;
|
||||
|
||||
for (const run of paragraphRuns) {
|
||||
let runLength = Math.min(run.length, leftLength);
|
||||
leftLength -= runLength;
|
||||
|
||||
if (!runLength) continue; // ignore 0 size runs
|
||||
|
||||
// extend last run if it's only for trailing \r
|
||||
if (leftLength === 1 && run === paragraphRuns[paragraphRuns.length - 1]) {
|
||||
runLength++;
|
||||
leftLength--;
|
||||
}
|
||||
|
||||
paragraphRunLengthArray.push(runLength);
|
||||
paragraphRunArray.push({
|
||||
ParagraphSheet: {
|
||||
DefaultStyleSheet: 0,
|
||||
Properties: encodeParagraphStyle({ ...defaultParagraphStyle, ...data.paragraphStyle, ...run.style }, fonts),
|
||||
},
|
||||
Adjustments: { Axis: [1, 0, 1], XY: [0, 0] },
|
||||
});
|
||||
}
|
||||
|
||||
if (leftLength) {
|
||||
paragraphRunLengthArray.push(leftLength);
|
||||
paragraphRunArray.push({
|
||||
ParagraphSheet: {
|
||||
DefaultStyleSheet: 0,
|
||||
Properties: encodeParagraphStyle({ ...defaultParagraphStyle, ...data.paragraphStyle }, fonts),
|
||||
},
|
||||
Adjustments: { Axis: [1, 0, 1], XY: [0, 0] },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (let i = 0, last = 0; i < text.length; i++) {
|
||||
if (text.charCodeAt(i) === 13) { // \r
|
||||
paragraphRunLengthArray.push(i - last + 1);
|
||||
paragraphRunArray.push({
|
||||
ParagraphSheet: {
|
||||
DefaultStyleSheet: 0,
|
||||
Properties: encodeParagraphStyle({ ...defaultParagraphStyle, ...data.paragraphStyle }, fonts),
|
||||
},
|
||||
Adjustments: { Axis: [1, 0, 1], XY: [0, 0] },
|
||||
});
|
||||
last = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styleSheetData = encodeStyle({ ...defaultStyle, font: defFont }, fonts);
|
||||
const styleRuns = data.styleRuns || [{ length: text.length, style: data.style || {} }];
|
||||
const styleRunArray: StyleRun[] = [];
|
||||
const styleRunLengthArray: number[] = [];
|
||||
|
||||
let leftLength = text.length;
|
||||
|
||||
for (const run of styleRuns) {
|
||||
let runLength = Math.min(run.length, leftLength);
|
||||
leftLength -= runLength;
|
||||
|
||||
if (!runLength) continue; // ignore 0 size runs
|
||||
|
||||
// extend last run if it's only for trailing \r
|
||||
if (leftLength === 1 && run === styleRuns[styleRuns.length - 1]) {
|
||||
runLength++;
|
||||
leftLength--;
|
||||
}
|
||||
|
||||
styleRunLengthArray.push(runLength);
|
||||
styleRunArray.push({
|
||||
StyleSheet: {
|
||||
StyleSheetData: encodeStyle({
|
||||
kerning: 0,
|
||||
autoKerning: true,
|
||||
fillColor: { r: 0, g: 0, b: 0 },
|
||||
...data.style,
|
||||
...run.style,
|
||||
}, fonts),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// add extra run to the end if existing ones didn't fill it up
|
||||
if (leftLength && styleRuns.length) {
|
||||
styleRunLengthArray.push(leftLength);
|
||||
styleRunArray.push({
|
||||
StyleSheet: {
|
||||
StyleSheetData: encodeStyle({
|
||||
kerning: 0,
|
||||
autoKerning: true,
|
||||
fillColor: { r: 0, g: 0, b: 0 },
|
||||
...data.style,
|
||||
}, fonts),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const gridInfo = { ...defaultGridInfo, ...data.gridInfo };
|
||||
const WritingDirection = data.orientation === 'vertical' ? 2 : 0;
|
||||
const Procession = data.orientation === 'vertical' ? 1 : 0;
|
||||
const ShapeType = data.shapeType === 'box' ? 1 : 0;
|
||||
const Photoshop: PhotoshopNode = {
|
||||
ShapeType,
|
||||
};
|
||||
|
||||
if (ShapeType === 0) {
|
||||
Photoshop.PointBase = data.pointBase || [0, 0];
|
||||
} else {
|
||||
Photoshop.BoxBounds = data.boxBounds || [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
// needed for correct order of properties
|
||||
Photoshop.Base = {
|
||||
ShapeType,
|
||||
TransformPoint0: [1, 0],
|
||||
TransformPoint1: [0, 1],
|
||||
TransformPoint2: [0, 0],
|
||||
};
|
||||
|
||||
const defaultResources = {
|
||||
KinsokuSet: [
|
||||
{
|
||||
Name: 'PhotoshopKinsokuHard',
|
||||
NoStart: '、。,.・:;?!ー―’”)〕]}〉》」』】ヽヾゝゞ々ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ゛゜?!)]},.:;℃℉¢%‰',
|
||||
NoEnd: '‘“(〔[{〈《「『【([{¥$£@§〒#',
|
||||
Keep: '―‥',
|
||||
Hanging: '、。.,',
|
||||
},
|
||||
{
|
||||
Name: 'PhotoshopKinsokuSoft',
|
||||
NoStart: '、。,.・:;?!’”)〕]}〉》」』】ヽヾゝゞ々',
|
||||
NoEnd: '‘“(〔[{〈《「『【',
|
||||
Keep: '―‥',
|
||||
Hanging: '、。.,',
|
||||
},
|
||||
],
|
||||
MojiKumiSet: [
|
||||
{ InternalName: 'Photoshop6MojiKumiSet1' },
|
||||
{ InternalName: 'Photoshop6MojiKumiSet2' },
|
||||
{ InternalName: 'Photoshop6MojiKumiSet3' },
|
||||
{ InternalName: 'Photoshop6MojiKumiSet4' },
|
||||
],
|
||||
TheNormalStyleSheet: 0,
|
||||
TheNormalParagraphSheet: 0,
|
||||
ParagraphSheetSet: [
|
||||
{
|
||||
Name: 'Normal RGB',
|
||||
DefaultStyleSheet: 0,
|
||||
Properties: encodeParagraphStyle({ ...defaultParagraphStyle, ...data.paragraphStyle }, fonts),
|
||||
},
|
||||
],
|
||||
StyleSheetSet: [
|
||||
{
|
||||
Name: 'Normal RGB',
|
||||
StyleSheetData: styleSheetData,
|
||||
},
|
||||
],
|
||||
FontSet: fonts.map<FontSet>(f => ({
|
||||
Name: f.name,
|
||||
Script: f.script || 0,
|
||||
FontType: f.type || 0,
|
||||
Synthetic: f.synthetic || 0,
|
||||
})),
|
||||
SuperscriptSize: data.superscriptSize ?? 0.583,
|
||||
SuperscriptPosition: data.superscriptPosition ?? 0.333,
|
||||
SubscriptSize: data.subscriptSize ?? 0.583,
|
||||
SubscriptPosition: data.subscriptPosition ?? 0.333,
|
||||
SmallCapSize: data.smallCapSize ?? 0.7,
|
||||
};
|
||||
|
||||
const engineData: EngineData = {
|
||||
EngineDict: {
|
||||
Editor: { Text: text },
|
||||
ParagraphRun: {
|
||||
DefaultRunData: {
|
||||
ParagraphSheet: { DefaultStyleSheet: 0, Properties: {} },
|
||||
Adjustments: { Axis: [1, 0, 1], XY: [0, 0] },
|
||||
},
|
||||
RunArray: paragraphRunArray,
|
||||
RunLengthArray: paragraphRunLengthArray,
|
||||
IsJoinable: 1,
|
||||
},
|
||||
StyleRun: {
|
||||
DefaultRunData: { StyleSheet: { StyleSheetData: {} } },
|
||||
RunArray: styleRunArray,
|
||||
RunLengthArray: styleRunLengthArray,
|
||||
IsJoinable: 2,
|
||||
},
|
||||
GridInfo: {
|
||||
GridIsOn: !!gridInfo.isOn,
|
||||
ShowGrid: !!gridInfo.show,
|
||||
GridSize: gridInfo.size ?? 18,
|
||||
GridLeading: gridInfo.leading ?? 22,
|
||||
GridColor: { Type: 1, Values: encodeColor(gridInfo.color) },
|
||||
GridLeadingFillColor: { Type: 1, Values: encodeColor(gridInfo.color) },
|
||||
AlignLineHeightToGridFlags: !!gridInfo.alignLineHeightToGridFlags,
|
||||
},
|
||||
AntiAlias: antialias.indexOf(data.antiAlias ?? 'sharp'),
|
||||
UseFractionalGlyphWidths: data.useFractionalGlyphWidths ?? true,
|
||||
Rendered: {
|
||||
Version: 1,
|
||||
Shapes: {
|
||||
WritingDirection,
|
||||
Children: [
|
||||
{
|
||||
ShapeType,
|
||||
Procession,
|
||||
Lines: { WritingDirection, Children: [] },
|
||||
Cookie: { Photoshop },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
ResourceDict: { ...defaultResources },
|
||||
DocumentResources: { ...defaultResources },
|
||||
};
|
||||
|
||||
// console.log('encodeEngineData', require('util').inspect(engineData, false, 99, true));
|
||||
return engineData;
|
||||
}
|
||||
160
npm-packages/mac-v2.4.x/ag-psd/src/utf8.ts
Normal file
160
npm-packages/mac-v2.4.x/ag-psd/src/utf8.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
function charLengthInBytes(code: number): number {
|
||||
if ((code & 0xffffff80) === 0) {
|
||||
return 1;
|
||||
} else if ((code & 0xfffff800) === 0) {
|
||||
return 2;
|
||||
} else if ((code & 0xffff0000) === 0) {
|
||||
return 3;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
export function stringLengthInBytes(value: string): number {
|
||||
let result = 0;
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const code = value.charCodeAt(i);
|
||||
|
||||
// high surrogate
|
||||
if (code >= 0xd800 && code <= 0xdbff) {
|
||||
if ((i + 1) < value.length) {
|
||||
const extra = value.charCodeAt(i + 1);
|
||||
|
||||
// low surrogate
|
||||
if ((extra & 0xfc00) === 0xdc00) {
|
||||
i++;
|
||||
result += charLengthInBytes(((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += charLengthInBytes(code);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function writeCharacter(buffer: Uint8Array | Buffer, offset: number, code: number): number {
|
||||
const length = charLengthInBytes(code);
|
||||
|
||||
switch (length) {
|
||||
case 1:
|
||||
buffer[offset] = code;
|
||||
break;
|
||||
case 2:
|
||||
buffer[offset] = ((code >> 6) & 0x1f) | 0xc0;
|
||||
buffer[offset + 1] = (code & 0x3f) | 0x80;
|
||||
break;
|
||||
case 3:
|
||||
buffer[offset] = ((code >> 12) & 0x0f) | 0xe0;
|
||||
buffer[offset + 1] = ((code >> 6) & 0x3f) | 0x80;
|
||||
buffer[offset + 2] = (code & 0x3f) | 0x80;
|
||||
break;
|
||||
default:
|
||||
buffer[offset] = ((code >> 18) & 0x07) | 0xf0;
|
||||
buffer[offset + 1] = ((code >> 12) & 0x3f) | 0x80;
|
||||
buffer[offset + 2] = ((code >> 6) & 0x3f) | 0x80;
|
||||
buffer[offset + 3] = (code & 0x3f) | 0x80;
|
||||
break;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
export function encodeStringTo(buffer: Uint8Array | Buffer, offset: number, value: string): number {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const code = value.charCodeAt(i);
|
||||
|
||||
// high surrogate
|
||||
if (code >= 0xd800 && code <= 0xdbff) {
|
||||
if ((i + 1) < value.length) {
|
||||
const extra = value.charCodeAt(i + 1);
|
||||
|
||||
// low surrogate
|
||||
if ((extra & 0xfc00) === 0xdc00) {
|
||||
i++;
|
||||
const fullCode = ((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
|
||||
offset += writeCharacter(buffer, offset, fullCode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
offset += writeCharacter(buffer, offset, code);
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
export function encodeString(value: string): Uint8Array {
|
||||
const buffer = new Uint8Array(stringLengthInBytes(value));
|
||||
encodeStringTo(buffer, 0, value);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function continuationByte(buffer: Uint8Array, index: number): number {
|
||||
if (index >= buffer.length) {
|
||||
throw Error('Invalid byte index');
|
||||
}
|
||||
|
||||
const continuationByte = buffer[index];
|
||||
|
||||
if ((continuationByte & 0xC0) === 0x80) {
|
||||
return continuationByte & 0x3F;
|
||||
} else {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
}
|
||||
|
||||
export function decodeString(value: Uint8Array): string {
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < value.length;) {
|
||||
const byte1 = value[i++];
|
||||
let code: number;
|
||||
|
||||
if ((byte1 & 0x80) === 0) {
|
||||
code = byte1;
|
||||
} else if ((byte1 & 0xe0) === 0xc0) {
|
||||
const byte2 = continuationByte(value, i++);
|
||||
code = ((byte1 & 0x1f) << 6) | byte2;
|
||||
|
||||
if (code < 0x80) {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
} else if ((byte1 & 0xf0) === 0xe0) {
|
||||
const byte2 = continuationByte(value, i++);
|
||||
const byte3 = continuationByte(value, i++);
|
||||
code = ((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3;
|
||||
|
||||
if (code < 0x0800) {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
|
||||
if (code >= 0xd800 && code <= 0xdfff) {
|
||||
throw Error(`Lone surrogate U+${code.toString(16).toUpperCase()} is not a scalar value`);
|
||||
}
|
||||
} else if ((byte1 & 0xf8) === 0xf0) {
|
||||
const byte2 = continuationByte(value, i++);
|
||||
const byte3 = continuationByte(value, i++);
|
||||
const byte4 = continuationByte(value, i++);
|
||||
code = ((byte1 & 0x0f) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
|
||||
|
||||
if (code < 0x010000 || code > 0x10ffff) {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
} else {
|
||||
throw Error('Invalid UTF-8 detected');
|
||||
}
|
||||
|
||||
if (code > 0xffff) {
|
||||
code -= 0x10000;
|
||||
result += String.fromCharCode(code >>> 10 & 0x3ff | 0xd800);
|
||||
code = 0xdc00 | code & 0x3ff;
|
||||
}
|
||||
|
||||
result += String.fromCharCode(code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user