@willwade/aac-processors 0.0.5 → 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 +0 -37
- package/dist/cli/index.js +2 -2
- package/dist/core/treeStructure.d.ts +9 -1
- package/dist/core/treeStructure.js +5 -1
- package/dist/processors/applePanelsProcessor.js +67 -41
- package/dist/processors/astericsGridProcessor.js +120 -88
- package/dist/processors/dotProcessor.js +31 -12
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +48 -77
- package/dist/processors/gridset/colorUtils.d.ts +69 -0
- package/dist/processors/gridset/colorUtils.js +331 -0
- package/dist/processors/gridset/helpers.d.ts +30 -0
- package/dist/processors/gridset/helpers.js +69 -0
- package/dist/processors/gridset/styleHelpers.d.ts +3 -4
- package/dist/processors/gridset/styleHelpers.js +10 -44
- package/dist/processors/gridset/wordlistHelpers.js +3 -2
- package/dist/processors/gridsetProcessor.js +97 -105
- package/dist/processors/index.d.ts +5 -3
- package/dist/processors/index.js +21 -2
- package/dist/processors/obfProcessor.d.ts +2 -0
- package/dist/processors/obfProcessor.js +133 -53
- package/dist/processors/opmlProcessor.js +13 -3
- package/dist/processors/snapProcessor.js +45 -15
- package/dist/processors/touchchatProcessor.js +45 -22
- package/dist/types/aac.d.ts +4 -0
- package/package.json +1 -1
|
@@ -32,7 +32,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
32
32
|
const ExcelJS = __importStar(require("exceljs"));
|
|
33
33
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
34
34
|
const treeStructure_1 = require("../core/treeStructure");
|
|
35
|
-
const treeStructure_2 = require("../core/treeStructure");
|
|
36
35
|
/**
|
|
37
36
|
* Excel Processor for converting AAC grids to Excel format
|
|
38
37
|
* Converts AAC tree structures to Excel workbooks with each page as a worksheet
|
|
@@ -44,47 +43,18 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
44
43
|
* @param filePathOrBuffer - Path to Excel file or Buffer containing Excel data
|
|
45
44
|
* @returns Array of all text content found in the Excel file
|
|
46
45
|
*/
|
|
47
|
-
extractTexts(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const workbook = new ExcelJS.Workbook();
|
|
51
|
-
if (Buffer.isBuffer(filePathOrBuffer)) {
|
|
52
|
-
// For buffer input, we need to read it differently
|
|
53
|
-
// This is a placeholder - actual implementation would need to handle buffer reading
|
|
54
|
-
throw new Error('Buffer input not yet implemented for Excel files');
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
// Read from file path
|
|
58
|
-
if (!fs_1.default.existsSync(filePathOrBuffer)) {
|
|
59
|
-
return texts;
|
|
60
|
-
}
|
|
61
|
-
// Note: ExcelJS readFile is async, but we need sync for this interface
|
|
62
|
-
// This is a limitation that would need to be addressed in a real implementation
|
|
63
|
-
throw new Error('Synchronous Excel reading not yet implemented');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
console.warn(`Failed to extract texts from Excel file: ${error}`);
|
|
68
|
-
return texts;
|
|
69
|
-
}
|
|
46
|
+
extractTexts(_filePathOrBuffer) {
|
|
47
|
+
console.warn('ExcelProcessor.extractTexts is not implemented yet.');
|
|
48
|
+
return [];
|
|
70
49
|
}
|
|
71
50
|
/**
|
|
72
51
|
* Load Excel file into AACTree structure
|
|
73
52
|
* @param filePathOrBuffer - Path to Excel file or Buffer containing Excel data
|
|
74
53
|
* @returns AACTree representation of the Excel file
|
|
75
54
|
*/
|
|
76
|
-
loadIntoTree(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// For now, return empty tree as Excel -> AAC conversion is not the primary use case
|
|
80
|
-
// This would be implemented if bidirectional conversion is needed
|
|
81
|
-
console.warn('Excel to AAC conversion not implemented - returning empty tree');
|
|
82
|
-
return tree;
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.warn(`Failed to load Excel file into tree: ${error}`);
|
|
86
|
-
return tree;
|
|
87
|
-
}
|
|
55
|
+
loadIntoTree(_filePathOrBuffer) {
|
|
56
|
+
console.warn('ExcelProcessor.loadIntoTree is not implemented yet.');
|
|
57
|
+
return new treeStructure_1.AACTree();
|
|
88
58
|
}
|
|
89
59
|
/**
|
|
90
60
|
* Process texts in Excel file (apply translations)
|
|
@@ -93,18 +63,13 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
93
63
|
* @param outputPath - Path where translated Excel file should be saved
|
|
94
64
|
* @returns Buffer containing the translated Excel file
|
|
95
65
|
*/
|
|
96
|
-
processTexts(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
throw new Error('Excel text processing not yet implemented');
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.warn(`Failed to process Excel texts: ${error}`);
|
|
105
|
-
// Return empty buffer as fallback
|
|
106
|
-
return Buffer.alloc(0);
|
|
66
|
+
processTexts(_filePathOrBuffer, _translations, outputPath) {
|
|
67
|
+
console.warn('ExcelProcessor.processTexts is not implemented yet.');
|
|
68
|
+
const outputDir = path_1.default.dirname(outputPath);
|
|
69
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
70
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
107
71
|
}
|
|
72
|
+
return Buffer.alloc(0);
|
|
108
73
|
}
|
|
109
74
|
/**
|
|
110
75
|
* Convert an AAC page to an Excel worksheet
|
|
@@ -113,7 +78,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
113
78
|
* @param tree - Full AAC tree for navigation context
|
|
114
79
|
* @param usedNames - Set of already used worksheet names to avoid duplicates
|
|
115
80
|
*/
|
|
116
|
-
|
|
81
|
+
convertPageToWorksheet(workbook, page, tree, usedNames = new Set()) {
|
|
117
82
|
// Create worksheet with page name (sanitized for Excel and unique)
|
|
118
83
|
const worksheetName = this.getUniqueWorksheetName(page.name || page.id, usedNames);
|
|
119
84
|
const worksheet = workbook.addWorksheet(worksheetName);
|
|
@@ -122,16 +87,16 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
122
87
|
// Add navigation row if enabled (optional feature)
|
|
123
88
|
let startRow = 1;
|
|
124
89
|
if (this.shouldAddNavigationRow()) {
|
|
125
|
-
|
|
90
|
+
this.addNavigationRow(worksheet, page, tree);
|
|
126
91
|
startRow = 2; // Start content after navigation row
|
|
127
92
|
}
|
|
128
93
|
// Convert grid layout if available
|
|
129
94
|
if (page.grid && page.grid.length > 0) {
|
|
130
|
-
|
|
95
|
+
this.convertGridLayout(worksheet, page.grid, startRow);
|
|
131
96
|
}
|
|
132
97
|
else {
|
|
133
98
|
// Convert button list to grid layout
|
|
134
|
-
|
|
99
|
+
this.convertButtonsToGrid(worksheet, page.buttons, rows, cols, startRow);
|
|
135
100
|
}
|
|
136
101
|
// Apply worksheet formatting
|
|
137
102
|
this.formatWorksheet(worksheet, rows, cols, startRow);
|
|
@@ -165,14 +130,14 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
165
130
|
* @param grid - 2D array of AAC buttons
|
|
166
131
|
* @param startRow - Starting row number
|
|
167
132
|
*/
|
|
168
|
-
|
|
133
|
+
convertGridLayout(worksheet, grid, startRow) {
|
|
169
134
|
for (let row = 0; row < grid.length; row++) {
|
|
170
135
|
for (let col = 0; col < grid[row].length; col++) {
|
|
171
136
|
const button = grid[row][col];
|
|
172
137
|
if (button) {
|
|
173
138
|
const excelRow = startRow + row;
|
|
174
139
|
const excelCol = col + 1; // Excel columns are 1-based
|
|
175
|
-
|
|
140
|
+
this.setButtonCell(worksheet, button, excelRow, excelCol);
|
|
176
141
|
}
|
|
177
142
|
}
|
|
178
143
|
}
|
|
@@ -185,7 +150,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
185
150
|
* @param cols - Number of columns in grid
|
|
186
151
|
* @param startRow - Starting row number
|
|
187
152
|
*/
|
|
188
|
-
|
|
153
|
+
convertButtonsToGrid(worksheet, buttons, rows, cols, startRow) {
|
|
189
154
|
for (let i = 0; i < buttons.length; i++) {
|
|
190
155
|
const button = buttons[i];
|
|
191
156
|
const row = Math.floor(i / cols);
|
|
@@ -193,7 +158,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
193
158
|
if (row < rows) {
|
|
194
159
|
const excelRow = startRow + row;
|
|
195
160
|
const excelCol = col + 1; // Excel columns are 1-based
|
|
196
|
-
|
|
161
|
+
this.setButtonCell(worksheet, button, excelRow, excelCol);
|
|
197
162
|
}
|
|
198
163
|
}
|
|
199
164
|
}
|
|
@@ -204,7 +169,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
204
169
|
* @param row - Excel row number
|
|
205
170
|
* @param col - Excel column number
|
|
206
171
|
*/
|
|
207
|
-
|
|
172
|
+
setButtonCell(worksheet, button, row, col) {
|
|
208
173
|
const cell = worksheet.getCell(row, col);
|
|
209
174
|
// Set cell value to button label
|
|
210
175
|
cell.value = button.label || '';
|
|
@@ -217,7 +182,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
217
182
|
this.applyCellStyling(cell, button.style);
|
|
218
183
|
}
|
|
219
184
|
// Add navigation link if this is a navigation button
|
|
220
|
-
if (button.semanticAction?.intent ===
|
|
185
|
+
if (button.semanticAction?.intent === treeStructure_1.AACSemanticIntent.NAVIGATE_TO && button.targetPageId) {
|
|
221
186
|
this.addNavigationLink(cell, button.targetPageId);
|
|
222
187
|
}
|
|
223
188
|
// Set cell size for better visibility
|
|
@@ -229,14 +194,16 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
229
194
|
* @param style - AAC style object
|
|
230
195
|
*/
|
|
231
196
|
applyCellStyling(cell, style) {
|
|
232
|
-
|
|
197
|
+
let fill;
|
|
233
198
|
const font = {};
|
|
234
|
-
|
|
199
|
+
let border;
|
|
235
200
|
// Background color
|
|
236
201
|
if (style.backgroundColor) {
|
|
237
|
-
fill
|
|
238
|
-
|
|
239
|
-
|
|
202
|
+
fill = {
|
|
203
|
+
type: 'pattern',
|
|
204
|
+
pattern: 'solid',
|
|
205
|
+
fgColor: { argb: this.convertColorToArgb(style.backgroundColor) },
|
|
206
|
+
};
|
|
240
207
|
}
|
|
241
208
|
// Font color
|
|
242
209
|
if (style.fontColor) {
|
|
@@ -263,24 +230,27 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
263
230
|
font.underline = true;
|
|
264
231
|
}
|
|
265
232
|
// Border
|
|
266
|
-
if (style.borderColor || style.borderWidth) {
|
|
267
|
-
const
|
|
233
|
+
if (style.borderColor || typeof style.borderWidth === 'number') {
|
|
234
|
+
const borderWidth = style.borderWidth ?? 1;
|
|
235
|
+
const borderStyle = borderWidth > 1 ? 'thick' : 'thin';
|
|
268
236
|
const borderColor = style.borderColor
|
|
269
237
|
? { argb: this.convertColorToArgb(style.borderColor) }
|
|
270
238
|
: { argb: 'FF000000' }; // Default black
|
|
271
|
-
border
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
239
|
+
border = {
|
|
240
|
+
top: { style: borderStyle, color: borderColor },
|
|
241
|
+
left: { style: borderStyle, color: borderColor },
|
|
242
|
+
bottom: { style: borderStyle, color: borderColor },
|
|
243
|
+
right: { style: borderStyle, color: borderColor },
|
|
244
|
+
};
|
|
275
245
|
}
|
|
276
246
|
// Apply styling to cell
|
|
277
|
-
if (
|
|
247
|
+
if (fill) {
|
|
278
248
|
cell.fill = fill;
|
|
279
249
|
}
|
|
280
250
|
if (Object.keys(font).length > 0) {
|
|
281
251
|
cell.font = font;
|
|
282
252
|
}
|
|
283
|
-
if (
|
|
253
|
+
if (border) {
|
|
284
254
|
cell.border = border;
|
|
285
255
|
}
|
|
286
256
|
// Center align text
|
|
@@ -369,7 +339,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
369
339
|
* @param page - Current AAC page
|
|
370
340
|
* @param tree - Full AAC tree for navigation context
|
|
371
341
|
*/
|
|
372
|
-
|
|
342
|
+
addNavigationRow(worksheet, page, tree) {
|
|
373
343
|
const navButtons = ExcelProcessor.NAVIGATION_BUTTONS;
|
|
374
344
|
for (let i = 0; i < navButtons.length; i++) {
|
|
375
345
|
const cell = worksheet.getCell(1, i + 1);
|
|
@@ -440,9 +410,9 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
440
410
|
// - Max 31 characters
|
|
441
411
|
// - Cannot contain: \ / ? * [ ] :
|
|
442
412
|
// - Cannot be empty
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
413
|
+
let cleaned = (name || '').replace(/[\\/?*:]/g, '_');
|
|
414
|
+
cleaned = cleaned.replace(/\[/g, '_').replace(/\]/g, '_');
|
|
415
|
+
cleaned = cleaned.substring(0, 31);
|
|
446
416
|
if (cleaned.length === 0) {
|
|
447
417
|
return 'Sheet1';
|
|
448
418
|
}
|
|
@@ -499,11 +469,12 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
499
469
|
await this.saveFromTreeAsync(tree, outputPath);
|
|
500
470
|
}
|
|
501
471
|
catch (error) {
|
|
502
|
-
|
|
472
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
473
|
+
console.error('Failed to save Excel file:', message);
|
|
503
474
|
try {
|
|
504
475
|
const fallbackPath = outputPath.replace(/\.xlsx$/i, '_error.txt');
|
|
505
476
|
fs_1.default.mkdirSync(path_1.default.dirname(fallbackPath), { recursive: true });
|
|
506
|
-
fs_1.default.writeFileSync(fallbackPath, `Error saving Excel file: ${
|
|
477
|
+
fs_1.default.writeFileSync(fallbackPath, `Error saving Excel file: ${message}`);
|
|
507
478
|
}
|
|
508
479
|
catch (writeError) {
|
|
509
480
|
console.error('Failed to write Excel error file:', writeError);
|
|
@@ -532,7 +503,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
532
503
|
// Convert each AAC page to an Excel worksheet
|
|
533
504
|
for (const pageId in tree.pages) {
|
|
534
505
|
const page = tree.pages[pageId];
|
|
535
|
-
|
|
506
|
+
this.convertPageToWorksheet(workbook, page, tree, usedNames);
|
|
536
507
|
}
|
|
537
508
|
// Save the workbook
|
|
538
509
|
await workbook.xlsx.writeFile(outputPath);
|
|
@@ -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
|
+
}
|