From cf9711d54bac2634440cec0defdab150d14048a3 Mon Sep 17 00:00:00 2001 From: quark Date: Thu, 6 Feb 2025 16:56:20 +0800 Subject: [PATCH] feat: add base support for path text --- src/engineData2.ts | 66 +++++++++++++++++++++++++++++++++++++++++++--- src/psd.ts | 12 +++++++++ src/psdReader.ts | 37 ++++++++++++++++++++++++++ src/text.ts | 3 ++- 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/engineData2.ts b/src/engineData2.ts index 2779b8b..f8fd2a0 100644 --- a/src/engineData2.ts +++ b/src/engineData2.ts @@ -1,6 +1,8 @@ /// Engine data 2 experiments // /test/engineData2.json:1109 is character codes +import { EngineData } from './text'; + interface KeysDict { [key: string]: { name?: string; @@ -127,7 +129,7 @@ const keysRoot: KeysDict = { }, }, }, - } + }, }, }, }, @@ -217,8 +219,64 @@ const keysRoot: KeysDict = { }, }, '8': { - name: '8', - children: {}, + name: 'TextFrameSet', + children: { + '0': { + uproot: true, + children: { + '0': { + name: 'TextPath', + children: { + '0': { name: 'Name' }, + '1': { + name: 'BezierCurve', + children: { + '0': { name: 'ControlPoints' }, + }, + }, + '2': { + name: 'Data', + children: { + '0': { name: 'Type' }, + '1': { name: 'Orientation' }, + '2': { name: 'FrameMatrix' }, + '4': { name: '4' }, + '6': { name: 'TextRange' }, + '7': { name: 'RowGutter' }, + '8': { name: 'ColumnGutter' }, + '9': { name: '9' }, + '10': { + name: 'BaselineAlignment', + children: { + '0': { name: 'Flag' }, + '1': { name: 'Min' }, + }, + }, + '11': { + name: 'PathData', + children: { + '1': { name: '1' }, + '0': { name: 'Reversed' }, + '2': { name: '2' }, + '3': { name: '3' }, + '4': { name: 'Spacing' }, + '5': { name: '5' }, + '6': { name: '6' }, + '7': { name: '7' }, + '18': { name: '18' }, + }, + }, + '12': { name: '12' }, + '13': { name: '13' }, + }, + }, + '3': { name: '3' }, + '97': { name: 'UUID' }, + }, + }, + }, + }, + }, }, '9': { name: 'Predefined', @@ -336,7 +394,7 @@ const keysRoot: KeysDict = { }, }; -function decodeObj(obj: any, keys: KeysDict): any { +function decodeObj(obj: EngineData, keys: KeysDict): any { if (obj === null) return obj; if (Array.isArray(obj)) return obj.map(x => decodeObj(x, keys)); if (typeof obj !== 'object') return obj; diff --git a/src/psd.ts b/src/psd.ts index af93784..01cfab9 100644 --- a/src/psd.ts +++ b/src/psd.ts @@ -427,6 +427,18 @@ export interface LayerTextData { boxBounds?: number[]; bounds?: UnitsBounds; boundingBox?: UnitsBounds; + + textPath?: { + BezierCurve?: { + /**the length number should be multiples of 8, which is present a bezier curve */ + ControlPoints: number[]; + }, + Data: { + Type: number; + FrameMatrix: number[]; + TextRange: number[]; + } + } } export interface PatternInfo { diff --git a/src/psdReader.ts b/src/psdReader.ts index 9e37dd1..a64c04f 100644 --- a/src/psdReader.ts +++ b/src/psdReader.ts @@ -3,6 +3,10 @@ import { Psd, Layer, ColorMode, SectionDividerType, LayerAdditionalInfo, ReadOpt import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, ChannelID, Compression, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas } from './helpers'; import { infoHandlersMap } from './additionalInfo'; import { resourceHandlersMap } from './imageResources'; +import { parseEngineData } from './engineData'; +import { toByteArray } from 'base64-js'; +import { decodeEngineData2 } from './engineData2'; +import type { EngineData } from './text'; interface ChannelInfo { id: ChannelID; @@ -340,6 +344,14 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) { // but add option to preserve file color mode (need to return image data instead of canvas in that case) // psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB + if(psd.engineData){ + const byteArray = toByteArray(psd.engineData); + const engineData = parseEngineData(byteArray); + const parsedEngineData = decodeEngineData2(engineData); + + assignGlobalEngineData(psd.children, parsedEngineData); + } + return psd; } @@ -690,6 +702,31 @@ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAddition }, false, u64); } +/** + * There is a Global text engine data outside text element. + * So, we need to pick global engine data and set to per text element. + */ +function assignGlobalEngineData(layers: Layer[] | undefined, globalEngineData: EngineData) { + const resources = globalEngineData?.ResourceDict?.TextFrameSet; + + if (!resources || Object.keys(resources).length === 0 || !layers?.length) { + return; + } + const resourceLength = Object.keys(resources).length; + let textIndex = 0; + layers.forEach((layer) => { + const isText = !!layer.text; + if (isText) { + if (textIndex < resourceLength) { + const resource = resources[textIndex++]; + layer.text!.textPath = resource.TextPath; + } else { + console.warn('Not enough resources for all text layers'); + } + } + }); +} + function createImageDataBitDepth(width: number, height: number, bitDepth: number): PixelData { if (bitDepth === 1 || bitDepth === 8) { return createImageData(width, height); diff --git a/src/text.ts b/src/text.ts index 5da06b7..9dc0170 100644 --- a/src/text.ts +++ b/src/text.ts @@ -97,6 +97,7 @@ interface ResourceDict { SubscriptSize: number; SubscriptPosition: number; SmallCapSize: number; + TextFrameSet?: any[]; } interface ParagraphRun { @@ -120,7 +121,7 @@ interface PhotoshopNode { }; } -interface EngineData { +export interface EngineData { EngineDict: { Editor: { Text: string; }; ParagraphRun: {