makeables 0.9.0 → 0.11.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/README.md +14 -1
- package/dist/badge-package-CMFI3BIR.js +53 -0
- package/dist/brand-NMENEWIZ.js +37 -0
- package/dist/brand-preview-W4FTJJM3.js +48 -0
- package/dist/chunk-4WLCMPRH.js +74 -0
- package/dist/chunk-6RBOBRL4.js +391 -0
- package/dist/chunk-BYXBJQAS.js +0 -0
- package/dist/chunk-DLTVENEP.js +3295 -0
- package/dist/{chunk-RFIQNWNF.js → chunk-YB2YJCBS.js} +475 -2928
- package/dist/chunk-ZZA6CR6X.js +700 -0
- package/dist/cli.js +2777 -480
- package/dist/kit-IEWHMBS2.js +18 -0
- package/dist/{mcp-HS2XZXSO.js → mcp-YBRUVIWG.js} +14 -19
- package/dist/submission-ATVS5YTK.js +76 -0
- package/package.json +3 -2
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InputError,
|
|
3
|
+
effectiveRasterWidth,
|
|
4
|
+
localSubmissionDraft,
|
|
5
|
+
packSheet,
|
|
6
|
+
placeNear,
|
|
7
|
+
prepareSheetPrint,
|
|
8
|
+
readSubmission,
|
|
9
|
+
renderBadge,
|
|
10
|
+
sheetPrintPdf,
|
|
11
|
+
stickerStyles
|
|
12
|
+
} from "./chunk-YB2YJCBS.js";
|
|
13
|
+
import {
|
|
14
|
+
assemblePackage,
|
|
15
|
+
badgeDesignSchema,
|
|
16
|
+
createStickerSheet,
|
|
17
|
+
designManifestSchema,
|
|
18
|
+
jsonBytes,
|
|
19
|
+
kitDocumentSchema,
|
|
20
|
+
kitMemberPackage,
|
|
21
|
+
packageJson,
|
|
22
|
+
packageZip,
|
|
23
|
+
sheetSizes,
|
|
24
|
+
sheetSpec,
|
|
25
|
+
sheetTypeIds,
|
|
26
|
+
sheetTypes,
|
|
27
|
+
stickerSheetSchema
|
|
28
|
+
} from "./chunk-DLTVENEP.js";
|
|
29
|
+
|
|
30
|
+
// src/kit.ts
|
|
31
|
+
import { mkdir, mkdtemp, readFile as readFile2, rename, rm, writeFile } from "fs/promises";
|
|
32
|
+
import { dirname, join, relative, resolve as resolve2 } from "path";
|
|
33
|
+
import { createCanvas as createCanvas2, loadImage } from "@napi-rs/canvas";
|
|
34
|
+
import sharp2 from "sharp";
|
|
35
|
+
|
|
36
|
+
// src/sheet.ts
|
|
37
|
+
import { readFile } from "fs/promises";
|
|
38
|
+
import { resolve } from "path";
|
|
39
|
+
import { deflateSync } from "zlib";
|
|
40
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
41
|
+
import sharp from "sharp";
|
|
42
|
+
async function readSheet(file) {
|
|
43
|
+
const value = JSON.parse(await readFile(resolve(file), "utf8"));
|
|
44
|
+
const parsed = stickerSheetSchema.safeParse(value);
|
|
45
|
+
if (!parsed.success)
|
|
46
|
+
throw new InputError(
|
|
47
|
+
`Invalid sheet: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`
|
|
48
|
+
);
|
|
49
|
+
return parsed.data;
|
|
50
|
+
}
|
|
51
|
+
function newSheet(type, size, name) {
|
|
52
|
+
if (typeof type !== "string" || !sheetTypeIds.includes(type))
|
|
53
|
+
throw new InputError(`--type must be one of ${sheetTypeIds.join(", ")}.`);
|
|
54
|
+
const sheet = createStickerSheet(
|
|
55
|
+
type,
|
|
56
|
+
typeof name === "string" ? name : void 0
|
|
57
|
+
);
|
|
58
|
+
if (size !== void 0) {
|
|
59
|
+
if (typeof size !== "string" || !(size in sheetSizes))
|
|
60
|
+
throw new InputError(`--size must be one of ${Object.keys(sheetSizes).join(", ")}.`);
|
|
61
|
+
sheet.size = size;
|
|
62
|
+
}
|
|
63
|
+
return parse(sheet);
|
|
64
|
+
}
|
|
65
|
+
function parse(sheet) {
|
|
66
|
+
const parsed = stickerSheetSchema.safeParse(sheet);
|
|
67
|
+
if (!parsed.success)
|
|
68
|
+
throw new InputError(parsed.error.issues.map((issue) => issue.message).join("; "));
|
|
69
|
+
return parsed.data;
|
|
70
|
+
}
|
|
71
|
+
async function readSticker(source) {
|
|
72
|
+
const style = stickerStyles.find((entry) => entry.source === source);
|
|
73
|
+
if (style) return badgeDesignSchema.parse(structuredClone(style));
|
|
74
|
+
const design = badgeDesignSchema.parse(JSON.parse(await readFile(resolve(source), "utf8")));
|
|
75
|
+
if (design.product !== "sticker") throw new InputError(`${source} is not a sticker design.`);
|
|
76
|
+
return design;
|
|
77
|
+
}
|
|
78
|
+
var artCache = /* @__PURE__ */ new Map();
|
|
79
|
+
function flatArt(design) {
|
|
80
|
+
const key = JSON.stringify(design);
|
|
81
|
+
let art = artCache.get(key);
|
|
82
|
+
if (!art) {
|
|
83
|
+
art = renderBadge(design, { face: "front", person: {} }).then(async ({ png, dimensions }) => {
|
|
84
|
+
const { data, info } = await sharp(png).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
85
|
+
return {
|
|
86
|
+
width: info.width,
|
|
87
|
+
height: info.height,
|
|
88
|
+
effectiveWidth: effectiveRasterWidth(design, dimensions),
|
|
89
|
+
rgba: new Uint8ClampedArray(data.buffer, data.byteOffset, data.length)
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
artCache.set(key, art);
|
|
93
|
+
}
|
|
94
|
+
return art;
|
|
95
|
+
}
|
|
96
|
+
async function sheetArt(sheet) {
|
|
97
|
+
return new Map(
|
|
98
|
+
await Promise.all(
|
|
99
|
+
sheet.items.map(async (item) => [item.id, await flatArt(item.design)])
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
function bounds(sheet) {
|
|
104
|
+
const spec = sheetSpec(sheet);
|
|
105
|
+
return { widthMm: spec.widthMm ?? 0, heightMm: spec.heightMm ?? 0 };
|
|
106
|
+
}
|
|
107
|
+
async function addToSheet(sheet, source, widthMm) {
|
|
108
|
+
const type = sheetTypes[sheet.type];
|
|
109
|
+
if (type.cut === "die-cut" && sheet.items.length)
|
|
110
|
+
throw new InputError("A single sticker file holds one sticker. Start another sheet.");
|
|
111
|
+
const width = widthMm === void 0 ? type.stickerWidthMm : Number(widthMm);
|
|
112
|
+
if (!Number.isFinite(width) || width < 5 || width > 300)
|
|
113
|
+
throw new InputError("--width-mm must be a number from 5 to 300.");
|
|
114
|
+
const design = await readSticker(source);
|
|
115
|
+
const base = (design.source ?? "sticker").replace(/[^a-z0-9-]/g, "").slice(0, 40) || "sticker";
|
|
116
|
+
let id = base;
|
|
117
|
+
for (let n = 2; sheet.items.some((item2) => item2.id === id); n++) id = `${base}-${n}`;
|
|
118
|
+
const spec = sheetSpec(sheet);
|
|
119
|
+
const item = {
|
|
120
|
+
id,
|
|
121
|
+
design,
|
|
122
|
+
x: (spec.widthMm ?? width) / 2,
|
|
123
|
+
y: (spec.heightMm ?? width) / 2,
|
|
124
|
+
rotation: 0,
|
|
125
|
+
widthMm: width
|
|
126
|
+
};
|
|
127
|
+
const next = parse({ ...sheet, items: [...sheet.items, item] });
|
|
128
|
+
if (type.cut === "die-cut") return { sheet: next, item, placed: true };
|
|
129
|
+
const print = prepareSheetPrint(next, await sheetArt(next));
|
|
130
|
+
const moved = placeNear(
|
|
131
|
+
spec,
|
|
132
|
+
bounds(next),
|
|
133
|
+
next.items.map((entry) => ({ item: entry, shape: print.shapes.get(entry.id) })),
|
|
134
|
+
id,
|
|
135
|
+
[item.x, item.y]
|
|
136
|
+
);
|
|
137
|
+
if (moved) next.items[next.items.length - 1] = moved;
|
|
138
|
+
return { sheet: next, item: moved ?? item, placed: Boolean(moved) };
|
|
139
|
+
}
|
|
140
|
+
async function packStickerSheet(sheet) {
|
|
141
|
+
if (sheetTypes[sheet.type].cut === "die-cut") return { sheet, missing: [] };
|
|
142
|
+
const print = prepareSheetPrint(sheet, await sheetArt(sheet));
|
|
143
|
+
const packed = packSheet(
|
|
144
|
+
sheetSpec(sheet),
|
|
145
|
+
bounds(sheet),
|
|
146
|
+
sheet.items.map((item) => ({ item, shape: print.shapes.get(item.id) })),
|
|
147
|
+
sheet.hero
|
|
148
|
+
);
|
|
149
|
+
return { sheet: parse({ ...sheet, items: packed.items }), missing: packed.missing };
|
|
150
|
+
}
|
|
151
|
+
async function checkSheet(sheet) {
|
|
152
|
+
return prepareSheetPrint(sheet, await sheetArt(sheet));
|
|
153
|
+
}
|
|
154
|
+
function sheetReport(print) {
|
|
155
|
+
return {
|
|
156
|
+
printable: print.printable,
|
|
157
|
+
cutValid: print.printable,
|
|
158
|
+
qualityWarnings: print.issues.filter(({ issue }) => ["low-resolution", "material-mismatch"].includes(issue.code)).map(({ id, issue }) => ({ id, ...issue })),
|
|
159
|
+
widthMm: Number(print.widthMm.toFixed(2)),
|
|
160
|
+
heightMm: Number(print.heightMm.toFixed(2)),
|
|
161
|
+
stickers: print.items.length,
|
|
162
|
+
violations: print.violations,
|
|
163
|
+
issues: print.issues.map(({ id, issue }) => ({ id, ...issue }))
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function exportSheetPdf(sheet, print) {
|
|
167
|
+
return sheetPrintPdf(sheet, print, async (bytes) => new Uint8Array(deflateSync(bytes)));
|
|
168
|
+
}
|
|
169
|
+
function sheetPreviewPng(print, dpi = 150) {
|
|
170
|
+
const scale = dpi / 25.4;
|
|
171
|
+
const canvas = createCanvas(
|
|
172
|
+
Math.round(print.widthMm * scale),
|
|
173
|
+
Math.round(print.heightMm * scale)
|
|
174
|
+
);
|
|
175
|
+
const context = canvas.getContext("2d");
|
|
176
|
+
context.fillStyle = "#f4f3ef";
|
|
177
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
178
|
+
for (const item of print.pdfItems) {
|
|
179
|
+
const art = createCanvas(item.art.width, item.art.height);
|
|
180
|
+
const data = art.getContext("2d").createImageData(item.art.width, item.art.height);
|
|
181
|
+
data.data.set(item.art.rgba);
|
|
182
|
+
art.getContext("2d").putImageData(data, 0, 0);
|
|
183
|
+
context.save();
|
|
184
|
+
context.translate(item.x * scale, item.y * scale);
|
|
185
|
+
context.rotate(item.rotation * Math.PI / 180);
|
|
186
|
+
context.drawImage(
|
|
187
|
+
art,
|
|
188
|
+
-item.widthMm / 2 * scale,
|
|
189
|
+
-item.heightMm / 2 * scale,
|
|
190
|
+
item.widthMm * scale,
|
|
191
|
+
item.heightMm * scale
|
|
192
|
+
);
|
|
193
|
+
context.restore();
|
|
194
|
+
context.strokeStyle = "#ff00ff";
|
|
195
|
+
context.lineWidth = Math.max(1, 0.2 * scale);
|
|
196
|
+
context.beginPath();
|
|
197
|
+
for (const loop of item.cut) {
|
|
198
|
+
for (const [i, [x, y]] of loop.entries())
|
|
199
|
+
if (i) context.lineTo(x * scale, y * scale);
|
|
200
|
+
else context.moveTo(x * scale, y * scale);
|
|
201
|
+
context.closePath();
|
|
202
|
+
}
|
|
203
|
+
context.stroke();
|
|
204
|
+
}
|
|
205
|
+
return canvas.toBuffer("image/png");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/kit.ts
|
|
209
|
+
async function readKit(file) {
|
|
210
|
+
return kitDocumentSchema.parse(JSON.parse(await readFile2(resolve2(file), "utf8")));
|
|
211
|
+
}
|
|
212
|
+
async function addKitMember(file, source, id, kind) {
|
|
213
|
+
const kit = await readKit(file);
|
|
214
|
+
const path = resolve2(source);
|
|
215
|
+
if (kind === "items") {
|
|
216
|
+
const pkg = await readSubmission(path, false);
|
|
217
|
+
if (pkg.manifest.product === "kit") throw new InputError("Nested kits are not supported.");
|
|
218
|
+
} else await readSheet(path);
|
|
219
|
+
return kitDocumentSchema.parse({
|
|
220
|
+
...kit,
|
|
221
|
+
[kind]: [...kit[kind], { id, file: relative(dirname(resolve2(file)), path) }]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async function kitCover(title, pieces) {
|
|
225
|
+
if (!pieces.length) throw new InputError("Add a piece before rendering the kit.");
|
|
226
|
+
const columns = Math.min(4, Math.max(2, Math.ceil(Math.sqrt(pieces.length))));
|
|
227
|
+
const width = 2e3, cell = (width - 160) / columns;
|
|
228
|
+
const height = 220 + Math.ceil(pieces.length / columns) * (cell + 64);
|
|
229
|
+
const canvas = createCanvas2(width, height);
|
|
230
|
+
const context = canvas.getContext("2d");
|
|
231
|
+
context.fillStyle = "#f4f1eb";
|
|
232
|
+
context.fillRect(0, 0, width, height);
|
|
233
|
+
context.fillStyle = "#232323";
|
|
234
|
+
context.font = "bold 56px sans-serif";
|
|
235
|
+
context.fillText(title, 80, 105, width - 160);
|
|
236
|
+
context.font = "22px sans-serif";
|
|
237
|
+
context.fillText(`${pieces.length} views \xB7 Makeables`, 80, 151);
|
|
238
|
+
for (const [index, piece] of pieces.entries()) {
|
|
239
|
+
const bytes = Buffer.from(piece.bytes);
|
|
240
|
+
const isSvg = bytes.subarray(0, 200).toString("utf8").includes("<svg") || bytes.subarray(0, 20).toString("utf8").includes("<?xml");
|
|
241
|
+
const image = await loadImage(isSvg ? await sharp2(bytes).png().toBuffer() : bytes);
|
|
242
|
+
const x = 80 + index % columns * cell;
|
|
243
|
+
const y = 195 + Math.floor(index / columns) * (cell + 64);
|
|
244
|
+
const scale = Math.min((cell - 54) / image.width, (cell - 54) / image.height);
|
|
245
|
+
const w = image.width * scale, h = image.height * scale;
|
|
246
|
+
context.save();
|
|
247
|
+
context.translate(x + cell / 2, y + cell / 2);
|
|
248
|
+
context.rotate((index % 3 - 1) * 0.035);
|
|
249
|
+
context.shadowColor = "#22222225";
|
|
250
|
+
context.shadowBlur = 18;
|
|
251
|
+
context.shadowOffsetY = 9;
|
|
252
|
+
context.drawImage(image, -w / 2, -h / 2, w, h);
|
|
253
|
+
context.restore();
|
|
254
|
+
context.font = "21px sans-serif";
|
|
255
|
+
context.textAlign = "center";
|
|
256
|
+
context.fillText(piece.name, x + cell / 2, y + cell + 24, cell - 28);
|
|
257
|
+
context.textAlign = "left";
|
|
258
|
+
}
|
|
259
|
+
return new Uint8Array(canvas.toBuffer("image/png"));
|
|
260
|
+
}
|
|
261
|
+
async function prepareKit(file) {
|
|
262
|
+
const kit = await readKit(file);
|
|
263
|
+
if (!kit.items.length) throw new InputError("Add at least one piece to the kit.");
|
|
264
|
+
const files = {};
|
|
265
|
+
const mime = {};
|
|
266
|
+
const items = [];
|
|
267
|
+
const views = [];
|
|
268
|
+
for (const item of kit.items) {
|
|
269
|
+
const pkg = await readSubmission(resolve2(dirname(resolve2(file)), item.file), false);
|
|
270
|
+
if (pkg.manifest.product === "kit") throw new InputError("Nested kits are not supported.");
|
|
271
|
+
const manifest = designManifestSchema.parse(pkg.manifest);
|
|
272
|
+
items.push({ id: item.id, manifest });
|
|
273
|
+
for (const entry of manifest.files) {
|
|
274
|
+
const path = `items/${item.id}/${entry.path}`;
|
|
275
|
+
files[path] = pkg.files[entry.path];
|
|
276
|
+
mime[path] = entry.mime;
|
|
277
|
+
}
|
|
278
|
+
views.push({ name: manifest.title, bytes: pkg.files[manifest.front] });
|
|
279
|
+
if (manifest.back)
|
|
280
|
+
views.push({ name: `${manifest.title} \xB7 back`, bytes: pkg.files[manifest.back] });
|
|
281
|
+
}
|
|
282
|
+
const sheets = [];
|
|
283
|
+
for (const entry of kit.sheets) {
|
|
284
|
+
const sheet = await readSheet(resolve2(dirname(resolve2(file)), entry.file));
|
|
285
|
+
for (const item of sheet.items) {
|
|
286
|
+
const draft = await localSubmissionDraft(item.design);
|
|
287
|
+
for (const [id, path] of Object.entries(draft.assets)) {
|
|
288
|
+
const previous = item.design.assets?.[id];
|
|
289
|
+
item.design.assets = {
|
|
290
|
+
...item.design.assets,
|
|
291
|
+
[id]: {
|
|
292
|
+
source: "image",
|
|
293
|
+
name: previous?.name ?? id,
|
|
294
|
+
data: `data:${draft.mime[path]};base64,${Buffer.from(draft.files[path]).toString("base64")}`,
|
|
295
|
+
...previous && "credit" in previous ? { credit: previous.credit } : {},
|
|
296
|
+
...previous?.source === "image" && previous.sourcePixels ? { sourcePixels: previous.sourcePixels } : {}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
stickerSheetSchema.parse(sheet);
|
|
302
|
+
const print = await checkSheet(sheet);
|
|
303
|
+
if (!print.printable)
|
|
304
|
+
throw new InputError(
|
|
305
|
+
`Sheet ${entry.id} is not printable: ${JSON.stringify(sheetReport(print))}`
|
|
306
|
+
);
|
|
307
|
+
const base = `sheets/${entry.id}`;
|
|
308
|
+
const document = `${base}.json`, preview = `${base}.png`, pdf = `${base}.pdf`;
|
|
309
|
+
files[document] = jsonBytes(sheet);
|
|
310
|
+
mime[document] = "application/json";
|
|
311
|
+
files[preview] = sheetPreviewPng(print);
|
|
312
|
+
mime[preview] = "image/png";
|
|
313
|
+
files[pdf] = await exportSheetPdf(sheet, print);
|
|
314
|
+
mime[pdf] = "application/pdf";
|
|
315
|
+
sheets.push({ id: entry.id, document, preview, pdf });
|
|
316
|
+
views.push({ name: sheet.name, bytes: files[preview] });
|
|
317
|
+
}
|
|
318
|
+
files["cover.png"] = await kitCover(kit.name, views);
|
|
319
|
+
mime["cover.png"] = "image/png";
|
|
320
|
+
return assemblePackage(
|
|
321
|
+
{
|
|
322
|
+
product: "kit",
|
|
323
|
+
title: kit.name,
|
|
324
|
+
front: "cover.png",
|
|
325
|
+
assets: {},
|
|
326
|
+
items,
|
|
327
|
+
sheets
|
|
328
|
+
},
|
|
329
|
+
files,
|
|
330
|
+
mime
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
async function exportKit(file) {
|
|
334
|
+
const pkg = await readSubmission(file);
|
|
335
|
+
if (pkg.manifest.product !== "kit") throw new InputError("Choose a kit document or package.");
|
|
336
|
+
return { pkg, bytes: await packageZip(pkg) };
|
|
337
|
+
}
|
|
338
|
+
async function importKit(file, out) {
|
|
339
|
+
const pkg = await readSubmission(file);
|
|
340
|
+
if (pkg.manifest.product !== "kit") throw new InputError("Choose a kit package.");
|
|
341
|
+
const target = resolve2(out);
|
|
342
|
+
const temporary = await mkdtemp(join(dirname(target), ".makeables-kit-"));
|
|
343
|
+
try {
|
|
344
|
+
const kit = {
|
|
345
|
+
format: "makeables-kit",
|
|
346
|
+
version: 1,
|
|
347
|
+
name: pkg.manifest.title,
|
|
348
|
+
items: [],
|
|
349
|
+
sheets: []
|
|
350
|
+
};
|
|
351
|
+
await mkdir(join(temporary, "items"));
|
|
352
|
+
await mkdir(join(temporary, "sheets"));
|
|
353
|
+
for (const item of pkg.manifest.items) {
|
|
354
|
+
const path = `items/${item.id}.makeables.json`;
|
|
355
|
+
await writeFile(
|
|
356
|
+
join(temporary, path),
|
|
357
|
+
JSON.stringify(packageJson(kitMemberPackage(pkg, item.id)))
|
|
358
|
+
);
|
|
359
|
+
kit.items.push({ id: item.id, file: path });
|
|
360
|
+
}
|
|
361
|
+
for (const sheet of pkg.manifest.sheets) {
|
|
362
|
+
const path = `sheets/${sheet.id}.json`;
|
|
363
|
+
await writeFile(join(temporary, path), pkg.files[sheet.document]);
|
|
364
|
+
kit.sheets.push({ id: sheet.id, file: path });
|
|
365
|
+
}
|
|
366
|
+
await writeFile(join(temporary, "kit.json"), JSON.stringify(kit, null, 2));
|
|
367
|
+
await mkdir(target);
|
|
368
|
+
await rename(temporary, target);
|
|
369
|
+
return { path: join(target, "kit.json"), items: kit.items.length, sheets: kit.sheets.length };
|
|
370
|
+
} catch (error) {
|
|
371
|
+
await rm(temporary, { recursive: true, force: true });
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export {
|
|
377
|
+
readSheet,
|
|
378
|
+
newSheet,
|
|
379
|
+
addToSheet,
|
|
380
|
+
packStickerSheet,
|
|
381
|
+
checkSheet,
|
|
382
|
+
sheetReport,
|
|
383
|
+
exportSheetPdf,
|
|
384
|
+
sheetPreviewPng,
|
|
385
|
+
readKit,
|
|
386
|
+
addKitMember,
|
|
387
|
+
kitCover,
|
|
388
|
+
prepareKit,
|
|
389
|
+
exportKit,
|
|
390
|
+
importKit
|
|
391
|
+
};
|
|
File without changes
|