@ps-generator-bridge/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,915 @@
1
+ 'use strict';
2
+
3
+ // src/photoshop/jsx-runner.ts
4
+ function evalJson(jsx, expr) {
5
+ return jsx.run(`JSON.stringify(${expr})`).then((s) => JSON.parse(s));
6
+ }
7
+ function evalNumber(jsx, expr) {
8
+ return evalJson(jsx, `Number(${expr})`);
9
+ }
10
+ function evalString(jsx, expr) {
11
+ return evalJson(jsx, `String(${expr})`);
12
+ }
13
+ function evalBool(jsx, expr) {
14
+ return evalJson(jsx, `Boolean(${expr})`);
15
+ }
16
+
17
+ // src/photoshop/JsxBuilder.ts
18
+ var JsxBuilder = class {
19
+ /**
20
+ * Escape a string into a JSX string literal. `JSON.stringify` handles quotes,
21
+ * newlines and Unicode.
22
+ *
23
+ * @example JsxBuilder.string("O'Brien") // -> "\"O'Brien\""
24
+ */
25
+ static string(value) {
26
+ return JSON.stringify(value);
27
+ }
28
+ /**
29
+ * Serialize a path to an ExtendScript `File` constructor.
30
+ *
31
+ * @example JsxBuilder.file("/path/to/file.psd") // -> 'new File("/path/to/file.psd")'
32
+ */
33
+ static file(path) {
34
+ return `new File(${JSON.stringify(path)})`;
35
+ }
36
+ /** Serialize a number, rejecting NaN/Infinity. */
37
+ static number(value) {
38
+ if (!isFinite(value)) {
39
+ throw new Error(`JsxBuilder.number: invalid value ${value}`);
40
+ }
41
+ return String(value);
42
+ }
43
+ /** Serialize a boolean. */
44
+ static boolean(value) {
45
+ return value ? "true" : "false";
46
+ }
47
+ /** Pass an enum string through (already in `EnumName.MEMBER` form). */
48
+ static enum_(value) {
49
+ return value;
50
+ }
51
+ /**
52
+ * Serialize a numeric array to a JSX array literal (bounds, crop, etc.).
53
+ *
54
+ * @example JsxBuilder.numberArray([0, 0, 100, 100]) // -> '[0,0,100,100]'
55
+ */
56
+ static numberArray(arr) {
57
+ return JSON.stringify(arr);
58
+ }
59
+ /** Serialize a 2-D numeric array (selection boundary points). */
60
+ static regionArray(region) {
61
+ return JSON.stringify(region);
62
+ }
63
+ /**
64
+ * Build a method-call expression from pre-serialized args.
65
+ *
66
+ * @example JsxBuilder.call("app.open", [JsxBuilder.file(path)])
67
+ * // -> 'app.open(new File("/path/to/file.psd"))'
68
+ */
69
+ static call(path, args) {
70
+ return `${path}(${args.join(", ")})`;
71
+ }
72
+ /**
73
+ * Build a property assignment statement.
74
+ *
75
+ * @example JsxBuilder.assign("app.activeDocument.activeLayer.name", JsxBuilder.string("New Name"))
76
+ * // -> 'app.activeDocument.activeLayer.name = "New Name"'
77
+ */
78
+ static assign(path, value) {
79
+ return `${path} = ${value}`;
80
+ }
81
+ };
82
+
83
+ // src/photoshop/PhotoshopLayer.ts
84
+ var _PhotoshopLayer = class _PhotoshopLayer {
85
+ constructor(_jsx, _path) {
86
+ this._jsx = _jsx;
87
+ this._path = _path;
88
+ }
89
+ // --- Layer read-only properties -----------------------------------------
90
+ /** Unique layer id. */
91
+ get id() {
92
+ return evalNumber(this._jsx, `${this._path}.id`);
93
+ }
94
+ /** Layer name. */
95
+ get name() {
96
+ return evalString(this._jsx, `${this._path}.name`);
97
+ }
98
+ /** Layer visibility. */
99
+ get visible() {
100
+ return evalBool(this._jsx, `${this._path}.visible`);
101
+ }
102
+ /** Layer opacity (0-100). */
103
+ get opacity() {
104
+ return evalNumber(this._jsx, `${this._path}.opacity`);
105
+ }
106
+ /**
107
+ * Blend mode as an enum-name string (e.g. "BlendMode.NORMAL"). ExtendScript
108
+ * yields the numeric code; the static map turns it into a readable name.
109
+ */
110
+ get blendMode() {
111
+ return evalNumber(this._jsx, `${this._path}.blendMode`).then(
112
+ (code) => _PhotoshopLayer._BLEND_MODE_MAP[code] ?? `BlendMode.UNKNOWN_${code}`
113
+ );
114
+ }
115
+ /** Whether the layer is fully locked. */
116
+ get allLocked() {
117
+ return evalBool(this._jsx, `${this._path}.allLocked`);
118
+ }
119
+ /**
120
+ * Layer bounds `[left, top, right, bottom]`.
121
+ *
122
+ * @remarks Units follow `rulerUnits`; values are not pixels unless it is
123
+ * `Units.PIXELS`.
124
+ */
125
+ get bounds() {
126
+ const expr = `(function(){ var b = ${this._path}.bounds; return [b[0], b[1], b[2], b[3]]; })()`;
127
+ return evalJson(this._jsx, expr);
128
+ }
129
+ /**
130
+ * Layer kind as an enum-name string (e.g. "LayerKind.NORMAL"). ArtLayer only;
131
+ * reading it on a LayerSet throws. Check `typename` first.
132
+ *
133
+ * @remarks GRADIENTFILL=4 and PATTERNFILL=4 collide in Adobe's enums, so a
134
+ * kind of 4 always maps to GRADIENTFILL.
135
+ */
136
+ get kind() {
137
+ return evalNumber(this._jsx, `${this._path}.kind`).then(
138
+ (code) => _PhotoshopLayer._LAYER_KIND_MAP[code] ?? `LayerKind.UNKNOWN_${code}`
139
+ );
140
+ }
141
+ /** Object type name ("ArtLayer" or "LayerSet"). */
142
+ get typename() {
143
+ return evalString(this._jsx, `${this._path}.typename`);
144
+ }
145
+ // --- Property writes -----------------------------------------------------
146
+ /** Set the layer name. */
147
+ async setName(value) {
148
+ await this._jsx.run(JsxBuilder.assign(`${this._path}.name`, JsxBuilder.string(value)));
149
+ }
150
+ /** Set layer visibility. */
151
+ async setVisible(value) {
152
+ await this._jsx.run(JsxBuilder.assign(`${this._path}.visible`, JsxBuilder.boolean(value)));
153
+ }
154
+ /** Set layer opacity (0-100). */
155
+ async setOpacity(value) {
156
+ await this._jsx.run(JsxBuilder.assign(`${this._path}.opacity`, JsxBuilder.number(value)));
157
+ }
158
+ /**
159
+ * Set the blend mode.
160
+ *
161
+ * @example
162
+ * import { BlendMode } from "@ps-generator-bridge/sdk/plugin";
163
+ * await layer.setBlendMode(BlendMode.MULTIPLY);
164
+ */
165
+ async setBlendMode(value) {
166
+ await this._jsx.run(JsxBuilder.assign(`${this._path}.blendMode`, JsxBuilder.enum_(value)));
167
+ }
168
+ /** Set whether the layer is fully locked. */
169
+ async setAllLocked(value) {
170
+ await this._jsx.run(JsxBuilder.assign(`${this._path}.allLocked`, JsxBuilder.boolean(value)));
171
+ }
172
+ // --- Methods -------------------------------------------------------------
173
+ /** Delete this layer. */
174
+ async remove() {
175
+ await this._jsx.run(`${this._path}.remove()`);
176
+ }
177
+ /** Duplicate this layer (the copy becomes `activeLayer`). */
178
+ async duplicate() {
179
+ await this._jsx.run(`${this._path}.duplicate()`);
180
+ return new _PhotoshopLayer(this._jsx, "app.activeDocument.activeLayer");
181
+ }
182
+ /**
183
+ * Move this layer relative to another.
184
+ *
185
+ * @param relativeObjectJsxPath JSX path of the reference layer (e.g.
186
+ * "app.activeDocument.layers[0]").
187
+ * @param insertionLocation placement enum.
188
+ *
189
+ * @remarks Pass a bare JSX path expression. A `PhotoshopLayers.getByName()`
190
+ * path contains quotes and cannot be used as a reference expression here.
191
+ *
192
+ * @example
193
+ * import { ElementPlacement } from "@ps-generator-bridge/sdk/plugin";
194
+ * await layer.move("app.activeDocument.layers[0]", ElementPlacement.PLACEBEFORE);
195
+ */
196
+ async move(relativeObjectJsxPath, insertionLocation) {
197
+ await this._jsx.run(
198
+ `${this._path}.move(${relativeObjectJsxPath}, ${JsxBuilder.enum_(insertionLocation)})`
199
+ );
200
+ }
201
+ /** Translate the layer by a pixel delta. */
202
+ async translate(deltaX, deltaY) {
203
+ await this._jsx.run(
204
+ JsxBuilder.call(`${this._path}.translate`, [
205
+ JsxBuilder.number(deltaX),
206
+ JsxBuilder.number(deltaY)
207
+ ])
208
+ );
209
+ }
210
+ /**
211
+ * Scale the layer.
212
+ *
213
+ * @param horizontal horizontal scale percent (150 = 150%).
214
+ * @param vertical vertical scale percent.
215
+ * @param anchor scaling anchor (optional).
216
+ */
217
+ async resize(horizontal, vertical, anchor) {
218
+ const args = [JsxBuilder.number(horizontal), JsxBuilder.number(vertical)];
219
+ if (anchor !== void 0) args.push(JsxBuilder.enum_(anchor));
220
+ await this._jsx.run(JsxBuilder.call(`${this._path}.resize`, args));
221
+ }
222
+ /**
223
+ * Rotate the layer.
224
+ *
225
+ * @param angle degrees, clockwise positive.
226
+ * @param anchor rotation anchor (optional).
227
+ */
228
+ async rotate(angle, anchor) {
229
+ const args = [JsxBuilder.number(angle)];
230
+ if (anchor !== void 0) args.push(JsxBuilder.enum_(anchor));
231
+ await this._jsx.run(JsxBuilder.call(`${this._path}.rotate`, args));
232
+ }
233
+ /** Move the layer to the end of its stack. */
234
+ async moveToEnd() {
235
+ await this._jsx.run(`${this._path}.moveToEnd()`);
236
+ }
237
+ };
238
+ /** BlendMode code -> enum-name map (all 27 members, plus newer ones). */
239
+ _PhotoshopLayer._BLEND_MODE_MAP = {
240
+ 1: "PASSTHROUGH",
241
+ 2: "NORMAL",
242
+ 3: "DISSOLVE",
243
+ 4: "DARKEN",
244
+ 5: "MULTIPLY",
245
+ 6: "COLORBURN",
246
+ 7: "LINEARBURN",
247
+ 8: "LIGHTEN",
248
+ 9: "SCREEN",
249
+ 10: "COLORDODGE",
250
+ 11: "LINEARDODGE",
251
+ 12: "OVERLAY",
252
+ 13: "SOFTLIGHT",
253
+ 14: "HARDLIGHT",
254
+ 15: "VIVIDLIGHT",
255
+ 16: "LINEARLIGHT",
256
+ 17: "PINLIGHT",
257
+ 18: "DIFFERENCE",
258
+ 19: "EXCLUSION",
259
+ 20: "HUE",
260
+ 21: "SATURATION",
261
+ 22: "COLORBLEND",
262
+ 23: "LUMINOSITY",
263
+ 26: "HARDMIX",
264
+ 27: "SUBTRACT",
265
+ 28: "DARKERCOLOR",
266
+ 29: "LIGHTERCOLOR",
267
+ 30: "DIVIDE"
268
+ };
269
+ // --- ArtLayer-only properties -------------------------------------------
270
+ /** LayerKind code -> enum-name map. */
271
+ _PhotoshopLayer._LAYER_KIND_MAP = {
272
+ 1: "NORMAL",
273
+ 2: "TEXT",
274
+ 3: "SOLIDFILL",
275
+ 4: "GRADIENTFILL",
276
+ 5: "LEVELS",
277
+ 6: "CURVES",
278
+ 7: "COLORBALANCE",
279
+ 8: "HUESATURATION",
280
+ 9: "BRIGHTNESSCONTRAST",
281
+ 10: "THRESHOLD",
282
+ 11: "POSTERIZE",
283
+ 12: "CHANNELMIXER",
284
+ 13: "GRADIENTMAP",
285
+ 14: "INVERSION",
286
+ 15: "EXPOSURE",
287
+ 16: "PHOTOFILTER",
288
+ 17: "SELECTIVECOLOR",
289
+ 18: "SMARTOBJECT",
290
+ 20: "VIBRANCE",
291
+ 21: "VIDEO",
292
+ 22: "BLACKANDWHITE",
293
+ 23: "LAYER3D",
294
+ 26: "COLORLOOKUP"
295
+ };
296
+ var PhotoshopLayer = _PhotoshopLayer;
297
+
298
+ // src/photoshop/PhotoshopLayers.ts
299
+ var PhotoshopLayers = class {
300
+ constructor(_jsx, _path) {
301
+ this._jsx = _jsx;
302
+ this._path = _path;
303
+ }
304
+ /** Number of layers in the collection. */
305
+ get length() {
306
+ return evalNumber(this._jsx, `${this._path}.length`);
307
+ }
308
+ /**
309
+ * Access a layer by index. The collection is 0-based, matching JavaScript;
310
+ * `layers[0]` is the top-most layer.
311
+ */
312
+ at(index) {
313
+ return new PhotoshopLayer(this._jsx, `${this._path}[${index}]`);
314
+ }
315
+ /**
316
+ * Look up a layer by name (case-sensitive). The returned wrapper works for
317
+ * property reads/writes but its path contains a `getByName(...)` call, so it
318
+ * must not be passed as `PhotoshopLayer.move()`'s reference path.
319
+ */
320
+ getByName(name) {
321
+ const escapedName = JsxBuilder.string(name);
322
+ return new PhotoshopLayer(this._jsx, `${this._path}.getByName(${escapedName})`);
323
+ }
324
+ };
325
+
326
+ // src/photoshop/PhotoshopSelection.ts
327
+ var PhotoshopSelection = class {
328
+ constructor(_jsx, _path) {
329
+ this._jsx = _jsx;
330
+ this._path = _path;
331
+ }
332
+ /**
333
+ * Selection bounds `[left, top, right, bottom]`. Throws when there is no
334
+ * selection.
335
+ *
336
+ * @remarks Units follow `rulerUnits`.
337
+ */
338
+ get bounds() {
339
+ const expr = `(function(){ var b = ${this._path}.bounds; return [b[0], b[1], b[2], b[3]]; })()`;
340
+ return evalJson(this._jsx, expr);
341
+ }
342
+ /** Whether the selection is a solid (un-feathered) rectangle. */
343
+ get solid() {
344
+ return evalBool(this._jsx, `${this._path}.solid`);
345
+ }
346
+ // --- Methods -------------------------------------------------------------
347
+ /** Select the whole canvas. */
348
+ async selectAll() {
349
+ await this._jsx.run(`${this._path}.selectAll()`);
350
+ }
351
+ /** Deselect. */
352
+ async deselect() {
353
+ await this._jsx.run(`${this._path}.deselect()`);
354
+ }
355
+ /** Invert the selection. */
356
+ async invert() {
357
+ await this._jsx.run(`${this._path}.invert()`);
358
+ }
359
+ /**
360
+ * Create a selection from a region of points.
361
+ *
362
+ * @param region polygon points, e.g. `[[0,0],[100,0],[100,100],[0,100]]`.
363
+ * @param type selection operation (optional, defaults to replace).
364
+ * @param feather feather radius in pixels (optional).
365
+ * @param antiAlias anti-alias the edges (optional).
366
+ *
367
+ * @example
368
+ * import { SelectionType } from "@ps-generator-bridge/sdk/plugin";
369
+ * await sel.select([[0,0],[100,0],[100,100],[0,100]], SelectionType.REPLACE, 0, true);
370
+ */
371
+ async select(region, type, feather, antiAlias) {
372
+ const args = [JsxBuilder.regionArray(region)];
373
+ if (type !== void 0) args.push(JsxBuilder.enum_(type));
374
+ if (feather !== void 0) args.push(JsxBuilder.number(feather));
375
+ if (antiAlias !== void 0) args.push(JsxBuilder.boolean(antiAlias));
376
+ await this._jsx.run(JsxBuilder.call(`${this._path}.select`, args));
377
+ }
378
+ /** Grow the selection by `by` pixels. */
379
+ async expand(by) {
380
+ await this._jsx.run(JsxBuilder.call(`${this._path}.expand`, [JsxBuilder.number(by)]));
381
+ }
382
+ /** Shrink the selection by `by` pixels. */
383
+ async contract(by) {
384
+ await this._jsx.run(JsxBuilder.call(`${this._path}.contract`, [JsxBuilder.number(by)]));
385
+ }
386
+ /** Feather the selection edge by `by` pixels. */
387
+ async feather(by) {
388
+ await this._jsx.run(JsxBuilder.call(`${this._path}.feather`, [JsxBuilder.number(by)]));
389
+ }
390
+ /** Translate the selection boundary (content stays put). */
391
+ async translateBoundary(deltaX, deltaY) {
392
+ await this._jsx.run(
393
+ JsxBuilder.call(`${this._path}.translateBoundary`, [
394
+ JsxBuilder.number(deltaX),
395
+ JsxBuilder.number(deltaY)
396
+ ])
397
+ );
398
+ }
399
+ };
400
+
401
+ // src/photoshop/PhotoshopDocument.ts
402
+ var PhotoshopDocument = class _PhotoshopDocument {
403
+ constructor(_jsx, _path) {
404
+ this._jsx = _jsx;
405
+ this._path = _path;
406
+ }
407
+ // --- Read-only properties -----------------------------------------------
408
+ /** Document name (file name, without directory). */
409
+ get name() {
410
+ return evalString(this._jsx, `${this._path}.name`);
411
+ }
412
+ /** Unique document id. */
413
+ get id() {
414
+ return evalNumber(this._jsx, `${this._path}.id`);
415
+ }
416
+ /**
417
+ * Document width.
418
+ *
419
+ * @remarks The unit follows Photoshop's current `app.preferences.rulerUnits`.
420
+ * For guaranteed pixels, set `rulerUnits` to `Units.PIXELS` first (e.g. via
421
+ * `this.jsx.run(...)`).
422
+ */
423
+ get width() {
424
+ return evalNumber(this._jsx, `${this._path}.width`);
425
+ }
426
+ /**
427
+ * Document height.
428
+ *
429
+ * @remarks The unit follows `rulerUnits` (see {@link width}).
430
+ */
431
+ get height() {
432
+ return evalNumber(this._jsx, `${this._path}.height`);
433
+ }
434
+ /** Document resolution (PPI). */
435
+ get resolution() {
436
+ return evalNumber(this._jsx, `${this._path}.resolution`);
437
+ }
438
+ /** Document color mode as an enum-name string (e.g. "DocumentMode.RGB"). */
439
+ get mode() {
440
+ const expr = `(function(){
441
+ var m = ${this._path}.mode;
442
+ if (m === DocumentMode.RGB) return "DocumentMode.RGB";
443
+ if (m === DocumentMode.CMYK) return "DocumentMode.CMYK";
444
+ if (m === DocumentMode.GRAYSCALE) return "DocumentMode.GRAYSCALE";
445
+ if (m === DocumentMode.LAB) return "DocumentMode.LAB";
446
+ if (m === DocumentMode.BITMAP) return "DocumentMode.BITMAP";
447
+ if (m === DocumentMode.INDEXEDCOLOR) return "DocumentMode.INDEXEDCOLOR";
448
+ if (m === DocumentMode.MULTICHANNEL) return "DocumentMode.MULTICHANNEL";
449
+ if (m === DocumentMode.DUOTONE) return "DocumentMode.DUOTONE";
450
+ return String(m);
451
+ })()`;
452
+ return evalJson(this._jsx, expr);
453
+ }
454
+ /** Whether the document is saved since its last change. */
455
+ get saved() {
456
+ return evalBool(this._jsx, `${this._path}.saved`);
457
+ }
458
+ /**
459
+ * Full document path (native `fsName`). For an unsaved document this may
460
+ * return a temporary path or throw.
461
+ */
462
+ get fullName() {
463
+ return evalString(this._jsx, `${this._path}.fullName.fsName`);
464
+ }
465
+ /** Directory containing the document (native `fsName`). */
466
+ get path() {
467
+ return evalString(this._jsx, `${this._path}.path.fsName`);
468
+ }
469
+ // --- Child navigation (synchronous; no request) -------------------------
470
+ /** The active layer. */
471
+ get activeLayer() {
472
+ return new PhotoshopLayer(this._jsx, `${this._path}.activeLayer`);
473
+ }
474
+ /** The Layers collection (ArtLayers + LayerSets). */
475
+ get layers() {
476
+ return new PhotoshopLayers(this._jsx, `${this._path}.layers`);
477
+ }
478
+ /** The selection. */
479
+ get selection() {
480
+ return new PhotoshopSelection(this._jsx, `${this._path}.selection`);
481
+ }
482
+ // --- Methods -------------------------------------------------------------
483
+ /** Save the document in its current format. */
484
+ async save() {
485
+ await this._jsx.run(`${this._path}.save()`);
486
+ }
487
+ /**
488
+ * Close the document.
489
+ *
490
+ * @param saving save behavior before closing (defaults to not saving).
491
+ *
492
+ * @example
493
+ * import { SaveOptions } from "@ps-generator-bridge/sdk/plugin";
494
+ * await doc.close(SaveOptions.DONOTSAVECHANGES);
495
+ */
496
+ async close(saving = "SaveOptions.DONOTSAVECHANGES") {
497
+ await this._jsx.run(JsxBuilder.call(`${this._path}.close`, [JsxBuilder.enum_(saving)]));
498
+ }
499
+ /**
500
+ * Save to a path. Mirrors ExtendScript
501
+ * `saveAs(saveIn, options?, asCopy?, extensionType?)`; only `saveIn` and
502
+ * `asCopy` are exposed, `options` is undefined and `extensionType` is left to
503
+ * the Photoshop default.
504
+ *
505
+ * @param saveIn destination path.
506
+ * @param asCopy save as a copy (does not change the document's saved state).
507
+ */
508
+ async saveAs(saveIn, asCopy) {
509
+ const script = `${this._path}.saveAs(${JsxBuilder.file(saveIn)}${asCopy !== void 0 ? `, undefined, ${JsxBuilder.boolean(asCopy)}` : ""})`;
510
+ await this._jsx.run(script);
511
+ }
512
+ /** Flatten all layers into a single background layer. */
513
+ async flatten() {
514
+ await this._jsx.run(`${this._path}.flatten()`);
515
+ }
516
+ /** Merge all visible layers. */
517
+ async mergeVisibleLayers() {
518
+ await this._jsx.run(`${this._path}.mergeVisibleLayers()`);
519
+ }
520
+ /** Rasterize all layers. */
521
+ async rasterizeAllLayers() {
522
+ await this._jsx.run(`${this._path}.rasterizeAllLayers()`);
523
+ }
524
+ /**
525
+ * Duplicate the document.
526
+ *
527
+ * @param name optional name for the copy.
528
+ * @returns the duplicated document.
529
+ *
530
+ * @remarks Assumes `duplicate()` makes the copy the active document, so the
531
+ * result points at `app.activeDocument`.
532
+ */
533
+ async duplicate(name) {
534
+ const args = name !== void 0 ? [JsxBuilder.string(name)] : [];
535
+ await this._jsx.run(JsxBuilder.call(`${this._path}.duplicate`, args));
536
+ return new _PhotoshopDocument(this._jsx, "app.activeDocument");
537
+ }
538
+ /**
539
+ * Resize the canvas.
540
+ *
541
+ * @param width new width in pixels.
542
+ * @param height new height in pixels.
543
+ * @param anchor anchor position (optional, defaults to center).
544
+ *
545
+ * @example
546
+ * import { AnchorPosition } from "@ps-generator-bridge/sdk/plugin";
547
+ * await doc.resizeCanvas(1920, 1080, AnchorPosition.MIDDLECENTER);
548
+ */
549
+ async resizeCanvas(width, height, anchor) {
550
+ const args = [JsxBuilder.number(width), JsxBuilder.number(height)];
551
+ if (anchor !== void 0) args.push(JsxBuilder.enum_(anchor));
552
+ await this._jsx.run(JsxBuilder.call(`${this._path}.resizeCanvas`, args));
553
+ }
554
+ /**
555
+ * Resize the image.
556
+ *
557
+ * @param width new width in pixels (optional).
558
+ * @param height new height in pixels (optional).
559
+ * @param resolution new resolution in PPI (optional).
560
+ */
561
+ async resizeImage(width, height, resolution) {
562
+ const args = [
563
+ width !== void 0 ? JsxBuilder.number(width) : "undefined",
564
+ height !== void 0 ? JsxBuilder.number(height) : "undefined",
565
+ resolution !== void 0 ? JsxBuilder.number(resolution) : "undefined"
566
+ ];
567
+ await this._jsx.run(JsxBuilder.call(`${this._path}.resizeImage`, args));
568
+ }
569
+ /** Rotate the canvas by `angle` degrees. */
570
+ async rotateCanvas(angle) {
571
+ await this._jsx.run(JsxBuilder.call(`${this._path}.rotateCanvas`, [JsxBuilder.number(angle)]));
572
+ }
573
+ /**
574
+ * Crop the document to `bounds` `[left, top, right, bottom]` (pixels).
575
+ *
576
+ * @remarks Only `bounds` is exposed; ExtendScript `crop()` also takes angle,
577
+ * width and height — reach those via `this.jsx.run(...)` if needed.
578
+ */
579
+ async crop(bounds) {
580
+ const script = `${this._path}.crop(${JsxBuilder.numberArray(Array.from(bounds))})`;
581
+ await this._jsx.run(script);
582
+ }
583
+ };
584
+
585
+ // src/photoshop/PhotoshopApp.ts
586
+ var PhotoshopApp = class {
587
+ constructor(_jsx) {
588
+ this._jsx = _jsx;
589
+ this._path = "app";
590
+ }
591
+ // --- Read-only properties -----------------------------------------------
592
+ /** Photoshop version (e.g. "25.0"). */
593
+ get version() {
594
+ return evalString(this._jsx, `${this._path}.version`);
595
+ }
596
+ /** Application locale (e.g. "zh_CN"). */
597
+ get locale() {
598
+ return evalString(this._jsx, `${this._path}.locale`);
599
+ }
600
+ /** Application name (e.g. "Adobe Photoshop"). */
601
+ get name() {
602
+ return evalString(this._jsx, `${this._path}.name`);
603
+ }
604
+ /** Internal build number. */
605
+ get build() {
606
+ return evalString(this._jsx, `${this._path}.build`);
607
+ }
608
+ /**
609
+ * Install path (native `fsName`). `app.path` is a File in ExtendScript, so the
610
+ * string comes from `.fsName`.
611
+ */
612
+ get path() {
613
+ return evalString(this._jsx, `${this._path}.path.fsName`);
614
+ }
615
+ /**
616
+ * Current foreground color (RGB).
617
+ *
618
+ * @remarks The first version returns only the RGB approximation; in CMYK/Lab
619
+ * documents this is Photoshop's converted RGB and may lose precision. The
620
+ * `cmyk` field is reserved and currently always undefined.
621
+ */
622
+ get foregroundColor() {
623
+ const expr = `(function(){
624
+ var c = ${this._path}.foregroundColor;
625
+ return {
626
+ model: "rgb",
627
+ rgb: { red: c.rgb.red, green: c.rgb.green, blue: c.rgb.blue, hexValue: c.rgb.hexValue }
628
+ };
629
+ })()`;
630
+ return evalJson(this._jsx, expr);
631
+ }
632
+ /** Shortcut for `activeDocument` reached through the `app` path. */
633
+ get activeDocument() {
634
+ return new PhotoshopDocument(this._jsx, `${this._path}.activeDocument`);
635
+ }
636
+ // --- Methods -------------------------------------------------------------
637
+ /**
638
+ * Open a file and return its Document wrapper. The opened document becomes the
639
+ * active document.
640
+ *
641
+ * @param filePath native or POSIX path.
642
+ *
643
+ * @example
644
+ * const doc = await this.photoshop.app.open("/Users/me/design.psd");
645
+ */
646
+ async open(filePath) {
647
+ const script = JsxBuilder.call(`${this._path}.open`, [JsxBuilder.file(filePath)]);
648
+ await this._jsx.run(script);
649
+ return new PhotoshopDocument(this._jsx, `${this._path}.activeDocument`);
650
+ }
651
+ /** Emit a beep. */
652
+ async beep() {
653
+ await this._jsx.run(`${this._path}.beep()`);
654
+ }
655
+ };
656
+
657
+ // src/photoshop/PsPhotoshopProxy.ts
658
+ var PsPhotoshopProxy = class {
659
+ constructor(jsx) {
660
+ this._jsx = jsx;
661
+ this.app = new PhotoshopApp(this._jsx);
662
+ }
663
+ /**
664
+ * The active document (shortcut for `app.activeDocument`). A fresh wrapper is
665
+ * created on each access; wrappers are lightweight and stateless.
666
+ */
667
+ get activeDocument() {
668
+ return new PhotoshopDocument(this._jsx, "app.activeDocument");
669
+ }
670
+ };
671
+
672
+ // src/photoshop/enums.ts
673
+ var SaveOptions = {
674
+ /** Do not save changes. */
675
+ DONOTSAVECHANGES: "SaveOptions.DONOTSAVECHANGES",
676
+ /** Prompt the user. */
677
+ PROMPTTOSAVECHANGES: "SaveOptions.PROMPTTOSAVECHANGES",
678
+ /** Save changes. */
679
+ SAVECHANGES: "SaveOptions.SAVECHANGES"
680
+ };
681
+ var LayerKind = {
682
+ NORMAL: "LayerKind.NORMAL",
683
+ TEXT: "LayerKind.TEXT",
684
+ SMARTOBJECT: "LayerKind.SMARTOBJECT",
685
+ SOLIDFILL: "LayerKind.SOLIDFILL",
686
+ /**
687
+ * @remarks In `enums.d.ts`, GRADIENTFILL=4 and PATTERNFILL=4 share a value
688
+ * (an Adobe enum conflict); a kind of 4 always maps back to GRADIENTFILL and
689
+ * the two cannot be told apart at runtime (disambiguate by layer name etc.).
690
+ */
691
+ GRADIENTFILL: "LayerKind.GRADIENTFILL",
692
+ PATTERNFILL: "LayerKind.PATTERNFILL",
693
+ LEVELS: "LayerKind.LEVELS",
694
+ CURVES: "LayerKind.CURVES",
695
+ BRIGHTNESSCONTRAST: "LayerKind.BRIGHTNESSCONTRAST",
696
+ HUESATURATION: "LayerKind.HUESATURATION",
697
+ COLORBALANCE: "LayerKind.COLORBALANCE",
698
+ INVERSION: "LayerKind.INVERSION",
699
+ POSTERIZE: "LayerKind.POSTERIZE",
700
+ THRESHOLD: "LayerKind.THRESHOLD",
701
+ EXPOSURE: "LayerKind.EXPOSURE",
702
+ VIBRANCE: "LayerKind.VIBRANCE",
703
+ VIDEO: "LayerKind.VIDEO",
704
+ LAYER3D: "LayerKind.LAYER3D",
705
+ BLACKANDWHITE: "LayerKind.BLACKANDWHITE",
706
+ CHANNELMIXER: "LayerKind.CHANNELMIXER",
707
+ GRADIENTMAP: "LayerKind.GRADIENTMAP",
708
+ SELECTIVECOLOR: "LayerKind.SELECTIVECOLOR",
709
+ PHOTOFILTER: "LayerKind.PHOTOFILTER",
710
+ COLORLOOKUP: "LayerKind.COLORLOOKUP"
711
+ };
712
+ var BlendMode = {
713
+ NORMAL: "BlendMode.NORMAL",
714
+ DISSOLVE: "BlendMode.DISSOLVE",
715
+ DARKEN: "BlendMode.DARKEN",
716
+ MULTIPLY: "BlendMode.MULTIPLY",
717
+ COLORBURN: "BlendMode.COLORBURN",
718
+ LINEARBURN: "BlendMode.LINEARBURN",
719
+ DARKERCOLOR: "BlendMode.DARKERCOLOR",
720
+ LIGHTEN: "BlendMode.LIGHTEN",
721
+ SCREEN: "BlendMode.SCREEN",
722
+ COLORDODGE: "BlendMode.COLORDODGE",
723
+ LINEARDODGE: "BlendMode.LINEARDODGE",
724
+ LIGHTERCOLOR: "BlendMode.LIGHTERCOLOR",
725
+ OVERLAY: "BlendMode.OVERLAY",
726
+ SOFTLIGHT: "BlendMode.SOFTLIGHT",
727
+ HARDLIGHT: "BlendMode.HARDLIGHT",
728
+ VIVIDLIGHT: "BlendMode.VIVIDLIGHT",
729
+ LINEARLIGHT: "BlendMode.LINEARLIGHT",
730
+ PINLIGHT: "BlendMode.PINLIGHT",
731
+ HARDMIX: "BlendMode.HARDMIX",
732
+ DIFFERENCE: "BlendMode.DIFFERENCE",
733
+ EXCLUSION: "BlendMode.EXCLUSION",
734
+ SUBTRACT: "BlendMode.SUBTRACT",
735
+ DIVIDE: "BlendMode.DIVIDE",
736
+ HUE: "BlendMode.HUE",
737
+ SATURATION: "BlendMode.SATURATION",
738
+ COLORBLEND: "BlendMode.COLORBLEND",
739
+ LUMINOSITY: "BlendMode.LUMINOSITY",
740
+ PASSTHROUGH: "BlendMode.PASSTHROUGH"
741
+ };
742
+ var ElementPlacement = {
743
+ PLACEATBEGINNING: "ElementPlacement.PLACEATBEGINNING",
744
+ PLACEINSIDE: "ElementPlacement.PLACEINSIDE",
745
+ PLACEBEFORE: "ElementPlacement.PLACEBEFORE",
746
+ PLACEAFTER: "ElementPlacement.PLACEAFTER",
747
+ PLACEATEND: "ElementPlacement.PLACEATEND"
748
+ };
749
+ var AnchorPosition = {
750
+ TOPLEFT: "AnchorPosition.TOPLEFT",
751
+ TOPCENTER: "AnchorPosition.TOPCENTER",
752
+ TOPRIGHT: "AnchorPosition.TOPRIGHT",
753
+ MIDDLELEFT: "AnchorPosition.MIDDLELEFT",
754
+ MIDDLECENTER: "AnchorPosition.MIDDLECENTER",
755
+ MIDDLERIGHT: "AnchorPosition.MIDDLERIGHT",
756
+ BOTTOMLEFT: "AnchorPosition.BOTTOMLEFT",
757
+ BOTTOMCENTER: "AnchorPosition.BOTTOMCENTER",
758
+ BOTTOMRIGHT: "AnchorPosition.BOTTOMRIGHT"
759
+ };
760
+ var DocumentMode = {
761
+ BITMAP: "DocumentMode.BITMAP",
762
+ GRAYSCALE: "DocumentMode.GRAYSCALE",
763
+ RGB: "DocumentMode.RGB",
764
+ CMYK: "DocumentMode.CMYK",
765
+ LAB: "DocumentMode.LAB",
766
+ INDEXEDCOLOR: "DocumentMode.INDEXEDCOLOR",
767
+ MULTICHANNEL: "DocumentMode.MULTICHANNEL",
768
+ DUOTONE: "DocumentMode.DUOTONE"
769
+ };
770
+ var SelectionType = {
771
+ REPLACE: "SelectionType.REPLACE",
772
+ EXTEND: "SelectionType.EXTEND",
773
+ DIMINISH: "SelectionType.DIMINISH",
774
+ INTERSECT: "SelectionType.INTERSECT"
775
+ };
776
+
777
+ // src/plugin/base.ts
778
+ var BASE_PLUGIN_BRAND = /* @__PURE__ */ Symbol.for("ps-generator-bridge.BasePlugin");
779
+ var BasePlugin = class {
780
+ constructor(id, plugin) {
781
+ this.id = id;
782
+ this.plugin = plugin;
783
+ }
784
+ /** Feature modules, reached by short key (shortcut for `this.plugin.modules`). */
785
+ get modules() {
786
+ return this.plugin.modules;
787
+ }
788
+ /**
789
+ * The jsx runner scoped to this plugin's own `jsx/` dir (shortcut for
790
+ * `this.plugin.jsx`, RFC 0005). `jsx.execute("x")` runs `<pluginRoot>/jsx/x.jsx`;
791
+ * `jsx.executeBuiltin("Document/getDocumentInfo")` reaches the host's built-in
792
+ * tree.
793
+ */
794
+ get jsx() {
795
+ return this.plugin.jsx;
796
+ }
797
+ /**
798
+ * Typed, listen-only Photoshop event stream (shortcut for `this.plugin.events`).
799
+ * Subscriptions are lazy on the server side: the first `on`/`once` for an event
800
+ * subscribes upstream, and the last `off` unsubscribes.
801
+ */
802
+ get events() {
803
+ return this.plugin.events;
804
+ }
805
+ /**
806
+ * Photoshop DOM proxy, a typed object wrapper over `this.jsx`. Read and write
807
+ * the live document through `this.photoshop.app` / `this.photoshop.activeDocument`
808
+ * (e.g. `await this.photoshop.activeDocument.name`) instead of hand-writing
809
+ * ExtendScript. Lazily built once and backed by this plugin's own jsx runner;
810
+ * drop to `this.jsx.run(...)` for anything the proxy does not cover.
811
+ */
812
+ get photoshop() {
813
+ return this._photoshop ??= new PsPhotoshopProxy(this.jsx);
814
+ }
815
+ /**
816
+ * Attach the per-plugin client bus. Called by the server's assembler after
817
+ * construction and before `listen`, so `broadcast`/`send` are live by the time
818
+ * any handler can fire. Not part of the public Plugin authoring API.
819
+ */
820
+ _attachBus(bus) {
821
+ this.bus = bus;
822
+ }
823
+ /** Push an Event to every online client of this plugin. */
824
+ broadcast(type, data) {
825
+ this.bus?.broadcast(type, data);
826
+ }
827
+ /** Push an Event to one client of this plugin (no-op if not connected). */
828
+ send(clientId, type, data) {
829
+ this.bus?.send(clientId, type, data);
830
+ }
831
+ /** Called after a client handshake registers with this plugin. Default no-op. */
832
+ onConnect(_clientId) {
833
+ }
834
+ /** Called after a client socket is removed from this plugin. Default no-op. */
835
+ onDisconnect(_clientId) {
836
+ }
837
+ };
838
+ Object.defineProperty(BasePlugin.prototype, BASE_PLUGIN_BRAND, {
839
+ value: true,
840
+ enumerable: false,
841
+ configurable: false,
842
+ writable: false
843
+ });
844
+ function isBasePluginClass(S) {
845
+ if (typeof S !== "function") return false;
846
+ const proto = S.prototype;
847
+ return proto != null && Boolean(proto[BASE_PLUGIN_BRAND]);
848
+ }
849
+
850
+ // src/plugin/decorators.ts
851
+ Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
852
+ var METADATA = Symbol.metadata;
853
+ var HANDLERS = /* @__PURE__ */ Symbol.for("ps-generator-bridge.handlers");
854
+ function pushHandler(metadata, meta) {
855
+ if (!Object.prototype.hasOwnProperty.call(metadata, HANDLERS)) {
856
+ metadata[HANDLERS] = [];
857
+ }
858
+ metadata[HANDLERS].push(meta);
859
+ }
860
+ function ws(name) {
861
+ return function(_value, context) {
862
+ pushHandler(context.metadata, {
863
+ kind: "ws",
864
+ name,
865
+ methodKey: String(context.name)
866
+ });
867
+ };
868
+ }
869
+ function api(pathOrRoute) {
870
+ const route = typeof pathOrRoute === "string" ? { method: "GET", url: pathOrRoute } : { method: pathOrRoute.method ?? "GET", url: pathOrRoute.url };
871
+ return function(_value, context) {
872
+ pushHandler(context.metadata, {
873
+ kind: "api",
874
+ method: route.method,
875
+ url: route.url,
876
+ methodKey: String(context.name)
877
+ });
878
+ };
879
+ }
880
+ function bootstrap(instance, target) {
881
+ const ctor = instance.constructor;
882
+ const collected = [];
883
+ let metadata = ctor[METADATA];
884
+ while (metadata) {
885
+ if (Object.prototype.hasOwnProperty.call(metadata, HANDLERS)) {
886
+ collected.push(...metadata[HANDLERS]);
887
+ }
888
+ metadata = Object.getPrototypeOf(metadata);
889
+ }
890
+ for (const meta of collected) {
891
+ const fn = instance[meta.methodKey];
892
+ if (typeof fn !== "function") continue;
893
+ const bound = fn.bind(instance);
894
+ if (meta.kind === "ws") {
895
+ target.registerMethod(meta.name, bound);
896
+ } else {
897
+ target.registerApi({ method: meta.method, url: meta.url, handler: bound });
898
+ }
899
+ }
900
+ }
901
+
902
+ exports.AnchorPosition = AnchorPosition;
903
+ exports.BasePlugin = BasePlugin;
904
+ exports.BlendMode = BlendMode;
905
+ exports.DocumentMode = DocumentMode;
906
+ exports.ElementPlacement = ElementPlacement;
907
+ exports.LayerKind = LayerKind;
908
+ exports.SaveOptions = SaveOptions;
909
+ exports.SelectionType = SelectionType;
910
+ exports.api = api;
911
+ exports.bootstrap = bootstrap;
912
+ exports.isBasePluginClass = isBasePluginClass;
913
+ exports.ws = ws;
914
+ //# sourceMappingURL=plugin.cjs.map
915
+ //# sourceMappingURL=plugin.cjs.map