@willwade/aac-processors 0.0.10 → 0.0.12

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 (57) hide show
  1. package/dist/cli/index.js +7 -0
  2. package/dist/core/analyze.js +1 -0
  3. package/dist/core/baseProcessor.d.ts +3 -0
  4. package/dist/core/treeStructure.d.ts +14 -2
  5. package/dist/core/treeStructure.js +8 -2
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.js +20 -3
  8. package/dist/{analytics → optional/analytics}/history.d.ts +3 -3
  9. package/dist/{analytics → optional/analytics}/history.js +3 -3
  10. package/dist/optional/analytics/index.d.ts +28 -0
  11. package/dist/optional/analytics/index.js +73 -0
  12. package/dist/optional/analytics/metrics/comparison.d.ts +36 -0
  13. package/dist/optional/analytics/metrics/comparison.js +330 -0
  14. package/dist/optional/analytics/metrics/core.d.ts +36 -0
  15. package/dist/optional/analytics/metrics/core.js +422 -0
  16. package/dist/optional/analytics/metrics/effort.d.ts +137 -0
  17. package/dist/optional/analytics/metrics/effort.js +198 -0
  18. package/dist/optional/analytics/metrics/index.d.ts +15 -0
  19. package/dist/optional/analytics/metrics/index.js +36 -0
  20. package/dist/optional/analytics/metrics/sentence.d.ts +49 -0
  21. package/dist/optional/analytics/metrics/sentence.js +112 -0
  22. package/dist/optional/analytics/metrics/types.d.ts +157 -0
  23. package/dist/optional/analytics/metrics/types.js +7 -0
  24. package/dist/optional/analytics/metrics/vocabulary.d.ts +65 -0
  25. package/dist/optional/analytics/metrics/vocabulary.js +140 -0
  26. package/dist/optional/analytics/reference/index.d.ts +51 -0
  27. package/dist/optional/analytics/reference/index.js +102 -0
  28. package/dist/optional/analytics/utils/idGenerator.d.ts +59 -0
  29. package/dist/optional/analytics/utils/idGenerator.js +96 -0
  30. package/dist/processors/gridset/colorUtils.d.ts +18 -0
  31. package/dist/processors/gridset/colorUtils.js +36 -0
  32. package/dist/processors/gridset/commands.d.ts +103 -0
  33. package/dist/processors/gridset/commands.js +958 -0
  34. package/dist/processors/gridset/index.d.ts +45 -0
  35. package/dist/processors/gridset/index.js +153 -0
  36. package/dist/processors/gridset/pluginTypes.d.ts +109 -0
  37. package/dist/processors/gridset/pluginTypes.js +285 -0
  38. package/dist/processors/gridset/resolver.d.ts +13 -0
  39. package/dist/processors/gridset/resolver.js +39 -1
  40. package/dist/processors/gridset/styleHelpers.d.ts +22 -0
  41. package/dist/processors/gridset/styleHelpers.js +35 -1
  42. package/dist/processors/gridset/symbolExtractor.d.ts +121 -0
  43. package/dist/processors/gridset/symbolExtractor.js +362 -0
  44. package/dist/processors/gridset/symbolSearch.d.ts +117 -0
  45. package/dist/processors/gridset/symbolSearch.js +280 -0
  46. package/dist/processors/gridset/symbols.d.ts +199 -0
  47. package/dist/processors/gridset/symbols.js +468 -0
  48. package/dist/processors/gridsetProcessor.js +59 -0
  49. package/dist/processors/index.d.ts +10 -1
  50. package/dist/processors/index.js +93 -2
  51. package/dist/processors/obfProcessor.js +25 -2
  52. package/dist/processors/obfsetProcessor.d.ts +26 -0
  53. package/dist/processors/obfsetProcessor.js +179 -0
  54. package/dist/processors/snapProcessor.js +29 -1
  55. package/dist/processors/touchchatProcessor.js +27 -0
  56. package/dist/types/aac.d.ts +21 -0
  57. package/package.json +1 -1
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveGrid3CellImage = resolveGrid3CellImage;
4
+ exports.isSymbolLibraryReference = isSymbolLibraryReference;
5
+ exports.parseImageSymbolReference = parseImageSymbolReference;
6
+ const symbols_1 = require("./symbols");
4
7
  function normalizeZipPathLocal(p) {
5
8
  const unified = p.replace(/\\/g, '/');
6
9
  try {
@@ -44,8 +47,22 @@ function resolveGrid3CellImage(zip, args, zipEntries) {
44
47
  const y = args.y;
45
48
  const entries = new Set(listZipEntries(zip, zipEntries));
46
49
  const has = (p) => entries.has(normalizeZipPathLocal(p));
47
- // Built-in resource like [grid3x]...
50
+ // Built-in resource like [grid3x]... (old format, not symbol library)
51
+ // Check this BEFORE general symbol references to avoid misclassification
48
52
  if (imageName && imageName.startsWith('[')) {
53
+ // Check if it's a symbol library reference like [widgit]/food/apple.png
54
+ // Symbol library references have a path after the library name
55
+ if ((0, symbols_1.isSymbolReference)(imageName)) {
56
+ const parsed = (0, symbols_1.parseSymbolReference)(imageName);
57
+ // If it's grid3x, it's a built-in resource, not a symbol library
58
+ if (parsed.library !== 'grid3x') {
59
+ // Symbol library references are NOT stored as files in the gridset
60
+ // They are resolved from the external Grid 3 installation
61
+ // Return null to indicate this is an external symbol reference
62
+ return null;
63
+ }
64
+ }
65
+ // For grid3x and other built-in resources, use the builtinHandler
49
66
  if (args.builtinHandler) {
50
67
  const mapped = args.builtinHandler(imageName);
51
68
  if (mapped)
@@ -102,3 +119,24 @@ function resolveGrid3CellImage(zip, args, zipEntries) {
102
119
  }
103
120
  return null;
104
121
  }
122
+ /**
123
+ * Check if an image reference is a symbol library reference
124
+ * @param imageName - Image reference from Grid 3
125
+ * @returns True if it's a symbol library reference
126
+ */
127
+ function isSymbolLibraryReference(imageName) {
128
+ if (!imageName)
129
+ return false;
130
+ return (0, symbols_1.isSymbolReference)(imageName.trim());
131
+ }
132
+ /**
133
+ * Parse a symbol library reference from an image name
134
+ * @param imageName - Image reference from Grid 3
135
+ * @returns Parsed reference or null if not a symbol reference
136
+ */
137
+ function parseImageSymbolReference(imageName) {
138
+ if (!isSymbolLibraryReference(imageName)) {
139
+ return null;
140
+ }
141
+ return (0, symbols_1.parseSymbolReference)(imageName.trim());
142
+ }
@@ -4,6 +4,27 @@
4
4
  * Utilities for creating and managing Grid3 styles, including default styles,
5
5
  * style XML generation, and style conversion utilities.
6
6
  */
7
+ /**
8
+ * Cell background shapes supported by Grid 3
9
+ * Maps to Grid 3's CellBackgroundShape enum
10
+ */
11
+ export declare enum CellBackgroundShape {
12
+ Rectangle = 0,
13
+ RoundedRectangle = 1,
14
+ FoldedCorner = 2,
15
+ Octagon = 3,
16
+ Folder = 4,
17
+ Ellipse = 5,
18
+ SpeechBubble = 6,
19
+ ThoughtBubble = 7,
20
+ Star = 8,
21
+ Circle = 9,
22
+ ColouredCorner = 10
23
+ }
24
+ /**
25
+ * Human-readable shape names
26
+ */
27
+ export declare const SHAPE_NAMES: Record<CellBackgroundShape, string>;
7
28
  /**
8
29
  * Grid3 Style object structure
9
30
  */
@@ -14,6 +35,7 @@ export interface Grid3Style {
14
35
  FontColour?: string;
15
36
  FontName?: string;
16
37
  FontSize?: string | number;
38
+ BackgroundShape?: CellBackgroundShape;
17
39
  }
18
40
  /**
19
41
  * Default Grid3 styles for common use cases
@@ -6,11 +6,45 @@
6
6
  * style XML generation, and style conversion utilities.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.ensureAlphaChannel = exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = void 0;
9
+ exports.ensureAlphaChannel = exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = exports.SHAPE_NAMES = exports.CellBackgroundShape = void 0;
10
10
  exports.createDefaultStylesXml = createDefaultStylesXml;
11
11
  exports.createCategoryStyle = createCategoryStyle;
12
12
  const fast_xml_parser_1 = require("fast-xml-parser");
13
13
  const colorUtils_1 = require("./colorUtils");
14
+ /**
15
+ * Cell background shapes supported by Grid 3
16
+ * Maps to Grid 3's CellBackgroundShape enum
17
+ */
18
+ var CellBackgroundShape;
19
+ (function (CellBackgroundShape) {
20
+ CellBackgroundShape[CellBackgroundShape["Rectangle"] = 0] = "Rectangle";
21
+ CellBackgroundShape[CellBackgroundShape["RoundedRectangle"] = 1] = "RoundedRectangle";
22
+ CellBackgroundShape[CellBackgroundShape["FoldedCorner"] = 2] = "FoldedCorner";
23
+ CellBackgroundShape[CellBackgroundShape["Octagon"] = 3] = "Octagon";
24
+ CellBackgroundShape[CellBackgroundShape["Folder"] = 4] = "Folder";
25
+ CellBackgroundShape[CellBackgroundShape["Ellipse"] = 5] = "Ellipse";
26
+ CellBackgroundShape[CellBackgroundShape["SpeechBubble"] = 6] = "SpeechBubble";
27
+ CellBackgroundShape[CellBackgroundShape["ThoughtBubble"] = 7] = "ThoughtBubble";
28
+ CellBackgroundShape[CellBackgroundShape["Star"] = 8] = "Star";
29
+ CellBackgroundShape[CellBackgroundShape["Circle"] = 9] = "Circle";
30
+ CellBackgroundShape[CellBackgroundShape["ColouredCorner"] = 10] = "ColouredCorner";
31
+ })(CellBackgroundShape || (exports.CellBackgroundShape = CellBackgroundShape = {}));
32
+ /**
33
+ * Human-readable shape names
34
+ */
35
+ exports.SHAPE_NAMES = {
36
+ [CellBackgroundShape.Rectangle]: 'Rectangle',
37
+ [CellBackgroundShape.RoundedRectangle]: 'Rounded Rectangle',
38
+ [CellBackgroundShape.FoldedCorner]: 'Folded Corner',
39
+ [CellBackgroundShape.Octagon]: 'Octagon',
40
+ [CellBackgroundShape.Folder]: 'Folder',
41
+ [CellBackgroundShape.Ellipse]: 'Ellipse',
42
+ [CellBackgroundShape.SpeechBubble]: 'Speech Bubble',
43
+ [CellBackgroundShape.ThoughtBubble]: 'Thought Bubble',
44
+ [CellBackgroundShape.Star]: 'Star',
45
+ [CellBackgroundShape.Circle]: 'Circle',
46
+ [CellBackgroundShape.ColouredCorner]: 'Coloured Corner',
47
+ };
14
48
  /**
15
49
  * Default Grid3 styles for common use cases
16
50
  * Colors are in 8-digit ARGB hex format (#AARRGGBBFF)
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Grid 3 Symbol Extraction Strategy
3
+ *
4
+ * For converting Grid 3 gridsets to other formats (like Asterics),
5
+ * we need to handle symbol library references properly.
6
+ *
7
+ * Strategy:
8
+ * 1. Check if image is embedded in gridset (extract directly)
9
+ * 2. If symbol library reference:
10
+ * a. Check if we can extract from .pix file (limited support)
11
+ * b. Provide reference/URL for manual resolution
12
+ * c. For Tawasol: provide alternative sources
13
+ */
14
+ import { type SymbolReference } from './symbols';
15
+ /**
16
+ * Image extraction result
17
+ */
18
+ export interface ExtractedImage {
19
+ found: boolean;
20
+ data?: Buffer;
21
+ format?: 'png' | 'jpg' | 'jpeg' | 'gif' | 'svg' | 'unknown';
22
+ source: 'embedded' | 'symbol-library' | 'external-file' | 'not-found';
23
+ reference?: string;
24
+ error?: string;
25
+ metadata?: {
26
+ library?: string;
27
+ symbolPath?: string;
28
+ attribution?: string;
29
+ license?: string;
30
+ };
31
+ }
32
+ /**
33
+ * Symbol extraction options
34
+ */
35
+ export interface SymbolExtractionOptions {
36
+ grid3Path?: string;
37
+ locale?: string;
38
+ preferEmbedded?: boolean;
39
+ includeAttribution?: boolean;
40
+ /** For Tawasol: try to find alternative sources */
41
+ tryAlternativeSources?: boolean;
42
+ /** Callback for when symbol needs manual extraction */
43
+ onMissingSymbol?: (ref: SymbolReference) => void;
44
+ }
45
+ /**
46
+ * Extract image data for a button
47
+ * @param gridsetBuffer - Gridset ZIP buffer
48
+ * @param resolvedImageEntry - Path to embedded image in gridset
49
+ * @param symbolReference - Symbol library reference
50
+ * @param options - Extraction options
51
+ * @returns Extracted image data
52
+ */
53
+ export declare function extractButtonImage(gridsetBuffer: Buffer, resolvedImageEntry: string | undefined, symbolReference: string | undefined, options?: SymbolExtractionOptions): ExtractedImage;
54
+ /**
55
+ * Extract image from symbol library
56
+ * @param reference - Symbol reference like "[tawasl]/food/apple.png"
57
+ * @param options - Extraction options
58
+ * @returns Extracted image or reference info
59
+ */
60
+ export declare function extractSymbolLibraryImage(reference: string, options?: SymbolExtractionOptions): ExtractedImage;
61
+ /**
62
+ * Convert extracted image to Asterics Grid format
63
+ * @param extracted - Extracted image
64
+ * @returns GridImage object for Asterics
65
+ */
66
+ export declare function convertToAstericsImage(extracted: ExtractedImage): any;
67
+ /**
68
+ * Generate a symbol extraction report
69
+ * Useful for identifying which symbols need manual extraction
70
+ */
71
+ export interface SymbolReport {
72
+ total: number;
73
+ embedded: number;
74
+ symbolLibraries: number;
75
+ notFound: number;
76
+ byLibrary: Record<string, number>;
77
+ missingSymbols: Array<{
78
+ reference: string;
79
+ library: string;
80
+ path: string;
81
+ attribution?: string;
82
+ license?: string;
83
+ }>;
84
+ }
85
+ /**
86
+ * Analyze symbol usage for a gridset
87
+ * @param tree - AAC tree
88
+ * @returns Symbol usage report
89
+ */
90
+ export declare function analyzeSymbolExtraction(tree: any): SymbolReport;
91
+ /**
92
+ * Suggest extraction strategy based on report
93
+ */
94
+ export declare function suggestExtractionStrategy(report: SymbolReport): string;
95
+ /**
96
+ * Export symbol references to CSV for manual extraction
97
+ */
98
+ export declare function exportSymbolReferencesToCsv(report: SymbolReport, outputPath: string): void;
99
+ /**
100
+ * Create a manifest file for missing symbols
101
+ */
102
+ export interface SymbolManifest {
103
+ generatedAt: string;
104
+ gridset: string;
105
+ totalSymbols: number;
106
+ embedded: number;
107
+ fromLibraries: number;
108
+ libraries: Record<string, {
109
+ count: number;
110
+ attribution?: string;
111
+ license?: string;
112
+ url?: string;
113
+ }>;
114
+ symbols: Array<{
115
+ pageId: string;
116
+ buttonId: string;
117
+ reference: string;
118
+ label?: string;
119
+ }>;
120
+ }
121
+ export declare function createSymbolManifest(tree: any, gridsetName: string): SymbolManifest;
@@ -0,0 +1,362 @@
1
+ "use strict";
2
+ /**
3
+ * Grid 3 Symbol Extraction Strategy
4
+ *
5
+ * For converting Grid 3 gridsets to other formats (like Asterics),
6
+ * we need to handle symbol library references properly.
7
+ *
8
+ * Strategy:
9
+ * 1. Check if image is embedded in gridset (extract directly)
10
+ * 2. If symbol library reference:
11
+ * a. Check if we can extract from .pix file (limited support)
12
+ * b. Provide reference/URL for manual resolution
13
+ * c. For Tawasol: provide alternative sources
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.extractButtonImage = extractButtonImage;
43
+ exports.extractSymbolLibraryImage = extractSymbolLibraryImage;
44
+ exports.convertToAstericsImage = convertToAstericsImage;
45
+ exports.analyzeSymbolExtraction = analyzeSymbolExtraction;
46
+ exports.suggestExtractionStrategy = suggestExtractionStrategy;
47
+ exports.exportSymbolReferencesToCsv = exportSymbolReferencesToCsv;
48
+ exports.createSymbolManifest = createSymbolManifest;
49
+ const fs = __importStar(require("fs"));
50
+ const adm_zip_1 = __importDefault(require("adm-zip"));
51
+ const symbols_1 = require("./symbols");
52
+ /**
53
+ * Known open-license symbol sources
54
+ */
55
+ const OPEN_LICENSE_SYMBOLS = {
56
+ tawasl: {
57
+ name: 'Tawasol',
58
+ attribution: 'Tawasol symbols by Mada (Qatar Assistive Technology Center)',
59
+ license: 'CC BY-SA 4.0',
60
+ url: 'https://mada.org.qa/en/resources/tawasol-symbols',
61
+ alternativeSources: ['https://github.com/mada-qatar/Tawasol'],
62
+ },
63
+ blissx: {
64
+ name: 'Blissymbols',
65
+ attribution: 'Blissymbolics Communication International',
66
+ license: 'CC BY-ND 3.0',
67
+ url: 'https://blissymbolics.org',
68
+ },
69
+ symoji: {
70
+ name: 'Symoji',
71
+ attribution: 'Smartbox Assistive Technology',
72
+ license: 'Proprietary - Free use in Grid 3',
73
+ },
74
+ };
75
+ /**
76
+ * Extract image data for a button
77
+ * @param gridsetBuffer - Gridset ZIP buffer
78
+ * @param resolvedImageEntry - Path to embedded image in gridset
79
+ * @param symbolReference - Symbol library reference
80
+ * @param options - Extraction options
81
+ * @returns Extracted image data
82
+ */
83
+ function extractButtonImage(gridsetBuffer, resolvedImageEntry, symbolReference, options = {}) {
84
+ // Priority 1: Use embedded image if available
85
+ if (resolvedImageEntry && options.preferEmbedded !== false) {
86
+ try {
87
+ const zip = new adm_zip_1.default(gridsetBuffer);
88
+ const entries = zip.getEntries();
89
+ const entry = entries.find((e) => e.entryName === resolvedImageEntry);
90
+ if (entry) {
91
+ const data = entry.getData();
92
+ const format = detectImageFormat(data);
93
+ return {
94
+ found: true,
95
+ data,
96
+ format,
97
+ source: 'embedded',
98
+ reference: resolvedImageEntry,
99
+ };
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.warn(`Failed to extract embedded image: ${String(error)}`);
104
+ }
105
+ }
106
+ // Priority 2: Check symbol library reference
107
+ if (symbolReference) {
108
+ return extractSymbolLibraryImage(symbolReference, options);
109
+ }
110
+ // Not found
111
+ return {
112
+ found: false,
113
+ source: 'not-found',
114
+ };
115
+ }
116
+ /**
117
+ * Extract image from symbol library
118
+ * @param reference - Symbol reference like "[tawasl]/food/apple.png"
119
+ * @param options - Extraction options
120
+ * @returns Extracted image or reference info
121
+ */
122
+ function extractSymbolLibraryImage(reference, options = {}) {
123
+ const ref = parseSymbolReferenceSafe(reference);
124
+ if (!ref || !ref.isValid) {
125
+ return {
126
+ found: false,
127
+ source: 'not-found',
128
+ reference,
129
+ };
130
+ }
131
+ // Get library metadata
132
+ const libInfo = OPEN_LICENSE_SYMBOLS[ref.library];
133
+ // Resolve symbol reference and extract from .symbols file
134
+ const resolved = (0, symbols_1.resolveSymbolReference)(reference, {
135
+ grid3Path: options.grid3Path,
136
+ });
137
+ const metadata = {
138
+ library: ref.library,
139
+ symbolPath: ref.path,
140
+ attribution: libInfo?.attribution,
141
+ license: libInfo?.license,
142
+ };
143
+ if (!resolved.found) {
144
+ // Symbol not found in library
145
+ if (options.onMissingSymbol) {
146
+ options.onMissingSymbol(ref);
147
+ }
148
+ return {
149
+ found: false,
150
+ source: 'symbol-library',
151
+ reference: reference,
152
+ metadata,
153
+ error: resolved.error,
154
+ };
155
+ }
156
+ // Successfully extracted!
157
+ const format = detectImageFormat(resolved.data);
158
+ return {
159
+ found: true,
160
+ data: resolved.data,
161
+ format,
162
+ source: 'symbol-library',
163
+ reference: reference,
164
+ metadata,
165
+ };
166
+ }
167
+ /**
168
+ * Convert extracted image to Asterics Grid format
169
+ * @param extracted - Extracted image
170
+ * @returns GridImage object for Asterics
171
+ */
172
+ function convertToAstericsImage(extracted) {
173
+ const image = {};
174
+ if (extracted.found && extracted.data) {
175
+ // Embed as base64
176
+ image.data = Buffer.from(extracted.data).toString('base64');
177
+ }
178
+ // Even if embedded, add attribution for symbol libraries
179
+ if (extracted.source === 'symbol-library') {
180
+ if (extracted.metadata?.attribution) {
181
+ image.author = extracted.metadata.attribution;
182
+ }
183
+ if (extracted.metadata?.license) {
184
+ image.searchProviderName = extracted.metadata.license;
185
+ }
186
+ }
187
+ // If not found but we have a reference, keep it for manual handling
188
+ if (!extracted.found && extracted.reference) {
189
+ image.url = `symbol:${extracted.reference}`;
190
+ if (extracted.metadata?.attribution) {
191
+ image.author = extracted.metadata.attribution;
192
+ }
193
+ }
194
+ return image;
195
+ }
196
+ /**
197
+ * Analyze symbol usage for a gridset
198
+ * @param tree - AAC tree
199
+ * @returns Symbol usage report
200
+ */
201
+ function analyzeSymbolExtraction(tree) {
202
+ const report = {
203
+ total: 0,
204
+ embedded: 0,
205
+ symbolLibraries: 0,
206
+ notFound: 0,
207
+ byLibrary: {},
208
+ missingSymbols: [],
209
+ };
210
+ for (const pageId in tree.pages) {
211
+ const page = tree.pages[pageId];
212
+ if (page.buttons) {
213
+ for (const button of page.buttons) {
214
+ report.total++;
215
+ // Embedded image
216
+ if (button.resolvedImageEntry && !button.symbolLibrary) {
217
+ report.embedded++;
218
+ continue;
219
+ }
220
+ // Symbol library reference
221
+ if (button.symbolLibrary) {
222
+ report.symbolLibraries++;
223
+ report.byLibrary[button.symbolLibrary] =
224
+ (report.byLibrary[button.symbolLibrary] || 0) + 1;
225
+ const ref = `[${button.symbolLibrary}]${button.symbolPath || ''}`;
226
+ const libInfo = OPEN_LICENSE_SYMBOLS[button.symbolLibrary];
227
+ report.missingSymbols.push({
228
+ reference: ref,
229
+ library: button.symbolLibrary,
230
+ path: button.symbolPath || '',
231
+ attribution: libInfo?.attribution,
232
+ license: libInfo?.license,
233
+ });
234
+ continue;
235
+ }
236
+ // Not found
237
+ if (!button.resolvedImageEntry && !button.symbolLibrary) {
238
+ report.notFound++;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return report;
244
+ }
245
+ /**
246
+ * Suggest extraction strategy based on report
247
+ */
248
+ function suggestExtractionStrategy(report) {
249
+ const suggestions = [];
250
+ if (report.embedded > 0) {
251
+ suggestions.push(`✓ Can extract ${report.embedded} embedded images directly`);
252
+ }
253
+ if (report.symbolLibraries > 0) {
254
+ suggestions.push(`⚠ ${report.symbolLibraries} symbol library references found:`);
255
+ Object.entries(report.byLibrary).forEach(([lib, count]) => {
256
+ const libInfo = OPEN_LICENSE_SYMBOLS[lib];
257
+ if (libInfo) {
258
+ suggestions.push(` - ${lib}: ${count} symbols (${libInfo.license})`);
259
+ if (libInfo.alternativeSources) {
260
+ suggestions.push(` Alternative: ${libInfo.alternativeSources.join(', ')}`);
261
+ }
262
+ }
263
+ else {
264
+ suggestions.push(` - ${lib}: ${count} symbols (Proprietary - requires Grid 3)`);
265
+ }
266
+ });
267
+ }
268
+ if (report.notFound > 0) {
269
+ suggestions.push(`✗ ${report.notFound} images not found`);
270
+ }
271
+ return suggestions.join('\n');
272
+ }
273
+ /**
274
+ * Detect image format from buffer
275
+ */
276
+ function detectImageFormat(buffer) {
277
+ if (buffer.length < 4)
278
+ return 'unknown';
279
+ // PNG: 89 50 4E 47
280
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
281
+ return 'png';
282
+ }
283
+ // JPEG: FF D8 FF
284
+ if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
285
+ return 'jpg';
286
+ }
287
+ // GIF: 47 49 46 38
288
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
289
+ return 'gif';
290
+ }
291
+ // SVG (check for <svg text)
292
+ const header = buffer.slice(0, Math.min(100, buffer.length)).toString('ascii').toLowerCase();
293
+ if (header.includes('<svg')) {
294
+ return 'svg';
295
+ }
296
+ return 'unknown';
297
+ }
298
+ /**
299
+ * Safe parse of symbol reference
300
+ */
301
+ function parseSymbolReferenceSafe(reference) {
302
+ try {
303
+ return (0, symbols_1.parseSymbolReference)(reference);
304
+ }
305
+ catch {
306
+ return null;
307
+ }
308
+ }
309
+ /**
310
+ * Export symbol references to CSV for manual extraction
311
+ */
312
+ function exportSymbolReferencesToCsv(report, outputPath) {
313
+ const lines = ['Reference,Library,Path,Attribution,License'];
314
+ for (const symbol of report.missingSymbols) {
315
+ lines.push(`"${symbol.reference}","${symbol.library}","${symbol.path}","${symbol.attribution || ''}","${symbol.license || ''}"`);
316
+ }
317
+ fs.writeFileSync(outputPath, lines.join('\n'));
318
+ }
319
+ function createSymbolManifest(tree, gridsetName) {
320
+ const manifest = {
321
+ generatedAt: new Date().toISOString(),
322
+ gridset: gridsetName,
323
+ totalSymbols: 0,
324
+ embedded: 0,
325
+ fromLibraries: 0,
326
+ libraries: {},
327
+ symbols: [],
328
+ };
329
+ for (const pageId in tree.pages) {
330
+ const page = tree.pages[pageId];
331
+ if (page.buttons) {
332
+ for (const button of page.buttons) {
333
+ manifest.totalSymbols++;
334
+ if (button.resolvedImageEntry && !button.symbolLibrary) {
335
+ manifest.embedded++;
336
+ continue;
337
+ }
338
+ if (button.symbolLibrary) {
339
+ manifest.fromLibraries++;
340
+ if (!manifest.libraries[button.symbolLibrary]) {
341
+ const libInfo = OPEN_LICENSE_SYMBOLS[button.symbolLibrary];
342
+ manifest.libraries[button.symbolLibrary] = {
343
+ count: 0,
344
+ attribution: libInfo?.attribution,
345
+ license: libInfo?.license,
346
+ url: libInfo?.url,
347
+ };
348
+ }
349
+ manifest.libraries[button.symbolLibrary].count++;
350
+ const ref = `[${button.symbolLibrary}]${button.symbolPath || ''}`;
351
+ manifest.symbols.push({
352
+ pageId,
353
+ buttonId: button.id,
354
+ reference: ref,
355
+ label: button.label,
356
+ });
357
+ }
358
+ }
359
+ }
360
+ }
361
+ return manifest;
362
+ }