@willwade/aac-processors 0.0.29 → 0.1.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.
Files changed (92) hide show
  1. package/README.md +52 -852
  2. package/dist/browser/core/baseProcessor.js +241 -0
  3. package/dist/browser/core/stringCasing.js +179 -0
  4. package/dist/browser/core/treeStructure.js +255 -0
  5. package/dist/browser/index.browser.js +73 -0
  6. package/dist/browser/processors/applePanelsProcessor.js +582 -0
  7. package/dist/browser/processors/astericsGridProcessor.js +1509 -0
  8. package/dist/browser/processors/dotProcessor.js +221 -0
  9. package/dist/browser/processors/gridset/commands.js +962 -0
  10. package/dist/browser/processors/gridset/crypto.js +53 -0
  11. package/dist/browser/processors/gridset/password.js +43 -0
  12. package/dist/browser/processors/gridset/pluginTypes.js +277 -0
  13. package/dist/browser/processors/gridset/resolver.js +137 -0
  14. package/dist/browser/processors/gridset/symbolAlignment.js +276 -0
  15. package/dist/browser/processors/gridset/symbols.js +421 -0
  16. package/dist/browser/processors/gridsetProcessor.js +2002 -0
  17. package/dist/browser/processors/obfProcessor.js +705 -0
  18. package/dist/browser/processors/opmlProcessor.js +274 -0
  19. package/dist/browser/types/aac.js +38 -0
  20. package/dist/browser/utilities/analytics/utils/idGenerator.js +89 -0
  21. package/dist/browser/utilities/translation/translationProcessor.js +200 -0
  22. package/dist/browser/utils/io.js +95 -0
  23. package/dist/browser/validation/baseValidator.js +156 -0
  24. package/dist/browser/validation/gridsetValidator.js +355 -0
  25. package/dist/browser/validation/obfValidator.js +500 -0
  26. package/dist/browser/validation/validationTypes.js +46 -0
  27. package/dist/cli/index.js +5 -5
  28. package/dist/core/analyze.d.ts +2 -2
  29. package/dist/core/analyze.js +2 -2
  30. package/dist/core/baseProcessor.d.ts +5 -4
  31. package/dist/core/baseProcessor.js +22 -27
  32. package/dist/core/treeStructure.d.ts +5 -5
  33. package/dist/core/treeStructure.js +1 -4
  34. package/dist/index.browser.d.ts +37 -0
  35. package/dist/index.browser.js +99 -0
  36. package/dist/index.d.ts +1 -48
  37. package/dist/index.js +1 -136
  38. package/dist/index.node.d.ts +48 -0
  39. package/dist/index.node.js +152 -0
  40. package/dist/processors/applePanelsProcessor.d.ts +5 -4
  41. package/dist/processors/applePanelsProcessor.js +58 -62
  42. package/dist/processors/astericsGridProcessor.d.ts +7 -6
  43. package/dist/processors/astericsGridProcessor.js +31 -42
  44. package/dist/processors/dotProcessor.d.ts +5 -4
  45. package/dist/processors/dotProcessor.js +25 -33
  46. package/dist/processors/excelProcessor.d.ts +4 -3
  47. package/dist/processors/excelProcessor.js +6 -3
  48. package/dist/processors/gridset/crypto.d.ts +18 -0
  49. package/dist/processors/gridset/crypto.js +57 -0
  50. package/dist/processors/gridset/helpers.d.ts +1 -1
  51. package/dist/processors/gridset/helpers.js +18 -8
  52. package/dist/processors/gridset/password.d.ts +20 -3
  53. package/dist/processors/gridset/password.js +17 -3
  54. package/dist/processors/gridset/wordlistHelpers.d.ts +3 -3
  55. package/dist/processors/gridset/wordlistHelpers.js +21 -20
  56. package/dist/processors/gridsetProcessor.d.ts +7 -12
  57. package/dist/processors/gridsetProcessor.js +118 -77
  58. package/dist/processors/obfProcessor.d.ts +9 -7
  59. package/dist/processors/obfProcessor.js +131 -56
  60. package/dist/processors/obfsetProcessor.d.ts +5 -4
  61. package/dist/processors/obfsetProcessor.js +10 -16
  62. package/dist/processors/opmlProcessor.d.ts +5 -4
  63. package/dist/processors/opmlProcessor.js +27 -34
  64. package/dist/processors/snapProcessor.d.ts +8 -7
  65. package/dist/processors/snapProcessor.js +15 -12
  66. package/dist/processors/touchchatProcessor.d.ts +8 -7
  67. package/dist/processors/touchchatProcessor.js +22 -17
  68. package/dist/types/aac.d.ts +0 -2
  69. package/dist/types/aac.js +2 -0
  70. package/dist/utils/io.d.ts +12 -0
  71. package/dist/utils/io.js +107 -0
  72. package/dist/validation/gridsetValidator.js +7 -7
  73. package/dist/validation/snapValidator.js +28 -35
  74. package/docs/BROWSER_USAGE.md +618 -0
  75. package/examples/README.md +77 -0
  76. package/examples/browser-test-server.js +81 -0
  77. package/examples/browser-test.html +331 -0
  78. package/examples/vitedemo/QUICKSTART.md +74 -0
  79. package/examples/vitedemo/README.md +157 -0
  80. package/examples/vitedemo/index.html +376 -0
  81. package/examples/vitedemo/package-lock.json +1221 -0
  82. package/examples/vitedemo/package.json +18 -0
  83. package/examples/vitedemo/src/main.ts +519 -0
  84. package/examples/vitedemo/test-files/example.dot +14 -0
  85. package/examples/vitedemo/test-files/example.grd +1 -0
  86. package/examples/vitedemo/test-files/example.gridset +0 -0
  87. package/examples/vitedemo/test-files/example.obz +0 -0
  88. package/examples/vitedemo/test-files/example.opml +18 -0
  89. package/examples/vitedemo/test-files/simple.obf +53 -0
  90. package/examples/vitedemo/tsconfig.json +24 -0
  91. package/examples/vitedemo/vite.config.ts +34 -0
  92. package/package.json +20 -4
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Crypto utilities for Gridsetx (encrypted Grid3 files)
3
+ * This module is only needed for .gridsetx files and uses Node-only crypto/zlib
4
+ */
5
+ /**
6
+ * Decrypt and inflate a Grid3 encrypted payload (DesktopContentEncrypter).
7
+ * Uses AES-256-CBC with key/IV derived from the password padded with spaces
8
+ * and then Deflate decompression.
9
+ *
10
+ * @param buffer - Encrypted buffer
11
+ * @param password - Password (defaults to 'Chocolate')
12
+ * @returns Decrypted and inflated buffer
13
+ */
14
+ export function decryptGridsetEntry(buffer, password) {
15
+ // Dynamic require to avoid breaking in browser environments
16
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
17
+ const crypto = require('crypto');
18
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
19
+ const zlib = require('zlib');
20
+ const pwd = (password || 'Chocolate').padEnd(32, ' ');
21
+ const key = Buffer.from(pwd.slice(0, 32), 'utf8');
22
+ const iv = Buffer.from(pwd.slice(0, 16), 'utf8');
23
+ try {
24
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
25
+ const decrypted = Buffer.concat([decipher.update(buffer), decipher.final()]);
26
+ try {
27
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
28
+ return zlib.inflateSync(decrypted);
29
+ }
30
+ catch {
31
+ // If data isn't deflated, return raw decrypted bytes
32
+ return decrypted;
33
+ }
34
+ }
35
+ catch {
36
+ return buffer;
37
+ }
38
+ }
39
+ /**
40
+ * Check if crypto operations are available in the current environment
41
+ */
42
+ export function isCryptoAvailable() {
43
+ try {
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ require('crypto');
46
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
47
+ require('zlib');
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
@@ -0,0 +1,43 @@
1
+ import path from 'path';
2
+ /**
3
+ * Resolve the password to use for Grid3 archives.
4
+ * Preference order:
5
+ * 1. Explicit processor option
6
+ * 2. GRIDSET_PASSWORD env var
7
+ */
8
+ export function resolveGridsetPassword(options, source) {
9
+ if (options?.gridsetPassword)
10
+ return options.gridsetPassword;
11
+ if (process.env.GRIDSET_PASSWORD)
12
+ return process.env.GRIDSET_PASSWORD;
13
+ if (typeof source === 'string') {
14
+ const ext = path.extname(source).toLowerCase();
15
+ if (ext === '.gridsetx')
16
+ return process.env.GRIDSET_PASSWORD;
17
+ }
18
+ return undefined;
19
+ }
20
+ export function resolveGridsetPasswordFromEnv() {
21
+ return process.env.GRIDSET_PASSWORD;
22
+ }
23
+ export function getZipEntriesWithPassword(zip, password) {
24
+ const entries = [];
25
+ // Note: JSZip doesn't support zip-level password protection like AdmZip
26
+ // Password protection for .gridsetx files is handled at the encrypted archive level
27
+ // in crypto.ts before the zip is loaded
28
+ if (password) {
29
+ console.warn('JSZip does not support zip-level password protection. For .gridsetx encrypted files, password is handled at the archive level.');
30
+ }
31
+ zip.forEach((relativePath, file) => {
32
+ entries.push({
33
+ name: relativePath,
34
+ entryName: relativePath,
35
+ dir: file.dir || false,
36
+ getData: async () => {
37
+ // Use 'uint8array' which is supported everywhere
38
+ return await file.async('uint8array');
39
+ },
40
+ });
41
+ });
42
+ return entries;
43
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Grid 3 Plugin Cell Type Detection
3
+ *
4
+ * Grid 3 uses three special cell types for different plugin functionalities:
5
+ * - Workspace: Full editing workspaces (Email, Chat, WordProcessor, etc.)
6
+ * - LiveCell: Dynamic content displays (Clock, Volume indicators, etc.)
7
+ * - AutoContent: Dynamic word/content suggestions
8
+ *
9
+ * This module provides detection and metadata extraction for these cell types.
10
+ */
11
+ /**
12
+ * Cell types in Grid 3
13
+ */
14
+ export var Grid3CellType;
15
+ (function (Grid3CellType) {
16
+ Grid3CellType["Regular"] = "regular";
17
+ Grid3CellType["Workspace"] = "workspace";
18
+ Grid3CellType["LiveCell"] = "livecell";
19
+ Grid3CellType["AutoContent"] = "autocontent";
20
+ })(Grid3CellType || (Grid3CellType = {}));
21
+ /**
22
+ * Known workspace types in Grid 3
23
+ */
24
+ export const WORKSPACE_TYPES = {
25
+ CHAT: 'Chat',
26
+ EMAIL: 'Email',
27
+ WORD_PROCESSOR: 'WordProcessor',
28
+ PHONE: 'Phone',
29
+ SMS: 'Sms',
30
+ WEB_BROWSER: 'WebBrowser',
31
+ COMPUTER_CONTROL: 'ComputerControl',
32
+ CALCULATOR: 'Calculator',
33
+ TIMER: 'Timer',
34
+ MUSIC_VIDEO: 'MusicVideo',
35
+ PHOTOS: 'Photos',
36
+ CONTACTS: 'Contacts',
37
+ INTERACTIVE_LEARNING: 'InteractiveLearning',
38
+ MESSAGE_BANKING: 'MessageBanking',
39
+ ENVIRONMENT_CONTROL: 'EnvironmentControl',
40
+ SETTINGS: 'Settings',
41
+ };
42
+ /**
43
+ * Known live cell types in Grid 3
44
+ */
45
+ export const LIVECELL_TYPES = {
46
+ DIGITAL_CLOCK: 'DigitalClock',
47
+ ANALOG_CLOCK: 'AnalogClock',
48
+ DATE_DISPLAY: 'DateDisplay',
49
+ PUBLIC_VOLUME: 'PublicVolume',
50
+ PUBLIC_SPEED: 'PublicSpeed',
51
+ PUBLIC_VOICE: 'PublicVoice',
52
+ MESSAGES: 'Messages',
53
+ BATTERY: 'Battery',
54
+ WIFI_STRENGTH: 'WifiStrength',
55
+ BLUETOOTH_STATUS: 'BluetoothStatus',
56
+ };
57
+ /**
58
+ * Known auto content types in Grid 3
59
+ */
60
+ export const AUTOCONTENT_TYPES = {
61
+ CHANGE_PUBLIC_VOICE: 'ChangePublicVoice',
62
+ CHANGE_PUBLIC_SPEED: 'ChangePublicSpeed',
63
+ EMAIL_CONTACTS: 'EmailContacts',
64
+ EMAIL_RECIPIENTS: 'EmailRecipients',
65
+ PHONE_CONTACTS: 'PhoneContacts',
66
+ SMS_CONTACTS: 'SmsContacts',
67
+ WEB_FAVORITES: 'WebFavorites',
68
+ WEB_HISTORY: 'WebHistory',
69
+ PREDICTION: 'Prediction',
70
+ GRAMMAR: 'Grammar',
71
+ CONTEXTUAL: 'Contextual',
72
+ };
73
+ /**
74
+ * Human-readable names for cell types
75
+ */
76
+ export function getCellTypeDisplayName(cellType) {
77
+ switch (cellType) {
78
+ case Grid3CellType.Workspace:
79
+ return 'Workspace';
80
+ case Grid3CellType.LiveCell:
81
+ return 'Live Cell';
82
+ case Grid3CellType.AutoContent:
83
+ return 'Auto Content';
84
+ case Grid3CellType.Regular:
85
+ return 'Regular';
86
+ default:
87
+ return 'Unknown';
88
+ }
89
+ }
90
+ /**
91
+ * Detect plugin cell type from Grid 3 cell content
92
+ *
93
+ * @param content - Grid 3 cell content object
94
+ * @returns Plugin metadata with detected type
95
+ */
96
+ export function detectPluginCellType(content) {
97
+ if (!content) {
98
+ return { cellType: Grid3CellType.Regular };
99
+ }
100
+ const contentType = content.ContentType || content.contenttype;
101
+ const contentSubType = content.ContentSubType || content.contentsubtype;
102
+ // Workspace cells - full editing workspaces
103
+ if (contentType === 'Workspace' || content.Style?.BasedOnStyle === 'Workspace') {
104
+ return {
105
+ cellType: Grid3CellType.Workspace,
106
+ subType: contentSubType || undefined,
107
+ pluginId: inferWorkspacePlugin(String(contentSubType || '')),
108
+ displayName: contentSubType ? `${contentSubType} Workspace` : 'Workspace',
109
+ };
110
+ }
111
+ // LiveCell detection - dynamic content displays
112
+ if (contentType === 'LiveCell' || content.Style?.BasedOnStyle === 'LiveCell') {
113
+ return {
114
+ cellType: Grid3CellType.LiveCell,
115
+ liveCellType: contentSubType || undefined,
116
+ pluginId: inferLiveCellPlugin(String(contentSubType || '')),
117
+ displayName: contentSubType || 'Live Cell',
118
+ };
119
+ }
120
+ // AutoContent detection - dynamic word/content suggestions
121
+ if (contentType === 'AutoContent' || content.Style?.BasedOnStyle === 'AutoContent') {
122
+ const autoContentType = extractAutoContentType(content) || contentSubType;
123
+ return {
124
+ cellType: Grid3CellType.AutoContent,
125
+ autoContentType: autoContentType ? String(autoContentType) : undefined,
126
+ pluginId: inferAutoContentPlugin(autoContentType ? String(autoContentType) : undefined),
127
+ displayName: autoContentType ? String(autoContentType) : 'Auto Content',
128
+ };
129
+ }
130
+ // Regular cell
131
+ return {
132
+ cellType: Grid3CellType.Regular,
133
+ };
134
+ }
135
+ /**
136
+ * Extract auto content type from AutoContent.Activate command
137
+ */
138
+ function extractAutoContentType(content) {
139
+ const commands = content.Commands?.Command || content.commands?.command;
140
+ if (!commands)
141
+ return undefined;
142
+ const commandArr = Array.isArray(commands) ? commands : [commands];
143
+ for (const command of commandArr) {
144
+ const commandId = command['@_ID'] || command.ID || command.id;
145
+ if (commandId === 'AutoContent.Activate') {
146
+ const parameters = command.Parameter || command.parameter;
147
+ const paramArr = Array.isArray(parameters) ? parameters : parameters ? [parameters] : [];
148
+ for (const param of paramArr) {
149
+ const key = param['@_Key'] || param.Key || param.key;
150
+ if (key === 'autocontenttype') {
151
+ return String(param['#text'] || param.text || param.value || '');
152
+ }
153
+ }
154
+ }
155
+ }
156
+ return undefined;
157
+ }
158
+ /**
159
+ * Infer plugin ID from workspace subtype
160
+ */
161
+ function inferWorkspacePlugin(subType) {
162
+ if (!subType)
163
+ return undefined;
164
+ const normalized = subType.toLowerCase();
165
+ if (normalized.includes('chat'))
166
+ return 'Grid3.Chat';
167
+ if (normalized.includes('email') || normalized.includes('mail'))
168
+ return 'Grid3.Email';
169
+ if (normalized.includes('word') || normalized.includes('doc'))
170
+ return 'Grid3.WordProcessor';
171
+ if (normalized.includes('phone'))
172
+ return 'Grid3.Phone';
173
+ if (normalized.includes('sms') || normalized.includes('text'))
174
+ return 'Grid3.Sms';
175
+ if (normalized.includes('browser') || normalized.includes('web'))
176
+ return 'Grid3.WebBrowser';
177
+ if (normalized.includes('computer'))
178
+ return 'Grid3.ComputerControl';
179
+ if (normalized.includes('calc'))
180
+ return 'Grid3.Calculator';
181
+ if (normalized.includes('timer'))
182
+ return 'Grid3.Timer';
183
+ if (normalized.includes('music') || normalized.includes('video'))
184
+ return 'Grid3.MusicVideo';
185
+ if (normalized.includes('photo') || normalized.includes('image'))
186
+ return 'Grid3.Photos';
187
+ if (normalized.includes('contact'))
188
+ return 'Grid3.Contacts';
189
+ if (normalized.includes('learning'))
190
+ return 'Grid3.InteractiveLearning';
191
+ if (normalized.includes('message') && normalized.includes('banking'))
192
+ return 'Grid3.MessageBanking';
193
+ if (normalized.includes('control'))
194
+ return 'Grid3.EnvironmentControl';
195
+ if (normalized.includes('settings'))
196
+ return 'Grid3.Settings';
197
+ return `Grid3.${subType}`;
198
+ }
199
+ /**
200
+ * Infer plugin ID from live cell type
201
+ */
202
+ function inferLiveCellPlugin(liveCellType) {
203
+ if (!liveCellType)
204
+ return undefined;
205
+ const normalized = liveCellType.toLowerCase();
206
+ if (normalized.includes('clock'))
207
+ return 'Grid3.Clock';
208
+ if (normalized.includes('date'))
209
+ return 'Grid3.Clock';
210
+ if (normalized.includes('volume'))
211
+ return 'Grid3.Volume';
212
+ if (normalized.includes('speed'))
213
+ return 'Grid3.Speed';
214
+ if (normalized.includes('voice'))
215
+ return 'Grid3.Speech';
216
+ if (normalized.includes('message'))
217
+ return 'Grid3.Chat';
218
+ if (normalized.includes('battery'))
219
+ return 'Grid3.Battery';
220
+ if (normalized.includes('wifi'))
221
+ return 'Grid3.Wifi';
222
+ if (normalized.includes('bluetooth'))
223
+ return 'Grid3.Bluetooth';
224
+ return `Grid3.${liveCellType}`;
225
+ }
226
+ /**
227
+ * Infer plugin ID from auto content type
228
+ */
229
+ function inferAutoContentPlugin(autoContentType) {
230
+ if (!autoContentType)
231
+ return undefined;
232
+ const normalized = autoContentType.toLowerCase();
233
+ if (normalized.includes('voice') || normalized.includes('speed'))
234
+ return 'Grid3.Speech';
235
+ if (normalized.includes('email') || normalized.includes('mail'))
236
+ return 'Grid3.Email';
237
+ if (normalized.includes('phone'))
238
+ return 'Grid3.Phone';
239
+ if (normalized.includes('sms') || normalized.includes('text'))
240
+ return 'Grid3.Sms';
241
+ if (normalized.includes('web') ||
242
+ normalized.includes('favorite') ||
243
+ normalized.includes('history')) {
244
+ return 'Grid3.WebBrowser';
245
+ }
246
+ if (normalized.includes('prediction'))
247
+ return 'Grid3.Prediction';
248
+ if (normalized.includes('grammar'))
249
+ return 'Grid3.Grammar';
250
+ if (normalized.includes('context'))
251
+ return 'Grid3.AutoContent';
252
+ return undefined;
253
+ }
254
+ /**
255
+ * Check if a cell is a workspace cell
256
+ */
257
+ export function isWorkspaceCell(metadata) {
258
+ return metadata.cellType === Grid3CellType.Workspace;
259
+ }
260
+ /**
261
+ * Check if a cell is a live cell
262
+ */
263
+ export function isLiveCell(metadata) {
264
+ return metadata.cellType === Grid3CellType.LiveCell;
265
+ }
266
+ /**
267
+ * Check if a cell is an auto content cell
268
+ */
269
+ export function isAutoContentCell(metadata) {
270
+ return metadata.cellType === Grid3CellType.AutoContent;
271
+ }
272
+ /**
273
+ * Check if a cell is a regular (non-plugin) cell
274
+ */
275
+ export function isRegularCell(metadata) {
276
+ return metadata.cellType === Grid3CellType.Regular;
277
+ }
@@ -0,0 +1,137 @@
1
+ import { isSymbolReference, parseSymbolReference } from './symbols';
2
+ function normalizeZipPathLocal(p) {
3
+ const unified = p.replace(/\\/g, '/');
4
+ try {
5
+ return unified.normalize('NFC');
6
+ }
7
+ catch {
8
+ return unified;
9
+ }
10
+ }
11
+ function listZipEntries(zip, zipEntries) {
12
+ try {
13
+ const raw = Array.isArray(zipEntries) && zipEntries.length > 0
14
+ ? zipEntries
15
+ : typeof zip?.getEntries === 'function'
16
+ ? zip.getEntries()
17
+ : [];
18
+ let entries = [];
19
+ if (Array.isArray(raw))
20
+ entries = raw;
21
+ const arr = entries;
22
+ return arr.map((e) => normalizeZipPathLocal(String(e.entryName)));
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ function extFromName(name) {
29
+ if (!name)
30
+ return undefined;
31
+ const m = name.match(/\.([A-Za-z0-9]+)$/);
32
+ return m ? `.${m[1].toLowerCase()}` : undefined;
33
+ }
34
+ function joinBaseDir(baseDir, leaf) {
35
+ const base = normalizeZipPathLocal(baseDir).replace(/\/?$/, '/');
36
+ return normalizeZipPathLocal(base + leaf.replace(/^\//, ''));
37
+ }
38
+ export function resolveGrid3CellImage(zip, args, zipEntries) {
39
+ const { baseDir, dynamicFiles } = args;
40
+ const imageName = args.imageName?.trim();
41
+ const x = args.x;
42
+ const y = args.y;
43
+ const entries = new Set(listZipEntries(zip, zipEntries));
44
+ const has = (p) => entries.has(normalizeZipPathLocal(p));
45
+ // Built-in resource like [grid3x]... (old format, not symbol library)
46
+ // Check this BEFORE general symbol references to avoid misclassification
47
+ if (imageName && imageName.startsWith('[')) {
48
+ // Check if it's a symbol library reference like [widgit]/food/apple.png
49
+ // Symbol library references have a path after the library name
50
+ if (isSymbolReference(imageName)) {
51
+ const parsed = parseSymbolReference(imageName);
52
+ // If it's grid3x, it's a built-in resource, not a symbol library
53
+ if (parsed.library !== 'grid3x') {
54
+ // Symbol library references are NOT stored as files in the gridset
55
+ // They are resolved from the external Grid 3 installation
56
+ // Return null to indicate this is an external symbol reference
57
+ return null;
58
+ }
59
+ }
60
+ // For grid3x and other built-in resources, use the builtinHandler
61
+ if (args.builtinHandler) {
62
+ const mapped = args.builtinHandler(imageName);
63
+ if (mapped)
64
+ return mapped;
65
+ }
66
+ return null;
67
+ }
68
+ // Direct declared file
69
+ if (imageName) {
70
+ const p1 = joinBaseDir(baseDir, imageName);
71
+ if (has(p1))
72
+ return p1;
73
+ const p2 = joinBaseDir(baseDir, `Images/${imageName}`);
74
+ if (has(p2))
75
+ return p2;
76
+ }
77
+ // FileMap.xml dynamic files
78
+ if (x != null && y != null && dynamicFiles && dynamicFiles.length > 0) {
79
+ const prefix = joinBaseDir(baseDir, `${x}-${y}-`);
80
+ const matches = dynamicFiles
81
+ .map((f) => normalizeZipPathLocal(f))
82
+ .filter((f) => f.startsWith(prefix));
83
+ if (matches.length > 0) {
84
+ const preferred = matches.find((m) => /text/i.test(m)) || matches[0];
85
+ if (has(preferred))
86
+ return preferred;
87
+ }
88
+ }
89
+ // Coordinate-based guesses
90
+ if (x != null && y != null) {
91
+ const ext = extFromName(imageName);
92
+ if (ext) {
93
+ const c1 = joinBaseDir(baseDir, `${x}-${y}-0-text-0${ext}`);
94
+ if (has(c1))
95
+ return c1;
96
+ const c2 = joinBaseDir(baseDir, `${x}-${y}${ext}`);
97
+ if (has(c2))
98
+ return c2;
99
+ }
100
+ else {
101
+ const candidates = [
102
+ `${x}-${y}-0-text-0.jpeg`,
103
+ `${x}-${y}-0-text-0.jpg`,
104
+ `${x}-${y}-0-text-0.png`,
105
+ `${x}-${y}.jpeg`,
106
+ `${x}-${y}.jpg`,
107
+ `${x}-${y}.png`,
108
+ ].map((n) => joinBaseDir(baseDir, n));
109
+ for (const c of candidates) {
110
+ if (has(c))
111
+ return c;
112
+ }
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+ /**
118
+ * Check if an image reference is a symbol library reference
119
+ * @param imageName - Image reference from Grid 3
120
+ * @returns True if it's a symbol library reference
121
+ */
122
+ export function isSymbolLibraryReference(imageName) {
123
+ if (!imageName)
124
+ return false;
125
+ return isSymbolReference(imageName.trim());
126
+ }
127
+ /**
128
+ * Parse a symbol library reference from an image name
129
+ * @param imageName - Image reference from Grid 3
130
+ * @returns Parsed reference or null if not a symbol reference
131
+ */
132
+ export function parseImageSymbolReference(imageName) {
133
+ if (!isSymbolLibraryReference(imageName)) {
134
+ return null;
135
+ }
136
+ return parseSymbolReference(imageName.trim());
137
+ }