@richardmcquiston01/unofficial-xcs-writer 0.6.0 → 0.7.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 +14 -0
- package/README.md +18 -6
- package/dist/index.cjs +59 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -9
- package/dist/index.d.ts +43 -9
- package/dist/index.js +59 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.7.0] - 2026-09-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`.xs` processing-profile support**: `.addText()`/`.addPath()`/`.addBitmap()` accept a new
|
|
13
|
+
`processing: { processingType, values }` option (`src/builder.ts`), which `.toXsBytes()`
|
|
14
|
+
(`src/xs.ts`'s `buildXsArchive`, now taking an optional `XsProcessingBinding[]`) turns into a
|
|
15
|
+
real `profiles.json` entry plus a matching `LASER_PLANE`-mode binding in
|
|
16
|
+
`devices/device-<id>.json`. Displays given identical `processingType`+`values` are merged into
|
|
17
|
+
one shared profile/binding, matching how real xTool Studio exports dedupe identical settings.
|
|
18
|
+
`.toBytes()` (`.xcs` output) ignores this option -- `.xcs`'s own per-display processing format
|
|
19
|
+
is structurally different and much less understood, so it isn't implemented (see `CLAUDE.md`'s
|
|
20
|
+
Open Issues).
|
|
21
|
+
|
|
8
22
|
## [0.6.0] - 2026-09-21
|
|
9
23
|
|
|
10
24
|
### Added
|
package/README.md
CHANGED
|
@@ -93,15 +93,25 @@ const project = createXCS('P2S')
|
|
|
93
93
|
fontSize: 18,
|
|
94
94
|
layout: layoutCurvedGlyphText(font, 'Arch Text', 6, 90, 60, 20, 'center'),
|
|
95
95
|
})
|
|
96
|
-
.addPath('M0 0L10 0L10 10L0 10Z', 0, 60, 10, 10
|
|
96
|
+
.addPath('M0 0L10 0L10 10L0 10Z', 0, 60, 10, 10, {
|
|
97
|
+
// Only affects .toXsBytes() output -- see below.
|
|
98
|
+
processing: { processingType: 'VECTOR_CUTTING', values: { power: 80, speed: 10 } },
|
|
99
|
+
})
|
|
97
100
|
.toBytes(); // Uint8Array, ready to write to a .xcs file
|
|
98
101
|
```
|
|
99
102
|
|
|
100
103
|
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.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
archive — same chainable builder, same display objects, different container.
|
|
105
|
+
|
|
106
|
+
Pass `processing: { processingType, values }` to `.addText()`/`.addPath()`/`.addBitmap()`'s
|
|
107
|
+
options to set that display's power/speed/etc. in the exported `.xs` file's `profiles.json` +
|
|
108
|
+
device bindings — `.xcs` output (`.toBytes()`) ignores this option; its own per-display
|
|
109
|
+
processing format is different and not supported yet. Displays given identical settings share
|
|
110
|
+
one profile/binding automatically. `processingType`/`values`' exact shape is xTool Studio's own
|
|
111
|
+
and undocumented beyond real exports — see `xs_samples/*.xs`'s `profiles.json` for examples
|
|
112
|
+
(`VECTOR_ENGRAVING`, `VECTOR_CUTTING`, `FILL_VECTOR_ENGRAVING`). Displays with no `processing`
|
|
113
|
+
option behave as before: a generated `.xs` file's power/speed may need to be set manually in
|
|
114
|
+
xTool Studio before cutting/engraving.
|
|
105
115
|
|
|
106
116
|
## API
|
|
107
117
|
|
|
@@ -176,7 +186,9 @@ Lower-level glyph-extraction primitives everything else in this library is built
|
|
|
176
186
|
| `.addBitmap(pngBase64, x, y, widthMm, heightMm, originWidthPx, originHeightPx, options?)` | An embedded PNG, physically sized in mm. |
|
|
177
187
|
| `.addLayer(color, name, order)` | A named, colored layer (one `#00befe` "Cyan" layer exists by default). |
|
|
178
188
|
|
|
179
|
-
|
|
189
|
+
`.addText()`/`.addPath()`/`.addBitmap()` all also accept a `processing: { processingType, values }` option — see "Building a project from scratch" above; it only affects `.toXsBytes()` output.
|
|
190
|
+
|
|
191
|
+
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`).
|
|
180
192
|
|
|
181
193
|
Text layout — build the `layout` option for `.addText()` with one of:
|
|
182
194
|
|
package/dist/index.cjs
CHANGED
|
@@ -362,6 +362,36 @@ function renderXsFile(buffer, variables, values, fonts) {
|
|
|
362
362
|
function encodeJson(value) {
|
|
363
363
|
return new TextEncoder().encode(JSON.stringify(value));
|
|
364
364
|
}
|
|
365
|
+
function generateXsId(prefix) {
|
|
366
|
+
const uuid = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
367
|
+
const r = Math.random() * 16 | 0;
|
|
368
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
369
|
+
return v.toString(16);
|
|
370
|
+
});
|
|
371
|
+
return `${prefix}:${uuid}`;
|
|
372
|
+
}
|
|
373
|
+
function buildProcessing(canvasId, bindings) {
|
|
374
|
+
const profiles = {};
|
|
375
|
+
const profileRefs = [];
|
|
376
|
+
const deviceBindings = [];
|
|
377
|
+
for (const binding of bindings) {
|
|
378
|
+
const profileId = generateXsId("profile");
|
|
379
|
+
profiles[profileId] = {
|
|
380
|
+
id: profileId,
|
|
381
|
+
processingType: binding.processingType,
|
|
382
|
+
values: binding.values
|
|
383
|
+
};
|
|
384
|
+
profileRefs.push(profileId);
|
|
385
|
+
deviceBindings.push({
|
|
386
|
+
bindingId: generateXsId("binding"),
|
|
387
|
+
canvasId,
|
|
388
|
+
mode: "LASER_PLANE",
|
|
389
|
+
baseProfileId: profileId,
|
|
390
|
+
displayIds: binding.displayIds
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return { profiles, profileRefs, deviceBindings };
|
|
394
|
+
}
|
|
365
395
|
function decodeDataUrl(dataUrl) {
|
|
366
396
|
const match = /^data:([^;]+);base64,(.*)$/.exec(dataUrl);
|
|
367
397
|
if (!match) return null;
|
|
@@ -386,11 +416,15 @@ function coverResourceEntries(coverDataUrl) {
|
|
|
386
416
|
})
|
|
387
417
|
};
|
|
388
418
|
}
|
|
389
|
-
function buildXsArchive(file) {
|
|
419
|
+
function buildXsArchive(file, processingBindings = []) {
|
|
390
420
|
const canvas = file.canvas[0];
|
|
391
421
|
if (!canvas) {
|
|
392
422
|
throw new Error("Cannot build a .xs archive from a project with no canvas.");
|
|
393
423
|
}
|
|
424
|
+
const { profiles, profileRefs, deviceBindings } = buildProcessing(
|
|
425
|
+
canvas.id,
|
|
426
|
+
processingBindings
|
|
427
|
+
);
|
|
394
428
|
return (0, import_fflate.zipSync)({
|
|
395
429
|
".format": new TextEncoder().encode("v2"),
|
|
396
430
|
"meta/persistence-meta.json": encodeJson({
|
|
@@ -421,7 +455,7 @@ function buildXsArchive(file) {
|
|
|
421
455
|
cover: "resources/project-cover.png",
|
|
422
456
|
customProjectData: { projectTraceID: file.projectTraceID }
|
|
423
457
|
}),
|
|
424
|
-
"profiles.json": encodeJson({ profiles
|
|
458
|
+
"profiles.json": encodeJson({ profiles }),
|
|
425
459
|
[`canvases/${canvas.id}.json`]: encodeJson({
|
|
426
460
|
id: canvas.id,
|
|
427
461
|
title: canvas.title,
|
|
@@ -468,9 +502,9 @@ function buildXsArchive(file) {
|
|
|
468
502
|
focalLen: null,
|
|
469
503
|
focalLength: null
|
|
470
504
|
},
|
|
471
|
-
profileRefs
|
|
505
|
+
profileRefs,
|
|
472
506
|
patches: {},
|
|
473
|
-
bindings:
|
|
507
|
+
bindings: deviceBindings
|
|
474
508
|
}
|
|
475
509
|
}
|
|
476
510
|
}
|
|
@@ -739,6 +773,8 @@ var XCSGenerator = class {
|
|
|
739
773
|
displays = [];
|
|
740
774
|
layers = /* @__PURE__ */ new Map();
|
|
741
775
|
options;
|
|
776
|
+
/** Keyed by `JSON.stringify([processingType, values])` to merge displays sharing identical settings into one profile. */
|
|
777
|
+
processingBindings = /* @__PURE__ */ new Map();
|
|
742
778
|
constructor(options = {}) {
|
|
743
779
|
this.options = {
|
|
744
780
|
deviceId: options.deviceId || "P2S",
|
|
@@ -760,6 +796,7 @@ var XCSGenerator = class {
|
|
|
760
796
|
addText(text, x, y, options = {}) {
|
|
761
797
|
const textObj = this.createTextObject(text, x, y, options);
|
|
762
798
|
this.displays.push(textObj);
|
|
799
|
+
this.registerProcessing(textObj.id, options.processing);
|
|
763
800
|
return this;
|
|
764
801
|
}
|
|
765
802
|
/**
|
|
@@ -768,6 +805,7 @@ var XCSGenerator = class {
|
|
|
768
805
|
addPath(pathData, x, y, width, height, options = {}) {
|
|
769
806
|
const pathObj = this.createPathObject(pathData, x, y, width, height, options);
|
|
770
807
|
this.displays.push(pathObj);
|
|
808
|
+
this.registerProcessing(pathObj.id, options.processing);
|
|
771
809
|
return this;
|
|
772
810
|
}
|
|
773
811
|
/**
|
|
@@ -851,6 +889,7 @@ var XCSGenerator = class {
|
|
|
851
889
|
opacity: 1
|
|
852
890
|
};
|
|
853
891
|
this.displays.push(bitmap);
|
|
892
|
+
this.registerProcessing(bitmap.id, options.processing);
|
|
854
893
|
return this;
|
|
855
894
|
}
|
|
856
895
|
/**
|
|
@@ -919,7 +958,22 @@ var XCSGenerator = class {
|
|
|
919
958
|
* container differs from `toBytes()`.
|
|
920
959
|
*/
|
|
921
960
|
toXsBytes() {
|
|
922
|
-
return buildXsArchive(this.generate());
|
|
961
|
+
return buildXsArchive(this.generate(), Array.from(this.processingBindings.values()));
|
|
962
|
+
}
|
|
963
|
+
/** Merges a display into an existing profile when another display already shares its exact `processing` settings, else starts a new one. */
|
|
964
|
+
registerProcessing(displayId, processing) {
|
|
965
|
+
if (!processing) return;
|
|
966
|
+
const key = JSON.stringify([processing.processingType, processing.values]);
|
|
967
|
+
const existing = this.processingBindings.get(key);
|
|
968
|
+
if (existing) {
|
|
969
|
+
existing.displayIds.push(displayId);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
this.processingBindings.set(key, {
|
|
973
|
+
processingType: processing.processingType,
|
|
974
|
+
values: processing.values,
|
|
975
|
+
displayIds: [displayId]
|
|
976
|
+
});
|
|
923
977
|
}
|
|
924
978
|
createCanvas() {
|
|
925
979
|
return {
|