@willwade/aac-processors 0.0.6 → 0.0.7

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 CHANGED
@@ -658,13 +658,8 @@ npm test
658
658
 
659
659
  - The repository keeps `package.json` at `0.0.0-development`; release tags control the published version.
660
660
  - Create a GitHub release with a semantic tag (e.g. `v2.1.0`). Publishing only runs for non-prerelease tags.
661
- - Add an `NPM_TOKEN` repository secret with publish rights. The release workflow uses it to authenticate and calls `npm publish`.
662
661
  - The workflow (`.github/workflows/publish.yml`) automatically installs dependencies, rewrites the package version from the tag, and runs the standard publish pipeline.
663
662
 
664
- **Private distribution options**
665
- - Unscoped packages on npm must be public. To keep this package private, re-scope it (e.g. `@your-org/aac-processors`) and configure `publishConfig.access: "restricted"`—this requires an npm org with paid seats.
666
- - Alternatively, publish to GitHub Packages by adjusting the workflow’s registry URL and using a `GITHUB_TOKEN` with the `packages: write` permission.
667
-
668
663
  ---
669
664
 
670
665
  ## 📄 License
@@ -715,15 +710,6 @@ Inspired by the Python AACProcessors project
715
710
  - [ ] **Add Symbol Tools coverage** (currently 0%) - Implement tests for PCS and ARASAAC symbol lookups to reach >70% coverage
716
711
  - [ ] **Fix property-based test failures** - Resolve TypeScript interface compatibility issues in edge case generators
717
712
 
718
- ### Recently Completed ✅
719
-
720
- - [x] **Core utilities test coverage** - Complete implementation for analyze.ts and fileProcessor.ts (0% → 100% coverage, 45 comprehensive tests, src/core/ directory 30% → 83% coverage)
721
- - [x] **CLI test infrastructure** - Fixed DotProcessor parsing and test expectations (0 → 25 passing tests, 100% CLI functionality)
722
- - [x] **Critical linting fixes** - Resolved unsafe argument types and CLI type safety issues (177 → 123 errors, 32% improvement)
723
- - [x] **Audio test syntax fixes** - Fixed non-null assertion errors in audio tests (21 → 5 failing tests, 76% improvement)
724
- - [x] **Comprehensive styling support** - Complete implementation across all AAC formats with cross-format preservation (Added: AACStyle interface, enhanced all processors, 7 new test cases, complete documentation)
725
- - [x] **TouchChatProcessor save/load functionality** - Fixed button persistence and ID mapping (coverage improved from 57.62% to 86.44%)
726
- - [x] **Build integration** - Ensure `npm run build` is executed before CLI tests (Fixed: All test scripts now automatically build before running)
727
713
 
728
714
  ### Medium Priority
729
715
 
@@ -749,29 +735,6 @@ Inspired by the Python AACProcessors project
749
735
  - [ ] **CI/CD improvements** - Add automated releases and npm publishing
750
736
  - [ ] **Documentation improvements** - Add more real-world examples and tutorials
751
737
 
752
- ### Known Issues
753
-
754
- - ⚠️ **Audio Persistence**: 5 functional tests failing due to audio recording not persisting through SnapProcessor save/load cycle
755
- - ⚠️ **Grid3 Layout**: Grid sizes not reliable and X,Y positions incorrect, particularly affecting Grid3 processor functionality
756
- - ⚠️ **Database Constraints**: UNIQUE constraint violations with real-world data files (Page.Id and buttons.id conflicts)
757
- - ⚠️ **Linting**: 123 ESLint issues remaining (mostly in test files, reduced from 177)
758
- - ⚠️ **SnapProcessor**: Lowest coverage at 48.32%, missing comprehensive audio handling tests
759
- - ⚠️ **Memory usage**: Large files (>50MB) may cause memory pressure
760
- - ⚠️ **Concurrent access**: Some processors not fully thread-safe for simultaneous writes
761
-
762
- ### 🧪 Current Test Status & Immediate Follow-Up
763
-
764
- As of the latest run (`npm test`), **47 suites pass / 6 fail (10 individual tests)**. Remaining blockers are:
765
-
766
- 1. **Edge-case loaders** – corrupted JSON/XML fixtures still expect explicit exceptions. Decide whether to restore the old throwing behaviour (Asterics/OPML/DOT) or update the tests to accept the softer error reporting.
767
- 2. **Gridset exports & styling** – round-trip continues to lose a button and the styling suite cannot find `style.xml`. GridsetProcessor needs to preserve button arrays and emit the styling assets Grid 3 expects.
768
- 3. **DOT navigation semantics** – the deterministic DOT test still falls back to `SPEAK`. Improve semantic reconstruction when loading navigation edges so navigation buttons survive round-trips.
769
- 4. **Advanced workflow scenario** – the multi-format pipeline still loses Spanish translations (likely during the Gridset ⇄ Snap steps); trace text propagation and patch the conversion chain.
770
- 5. **Styling suite** – Grid 3 export still lacks `style.xml`; ensure the styling assets are generated alongside the gridset payload.
771
- 6. **Memory comparison suite** – memory delta expectations are still unmet (TouchChat GC + DOT vs others). Either recalibrate the harness or tune the processors before re-enabling the assertions.
772
-
773
- Clearing these items will put the test matrix back in the green and unblock the release.
774
-
775
738
  ## More enhancements
776
739
 
777
740
  - Much more effort put into fixing the layout issues. Grid sizes are not reliably and X, Y positions too. Particularly in the Grid3
