@richardmcquiston01/unofficial-xcs-writer 0.2.0 → 0.3.1
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 +66 -0
- package/LICENSE +21 -0
- package/README.md +135 -119
- package/dist/index.cjs +225 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +212 -2
- package/dist/index.js.map +1 -1
- package/donate.svg +2 -0
- package/package.json +53 -51
- package/third_party/arimo/LICENSE +200 -200
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.1] - 2026-08-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Replaced the ad-hoc PayPal "Support" section in the README with the standard
|
|
13
|
+
"Buy Me a Coffee" donation block (Stripe link plus a scannable QR code at
|
|
14
|
+
`donate.svg`), now the last section of the README.
|
|
15
|
+
- `donate.svg` is included in the published tarball so the README's QR image
|
|
16
|
+
resolves for consumers reading it from the package.
|
|
17
|
+
|
|
18
|
+
## [0.3.0] - 2026-07-14
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Root `LICENSE` file (MIT) — previously declared in `package.json` but not shipped.
|
|
23
|
+
- `prepublishOnly` script that rebuilds and runs the test suite before every
|
|
24
|
+
`npm publish`, preventing stale-`dist/` releases.
|
|
25
|
+
- This changelog.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Republished the glyph outline extraction feature with a correct build. The
|
|
30
|
+
0.2.0 tarball on npm was packed from a stale `dist/` and does not actually
|
|
31
|
+
contain the glyph regeneration code its README describes — use 0.3.0 instead.
|
|
32
|
+
|
|
33
|
+
## [0.2.0] - 2026-07-14
|
|
34
|
+
|
|
35
|
+
> **Warning:** the npm tarball for this version was built from a stale `dist/`
|
|
36
|
+
> and lacks the glyph extraction feature listed below. Use 0.3.0 or later.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- Real per-character glyph outline extraction (`src/glyphs.ts`, powered by
|
|
41
|
+
[opentype.js](https://github.com/opentypejs/opentype.js)). When
|
|
42
|
+
`renderXcsFile` changes a TEXT display's text, its `charJSONs` /
|
|
43
|
+
`fontData.glyphData` are regenerated with real glyph outlines so xTool
|
|
44
|
+
Studio renders the substituted text correctly.
|
|
45
|
+
- Optional `fonts` parameter on `renderXcsFile` to supply real font bytes
|
|
46
|
+
(TTF/OTF/WOFF) keyed by `style.fontFamily`.
|
|
47
|
+
- Bundled fallback font: Arimo v5.2.8 (Apache-2.0, metric-compatible with
|
|
48
|
+
Arial) for families not supplied via `fonts`.
|
|
49
|
+
- Lower-level glyph primitives exported for direct use: `loadFont`,
|
|
50
|
+
`loadDefaultFont`, `layoutText`.
|
|
51
|
+
|
|
52
|
+
### Known limitations
|
|
53
|
+
|
|
54
|
+
- Curved-text layout (`style.curveX`/`curveY`) is not reproduced —
|
|
55
|
+
substituted text on a curved TEXT display gets correctly-shaped glyphs laid
|
|
56
|
+
out on a straight baseline.
|
|
57
|
+
|
|
58
|
+
## [0.1.0] - 2026-05-19
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- Initial release: read, validate, and apply `{{token}}` variable
|
|
63
|
+
substitution to xTool Creative Space `.xcs` files.
|
|
64
|
+
- Core API: `assertXcsFormat`, `readXcsFile`, `extractXcsTokens`,
|
|
65
|
+
`renderXcsFile`, and the `XcsVariable` type.
|
|
66
|
+
- Dual ESM + CJS build with TypeScript declarations via tsup.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Richard McQuiston
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,119 +1,135 @@
|
|
|
1
|
-
# unofficial-xcs-writer
|
|
2
|
-
|
|
3
|
-
A framework-agnostic TypeScript library for reading, writing, and applying variable substitution to `.xcs` files produced by [xTool Creative Space](https://www.xtool.com/pages/software). Not affiliated with xTool.
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
### `
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
### `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
### `
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
# unofficial-xcs-writer
|
|
2
|
+
|
|
3
|
+
A framework-agnostic TypeScript library for reading, writing, and applying variable substitution to `.xcs` files produced by [xTool Creative Space](https://www.xtool.com/pages/software). Not affiliated with xTool.
|
|
4
|
+
|
|
5
|
+
## What is an XCS file?
|
|
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. This library lets you:
|
|
8
|
+
|
|
9
|
+
- **Read** an XCS file and inspect its contents
|
|
10
|
+
- **Extract** `{{token}}` template placeholders from text objects
|
|
11
|
+
- **Render** a filled-in copy by substituting values for those placeholders
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @richardmcquiston01/unofficial-xcs-writer
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @richardmcquiston01/unofficial-xcs-writer
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { assertXcsFormat, extractXcsTokens, renderXcsFile } from '@richardmcquiston01/unofficial-xcs-writer';
|
|
25
|
+
import type { XcsVariable } from '@richardmcquiston01/unofficial-xcs-writer';
|
|
26
|
+
|
|
27
|
+
// Load an .xcs file as an ArrayBuffer (browser or Node.js)
|
|
28
|
+
const buffer = await fetch('/templates/name-tag.xcs').then(r => r.arrayBuffer());
|
|
29
|
+
|
|
30
|
+
// Validate it's a real XCS file
|
|
31
|
+
assertXcsFormat(buffer); // throws if invalid
|
|
32
|
+
|
|
33
|
+
// Discover which {{token}} placeholders are in the file
|
|
34
|
+
const tokens = extractXcsTokens(buffer);
|
|
35
|
+
// e.g. ['FirstName', 'LastName']
|
|
36
|
+
|
|
37
|
+
// Define variables and supply values
|
|
38
|
+
const variables: XcsVariable[] = [
|
|
39
|
+
{ token: 'FirstName', defaultValue: 'Guest' },
|
|
40
|
+
{ token: 'LastName' },
|
|
41
|
+
];
|
|
42
|
+
const values = { FirstName: 'Jane', LastName: 'Smith' };
|
|
43
|
+
|
|
44
|
+
// Produce a filled-in .xcs file ready for download
|
|
45
|
+
const output: Uint8Array = renderXcsFile(buffer, variables, values);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### `assertXcsFormat(buffer: ArrayBuffer): void`
|
|
51
|
+
|
|
52
|
+
Validates that `buffer` is a parseable XCS JSON file containing a `canvas` array. Throws a descriptive error if the file is invalid or unrecognized.
|
|
53
|
+
|
|
54
|
+
### `readXcsFile(buffer: ArrayBuffer): string`
|
|
55
|
+
|
|
56
|
+
Decodes the buffer and returns the raw JSON string. Useful for inspection or pass-through scenarios.
|
|
57
|
+
|
|
58
|
+
### `extractXcsTokens(buffer: ArrayBuffer): string[]`
|
|
59
|
+
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
### `renderXcsFile(buffer, variables, values, fonts?): Uint8Array`
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
Whenever a display's text actually changes, its glyph outline data (`charJSONs` / `fontData.glyphData`) is regenerated from a real font so xTool Studio renders the new text correctly — xTool Studio only trusts a TEXT display's stored glyph shapes; it does not re-shape text on its own unless you manually reselect the font inside the app. Displays whose text doesn't change are left completely untouched.
|
|
67
|
+
|
|
68
|
+
Pass `fonts` to supply real font file bytes (TTF/OTF/WOFF) keyed by a display's `style.fontFamily`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const arialBuffer = await fetch('/fonts/arial.ttf').then(r => r.arrayBuffer());
|
|
72
|
+
renderXcsFile(buffer, variables, values, { Arial: arialBuffer });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Any `fontFamily` not present in `fonts` falls back to a bundled default (Arimo, Apache-2.0, metric-compatible with Arial) — so the fix works out of the box with no font files required, just with shapes drawn from Arimo rather than the exact requested typeface.
|
|
76
|
+
|
|
77
|
+
**Known limitation:** only straight horizontal-baseline layout is reproduced. xTool Studio's `style.curveX`/`curveY` curved-text layout formula is undocumented and isn't replicated — substituted text on a curved TEXT display gets correctly-shaped glyphs laid out on a straight line instead of the original curve.
|
|
78
|
+
|
|
79
|
+
### `XcsVariable`
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
interface XcsVariable {
|
|
83
|
+
token: string; // token name without braces, e.g. "LastName"
|
|
84
|
+
defaultValue?: string;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `loadFont(buffer: ArrayBuffer)` / `loadDefaultFont()` / `layoutText(font, text, originX, originY)`
|
|
89
|
+
|
|
90
|
+
Lower-level glyph-extraction primitives `renderXcsFile` is built on, exported for direct use (e.g. building a `TEXT` display from scratch). See `src/glyphs.ts` for the exact xTool JSON conventions these were reverse-engineered against.
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pnpm install # install dev dependencies
|
|
96
|
+
pnpm build # compile → dist/ (ESM + CJS + .d.ts)
|
|
97
|
+
pnpm dev # watch mode — rebuilds on save
|
|
98
|
+
pnpm test # run tests with vitest
|
|
99
|
+
npx tsc --noEmit # type-check without emitting
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Output files after `pnpm build`:
|
|
103
|
+
|
|
104
|
+
| File | Format |
|
|
105
|
+
|------|--------|
|
|
106
|
+
| `dist/index.js` | ESM |
|
|
107
|
+
| `dist/index.cjs` | CommonJS |
|
|
108
|
+
| `dist/index.d.ts` | TypeScript declarations |
|
|
109
|
+
|
|
110
|
+
### Releasing
|
|
111
|
+
|
|
112
|
+
Publishing to npm is automated. Bump the version in `package.json`, add a
|
|
113
|
+
`CHANGELOG.md` entry, then create a GitHub release tagged `vX.Y.Z` — the
|
|
114
|
+
[publish workflow](.github/workflows/publish.yml) checks the tag against
|
|
115
|
+
`package.json`, rebuilds, runs the tests, and publishes with provenance.
|
|
116
|
+
It needs an `NPM_TOKEN` repository secret with publish rights.
|
|
117
|
+
|
|
118
|
+
## Notes
|
|
119
|
+
|
|
120
|
+
- 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.)
|
|
121
|
+
- Only `TEXT` display objects are modified during substitution. Geometry, bitmaps, device configuration, and all other fields pass through unchanged.
|
|
122
|
+
- This library targets the current xTool Creative Space JSON format. Older versions of the software may export a different structure.
|
|
123
|
+
- 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`).
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[MIT](LICENSE). The bundled Arimo fallback font is separately licensed under Apache-2.0 (`third_party/arimo/LICENSE`).
|
|
128
|
+
|
|
129
|
+
## Buy Me a Coffee
|
|
130
|
+
|
|
131
|
+
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.
|
|
132
|
+
|
|
133
|
+
[**Donate via Stripe**](https://donate.stripe.com/00w5kD3Gj1Xo9v7gVOcs800), or scan:
|
|
134
|
+
|
|
135
|
+
[](https://donate.stripe.com/00w5kD3Gj1Xo9v7gVOcs800)
|