@pixerate/editor 0.268.2 → 0.269.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.
- package/dist/agent/index.cjs +27 -27
- package/dist/agent/index.js +1 -1
- package/dist/animation-editor/index.cjs +61 -61
- package/dist/animation-editor/index.js +5 -5
- package/dist/brushes/import/abr.d.cts +48 -0
- package/dist/brushes/import/abr.d.ts +48 -0
- package/dist/brushes/import/bplist.d.cts +24 -0
- package/dist/brushes/import/bplist.d.ts +24 -0
- package/dist/brushes/import/pack.d.cts +82 -0
- package/dist/brushes/import/pack.d.ts +82 -0
- package/dist/brushes/import/zip.d.cts +31 -0
- package/dist/brushes/import/zip.d.ts +31 -0
- package/dist/brushes/index.cjs +26 -6
- package/dist/brushes/index.d.cts +2 -0
- package/dist/brushes/index.d.ts +2 -0
- package/dist/brushes/index.js +22 -2
- package/dist/{chunk-SIFAIGB3.js → chunk-3AJCEJNH.js} +2 -162
- package/dist/{chunk-YXP6X3SY.cjs → chunk-3KNQ2W6X.cjs} +697 -10
- package/dist/{chunk-QNY7BDFP.js → chunk-4CPOKP27.js} +1 -1
- package/dist/{chunk-J4GXP53R.cjs → chunk-4NMPUJUK.cjs} +86 -86
- package/dist/{chunk-LDOD7VCT.js → chunk-4YSPQGZW.js} +1 -1
- package/dist/{chunk-JBB3GDBB.cjs → chunk-56RHCIJV.cjs} +2 -2
- package/dist/{chunk-4IMD2TLY.cjs → chunk-AI4F33IV.cjs} +3166 -2966
- package/dist/{chunk-YOVXQHA3.cjs → chunk-BENSE3VX.cjs} +23 -23
- package/dist/{chunk-VDGBHYC7.js → chunk-F7OO3DHP.js} +157 -1
- package/dist/{chunk-UVTO3D4E.js → chunk-FUDP5QBB.js} +1 -1
- package/dist/{chunk-3XDSNIGF.js → chunk-ININNHLM.js} +687 -0
- package/dist/{chunk-WIC7TG73.cjs → chunk-M4UQA2T4.cjs} +18 -18
- package/dist/{chunk-ITURMC2M.js → chunk-N3UMQVKX.js} +176 -176
- package/dist/{chunk-UWEQ55MY.cjs → chunk-N4KP3UQR.cjs} +157 -1
- package/dist/{chunk-D5BXSOSU.js → chunk-QGVTOCNB.js} +3118 -2918
- package/dist/{chunk-M6DNZNVY.js → chunk-RWDSSK2I.js} +4 -2
- package/dist/{chunk-CVFAAZXE.cjs → chunk-SPIO2GJU.cjs} +8 -6
- package/dist/{chunk-RHEMMGCN.cjs → chunk-T4JPGBW3.cjs} +173 -173
- package/dist/{chunk-HFY5LG5J.js → chunk-THBJHPUL.js} +1 -1
- package/dist/{chunk-NPWWSCWT.cjs → chunk-VBRHE4AM.cjs} +35 -35
- package/dist/{chunk-AZC2NDAR.js → chunk-WB22XMW3.js} +6 -6
- package/dist/{chunk-NIBFDFYW.cjs → chunk-YBMUHIOM.cjs} +12 -172
- package/dist/extensions/index.cjs +18 -4
- package/dist/extensions/index.d.cts +1 -0
- package/dist/extensions/index.d.ts +1 -0
- package/dist/extensions/index.js +17 -3
- package/dist/extensions/prompt-editor.d.cts +109 -0
- package/dist/extensions/prompt-editor.d.ts +109 -0
- package/dist/generation/index.cjs +15 -15
- package/dist/generation/index.js +3 -3
- package/dist/grammar/index.cjs +4 -4
- package/dist/grammar/index.js +7 -7
- package/dist/image-editor/index.cjs +785 -785
- package/dist/image-editor/index.js +6 -6
- package/dist/index.cjs +27 -13
- package/dist/index.js +23 -9
- package/dist/paint-editor/index.cjs +91 -91
- package/dist/paint-editor/index.js +6 -6
- package/dist/vector/index.cjs +1001 -1001
- package/dist/vector/index.js +6 -6
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
|
|
2
2
|
|
|
3
3
|
var _chunkPFHJS4HEcjs = require('./chunk-PFHJS4HE.cjs');
|
|
4
4
|
|
|
@@ -20,12 +20,21 @@ var _chunkPFHJS4HEcjs = require('./chunk-PFHJS4HE.cjs');
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
var _chunkEU2ZZA6Lcjs = require('./chunk-EU2ZZA6L.cjs');
|
|
28
31
|
|
|
32
|
+
|
|
33
|
+
var _chunkW5M3J7PVcjs = require('./chunk-W5M3J7PV.cjs');
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
var _chunkXFM3OCHUcjs = require('./chunk-XFM3OCHU.cjs');
|
|
37
|
+
|
|
29
38
|
// src/brushes/patch.ts
|
|
30
39
|
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
31
40
|
var copy = (v) => JSON.parse(JSON.stringify(v));
|
|
@@ -58,6 +67,638 @@ function changedBrushPaths(a, b, path = "") {
|
|
|
58
67
|
return JSON.stringify(a) === JSON.stringify(b) ? [] : [path];
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
// src/brushes/import/abr.ts
|
|
71
|
+
var AbrError = class extends Error {
|
|
72
|
+
constructor(message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "AbrError";
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var MAX_TIP_PIXELS = 5e3 * 5e3;
|
|
78
|
+
var MAX_TIPS = 1e3;
|
|
79
|
+
var Reader = (_class = class {
|
|
80
|
+
constructor(bytes, end = bytes.length) {;_class.prototype.__init.call(this);
|
|
81
|
+
this.bytes = bytes;
|
|
82
|
+
this.end = end;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__init() {this.pos = 0}
|
|
87
|
+
need(n) {
|
|
88
|
+
if (n < 0 || this.pos + n > this.end) throw new AbrError("The brush file ends early or a size in it is wrong.");
|
|
89
|
+
}
|
|
90
|
+
u8() {
|
|
91
|
+
this.need(1);
|
|
92
|
+
return this.bytes[this.pos++];
|
|
93
|
+
}
|
|
94
|
+
u16() {
|
|
95
|
+
this.need(2);
|
|
96
|
+
const v = this.bytes[this.pos] << 8 | this.bytes[this.pos + 1];
|
|
97
|
+
this.pos += 2;
|
|
98
|
+
return v;
|
|
99
|
+
}
|
|
100
|
+
i16() {
|
|
101
|
+
const v = this.u16();
|
|
102
|
+
return v & 32768 ? v - 65536 : v;
|
|
103
|
+
}
|
|
104
|
+
u32() {
|
|
105
|
+
this.need(4);
|
|
106
|
+
const b = this.bytes;
|
|
107
|
+
const v = (b[this.pos] << 24 | b[this.pos + 1] << 16 | b[this.pos + 2] << 8 | b[this.pos + 3]) >>> 0;
|
|
108
|
+
this.pos += 4;
|
|
109
|
+
return v;
|
|
110
|
+
}
|
|
111
|
+
i32() {
|
|
112
|
+
return this.u32() | 0;
|
|
113
|
+
}
|
|
114
|
+
take(n) {
|
|
115
|
+
this.need(n);
|
|
116
|
+
const out = this.bytes.subarray(this.pos, this.pos + n);
|
|
117
|
+
this.pos += n;
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
skip(n) {
|
|
121
|
+
this.need(n);
|
|
122
|
+
this.pos += n;
|
|
123
|
+
}
|
|
124
|
+
ascii(n) {
|
|
125
|
+
return String.fromCharCode(...this.take(n));
|
|
126
|
+
}
|
|
127
|
+
}, _class);
|
|
128
|
+
function unpackRows(r, height, rowBytes) {
|
|
129
|
+
const lengths = [];
|
|
130
|
+
for (let y = 0; y < height; y++) lengths.push(r.u16());
|
|
131
|
+
const out = new Uint8Array(rowBytes * height);
|
|
132
|
+
for (let y = 0; y < height; y++) {
|
|
133
|
+
const row = new Reader(r.take(lengths[y]));
|
|
134
|
+
let x = 0;
|
|
135
|
+
while (row.pos < row.end && x < rowBytes) {
|
|
136
|
+
const n = row.u8();
|
|
137
|
+
if (n < 128) {
|
|
138
|
+
const run = Math.min(n + 1, rowBytes - x);
|
|
139
|
+
out.set(row.take(run), y * rowBytes + x);
|
|
140
|
+
if (n + 1 > run) row.skip(Math.min(n + 1 - run, row.end - row.pos));
|
|
141
|
+
x += run;
|
|
142
|
+
} else if (n > 128) {
|
|
143
|
+
const v = row.u8();
|
|
144
|
+
const run = Math.min(257 - n, rowBytes - x);
|
|
145
|
+
out.fill(v, y * rowBytes + x, y * rowBytes + x + run);
|
|
146
|
+
x += run;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
function readImage(r, width, height, depth, compression) {
|
|
153
|
+
if (!(width > 0 && height > 0)) throw new AbrError(`A tip is ${width}\xD7${height} px.`);
|
|
154
|
+
if (width * height > MAX_TIP_PIXELS) throw new AbrError(`A tip is ${width}\xD7${height} px; tips over ${MAX_TIP_PIXELS} pixels are not read.`);
|
|
155
|
+
if (depth !== 8 && depth !== 16) throw new AbrError(`A tip has ${depth}-bit depth; 8 and 16 bits are read.`);
|
|
156
|
+
const bpp = depth / 8;
|
|
157
|
+
const rowBytes = width * bpp;
|
|
158
|
+
const data = compression === 0 ? r.take(rowBytes * height) : compression === 1 ? unpackRows(r, height, rowBytes) : null;
|
|
159
|
+
if (!data) throw new AbrError(`A tip uses compression ${compression}; raw and PackBits are read.`);
|
|
160
|
+
if (bpp === 1) return Uint8Array.from(data);
|
|
161
|
+
const out = new Uint8Array(width * height);
|
|
162
|
+
for (let i = 0; i < out.length; i++) out[i] = data[i * 2];
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
166
|
+
function readV1(r, version, file) {
|
|
167
|
+
const count = r.u16();
|
|
168
|
+
for (let i = 0; i < count && file.tips.length < MAX_TIPS; i++) {
|
|
169
|
+
const type = r.u16();
|
|
170
|
+
const size = r.u32();
|
|
171
|
+
r.need(size);
|
|
172
|
+
const end = r.pos + size;
|
|
173
|
+
const b = new Reader(r.bytes, end);
|
|
174
|
+
b.pos = r.pos;
|
|
175
|
+
try {
|
|
176
|
+
if (type === 1) {
|
|
177
|
+
b.skip(4);
|
|
178
|
+
const spacing = b.u16();
|
|
179
|
+
const diameter = b.u16();
|
|
180
|
+
const roundness = b.u16();
|
|
181
|
+
const angle = b.i16();
|
|
182
|
+
const hardness = b.u16();
|
|
183
|
+
file.tips.push({ kind: "computed", diameter: clamp(diameter, 1, 2500), roundness: clamp(roundness / 100, 0.01, 1), angle: clamp(angle, -180, 180), hardness: clamp(hardness / 100, 0, 1), spacing: clamp(spacing / 100, 0.01, 10) });
|
|
184
|
+
} else if (type === 2) {
|
|
185
|
+
b.skip(4);
|
|
186
|
+
const spacing = b.u16();
|
|
187
|
+
let name;
|
|
188
|
+
if (version === 2) {
|
|
189
|
+
const n = b.u32();
|
|
190
|
+
if (n > 1024) throw new AbrError("A tip name is too long.");
|
|
191
|
+
const chars = [];
|
|
192
|
+
for (let k = 0; k < n; k++) chars.push(b.u16());
|
|
193
|
+
name = String.fromCharCode(...chars).replace(/\0+$/, "").trim() || void 0;
|
|
194
|
+
}
|
|
195
|
+
b.skip(1 + 8);
|
|
196
|
+
const top = b.i32();
|
|
197
|
+
const left = b.i32();
|
|
198
|
+
const bottom = b.i32();
|
|
199
|
+
const right = b.i32();
|
|
200
|
+
const depth = b.u16();
|
|
201
|
+
const compression = b.u8();
|
|
202
|
+
const width = right - left;
|
|
203
|
+
const height = bottom - top;
|
|
204
|
+
file.tips.push({ kind: "sampled", ...name ? { name } : {}, width, height, mask: readImage(b, width, height, depth, compression), ...spacing ? { spacing: clamp(spacing / 100, 0.01, 10) } : {} });
|
|
205
|
+
} else file.unsupported.push(`Brush ${i + 1} is of type ${type}, which is not read.`);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
file.unsupported.push(`Brush ${i + 1} could not be read: ${e.message}`);
|
|
208
|
+
}
|
|
209
|
+
r.pos = end;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function readDescriptor(bytes) {
|
|
213
|
+
const out = /* @__PURE__ */ new Map();
|
|
214
|
+
const find = (pattern, from) => {
|
|
215
|
+
outer: for (let p = from; p <= bytes.length - pattern.length; p++) {
|
|
216
|
+
for (let k = 0; k < pattern.length; k++) if (bytes[p + k] !== pattern[k]) continue outer;
|
|
217
|
+
return p;
|
|
218
|
+
}
|
|
219
|
+
return -1;
|
|
220
|
+
};
|
|
221
|
+
const code = (s) => [...s].map((c) => c.charCodeAt(0));
|
|
222
|
+
const text = (p) => {
|
|
223
|
+
const r = new Reader(bytes);
|
|
224
|
+
r.pos = p;
|
|
225
|
+
try {
|
|
226
|
+
const n = r.u32();
|
|
227
|
+
if (n > 512) return null;
|
|
228
|
+
const chars = [];
|
|
229
|
+
for (let k = 0; k < n; k++) chars.push(r.u16());
|
|
230
|
+
return String.fromCharCode(...chars).replace(/\0+$/, "");
|
|
231
|
+
} catch (e2) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const NAME = code("Nm TEXT");
|
|
236
|
+
const SAMPLED = code("sampledDataTEXT");
|
|
237
|
+
const SPACING = code("SpcnUntF#Prc");
|
|
238
|
+
let lastName;
|
|
239
|
+
let lastNameAt = -1;
|
|
240
|
+
for (let p = find(SAMPLED, 0); p >= 0; p = find(SAMPLED, p + SAMPLED.length)) {
|
|
241
|
+
for (let q = find(NAME, lastNameAt + 1); q >= 0 && q < p; q = find(NAME, q + NAME.length)) {
|
|
242
|
+
lastName = _nullishCoalesce(text(q + NAME.length), () => ( void 0));
|
|
243
|
+
lastNameAt = q;
|
|
244
|
+
}
|
|
245
|
+
const uuid = text(p + SAMPLED.length);
|
|
246
|
+
if (!uuid) continue;
|
|
247
|
+
const s = find(SPACING, p);
|
|
248
|
+
let spacing;
|
|
249
|
+
if (s >= 0 && s + SPACING.length + 8 <= bytes.length) {
|
|
250
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset + s + SPACING.length, 8).getFloat64(0);
|
|
251
|
+
if (Number.isFinite(v) && v > 0) spacing = clamp(v / 100, 0.01, 10);
|
|
252
|
+
}
|
|
253
|
+
if (!out.has(uuid)) out.set(uuid, { ...lastName ? { name: lastName.trim() } : {}, ...spacing !== void 0 ? { spacing } : {} });
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
function readV6(r, subversion, file) {
|
|
258
|
+
const samples = [];
|
|
259
|
+
let presets = /* @__PURE__ */ new Map();
|
|
260
|
+
while (r.pos + 12 <= r.end) {
|
|
261
|
+
const sig = r.ascii(4);
|
|
262
|
+
if (sig !== "8BIM" && sig !== "8B64") break;
|
|
263
|
+
const key = r.ascii(4);
|
|
264
|
+
const len = r.u32();
|
|
265
|
+
r.need(len);
|
|
266
|
+
const end = r.pos + len;
|
|
267
|
+
if (key === "samp") {
|
|
268
|
+
const s = new Reader(r.bytes, end);
|
|
269
|
+
s.pos = r.pos;
|
|
270
|
+
while (s.pos + 4 <= end && samples.length < MAX_TIPS) {
|
|
271
|
+
const size = s.u32();
|
|
272
|
+
const padded = size + (4 - size % 4) % 4;
|
|
273
|
+
const next = s.pos + padded;
|
|
274
|
+
if (s.pos + size > end) break;
|
|
275
|
+
const b = new Reader(r.bytes, s.pos + size);
|
|
276
|
+
b.pos = s.pos;
|
|
277
|
+
try {
|
|
278
|
+
const keyLen = b.u8();
|
|
279
|
+
const uuid = b.ascii(keyLen);
|
|
280
|
+
b.pos = s.pos + (subversion === 1 ? 47 : 301);
|
|
281
|
+
const top = b.i32();
|
|
282
|
+
const left = b.i32();
|
|
283
|
+
const bottom = b.i32();
|
|
284
|
+
const right = b.i32();
|
|
285
|
+
const depth = b.u16();
|
|
286
|
+
const compression = b.u8();
|
|
287
|
+
const width = right - left;
|
|
288
|
+
const height = bottom - top;
|
|
289
|
+
samples.push({ uuid, tip: { kind: "sampled", width, height, mask: readImage(b, width, height, depth, compression) } });
|
|
290
|
+
} catch (e) {
|
|
291
|
+
file.unsupported.push(`Sampled tip ${samples.length + 1} could not be read: ${e.message}`);
|
|
292
|
+
}
|
|
293
|
+
s.pos = Math.min(next, end);
|
|
294
|
+
}
|
|
295
|
+
} else if (key === "desc") presets = readDescriptor(r.bytes.subarray(r.pos, end));
|
|
296
|
+
else if (key === "patt") file.unsupported.push("Patterns (the brushes' textures) are not imported; add a pattern image as a grain source with add_brush_source.");
|
|
297
|
+
r.pos = end + len % 2;
|
|
298
|
+
}
|
|
299
|
+
for (const { uuid, tip } of samples) {
|
|
300
|
+
const p = presets.get(uuid);
|
|
301
|
+
file.tips.push({ ...tip, ..._optionalChain([p, 'optionalAccess', _ => _.name]) ? { name: p.name } : {}, ..._optionalChain([p, 'optionalAccess', _2 => _2.spacing]) !== void 0 ? { spacing: p.spacing } : {} });
|
|
302
|
+
}
|
|
303
|
+
if (presets.size) file.unsupported.push("Preset dynamics, textures, dual brushes and colour settings are not mapped; each tip imports with its name and spacing.");
|
|
304
|
+
}
|
|
305
|
+
function readAbr(bytes) {
|
|
306
|
+
const r = new Reader(bytes);
|
|
307
|
+
let version;
|
|
308
|
+
try {
|
|
309
|
+
version = r.u16();
|
|
310
|
+
} catch (e3) {
|
|
311
|
+
throw new AbrError("Not a brush file: it is empty.");
|
|
312
|
+
}
|
|
313
|
+
const file = { version, tips: [], unsupported: [] };
|
|
314
|
+
try {
|
|
315
|
+
if (version === 1 || version === 2) readV1(r, version, file);
|
|
316
|
+
else if (version === 6 || version === 7 || version === 10) {
|
|
317
|
+
const subversion = r.u16();
|
|
318
|
+
if (subversion !== 1 && subversion !== 2) throw new AbrError(`ABR version ${version}.${subversion} is not supported.`);
|
|
319
|
+
readV6(r, subversion, file);
|
|
320
|
+
} else throw new AbrError(`Not a Photoshop brush file, or ABR version ${version}, which is not supported (1, 2, 6, 7 and 10 are).`);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
if (!file.tips.length) throw e instanceof AbrError ? e : new AbrError(e.message);
|
|
323
|
+
file.unsupported.push(`The rest of the file could not be read: ${e.message}`);
|
|
324
|
+
}
|
|
325
|
+
return file;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/brushes/import/bplist.ts
|
|
329
|
+
var PlistError = class extends Error {
|
|
330
|
+
constructor(message) {
|
|
331
|
+
super(message);
|
|
332
|
+
this.name = "PlistError";
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
var MAX_OBJECTS = 2e5;
|
|
336
|
+
var MAX_DEPTH = 64;
|
|
337
|
+
function isBinaryPlist(bytes) {
|
|
338
|
+
return bytes.length >= 40 && String.fromCharCode(...bytes.subarray(0, 8)) === "bplist00";
|
|
339
|
+
}
|
|
340
|
+
function readBinaryPlist(bytes) {
|
|
341
|
+
if (!isBinaryPlist(bytes)) throw new PlistError("Not a binary property list.");
|
|
342
|
+
const t = bytes.length - 32;
|
|
343
|
+
const offsetSize = bytes[t + 6];
|
|
344
|
+
const refSize = bytes[t + 7];
|
|
345
|
+
const uint = (p, n) => {
|
|
346
|
+
if (p < 0 || p + n > bytes.length) throw new PlistError("A property list offset is out of bounds.");
|
|
347
|
+
let v = 0;
|
|
348
|
+
for (let i = 0; i < n; i++) v = v * 256 + bytes[p + i];
|
|
349
|
+
return v;
|
|
350
|
+
};
|
|
351
|
+
const count = uint(t + 8, 8);
|
|
352
|
+
const top = uint(t + 16, 8);
|
|
353
|
+
const tableAt = uint(t + 24, 8);
|
|
354
|
+
if (![1, 2, 4, 8].includes(offsetSize) || ![1, 2, 4, 8].includes(refSize)) throw new PlistError("The property list trailer is malformed.");
|
|
355
|
+
if (count > MAX_OBJECTS || top >= count || tableAt + count * offsetSize > t) throw new PlistError("The property list is malformed or too large.");
|
|
356
|
+
const offsetOf = (i) => {
|
|
357
|
+
if (i >= count) throw new PlistError("A property list reference is out of range.");
|
|
358
|
+
const o = uint(tableAt + i * offsetSize, offsetSize);
|
|
359
|
+
if (o >= tableAt) throw new PlistError("A property list object is out of bounds.");
|
|
360
|
+
return o;
|
|
361
|
+
};
|
|
362
|
+
const reading = /* @__PURE__ */ new Set();
|
|
363
|
+
const read = (index, depth) => {
|
|
364
|
+
if (depth > MAX_DEPTH) throw new PlistError("The property list is nested too deeply.");
|
|
365
|
+
if (reading.has(index)) throw new PlistError("The property list refers to itself.");
|
|
366
|
+
let p = offsetOf(index);
|
|
367
|
+
const marker = bytes[p];
|
|
368
|
+
const kind = marker >> 4;
|
|
369
|
+
let info = marker & 15;
|
|
370
|
+
p++;
|
|
371
|
+
const length = () => {
|
|
372
|
+
if (info !== 15) return info;
|
|
373
|
+
const m = bytes[p];
|
|
374
|
+
if (m >> 4 !== 1) throw new PlistError("A property list length is malformed.");
|
|
375
|
+
const n = 1 << (m & 15);
|
|
376
|
+
const v = uint(p + 1, n);
|
|
377
|
+
p += 1 + n;
|
|
378
|
+
return v;
|
|
379
|
+
};
|
|
380
|
+
switch (kind) {
|
|
381
|
+
case 0:
|
|
382
|
+
return info === 8 ? false : info === 9 ? true : null;
|
|
383
|
+
case 1: {
|
|
384
|
+
const n = 1 << info;
|
|
385
|
+
if (n === 8) {
|
|
386
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset + p, 8).getBigInt64(0);
|
|
387
|
+
return Number(v);
|
|
388
|
+
}
|
|
389
|
+
return uint(p, n);
|
|
390
|
+
}
|
|
391
|
+
case 2: {
|
|
392
|
+
const n = 1 << info;
|
|
393
|
+
if (p + n > bytes.length) throw new PlistError("A property list number is out of bounds.");
|
|
394
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + p, n);
|
|
395
|
+
return n === 4 ? view.getFloat32(0) : n === 8 ? view.getFloat64(0) : 0;
|
|
396
|
+
}
|
|
397
|
+
case 3:
|
|
398
|
+
return p + 8 <= bytes.length ? new DataView(bytes.buffer, bytes.byteOffset + p, 8).getFloat64(0) : 0;
|
|
399
|
+
case 4: {
|
|
400
|
+
const n = length();
|
|
401
|
+
if (p + n > bytes.length) throw new PlistError("Property list data is out of bounds.");
|
|
402
|
+
return bytes.slice(p, p + n);
|
|
403
|
+
}
|
|
404
|
+
case 5: {
|
|
405
|
+
const n = length();
|
|
406
|
+
if (p + n > bytes.length) throw new PlistError("A property list string is out of bounds.");
|
|
407
|
+
return String.fromCharCode(...bytes.subarray(p, p + n));
|
|
408
|
+
}
|
|
409
|
+
case 6: {
|
|
410
|
+
const n = length();
|
|
411
|
+
if (p + n * 2 > bytes.length) throw new PlistError("A property list string is out of bounds.");
|
|
412
|
+
let s = "";
|
|
413
|
+
for (let i = 0; i < n; i++) s += String.fromCharCode(bytes[p + i * 2] << 8 | bytes[p + i * 2 + 1]);
|
|
414
|
+
return s;
|
|
415
|
+
}
|
|
416
|
+
case 8:
|
|
417
|
+
return { uid: uint(p, info + 1) };
|
|
418
|
+
case 10:
|
|
419
|
+
case 12: {
|
|
420
|
+
const n = length();
|
|
421
|
+
reading.add(index);
|
|
422
|
+
const out = [];
|
|
423
|
+
for (let i = 0; i < n; i++) out.push(read(uint(p + i * refSize, refSize), depth + 1));
|
|
424
|
+
reading.delete(index);
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
427
|
+
case 13: {
|
|
428
|
+
const n = length();
|
|
429
|
+
reading.add(index);
|
|
430
|
+
const out = {};
|
|
431
|
+
for (let i = 0; i < n; i++) {
|
|
432
|
+
const key = read(uint(p + i * refSize, refSize), depth + 1);
|
|
433
|
+
if (typeof key !== "string") throw new PlistError("A property list key is not a string.");
|
|
434
|
+
out[key] = read(uint(p + (n + i) * refSize, refSize), depth + 1);
|
|
435
|
+
}
|
|
436
|
+
reading.delete(index);
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
default:
|
|
440
|
+
throw new PlistError(`Property list object type ${kind.toString(16)} is not supported.`);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
return read(top, 0);
|
|
444
|
+
}
|
|
445
|
+
function keyedArchiveRoot(plist) {
|
|
446
|
+
const top = plist;
|
|
447
|
+
const objects = Array.isArray(_optionalChain([top, 'optionalAccess', _3 => _3.$objects])) ? top.$objects : null;
|
|
448
|
+
const root = _optionalChain([top, 'optionalAccess', _4 => _4.$top, 'optionalAccess', _5 => _5.root]);
|
|
449
|
+
if (!objects || !root || typeof root !== "object" || !("uid" in root)) return null;
|
|
450
|
+
const at = (v) => v && typeof v === "object" && "uid" in v && typeof v.uid === "number" ? _nullishCoalesce(objects[v.uid], () => ( null)) : v;
|
|
451
|
+
const obj = at(root);
|
|
452
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj) || obj instanceof Uint8Array) return null;
|
|
453
|
+
const out = {};
|
|
454
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
455
|
+
const value = at(v);
|
|
456
|
+
out[k] = value === "$null" ? null : value;
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/brushes/import/zip.ts
|
|
462
|
+
var ZipError = class extends Error {
|
|
463
|
+
constructor(message) {
|
|
464
|
+
super(message);
|
|
465
|
+
this.name = "ZipError";
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
var u16 = (b, p) => b[p] | b[p + 1] << 8;
|
|
469
|
+
var u32 = (b, p) => (b[p] | b[p + 1] << 8 | b[p + 2] << 16 | b[p + 3] << 24) >>> 0;
|
|
470
|
+
function isZip(bytes) {
|
|
471
|
+
return bytes.length >= 22 && bytes[0] === 80 && bytes[1] === 75 && (bytes[2] === 3 || bytes[2] === 5) && (bytes[3] === 4 || bytes[3] === 6);
|
|
472
|
+
}
|
|
473
|
+
function readZip(bytes, limits = {}) {
|
|
474
|
+
const maxEntries = _nullishCoalesce(limits.maxEntries, () => ( 4096));
|
|
475
|
+
const maxEntry = _nullishCoalesce(limits.maxEntrySize, () => ( 64 * 1024 * 1024));
|
|
476
|
+
const maxTotal = _nullishCoalesce(limits.maxTotalSize, () => ( 512 * 1024 * 1024));
|
|
477
|
+
let eocd = -1;
|
|
478
|
+
for (let p2 = bytes.length - 22; p2 >= Math.max(0, bytes.length - 22 - 65535); p2--) {
|
|
479
|
+
if (u32(bytes, p2) === 101010256) {
|
|
480
|
+
eocd = p2;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (eocd < 0) throw new ZipError("Not a ZIP archive: no end of central directory.");
|
|
485
|
+
const count = u16(bytes, eocd + 10);
|
|
486
|
+
const dirSize = u32(bytes, eocd + 12);
|
|
487
|
+
const dirOffset = u32(bytes, eocd + 16);
|
|
488
|
+
if (count === 65535 || dirOffset === 4294967295) throw new ZipError("ZIP64 archives are not supported.");
|
|
489
|
+
if (count > maxEntries) throw new ZipError(`The archive has ${count} entries; at most ${maxEntries} are read.`);
|
|
490
|
+
if (dirOffset + dirSize > eocd) throw new ZipError("The ZIP central directory is out of bounds.");
|
|
491
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
492
|
+
const entries = [];
|
|
493
|
+
let total = 0;
|
|
494
|
+
let p = dirOffset;
|
|
495
|
+
for (let i = 0; i < count; i++) {
|
|
496
|
+
if (p + 46 > eocd || u32(bytes, p) !== 33639248) throw new ZipError(`ZIP directory entry ${i} is malformed.`);
|
|
497
|
+
const flags = u16(bytes, p + 8);
|
|
498
|
+
const method = u16(bytes, p + 10);
|
|
499
|
+
const csize = u32(bytes, p + 20);
|
|
500
|
+
const size = u32(bytes, p + 24);
|
|
501
|
+
const nameLen = u16(bytes, p + 28);
|
|
502
|
+
const extraLen = u16(bytes, p + 30);
|
|
503
|
+
const commentLen = u16(bytes, p + 32);
|
|
504
|
+
const local = u32(bytes, p + 42);
|
|
505
|
+
if (p + 46 + nameLen > eocd) throw new ZipError(`ZIP directory entry ${i} is malformed.`);
|
|
506
|
+
const name = decoder.decode(bytes.subarray(p + 46, p + 46 + nameLen)).replace(/\\/g, "/");
|
|
507
|
+
p += 46 + nameLen + extraLen + commentLen;
|
|
508
|
+
if (name.endsWith("/")) continue;
|
|
509
|
+
if (flags & 1) throw new ZipError(`"${name}" is encrypted; encrypted archives are not supported.`);
|
|
510
|
+
if (method !== 0 && method !== 8) throw new ZipError(`"${name}" uses compression method ${method}; only stored and deflated files are read.`);
|
|
511
|
+
if (size > maxEntry) throw new ZipError(`"${name}" is ${size} bytes; files over ${maxEntry} bytes are not read.`);
|
|
512
|
+
total += size;
|
|
513
|
+
if (total > maxTotal) throw new ZipError(`The archive unpacks to more than ${maxTotal} bytes.`);
|
|
514
|
+
if (local + 30 > bytes.length || u32(bytes, local) !== 67324752) throw new ZipError(`"${name}" has no local header.`);
|
|
515
|
+
const start = local + 30 + u16(bytes, local + 26) + u16(bytes, local + 28);
|
|
516
|
+
if (start + csize > bytes.length) throw new ZipError(`"${name}" runs past the end of the archive.`);
|
|
517
|
+
let cached = null;
|
|
518
|
+
entries.push({
|
|
519
|
+
name,
|
|
520
|
+
size,
|
|
521
|
+
read() {
|
|
522
|
+
if (cached) return cached;
|
|
523
|
+
const raw = bytes.subarray(start, start + csize);
|
|
524
|
+
const out = method === 0 ? raw : _chunkXFM3OCHUcjs.inflateRaw.call(void 0, raw, { maxOutput: Math.max(size, 1), sizeHint: size });
|
|
525
|
+
if (out.length !== size) throw new ZipError(`"${name}" unpacked to ${out.length} bytes, not the ${size} its directory says.`);
|
|
526
|
+
cached = out;
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return entries;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/brushes/import/pack.ts
|
|
535
|
+
var BRUSH_PACK_FORMATS = ["abr", "brush", "brushset"];
|
|
536
|
+
var MAX_BRUSH_PACK_BYTES = 64 * 1024 * 1024;
|
|
537
|
+
var MAX_PACK_BRUSHES = 300;
|
|
538
|
+
var BrushPackError = class extends Error {
|
|
539
|
+
constructor(message) {
|
|
540
|
+
super(message);
|
|
541
|
+
this.name = "BrushPackError";
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
var slug = (s, max = 40) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max).replace(/-+$/, "");
|
|
545
|
+
var stem = (fileName) => fileName.replace(/^.*[\\/]/, "").replace(/\.[a-z0-9]+$/i, "");
|
|
546
|
+
var clamp2 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
547
|
+
function brushPackFormat(bytes, fileName, format) {
|
|
548
|
+
if (format) return format;
|
|
549
|
+
const ext = _optionalChain([/\.([a-z]+)$/i, 'access', _6 => _6.exec, 'call', _7 => _7(fileName), 'optionalAccess', _8 => _8[1], 'optionalAccess', _9 => _9.toLowerCase, 'call', _10 => _10()]);
|
|
550
|
+
if (ext === "abr" || ext === "brush" || ext === "brushset") return ext;
|
|
551
|
+
if (isZip(bytes)) return "brushset";
|
|
552
|
+
if (bytes.length >= 2 && [1, 2, 6, 7, 10].includes(bytes[0] << 8 | bytes[1])) return "abr";
|
|
553
|
+
throw new BrushPackError(`"${fileName}" is not a brush pack this reads: give a .abr (Photoshop), .brush or .brushset (Procreate) file.`);
|
|
554
|
+
}
|
|
555
|
+
function idMaker(prefix) {
|
|
556
|
+
const used = /* @__PURE__ */ new Set();
|
|
557
|
+
return (name) => {
|
|
558
|
+
const base = `${prefix}-${slug(name, 20) || "brush"}`.slice(0, 58).replace(/-+$/, "");
|
|
559
|
+
let id = base;
|
|
560
|
+
for (let n = 2; used.has(id) || !_chunkEU2ZZA6Lcjs.BRUSH_ID.test(id); n++) id = `${base}-${n}`;
|
|
561
|
+
used.add(id);
|
|
562
|
+
return id;
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function licenceText(name, bytes) {
|
|
566
|
+
let text = new TextDecoder("utf-8", { fatal: false }).decode(bytes.subarray(0, 64 * 1024));
|
|
567
|
+
if (/\.rtf$/i.test(name)) text = text.replace(/\\[a-z]+-?\d* ?|[{}]/gi, "");
|
|
568
|
+
text = text.replace(/\r\n?/g, "\n").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
569
|
+
return text ? `${stem(name)}: ${text.slice(0, 4e3)}` : null;
|
|
570
|
+
}
|
|
571
|
+
var LICENCE_FILE = /(^|\/)[^/]*(licen[cs]e|terms|eula|readme|copyright)[^/]*\.(txt|md|rtf)$/i;
|
|
572
|
+
function readAbrPack(bytes, name, prefix, licence, pack) {
|
|
573
|
+
const file = readAbr(bytes);
|
|
574
|
+
pack.unsupported.push(...file.unsupported);
|
|
575
|
+
const id = idMaker(prefix);
|
|
576
|
+
file.tips.slice(0, MAX_PACK_BRUSHES).forEach((tip, i) => {
|
|
577
|
+
const label = _nullishCoalesce(tip.name, () => ( `${name} ${i + 1}`));
|
|
578
|
+
const brushId = id(_nullishCoalesce(tip.name, () => ( String(i + 1))));
|
|
579
|
+
const base = { id: brushId, name: label.slice(0, 80), kind: "mark", tags: ["imported"], description: `Imported from ${name}.abr.` };
|
|
580
|
+
if (tip.kind === "computed") {
|
|
581
|
+
pack.brushes.push({ ...base, tip: { shape: tip.roundness < 1 ? "flat" : "round", size: tip.diameter, hardness: tip.hardness, spacing: tip.spacing, roundness: tip.roundness, angle: tip.angle } });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const source = _chunkEU2ZZA6Lcjs.brushSourceFromMask.call(void 0, { id: `${brushId}-shape`, name: label, licence, origin: `${name}.abr` }, tip.width, tip.height, tip.mask);
|
|
586
|
+
pack.sources.push(source);
|
|
587
|
+
pack.brushes.push({ ...base, tip: { shape: "stamp", size: clamp2(Math.max(tip.width, tip.height), 1, 2500), spacing: _nullishCoalesce(tip.spacing, () => ( 0.25)), stamps: [_chunkEU2ZZA6Lcjs.brushSourceRef.call(void 0, source.id)] } });
|
|
588
|
+
} catch (e) {
|
|
589
|
+
pack.unsupported.push(`"${label}": ${e.message}`);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
if (file.tips.length > MAX_PACK_BRUSHES) pack.unsupported.push(`Only the first ${MAX_PACK_BRUSHES} of ${file.tips.length} tips were imported.`);
|
|
593
|
+
}
|
|
594
|
+
function readProcreatePack(bytes, name, prefix, licence, pack) {
|
|
595
|
+
let entries;
|
|
596
|
+
try {
|
|
597
|
+
entries = readZip(bytes);
|
|
598
|
+
} catch (e) {
|
|
599
|
+
throw new BrushPackError(`"${name}" can't be read as a Procreate brush pack: ${e.message}`);
|
|
600
|
+
}
|
|
601
|
+
const byName = new Map(entries.map((e) => [e.name, e]));
|
|
602
|
+
const found = [];
|
|
603
|
+
for (const e of entries) {
|
|
604
|
+
if (!LICENCE_FILE.test(e.name) || e.size > 1024 * 1024) continue;
|
|
605
|
+
const text = licenceText(e.name, e.read());
|
|
606
|
+
if (text) found.push(text);
|
|
607
|
+
}
|
|
608
|
+
pack.licence.push(...found);
|
|
609
|
+
const terms = found.length ? found.join("\n\n").slice(0, 2e3) : void 0;
|
|
610
|
+
const dirs = entries.filter((e) => /(^|\/)Brush\.archive$/.test(e.name)).map((e) => e.name.slice(0, -"Brush.archive".length));
|
|
611
|
+
if (!dirs.length) throw new BrushPackError(`"${name}" has no Procreate brushes (no Brush.archive inside).`);
|
|
612
|
+
const id = idMaker(prefix);
|
|
613
|
+
dirs.slice(0, MAX_PACK_BRUSHES).forEach((dir, i) => {
|
|
614
|
+
let settings = null;
|
|
615
|
+
try {
|
|
616
|
+
const archive = byName.get(`${dir}Brush.archive`).read();
|
|
617
|
+
if (isBinaryPlist(archive)) settings = keyedArchiveRoot(readBinaryPlist(archive));
|
|
618
|
+
} catch (e4) {
|
|
619
|
+
settings = null;
|
|
620
|
+
}
|
|
621
|
+
const own = typeof _optionalChain([settings, 'optionalAccess', _11 => _11.name]) === "string" && settings.name.trim() ? settings.name.trim() : null;
|
|
622
|
+
const label = (_nullishCoalesce(own, () => ( (dirs.length === 1 ? name : `${name} ${i + 1}`)))).slice(0, 80);
|
|
623
|
+
const brushId = id(_nullishCoalesce(own, () => ( String(i + 1))));
|
|
624
|
+
const where = `${name}${dir ? `/${dir.replace(/\/$/, "")}` : ""}`;
|
|
625
|
+
const source = (file, kind) => {
|
|
626
|
+
const e = byName.get(`${dir}${file}`);
|
|
627
|
+
if (!e) return null;
|
|
628
|
+
try {
|
|
629
|
+
const s = _chunkEU2ZZA6Lcjs.makeBrushSource.call(void 0, { id: `${brushId}-${kind}`, name: `${label} ${kind}`, image: e.read(), channel: "lightness", licence: [licence, terms].filter(Boolean).join("\n\n") || void 0, origin: `${where}/${file}` });
|
|
630
|
+
pack.sources.push(s);
|
|
631
|
+
return s;
|
|
632
|
+
} catch (err) {
|
|
633
|
+
pack.unsupported.push(`"${label}" ${kind}: ${err.message}`);
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
const shape = source("Shape.png", "shape");
|
|
638
|
+
const grain = source("Grain.png", "grain");
|
|
639
|
+
const plot = typeof _optionalChain([settings, 'optionalAccess', _12 => _12.plotSpacing]) === "number" && settings.plotSpacing > 0 ? clamp2(settings.plotSpacing, 0.01, 10) : void 0;
|
|
640
|
+
pack.brushes.push({
|
|
641
|
+
id: brushId,
|
|
642
|
+
name: label,
|
|
643
|
+
kind: "mark",
|
|
644
|
+
tags: ["imported", "procreate"],
|
|
645
|
+
description: `Imported from ${name}.`,
|
|
646
|
+
tip: {
|
|
647
|
+
shape: shape ? "stamp" : "round",
|
|
648
|
+
size: 40,
|
|
649
|
+
...plot !== void 0 ? { spacing: plot } : {},
|
|
650
|
+
...shape ? { stamps: [_chunkEU2ZZA6Lcjs.brushSourceRef.call(void 0, shape.id)] } : {},
|
|
651
|
+
...grain ? { grain: { source: _chunkEU2ZZA6Lcjs.brushSourceRef.call(void 0, grain.id), mode: "moving" } } : {}
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
if (!settings) pack.unsupported.push(`"${label}": its Brush.archive could not be read; the brush takes its name from the file and default settings.`);
|
|
655
|
+
});
|
|
656
|
+
if (dirs.length > MAX_PACK_BRUSHES) pack.unsupported.push(`Only the first ${MAX_PACK_BRUSHES} of ${dirs.length} brushes were imported.`);
|
|
657
|
+
pack.unsupported.push("Procreate settings other than the name, shape, grain and spacing (dynamics, Apple Pencil curves, taper, wet mix, colour dynamics, rendering modes) are not mapped.");
|
|
658
|
+
}
|
|
659
|
+
function readBrushPack(bytes, options) {
|
|
660
|
+
if (bytes.length > MAX_BRUSH_PACK_BYTES) throw new BrushPackError(`"${options.fileName}" is ${bytes.length} bytes; packs over ${MAX_BRUSH_PACK_BYTES} bytes are not read.`);
|
|
661
|
+
const format = brushPackFormat(bytes, options.fileName, options.format);
|
|
662
|
+
const name = stem(options.fileName) || "Brushes";
|
|
663
|
+
const prefix = slug(_nullishCoalesce(options.prefix, () => ( name))) || "pack";
|
|
664
|
+
const licence = _optionalChain([options, 'access', _13 => _13.licence, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()]) ? options.licence.trim().slice(0, 2e3) : void 0;
|
|
665
|
+
const pack = { format, brushes: [], sources: [], unsupported: [], licence: licence ? [licence] : [] };
|
|
666
|
+
try {
|
|
667
|
+
if (format === "abr") readAbrPack(bytes, name, prefix, licence, pack);
|
|
668
|
+
else readProcreatePack(bytes, name, prefix, licence, pack);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
throw e instanceof BrushPackError ? e : new BrushPackError(`"${options.fileName}" can't be read: ${e.message}`);
|
|
671
|
+
}
|
|
672
|
+
if (!pack.brushes.length) throw new BrushPackError(`"${options.fileName}" has no brushes that can be imported. ${pack.unsupported.join(" ")}`.trim());
|
|
673
|
+
return pack;
|
|
674
|
+
}
|
|
675
|
+
async function importBrushPack(library, bytes, options) {
|
|
676
|
+
const pack = readBrushPack(bytes, options);
|
|
677
|
+
const r = await library.importFile({ format: "pixerate-brushes", version: 2, brushes: pack.brushes, sources: pack.sources }, { overwrite: !!options.overwrite });
|
|
678
|
+
const done = /* @__PURE__ */ new Set([...r.added, ...r.replaced]);
|
|
679
|
+
return {
|
|
680
|
+
format: pack.format,
|
|
681
|
+
brushes: pack.brushes.filter((b) => done.has(b.id)).map((b) => ({ id: b.id, name: b.name })),
|
|
682
|
+
sources: _nullishCoalesce(r.sources, () => ( [])),
|
|
683
|
+
skipped: r.skipped,
|
|
684
|
+
unsupported: pack.unsupported,
|
|
685
|
+
licence: pack.licence,
|
|
686
|
+
problems: r.problems
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
async function importBrushFile(library, file, options = {}) {
|
|
690
|
+
if (/\.json$/i.test(file.name)) {
|
|
691
|
+
let raw;
|
|
692
|
+
try {
|
|
693
|
+
raw = JSON.parse(await file.text());
|
|
694
|
+
} catch (e5) {
|
|
695
|
+
throw new BrushPackError(`"${file.name}" is not JSON.`);
|
|
696
|
+
}
|
|
697
|
+
return library.importFile(raw, { overwrite: !!options.overwrite });
|
|
698
|
+
}
|
|
699
|
+
return importBrushPack(library, new Uint8Array(await file.arrayBuffer()), { fileName: file.name, ...options });
|
|
700
|
+
}
|
|
701
|
+
|
|
61
702
|
// src/brushes/tools.ts
|
|
62
703
|
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
63
704
|
var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
@@ -84,10 +725,10 @@ function summary(entry, profile, withCheck) {
|
|
|
84
725
|
engine,
|
|
85
726
|
tip: b.tip.shape,
|
|
86
727
|
...b.tip.size !== void 0 ? { size: b.tip.size } : {},
|
|
87
|
-
marks: _nullishCoalesce(_optionalChain([b, 'access',
|
|
88
|
-
surface: _nullishCoalesce(_optionalChain([b, 'access',
|
|
89
|
-
..._optionalChain([b, 'access',
|
|
90
|
-
..._optionalChain([b, 'access',
|
|
728
|
+
marks: _nullishCoalesce(_optionalChain([b, 'access', _16 => _16.marks, 'optionalAccess', _17 => _17.kind]), () => ( "continuous")),
|
|
729
|
+
surface: _nullishCoalesce(_optionalChain([b, 'access', _18 => _18.behaviour, 'optionalAccess', _19 => _19.surface]), () => ( "dry")),
|
|
730
|
+
..._optionalChain([b, 'access', _20 => _20.light, 'optionalAccess', _21 => _21.amount]) ? { light: b.light.amount } : {},
|
|
731
|
+
..._optionalChain([b, 'access', _22 => _22.tags, 'optionalAccess', _23 => _23.length]) ? { tags: b.tags } : {},
|
|
91
732
|
...check ? { renderable: check.ok } : {}
|
|
92
733
|
};
|
|
93
734
|
}
|
|
@@ -117,7 +758,7 @@ function createBrushLibraryTools(library, options = {}) {
|
|
|
117
758
|
const ids = (await library.listSources()).map((x) => x.id);
|
|
118
759
|
issues.push({ facet, severity: "error", reason: `There is no image source "${id}" in the brush library. ${_chunkEU2ZZA6Lcjs.closest.call(void 0, id, ids)}`.trim(), fix: "Add it with add_brush_source, or use a ref list_brush_sources lists." });
|
|
119
760
|
}
|
|
120
|
-
const provider = _optionalChain([options, 'access',
|
|
761
|
+
const provider = _optionalChain([options, 'access', _24 => _24.stickers, 'optionalCall', _25 => _25()]);
|
|
121
762
|
if (!provider || brush.tip.shape !== "stamp") return { ok: !issues.some((i) => i.severity === "error"), issues };
|
|
122
763
|
for (const ref of _nullishCoalesce(brush.tip.stamps, () => ( []))) {
|
|
123
764
|
if (ref.startsWith("editor:") || _chunkEU2ZZA6Lcjs.isBrushSourceRef.call(void 0, ref)) continue;
|
|
@@ -270,7 +911,7 @@ function createBrushLibraryTools(library, options = {}) {
|
|
|
270
911
|
annotations: { title: "Preview a brush", ...READ },
|
|
271
912
|
handler: async ({ id, width, height, color }) => {
|
|
272
913
|
const { brush } = await entry(id);
|
|
273
|
-
const own = typeof _optionalChain([brush, 'access',
|
|
914
|
+
const own = typeof _optionalChain([brush, 'access', _26 => _26.colour, 'optionalAccess', _27 => _27.default]) === "string" && /^#[0-9a-f]{6}/i.test(brush.colour.default) ? brush.colour.default.slice(0, 7) : void 0;
|
|
274
915
|
const w = _nullishCoalesce(width, () => ( 256));
|
|
275
916
|
const h = _nullishCoalesce(height, () => ( 96));
|
|
276
917
|
const image = await render(brush, { width: w, height: h, color: _nullishCoalesce(_nullishCoalesce(color, () => ( own)), () => ( "#1f2937")) });
|
|
@@ -402,6 +1043,42 @@ function createBrushLibraryTools(library, options = {}) {
|
|
|
402
1043
|
},
|
|
403
1044
|
annotations: { title: "Import brushes", ...WRITE, destructiveHint: true },
|
|
404
1045
|
handler: async ({ file, overwrite }) => library.importFile(file, { overwrite: !!overwrite })
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
name: "import_brush_pack",
|
|
1049
|
+
title: "Import a brush pack",
|
|
1050
|
+
description: `Imports a brush pack someone already owns: Photoshop .abr (sampled tips become image sources and stamp-tip brushes, computed tips round or flat brushes; preset names and spacing where the file has them) or Procreate .brush / .brushset (each brush's shape becomes its image tip and its grain its tip grain, with its name and spacing). Brushes and sources are added to the library in one import; ids already saved are skipped unless overwrite. Returns format, brushes (id, name, renderable: whether this host, ${profile.name}, can paint it), sources (ids), skipped, unsupported (what was not imported or mapped, in words), licence (the licence given and any licence or terms files found in the pack, also kept on each source) and problems. Packs are often licensed for personal use only: pass the licence they came with.`,
|
|
1051
|
+
inputSchema: {
|
|
1052
|
+
type: "object",
|
|
1053
|
+
properties: {
|
|
1054
|
+
file: { type: "string", minLength: 4, maxLength: Math.ceil(MAX_BRUSH_PACK_BYTES * 4 / 3) + 4, description: `The pack's bytes, base64 (at most ${MAX_BRUSH_PACK_BYTES / 1024 / 1024} MB).` },
|
|
1055
|
+
fileName: { type: "string", minLength: 1, maxLength: 200, description: 'The file name, e.g. "Inky.abr": its extension picks the format and its name prefixes the brush ids and names.' },
|
|
1056
|
+
format: { type: "string", enum: [...BRUSH_PACK_FORMATS], description: "abr, brush or brushset (default: from the file name, then the contents)." },
|
|
1057
|
+
prefix: { type: "string", pattern: "^[a-z0-9][a-z0-9-]{0,39}$", description: "Id prefix for the brushes and sources (default: from the file name)." },
|
|
1058
|
+
licence: { type: "string", maxLength: 2e3, description: "The licence or terms the pack came with, kept on every source it adds." },
|
|
1059
|
+
overwrite: { type: "boolean", description: "Replace saved brushes and sources with the same ids (default false: skip them)." }
|
|
1060
|
+
},
|
|
1061
|
+
required: ["file", "fileName"],
|
|
1062
|
+
additionalProperties: false
|
|
1063
|
+
},
|
|
1064
|
+
annotations: { title: "Import a brush pack", ...WRITE, destructiveHint: true },
|
|
1065
|
+
handler: async ({ file, fileName, format, prefix, licence, overwrite }) => {
|
|
1066
|
+
let bytes;
|
|
1067
|
+
try {
|
|
1068
|
+
bytes = _chunkW5M3J7PVcjs.base64ToBytes.call(void 0, file.replace(/^data:[^,]*,/, ""));
|
|
1069
|
+
} catch (e6) {
|
|
1070
|
+
throw new (0, _chunkEU2ZZA6Lcjs.BrushLibraryError)("invalid", "file is not base64: pass the pack's bytes as base64 (a data: URL works too).");
|
|
1071
|
+
}
|
|
1072
|
+
let r;
|
|
1073
|
+
try {
|
|
1074
|
+
r = await importBrushPack(library, bytes, { fileName, ...format ? { format } : {}, ...prefix ? { prefix } : {}, ...licence ? { licence } : {}, overwrite: !!overwrite });
|
|
1075
|
+
} catch (e) {
|
|
1076
|
+
if (e instanceof _chunkEU2ZZA6Lcjs.BrushLibraryError) throw e;
|
|
1077
|
+
throw new (0, _chunkEU2ZZA6Lcjs.BrushLibraryError)("invalid", `${e.message} Nothing was imported.`);
|
|
1078
|
+
}
|
|
1079
|
+
const entries = await Promise.all(r.brushes.map((b) => library.get(b.id)));
|
|
1080
|
+
return { ...r, brushes: r.brushes.map((b, i) => ({ ...b, renderable: entries[i] ? _chunkEU2ZZA6Lcjs.checkBrush.call(void 0, entries[i].brush, profile).ok : false })) };
|
|
1081
|
+
}
|
|
405
1082
|
}
|
|
406
1083
|
);
|
|
407
1084
|
return tools;
|
|
@@ -442,7 +1119,7 @@ var PICKUP_SLIDERS = [
|
|
|
442
1119
|
];
|
|
443
1120
|
function brushEngineControls(brush) {
|
|
444
1121
|
const c = engineControls(brush);
|
|
445
|
-
const wetPaint = brush.engine === "wet" && _optionalChain([brush, 'access',
|
|
1122
|
+
const wetPaint = brush.engine === "wet" && _optionalChain([brush, 'access', _28 => _28.wet, 'optionalAccess', _29 => _29.tool]) === "paint";
|
|
446
1123
|
if (!c || !(wetPaint || ["dab", "mixer", "particle", "bristle", "impasto"].includes(brush.engine))) return c;
|
|
447
1124
|
const extra = brush.engine === "impasto" ? [{ path: "impasto.rim", label: "Rim", min: 0, max: 1, step: 0.01 }, { path: "impasto.clarity", label: "Clarity", min: 0, max: 1, step: 0.01 }] : [];
|
|
448
1125
|
return { ...c, sliders: [...c.sliders, LIGHT_SLIDER, ...extra], light: true };
|
|
@@ -525,7 +1202,7 @@ function engineControls(brush) {
|
|
|
525
1202
|
impastoTool: true
|
|
526
1203
|
};
|
|
527
1204
|
case "wet": {
|
|
528
|
-
const tool = _nullishCoalesce(_optionalChain([brush, 'access',
|
|
1205
|
+
const tool = _nullishCoalesce(_optionalChain([brush, 'access', _30 => _30.wet, 'optionalAccess', _31 => _31.tool]), () => ( "paint"));
|
|
529
1206
|
const sliders = tool === "paint" ? [
|
|
530
1207
|
{ path: "wet.water", label: "Water", min: 0, max: 1, step: 0.01 },
|
|
531
1208
|
{ path: "wet.pigment", label: "Pigment", min: 0, max: 1, step: 0.01 },
|
|
@@ -600,4 +1277,14 @@ function patchBrushValue(brush, path, value) {
|
|
|
600
1277
|
|
|
601
1278
|
|
|
602
1279
|
|
|
603
|
-
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
exports.BRUSH_PATCH_PROPERTIES = BRUSH_PATCH_PROPERTIES; exports.UNSETTABLE_BRUSH_FACETS = UNSETTABLE_BRUSH_FACETS; exports.BRUSH_UNSET_PROPERTY = BRUSH_UNSET_PROPERTY; exports.mergeBrushPatch = mergeBrushPatch; exports.changedBrushPaths = changedBrushPaths; exports.AbrError = AbrError; exports.readAbr = readAbr; exports.BRUSH_PACK_FORMATS = BRUSH_PACK_FORMATS; exports.MAX_BRUSH_PACK_BYTES = MAX_BRUSH_PACK_BYTES; exports.MAX_PACK_BRUSHES = MAX_PACK_BRUSHES; exports.BrushPackError = BrushPackError; exports.brushPackFormat = brushPackFormat; exports.readBrushPack = readBrushPack; exports.importBrushPack = importBrushPack; exports.importBrushFile = importBrushFile; exports.createBrushLibraryTools = createBrushLibraryTools; exports.createBrushLibraryToolProvider = createBrushLibraryToolProvider; exports.BRUSH_SLIDERS = BRUSH_SLIDERS; exports.brushEngineControls = brushEngineControls; exports.multiColorSliders = multiColorSliders; exports.patchBrushValue = patchBrushValue;
|