@@ -17,17 +17,14 @@ class DotProcessor extends baseProcessor_1.BaseProcessor {
17
17
  const edges = [];
18
18
  // Extract all edge statements using regex to handle single-line DOT files
19
19
  const edgeRegex = /"?([^"\s]+)"?\s*->\s*"?([^"\s]+)"?(?:\s*\[label="([^"]+)"\])?/g;
20
- const nodeRegex = /"?([^"\s]+)"?\s*\[label="([^"]+)"\]/g;
21
- // Find all explicit node definitions
22
- let nodeMatch;
23
- while ((nodeMatch = nodeRegex.exec(content)) !== null) {
24
- const [, id, label] = nodeMatch;
25
- nodes.set(id, { id, label });
26
- }
27
- // Find all edge definitions
20
+ // We need to find nodes, but avoid matching the target of an edge which might look like a node definition
21
+ // e.g. A -> B [label="L"] -- "B [label="L"]" looks like a node def
22
+ // Strategy: Find all edges, record them, and then "mask" them in the content to avoid false positives for nodes
23
+ let maskedContent = content;
28
24
  let edgeMatch;
25
+ // Find all edge definitions
29
26
  while ((edgeMatch = edgeRegex.exec(content)) !== null) {
30
- const [, from, to, label] = edgeMatch;
27
+ const [fullMatch, from, to, label] = edgeMatch;
31
28
  edges.push({ from, to, label });
32
29
  // Add nodes if they don't exist (implicit definition)
33
30
  if (!nodes.has(from)) {
@@ -36,6 +33,23 @@ class DotProcessor extends baseProcessor_1.BaseProcessor {
36
33
  if (!nodes.has(to)) {
37
34
  nodes.set(to, { id: to, label: to });
38
35
  }
36
+ // Mask this edge in the content so we don't match it as a node
37
+ // We replace it with spaces to preserve indices if needed, but simple replacement is enough here
38
+ maskedContent = maskedContent.replace(fullMatch, ' '.repeat(fullMatch.length));
39
+ }
40
+ // Now find explicit node definitions in the masked content
41
+ // This regex matches: ID [label="LABEL"]
42
+ // We use a non-greedy match for the label content to handle escaped quotes if possible,
43
+ // but the previous regex `[^"]+` was too simple.
44
+ // Better regex for quoted string content: (?:[^"\\]|\\.)*
45
+ const nodeRegex = /"?([^"\s]+)"?\s*\[label="((?:[^"\\]|\\.)*)"\]/g;
46
+ let nodeMatch;
47
+ while ((nodeMatch = nodeRegex.exec(maskedContent)) !== null) {
48
+ const [, id, rawLabel] = nodeMatch;
49
+ // Unescape the label: replace \" with " and \\ with \
50
+ const label = rawLabel.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
51
+ // Only update if not already defined or if we want to override the implicit label
52
+ nodes.set(id, { id, label });
39
53
  }
40
54
  return { nodes: Array.from(nodes.values()), edges };
41
55
  }
@@ -82,7 +96,8 @@ class DotProcessor extends baseProcessor_1.BaseProcessor {
82
96
  let hasControl = false;
83
97
  for (let i = 0; i < head.length; i++) {
84
98
  const code = head.charCodeAt(i);
85
- if (code === 0 || (code >= 0 && code <= 8) || (code >= 14 && code <= 31) || code >= 127) {
99
+ // Allow UTF-8 characters (code >= 127)
100
+ if (code === 0 || (code >= 0 && code <= 8) || (code >= 14 && code <= 31)) {
86
101
  hasControl = true;
87
102
  break;
88
103
  }
@@ -150,10 +165,14 @@ class DotProcessor extends baseProcessor_1.BaseProcessor {
150
165
  }
151
166
  saveFromTree(tree, _outputPath) {
152
167
  let dotContent = 'digraph AACBoard {\n';
168
+ // Helper to escape DOT string
169
+ const escapeDotString = (str) => {
170
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
171
+ };
153
172
  // Add nodes
154
173
  for (const pageId in tree.pages) {
155
174
  const page = tree.pages[pageId];
156
- dotContent += ` "${page.id}" [label="${page.name}"]\n`;
175
+ dotContent += ` "${page.id}" [label="${escapeDotString(page.name)}"]\n`;
157
176
  }
158
177
  // Add edges from navigation buttons (semantic intent or legacy targetPageId)
159
178
  for (const pageId in tree.pages) {
@@ -166,7 +185,7 @@ class DotProcessor extends baseProcessor_1.BaseProcessor {
166
185
  .forEach((btn) => {
167
186
  const target = btn.semanticAction?.targetId || btn.targetPageId;
168
187
  if (target) {
169
- dotContent += ` "${page.id}" -> "${target}" [label="${btn.label}"]\n`;
188
+ dotContent += ` "${page.id}" -> "${target}" [label="${escapeDotString(btn.label)}"]\n`;
170
189
  }
171
190
  });
172
191
  }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Grid3 Color Utilities
3
+ *
4
+ * Comprehensive color handling for Grid3 format, including:
5
+ * - CSS color name lookup (147 named colors)
6
+ * - Color format conversion (hex, RGB, RGBA, named colors)
7
+ * - Color manipulation (darkening, normalization)
8
+ * - Grid3-specific color formatting (8-digit ARGB hex)
9
+ */
10
+ /**
11
+ * Get RGB values for a CSS color name
12
+ * @param name - CSS color name (case-insensitive)
13
+ * @returns RGB tuple [r, g, b] or undefined if not found
14
+ */
15
+ export declare function getNamedColor(name: string): [number, number, number] | undefined;
16
+ /**
17
+ * Convert RGBA values to hex format
18
+ * @param r - Red channel (0-255)
19
+ * @param g - Green channel (0-255)
20
+ * @param b - Blue channel (0-255)
21
+ * @param a - Alpha channel (0-1)
22
+ * @returns Hex color string in format #RRGGBBAA
23
+ */
24
+ export declare function rgbaToHex(r: number, g: number, b: number, a: number): string;
25
+ /**
26
+ * Convert a single color channel value to hex
27
+ * @param value - Channel value (0-255)
28
+ * @returns Two-digit hex string
29
+ */
30
+ export declare function channelToHex(value: number): string;
31
+ /**
32
+ * Clamp RGB channel value to valid range
33
+ * @param value - Channel value
34
+ * @returns Clamped value (0-255)
35
+ */
36
+ export declare function clampColorChannel(value: number): number;
37
+ /**
38
+ * Clamp alpha value to valid range
39
+ * @param value - Alpha value
40
+ * @returns Clamped value (0-1)
41
+ */
42
+ export declare function clampAlpha(value: number): number;
43
+ /**
44
+ * Convert any color format to hex
45
+ * Supports: hex (#RGB, #RRGGBB, #RRGGBBAA), RGB/RGBA, and CSS color names
46
+ * @param value - Color string in any supported format
47
+ * @returns Hex color string (#RRGGBBAA) or undefined if invalid
48
+ */
49
+ export declare function toHexColor(value: string): string | undefined;
50
+ /**
51
+ * Darken a hex color by a specified amount
52
+ * @param hex - Hex color string
53
+ * @param amount - Amount to darken (0-255)
54
+ * @returns Darkened hex color
55
+ */
56
+ export declare function darkenColor(hex: string, amount: number): string;
57
+ /**
58
+ * Normalize any color format to Grid3's 8-digit hex format
59
+ * @param input - Color string in any supported format
60
+ * @param fallback - Fallback color if input is invalid (default: white)
61
+ * @returns Normalized color in format #AARRGGBBFF
62
+ */
63
+ export declare function normalizeColor(input: string, fallback?: string): string;
64
+ /**
65
+ * Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
66
+ * @param color - Color string (hex format)
67
+ * @returns Color with alpha channel in format #AARRGGBBFF
68
+ */
69
+ export declare function ensureAlphaChannel(color: string | undefined): string;
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ /**
3
+ * Grid3 Color Utilities
4
+ *
5
+ * Comprehensive color handling for Grid3 format, including:
6
+ * - CSS color name lookup (147 named colors)
7
+ * - Color format conversion (hex, RGB, RGBA, named colors)
8
+ * - Color manipulation (darkening, normalization)
9
+ * - Grid3-specific color formatting (8-digit ARGB hex)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getNamedColor = getNamedColor;
13
+ exports.rgbaToHex = rgbaToHex;
14
+ exports.channelToHex = channelToHex;
15
+ exports.clampColorChannel = clampColorChannel;
16
+ exports.clampAlpha = clampAlpha;
17
+ exports.toHexColor = toHexColor;
18
+ exports.darkenColor = darkenColor;
19
+ exports.normalizeColor = normalizeColor;
20
+ exports.ensureAlphaChannel = ensureAlphaChannel;
21
+ /**
22
+ * CSS color names to RGB values
23
+ * Supports 147 standard CSS color names
24
+ */
25
+ const CSS_COLORS = {
26
+ aliceblue: [240, 248, 255],
27
+ antiquewhite: [250, 235, 215],
28
+ aqua: [0, 255, 255],
29
+ aquamarine: [127, 255, 212],
30
+ azure: [240, 255, 255],
31
+ beige: [245, 245, 220],
32
+ bisque: [255, 228, 196],
33
+ black: [0, 0, 0],
34
+ blanchedalmond: [255, 235, 205],
35
+ blue: [0, 0, 255],
36
+ blueviolet: [138, 43, 226],
37
+ brown: [165, 42, 42],
38
+ burlywood: [222, 184, 135],
39
+ cadetblue: [95, 158, 160],
40
+ chartreuse: [127, 255, 0],
41
+ chocolate: [210, 105, 30],
42
+ coral: [255, 127, 80],
43
+ cornflowerblue: [100, 149, 237],
44
+ cornsilk: [255, 248, 220],
45
+ crimson: [220, 20, 60],
46
+ cyan: [0, 255, 255],
47
+ darkblue: [0, 0, 139],
48
+ darkcyan: [0, 139, 139],
49
+ darkgoldenrod: [184, 134, 11],
50
+ darkgray: [169, 169, 169],
51
+ darkgreen: [0, 100, 0],
52
+ darkgrey: [169, 169, 169],
53
+ darkkhaki: [189, 183, 107],
54
+ darkmagenta: [139, 0, 139],
55
+ darkolivegreen: [85, 107, 47],
56
+ darkorange: [255, 140, 0],
57
+ darkorchid: [153, 50, 204],
58
+ darkred: [139, 0, 0],
59
+ darksalmon: [233, 150, 122],
60
+ darkseagreen: [143, 188, 143],
61
+ darkslateblue: [72, 61, 139],
62
+ darkslategray: [47, 79, 79],
63
+ darkslategrey: [47, 79, 79],
64
+ darkturquoise: [0, 206, 209],
65
+ darkviolet: [148, 0, 211],
66
+ deeppink: [255, 20, 147],
67
+ deepskyblue: [0, 191, 255],
68
+ dimgray: [105, 105, 105],
69
+ dimgrey: [105, 105, 105],
70
+ dodgerblue: [30, 144, 255],
71
+ firebrick: [178, 34, 34],
72
+ floralwhite: [255, 250, 240],
73
+ forestgreen: [34, 139, 34],
74
+ fuchsia: [255, 0, 255],
75
+ gainsboro: [220, 220, 220],
76
+ ghostwhite: [248, 248, 255],
77
+ gold: [255, 215, 0],
78
+ goldenrod: [218, 165, 32],
79
+ gray: [128, 128, 128],
80
+ grey: [128, 128, 128],
81
+ green: [0, 128, 0],
82
+ greenyellow: [173, 255, 47],
83
+ honeydew: [240, 255, 240],
84
+ hotpink: [255, 105, 180],
85
+ indianred: [205, 92, 92],
86
+ indigo: [75, 0, 130],
87
+ ivory: [255, 255, 240],
88
+ khaki: [240, 230, 140],
89
+ lavender: [230, 230, 250],
90
+ lavenderblush: [255, 240, 245],
91
+ lawngreen: [124, 252, 0],
92
+ lemonchiffon: [255, 250, 205],
93
+ lightblue: [173, 216, 230],
94
+ lightcoral: [240, 128, 128],
95
+ lightcyan: [224, 255, 255],
96
+ lightgoldenrodyellow: [250, 250, 210],
97
+ lightgray: [211, 211, 211],
98
+ lightgreen: [144, 238, 144],
99
+ lightgrey: [211, 211, 211],
100
+ lightpink: [255, 182, 193],
101
+ lightsalmon: [255, 160, 122],
102
+ lightseagreen: [32, 178, 170],
103
+ lightskyblue: [135, 206, 250],
104
+ lightslategray: [119, 136, 153],
105
+ lightslategrey: [119, 136, 153],
106
+ lightsteelblue: [176, 196, 222],
107
+ lightyellow: [255, 255, 224],
108
+ lime: [0, 255, 0],
109
+ limegreen: [50, 205, 50],
110
+ linen: [250, 240, 230],
111
+ magenta: [255, 0, 255],
112
+ maroon: [128, 0, 0],
113
+ mediumaquamarine: [102, 205, 170],
114
+ mediumblue: [0, 0, 205],
115
+ mediumorchid: [186, 85, 211],
116
+ mediumpurple: [147, 112, 219],
117
+ mediumseagreen: [60, 179, 113],
118
+ mediumslateblue: [123, 104, 238],
119
+ mediumspringgreen: [0, 250, 154],
120
+ mediumturquoise: [72, 209, 204],
121
+ mediumvioletred: [199, 21, 133],
122
+ midnightblue: [25, 25, 112],
123
+ mintcream: [245, 255, 250],
124
+ mistyrose: [255, 228, 225],
125
+ moccasin: [255, 228, 181],
126
+ navajowhite: [255, 222, 173],
127
+ navy: [0, 0, 128],
128
+ oldlace: [253, 245, 230],
129
+ olive: [128, 128, 0],
130
+ olivedrab: [107, 142, 35],
131
+ orange: [255, 165, 0],
132
+ orangered: [255, 69, 0],
133
+ orchid: [218, 112, 214],
134
+ palegoldenrod: [238, 232, 170],
135
+ palegreen: [152, 251, 152],
136
+ paleturquoise: [175, 238, 238],
137
+ palevioletred: [219, 112, 147],
138
+ papayawhip: [255, 239, 213],
139
+ peachpuff: [255, 218, 185],
140
+ peru: [205, 133, 63],
141
+ pink: [255, 192, 203],
142
+ plum: [221, 160, 221],
143
+ powderblue: [176, 224, 230],
144
+ purple: [128, 0, 128],
145
+ rebeccapurple: [102, 51, 153],
146
+ red: [255, 0, 0],
147
+ rosybrown: [188, 143, 143],
148
+ royalblue: [65, 105, 225],
149
+ saddlebrown: [139, 69, 19],
150
+ salmon: [250, 128, 114],
151
+ sandybrown: [244, 164, 96],
152
+ seagreen: [46, 139, 87],
153
+ seashell: [255, 245, 238],
154
+ sienna: [160, 82, 45],
155
+ silver: [192, 192, 192],
156
+ skyblue: [135, 206, 235],
157
+ slateblue: [106, 90, 205],
158
+ slategray: [112, 128, 144],
159
+ slategrey: [112, 128, 144],
160
+ snow: [255, 250, 250],
161
+ springgreen: [0, 255, 127],
162
+ steelblue: [70, 130, 180],
163
+ tan: [210, 180, 140],
164
+ teal: [0, 128, 128],
165
+ thistle: [216, 191, 216],
166
+ tomato: [255, 99, 71],
167
+ turquoise: [64, 224, 208],
168
+ violet: [238, 130, 238],
169
+ wheat: [245, 222, 179],
170
+ white: [255, 255, 255],
171
+ whitesmoke: [245, 245, 245],
172
+ yellow: [255, 255, 0],
173
+ yellowgreen: [154, 205, 50],
174
+ };
175
+ /**
176
+ * Get RGB values for a CSS color name
177
+ * @param name - CSS color name (case-insensitive)
178
+ * @returns RGB tuple [r, g, b] or undefined if not found
179
+ */
180
+ function getNamedColor(name) {
181
+ const color = CSS_COLORS[name.toLowerCase()];
182
+ return color;
183
+ }
184
+ /**
185
+ * Convert RGBA values to hex format
186
+ * @param r - Red channel (0-255)
187
+ * @param g - Green channel (0-255)
188
+ * @param b - Blue channel (0-255)
189
+ * @param a - Alpha channel (0-1)
190
+ * @returns Hex color string in format #RRGGBBAA
191
+ */
192
+ function rgbaToHex(r, g, b, a) {
193
+ const red = channelToHex(r);
194
+ const green = channelToHex(g);
195
+ const blue = channelToHex(b);
196
+ const alpha = channelToHex(Math.round(a * 255));
197
+ return `#${red}${green}${blue}${alpha}`;
198
+ }
199
+ /**
200
+ * Convert a single color channel value to hex
201
+ * @param value - Channel value (0-255)
202
+ * @returns Two-digit hex string
203
+ */
204
+ function channelToHex(value) {
205
+ const clamped = Math.max(0, Math.min(255, Math.round(value)));
206
+ return clamped.toString(16).padStart(2, '0').toUpperCase();
207
+ }
208
+ /**
209
+ * Clamp RGB channel value to valid range
210
+ * @param value - Channel value
211
+ * @returns Clamped value (0-255)
212
+ */
213
+ function clampColorChannel(value) {
214
+ if (Number.isNaN(value)) {
215
+ return 0;
216
+ }
217
+ return Math.max(0, Math.min(255, value));
218
+ }
219
+ /**
220
+ * Clamp alpha value to valid range
221
+ * @param value - Alpha value
222
+ * @returns Clamped value (0-1)
223
+ */
224
+ function clampAlpha(value) {
225
+ if (Number.isNaN(value)) {
226
+ return 1;
227
+ }
228
+ return Math.max(0, Math.min(1, value));
229
+ }
230
+ /**
231
+ * Convert any color format to hex
232
+ * Supports: hex (#RGB, #RRGGBB, #RRGGBBAA), RGB/RGBA, and CSS color names
233
+ * @param value - Color string in any supported format
234
+ * @returns Hex color string (#RRGGBBAA) or undefined if invalid
235
+ */
236
+ function toHexColor(value) {
237
+ // Try hex format
238
+ const hexMatch = value.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);
239
+ if (hexMatch) {
240
+ const hex = hexMatch[1];
241
+ if (hex.length === 3 || hex.length === 4) {
242
+ return `#${hex
243
+ .split('')
244
+ .map((char) => char + char)
245
+ .join('')}`;
246
+ }
247
+ return `#${hex}`;
248
+ }
249
+ // Try RGB/RGBA format
250
+ const rgbMatch = value.match(/^rgba?\((.+)\)$/i);
251
+ if (rgbMatch) {
252
+ const parts = rgbMatch[1]
253
+ .split(',')
254
+ .map((part) => part.trim())
255
+ .filter(Boolean);
256
+ if (parts.length === 3 || parts.length === 4) {
257
+ const [r, g, b, a] = parts;
258
+ const red = clampColorChannel(parseFloat(r));
259
+ const green = clampColorChannel(parseFloat(g));
260
+ const blue = clampColorChannel(parseFloat(b));
261
+ const alpha = parts.length === 4 ? clampAlpha(parseFloat(a)) : 1;
262
+ return rgbaToHex(red, green, blue, alpha);
263
+ }
264
+ }
265
+ // Try CSS color name
266
+ const rgb = getNamedColor(value);
267
+ if (rgb) {
268
+ return rgbaToHex(rgb[0], rgb[1], rgb[2], 1);
269
+ }
270
+ return undefined;
271
+ }
272
+ /**
273
+ * Darken a hex color by a specified amount
274
+ * @param hex - Hex color string
275
+ * @param amount - Amount to darken (0-255)
276
+ * @returns Darkened hex color
277
+ */
278
+ function darkenColor(hex, amount) {
279
+ const normalized = ensureAlphaChannel(hex).substring(1); // strip #
280
+ const rgb = normalized.substring(0, 6);
281
+ const alpha = normalized.substring(6) || 'FF';
282
+ const r = parseInt(rgb.substring(0, 2), 16);
283
+ const g = parseInt(rgb.substring(2, 4), 16);
284
+ const b = parseInt(rgb.substring(4, 6), 16);
285
+ const clamp = (val) => Math.max(0, Math.min(255, val));
286
+ const newR = clamp(r - amount);
287
+ const newG = clamp(g - amount);
288
+ const newB = clamp(b - amount);
289
+ return `#${channelToHex(newR)}${channelToHex(newG)}${channelToHex(newB)}${alpha.toUpperCase()}`;
290
+ }
291
+ /**
292
+ * Normalize any color format to Grid3's 8-digit hex format
293
+ * @param input - Color string in any supported format
294
+ * @param fallback - Fallback color if input is invalid (default: white)
295
+ * @returns Normalized color in format #AARRGGBBFF
296
+ */
297
+ function normalizeColor(input, fallback = '#FFFFFFFF') {
298
+ const trimmed = input.trim();
299
+ if (!trimmed) {
300
+ return fallback;
301
+ }
302
+ const hex = toHexColor(trimmed);
303
+ if (hex) {
304
+ return ensureAlphaChannel(hex).toUpperCase();
305
+ }
306
+ return fallback;
307
+ }
308
+ /**
309
+ * Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
310
+ * @param color - Color string (hex format)
311
+ * @returns Color with alpha channel in format #AARRGGBBFF
312
+ */
313
+ function ensureAlphaChannel(color) {
314
+ if (!color)
315
+ return '#FFFFFFFF';
316
+ // If already 8 digits (with alpha), return as is
317
+ if (color.match(/^#[0-9A-Fa-f]{8}$/))
318
+ return color;
319
+ // If 6 digits (no alpha), add FF for fully opaque
320
+ if (color.match(/^#[0-9A-Fa-f]{6}$/))
321
+ return color + 'FF';
322
+ // If 3 digits (shorthand), expand to 8
323
+ if (color.match(/^#[0-9A-Fa-f]{3}$/)) {
324
+ const r = color[1];
325
+ const g = color[2];
326
+ const b = color[3];
327
+ return `#${r}${r}${g}${g}${b}${b}FF`;
328
+ }
329
+ // Invalid or unknown format, return white
330
+ return '#FFFFFFFF';
331
+ }
@@ -2,3 +2,33 @@ import { AACTree } from '../../core/treeStructure';
2
2
  export declare function getPageTokenImageMap(tree: AACTree, pageId: string): Map<string, string>;
3
3
  export declare function getAllowedImageEntries(tree: AACTree): Set<string>;
4
4
  export declare function openImage(gridsetBuffer: Buffer, entryPath: string): Buffer | null;
5
+ /**
6
+ * Generate a random GUID for Grid3 elements
7
+ * Grid3 uses GUIDs for grid identification
8
+ * @returns A UUID v4-like string in the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
9
+ */
10
+ export declare function generateGrid3Guid(): string;
11
+ /**
12
+ * Create Grid3 settings XML with start grid and common settings
13
+ * @param startGrid - Name of the grid to start on
14
+ * @param options - Optional settings (scan, hover, language, etc.)
15
+ * @returns XML string for Settings.xml
16
+ */
17
+ export declare function createSettingsXml(startGrid: string, options?: {
18
+ scanEnabled?: boolean;
19
+ scanTimeoutMs?: number;
20
+ hoverEnabled?: boolean;
21
+ hoverTimeoutMs?: number;
22
+ mouseclickEnabled?: boolean;
23
+ language?: string;
24
+ }): string;
25
+ /**
26
+ * Create Grid3 FileMap.xml content
27
+ * @param grids - Array of grid configurations with name and path
28
+ * @returns XML string for FileMap.xml
29
+ */
30
+ export declare function createFileMapXml(grids: Array<{
31
+ name: string;
32
+ path: string;
33
+ dynamicFiles?: string[];
34
+ }>): string;
@@ -6,7 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getPageTokenImageMap = getPageTokenImageMap;
7
7
  exports.getAllowedImageEntries = getAllowedImageEntries;
8
8
  exports.openImage = openImage;
9
+ exports.generateGrid3Guid = generateGrid3Guid;
10
+ exports.createSettingsXml = createSettingsXml;
11
+ exports.createFileMapXml = createFileMapXml;
9
12
  const adm_zip_1 = __importDefault(require("adm-zip"));
13
+ const fast_xml_parser_1 = require("fast-xml-parser");
10
14
  function normalizeZipPath(p) {
11
15
  const unified = p.replace(/\\/g, '/');
12
16
  try {
@@ -46,3 +50,68 @@ function openImage(gridsetBuffer, entryPath) {
46
50
  return null;
47
51
  return entry.getData();
48
52
  }
53
+ /**
54
+ * Generate a random GUID for Grid3 elements
55
+ * Grid3 uses GUIDs for grid identification
56
+ * @returns A UUID v4-like string in the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
57
+ */
58
+ function generateGrid3Guid() {
59
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
60
+ const r = (Math.random() * 16) | 0;
61
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
62
+ return v.toString(16);
63
+ });
64
+ }
65
+ /**
66
+ * Create Grid3 settings XML with start grid and common settings
67
+ * @param startGrid - Name of the grid to start on
68
+ * @param options - Optional settings (scan, hover, language, etc.)
69
+ * @returns XML string for Settings.xml
70
+ */
71
+ function createSettingsXml(startGrid, options) {
72
+ const builder = new fast_xml_parser_1.XMLBuilder({
73
+ ignoreAttributes: false,
74
+ format: true,
75
+ indentBy: ' ',
76
+ });
77
+ const settingsData = {
78
+ GridSetSettings: {
79
+ '@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
80
+ StartGrid: startGrid,
81
+ ScanEnabled: options?.scanEnabled?.toString() ?? 'false',
82
+ ScanTimeoutMs: options?.scanTimeoutMs?.toString() ?? '2000',
83
+ HoverEnabled: options?.hoverEnabled?.toString() ?? 'false',
84
+ HoverTimeoutMs: options?.hoverTimeoutMs?.toString() ?? '1000',
85
+ MouseclickEnabled: options?.mouseclickEnabled?.toString() ?? 'true',
86
+ Language: options?.language ?? 'en-US',
87
+ },
88
+ };
89
+ return builder.build(settingsData);
90
+ }
91
+ /**
92
+ * Create Grid3 FileMap.xml content
93
+ * @param grids - Array of grid configurations with name and path
94
+ * @returns XML string for FileMap.xml
95
+ */
96
+ function createFileMapXml(grids) {
97
+ const builder = new fast_xml_parser_1.XMLBuilder({
98
+ ignoreAttributes: false,
99
+ format: true,
100
+ indentBy: ' ',
101
+ });
102
+ const entries = grids.map((grid) => ({
103
+ '@_StaticFile': grid.path,
104
+ ...(grid.dynamicFiles && grid.dynamicFiles.length > 0
105
+ ? { DynamicFiles: { File: grid.dynamicFiles } }
106
+ : {}),
107
+ }));
108
+ const fileMapData = {
109
+ FileMap: {
110
+ '@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
111
+ Entries: {
112
+ Entry: entries,
113
+ },
114
+ },
115
+ };
116
+ return builder.build(fileMapData);
117
+ }
@@ -25,11 +25,10 @@ export declare const DEFAULT_GRID3_STYLES: Record<string, Grid3Style>;
25
25
  */
26
26
  export declare const CATEGORY_STYLES: Record<string, Grid3Style>;
27
27
  /**
28
- * Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
29
- * @param color - Color string (hex format)
30
- * @returns Color with alpha channel in format #AARRGGBBFF
28
+ * Re-export ensureAlphaChannel from colorUtils for backward compatibility
29
+ * @deprecated Use ensureAlphaChannel from colorUtils instead
31
30
  */
32
- export declare function ensureAlphaChannel(color: string | undefined): string;
31
+ export { ensureAlphaChannel } from './colorUtils';
33
32
  /**
34
33
  * Create a Grid3 style XML string with default and category styles
35
34
  * @param includeCategories - Whether to include category-specific styles (default: true)
@@ -6,11 +6,11 @@
6
6
  * style XML generation, and style conversion utilities.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = void 0;
10
- exports.ensureAlphaChannel = ensureAlphaChannel;
9
+ exports.ensureAlphaChannel = exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = void 0;
11
10
  exports.createDefaultStylesXml = createDefaultStylesXml;
12
11
  exports.createCategoryStyle = createCategoryStyle;
13
12
  const fast_xml_parser_1 = require("fast-xml-parser");
13
+ const colorUtils_1 = require("./colorUtils");
14
14
  /**
15
15
  * Default Grid3 styles for common use cases
16
16
  * Colors are in 8-digit ARGB hex format (#AARRGGBBFF)
@@ -119,29 +119,11 @@ exports.CATEGORY_STYLES = {
119
119
  },
120
120
  };
121
121
  /**
122
- * Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
123
- * @param color - Color string (hex format)
124
- * @returns Color with alpha channel in format #AARRGGBBFF
122
+ * Re-export ensureAlphaChannel from colorUtils for backward compatibility
123
+ * @deprecated Use ensureAlphaChannel from colorUtils instead
125
124
  */
126
- function ensureAlphaChannel(color) {
127
- if (!color)
128
- return '#FFFFFFFF';
129
- // If already 8 digits (with alpha), return as is
130
- if (color.match(/^#[0-9A-Fa-f]{8}$/))
131
- return color;
132
- // If 6 digits (no alpha), add FF for fully opaque
133
- if (color.match(/^#[0-9A-Fa-f]{6}$/))
134
- return color + 'FF';
135
- // If 3 digits (shorthand), expand to 8
136
- if (color.match(/^#[0-9A-Fa-f]{3}$/)) {
137
- const r = color[1];
138
- const g = color[2];
139
- const b = color[3];
140
- return `#${r}${r}${g}${g}${b}${b}FF`;
141
- }
142
- // Invalid or unknown format, return white
143
- return '#FFFFFFFF';
144
- }
125
+ var colorUtils_2 = require("./colorUtils");
126
+ Object.defineProperty(exports, "ensureAlphaChannel", { enumerable: true, get: function () { return colorUtils_2.ensureAlphaChannel; } });
145
127
  /**
146
128
  * Create a Grid3 style XML string with default and category styles
147
129
  * @param includeCategories - Whether to include category-specific styles (default: true)
@@ -185,27 +167,11 @@ function createDefaultStylesXml(includeCategories = true) {
185
167
  */
186
168
  function createCategoryStyle(categoryName, backgroundColor, fontColor = '#FFFFFFFF') {
187
169
  return {
188
- BackColour: ensureAlphaChannel(backgroundColor),
189
- TileColour: ensureAlphaChannel(backgroundColor),
190
- BorderColour: ensureAlphaChannel(darkenColor(backgroundColor, 30)),
191
- FontColour: ensureAlphaChannel(fontColor),
170
+ BackColour: (0, colorUtils_1.ensureAlphaChannel)(backgroundColor),
171
+ TileColour: (0, colorUtils_1.ensureAlphaChannel)(backgroundColor),
172
+ BorderColour: (0, colorUtils_1.ensureAlphaChannel)((0, colorUtils_1.darkenColor)(backgroundColor, 30)),
173
+ FontColour: (0, colorUtils_1.ensureAlphaChannel)(fontColor),
192
174
  FontName: 'Arial',
193
175
  FontSize: '16',
194
176
  };
195
177
  }
196
- /**
197
- * Darken a hex color by a given amount
198
- * @param hexColor - Hex color string
199
- * @param amount - Amount to darken (0-255)
200
- * @returns Darkened hex color
201
- */
202
- function darkenColor(hexColor, amount) {
203
- const normalized = ensureAlphaChannel(hexColor);
204
- const hex = normalized.slice(1, 7); // Extract RGB part (skip # and alpha)
205
- const num = parseInt(hex, 16);
206
- const clamp = (value) => Math.max(0, Math.min(255, value));
207
- const r = clamp(((num >> 16) & 0xff) - amount);
208
- const g = clamp(((num >> 8) & 0xff) - amount);
209
- const b = clamp((num & 0xff) - amount);
210
- return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
211
- }
@@ -7,10 +7,12 @@ export { OpmlProcessor } from './opmlProcessor';
7
7
  export { SnapProcessor } from './snapProcessor';
8
8
  export { TouchChatProcessor } from './touchchatProcessor';
9
9
  export { AstericsGridProcessor } from './astericsGridProcessor';
10
- export { getPageTokenImageMap, getAllowedImageEntries, openImage } from './gridset/helpers';
11
- export { getPageTokenImageMap as getGridsetPageTokenImageMap, getAllowedImageEntries as getGridsetAllowedImageEntries, openImage as openGridsetImage, } from './gridset/helpers';
10
+ export { getPageTokenImageMap, getAllowedImageEntries, openImage, generateGrid3Guid, createSettingsXml, createFileMapXml, } from './gridset/helpers';
11
+ export { getPageTokenImageMap as getGridsetPageTokenImageMap, getAllowedImageEntries as getGridsetAllowedImageEntries, openImage as openGridsetImage, generateGrid3Guid as generateGridsetGuid, createSettingsXml as createGridsetSettingsXml, createFileMapXml as createGridsetFileMapXml, } from './gridset/helpers';
12
12
  export { resolveGrid3CellImage } from './gridset/resolver';
13
13
  export { createWordlist, extractWordlists, updateWordlist, wordlistToXml, type WordList, type WordListItem, } from './gridset/wordlistHelpers';
14
- export { DEFAULT_GRID3_STYLES, CATEGORY_STYLES, ensureAlphaChannel, createDefaultStylesXml, createCategoryStyle, type Grid3Style, } from './gridset/styleHelpers';
14
+ export { getNamedColor, rgbaToHex, channelToHex, clampColorChannel, clampAlpha, toHexColor, darkenColor, normalizeColor, ensureAlphaChannel, } from './gridset/colorUtils';
15
+ export { DEFAULT_GRID3_STYLES, CATEGORY_STYLES, createDefaultStylesXml, createCategoryStyle, } from './gridset/styleHelpers';
16
+ export { ensureAlphaChannel as ensureAlphaChannelFromStyles } from './gridset/styleHelpers';
15
17
  export { getPageTokenImageMap as getSnapPageTokenImageMap, getAllowedImageEntries as getSnapAllowedImageEntries, openImage as openSnapImage, } from './snap/helpers';
16
18
  export { getPageTokenImageMap as getTouchChatPageTokenImageMap, getAllowedImageEntries as getTouchChatAllowedImageEntries, openImage as openTouchChatImage, } from './touchchat/helpers';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.openTouchChatImage = exports.getTouchChatAllowedImageEntries = exports.getTouchChatPageTokenImageMap = exports.openSnapImage = exports.getSnapAllowedImageEntries = exports.getSnapPageTokenImageMap = exports.createCategoryStyle = exports.createDefaultStylesXml = exports.ensureAlphaChannel = exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = exports.wordlistToXml = exports.updateWordlist = exports.extractWordlists = exports.createWordlist = exports.resolveGrid3CellImage = exports.openGridsetImage = exports.getGridsetAllowedImageEntries = exports.getGridsetPageTokenImageMap = exports.openImage = exports.getAllowedImageEntries = exports.getPageTokenImageMap = exports.AstericsGridProcessor = exports.TouchChatProcessor = exports.SnapProcessor = exports.OpmlProcessor = exports.ObfProcessor = exports.GridsetProcessor = exports.ExcelProcessor = exports.DotProcessor = exports.ApplePanelsProcessor = void 0;
3
+ exports.openTouchChatImage = exports.getTouchChatAllowedImageEntries = exports.getTouchChatPageTokenImageMap = exports.openSnapImage = exports.getSnapAllowedImageEntries = exports.getSnapPageTokenImageMap = exports.ensureAlphaChannelFromStyles = exports.createCategoryStyle = exports.createDefaultStylesXml = exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = exports.ensureAlphaChannel = exports.normalizeColor = exports.darkenColor = exports.toHexColor = exports.clampAlpha = exports.clampColorChannel = exports.channelToHex = exports.rgbaToHex = exports.getNamedColor = exports.wordlistToXml = exports.updateWordlist = exports.extractWordlists = exports.createWordlist = exports.resolveGrid3CellImage = exports.createGridsetFileMapXml = exports.createGridsetSettingsXml = exports.generateGridsetGuid = exports.openGridsetImage = exports.getGridsetAllowedImageEntries = exports.getGridsetPageTokenImageMap = exports.createFileMapXml = exports.createSettingsXml = exports.generateGrid3Guid = exports.openImage = exports.getAllowedImageEntries = exports.getPageTokenImageMap = exports.AstericsGridProcessor = exports.TouchChatProcessor = exports.SnapProcessor = exports.OpmlProcessor = exports.ObfProcessor = exports.GridsetProcessor = exports.ExcelProcessor = exports.DotProcessor = exports.ApplePanelsProcessor = void 0;
4
4
  var applePanelsProcessor_1 = require("./applePanelsProcessor");
5
5
  Object.defineProperty(exports, "ApplePanelsProcessor", { enumerable: true, get: function () { return applePanelsProcessor_1.ApplePanelsProcessor; } });
6
6
  var dotProcessor_1 = require("./dotProcessor");
@@ -24,10 +24,16 @@ var helpers_1 = require("./gridset/helpers");
24
24
  Object.defineProperty(exports, "getPageTokenImageMap", { enumerable: true, get: function () { return helpers_1.getPageTokenImageMap; } });
25
25
  Object.defineProperty(exports, "getAllowedImageEntries", { enumerable: true, get: function () { return helpers_1.getAllowedImageEntries; } });
26
26
  Object.defineProperty(exports, "openImage", { enumerable: true, get: function () { return helpers_1.openImage; } });
27
+ Object.defineProperty(exports, "generateGrid3Guid", { enumerable: true, get: function () { return helpers_1.generateGrid3Guid; } });
28
+ Object.defineProperty(exports, "createSettingsXml", { enumerable: true, get: function () { return helpers_1.createSettingsXml; } });
29
+ Object.defineProperty(exports, "createFileMapXml", { enumerable: true, get: function () { return helpers_1.createFileMapXml; } });
27
30
  var helpers_2 = require("./gridset/helpers");
28
31
  Object.defineProperty(exports, "getGridsetPageTokenImageMap", { enumerable: true, get: function () { return helpers_2.getPageTokenImageMap; } });
29
32
  Object.defineProperty(exports, "getGridsetAllowedImageEntries", { enumerable: true, get: function () { return helpers_2.getAllowedImageEntries; } });
30
33
  Object.defineProperty(exports, "openGridsetImage", { enumerable: true, get: function () { return helpers_2.openImage; } });
34
+ Object.defineProperty(exports, "generateGridsetGuid", { enumerable: true, get: function () { return helpers_2.generateGrid3Guid; } });
35
+ Object.defineProperty(exports, "createGridsetSettingsXml", { enumerable: true, get: function () { return helpers_2.createSettingsXml; } });
36
+ Object.defineProperty(exports, "createGridsetFileMapXml", { enumerable: true, get: function () { return helpers_2.createFileMapXml; } });
31
37
  var resolver_1 = require("./gridset/resolver");
32
38
  Object.defineProperty(exports, "resolveGrid3CellImage", { enumerable: true, get: function () { return resolver_1.resolveGrid3CellImage; } });
33
39
  // Gridset (Grid 3) wordlist helpers
@@ -36,13 +42,26 @@ Object.defineProperty(exports, "createWordlist", { enumerable: true, get: functi
36
42
  Object.defineProperty(exports, "extractWordlists", { enumerable: true, get: function () { return wordlistHelpers_1.extractWordlists; } });
37
43
  Object.defineProperty(exports, "updateWordlist", { enumerable: true, get: function () { return wordlistHelpers_1.updateWordlist; } });
38
44
  Object.defineProperty(exports, "wordlistToXml", { enumerable: true, get: function () { return wordlistHelpers_1.wordlistToXml; } });
45
+ // Gridset (Grid 3) color utilities
46
+ var colorUtils_1 = require("./gridset/colorUtils");
47
+ Object.defineProperty(exports, "getNamedColor", { enumerable: true, get: function () { return colorUtils_1.getNamedColor; } });
48
+ Object.defineProperty(exports, "rgbaToHex", { enumerable: true, get: function () { return colorUtils_1.rgbaToHex; } });
49
+ Object.defineProperty(exports, "channelToHex", { enumerable: true, get: function () { return colorUtils_1.channelToHex; } });
50
+ Object.defineProperty(exports, "clampColorChannel", { enumerable: true, get: function () { return colorUtils_1.clampColorChannel; } });
51
+ Object.defineProperty(exports, "clampAlpha", { enumerable: true, get: function () { return colorUtils_1.clampAlpha; } });
52
+ Object.defineProperty(exports, "toHexColor", { enumerable: true, get: function () { return colorUtils_1.toHexColor; } });
53
+ Object.defineProperty(exports, "darkenColor", { enumerable: true, get: function () { return colorUtils_1.darkenColor; } });
54
+ Object.defineProperty(exports, "normalizeColor", { enumerable: true, get: function () { return colorUtils_1.normalizeColor; } });
55
+ Object.defineProperty(exports, "ensureAlphaChannel", { enumerable: true, get: function () { return colorUtils_1.ensureAlphaChannel; } });
39
56
  // Gridset (Grid 3) style helpers
40
57
  var styleHelpers_1 = require("./gridset/styleHelpers");
41
58
  Object.defineProperty(exports, "DEFAULT_GRID3_STYLES", { enumerable: true, get: function () { return styleHelpers_1.DEFAULT_GRID3_STYLES; } });
42
59
  Object.defineProperty(exports, "CATEGORY_STYLES", { enumerable: true, get: function () { return styleHelpers_1.CATEGORY_STYLES; } });
43
- Object.defineProperty(exports, "ensureAlphaChannel", { enumerable: true, get: function () { return styleHelpers_1.ensureAlphaChannel; } });
44
60
  Object.defineProperty(exports, "createDefaultStylesXml", { enumerable: true, get: function () { return styleHelpers_1.createDefaultStylesXml; } });
45
61
  Object.defineProperty(exports, "createCategoryStyle", { enumerable: true, get: function () { return styleHelpers_1.createCategoryStyle; } });
62
+ // Re-export ensureAlphaChannel from styleHelpers for backward compatibility
63
+ var styleHelpers_2 = require("./gridset/styleHelpers");
64
+ Object.defineProperty(exports, "ensureAlphaChannelFromStyles", { enumerable: true, get: function () { return styleHelpers_2.ensureAlphaChannel; } });
46
65
  // Snap helpers (stubs)
47
66
  var helpers_3 = require("./snap/helpers");
48
67
  Object.defineProperty(exports, "getSnapPageTokenImageMap", { enumerable: true, get: function () { return helpers_3.getPageTokenImageMap; } });
@@ -144,12 +144,15 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
144
144
  }
145
145
  const obj = JSON.parse(str);
146
146
  if (obj && typeof obj === 'object' && 'id' in obj && 'buttons' in obj) {
147
+ // Validate buttons is an array
148
+ if (!Array.isArray(obj.buttons)) {
149
+ throw new Error('Invalid OBF: buttons must be an array');
150
+ }
147
151
  return obj;
148
152
  }
149
153
  }
150
154
  catch (error) {
151
155
  // Log parsing errors for debugging but don't throw
152
- console.warn(`Failed to parse OBF JSON: ${error.message}`);
153
156
  }
154
157
  return null;
155
158
  }
@@ -476,11 +476,33 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
476
476
  serializedMetadata = audio.metadata || null;
477
477
  useMessageRecording = 1;
478
478
  }
479
- insertButton.run(buttonIdCounter++, button.label || '', button.message || button.label || '', navigatePageId, elementRefId, null, null, messageRecordingId, serializedMetadata, useMessageRecording, button.style?.fontColor ? parseInt(button.style.fontColor.replace('#', ''), 16) : null, button.style?.backgroundColor
480
- ? parseInt(button.style.backgroundColor.replace('#', ''), 16)
481
- : null, button.style?.borderColor
482
- ? parseInt(button.style.borderColor.replace('#', ''), 16)
483
- : null, button.style?.borderWidth, button.style?.fontSize, button.style?.fontFamily, button.style?.fontStyle ? parseInt(button.style.fontStyle) : null);
479
+ // Retry logic for SQLite operations
480
+ let retries = 3;
481
+ while (retries > 0) {
482
+ try {
483
+ insertButton.run(buttonIdCounter++, button.label || '', button.message || button.label || '', navigatePageId, elementRefId, null, null, messageRecordingId, serializedMetadata, useMessageRecording, button.style?.fontColor
484
+ ? parseInt(button.style.fontColor.replace('#', ''), 16)
485
+ : null, button.style?.backgroundColor
486
+ ? parseInt(button.style.backgroundColor.replace('#', ''), 16)
487
+ : null, button.style?.borderColor
488
+ ? parseInt(button.style.borderColor.replace('#', ''), 16)
489
+ : null, button.style?.borderWidth, button.style?.fontSize, button.style?.fontFamily, button.style?.fontStyle ? parseInt(button.style.fontStyle) : null);
490
+ break; // Success
491
+ }
492
+ catch (err) {
493
+ if (err.code === 'SQLITE_IOERR' && retries > 1) {
494
+ retries--;
495
+ // Wait a bit before retrying
496
+ const now = Date.now();
497
+ while (Date.now() - now < 100) {
498
+ /* busy wait */
499
+ }
500
+ }
501
+ else {
502
+ throw err;
503
+ }
504
+ }
505
+ }
484
506
  // Insert ElementPlacement
485
507
  const insertPlacement = db.prepare('INSERT INTO ElementPlacement (Id, ElementReferenceId, GridPosition) VALUES (?, ?, ?)');
486
508
  insertPlacement.run(placementIdCounter++, elementRefId, gridPosition);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willwade/aac-processors",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",