@richardmcquiston01/unofficial-xcs-writer 0.4.1 → 0.6.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/CHANGELOG.md +31 -0
- package/README.md +51 -7
- package/dist/index.cjs +236 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +232 -44
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.0] - 2026-09-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`.xs` generation**: `XCSGenerator.toXsBytes()` (`src/xs.ts`'s new `buildXsArchive`) exports a
|
|
13
|
+
built project as a `.xs` v2 archive instead of `.xcs` -- the same chainable builder
|
|
14
|
+
(`.addText()`/`.addPath()`/`.addBitmap()`/`.addLayer()`), same display objects, different
|
|
15
|
+
container. `project.json`/`profiles.json`/`devices/`/`canvases/` metadata is synthesized
|
|
16
|
+
fresh, always as a single canvas in a single `displays-0.json` chunk, with the cover image
|
|
17
|
+
written as a real `resources/project-cover.png` file instead of inline base64. Processing
|
|
18
|
+
profiles and device bindings are left empty (no `addProfile`-style API exists yet) -- a
|
|
19
|
+
generated `.xs` file's power/speed settings may need to be configured manually in xTool
|
|
20
|
+
Studio before cutting/engraving.
|
|
21
|
+
|
|
22
|
+
## [0.5.0] - 2026-09-20
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`.xs` (v2 workspace) format support** (`src/xs.ts`): `assertXsFormat`, `extractXsTokens`,
|
|
27
|
+
and `renderXsFile` read and apply `{{token}}` substitution to xTool Studio v1.7+'s new `.xs`
|
|
28
|
+
format — a ZIP archive of chunked per-canvas display files, unlike `.xcs`'s single plain-JSON
|
|
29
|
+
document. `.xcs` is now legacy as of xTool Studio v1.7 (still readable, no longer savable from
|
|
30
|
+
the app). The `TEXT` display schema itself (`text`/`style`/`fontData`/`charJSONs`) is
|
|
31
|
+
unchanged from `.xcs`, so token extraction and glyph regeneration share their implementation
|
|
32
|
+
with `renderXcsFile`/`extractXcsTokens` via a new `src/substitution.ts` module; only the
|
|
33
|
+
container format differs. Archives are read/written with [fflate](https://github.com/101arrowz/fflate)
|
|
34
|
+
(new runtime dependency); every entry other than the modified displays chunks round-trips
|
|
35
|
+
byte-for-byte unchanged, including large `PATH` displays' deduplicated `vectors/<bucketType>/`
|
|
36
|
+
vector store. `.xs` *generation* (building a new project from scratch) isn't implemented yet
|
|
37
|
+
— see `CLAUDE.md`'s "Open Issues".
|
|
38
|
+
|
|
8
39
|
## [0.4.1] - 2026-09-19
|
|
9
40
|
|
|
10
41
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
# unofficial-xcs-writer
|
|
2
2
|
|
|
3
|
-
A framework-agnostic TypeScript library for reading, writing, building, and applying variable substitution to `.xcs` files produced by [xTool Creative Space](https://www.xtool.com/pages/software). Not affiliated with xTool.
|
|
3
|
+
A framework-agnostic TypeScript library for reading, writing, building, and applying variable substitution to `.xcs` and `.xs` files produced by [xTool Creative Space](https://www.xtool.com/pages/software). Not affiliated with xTool.
|
|
4
4
|
|
|
5
|
-
## What is an XCS file?
|
|
5
|
+
## What is an XCS / XS file?
|
|
6
6
|
|
|
7
|
-
An `.xcs` file is a plain UTF-8 JSON document exported by xTool Creative Space. It describes a laser project — canvas dimensions, design objects (shapes, text, images), layer assignments, and device parameters.
|
|
7
|
+
An `.xcs` file is a plain UTF-8 JSON document exported by xTool Creative Space. It describes a laser project — canvas dimensions, design objects (shapes, text, images), layer assignments, and device parameters.
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
Starting with xTool Studio v1.7, new projects are saved as `.xs` instead — a ZIP archive of several JSON files (project metadata, per-canvas display chunks, processing profiles, device bindings, and binary resources as real files rather than inline base64). `.xcs` is now legacy: v1.7+ can still open `.xcs` files but can no longer save back to that format. See `src/xs.ts` for the full format writeup.
|
|
10
|
+
|
|
11
|
+
This library lets you, for both formats:
|
|
12
|
+
|
|
13
|
+
- **Read** a file and inspect its contents
|
|
10
14
|
- **Extract** `{{token}}` template placeholders from text objects
|
|
11
15
|
- **Render** a filled-in copy by substituting values for those placeholders
|
|
12
|
-
- **Build** a new
|
|
16
|
+
- **Build** a new project from scratch — text (straight, multi-line, or curved), paths, and embedded images — and export it as either `.xcs` or `.xs`
|
|
13
17
|
|
|
14
18
|
## Installation
|
|
15
19
|
|
|
@@ -46,6 +50,21 @@ const values = { FirstName: 'Jane', LastName: 'Smith' };
|
|
|
46
50
|
const output: Uint8Array = renderXcsFile(buffer, variables, values);
|
|
47
51
|
```
|
|
48
52
|
|
|
53
|
+
### Working with `.xs` files
|
|
54
|
+
|
|
55
|
+
Same shape, different functions — `.xs` is a ZIP archive, so there's no `readXsFile` equivalent to `readXcsFile` (there's no single JSON string to return), but token extraction and substitution work the same way:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { assertXsFormat, extractXsTokens, renderXsFile } from '@richardmcquiston01/unofficial-xcs-writer';
|
|
59
|
+
|
|
60
|
+
const buffer = await fetch('/templates/name-tag.xs').then(r => r.arrayBuffer());
|
|
61
|
+
|
|
62
|
+
assertXsFormat(buffer); // throws if invalid
|
|
63
|
+
const tokens = extractXsTokens(buffer); // e.g. ['FirstName', 'LastName']
|
|
64
|
+
|
|
65
|
+
const output: Uint8Array = renderXsFile(buffer, variables, values);
|
|
66
|
+
```
|
|
67
|
+
|
|
49
68
|
### Building a project from scratch
|
|
50
69
|
|
|
51
70
|
```ts
|
|
@@ -78,6 +97,12 @@ const project = createXCS('P2S')
|
|
|
78
97
|
.toBytes(); // Uint8Array, ready to write to a .xcs file
|
|
79
98
|
```
|
|
80
99
|
|
|
100
|
+
Call `.toXsBytes()` instead of `.toBytes()` to export the same project as a `.xs` (v2 workspace)
|
|
101
|
+
archive — same chainable builder, same display objects, different container. Processing
|
|
102
|
+
profiles/device bindings aren't populated (there's no `addProfile`-style API yet), so a
|
|
103
|
+
generated `.xs` file's power/speed may need to be set manually in xTool Studio before
|
|
104
|
+
cutting/engraving.
|
|
105
|
+
|
|
81
106
|
## API
|
|
82
107
|
|
|
83
108
|
### `assertXcsFormat(buffer: ArrayBuffer): void`
|
|
@@ -118,6 +143,24 @@ interface XcsVariable {
|
|
|
118
143
|
}
|
|
119
144
|
```
|
|
120
145
|
|
|
146
|
+
### `.xs` (v2 workspace) format
|
|
147
|
+
|
|
148
|
+
### `assertXsFormat(buffer: ArrayBuffer): void`
|
|
149
|
+
|
|
150
|
+
Validates that `buffer` is a ZIP archive with a `.format` entry equal to `"v2"` and a `project.json` entry. Throws a descriptive error otherwise.
|
|
151
|
+
|
|
152
|
+
### `extractXsTokens(buffer: ArrayBuffer): string[]`
|
|
153
|
+
|
|
154
|
+
Same as `extractXcsTokens`, but scans every `canvases/<canvasId>/displays-<chunkIndex>.json` chunk across the archive instead of a single `canvas` array.
|
|
155
|
+
|
|
156
|
+
### `renderXsFile(buffer, variables, values, fonts?): Uint8Array`
|
|
157
|
+
|
|
158
|
+
Same substitution and glyph-regeneration behavior as `renderXcsFile` (see above — including the same `fonts` parameter and curved-text limitation), applied to every displays chunk. Returns a re-zipped `.xs` archive; every entry other than the modified displays chunks is passed through byte-for-byte unchanged.
|
|
159
|
+
|
|
160
|
+
### `buildXsArchive(file: XCSFile): Uint8Array`
|
|
161
|
+
|
|
162
|
+
Packages an already-`generate()`d project (see Building, below) into a `.xs` v2 ZIP archive. You won't normally call this directly — use `XCSGenerator.toXsBytes()` instead, which calls it for you.
|
|
163
|
+
|
|
121
164
|
### `loadFont(buffer: ArrayBuffer)` / `loadDefaultFont()` / `layoutText(font, text, originX, originY)`
|
|
122
165
|
|
|
123
166
|
Lower-level glyph-extraction primitives everything else in this library is built on, exported for direct use. See `src/glyphs.ts` for the exact xTool JSON conventions these were reverse-engineered against.
|
|
@@ -133,7 +176,7 @@ Lower-level glyph-extraction primitives everything else in this library is built
|
|
|
133
176
|
| `.addBitmap(pngBase64, x, y, widthMm, heightMm, originWidthPx, originHeightPx, options?)` | An embedded PNG, physically sized in mm. |
|
|
134
177
|
| `.addLayer(color, name, order)` | A named, colored layer (one `#00befe` "Cyan" layer exists by default). |
|
|
135
178
|
|
|
136
|
-
Then `.generate()` (the plain `XCSFile` object), `.toJSON()` (string),
|
|
179
|
+
Then `.generate()` (the plain `XCSFile` object), `.toJSON()` (string), `.toBytes()` (`Uint8Array`, matching `renderXcsFile`'s output type, as `.xcs`), or `.toXsBytes()` (`Uint8Array`, matching `renderXsFile`'s output type, as `.xs` — see the note in "Building a project from scratch" above about unset processing profiles).
|
|
137
180
|
|
|
138
181
|
Text layout — build the `layout` option for `.addText()` with one of:
|
|
139
182
|
|
|
@@ -175,7 +218,8 @@ It needs an `NPM_TOKEN` repository secret with publish rights.
|
|
|
175
218
|
|
|
176
219
|
- All inputs and outputs use plain browser-compatible types (`ArrayBuffer`, `Uint8Array`, `string`) — no Node.js APIs, no DOM, no framework required. (`atob`/`crypto.randomUUID` are used for the bundled font and glyph IDs; both are available in browsers and Node ≥ 19.)
|
|
177
220
|
- Only `TEXT` display objects are modified during substitution. Geometry, bitmaps, device configuration, and all other fields pass through unchanged.
|
|
178
|
-
- This library targets the current xTool Creative Space JSON
|
|
221
|
+
- This library targets the current xTool Creative Space JSON/`.xs` formats. Older versions of the software may export a different structure.
|
|
222
|
+
- `.xs` archives are read and re-written with [fflate](https://github.com/101arrowz/fflate); large `PATH` displays' vector data may be deduplicated into a separate `vectors/<bucketType>/` store rather than inlined — this library never touches `PATH` displays, so that structure round-trips untouched.
|
|
179
223
|
- Glyph outline extraction is powered by [opentype.js](https://github.com/opentypejs/opentype.js). The bundled fallback font is [Arimo](https://fonts.google.com/specimen/Arimo) v5.2.8 via [`@fontsource/arimo`](https://www.npmjs.com/package/@fontsource/arimo) (Apache License 2.0, see `third_party/arimo/LICENSE`).
|
|
180
224
|
|
|
181
225
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -32,9 +32,12 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
XCSGenerator: () => XCSGenerator,
|
|
34
34
|
assertXcsFormat: () => assertXcsFormat,
|
|
35
|
+
assertXsFormat: () => assertXsFormat,
|
|
36
|
+
buildXsArchive: () => buildXsArchive,
|
|
35
37
|
createXCS: () => createXCS,
|
|
36
38
|
effectiveCurveDeg: () => effectiveCurveDeg,
|
|
37
39
|
extractXcsTokens: () => extractXcsTokens,
|
|
40
|
+
extractXsTokens: () => extractXsTokens,
|
|
38
41
|
fontSizePoints: () => fontSizePoints,
|
|
39
42
|
layoutCurvedGlyphText: () => layoutCurvedGlyphText,
|
|
40
43
|
layoutGlyphText: () => layoutGlyphText,
|
|
@@ -44,6 +47,7 @@ __export(index_exports, {
|
|
|
44
47
|
loadFont: () => loadFont,
|
|
45
48
|
readXcsFile: () => readXcsFile,
|
|
46
49
|
renderXcsFile: () => renderXcsFile,
|
|
50
|
+
renderXsFile: () => renderXsFile,
|
|
47
51
|
translateGlyphLayout: () => translateGlyphLayout
|
|
48
52
|
});
|
|
49
53
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -228,8 +232,56 @@ function layoutText(font, text, originX, originY) {
|
|
|
228
232
|
};
|
|
229
233
|
}
|
|
230
234
|
|
|
231
|
-
// src/
|
|
235
|
+
// src/substitution.ts
|
|
232
236
|
var TOKEN_RE = /\{\{([^}]+)\}\}/g;
|
|
237
|
+
function collectTextTokens(displays, seen) {
|
|
238
|
+
for (const display of displays) {
|
|
239
|
+
if (display.type === "TEXT" && typeof display.text === "string") {
|
|
240
|
+
for (const match of display.text.matchAll(TOKEN_RE)) {
|
|
241
|
+
seen.add(match[1]);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function resolveFont(fontFamily, fonts, cache) {
|
|
247
|
+
const key = fontFamily ?? "";
|
|
248
|
+
const cached = cache.get(key);
|
|
249
|
+
if (cached) return cached;
|
|
250
|
+
const suppliedBuffer = fontFamily ? fonts?.[fontFamily] : void 0;
|
|
251
|
+
const font = suppliedBuffer ? loadFont(suppliedBuffer) : loadDefaultFont();
|
|
252
|
+
cache.set(key, font);
|
|
253
|
+
return font;
|
|
254
|
+
}
|
|
255
|
+
function substituteTextDisplays(displays, variables, values, fonts, fontCache) {
|
|
256
|
+
for (const display of displays) {
|
|
257
|
+
if (display.type !== "TEXT" || typeof display.text !== "string") continue;
|
|
258
|
+
const originalText = display.text;
|
|
259
|
+
let text = originalText;
|
|
260
|
+
for (const variable of variables) {
|
|
261
|
+
const replacement = values[variable.token] ?? variable.defaultValue ?? "";
|
|
262
|
+
text = text.replaceAll(`{{${variable.token}}}`, replacement);
|
|
263
|
+
}
|
|
264
|
+
display.text = text;
|
|
265
|
+
if (text === originalText) continue;
|
|
266
|
+
const style = display.style;
|
|
267
|
+
const font = resolveFont(style?.fontFamily, fonts, fontCache);
|
|
268
|
+
const originX = typeof display.x === "number" ? display.x : 0;
|
|
269
|
+
const originY = typeof display.y === "number" ? display.y : 0;
|
|
270
|
+
const layout = layoutText(font, text, originX, originY);
|
|
271
|
+
display.fontData = layout.fontData;
|
|
272
|
+
display.charJSONs = layout.charJSONs;
|
|
273
|
+
if (layout.x !== null && layout.y !== null) {
|
|
274
|
+
display.x = layout.x;
|
|
275
|
+
display.y = layout.y;
|
|
276
|
+
display.offsetX = layout.x;
|
|
277
|
+
display.offsetY = layout.y;
|
|
278
|
+
display.width = layout.width;
|
|
279
|
+
display.height = layout.height;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/xcs.ts
|
|
233
285
|
function assertXcsFormat(buffer) {
|
|
234
286
|
let project;
|
|
235
287
|
try {
|
|
@@ -248,60 +300,186 @@ function extractXcsTokens(buffer) {
|
|
|
248
300
|
const project = JSON.parse(readXcsFile(buffer));
|
|
249
301
|
const seen = /* @__PURE__ */ new Set();
|
|
250
302
|
for (const canvas of project.canvas) {
|
|
251
|
-
|
|
252
|
-
if (display.type === "TEXT" && typeof display.text === "string") {
|
|
253
|
-
for (const match of display.text.matchAll(TOKEN_RE)) {
|
|
254
|
-
seen.add(match[1]);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
303
|
+
collectTextTokens(canvas.displays, seen);
|
|
258
304
|
}
|
|
259
305
|
return Array.from(seen);
|
|
260
306
|
}
|
|
261
|
-
function resolveFont(fontFamily, fonts, cache) {
|
|
262
|
-
const key = fontFamily ?? "";
|
|
263
|
-
const cached = cache.get(key);
|
|
264
|
-
if (cached) return cached;
|
|
265
|
-
const suppliedBuffer = fontFamily ? fonts?.[fontFamily] : void 0;
|
|
266
|
-
const font = suppliedBuffer ? loadFont(suppliedBuffer) : loadDefaultFont();
|
|
267
|
-
cache.set(key, font);
|
|
268
|
-
return font;
|
|
269
|
-
}
|
|
270
307
|
function renderXcsFile(buffer, variables, values, fonts) {
|
|
271
308
|
const project = JSON.parse(readXcsFile(buffer));
|
|
272
309
|
const fontCache = /* @__PURE__ */ new Map();
|
|
273
310
|
for (const canvas of project.canvas) {
|
|
274
|
-
|
|
275
|
-
if (display.type === "TEXT" && typeof display.text === "string") {
|
|
276
|
-
const originalText = display.text;
|
|
277
|
-
let text = originalText;
|
|
278
|
-
for (const variable of variables) {
|
|
279
|
-
const replacement = values[variable.token] ?? variable.defaultValue ?? "";
|
|
280
|
-
text = text.replaceAll(`{{${variable.token}}}`, replacement);
|
|
281
|
-
}
|
|
282
|
-
display.text = text;
|
|
283
|
-
if (text === originalText) continue;
|
|
284
|
-
const style = display.style;
|
|
285
|
-
const font = resolveFont(style?.fontFamily, fonts, fontCache);
|
|
286
|
-
const originX = typeof display.x === "number" ? display.x : 0;
|
|
287
|
-
const originY = typeof display.y === "number" ? display.y : 0;
|
|
288
|
-
const layout = layoutText(font, text, originX, originY);
|
|
289
|
-
display.fontData = layout.fontData;
|
|
290
|
-
display.charJSONs = layout.charJSONs;
|
|
291
|
-
if (layout.x !== null && layout.y !== null) {
|
|
292
|
-
display.x = layout.x;
|
|
293
|
-
display.y = layout.y;
|
|
294
|
-
display.offsetX = layout.x;
|
|
295
|
-
display.offsetY = layout.y;
|
|
296
|
-
display.width = layout.width;
|
|
297
|
-
display.height = layout.height;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
311
|
+
substituteTextDisplays(canvas.displays, variables, values, fonts, fontCache);
|
|
301
312
|
}
|
|
302
313
|
return new TextEncoder().encode(JSON.stringify(project));
|
|
303
314
|
}
|
|
304
315
|
|
|
316
|
+
// src/xs.ts
|
|
317
|
+
var import_fflate = require("fflate");
|
|
318
|
+
var DISPLAYS_CHUNK_RE = /^canvases\/[^/]+\/displays-\d+\.json$/;
|
|
319
|
+
function decodeJson(bytes) {
|
|
320
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
321
|
+
}
|
|
322
|
+
function assertXsFormat(buffer) {
|
|
323
|
+
let entries;
|
|
324
|
+
try {
|
|
325
|
+
entries = (0, import_fflate.unzipSync)(new Uint8Array(buffer));
|
|
326
|
+
} catch (err) {
|
|
327
|
+
throw new Error(`Not a valid .xs file: ${err.message}`);
|
|
328
|
+
}
|
|
329
|
+
const format = entries[".format"];
|
|
330
|
+
if (!format) {
|
|
331
|
+
throw new Error("Not a valid .xs file: missing .format entry.");
|
|
332
|
+
}
|
|
333
|
+
const formatVersion = new TextDecoder().decode(format).trim();
|
|
334
|
+
if (formatVersion !== "v2") {
|
|
335
|
+
throw new Error(`Unsupported .xs format version "${formatVersion}" (expected "v2").`);
|
|
336
|
+
}
|
|
337
|
+
if (!entries["project.json"]) {
|
|
338
|
+
throw new Error("Not a valid .xs file: missing project.json.");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function extractXsTokens(buffer) {
|
|
342
|
+
const entries = (0, import_fflate.unzipSync)(new Uint8Array(buffer));
|
|
343
|
+
const seen = /* @__PURE__ */ new Set();
|
|
344
|
+
for (const [path, data] of Object.entries(entries)) {
|
|
345
|
+
if (!DISPLAYS_CHUNK_RE.test(path)) continue;
|
|
346
|
+
const chunk = decodeJson(data);
|
|
347
|
+
collectTextTokens(chunk.displays, seen);
|
|
348
|
+
}
|
|
349
|
+
return Array.from(seen);
|
|
350
|
+
}
|
|
351
|
+
function renderXsFile(buffer, variables, values, fonts) {
|
|
352
|
+
const entries = (0, import_fflate.unzipSync)(new Uint8Array(buffer));
|
|
353
|
+
const fontCache = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const [path, data] of Object.entries(entries)) {
|
|
355
|
+
if (!DISPLAYS_CHUNK_RE.test(path)) continue;
|
|
356
|
+
const chunk = decodeJson(data);
|
|
357
|
+
substituteTextDisplays(chunk.displays, variables, values, fonts, fontCache);
|
|
358
|
+
entries[path] = new TextEncoder().encode(JSON.stringify(chunk));
|
|
359
|
+
}
|
|
360
|
+
return (0, import_fflate.zipSync)(entries);
|
|
361
|
+
}
|
|
362
|
+
function encodeJson(value) {
|
|
363
|
+
return new TextEncoder().encode(JSON.stringify(value));
|
|
364
|
+
}
|
|
365
|
+
function decodeDataUrl(dataUrl) {
|
|
366
|
+
const match = /^data:([^;]+);base64,(.*)$/.exec(dataUrl);
|
|
367
|
+
if (!match) return null;
|
|
368
|
+
const [, mimeType, base64] = match;
|
|
369
|
+
const binary = atob(base64);
|
|
370
|
+
const bytes = new Uint8Array(binary.length);
|
|
371
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
372
|
+
return { bytes, mimeType };
|
|
373
|
+
}
|
|
374
|
+
function coverResourceEntries(coverDataUrl) {
|
|
375
|
+
const cover = decodeDataUrl(coverDataUrl);
|
|
376
|
+
if (!cover) return {};
|
|
377
|
+
return {
|
|
378
|
+
"resources/project-cover.png": cover.bytes,
|
|
379
|
+
"resources/project-cover.png.meta.json": encodeJson({
|
|
380
|
+
ref: "resources/project-cover.png",
|
|
381
|
+
metadata: {
|
|
382
|
+
kind: "image",
|
|
383
|
+
source: { type: "workspace", value: "project-cover.png" },
|
|
384
|
+
mimeType: cover.mimeType
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function buildXsArchive(file) {
|
|
390
|
+
const canvas = file.canvas[0];
|
|
391
|
+
if (!canvas) {
|
|
392
|
+
throw new Error("Cannot build a .xs archive from a project with no canvas.");
|
|
393
|
+
}
|
|
394
|
+
return (0, import_fflate.zipSync)({
|
|
395
|
+
".format": new TextEncoder().encode("v2"),
|
|
396
|
+
"meta/persistence-meta.json": encodeJson({
|
|
397
|
+
schemaVersion: "2.0.0",
|
|
398
|
+
protocol: "xcs-workspace-v2"
|
|
399
|
+
}),
|
|
400
|
+
"project.json": encodeJson({
|
|
401
|
+
__v2__: true,
|
|
402
|
+
version: "2.0.0",
|
|
403
|
+
schemaMeta: { schemaVersion: "2.1.0", format: "directory" },
|
|
404
|
+
projectId: file.projectTraceID,
|
|
405
|
+
projectTraceID: file.projectTraceID,
|
|
406
|
+
projectName: "Untitled",
|
|
407
|
+
activeCanvasId: canvas.id,
|
|
408
|
+
activeDeviceId: file.device.id,
|
|
409
|
+
versionInfo: {
|
|
410
|
+
source: "web",
|
|
411
|
+
appVersion: "",
|
|
412
|
+
savedAt: file.modify,
|
|
413
|
+
ua: file.ua,
|
|
414
|
+
minRequiredVersion: file.minRequiredVersion,
|
|
415
|
+
appMinRequiredVersion: file.appMinRequiredVersion,
|
|
416
|
+
webMinRequiredVersion: file.webMinRequiredVersion
|
|
417
|
+
},
|
|
418
|
+
created: file.created,
|
|
419
|
+
modify: file.modify,
|
|
420
|
+
modules: { canvases: [canvas.id], devices: [file.device.id] },
|
|
421
|
+
cover: "resources/project-cover.png",
|
|
422
|
+
customProjectData: { projectTraceID: file.projectTraceID }
|
|
423
|
+
}),
|
|
424
|
+
"profiles.json": encodeJson({ profiles: {} }),
|
|
425
|
+
[`canvases/${canvas.id}.json`]: encodeJson({
|
|
426
|
+
id: canvas.id,
|
|
427
|
+
title: canvas.title,
|
|
428
|
+
hidden: false,
|
|
429
|
+
layerData: canvas.layerData,
|
|
430
|
+
groupData: canvas.groupData,
|
|
431
|
+
extendInfo: {
|
|
432
|
+
version: canvas.extendInfo.version,
|
|
433
|
+
minCanvasVersion: canvas.extendInfo.minCanvasVersion,
|
|
434
|
+
displayProcessConfigMap: {},
|
|
435
|
+
rulerPluginData: { rulerGuide: [] },
|
|
436
|
+
type: "2d"
|
|
437
|
+
},
|
|
438
|
+
chunkLayout: { displayCount: canvas.displays.length, chunkCount: 1, chunkIndexes: [0] }
|
|
439
|
+
}),
|
|
440
|
+
[`canvases/${canvas.id}/displays-0.json`]: encodeJson({
|
|
441
|
+
canvasId: canvas.id,
|
|
442
|
+
chunkIndex: 0,
|
|
443
|
+
displays: canvas.displays
|
|
444
|
+
}),
|
|
445
|
+
[`devices/device-${file.device.id}.json`]: encodeJson({
|
|
446
|
+
id: file.device.id,
|
|
447
|
+
deviceCode: file.device.id,
|
|
448
|
+
extId: file.extId,
|
|
449
|
+
extName: file.extName,
|
|
450
|
+
power: [file.device.power],
|
|
451
|
+
processing: {
|
|
452
|
+
[canvas.id]: {
|
|
453
|
+
id: canvas.id,
|
|
454
|
+
activeMode: "LASER_PLANE",
|
|
455
|
+
modes: {
|
|
456
|
+
LASER_PLANE: {
|
|
457
|
+
ignoredDisplayIds: [],
|
|
458
|
+
data: {
|
|
459
|
+
material: 0,
|
|
460
|
+
thickness: null,
|
|
461
|
+
perimeter: null,
|
|
462
|
+
diameter: null,
|
|
463
|
+
isProcessByLayer: false,
|
|
464
|
+
pathPlanning: "auto",
|
|
465
|
+
fillPlanning: "separate",
|
|
466
|
+
scanDirection: "topToBottom",
|
|
467
|
+
enableOddEvenKerf: true,
|
|
468
|
+
focalLen: null,
|
|
469
|
+
focalLength: null
|
|
470
|
+
},
|
|
471
|
+
profileRefs: [],
|
|
472
|
+
patches: {},
|
|
473
|
+
bindings: []
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}),
|
|
479
|
+
...coverResourceEntries(file.cover)
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
305
483
|
// src/layout.ts
|
|
306
484
|
var LAYOUT_MM_PER_EM = 25.4;
|
|
307
485
|
function fontSizePoints(emSizeMm) {
|
|
@@ -733,6 +911,16 @@ var XCSGenerator = class {
|
|
|
733
911
|
toBytes() {
|
|
734
912
|
return new TextEncoder().encode(this.toJSON());
|
|
735
913
|
}
|
|
914
|
+
/**
|
|
915
|
+
* Export as a `.xs` (v2 workspace) ZIP archive instead of `.xcs` --
|
|
916
|
+
* see `buildXsArchive` in `src/xs.ts` for the container format and
|
|
917
|
+
* its limitations (no processing profiles/device bindings yet).
|
|
918
|
+
* Display objects themselves carry over unchanged; only the
|
|
919
|
+
* container differs from `toBytes()`.
|
|
920
|
+
*/
|
|
921
|
+
toXsBytes() {
|
|
922
|
+
return buildXsArchive(this.generate());
|
|
923
|
+
}
|
|
736
924
|
createCanvas() {
|
|
737
925
|
return {
|
|
738
926
|
id: this.canvasId,
|
|
@@ -967,9 +1155,12 @@ function createXCS(deviceId = "P2S") {
|
|
|
967
1155
|
0 && (module.exports = {
|
|
968
1156
|
XCSGenerator,
|
|
969
1157
|
assertXcsFormat,
|
|
1158
|
+
assertXsFormat,
|
|
1159
|
+
buildXsArchive,
|
|
970
1160
|
createXCS,
|
|
971
1161
|
effectiveCurveDeg,
|
|
972
1162
|
extractXcsTokens,
|
|
1163
|
+
extractXsTokens,
|
|
973
1164
|
fontSizePoints,
|
|
974
1165
|
layoutCurvedGlyphText,
|
|
975
1166
|
layoutGlyphText,
|
|
@@ -979,6 +1170,7 @@ function createXCS(deviceId = "P2S") {
|
|
|
979
1170
|
loadFont,
|
|
980
1171
|
readXcsFile,
|
|
981
1172
|
renderXcsFile,
|
|
1173
|
+
renderXsFile,
|
|
982
1174
|
translateGlyphLayout
|
|
983
1175
|
});
|
|
984
1176
|
//# sourceMappingURL=index.cjs.map
|