@richardmcquiston01/unofficial-xcs-writer 0.8.1 → 0.8.2

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.
Files changed (4) hide show
  1. package/API.md +32 -0
  2. package/CHANGELOG.md +43 -0
  3. package/README.md +110 -104
  4. package/package.json +1 -1
package/API.md CHANGED
@@ -1,17 +1,27 @@
1
1
  # API Reference
2
2
 
3
+ [Back to README](./README.md)
4
+
5
+ | [`assertXcsFormat(buffer: ArrayBuffer): void`](#assertxcsformatbuffer-arraybuffer-void) | [`readXcsFile(buffer: ArrayBuffer): string`](#readxcsfilebuffer-arraybuffer-string) | [`extractXcsTokens(buffer: ArrayBuffer): string[]`](#extractxcstokensbuffer-arraybuffer-string) | [`renderXcsFile(buffer, variables, values, fonts?): Uint8Array`](#renderxcsfilebuffer-variables-values-fonts-uint8array) | [`XcsVariable`](#xcsvariable) | [`.xs` (v2 workspace) format](#xs-v2-workspace-format) | [`assertXsFormat(buffer: ArrayBuffer): void`](#assertxsformatbuffer-arraybuffer-void) | [`extractXsTokens(buffer: ArrayBuffer): string[]`](#extractxstokensbuffer-arraybuffer-string) | [`renderXsFile(buffer, variables, values, fonts?): Uint8Array`](#renderxsfilebuffer-variables-values-fonts-uint8array) | [`buildXsArchive(file: XCSFile): Uint8Array`](#buildxsarchivefile-xcsfile-uint8array) | [`loadFont(buffer: ArrayBuffer)` / `loadDefaultFont()` / `layoutText(font, text, originX, originY)`](#loadfontbuffer-arraybuffer--loaddefaultfont--layouttextfont-text-originx-originy) | [Building (`src/builder.ts`, `src/layout.ts`, `src/machines.ts`)](#building-srcbuilderts-srclayoutts-srcmachinests) |
6
+
3
7
  ## `assertXcsFormat(buffer: ArrayBuffer): void`
4
8
 
5
9
  Validates that `buffer` is a parseable XCS JSON file containing a `canvas` array. Throws a descriptive error if the file is invalid or unrecognized.
6
10
 
11
+ [Back to Top](#api-reference)
12
+
7
13
  ## `readXcsFile(buffer: ArrayBuffer): string`
8
14
 
9
15
  Decodes the buffer and returns the raw JSON string. Useful for inspection or pass-through scenarios.
10
16
 
17
+ [Back to Top](#api-reference)
18
+
11
19
  ## `extractXcsTokens(buffer: ArrayBuffer): string[]`
12
20
 
13
21
  Scans all `TEXT` display objects across all canvases and returns the unique token names found (without braces). For example, `{{LastName}}` in the file → `"LastName"` in the returned array.
14
22
 
23
+ [Back to Top](#api-reference)
24
+
15
25
  ## `renderXcsFile(buffer, variables, values, fonts?): Uint8Array`
16
26
 
17
27
  Applies substitution to every `TEXT` display object and returns the modified file encoded as a UTF-8 `Uint8Array`. Tokens with no matching entry in `values` fall back to `variable.defaultValue`, or an empty string if no default is set.
@@ -29,6 +39,8 @@ Any `fontFamily` not present in `fonts` falls back to a bundled default (Arimo,
29
39
 
30
40
  **Known limitation:** `renderXcsFile` only reproduces straight horizontal-baseline layout when substituting into an *existing* TEXT display. xTool Studio's `style.curveX`/`curveY` curved-text formula is undocumented and can't be reverse-decoded from a file alone, so a curved display's substituted text gets correctly-shaped glyphs laid out straight instead of on the original curve. This doesn't apply to the *building* API below — there, you supply the curve angle yourself (see `layoutCurvedGlyphText`), so there's nothing to decode.
31
41
 
42
+ [Back to Top](#api-reference)
43
+
32
44
  ## `XcsVariable`
33
45
 
34
46
  ```ts
@@ -38,28 +50,42 @@ interface XcsVariable {
38
50
  }
39
51
  ```
40
52
 
53
+ [Back to Top](#api-reference)
54
+
41
55
  ## `.xs` (v2 workspace) format
42
56
 
57
+ [Back to Top](#api-reference)
58
+
43
59
  ## `assertXsFormat(buffer: ArrayBuffer): void`
44
60
 
45
61
  Validates that `buffer` is a ZIP archive with a `.format` entry equal to `"v2"` and a `project.json` entry. Throws a descriptive error otherwise.
46
62
 
63
+ [Back to Top](#api-reference)
64
+
47
65
  ## `extractXsTokens(buffer: ArrayBuffer): string[]`
48
66
 
49
67
  Same as `extractXcsTokens`, but scans every `canvases/<canvasId>/displays-<chunkIndex>.json` chunk across the archive instead of a single `canvas` array.
50
68
 
69
+ [Back to Top](#api-reference)
70
+
51
71
  ## `renderXsFile(buffer, variables, values, fonts?): Uint8Array`
52
72
 
53
73
  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.
54
74
 
75
+ [Back to Top](#api-reference)
76
+
55
77
  ## `buildXsArchive(file: XCSFile): Uint8Array`
56
78
 
57
79
  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.
58
80
 
81
+ [Back to Top](#api-reference)
82
+
59
83
  ## `loadFont(buffer: ArrayBuffer)` / `loadDefaultFont()` / `layoutText(font, text, originX, originY)`
60
84
 
61
85
  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.
62
86
 
87
+ [Back to Top](#api-reference)
88
+
63
89
  ## Building (`src/builder.ts`, `src/layout.ts`, `src/machines.ts`)
64
90
 
65
91
  `createXCS(deviceId?)` / `new XCSGenerator(options?)` — starts a new project (one canvas, one default layer). `deviceId` (and `options.deviceId`) accepts a `XTOOL_MACHINES` key, a `MachineProfile` object, or a raw device id string — see "Building a project from scratch" in the [README](./README.md). Chainable methods:
@@ -84,3 +110,9 @@ Text layout — build the `layout` option for `.addText()` with one of:
84
110
  | `layoutCurvedGlyphText(font, text, emSizeMm, curveDeg, boxWidthMm, boxHeightMm, align?, letterSpacingMm?)` | Single-line, curved along an arc spanning `curveDeg` degrees across a `boxWidthMm`-wide box (positive arches the apex upward, negative bows it downward — same convention as SVG `<textPath>`/canvas curved-text editors). Multi-line curved text isn't supported (matches typical curved-text editors, which also flatten multi-line curved text to one line). **Not yet visually verified against real xTool Studio rendering** (there's no way to check outside the application itself) — the geometry is self-consistent and unit-tested, but treat curved output as best-effort until you've spot-checked it. |
85
111
 
86
112
  All three return `GlyphTextLayout | null` (`null` for empty/whitespace-only text). `translateGlyphLayout(layout, dxMm, dyMm)` cheaply shifts an already-built layout without relaying out the font.
113
+
114
+ [Back to Top](#api-reference)
115
+
116
+ ---
117
+
118
+ [Back to README](./README.md)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ [Back to README](./README.md)
4
+
5
+ | [Unreleased](#unreleased) | [0.8.2 - 2026-10-13](#082---2026-10-13) | [0.8.1 - 2026-09-21](#081---2026-09-21) | [0.8.0 - 2026-09-21](#080---2026-09-21) | [0.7.0 - 2026-09-21](#070---2026-09-21) | [0.6.0 - 2026-09-21](#060---2026-09-21) | [0.5.0 - 2026-09-20](#050---2026-09-20) | [0.4.1 - 2026-09-19](#041---2026-09-19) | [0.4.0 - 2026-09-19](#040---2026-09-19) | [0.3.1 - 2026-08-17](#031---2026-08-17) | [0.3.0 - 2026-07-14](#030---2026-07-14) | [0.2.0 - 2026-07-14](#020---2026-07-14) | [0.1.0 - 2026-05-19](#010---2026-05-19) |
6
+
3
7
  All notable changes to this project will be documented in this file.
4
8
 
5
9
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
@@ -7,6 +11,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
11
 
8
12
  ## [Unreleased]
9
13
 
14
+ [Back to Top](#changelog)
15
+
16
+ ## [0.8.2] - 2026-10-13
17
+
18
+ ### Changed
19
+
20
+ - **Documentation only**: no library code changed. Rewrote `README.md` to be shorter, with an
21
+ author block, a Table of Contents, Back to Top links, a Supported Machines table, and the
22
+ License section corrected to MIT. Added a Back to README link, a menu row, and Back to Top
23
+ links to `API.md` and `CHANGELOG.md`.
24
+
25
+ [Back to Top](#changelog)
26
+
10
27
  ## [0.8.1] - 2026-09-21
11
28
 
12
29
  ### Changed
@@ -22,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
39
  registered trademarks of Makeblock Co., Ltd., and that this project isn't affiliated with,
23
40
  endorsed by, or sponsored by Makeblock or xTool.
24
41
 
42
+ [Back to Top](#changelog)
43
+
25
44
  ## [0.8.0] - 2026-09-21
26
45
 
27
46
  ### Added
@@ -42,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
61
  (`file.device.id`, i.e. `"P2S"`). `.xcs` output (`.toBytes()`) is unaffected -- it has no
43
62
  `deviceCode` field.
44
63
 
64
+ [Back to Top](#changelog)
65
+
45
66
  ## [0.7.0] - 2026-09-21
46
67
 
47
68
  ### Added
@@ -56,6 +77,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
56
77
  is structurally different and much less understood, so it isn't implemented (see `CLAUDE.md`'s
57
78
  Open Issues).
58
79
 
80
+ [Back to Top](#changelog)
81
+
59
82
  ## [0.6.0] - 2026-09-21
60
83
 
61
84
  ### Added
@@ -70,6 +93,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
93
  generated `.xs` file's power/speed settings may need to be configured manually in xTool
71
94
  Studio before cutting/engraving.
72
95
 
96
+ [Back to Top](#changelog)
97
+
73
98
  ## [0.5.0] - 2026-09-20
74
99
 
75
100
  ### Added
@@ -87,6 +112,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
87
112
  vector store. `.xs` *generation* (building a new project from scratch) isn't implemented yet
88
113
  — see `CLAUDE.md`'s "Open Issues".
89
114
 
115
+ [Back to Top](#changelog)
116
+
90
117
  ## [0.4.1] - 2026-09-19
91
118
 
92
119
  ### Changed
@@ -97,6 +124,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
97
124
  from 0.4.0; this is the first published release carrying the generation API
98
125
  and curved-text layout described below.
99
126
 
127
+ [Back to Top](#changelog)
128
+
100
129
  ## [0.4.0] - 2026-09-19
101
130
 
102
131
  ### Added
@@ -124,6 +153,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
124
153
  still can't decode an existing curved display's `style.curveX`/`curveY`, but the new building
125
154
  API doesn't have that problem (the caller supplies the angle directly).
126
155
 
156
+ [Back to Top](#changelog)
157
+
127
158
  ## [0.3.1] - 2026-08-17
128
159
 
129
160
  ### Changed
@@ -134,6 +165,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
134
165
  - `donate.svg` is included in the published tarball so the README's QR image
135
166
  resolves for consumers reading it from the package.
136
167
 
168
+ [Back to Top](#changelog)
169
+
137
170
  ## [0.3.0] - 2026-07-14
138
171
 
139
172
  ### Added
@@ -149,6 +182,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
149
182
  0.2.0 tarball on npm was packed from a stale `dist/` and does not actually
150
183
  contain the glyph regeneration code its README describes — use 0.3.0 instead.
151
184
 
185
+ [Back to Top](#changelog)
186
+
152
187
  ## [0.2.0] - 2026-07-14
153
188
 
154
189
  > **Warning:** the npm tarball for this version was built from a stale `dist/`
@@ -174,6 +209,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
174
209
  substituted text on a curved TEXT display gets correctly-shaped glyphs laid
175
210
  out on a straight baseline.
176
211
 
212
+ [Back to Top](#changelog)
213
+
177
214
  ## [0.1.0] - 2026-05-19
178
215
 
179
216
  ### Added
@@ -183,3 +220,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
183
220
  - Core API: `assertXcsFormat`, `readXcsFile`, `extractXcsTokens`,
184
221
  `renderXcsFile`, and the `XcsVariable` type.
185
222
  - Dual ESM + CJS build with TypeScript declarations via tsup.
223
+
224
+ [Back to Top](#changelog)
225
+
226
+ ---
227
+
228
+ [Back to README](./README.md)
package/README.md CHANGED
@@ -1,23 +1,43 @@
1
1
  # unofficial-xcs-writer
2
2
 
3
- > **Disclaimer:** This is an unofficial, community-built library. It is not affiliated with, endorsed by, or sponsored by xTool in any way.
3
+ - Author: [Richard McQuiston](mailto:richard.mcquiston@gmail.com)
4
+ - Website: https://richardmcquiston.com/
4
5
 
5
- A framework-agnostic TypeScript library for reading, writing, building, and applying variable substitution to `.xcs` and `.xs` files produced by xTool Creative Space.
6
+ > **Disclaimer:** This is an unofficial, community-built library. It is not affiliated with, endorsed by, or sponsored by xTool or Makeblock.
6
7
 
7
- ## What is an XCS / XS file?
8
+ ## Table of Contents
8
9
 
9
- 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.
10
+ | [Overview](#overview) | [Features](#features) | [Getting Started](#getting-started) | [Supported Machines](#supported-machines) | [Known Limitations](#known-limitations) | [More Information](#more-information) | [Development](#development) | [Buy Me a Coffee](#buy-me-a-coffee) | [License](#license) | [Copyright](#copyright) |
10
11
 
11
- 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.
12
+ ## Overview
12
13
 
13
- This library lets you, for both formats:
14
+ This is a TypeScript library for xTool project files. It reads, builds, and fills in templates for `.xcs` and `.xs` files.
14
15
 
15
- - **Read** a file and inspect its contents
16
- - **Extract** `{{token}}` template placeholders from text objects
17
- - **Render** a filled-in copy by substituting values for those placeholders
18
- - **Build** a new project from scratch — text (straight, multi-line, or curved), paths, and embedded images — and export it as either `.xcs` or `.xs`
16
+ An `.xcs` file is plain JSON. An `.xs` file is a ZIP archive of JSON files. xTool Studio 1.7 and newer saves `.xs` by default. It can still open `.xcs` files but can no longer save them.
19
17
 
20
- ## Installation
18
+ The library uses no framework, no DOM, and no Node.js-only features. It works in browsers and in Node.js.
19
+
20
+ [Back to Top](#table-of-contents)
21
+
22
+ ## Features
23
+
24
+ - Find `{{token}}` placeholders in text objects.
25
+ - Fill in the placeholders with your own values. The library draws new glyph outlines for the changed text.
26
+ - Build a project from scratch. You can add straight, multi-line, or curved text, paths, and images.
27
+ - Save the project as `.xcs` or `.xs`.
28
+ - Pick a target machine from a list of 13 xTool devices.
29
+ - Set power and speed for each object in `.xs` output.
30
+
31
+ [Back to Top](#table-of-contents)
32
+
33
+ ## Getting Started
34
+
35
+ ### Prerequisites
36
+
37
+ - Node.js 19 or newer, or a current web browser.
38
+ - A package manager such as npm or pnpm.
39
+
40
+ ### Installation
21
41
 
22
42
  ```bash
23
43
  npm install @richardmcquiston01/unofficial-xcs-writer
@@ -25,49 +45,46 @@ npm install @richardmcquiston01/unofficial-xcs-writer
25
45
  pnpm add @richardmcquiston01/unofficial-xcs-writer
26
46
  ```
27
47
 
28
- ## Usage
48
+ ### Usage
49
+
50
+ To fill in a template, follow these steps:
51
+
52
+ 1. Load the file as an `ArrayBuffer`.
53
+ 2. Check the format with `assertXcsFormat`. It throws an error if the file is not valid.
54
+ 3. List the placeholders with `extractXcsTokens`.
55
+ 4. Call `renderXcsFile` with your values.
56
+
57
+ For `.xs` files, use `assertXsFormat`, `extractXsTokens`, and `renderXsFile`. They work the same way.
58
+
59
+ To build a project, call `createXCS` with a machine name. Add objects with `.addText()`, `.addPath()`, and `.addBitmap()`. Finish with `.toBytes()` for `.xcs` or `.toXsBytes()` for `.xs`.
60
+
61
+ ### Examples
62
+
63
+ Fill in a template:
29
64
 
30
65
  ```ts
31
- import { assertXcsFormat, extractXcsTokens, renderXcsFile } from '@richardmcquiston01/unofficial-xcs-writer';
66
+ import {
67
+ assertXcsFormat,
68
+ extractXcsTokens,
69
+ renderXcsFile,
70
+ } from '@richardmcquiston01/unofficial-xcs-writer';
32
71
  import type { XcsVariable } from '@richardmcquiston01/unofficial-xcs-writer';
33
72
 
34
- // Load an .xcs file as an ArrayBuffer (browser or Node.js)
35
- const buffer = await fetch('/templates/name-tag.xcs').then(r => r.arrayBuffer());
36
-
37
- // Validate it's a real XCS file
38
- assertXcsFormat(buffer); // throws if invalid
73
+ const buffer: ArrayBuffer = await fetch('/templates/name-tag.xcs').then((r) => r.arrayBuffer());
39
74
 
40
- // Discover which {{token}} placeholders are in the file
41
- const tokens = extractXcsTokens(buffer);
42
- // e.g. ['FirstName', 'LastName']
75
+ assertXcsFormat(buffer);
76
+ const tokens: string[] = extractXcsTokens(buffer); // ['FirstName', 'LastName']
43
77
 
44
- // Define variables and supply values
45
78
  const variables: XcsVariable[] = [
46
79
  { token: 'FirstName', defaultValue: 'Guest' },
47
80
  { token: 'LastName' },
48
81
  ];
49
- const values = { FirstName: 'Jane', LastName: 'Smith' };
82
+ const values: Record<string, string> = { FirstName: 'Jane', LastName: 'Smith' };
50
83
 
51
- // Produce a filled-in .xcs file ready for download
52
84
  const output: Uint8Array = renderXcsFile(buffer, variables, values);
53
85
  ```
54
86
 
55
- ### Working with `.xs` files
56
-
57
- 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:
58
-
59
- ```ts
60
- import { assertXsFormat, extractXsTokens, renderXsFile } from '@richardmcquiston01/unofficial-xcs-writer';
61
-
62
- const buffer = await fetch('/templates/name-tag.xs').then(r => r.arrayBuffer());
63
-
64
- assertXsFormat(buffer); // throws if invalid
65
- const tokens = extractXsTokens(buffer); // e.g. ['FirstName', 'LastName']
66
-
67
- const output: Uint8Array = renderXsFile(buffer, variables, values);
68
- ```
69
-
70
- ### Building a project from scratch
87
+ Build a project from scratch:
71
88
 
72
89
  ```ts
73
90
  import {
@@ -79,107 +96,96 @@ import {
79
96
 
80
97
  const font = loadDefaultFont();
81
98
 
82
- const project = createXCS('P2S')
83
- // Straight text needs a real glyph layout so xTool Studio renders
84
- // actual outlines instead of placeholder blocks.
99
+ const bytes: Uint8Array = createXCS('P2S')
85
100
  .addText('Hello', 10, 10, {
86
101
  fontFamily: 'Arial',
87
102
  fontSize: 24,
88
103
  layout: layoutGlyphText(font, 'Hello', 8, 10, 10),
89
104
  })
90
- // Curved text: same idea, via layoutCurvedGlyphText -- the curve
91
- // is baked into each character's own glyph shape and position, the
92
- // way substituted text already bakes straight shapes.
93
105
  .addText('Arch Text', 0, 40, {
94
106
  fontFamily: 'Arial',
95
107
  fontSize: 18,
96
108
  layout: layoutCurvedGlyphText(font, 'Arch Text', 6, 90, 60, 20, 'center'),
97
109
  })
98
110
  .addPath('M0 0L10 0L10 10L0 10Z', 0, 60, 10, 10, {
99
- // Only affects .toXsBytes() output -- see below.
100
111
  processing: { processingType: 'VECTOR_CUTTING', values: { power: 80, speed: 10 } },
101
112
  })
102
- .toBytes(); // Uint8Array, ready to write to a .xcs file
113
+ .toXsBytes();
103
114
  ```
104
115
 
105
- Call `.toXsBytes()` instead of `.toBytes()` to export the same project as a `.xs` (v2 workspace)
106
- archive — same chainable builder, same display objects, different container.
107
-
108
- Pass `processing: { processingType, values }` to `.addText()`/`.addPath()`/`.addBitmap()`'s
109
- options to set that display's power/speed/etc. in the exported `.xs` file's `profiles.json` +
110
- device bindings — `.xcs` output (`.toBytes()`) ignores this option; its own per-display
111
- processing format is different and not supported yet. Displays given identical settings share
112
- one profile/binding automatically. `processingType`/`values`' exact shape is xTool Studio's own
113
- and undocumented beyond real exports — see `xs_samples/*.xs`'s `profiles.json` for examples
114
- (`VECTOR_ENGRAVING`, `VECTOR_CUTTING`, `FILL_VECTOR_ENGRAVING`). Displays with no `processing`
115
- option behave as before: a generated `.xs` file's power/speed may need to be set manually in
116
- xTool Studio before cutting/engraving.
117
-
118
- `createXCS('P2S')`'s argument (and `XCSGeneratorOptions.deviceId`) accepts a known machine name
119
- from `XTOOL_MACHINES` — currently 13 machines verified from real exports in this repo (`"P2S"`,
120
- `"F2 Ultra UV"`, `"S1"`, `"P3"`, `"M2"`, `"F2"`, `"F2 Ultra (Single)"`, `"F2 Ultra"`,
121
- `"M1 Ultra"`, `"F1"`, `"F1 Lite"`, `"F1 Ultra"`, `"MetalFab"`) — and resolves the real
122
- `extId`/`extName`/`deviceCode` (`.xs` only)/default power for that machine, instead of reusing
123
- whatever string you pass for all of them. Export the same design for multiple machines by
124
- building it once and calling `.toXsBytes()`/`.toBytes()` once per `createXCS(machineName)`:
116
+ Keep these points in mind:
125
117
 
126
- ```ts
127
- import { createXCS, XTOOL_MACHINES } from '@richardmcquiston01/unofficial-xcs-writer';
118
+ - Text needs a glyph `layout`. Without one, xTool Studio shows placeholder blocks instead of real letters.
119
+ - The `processing` option only changes `.xs` output. Objects with the same settings share one profile.
120
+ - The library does not check the processing values. Confirm power and speed in xTool Studio before you cut.
128
121
 
129
- for (const machine of Object.keys(XTOOL_MACHINES)) {
130
- const bytes = createXCS(machine).addPath('M0 0L10 0L10 10L0 10Z', 0, 0, 10, 10).toXsBytes();
131
- // write `bytes` to e.g. `design-${machine}.xs`
132
- }
133
- ```
122
+ [Back to Top](#table-of-contents)
134
123
 
135
- A device id not in the catalog still works exactly as before (no `.xs` `deviceCode`, `extId`/
136
- `extName`/`device.id` all set to that string, default power `55`); you can also pass a
137
- `MachineProfile` object directly for a machine not yet in the catalog.
124
+ ## Supported Machines
138
125
 
139
- ## Buy Me a Coffee
126
+ Pass one of these names to `createXCS`. The library then uses the real device ids and default power for that machine.
140
127
 
141
- If this app, code, or repository has helped you or someone you know, please consider donating. I appreciate any help to offset the costs of development and/or AI Credits.
128
+ | | | | |
129
+ |---|---|---|---|
130
+ | P2S | P3 | S1 | M2 |
131
+ | M1 Ultra | F1 | F1 Lite | F1 Ultra |
132
+ | F2 | F2 Ultra | F2 Ultra (Single) | F2 Ultra UV |
133
+ | MetalFab | | | |
142
134
 
143
- [**Donate via Stripe**](https://donate.stripe.com/00w5kD3Gj1Xo9v7gVOcs800), or scan:
135
+ You can also pass a `MachineProfile` object or a plain device id string. A plain string still works, but it gets default values. To export one design for every machine, loop over `Object.keys(XTOOL_MACHINES)`.
144
136
 
145
- [![Donate via Stripe](./donate.svg)](https://donate.stripe.com/00w5kD3Gj1Xo9v7gVOcs800)
137
+ [Back to Top](#table-of-contents)
138
+
139
+ ## Known Limitations
140
+
141
+ - New text on an already curved text object is laid out straight.
142
+ - Curved text built from scratch has not been checked against real xTool Studio output.
143
+ - Multi-line curved text is not supported.
144
+ - `.xcs` output has no processing-profile support.
146
145
 
147
- ## API
146
+ [Back to Top](#table-of-contents)
148
147
 
149
- See [API.md](./API.md) for the full API reference — function signatures, options, types, and important behavior notes (glyph regeneration, curved-text limitations, machine catalog usage, etc.).
148
+ ## More Information
149
+
150
+ - [API.md](./API.md) lists every function, option, and type.
151
+ - [CHANGELOG.md](./CHANGELOG.md) lists changes for each release.
152
+
153
+ [Back to Top](#table-of-contents)
150
154
 
151
155
  ## Development
152
156
 
153
157
  ```bash
154
- pnpm install # install dev dependencies
155
- pnpm build # compile → dist/ (ESM + CJS + .d.ts)
156
- pnpm dev # watch mode — rebuilds on save
157
- pnpm test # run tests with vitest
158
- npx tsc --noEmit # type-check without emitting
158
+ pnpm install # install dependencies
159
+ pnpm build # build to dist/ (ESM, CJS, and type files)
160
+ pnpm dev # rebuild on every save
161
+ pnpm test # run the tests
162
+ pnpm typecheck # check types without building
159
163
  ```
160
164
 
161
- Output files after `pnpm build`:
165
+ The library uses [opentype.js](https://github.com/opentypejs/opentype.js) to read glyph outlines and [fflate](https://github.com/101arrowz/fflate) to read and write ZIP archives. The built-in fallback font is [Arimo](https://fonts.google.com/specimen/Arimo) version 5.2.8, under the Apache 2 license. Its license text is in `third_party/arimo/LICENSE`.
166
+
167
+ [Back to Top](#table-of-contents)
162
168
 
163
- | File | Format |
164
- |------|--------|
165
- | `dist/index.js` | ESM |
166
- | `dist/index.cjs` | CommonJS |
167
- | `dist/index.d.ts` | TypeScript declarations |
169
+ ## Buy Me a Coffee
168
170
 
169
- ## Notes
171
+ If this app, code, or repository has helped you or someone you know, please consider donating. I appreciate any help to offset the costs of development and/or AI Credits.
170
172
 
171
- - 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.)
172
- - Only `TEXT` display objects are modified during substitution. Geometry, bitmaps, device configuration, and all other fields pass through unchanged.
173
- - This library targets the current xTool Creative Space JSON/`.xs` formats. Older versions of the software may export a different structure.
174
- - `.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.
175
- - 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`).
173
+ [**Donate via Stripe**](https://donate.stripe.com/00w5kD3Gj1Xo9v7gVOcs800), or scan:
174
+
175
+ [![Donate via Stripe](./donate.svg)](https://donate.stripe.com/00w5kD3Gj1Xo9v7gVOcs800)
176
+
177
+ [Back to Top](#table-of-contents)
176
178
 
177
179
  ## License
178
180
 
179
- [MIT](LICENSE). The bundled Arimo fallback font is separately licensed under Apache-2.0 (`third_party/arimo/LICENSE`).
181
+ MIT. See [LICENSE](./LICENSE). The bundled Arimo font uses the Apache 2 license.
182
+
183
+ [Back to Top](#table-of-contents)
180
184
 
181
185
  ## Copyright
182
186
 
183
- (c)2026 Richard McQuiston
187
+ (c)2026 Richard McQuiston. All rights reserved.
188
+
189
+ xTool, xTool Studio, and xTool Creative Space are trademarks of Makeblock Co., Ltd. This project is not affiliated with, endorsed by, or sponsored by Makeblock Co., Ltd. or xTool.
184
190
 
185
- xTool, xTool Studio, and xTool Creative Space are registered trademarks of Makeblock Co., Ltd. This project is not affiliated with, endorsed by, or sponsored by Makeblock Co., Ltd. or xTool.
191
+ [Back to Top](#table-of-contents)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richardmcquiston01/unofficial-xcs-writer",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Read, write, build, and apply variable substitution to xTool Creative Space .xcs and .xs files, with real glyph outline extraction for text",
5
5
  "license": "MIT",
6
6
  "author": "Richard McQuiston <richard.mcquiston01@gmail.com>",