@willwade/aac-processors 0.1.19 → 0.1.21
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/dist/browser/core/baseProcessor.js +4 -0
- package/dist/browser/processors/applePanelsProcessor.js +24 -31
- package/dist/browser/processors/astericsGridProcessor.js +10 -3
- package/dist/browser/processors/dotProcessor.js +5 -2
- package/dist/browser/processors/gridset/colorUtils.js +354 -0
- package/dist/browser/processors/gridset/helpers.js +49 -45
- package/dist/browser/processors/gridset/index.js +61 -0
- package/dist/browser/processors/gridset/styleHelpers.js +205 -0
- package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
- package/dist/browser/processors/gridset/symbolSearch.js +248 -0
- package/dist/browser/processors/gridset/symbols.js +35 -68
- package/dist/browser/processors/gridsetProcessor.js +32 -41
- package/dist/browser/processors/obfProcessor.js +53 -45
- package/dist/browser/processors/opmlProcessor.js +5 -2
- package/dist/browser/processors/snap/helpers.js +49 -45
- package/dist/browser/processors/snapProcessor.js +67 -31
- package/dist/browser/processors/touchchatProcessor.js +54 -45
- package/dist/browser/utilities/analytics/reference/index.js +27 -19
- package/dist/browser/utils/io.js +67 -14
- package/dist/browser/utils/sqlite.js +6 -8
- package/dist/browser/utils/zip.js +45 -43
- package/dist/browser/validation/baseValidator.js +5 -0
- package/dist/browser/validation/gridsetValidator.js +12 -20
- package/dist/browser/validation/obfValidator.js +5 -4
- package/dist/browser/validation/snapValidator.js +9 -5
- package/dist/browser/validation/touchChatValidator.js +21 -11
- package/dist/cli/index.js +10 -15
- package/dist/core/baseProcessor.d.ts +7 -7
- package/dist/core/baseProcessor.js +4 -0
- package/dist/processors/applePanelsProcessor.js +29 -36
- package/dist/processors/astericsGridProcessor.js +20 -13
- package/dist/processors/dotProcessor.js +10 -7
- package/dist/processors/excelProcessor.js +9 -12
- package/dist/processors/gridset/helpers.d.ts +9 -11
- package/dist/processors/gridset/helpers.js +49 -71
- package/dist/processors/gridset/imageDebug.d.ts +3 -5
- package/dist/processors/gridset/imageDebug.js +4 -4
- package/dist/processors/gridset/password.d.ts +1 -1
- package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
- package/dist/processors/gridset/symbolExtractor.js +15 -38
- package/dist/processors/gridset/symbolSearch.d.ts +3 -2
- package/dist/processors/gridset/symbolSearch.js +12 -34
- package/dist/processors/gridset/symbols.d.ts +8 -6
- package/dist/processors/gridset/symbols.js +34 -67
- package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
- package/dist/processors/gridset/wordlistHelpers.js +15 -74
- package/dist/processors/gridsetProcessor.js +36 -68
- package/dist/processors/obfProcessor.js +58 -73
- package/dist/processors/obfsetProcessor.js +2 -2
- package/dist/processors/opmlProcessor.js +10 -7
- package/dist/processors/snap/helpers.d.ts +8 -8
- package/dist/processors/snap/helpers.js +50 -72
- package/dist/processors/snapProcessor.js +66 -30
- package/dist/processors/touchchatProcessor.js +54 -45
- package/dist/utilities/analytics/index.d.ts +3 -2
- package/dist/utilities/analytics/index.js +8 -10
- package/dist/utilities/analytics/reference/index.d.ts +5 -3
- package/dist/utilities/analytics/reference/index.js +26 -18
- package/dist/utilities/symbolTools.d.ts +4 -2
- package/dist/utilities/symbolTools.js +16 -15
- package/dist/utils/io.d.ts +24 -6
- package/dist/utils/io.js +64 -14
- package/dist/utils/sqlite.d.ts +2 -0
- package/dist/utils/sqlite.js +6 -8
- package/dist/utils/zip.d.ts +7 -3
- package/dist/utils/zip.js +45 -43
- package/dist/validation/applePanelsValidator.d.ts +2 -1
- package/dist/validation/applePanelsValidator.js +9 -11
- package/dist/validation/astericsValidator.d.ts +2 -1
- package/dist/validation/astericsValidator.js +5 -4
- package/dist/validation/baseValidator.d.ts +2 -2
- package/dist/validation/baseValidator.js +5 -0
- package/dist/validation/dotValidator.d.ts +2 -1
- package/dist/validation/dotValidator.js +5 -4
- package/dist/validation/excelValidator.d.ts +2 -1
- package/dist/validation/excelValidator.js +5 -4
- package/dist/validation/gridsetValidator.d.ts +2 -1
- package/dist/validation/gridsetValidator.js +11 -22
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +5 -4
- package/dist/validation/obfValidator.d.ts +2 -1
- package/dist/validation/obfValidator.js +5 -4
- package/dist/validation/obfsetValidator.d.ts +2 -1
- package/dist/validation/obfsetValidator.js +5 -4
- package/dist/validation/opmlValidator.d.ts +2 -1
- package/dist/validation/opmlValidator.js +5 -4
- package/dist/validation/snapValidator.d.ts +2 -1
- package/dist/validation/snapValidator.js +9 -5
- package/dist/validation/touchChatValidator.d.ts +4 -6
- package/dist/validation/touchChatValidator.js +21 -11
- package/dist/validation/validationTypes.d.ts +8 -1
- package/package.json +1 -1
- package/dist/core/fileProcessor.d.ts +0 -7
- package/dist/core/fileProcessor.js +0 -52
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
*/
|
|
42
42
|
import { AACSemanticCategory } from './treeStructure';
|
|
43
43
|
import { detectCasing, isNumericOrEmpty } from './stringCasing';
|
|
44
|
+
import { defaultFileAdapter } from '../utils/io';
|
|
45
|
+
import { getZipAdapter } from '../utils/zip';
|
|
44
46
|
class BaseProcessor {
|
|
45
47
|
constructor(options = {}) {
|
|
46
48
|
// Default configuration: exclude navigation/system buttons
|
|
@@ -48,6 +50,8 @@ class BaseProcessor {
|
|
|
48
50
|
excludeNavigationButtons: true,
|
|
49
51
|
excludeSystemButtons: true,
|
|
50
52
|
preserveAllButtons: false,
|
|
53
|
+
fileAdapter: defaultFileAdapter,
|
|
54
|
+
zipAdapter: getZipAdapter,
|
|
51
55
|
...options,
|
|
52
56
|
};
|
|
53
57
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { BaseProcessor, } from '../core/baseProcessor';
|
|
2
2
|
import { AACTree, AACPage, AACButton, AACSemanticCategory, AACSemanticIntent, } from '../core/treeStructure';
|
|
3
|
-
// Removed unused import: FileProcessor
|
|
4
3
|
import plist from 'plist';
|
|
5
4
|
import { ValidationFailureError, buildValidationResultFromMessage, } from '../validation/validationTypes';
|
|
6
|
-
import { getBasename
|
|
5
|
+
import { getBasename } from '../utils/io';
|
|
7
6
|
function isNormalizedPanel(panel) {
|
|
8
7
|
return typeof panel.id === 'string';
|
|
9
8
|
}
|
|
@@ -86,17 +85,16 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
86
85
|
return texts;
|
|
87
86
|
}
|
|
88
87
|
async loadIntoTree(filePathOrBuffer) {
|
|
88
|
+
const { readBinaryFromInput, readTextFromInput, pathExists, getFileSize, join } = this.options.fileAdapter;
|
|
89
89
|
await Promise.resolve();
|
|
90
90
|
const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.plist';
|
|
91
91
|
let buffer;
|
|
92
92
|
try {
|
|
93
93
|
if (typeof filePathOrBuffer === 'string') {
|
|
94
|
-
const fs = getFs();
|
|
95
|
-
const path = getPath();
|
|
96
94
|
if (filePathOrBuffer.endsWith('.ascconfig')) {
|
|
97
|
-
const panelDefsPath =
|
|
98
|
-
if (
|
|
99
|
-
buffer =
|
|
95
|
+
const panelDefsPath = join(filePathOrBuffer, 'Contents', 'Resources', 'PanelDefinitions.plist');
|
|
96
|
+
if (pathExists(panelDefsPath)) {
|
|
97
|
+
buffer = readBinaryFromInput(panelDefsPath);
|
|
100
98
|
}
|
|
101
99
|
else {
|
|
102
100
|
const validation = buildValidationResultFromMessage({
|
|
@@ -111,7 +109,7 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
else {
|
|
114
|
-
buffer =
|
|
112
|
+
buffer = readBinaryFromInput(filePathOrBuffer);
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
else {
|
|
@@ -247,8 +245,7 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
247
245
|
filename,
|
|
248
246
|
filesize: typeof filePathOrBuffer === 'string'
|
|
249
247
|
? (() => {
|
|
250
|
-
|
|
251
|
-
return fs.existsSync(filePathOrBuffer) ? fs.statSync(filePathOrBuffer).size : 0;
|
|
248
|
+
return pathExists(filePathOrBuffer) ? getFileSize(filePathOrBuffer) : 0;
|
|
252
249
|
})()
|
|
253
250
|
: readBinaryFromInput(filePathOrBuffer).byteLength,
|
|
254
251
|
format: 'applepanels',
|
|
@@ -260,6 +257,7 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
260
257
|
}
|
|
261
258
|
}
|
|
262
259
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
260
|
+
const { readBinaryFromInput, join } = this.options.fileAdapter;
|
|
263
261
|
// Load the tree, apply translations, and save to new file
|
|
264
262
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
265
263
|
// Apply translations to all text content
|
|
@@ -311,12 +309,12 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
311
309
|
if (outputPath.endsWith('.plist')) {
|
|
312
310
|
return readBinaryFromInput(outputPath);
|
|
313
311
|
}
|
|
314
|
-
const path = getPath();
|
|
315
312
|
const configPath = outputPath.endsWith('.ascconfig') ? outputPath : `${outputPath}.ascconfig`;
|
|
316
|
-
const panelDefsPath =
|
|
313
|
+
const panelDefsPath = join(configPath, 'Contents', 'Resources', 'PanelDefinitions.plist');
|
|
317
314
|
return readBinaryFromInput(panelDefsPath);
|
|
318
315
|
}
|
|
319
316
|
async saveFromTree(tree, outputPath) {
|
|
317
|
+
const { writeTextToPath, pathExists, mkDir, join, dirname } = this.options.fileAdapter;
|
|
320
318
|
await Promise.resolve();
|
|
321
319
|
// Support two output modes:
|
|
322
320
|
// 1) Single-file .plist (PanelDefinitions.plist content written directly)
|
|
@@ -327,17 +325,15 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
327
325
|
let contentsPath = '';
|
|
328
326
|
let resourcesPath = '';
|
|
329
327
|
if (!isSinglePlist) {
|
|
330
|
-
const fs = getFs();
|
|
331
|
-
const path = getPath();
|
|
332
328
|
configPath = outputPath.endsWith('.ascconfig') ? outputPath : `${outputPath}.ascconfig`;
|
|
333
|
-
contentsPath =
|
|
334
|
-
resourcesPath =
|
|
335
|
-
if (!
|
|
336
|
-
|
|
337
|
-
if (!
|
|
338
|
-
|
|
339
|
-
if (!
|
|
340
|
-
|
|
329
|
+
contentsPath = join(configPath, 'Contents');
|
|
330
|
+
resourcesPath = join(contentsPath, 'Resources');
|
|
331
|
+
if (!pathExists(configPath))
|
|
332
|
+
mkDir(configPath, { recursive: true });
|
|
333
|
+
if (!pathExists(contentsPath))
|
|
334
|
+
mkDir(contentsPath, { recursive: true });
|
|
335
|
+
if (!pathExists(resourcesPath))
|
|
336
|
+
mkDir(resourcesPath, { recursive: true });
|
|
341
337
|
// Create Info.plist (bundle mode only)
|
|
342
338
|
const infoPlist = {
|
|
343
339
|
ASCConfigurationDisplayName: tree.metadata?.name || 'AAC Processors Export',
|
|
@@ -353,10 +349,10 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
353
349
|
`Generated by AAC Processors${tree.metadata?.author ? ` - Author: ${tree.metadata.author}` : ''}`,
|
|
354
350
|
};
|
|
355
351
|
const infoPlistContent = plist.build(infoPlist);
|
|
356
|
-
writeTextToPath(
|
|
352
|
+
writeTextToPath(join(contentsPath, 'Info.plist'), infoPlistContent);
|
|
357
353
|
// Create AssetIndex.plist (empty)
|
|
358
354
|
const assetIndexContent = plist.build({});
|
|
359
|
-
writeTextToPath(
|
|
355
|
+
writeTextToPath(join(resourcesPath, 'AssetIndex.plist'), assetIndexContent);
|
|
360
356
|
}
|
|
361
357
|
// Build PanelDefinitions content from tree
|
|
362
358
|
const panelsDict = {};
|
|
@@ -482,17 +478,14 @@ class ApplePanelsProcessor extends BaseProcessor {
|
|
|
482
478
|
const panelDefsContent = plist.build(panelDefinitions);
|
|
483
479
|
if (isSinglePlist) {
|
|
484
480
|
// Write single PanelDefinitions.plist file directly
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (!fs.existsSync(dir))
|
|
489
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
481
|
+
const dir = dirname(outputPath);
|
|
482
|
+
if (!pathExists(dir))
|
|
483
|
+
mkDir(dir, { recursive: true });
|
|
490
484
|
writeTextToPath(outputPath, panelDefsContent);
|
|
491
485
|
}
|
|
492
486
|
else {
|
|
493
487
|
// Write into bundle structure
|
|
494
|
-
|
|
495
|
-
writeTextToPath(path.join(resourcesPath, 'PanelDefinitions.plist'), panelDefsContent);
|
|
488
|
+
writeTextToPath(join(resourcesPath, 'PanelDefinitions.plist'), panelDefsContent);
|
|
496
489
|
}
|
|
497
490
|
}
|
|
498
491
|
createApplePanelsAction(button) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseProcessor, } from '../core/baseProcessor';
|
|
2
2
|
import { AACTree, AACPage, AACButton, AACSemanticCategory, AACSemanticIntent, } from '../core/treeStructure';
|
|
3
3
|
import { ValidationFailureError, buildValidationResultFromMessage, } from '../validation/validationTypes';
|
|
4
|
-
import { getBasename,
|
|
4
|
+
import { getBasename, encodeBase64 } from '../utils/io';
|
|
5
5
|
const DEFAULT_COLOR_SCHEME_DEFINITIONS = [
|
|
6
6
|
{
|
|
7
7
|
name: 'CS_MODIFIED_FITZGERALD_KEY_VERY_LIGHT',
|
|
@@ -571,6 +571,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
571
571
|
return texts;
|
|
572
572
|
}
|
|
573
573
|
extractRawTexts(filePathOrBuffer) {
|
|
574
|
+
const { readTextFromInput } = this.options.fileAdapter;
|
|
574
575
|
let content = readTextFromInput(filePathOrBuffer);
|
|
575
576
|
// Remove BOM if present
|
|
576
577
|
if (content.charCodeAt(0) === 0xfeff) {
|
|
@@ -651,6 +652,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
651
652
|
}
|
|
652
653
|
}
|
|
653
654
|
async loadIntoTree(filePathOrBuffer) {
|
|
655
|
+
const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
|
|
654
656
|
await Promise.resolve();
|
|
655
657
|
const tree = new AACTree();
|
|
656
658
|
const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.grd';
|
|
@@ -1049,6 +1051,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
1049
1051
|
});
|
|
1050
1052
|
}
|
|
1051
1053
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
1054
|
+
const { readTextFromInput, readBinaryFromInput, writeTextToPath } = this.options.fileAdapter;
|
|
1052
1055
|
await Promise.resolve();
|
|
1053
1056
|
let content = readTextFromInput(filePathOrBuffer);
|
|
1054
1057
|
// Remove BOM if present
|
|
@@ -1172,6 +1175,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
1172
1175
|
}
|
|
1173
1176
|
}
|
|
1174
1177
|
async saveFromTree(tree, outputPath) {
|
|
1178
|
+
const { writeTextToPath } = this.options.fileAdapter;
|
|
1175
1179
|
await Promise.resolve();
|
|
1176
1180
|
// Use default Asterics Grid styling instead of taking from first page
|
|
1177
1181
|
// This prevents issues where the first page has unusual colors (like purple)
|
|
@@ -1381,6 +1385,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
1381
1385
|
* Add audio recording to a specific grid element
|
|
1382
1386
|
*/
|
|
1383
1387
|
addAudioToElement(filePath, elementId, audioData, metadata) {
|
|
1388
|
+
const { readTextFromInput, writeTextToPath } = this.options.fileAdapter;
|
|
1384
1389
|
let content = readTextFromInput(filePath);
|
|
1385
1390
|
// Remove BOM if present
|
|
1386
1391
|
if (content.charCodeAt(0) === 0xfeff) {
|
|
@@ -1430,9 +1435,9 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
1430
1435
|
* Create a copy of the grid file with audio recordings added
|
|
1431
1436
|
*/
|
|
1432
1437
|
createAudioEnhancedGridFile(sourceFilePath, targetFilePath, audioMappings) {
|
|
1438
|
+
const { writeBinaryToPath, readBinaryFromInput } = this.options.fileAdapter;
|
|
1433
1439
|
// Copy the source file to target
|
|
1434
|
-
|
|
1435
|
-
fs.copyFileSync(sourceFilePath, targetFilePath);
|
|
1440
|
+
writeBinaryToPath(targetFilePath, readBinaryFromInput(sourceFilePath));
|
|
1436
1441
|
// Add audio recordings to the copy
|
|
1437
1442
|
audioMappings.forEach((audioInfo, elementId) => {
|
|
1438
1443
|
try {
|
|
@@ -1448,6 +1453,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
1448
1453
|
* Extract all element IDs from the grid file for audio mapping
|
|
1449
1454
|
*/
|
|
1450
1455
|
getElementIds(filePathOrBuffer) {
|
|
1456
|
+
const { readTextFromInput } = this.options.fileAdapter;
|
|
1451
1457
|
let content = readTextFromInput(filePathOrBuffer);
|
|
1452
1458
|
// Remove BOM if present
|
|
1453
1459
|
if (content.charCodeAt(0) === 0xfeff) {
|
|
@@ -1471,6 +1477,7 @@ class AstericsGridProcessor extends BaseProcessor {
|
|
|
1471
1477
|
* Check if an element has audio recording
|
|
1472
1478
|
*/
|
|
1473
1479
|
hasAudioRecording(filePathOrBuffer, elementId) {
|
|
1480
|
+
const { readTextFromInput } = this.options.fileAdapter;
|
|
1474
1481
|
let content = readTextFromInput(filePathOrBuffer);
|
|
1475
1482
|
// Remove BOM if present
|
|
1476
1483
|
if (content.charCodeAt(0) === 0xfeff) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { BaseProcessor, } from '../core/baseProcessor';
|
|
2
2
|
import { AACTree, AACPage, AACButton, AACSemanticIntent } from '../core/treeStructure';
|
|
3
|
-
// Removed unused import: FileProcessor
|
|
4
3
|
import { ValidationFailureError, buildValidationResultFromMessage, } from '../validation/validationTypes';
|
|
5
|
-
import { getBasename,
|
|
4
|
+
import { getBasename, encodeText } from '../utils/io';
|
|
6
5
|
class DotProcessor extends BaseProcessor {
|
|
7
6
|
constructor(options) {
|
|
8
7
|
super(options);
|
|
@@ -49,6 +48,7 @@ class DotProcessor extends BaseProcessor {
|
|
|
49
48
|
return { nodes: Array.from(nodes.values()), edges };
|
|
50
49
|
}
|
|
51
50
|
async extractTexts(filePathOrBuffer) {
|
|
51
|
+
const { readTextFromInput } = this.options.fileAdapter;
|
|
52
52
|
await Promise.resolve();
|
|
53
53
|
const content = readTextFromInput(filePathOrBuffer);
|
|
54
54
|
const { nodes, edges } = this.parseDotFile(content);
|
|
@@ -66,6 +66,7 @@ class DotProcessor extends BaseProcessor {
|
|
|
66
66
|
return texts;
|
|
67
67
|
}
|
|
68
68
|
async loadIntoTree(filePathOrBuffer) {
|
|
69
|
+
const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
|
|
69
70
|
await Promise.resolve();
|
|
70
71
|
const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.dot';
|
|
71
72
|
const buffer = readBinaryFromInput(filePathOrBuffer);
|
|
@@ -155,6 +156,7 @@ class DotProcessor extends BaseProcessor {
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
159
|
+
const { readTextFromInput, writeBinaryToPath } = this.options.fileAdapter;
|
|
158
160
|
await Promise.resolve();
|
|
159
161
|
const content = readTextFromInput(filePathOrBuffer);
|
|
160
162
|
let translatedContent = content;
|
|
@@ -171,6 +173,7 @@ class DotProcessor extends BaseProcessor {
|
|
|
171
173
|
return resultBuffer;
|
|
172
174
|
}
|
|
173
175
|
async saveFromTree(tree, _outputPath) {
|
|
176
|
+
const { writeTextToPath } = this.options.fileAdapter;
|
|
174
177
|
await Promise.resolve();
|
|
175
178
|
let dotContent = `digraph "${tree.metadata?.name || 'AACBoard'}" {\n`;
|
|
176
179
|
// Helper to escape DOT string
|
|
@@ -0,0 +1,354 @@
|
|
|
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
|
+
* CSS color names to RGB values
|
|
12
|
+
* Supports 147 standard CSS color names
|
|
13
|
+
*/
|
|
14
|
+
const CSS_COLORS = {
|
|
15
|
+
aliceblue: [240, 248, 255],
|
|
16
|
+
antiquewhite: [250, 235, 215],
|
|
17
|
+
aqua: [0, 255, 255],
|
|
18
|
+
aquamarine: [127, 255, 212],
|
|
19
|
+
azure: [240, 255, 255],
|
|
20
|
+
beige: [245, 245, 220],
|
|
21
|
+
bisque: [255, 228, 196],
|
|
22
|
+
black: [0, 0, 0],
|
|
23
|
+
blanchedalmond: [255, 235, 205],
|
|
24
|
+
blue: [0, 0, 255],
|
|
25
|
+
blueviolet: [138, 43, 226],
|
|
26
|
+
brown: [165, 42, 42],
|
|
27
|
+
burlywood: [222, 184, 135],
|
|
28
|
+
cadetblue: [95, 158, 160],
|
|
29
|
+
chartreuse: [127, 255, 0],
|
|
30
|
+
chocolate: [210, 105, 30],
|
|
31
|
+
coral: [255, 127, 80],
|
|
32
|
+
cornflowerblue: [100, 149, 237],
|
|
33
|
+
cornsilk: [255, 248, 220],
|
|
34
|
+
crimson: [220, 20, 60],
|
|
35
|
+
cyan: [0, 255, 255],
|
|
36
|
+
darkblue: [0, 0, 139],
|
|
37
|
+
darkcyan: [0, 139, 139],
|
|
38
|
+
darkgoldenrod: [184, 134, 11],
|
|
39
|
+
darkgray: [169, 169, 169],
|
|
40
|
+
darkgreen: [0, 100, 0],
|
|
41
|
+
darkgrey: [169, 169, 169],
|
|
42
|
+
darkkhaki: [189, 183, 107],
|
|
43
|
+
darkmagenta: [139, 0, 139],
|
|
44
|
+
darkolivegreen: [85, 107, 47],
|
|
45
|
+
darkorange: [255, 140, 0],
|
|
46
|
+
darkorchid: [153, 50, 204],
|
|
47
|
+
darkred: [139, 0, 0],
|
|
48
|
+
darksalmon: [233, 150, 122],
|
|
49
|
+
darkseagreen: [143, 188, 143],
|
|
50
|
+
darkslateblue: [72, 61, 139],
|
|
51
|
+
darkslategray: [47, 79, 79],
|
|
52
|
+
darkslategrey: [47, 79, 79],
|
|
53
|
+
darkturquoise: [0, 206, 209],
|
|
54
|
+
darkviolet: [148, 0, 211],
|
|
55
|
+
deeppink: [255, 20, 147],
|
|
56
|
+
deepskyblue: [0, 191, 255],
|
|
57
|
+
dimgray: [105, 105, 105],
|
|
58
|
+
dimgrey: [105, 105, 105],
|
|
59
|
+
dodgerblue: [30, 144, 255],
|
|
60
|
+
firebrick: [178, 34, 34],
|
|
61
|
+
floralwhite: [255, 250, 240],
|
|
62
|
+
forestgreen: [34, 139, 34],
|
|
63
|
+
fuchsia: [255, 0, 255],
|
|
64
|
+
gainsboro: [220, 220, 220],
|
|
65
|
+
ghostwhite: [248, 248, 255],
|
|
66
|
+
gold: [255, 215, 0],
|
|
67
|
+
goldenrod: [218, 165, 32],
|
|
68
|
+
gray: [128, 128, 128],
|
|
69
|
+
grey: [128, 128, 128],
|
|
70
|
+
green: [0, 128, 0],
|
|
71
|
+
greenyellow: [173, 255, 47],
|
|
72
|
+
honeydew: [240, 255, 240],
|
|
73
|
+
hotpink: [255, 105, 180],
|
|
74
|
+
indianred: [205, 92, 92],
|
|
75
|
+
indigo: [75, 0, 130],
|
|
76
|
+
ivory: [255, 255, 240],
|
|
77
|
+
khaki: [240, 230, 140],
|
|
78
|
+
lavender: [230, 230, 250],
|
|
79
|
+
lavenderblush: [255, 240, 245],
|
|
80
|
+
lawngreen: [124, 252, 0],
|
|
81
|
+
lemonchiffon: [255, 250, 205],
|
|
82
|
+
lightblue: [173, 216, 230],
|
|
83
|
+
lightcoral: [240, 128, 128],
|
|
84
|
+
lightcyan: [224, 255, 255],
|
|
85
|
+
lightgoldenrodyellow: [250, 250, 210],
|
|
86
|
+
lightgray: [211, 211, 211],
|
|
87
|
+
lightgreen: [144, 238, 144],
|
|
88
|
+
lightgrey: [211, 211, 211],
|
|
89
|
+
lightpink: [255, 182, 193],
|
|
90
|
+
lightsalmon: [255, 160, 122],
|
|
91
|
+
lightseagreen: [32, 178, 170],
|
|
92
|
+
lightskyblue: [135, 206, 250],
|
|
93
|
+
lightslategray: [119, 136, 153],
|
|
94
|
+
lightslategrey: [119, 136, 153],
|
|
95
|
+
lightsteelblue: [176, 196, 222],
|
|
96
|
+
lightyellow: [255, 255, 224],
|
|
97
|
+
lime: [0, 255, 0],
|
|
98
|
+
limegreen: [50, 205, 50],
|
|
99
|
+
linen: [250, 240, 230],
|
|
100
|
+
magenta: [255, 0, 255],
|
|
101
|
+
maroon: [128, 0, 0],
|
|
102
|
+
mediumaquamarine: [102, 205, 170],
|
|
103
|
+
mediumblue: [0, 0, 205],
|
|
104
|
+
mediumorchid: [186, 85, 211],
|
|
105
|
+
mediumpurple: [147, 112, 219],
|
|
106
|
+
mediumseagreen: [60, 179, 113],
|
|
107
|
+
mediumslateblue: [123, 104, 238],
|
|
108
|
+
mediumspringgreen: [0, 250, 154],
|
|
109
|
+
mediumturquoise: [72, 209, 204],
|
|
110
|
+
mediumvioletred: [199, 21, 133],
|
|
111
|
+
midnightblue: [25, 25, 112],
|
|
112
|
+
mintcream: [245, 255, 250],
|
|
113
|
+
mistyrose: [255, 228, 225],
|
|
114
|
+
moccasin: [255, 228, 181],
|
|
115
|
+
navajowhite: [255, 222, 173],
|
|
116
|
+
navy: [0, 0, 128],
|
|
117
|
+
oldlace: [253, 245, 230],
|
|
118
|
+
olive: [128, 128, 0],
|
|
119
|
+
olivedrab: [107, 142, 35],
|
|
120
|
+
orange: [255, 165, 0],
|
|
121
|
+
orangered: [255, 69, 0],
|
|
122
|
+
orchid: [218, 112, 214],
|
|
123
|
+
palegoldenrod: [238, 232, 170],
|
|
124
|
+
palegreen: [152, 251, 152],
|
|
125
|
+
paleturquoise: [175, 238, 238],
|
|
126
|
+
palevioletred: [219, 112, 147],
|
|
127
|
+
papayawhip: [255, 239, 213],
|
|
128
|
+
peachpuff: [255, 218, 185],
|
|
129
|
+
peru: [205, 133, 63],
|
|
130
|
+
pink: [255, 192, 203],
|
|
131
|
+
plum: [221, 160, 221],
|
|
132
|
+
powderblue: [176, 224, 230],
|
|
133
|
+
purple: [128, 0, 128],
|
|
134
|
+
rebeccapurple: [102, 51, 153],
|
|
135
|
+
red: [255, 0, 0],
|
|
136
|
+
rosybrown: [188, 143, 143],
|
|
137
|
+
royalblue: [65, 105, 225],
|
|
138
|
+
saddlebrown: [139, 69, 19],
|
|
139
|
+
salmon: [250, 128, 114],
|
|
140
|
+
sandybrown: [244, 164, 96],
|
|
141
|
+
seagreen: [46, 139, 87],
|
|
142
|
+
seashell: [255, 245, 238],
|
|
143
|
+
sienna: [160, 82, 45],
|
|
144
|
+
silver: [192, 192, 192],
|
|
145
|
+
skyblue: [135, 206, 235],
|
|
146
|
+
slateblue: [106, 90, 205],
|
|
147
|
+
slategray: [112, 128, 144],
|
|
148
|
+
slategrey: [112, 128, 144],
|
|
149
|
+
snow: [255, 250, 250],
|
|
150
|
+
springgreen: [0, 255, 127],
|
|
151
|
+
steelblue: [70, 130, 180],
|
|
152
|
+
tan: [210, 180, 140],
|
|
153
|
+
teal: [0, 128, 128],
|
|
154
|
+
thistle: [216, 191, 216],
|
|
155
|
+
tomato: [255, 99, 71],
|
|
156
|
+
turquoise: [64, 224, 208],
|
|
157
|
+
violet: [238, 130, 238],
|
|
158
|
+
wheat: [245, 222, 179],
|
|
159
|
+
white: [255, 255, 255],
|
|
160
|
+
whitesmoke: [245, 245, 245],
|
|
161
|
+
yellow: [255, 255, 0],
|
|
162
|
+
yellowgreen: [154, 205, 50],
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Get RGB values for a CSS color name
|
|
166
|
+
* @param name - CSS color name (case-insensitive)
|
|
167
|
+
* @returns RGB tuple [r, g, b] or undefined if not found
|
|
168
|
+
*/
|
|
169
|
+
export function getNamedColor(name) {
|
|
170
|
+
const color = CSS_COLORS[name.toLowerCase()];
|
|
171
|
+
return color;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Convert RGBA values to hex format
|
|
175
|
+
* @param r - Red channel (0-255)
|
|
176
|
+
* @param g - Green channel (0-255)
|
|
177
|
+
* @param b - Blue channel (0-255)
|
|
178
|
+
* @param a - Alpha channel (0-1)
|
|
179
|
+
* @returns Hex color string in format #RRGGBBAA
|
|
180
|
+
*/
|
|
181
|
+
export function rgbaToHex(r, g, b, a) {
|
|
182
|
+
const red = channelToHex(r);
|
|
183
|
+
const green = channelToHex(g);
|
|
184
|
+
const blue = channelToHex(b);
|
|
185
|
+
const alpha = channelToHex(Math.round(a * 255));
|
|
186
|
+
return `#${red}${green}${blue}${alpha}`;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Convert a single color channel value to hex
|
|
190
|
+
* @param value - Channel value (0-255)
|
|
191
|
+
* @returns Two-digit hex string
|
|
192
|
+
*/
|
|
193
|
+
export function channelToHex(value) {
|
|
194
|
+
const clamped = Math.max(0, Math.min(255, Math.round(value)));
|
|
195
|
+
return clamped.toString(16).padStart(2, '0').toUpperCase();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clamp RGB channel value to valid range
|
|
199
|
+
* @param value - Channel value
|
|
200
|
+
* @returns Clamped value (0-255)
|
|
201
|
+
*/
|
|
202
|
+
export function clampColorChannel(value) {
|
|
203
|
+
if (Number.isNaN(value)) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
return Math.max(0, Math.min(255, value));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Clamp alpha value to valid range
|
|
210
|
+
* @param value - Alpha value
|
|
211
|
+
* @returns Clamped value (0-1)
|
|
212
|
+
*/
|
|
213
|
+
export function clampAlpha(value) {
|
|
214
|
+
if (Number.isNaN(value)) {
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
return Math.max(0, Math.min(1, value));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Convert any color format to hex
|
|
221
|
+
* Supports: hex (#RGB, #RRGGBB, #RRGGBBAA), RGB/RGBA, and CSS color names
|
|
222
|
+
* @param value - Color string in any supported format
|
|
223
|
+
* @returns Hex color string (#RRGGBBAA) or undefined if invalid
|
|
224
|
+
*/
|
|
225
|
+
export function toHexColor(value) {
|
|
226
|
+
// Try hex format
|
|
227
|
+
const hexMatch = value.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);
|
|
228
|
+
if (hexMatch) {
|
|
229
|
+
const hex = hexMatch[1];
|
|
230
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
231
|
+
return `#${hex
|
|
232
|
+
.split('')
|
|
233
|
+
.map((char) => char + char)
|
|
234
|
+
.join('')}`;
|
|
235
|
+
}
|
|
236
|
+
return `#${hex}`;
|
|
237
|
+
}
|
|
238
|
+
// Try RGB/RGBA format
|
|
239
|
+
const rgbMatch = value.match(/^rgba?\((.+)\)$/i);
|
|
240
|
+
if (rgbMatch) {
|
|
241
|
+
const parts = rgbMatch[1]
|
|
242
|
+
.split(',')
|
|
243
|
+
.map((part) => part.trim())
|
|
244
|
+
.filter(Boolean);
|
|
245
|
+
if (parts.length === 3 || parts.length === 4) {
|
|
246
|
+
const [r, g, b, a] = parts;
|
|
247
|
+
const red = clampColorChannel(parseFloat(r));
|
|
248
|
+
const green = clampColorChannel(parseFloat(g));
|
|
249
|
+
const blue = clampColorChannel(parseFloat(b));
|
|
250
|
+
const alpha = parts.length === 4 ? clampAlpha(parseFloat(a)) : 1;
|
|
251
|
+
return rgbaToHex(red, green, blue, alpha);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Try CSS color name
|
|
255
|
+
const rgb = getNamedColor(value);
|
|
256
|
+
if (rgb) {
|
|
257
|
+
return rgbaToHex(rgb[0], rgb[1], rgb[2], 1);
|
|
258
|
+
}
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Darken a hex color by a specified amount
|
|
263
|
+
* @param hex - Hex color string
|
|
264
|
+
* @param amount - Amount to darken (0-255)
|
|
265
|
+
* @returns Darkened hex color
|
|
266
|
+
*/
|
|
267
|
+
export function darkenColor(hex, amount) {
|
|
268
|
+
const normalized = ensureAlphaChannel(hex).substring(1); // strip #
|
|
269
|
+
const rgb = normalized.substring(0, 6);
|
|
270
|
+
const alpha = normalized.substring(6) || 'FF';
|
|
271
|
+
const r = parseInt(rgb.substring(0, 2), 16);
|
|
272
|
+
const g = parseInt(rgb.substring(2, 4), 16);
|
|
273
|
+
const b = parseInt(rgb.substring(4, 6), 16);
|
|
274
|
+
const clamp = (val) => Math.max(0, Math.min(255, val));
|
|
275
|
+
const newR = clamp(r - amount);
|
|
276
|
+
const newG = clamp(g - amount);
|
|
277
|
+
const newB = clamp(b - amount);
|
|
278
|
+
return `#${channelToHex(newR)}${channelToHex(newG)}${channelToHex(newB)}${alpha.toUpperCase()}`;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Lighten a hex color by a specified amount
|
|
282
|
+
* @param hex - Hex color string
|
|
283
|
+
* @param amount - Amount to lighten (0-255)
|
|
284
|
+
* @returns Lightened hex color
|
|
285
|
+
*/
|
|
286
|
+
export function lightenColor(hex, amount) {
|
|
287
|
+
const normalized = ensureAlphaChannel(hex).substring(1); // strip #
|
|
288
|
+
const rgb = normalized.substring(0, 6);
|
|
289
|
+
const alpha = normalized.substring(6) || 'FF';
|
|
290
|
+
const r = parseInt(rgb.substring(0, 2), 16);
|
|
291
|
+
const g = parseInt(rgb.substring(2, 4), 16);
|
|
292
|
+
const b = parseInt(rgb.substring(4, 6), 16);
|
|
293
|
+
const clamp = (val) => Math.max(0, Math.min(255, val));
|
|
294
|
+
const newR = clamp(r + amount);
|
|
295
|
+
const newG = clamp(g + amount);
|
|
296
|
+
const newB = clamp(b + amount);
|
|
297
|
+
return `#${channelToHex(newR)}${channelToHex(newG)}${channelToHex(newB)}${alpha.toUpperCase()}`;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Convert hex color to RGBA object
|
|
301
|
+
* @param hex - Hex color string (#RRGGBB or #RRGGBBAA)
|
|
302
|
+
* @returns RGBA object with r, g, b, a properties (0-1 for alpha)
|
|
303
|
+
*/
|
|
304
|
+
export function hexToRgba(hex) {
|
|
305
|
+
const normalized = ensureAlphaChannel(hex).substring(1); // strip #
|
|
306
|
+
const rgb = normalized.substring(0, 6);
|
|
307
|
+
const alphaHex = normalized.substring(6) || 'FF';
|
|
308
|
+
const r = parseInt(rgb.substring(0, 2), 16);
|
|
309
|
+
const g = parseInt(rgb.substring(2, 4), 16);
|
|
310
|
+
const b = parseInt(rgb.substring(4, 6), 16);
|
|
311
|
+
const a = parseInt(alphaHex, 16) / 255;
|
|
312
|
+
return { r, g, b, a };
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Normalize any color format to Grid3's 8-digit hex format
|
|
316
|
+
* @param input - Color string in any supported format
|
|
317
|
+
* @param fallback - Fallback color if input is invalid (default: white)
|
|
318
|
+
* @returns Normalized color in format #AARRGGBBFF
|
|
319
|
+
*/
|
|
320
|
+
export function normalizeColor(input, fallback = '#FFFFFFFF') {
|
|
321
|
+
const trimmed = input.trim();
|
|
322
|
+
if (!trimmed) {
|
|
323
|
+
return fallback;
|
|
324
|
+
}
|
|
325
|
+
const hex = toHexColor(trimmed);
|
|
326
|
+
if (hex) {
|
|
327
|
+
return ensureAlphaChannel(hex).toUpperCase();
|
|
328
|
+
}
|
|
329
|
+
return fallback;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
|
|
333
|
+
* @param color - Color string (hex format)
|
|
334
|
+
* @returns Color with alpha channel in format #AARRGGBBFF
|
|
335
|
+
*/
|
|
336
|
+
export function ensureAlphaChannel(color) {
|
|
337
|
+
if (!color)
|
|
338
|
+
return '#FFFFFFFF';
|
|
339
|
+
// If already 8 digits (with alpha), return as is
|
|
340
|
+
if (color.match(/^#[0-9A-Fa-f]{8}$/))
|
|
341
|
+
return color;
|
|
342
|
+
// If 6 digits (no alpha), add FF for fully opaque
|
|
343
|
+
if (color.match(/^#[0-9A-Fa-f]{6}$/))
|
|
344
|
+
return color + 'FF';
|
|
345
|
+
// If 3 digits (shorthand), expand to 8
|
|
346
|
+
if (color.match(/^#[0-9A-Fa-f]{3}$/)) {
|
|
347
|
+
const r = color[1];
|
|
348
|
+
const g = color[2];
|
|
349
|
+
const b = color[3];
|
|
350
|
+
return `#${r}${r}${g}${g}${b}${b}FF`;
|
|
351
|
+
}
|
|
352
|
+
// Invalid or unknown format, return white
|
|
353
|
+
return '#FFFFFFFF';
|
|
354
|
+
}
|