Skip to content

use opendesign/psd-ts as webtoon/psd #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/octopus-psd/examples/node/convert-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async function convertDir(dirPath: string) {
}
}

async function convert(locations: string[]) {
export async function convert(locations: string[]) {
for (const location of locations) {
if (await isDirectory(location)) {
await convertDir(location)
Expand Down
7 changes: 5 additions & 2 deletions packages/octopus-psd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"clean": "rimraf ./node_modules ./lib ./dist ./workdir ./test/integration/report ./jsdoc",
"convert:debug": "yarn build && node ./lib/examples/node/convert-debug.js",
"convert:local": "yarn build && node ./lib/examples/node/convert-local.js",
"convert:bulk": "yarn clean:workdir && yarn build && node ./lib/examples/node/convert-bulk.js",
"prepack": "rimraf ./lib && yarn run build",
"test": "yarn test:unit && yarn test:integration",
"test:unit": "yarn build && yarn node --experimental-vm-modules $(yarn bin jest)",
Expand All @@ -20,18 +21,18 @@
"watch": "tsc -w"
},
"dependencies": {
"@avocode/psd-parser": "0.8.4",
"@webtoon/psd": "npm:@opendesign/[email protected]",
"@opendesign/manifest-ts": "3.0.0-alpha.38",
"@opendesign/octopus-common": "3.0.0-rc.27",
"@opendesign/octopus-ts": "3.0.0-alpha.38",
"@types/chalk": "^2.2.0",
"@types/lodash": "^4.14.178",
"@types/pino": "^6.3.3",
"@types/rimraf": "^3.0.2",
"@types/uuid": "^8.3.1",
"chalk": "^4.1.2",
"dotenv": "16.0.0",
"image-size": "^1.0.1",
"jimp": "^0.16.2",
"lodash": "^4.17.21",
"paper": "0.12.15",
"pino": "^6.7.0",
Expand All @@ -41,6 +42,8 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/pino-pretty": "4.7.5",
"@types/pino-std-serializers": "2.4.1",
"@types/jest": "^27.4.0",
"@types/node": "*",
"handlebars": "^4.7.7",
Expand Down
Binary file added packages/octopus-psd/pattern-01.psd
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class OctopusComponent {
constructor(options: OctopusComponentOptions) {
this._sourceComponent = options.sourceComponent
this._designConverter = options.designConverter

this._layers = createOctopusLayers(this.sourceComponent.layers, this)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export class OctopusEffectBevelEmboss extends OctopusEffectBase {
protected _parentLayer: OctopusLayerBase
private _bevelEmboss: SourceEffectBevelEmboss

static BEVEL_EMBOSS_TYPE_MAP = {
OtrB: 'outerBevel',
InrB: 'innerBevel',
Embs: 'emboss',
PlEb: 'pillowEmboss',
strokeEmboss: 'strokeEmboss',
} as const

constructor(options: OctopusEffectBevelEmbossOptions) {
super(options)
this._parentLayer = options.parentLayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export class OctopusEffectFillGradient {
private _isStroke: boolean

static GRADIENT_TYPE_MAP = {
linear: 'LINEAR',
radial: 'RADIAL',
Lnr: 'LINEAR',
Rdl: 'RADIAL',
Angl: 'ANGULAR',
Dmnd: 'DIAMOND',
reflected: 'REFLECTED',
Rflc: 'REFLECTED',
} as const

constructor(options: OctopusFillGradientOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class OctopusEffectFill {
}

private get _imageName(): string {
return `${this._fill?.pattern?.ID}.png`
return `${this._fill?.pattern?.Idnt}.png`
}

private get _imagePath(): string | undefined {
Expand All @@ -66,7 +66,7 @@ export class OctopusEffectFill {
const image = this._image
const { width, height } = image ?? {}
if (width === undefined || height === undefined) {
logger.warn('Unknown image', { image, id: this._fill?.pattern?.ID })
logger.warn('Unknown image', { image, id: this._fill?.pattern?.Idnt })
return null
}
const matrix = createMatrix(width, 0, 0, height, ...this._offset)
Expand All @@ -77,7 +77,7 @@ export class OctopusEffectFill {

convert(): Octopus['Fill'] | null {
const fill = this._fill
if (!fill.enabled) return null
if (!fill?.enabled) return null
switch (this.fillType) {
case 'GRADIENT': {
const parentLayer = this._parentLayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class OctopusEffectOverlayPattern extends OctopusEffectBase {
}

private get _imageName(): string {
return `${this._fill?.pattern?.ID}.png`
return `${this._fill?.pattern?.Idnt}.png`
}

private get _imagePath(): string | undefined {
Expand All @@ -63,7 +63,7 @@ export class OctopusEffectOverlayPattern extends OctopusEffectBase {
const image = this._image
const { width, height } = image ?? {}
if (width === undefined || height === undefined) {
logger.warn('Unknown image', { image, id: this._fill?.pattern?.ID })
logger.warn('Unknown image', { image, id: this._fill?.pattern?.Idnt })
return null
}
const matrix = createMatrix(width, 0, 0, height, ...this._offset)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { OctopusLayerShape } from './octopus-layer-shape.js'
import type { OctopusLayer } from '../../factories/create-octopus-layer'
import type { SourceLayer } from '../../factories/create-source-layer'
import type { Octopus } from '../../typings/octopus'
import type { RawLayerLayer, RawLayerShape, RawPath } from '../../typings/raw'
import type { RawLayerLayer, RawLayerShape } from '../../typings/raw'
import type { SourceBounds, SourceColor } from '../../typings/source'
import type { SourceLayerLayer } from '../source/source-layer-layer'
import type { SourceLayerShape } from '../source/source-layer-shape'
Expand Down Expand Up @@ -95,9 +95,24 @@ export class OctopusLayerMaskGroup {
if (!bitmapMask) return octopusLayer
const { width, height } = octopusLayer.parentComponent.dimensions
const bounds = { left: 0, right: width, top: 0, bottom: height }
const raw: RawLayerLayer = { type: 'layer', bitmapBounds: bounds, bounds, visible: false, imageName: bitmapMask }
const maskSourceLayer = createSourceLayer({ layer: raw, parent: sourceLayer?.parent }) as SourceLayerLayer

const raw: RawLayerLayer = {
addedType: 'layer',
width: bounds.right - bounds.left,
height: bounds.bottom - bounds.top,
top: bounds.top,
left: bounds.left,
isHidden: true,
parsedProperties: {
lyid: bitmapMask.replace('.png', ''),
},
}
const maskSourceLayer = createSourceLayer({
layer: raw,
parent: sourceLayer?.parent,
}) as SourceLayerLayer
const maskAdapter = new OctopusLayerShapeLayerAdapter({ parent, sourceLayer: maskSourceLayer })

const mask = new OctopusLayerShape({ parent, sourceLayer, adapter: maskAdapter })
return new OctopusLayerMaskGroup({
parent,
Expand All @@ -115,10 +130,20 @@ export class OctopusLayerMaskGroup {
parent,
}: CreateWrapMaskOptions<T>): OctopusLayerMaskGroup | T {
const path = sourceLayer.path

if (!path) return octopusLayer
const raw: RawLayerShape = { type: 'shapeLayer', visible: false, path: path.raw as RawPath }
const maskSourceLayer = createSourceLayer({ layer: raw, parent: sourceLayer?.parent }) as SourceLayerShape
const raw: RawLayerShape = {
addedType: 'shapeLayer',
isHidden: true,
parsedProperties: { vmsk: path.vectorMaskSetting, vogk: path.vectorOriginationData },
}

const maskSourceLayer = createSourceLayer({
layer: raw,
parent: sourceLayer?.parent,
}) as SourceLayerShape
const maskAdapter = new OctopusLayerShapeShapeAdapter({ parent, sourceLayer: maskSourceLayer })

const mask = new OctopusLayerShape({ parent, sourceLayer, adapter: maskAdapter })
const id = `${octopusLayer.id}-ShapeMask`
return new OctopusLayerMaskGroup({ id, parent, mask, maskBasis: 'BODY', layers: [octopusLayer] })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class OctopusLayerShapeShapePath {
private _getPathRectangle(pathComponents: SourcePathComponent[]): Octopus['PathRectangle'] {
const rect = pathComponents[0]
const { bottom, left, right, top } = rect.origin.bounds

const [layerTx, layerTy] = this._parentLayer.layerTranslation
const tx = left - layerTx
const ty = top - layerTy
Expand Down
40 changes: 33 additions & 7 deletions packages/octopus-psd/src/entities/octopus/octopus-manifest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import path from 'path'

import { asString } from '@opendesign/octopus-common/dist/utils/as.js'
import { traverseAndFind } from '@opendesign/octopus-common/dist/utils/common.js'
import { asArray, asString } from '@opendesign/octopus-common/dist/utils/as.js'

import { getFontProperties } from '../../utils/text.js'

import type { OctopusPSDConverter } from '../..'
import type { Manifest } from '../../typings/manifest'
import type { EngineData, NodeChildWithProps, ParsedPsd } from '../../typings/raw'
import type { SourceBounds } from '../../typings/source'
import type { SourceComponent } from '../source/source-component'
import type { SourceDesign } from '../source/source-design'
Expand Down Expand Up @@ -106,22 +108,46 @@ export class OctopusManifest {
}
}

private _getComponentAssetsFonts(raw: Record<string, unknown>): string[] {
const entries = traverseAndFind(raw, (obj: unknown) => Object(obj)?.fontPostScriptName)
return [...new Set(entries)] as string[]
private _getFontNames(engineData: EngineData | undefined): string[] {
const { ResourceDict } = engineData ?? {}
const { FontSet } = ResourceDict ?? {}
const { RunArray } = engineData?.EngineDict?.StyleRun ?? {}
const fontSet = asArray(FontSet)
const runArray = asArray(RunArray)

return runArray.map(({ StyleSheet }) => {
return getFontProperties(fontSet, StyleSheet?.StyleSheetData).fontName
})
}

private _getComponentAssetsFonts(
raw: ParsedPsd | NodeChildWithProps,
fontsSet: Set<string> = new Set()
): Set<string> {
if ('textProperties' in raw) {
const fonts = this._getFontNames(raw.textProperties)
fonts.forEach((fontName) => fontsSet.add(fontName))
}

if ('children' in raw) {
raw.children?.forEach((child) => this._getComponentAssetsFonts(child, fontsSet))
}

return fontsSet
}

private _getComponentAssets(targetComponent: SourceComponent): Manifest['Assets'] | null {
const raw = targetComponent?.raw
if (!raw) return null

const images: Manifest['AssetImage'][] = this._sourceDesign.images.map((image) => {
const path = this.getExportedRelativeImageByName(image.name) ?? ''
const location: Manifest['ResourceLocation'] = { type: 'RELATIVE', path }
return { location, refId: image.name }
})

const fonts: Manifest['AssetFont'][] = this._getComponentAssetsFonts(raw).map((font) => ({ name: font }))
const fonts: Manifest['AssetFont'][] = Array.from(this._getComponentAssetsFonts(raw as ParsedPsd)).map((font) => ({
name: font,
}))

return {
...(images.length ? { images } : null),
Expand Down
62 changes: 46 additions & 16 deletions packages/octopus-psd/src/entities/source/source-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,48 @@ import { asArray, asFiniteNumber } from '@opendesign/octopus-common/dist/utils/a
import { push } from '@opendesign/octopus-common/dist/utils/common.js'

import { createSourceLayer } from '../../factories/create-source-layer.js'
import { getArtboardColor, getBoundsFor, isArtboard } from '../../utils/source.js'
import { getArtboardColor, getBoundsFor, getLayerBounds } from '../../utils/source.js'
import { SourceEntity } from './source-entity.js'

import type { SourceLayer } from '../../factories/create-source-layer'
import type { RawComponent, RawLayer } from '../../typings/raw'
import type { NodeChildWithProps, ParsedPsd, RawColor } from '../../typings/raw'
import type { SourceBounds, SourceColor } from '../../typings/source'
import type { SourceDesign } from './source-design.js'

export type SourceComponentOptions = {
raw: RawComponent & RawLayer
isPasteboard?: boolean
raw: ParsedPsd | NodeChildWithProps
parent: SourceDesign
}

export class SourceComponent extends SourceEntity {
protected _rawValue: RawComponent & RawLayer
protected _rawValue: NodeChildWithProps | ParsedPsd
private _layers: SourceLayer[]
private _isPasteboard: boolean
private _parent: SourceDesign

static DEFAULT_ID = 'pasteboard-1'
static DEFAULT_NAME = 'Pasteboard'

constructor({ raw, isPasteboard }: SourceComponentOptions) {
constructor({ isPasteboard, raw, parent }: SourceComponentOptions) {
super(raw)
this._layers = this._initLayers()
this._isPasteboard = isPasteboard ?? false
this._parent = parent
}

private _initLayers() {
const layers = asArray(this._rawValue?.layers).reduce((layers: SourceLayer[], layer: RawLayer) => {
const sourceLayer = createSourceLayer({ layer, parent: this })
const layers = asArray(this._rawValue?.children).reduce((layers: SourceLayer[], layer) => {
const sourceLayer = createSourceLayer({
layer: layer as unknown as NodeChildWithProps,
parent: this,
})
return sourceLayer ? push(layers, sourceLayer) : layers
}, [])
return layers
}

get raw(): RawComponent & RawLayer {
get raw(): NodeChildWithProps | ParsedPsd {
return this._rawValue
}

Expand All @@ -45,12 +52,19 @@ export class SourceComponent extends SourceEntity {
}

get bounds(): SourceBounds {
const artboardRect = this._rawValue.artboard?.artboardRect
return artboardRect ? getBoundsFor(artboardRect) : getBoundsFor(this._rawValue.bounds)
const artboardRect = this._rawValue.parsedProperties?.artb?.artboardRect

return artboardRect
? getBoundsFor(artboardRect)
: this._rawValue?.type === 'Psd'
? getBoundsFor({ Rght: this._rawValue?.width, Btom: this._rawValue?.height })
: getLayerBounds(this._rawValue)
}

get id(): string {
return this._rawValue.id !== undefined ? String(this._rawValue.id) : SourceComponent.DEFAULT_ID
return this._rawValue?.parsedProperties?.lyid !== undefined
? String(this._rawValue.parsedProperties?.lyid)
: SourceComponent.DEFAULT_ID
}

get name(): string {
Expand All @@ -62,18 +76,34 @@ export class SourceComponent extends SourceEntity {
}

get isArtboard(): boolean {
return isArtboard(this._rawValue)
return Boolean(this._rawValue?.parsedProperties?.artb)
}

get artboardColor(): SourceColor | null {
return getArtboardColor(this._rawValue)
return getArtboardColor(this.artboardBackgroundType, this.rawArtboardColor)
}

get artboardBackgroundType(): number | undefined {
return this._rawValue?.parsedProperties?.artb?.artboardBackgroundType
}

get resolution(): number | undefined {
return this._rawValue.resolution
get rawArtboardColor(): RawColor | undefined {
return this._rawValue?.parsedProperties?.artb?.Clr
}

get globalLightAngle(): number {
return asFiniteNumber(this._rawValue.globalLight?.angle, 0)
if ('globalLightAngle' in this._rawValue) {
return asFiniteNumber(this._rawValue.globalLightAngle, 0)
}

return 0
}

get documentWidth(): number {
return this._parent.documentWidth
}

get documentHeight(): number {
return this._parent.documentHeight
}
}
Loading