saturon 0.2.2 → 0.3.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.
- package/LICENSE +1 -1
- package/README.md +5 -4
- package/dist/Color.d.ts +58 -46
- package/dist/Color.js +144 -221
- package/dist/converters.d.ts +39 -145
- package/dist/converters.js +12 -432
- package/dist/engine.d.ts +148 -0
- package/dist/engine.js +1900 -0
- package/dist/index.umd.js +1800 -1312
- package/dist/index.umd.min.js +143 -1
- package/dist/math.d.ts +3 -12
- package/dist/math.js +12 -21
- package/dist/tests/Color.test.d.ts +17 -0
- package/dist/tests/Color.test.js +1062 -0
- package/dist/tests/wpt.test.d.ts +1 -0
- package/dist/tests/wpt.test.js +2129 -0
- package/dist/types.d.ts +66 -76
- package/dist/utils.d.ts +10 -141
- package/dist/utils.js +111 -1059
- package/package.json +10 -11
package/dist/index.umd.js
CHANGED
|
@@ -126,53 +126,32 @@ var Saturon = (() => {
|
|
|
126
126
|
return product.map((x) => x[0]);
|
|
127
127
|
return product;
|
|
128
128
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
expression += input[i];
|
|
139
|
-
i++;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (input[i] === "(") {
|
|
143
|
-
expression += "(";
|
|
144
|
-
i++;
|
|
145
|
-
depth = 1;
|
|
146
|
-
while (i < input.length && depth > 0) {
|
|
147
|
-
const char = input[i];
|
|
148
|
-
if (char === "(")
|
|
149
|
-
depth++;
|
|
150
|
-
else if (char === ")")
|
|
151
|
-
depth--;
|
|
152
|
-
if (depth > 0)
|
|
153
|
-
expression += char;
|
|
154
|
-
i++;
|
|
155
|
-
}
|
|
156
|
-
expression += ")";
|
|
157
|
-
}
|
|
158
|
-
return { expression, end: i };
|
|
129
|
+
function normalize(component, value) {
|
|
130
|
+
const [min2, max2] = Array.isArray(value) ? value : value === "hue" ? [0, 360] : [0, 100];
|
|
131
|
+
if (Number.isNaN(component))
|
|
132
|
+
return 0;
|
|
133
|
+
if (component === Infinity)
|
|
134
|
+
return max2;
|
|
135
|
+
if (component === -Infinity)
|
|
136
|
+
return min2;
|
|
137
|
+
return typeof component === "number" ? component : 0;
|
|
159
138
|
}
|
|
160
139
|
function fit(coords, model, options = {}) {
|
|
161
140
|
const { method = config.defaults.fit, precision } = options;
|
|
162
141
|
const { components } = colorModels[model];
|
|
163
|
-
const
|
|
142
|
+
const defs = Object.values(components).reduce((arr, props) => (arr[props.index] = props, arr), []);
|
|
164
143
|
let clipped;
|
|
165
|
-
if (method === "none")
|
|
144
|
+
if (method === "none")
|
|
166
145
|
clipped = coords;
|
|
167
|
-
|
|
168
|
-
clipped = coords.
|
|
169
|
-
const prop =
|
|
146
|
+
else if (method === "clip") {
|
|
147
|
+
clipped = coords.map((v, i) => {
|
|
148
|
+
const prop = defs[i];
|
|
170
149
|
if (!prop)
|
|
171
150
|
throw new Error(`Missing component properties for index ${i}.`);
|
|
172
|
-
if (prop.value === "
|
|
151
|
+
if (prop.value === "hue")
|
|
173
152
|
return (v % 360 + 360) % 360;
|
|
174
|
-
const [
|
|
175
|
-
return Math.min(
|
|
153
|
+
const [min2, max2] = Array.isArray(prop.value) ? prop.value : [0, 100];
|
|
154
|
+
return Math.min(max2, Math.max(min2, v));
|
|
176
155
|
});
|
|
177
156
|
} else {
|
|
178
157
|
const fn = fitMethods[method];
|
|
@@ -181,631 +160,26 @@ var Saturon = (() => {
|
|
|
181
160
|
}
|
|
182
161
|
clipped = fn(coords, model);
|
|
183
162
|
}
|
|
184
|
-
return clipped.
|
|
163
|
+
return clipped.map((v, i) => {
|
|
185
164
|
let p;
|
|
186
165
|
if (typeof precision === "number" || precision === null)
|
|
187
166
|
p = precision;
|
|
188
167
|
else if (typeof precision === "undefined")
|
|
189
|
-
p =
|
|
168
|
+
p = defs[i]?.precision ?? 3;
|
|
190
169
|
else
|
|
191
170
|
throw new TypeError(`Invalid precision value: ${precision}.`);
|
|
192
171
|
return p === null ? v : Number(v.toFixed(p));
|
|
193
172
|
});
|
|
194
173
|
}
|
|
195
|
-
function modelConverterToColorConverter(name, converter) {
|
|
196
|
-
const evaluateComponent = (token, value, base = {}, commaSeparated = false, relative = false) => {
|
|
197
|
-
const parsePercent = (str, min2, max2) => {
|
|
198
|
-
const percent = parseFloat(str);
|
|
199
|
-
if (isNaN(percent))
|
|
200
|
-
throw new Error(`Invalid percentage value: '${str}'.`);
|
|
201
|
-
if (value === "percentage")
|
|
202
|
-
return percent;
|
|
203
|
-
if (min2 < 0 && max2 > 0)
|
|
204
|
-
return percent / 100 * (max2 - min2) / 2;
|
|
205
|
-
return percent / 100 * (max2 - min2) + min2;
|
|
206
|
-
};
|
|
207
|
-
const parseAngle = (token2) => {
|
|
208
|
-
const value2 = parseFloat(token2);
|
|
209
|
-
if (isNaN(value2))
|
|
210
|
-
throw new Error(`Invalid angle value: '${token2}'.`);
|
|
211
|
-
if (token2.slice(-3) === "deg")
|
|
212
|
-
return value2;
|
|
213
|
-
if (token2.slice(-3) === "rad")
|
|
214
|
-
return value2 * (180 / pi);
|
|
215
|
-
if (token2.slice(-4) === "grad")
|
|
216
|
-
return value2 * 0.9;
|
|
217
|
-
if (token2.slice(-4) === "turn")
|
|
218
|
-
return value2 * 360;
|
|
219
|
-
return value2;
|
|
220
|
-
};
|
|
221
|
-
const parseCalc = (token2, _min, _max) => {
|
|
222
|
-
const tokenize2 = (s) => {
|
|
223
|
-
const out = [];
|
|
224
|
-
for (let i = 0; i < s.length; ) {
|
|
225
|
-
const c = s[i];
|
|
226
|
-
if (/\s/.test(c)) {
|
|
227
|
-
i++;
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
if (s.slice(i, i + 2) === "**") {
|
|
231
|
-
out.push({ type: "operator", value: "**" });
|
|
232
|
-
i += 2;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (/[0-9]/.test(c) || c === "." && /[0-9]/.test(s[i + 1] || "")) {
|
|
236
|
-
let num = "";
|
|
237
|
-
while (i < s.length && /[0-9.]/.test(s[i]))
|
|
238
|
-
num += s[i++];
|
|
239
|
-
if (i < s.length && /[eE]/.test(s[i])) {
|
|
240
|
-
num += s[i++];
|
|
241
|
-
if (/[+-]/.test(s[i]))
|
|
242
|
-
num += s[i++];
|
|
243
|
-
while (i < s.length && /[0-9]/.test(s[i]))
|
|
244
|
-
num += s[i++];
|
|
245
|
-
}
|
|
246
|
-
out.push({ type: "number", value: parseFloat(num) });
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
if (/[a-zA-Z_]/.test(c)) {
|
|
250
|
-
let id = "";
|
|
251
|
-
while (i < s.length && /[a-zA-Z0-9_]/.test(s[i]))
|
|
252
|
-
id += s[i++];
|
|
253
|
-
out.push({ type: "identifier", value: id });
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if ("+-*/%(),".includes(c)) {
|
|
257
|
-
out.push({ type: "operator", value: c });
|
|
258
|
-
i++;
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
throw new Error(`Unexpected character: ${c}`);
|
|
262
|
-
}
|
|
263
|
-
return out;
|
|
264
|
-
};
|
|
265
|
-
const parse = (tokens) => {
|
|
266
|
-
let pos = 0;
|
|
267
|
-
const cur = () => pos < tokens.length ? tokens[pos] : null;
|
|
268
|
-
const nxt = () => {
|
|
269
|
-
if (pos >= tokens.length)
|
|
270
|
-
throw new Error("Unexpected end of input");
|
|
271
|
-
return tokens[pos++];
|
|
272
|
-
};
|
|
273
|
-
const expect = (v) => {
|
|
274
|
-
const t = cur();
|
|
275
|
-
if (!t || t.value !== v)
|
|
276
|
-
throw new Error(`Expected "${v}" but got "${t ? t.value : "end of input"}`);
|
|
277
|
-
nxt();
|
|
278
|
-
};
|
|
279
|
-
const parsePrimary = () => {
|
|
280
|
-
const t = cur();
|
|
281
|
-
if (!t)
|
|
282
|
-
throw new Error("Unexpected end of input");
|
|
283
|
-
if (t.type === "number") {
|
|
284
|
-
nxt();
|
|
285
|
-
return { type: "number", value: t.value };
|
|
286
|
-
}
|
|
287
|
-
if (t.type === "identifier") {
|
|
288
|
-
nxt();
|
|
289
|
-
if (cur() && cur().value === "(") {
|
|
290
|
-
nxt();
|
|
291
|
-
const args = [];
|
|
292
|
-
if (cur() && cur().value !== ")") {
|
|
293
|
-
args.push(parseAdd());
|
|
294
|
-
while (cur() && cur().value === ",") {
|
|
295
|
-
nxt();
|
|
296
|
-
args.push(parseAdd());
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
expect(")");
|
|
300
|
-
return { type: "call", func: t.value, args };
|
|
301
|
-
}
|
|
302
|
-
return { type: "var", name: t.value };
|
|
303
|
-
}
|
|
304
|
-
if (t.value === "(") {
|
|
305
|
-
nxt();
|
|
306
|
-
const e2 = parseAdd();
|
|
307
|
-
expect(")");
|
|
308
|
-
return e2;
|
|
309
|
-
}
|
|
310
|
-
throw new Error(`Unexpected token: ${t.value}`);
|
|
311
|
-
};
|
|
312
|
-
const parseUnary = () => {
|
|
313
|
-
if (cur() && (cur().value === "+" || cur().value === "-")) {
|
|
314
|
-
const op = nxt().value;
|
|
315
|
-
return { type: "unary", op, arg: parseUnary() };
|
|
316
|
-
}
|
|
317
|
-
return parsePrimary();
|
|
318
|
-
};
|
|
319
|
-
const parsePower = () => {
|
|
320
|
-
let left = parseUnary();
|
|
321
|
-
while (cur() && cur().value === "**") {
|
|
322
|
-
const op = nxt().value;
|
|
323
|
-
left = { type: "binary", op, left, right: parseUnary() };
|
|
324
|
-
}
|
|
325
|
-
return left;
|
|
326
|
-
};
|
|
327
|
-
const parseMul = () => {
|
|
328
|
-
let left = parsePower();
|
|
329
|
-
while (cur() && ["*", "/", "%"].includes(String(cur().value))) {
|
|
330
|
-
const op = nxt().value;
|
|
331
|
-
left = { type: "binary", op, left, right: parsePower() };
|
|
332
|
-
}
|
|
333
|
-
return left;
|
|
334
|
-
};
|
|
335
|
-
const parseAdd = () => {
|
|
336
|
-
let left = parseMul();
|
|
337
|
-
while (cur() && (cur().value === "+" || cur().value === "-")) {
|
|
338
|
-
const op = nxt().value;
|
|
339
|
-
left = { type: "binary", op, left, right: parseMul() };
|
|
340
|
-
}
|
|
341
|
-
return left;
|
|
342
|
-
};
|
|
343
|
-
const ast = parseAdd();
|
|
344
|
-
if (pos < tokens.length)
|
|
345
|
-
throw new Error(`Extra tokens after expression: ${tokens.slice(pos).map((t) => t.value).join(" ")}`);
|
|
346
|
-
return ast;
|
|
347
|
-
};
|
|
348
|
-
const evaluate = (ast, env) => {
|
|
349
|
-
switch (ast.type) {
|
|
350
|
-
case "number":
|
|
351
|
-
return ast.value;
|
|
352
|
-
case "var": {
|
|
353
|
-
const v = env[ast.name];
|
|
354
|
-
if (v === void 0)
|
|
355
|
-
throw new Error(`Unknown variable: ${ast.name}`);
|
|
356
|
-
if (typeof v === "function")
|
|
357
|
-
throw new Error(`Expected variable but found function: ${ast.name}`);
|
|
358
|
-
return v;
|
|
359
|
-
}
|
|
360
|
-
case "binary": {
|
|
361
|
-
const L = evaluate(ast.left, env), R = evaluate(ast.right, env);
|
|
362
|
-
switch (ast.op) {
|
|
363
|
-
case "+":
|
|
364
|
-
return L + R;
|
|
365
|
-
case "-":
|
|
366
|
-
return L - R;
|
|
367
|
-
case "*":
|
|
368
|
-
return L * R;
|
|
369
|
-
case "/":
|
|
370
|
-
return L / R;
|
|
371
|
-
case "%":
|
|
372
|
-
return L % R;
|
|
373
|
-
case "**":
|
|
374
|
-
return L ** R;
|
|
375
|
-
default:
|
|
376
|
-
throw new Error(`Unknown binary operator: ${ast.op}`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
case "unary": {
|
|
380
|
-
const v = evaluate(ast.arg, env);
|
|
381
|
-
switch (ast.op) {
|
|
382
|
-
case "+":
|
|
383
|
-
return +v;
|
|
384
|
-
case "-":
|
|
385
|
-
return -v;
|
|
386
|
-
default:
|
|
387
|
-
throw new Error(`Unknown unary operator: ${ast.op}`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
case "call": {
|
|
391
|
-
const fn = env[ast.func];
|
|
392
|
-
if (typeof fn !== "function")
|
|
393
|
-
throw new Error(`Unknown function: ${ast.func}`);
|
|
394
|
-
return fn(...ast.args.map((a) => evaluate(a, env)));
|
|
395
|
-
}
|
|
396
|
-
default: {
|
|
397
|
-
throw new Error(`Unknown AST node type: ${ast.type}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
let inner = token2.slice(5, -1).trim();
|
|
402
|
-
if (inner === "infinity")
|
|
403
|
-
return _max;
|
|
404
|
-
if (inner === "-infinity")
|
|
405
|
-
return _min;
|
|
406
|
-
if (inner === "NaN")
|
|
407
|
-
return 0;
|
|
408
|
-
inner = inner.replace(/(\d+(\.\d+)?)%/g, (m) => {
|
|
409
|
-
if (relative === true)
|
|
410
|
-
throw new Error("<angle> and <percentage> values are converted to <number> in relative syntax.");
|
|
411
|
-
const r = parsePercent(m, _min, _max);
|
|
412
|
-
return r !== void 0 ? String(r) : "0";
|
|
413
|
-
});
|
|
414
|
-
inner = inner.replace(/(\d+(\.\d+)?)(deg|rad|grad|turn)/g, (_, num, __, unit) => {
|
|
415
|
-
if (relative === true)
|
|
416
|
-
throw new Error("<angle> and <percentage> values are converted to <number> in relative syntax.");
|
|
417
|
-
return String(parseAngle(`${parseFloat(num)}${unit}`));
|
|
418
|
-
});
|
|
419
|
-
const caclEnv = {
|
|
420
|
-
...base,
|
|
421
|
-
pi,
|
|
422
|
-
e,
|
|
423
|
-
tau: pi * 2,
|
|
424
|
-
pow,
|
|
425
|
-
sqrt,
|
|
426
|
-
sin,
|
|
427
|
-
cos,
|
|
428
|
-
tan,
|
|
429
|
-
asin,
|
|
430
|
-
acos,
|
|
431
|
-
atan,
|
|
432
|
-
atan2,
|
|
433
|
-
exp,
|
|
434
|
-
log,
|
|
435
|
-
log10,
|
|
436
|
-
log2,
|
|
437
|
-
abs,
|
|
438
|
-
min,
|
|
439
|
-
max,
|
|
440
|
-
hypot,
|
|
441
|
-
round,
|
|
442
|
-
ceil,
|
|
443
|
-
floor,
|
|
444
|
-
sign,
|
|
445
|
-
trunc,
|
|
446
|
-
random
|
|
447
|
-
};
|
|
448
|
-
try {
|
|
449
|
-
const tokens = tokenize2(inner);
|
|
450
|
-
const ast = parse(tokens);
|
|
451
|
-
const result = evaluate(ast, caclEnv);
|
|
452
|
-
return result;
|
|
453
|
-
} catch (err) {
|
|
454
|
-
throw new Error(`Evaluation error: ${err}`);
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
const evaluateAngle = () => {
|
|
458
|
-
if (/^-?(?:\d+|\d*\.\d+)(?:deg|rad|grad|turn)$/.test(token)) {
|
|
459
|
-
return parseAngle(token);
|
|
460
|
-
}
|
|
461
|
-
if (/^-?(?:\d+|\d*\.\d+)$/.test(token)) {
|
|
462
|
-
return parseFloat(token);
|
|
463
|
-
}
|
|
464
|
-
const [min2, max2] = [0, 360];
|
|
465
|
-
if (token[token.length - 1] === "%") {
|
|
466
|
-
if (commaSeparated && supportsLegacy === true) {
|
|
467
|
-
throw new Error("The legacy color syntax does not allow percentages for <angle> components.");
|
|
468
|
-
}
|
|
469
|
-
if (relative === true) {
|
|
470
|
-
throw new Error("The relative color syntax doesn't allow percentages for <angle> components.");
|
|
471
|
-
}
|
|
472
|
-
return parsePercent(token, min2, max2);
|
|
473
|
-
}
|
|
474
|
-
if (token.slice(0, 5) === "calc(") {
|
|
475
|
-
return parseCalc(token, min2, max2);
|
|
476
|
-
}
|
|
477
|
-
throw new Error(`Invalid angle value: '${token}'. Must be a number, a number with a unit (deg, rad, grad, turn), or a percentage.`);
|
|
478
|
-
};
|
|
479
|
-
const evaluatePercent = () => {
|
|
480
|
-
if (/^-?(?:\d+|\d*\.\d+)$/.test(token)) {
|
|
481
|
-
if (commaSeparated && supportsLegacy === true) {
|
|
482
|
-
throw new Error("The legacy color syntax does not allow numbers for <percentage> components.");
|
|
483
|
-
}
|
|
484
|
-
return parseFloat(token);
|
|
485
|
-
}
|
|
486
|
-
const [min2, max2] = [0, 100];
|
|
487
|
-
if (token[token.length - 1] === "%") {
|
|
488
|
-
return parsePercent(token, min2, max2);
|
|
489
|
-
}
|
|
490
|
-
if (token.slice(0, 5) === "calc(") {
|
|
491
|
-
return parseCalc(token, min2, max2);
|
|
492
|
-
}
|
|
493
|
-
throw new Error(`Invalid percentage value: '${token}'. Must be a percentage or a number.`);
|
|
494
|
-
};
|
|
495
|
-
const evaluateNumber = () => {
|
|
496
|
-
if (/^-?(?:\d+|\d*\.\d+)$/.test(token)) {
|
|
497
|
-
return parseFloat(token);
|
|
498
|
-
}
|
|
499
|
-
const [min2, max2] = value;
|
|
500
|
-
if (token[token.length - 1] === "%") {
|
|
501
|
-
return parsePercent(token, min2, max2);
|
|
502
|
-
}
|
|
503
|
-
if (token.slice(0, 5) === "calc(") {
|
|
504
|
-
return parseCalc(token, min2, max2);
|
|
505
|
-
}
|
|
506
|
-
throw new Error(`Invalid number value: '${token}'. Must be a number${relative === false ? " or a percentage" : ""}.`);
|
|
507
|
-
};
|
|
508
|
-
if (token === "none")
|
|
509
|
-
return 0;
|
|
510
|
-
if (token in base)
|
|
511
|
-
return base[token];
|
|
512
|
-
if (value === "angle")
|
|
513
|
-
return evaluateAngle();
|
|
514
|
-
if (value === "percentage")
|
|
515
|
-
return evaluatePercent();
|
|
516
|
-
if (Array.isArray(value))
|
|
517
|
-
return evaluateNumber();
|
|
518
|
-
throw new Error(`Unable to parse component token: ${token}`);
|
|
519
|
-
};
|
|
520
|
-
const parseAST = (ast) => {
|
|
521
|
-
const { fn, space, fromOrigin, c1, c2, c3, alpha, commaSeparated } = ast;
|
|
522
|
-
const { components: components2, supportsLegacy: supportsLegacy2 } = converter;
|
|
523
|
-
components2.alpha = { index: 3, value: [0, 1], precision: 3 };
|
|
524
|
-
if (commaSeparated && supportsLegacy2 !== true) {
|
|
525
|
-
throw new Error(`<${fn}()> does not support comma-separated syntax.`);
|
|
526
|
-
}
|
|
527
|
-
const sorted = Object.entries(components2).sort((a, b) => a[1].index - b[1].index);
|
|
528
|
-
if (fromOrigin) {
|
|
529
|
-
let colorSpace;
|
|
530
|
-
if (fn === "color") {
|
|
531
|
-
colorSpace = space;
|
|
532
|
-
} else if (fn in colorModels) {
|
|
533
|
-
colorSpace = fn;
|
|
534
|
-
} else {
|
|
535
|
-
for (const model in colorModels) {
|
|
536
|
-
if (colorModels[model].alphaVariant === fn) {
|
|
537
|
-
colorSpace = model;
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
const originComponents = Color.from(fromOrigin).in(colorSpace).toObject({ fit: "none", precision: null });
|
|
543
|
-
const evaluatedComponents = [c1, c2, c3, alpha].map((token, i) => {
|
|
544
|
-
const [, meta] = sorted[i];
|
|
545
|
-
return evaluateComponent(token, meta.value, originComponents, commaSeparated, true);
|
|
546
|
-
});
|
|
547
|
-
return evaluatedComponents.slice(0, 4);
|
|
548
|
-
} else {
|
|
549
|
-
const result = [];
|
|
550
|
-
const percentFlags = [];
|
|
551
|
-
const tokens = [c1, c2, c3, alpha];
|
|
552
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
553
|
-
const [, meta] = sorted[i];
|
|
554
|
-
const token = tokens[i];
|
|
555
|
-
if (commaSeparated && token === "none") {
|
|
556
|
-
throw new Error(`${fn}() cannot use "none" in comma-separated syntax.`);
|
|
557
|
-
}
|
|
558
|
-
if (meta.index !== 3 && meta.value !== "angle" && meta.value !== "percentage" && token.slice(0, 5) !== "calc(") {
|
|
559
|
-
percentFlags.push(token.trim()[token.length - 1] === "%");
|
|
560
|
-
}
|
|
561
|
-
if (token) {
|
|
562
|
-
const value = evaluateComponent(token, meta.value, {}, commaSeparated);
|
|
563
|
-
result[meta.index] = value;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (commaSeparated && percentFlags.length > 1) {
|
|
567
|
-
const allPercent = percentFlags.every(Boolean);
|
|
568
|
-
const nonePercent = percentFlags.every((f) => !f);
|
|
569
|
-
if (!allPercent && !nonePercent) {
|
|
570
|
-
throw new Error(`${fn}()'s <number> components must all be numbers or all percentages.`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return result.slice(0, 4);
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
const getAST = (tokens) => {
|
|
577
|
-
const getAlpha = (index, separator = "/") => {
|
|
578
|
-
if (tokens[index] !== void 0) {
|
|
579
|
-
if (tokens[index] === separator) {
|
|
580
|
-
return { value: tokens[index + 1], hasAlpha: true };
|
|
581
|
-
}
|
|
582
|
-
throw new Error("Invalid alpha separator");
|
|
583
|
-
}
|
|
584
|
-
return { value: "1", hasAlpha: false };
|
|
585
|
-
};
|
|
586
|
-
let fn, space, fromOrigin, c1, c2, c3, alpha, commaSeparated = false, expectedLength;
|
|
587
|
-
if (tokens[0] === "color") {
|
|
588
|
-
fn = "color";
|
|
589
|
-
if (tokens[1] === "from") {
|
|
590
|
-
space = tokens[3];
|
|
591
|
-
fromOrigin = tokens[2];
|
|
592
|
-
c1 = tokens[4];
|
|
593
|
-
c2 = tokens[5];
|
|
594
|
-
c3 = tokens[6];
|
|
595
|
-
const { value, hasAlpha } = getAlpha(7);
|
|
596
|
-
expectedLength = hasAlpha ? 9 : 7;
|
|
597
|
-
alpha = value;
|
|
598
|
-
} else {
|
|
599
|
-
space = tokens[1];
|
|
600
|
-
fromOrigin = null;
|
|
601
|
-
c1 = tokens[2];
|
|
602
|
-
c2 = tokens[3];
|
|
603
|
-
c3 = tokens[4];
|
|
604
|
-
const { value, hasAlpha } = getAlpha(5);
|
|
605
|
-
expectedLength = hasAlpha ? 7 : 5;
|
|
606
|
-
alpha = value;
|
|
607
|
-
}
|
|
608
|
-
} else {
|
|
609
|
-
fn = tokens[0];
|
|
610
|
-
space = null;
|
|
611
|
-
if (tokens[1] === "from") {
|
|
612
|
-
fromOrigin = tokens[2];
|
|
613
|
-
c1 = tokens[3];
|
|
614
|
-
c2 = tokens[4];
|
|
615
|
-
c3 = tokens[5];
|
|
616
|
-
const { value, hasAlpha } = getAlpha(6);
|
|
617
|
-
expectedLength = hasAlpha ? 8 : 6;
|
|
618
|
-
alpha = value;
|
|
619
|
-
} else {
|
|
620
|
-
fromOrigin = null;
|
|
621
|
-
c1 = tokens[1];
|
|
622
|
-
if (tokens[2] === "," && tokens[4] === ",") {
|
|
623
|
-
commaSeparated = true;
|
|
624
|
-
c2 = tokens[3];
|
|
625
|
-
c3 = tokens[5];
|
|
626
|
-
const { value, hasAlpha } = getAlpha(6, ",");
|
|
627
|
-
expectedLength = hasAlpha ? 8 : 6;
|
|
628
|
-
if (hasAlpha && tokens[6] !== ",") {
|
|
629
|
-
throw new Error("Comma optional syntax requires no commas at all.");
|
|
630
|
-
}
|
|
631
|
-
alpha = value;
|
|
632
|
-
} else {
|
|
633
|
-
c2 = tokens[2];
|
|
634
|
-
c3 = tokens[3];
|
|
635
|
-
const { value, hasAlpha } = getAlpha(4);
|
|
636
|
-
expectedLength = hasAlpha ? 6 : 4;
|
|
637
|
-
alpha = value;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
if (tokens.length !== expectedLength) {
|
|
642
|
-
throw new Error(`Invalid number of tokens for ${fn}(): expected ${expectedLength} but got ${tokens.length}.`);
|
|
643
|
-
}
|
|
644
|
-
return { fn, space, fromOrigin, c1, c2, c3, alpha, commaSeparated };
|
|
645
|
-
};
|
|
646
|
-
const tokenize = (str) => {
|
|
647
|
-
const tokens = [];
|
|
648
|
-
let i = 0;
|
|
649
|
-
let funcName = "";
|
|
650
|
-
while (i < str.length && str[i] !== "(") {
|
|
651
|
-
funcName += str[i];
|
|
652
|
-
i++;
|
|
653
|
-
}
|
|
654
|
-
funcName = funcName.trim();
|
|
655
|
-
tokens.push(funcName);
|
|
656
|
-
const innerStart = str.indexOf("(") + 1;
|
|
657
|
-
const innerStr = str.slice(innerStart, -1).trim();
|
|
658
|
-
i = 0;
|
|
659
|
-
if (innerStr.slice(0, 5) === "from ") {
|
|
660
|
-
tokens.push("from");
|
|
661
|
-
i += 5;
|
|
662
|
-
while (i < innerStr.length && innerStr[i] === " ")
|
|
663
|
-
i++;
|
|
664
|
-
const colorStart = i;
|
|
665
|
-
while (i < innerStr.length && innerStr[i] !== " ")
|
|
666
|
-
i++;
|
|
667
|
-
const colorStr = innerStr.slice(colorStart, i);
|
|
668
|
-
if (colorStr.includes("(")) {
|
|
669
|
-
const { expression, end } = extractBalancedExpression(innerStr, colorStart);
|
|
670
|
-
tokens.push(expression);
|
|
671
|
-
i = end;
|
|
672
|
-
} else {
|
|
673
|
-
tokens.push(colorStr);
|
|
674
|
-
}
|
|
675
|
-
while (i < innerStr.length && innerStr[i] === " ")
|
|
676
|
-
i++;
|
|
677
|
-
}
|
|
678
|
-
if (tokens[0] === "color" && i < innerStr.length) {
|
|
679
|
-
const spaceStart = i;
|
|
680
|
-
while (i < innerStr.length && innerStr[i] !== " ")
|
|
681
|
-
i++;
|
|
682
|
-
tokens.push(innerStr.slice(spaceStart, i));
|
|
683
|
-
while (i < innerStr.length && innerStr[i] === " ")
|
|
684
|
-
i++;
|
|
685
|
-
}
|
|
686
|
-
while (i < innerStr.length) {
|
|
687
|
-
const char = innerStr[i];
|
|
688
|
-
if (char === ",") {
|
|
689
|
-
tokens.push(",");
|
|
690
|
-
i++;
|
|
691
|
-
if (innerStr[i] === " ")
|
|
692
|
-
i++;
|
|
693
|
-
} else if (char === "/") {
|
|
694
|
-
tokens.push("/");
|
|
695
|
-
i++;
|
|
696
|
-
if (innerStr[i] === " ")
|
|
697
|
-
i++;
|
|
698
|
-
} else if (char === " ") {
|
|
699
|
-
i++;
|
|
700
|
-
} else if (/[a-zA-Z#]/.test(char)) {
|
|
701
|
-
const identStart = i;
|
|
702
|
-
let ident = "";
|
|
703
|
-
while (i < innerStr.length && /[a-zA-Z0-9-%#]/.test(innerStr[i])) {
|
|
704
|
-
ident += innerStr[i];
|
|
705
|
-
i++;
|
|
706
|
-
}
|
|
707
|
-
if (i < innerStr.length && innerStr[i] === "(") {
|
|
708
|
-
const { expression, end } = extractBalancedExpression(innerStr, identStart);
|
|
709
|
-
tokens.push(expression);
|
|
710
|
-
i = end;
|
|
711
|
-
} else {
|
|
712
|
-
tokens.push(ident);
|
|
713
|
-
}
|
|
714
|
-
} else if (/[\d.-]/.test(char)) {
|
|
715
|
-
let num = "";
|
|
716
|
-
while (i < innerStr.length && /[\d.eE+-]/.test(innerStr[i])) {
|
|
717
|
-
num += innerStr[i];
|
|
718
|
-
i++;
|
|
719
|
-
}
|
|
720
|
-
if (i < innerStr.length && innerStr[i] === "%") {
|
|
721
|
-
num += "%";
|
|
722
|
-
i++;
|
|
723
|
-
tokens.push(num);
|
|
724
|
-
} else if (i < innerStr.length && /[a-zA-Z]/.test(innerStr[i])) {
|
|
725
|
-
let unit = "";
|
|
726
|
-
while (i < innerStr.length && /[a-zA-Z]/.test(innerStr[i])) {
|
|
727
|
-
unit += innerStr[i];
|
|
728
|
-
i++;
|
|
729
|
-
}
|
|
730
|
-
tokens.push(num + unit);
|
|
731
|
-
} else {
|
|
732
|
-
tokens.push(num);
|
|
733
|
-
}
|
|
734
|
-
} else {
|
|
735
|
-
throw new Error(`Unexpected character: ${char}`);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
return tokens;
|
|
739
|
-
};
|
|
740
|
-
const validateRelativeColorSpace = (str, name2) => {
|
|
741
|
-
const prefix = "color(from ";
|
|
742
|
-
if (str.slice(0, 11) !== prefix || str[str.length - 1] !== ")") {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
const innerStr = str.slice(prefix.length, -1).trim();
|
|
746
|
-
const { expression, end } = extractBalancedExpression(innerStr, 0);
|
|
747
|
-
if (!expression) {
|
|
748
|
-
return false;
|
|
749
|
-
}
|
|
750
|
-
const rest = innerStr.slice(end).trim();
|
|
751
|
-
const parts = rest.split(/\s+/);
|
|
752
|
-
if (parts.length < 1) {
|
|
753
|
-
return false;
|
|
754
|
-
}
|
|
755
|
-
const colorSpace = parts[0];
|
|
756
|
-
return colorSpace === name2;
|
|
757
|
-
};
|
|
758
|
-
const { components, bridge, fromBridge, toBridge, alphaVariant, supportsLegacy } = converter;
|
|
759
|
-
const { PI: pi, E: e, pow, sqrt, sin, cos, tan, asin, acos, atan, atan2, exp, log, log10, log2, abs, min, max, hypot, round, ceil, floor, sign, trunc, random } = Math;
|
|
760
|
-
return {
|
|
761
|
-
isValid: (str) => {
|
|
762
|
-
const { alphaVariant: alphaVariant2 = name } = converter;
|
|
763
|
-
if (name in colorSpaces) {
|
|
764
|
-
const startsWithColor = str.slice(0, `color(${name} `.length) === `color(${name} `;
|
|
765
|
-
const startsWithFrom = str.slice(0, "color(from".length) === "color(from" && validateRelativeColorSpace(str, name);
|
|
766
|
-
return (startsWithColor || startsWithFrom) && str[str.length - 1] === ")";
|
|
767
|
-
}
|
|
768
|
-
return (str.slice(0, `${name}(`.length) === `${name}(` || str.slice(0, `${alphaVariant2}(`.length) === `${alphaVariant2}(`) && str[str.length - 1] === ")";
|
|
769
|
-
},
|
|
770
|
-
bridge,
|
|
771
|
-
toBridge: (coords) => [...toBridge(coords.slice(0, 3)), coords[3] ?? 1],
|
|
772
|
-
parse: (str) => {
|
|
773
|
-
const tokens = tokenize(str);
|
|
774
|
-
const ast = getAST(tokens);
|
|
775
|
-
const components2 = parseAST(ast);
|
|
776
|
-
return [...components2.slice(0, 3), components2[3] ?? 1];
|
|
777
|
-
},
|
|
778
|
-
fromBridge: (coords) => [...fromBridge(coords), coords[3] ?? 1],
|
|
779
|
-
format: ([c1, c2, c3, a = 1], options = {}) => {
|
|
780
|
-
const { legacy = false, fit: fitMethod = config.defaults.fit, precision, units = false } = options;
|
|
781
|
-
const clipped = fit([c1, c2, c3], name, { method: fitMethod, precision });
|
|
782
|
-
const alpha = Number(min(max(a, 0), 1).toFixed(3)).toString();
|
|
783
|
-
const formatted = clipped.map((v, index) => {
|
|
784
|
-
if ((units || legacy) && components) {
|
|
785
|
-
const def = Object.values(components).find((comp) => comp.index === index);
|
|
786
|
-
if (def?.value === "percentage")
|
|
787
|
-
return `${v}%`;
|
|
788
|
-
if (def?.value === "angle" && units)
|
|
789
|
-
return `${v}deg`;
|
|
790
|
-
}
|
|
791
|
-
return v.toString();
|
|
792
|
-
});
|
|
793
|
-
if (name in colorSpaces) {
|
|
794
|
-
return `color(${name} ${formatted.join(" ")}${a !== 1 ? ` / ${alpha}` : ""})`;
|
|
795
|
-
}
|
|
796
|
-
if (legacy && supportsLegacy) {
|
|
797
|
-
return a === 1 ? `${name}(${formatted.join(", ")})` : `${alphaVariant || name}(${formatted.join(", ")}, ${alpha})`;
|
|
798
|
-
}
|
|
799
|
-
return `${name}(${formatted.join(" ")}${a !== 1 ? ` / ${alpha}` : ""})`;
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
174
|
function spaceConverterToModelConverter(name, converter) {
|
|
804
175
|
const { fromLinear = (c) => c, toLinear = (c) => c, toBridgeMatrix, fromBridgeMatrix } = converter;
|
|
805
176
|
return {
|
|
806
177
|
supportsLegacy: false,
|
|
807
178
|
targetGamut: converter.targetGamut === null ? null : name,
|
|
808
|
-
components:
|
|
179
|
+
components: {
|
|
180
|
+
...Object.fromEntries(converter.components.map((comp, index) => [comp, { index, value: [0, 1], precision: 5 }])),
|
|
181
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
182
|
+
},
|
|
809
183
|
bridge: converter.bridge,
|
|
810
184
|
toBridge: (coords) => {
|
|
811
185
|
return multiplyMatrices(toBridgeMatrix, coords.map((c) => toLinear(c)));
|
|
@@ -813,8 +187,91 @@ var Saturon = (() => {
|
|
|
813
187
|
fromBridge: (coords) => multiplyMatrices(fromBridgeMatrix, coords).map((c) => fromLinear(c))
|
|
814
188
|
};
|
|
815
189
|
}
|
|
190
|
+
function hueDelta(a, b) {
|
|
191
|
+
const d = ((b - a) % 360 + 360) % 360;
|
|
192
|
+
return d > 180 ? d - 360 : d;
|
|
193
|
+
}
|
|
194
|
+
function hueDeltaLong(a, b) {
|
|
195
|
+
const d = hueDelta(a, b);
|
|
196
|
+
return d >= 0 ? d - 360 : d + 360;
|
|
197
|
+
}
|
|
198
|
+
function interpHue(a, b, t, method) {
|
|
199
|
+
if (Number.isNaN(a) || Number.isNaN(b))
|
|
200
|
+
return NaN;
|
|
201
|
+
switch (method) {
|
|
202
|
+
case "shorter":
|
|
203
|
+
return ((a + t * hueDelta(a, b)) % 360 + 360) % 360;
|
|
204
|
+
case "longer":
|
|
205
|
+
return ((a + t * hueDeltaLong(a, b)) % 360 + 360) % 360;
|
|
206
|
+
case "increasing":
|
|
207
|
+
return ((a * (1 - t) + (b < a ? b + 360 : b) * t) % 360 + 360) % 360;
|
|
208
|
+
case "decreasing":
|
|
209
|
+
return ((a * (1 - t) + (b > a ? b - 360 : b) * t) % 360 + 360) % 360;
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`Invalid hue interpolation: ${method}`);
|
|
212
|
+
}
|
|
213
|
+
function mixTwo(colorA, colorB, t, model = "oklab", hue = "shorter") {
|
|
214
|
+
const A = colorA.in(model).coords.slice();
|
|
215
|
+
const B = colorB.in(model).coords.slice();
|
|
216
|
+
const { components } = colorModels[model];
|
|
217
|
+
if (!components)
|
|
218
|
+
throw new Error(`Model ${model} does not have defined components.`);
|
|
219
|
+
const norm = (v) => Array.isArray(v) ? v : v === "hue" ? [0, 360] : [0, 100];
|
|
220
|
+
const repair = (a, b, v) => {
|
|
221
|
+
const [min2, max2] = norm(v);
|
|
222
|
+
const fixInf = (x) => x === Infinity ? max2 : x === -Infinity ? min2 : x;
|
|
223
|
+
a = fixInf(a);
|
|
224
|
+
b = fixInf(b);
|
|
225
|
+
const aNaN = Number.isNaN(a);
|
|
226
|
+
const bNaN = Number.isNaN(b);
|
|
227
|
+
if (aNaN && bNaN)
|
|
228
|
+
return { a: NaN, b: NaN };
|
|
229
|
+
if (aNaN)
|
|
230
|
+
return { a: b, b };
|
|
231
|
+
if (bNaN)
|
|
232
|
+
return { a, b: a };
|
|
233
|
+
return { a, b, ok: true };
|
|
234
|
+
};
|
|
235
|
+
const hueIndex = components.h?.index ?? -1;
|
|
236
|
+
for (const key in components) {
|
|
237
|
+
const { index, value } = components[key];
|
|
238
|
+
if (index > 3)
|
|
239
|
+
continue;
|
|
240
|
+
const r = repair(A[index], B[index], value);
|
|
241
|
+
if (r.ok) {
|
|
242
|
+
const [min2, max2] = norm(value);
|
|
243
|
+
const fixInf = (x) => x === Infinity ? max2 : x === -Infinity ? min2 : x;
|
|
244
|
+
A[index] = fixInf(A[index]);
|
|
245
|
+
B[index] = fixInf(B[index]);
|
|
246
|
+
} else {
|
|
247
|
+
A[index] = r.a;
|
|
248
|
+
B[index] = r.b;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
t = Math.min(1, Math.max(0, t));
|
|
252
|
+
if (t === 0)
|
|
253
|
+
return new Color(model, [...A]);
|
|
254
|
+
if (t === 1)
|
|
255
|
+
return new Color(model, [...B]);
|
|
256
|
+
const aA = A[3] ?? 1;
|
|
257
|
+
const aB = B[3] ?? 1;
|
|
258
|
+
const resolvedA = A.slice(0, 3);
|
|
259
|
+
const resolvedB = B.slice(0, 3);
|
|
260
|
+
if (aA < 1 || aB < 1) {
|
|
261
|
+
const premixed = resolvedA.map((a2, i) => {
|
|
262
|
+
const b = resolvedB[i];
|
|
263
|
+
return i === hueIndex ? interpHue(a2, b, t, hue) : a2 * aA * (1 - t) + b * aB * t;
|
|
264
|
+
});
|
|
265
|
+
const a = aA * (1 - t) + aB * t;
|
|
266
|
+
const out = a > 0 ? premixed.map((v, i) => i === hueIndex ? v : v / a) : resolvedA.map((_, i) => i === hueIndex ? premixed[i] : 0);
|
|
267
|
+
return new Color(model, [...out, a]);
|
|
268
|
+
}
|
|
269
|
+
const mixed = resolvedA.map((a, i) => i === hueIndex ? interpHue(a, resolvedB[i], t, hue) : a + (resolvedB[i] - a) * t);
|
|
270
|
+
return new Color(model, [...mixed, 1]);
|
|
271
|
+
}
|
|
816
272
|
|
|
817
273
|
// dist/math.js
|
|
274
|
+
var EPSILON = 1e-5;
|
|
818
275
|
var MATRICES = {
|
|
819
276
|
D50_to_D65: [
|
|
820
277
|
[0.955473421488075, -0.02309845494876471, 0.06325924320057072],
|
|
@@ -897,15 +354,6 @@ var Saturon = (() => {
|
|
|
897
354
|
[1, -0.0894841775298119, -1.2914855480194092]
|
|
898
355
|
]
|
|
899
356
|
};
|
|
900
|
-
var EASINGS = {
|
|
901
|
-
linear: (t) => t,
|
|
902
|
-
"ease-in": (t) => t * t,
|
|
903
|
-
"ease-out": (t) => t * (2 - t),
|
|
904
|
-
"ease-in-out": (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
905
|
-
"ease-in-cubic": (t) => t * t * t,
|
|
906
|
-
"ease-out-cubic": (t) => --t * t * t + 1,
|
|
907
|
-
"ease-in-out-cubic": (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
|
|
908
|
-
};
|
|
909
357
|
var fitMethods = {
|
|
910
358
|
"chroma-reduction": (coords, model) => {
|
|
911
359
|
const color = new Color(model, coords);
|
|
@@ -921,7 +369,7 @@ var Saturon = (() => {
|
|
|
921
369
|
let C_low = 0;
|
|
922
370
|
let C_high = 1;
|
|
923
371
|
const epsilon = 1e-6;
|
|
924
|
-
let clipped
|
|
372
|
+
let clipped;
|
|
925
373
|
while (C_high - C_low > epsilon) {
|
|
926
374
|
const C_mid = (C_low + C_high) / 2;
|
|
927
375
|
const candidate_color = new Color("oklch", [L_clipped, C_mid, H]);
|
|
@@ -970,14 +418,14 @@ var Saturon = (() => {
|
|
|
970
418
|
const E = current.deltaEOK(initialClippedColor);
|
|
971
419
|
if (E < JND)
|
|
972
420
|
return clipped;
|
|
973
|
-
let
|
|
974
|
-
let
|
|
421
|
+
let min2 = 0;
|
|
422
|
+
let max2 = C;
|
|
975
423
|
let min_inGamut = true;
|
|
976
|
-
while (
|
|
977
|
-
const chroma = (
|
|
424
|
+
while (max2 - min2 > epsilon) {
|
|
425
|
+
const chroma = (min2 + max2) / 2;
|
|
978
426
|
const candidate = new Color("oklch", [L, chroma, H]);
|
|
979
427
|
if (min_inGamut && candidate.inGamut(targetGamut, 1e-5))
|
|
980
|
-
|
|
428
|
+
min2 = chroma;
|
|
981
429
|
else {
|
|
982
430
|
const clippedCoords = fit(candidate.in(model).toArray({ fit: "none", precision: null }).slice(0, 3), model, { method: "clip" });
|
|
983
431
|
clipped = clippedCoords;
|
|
@@ -988,10 +436,10 @@ var Saturon = (() => {
|
|
|
988
436
|
return clipped;
|
|
989
437
|
else {
|
|
990
438
|
min_inGamut = false;
|
|
991
|
-
|
|
439
|
+
min2 = chroma;
|
|
992
440
|
}
|
|
993
441
|
} else
|
|
994
|
-
|
|
442
|
+
max2 = chroma;
|
|
995
443
|
}
|
|
996
444
|
}
|
|
997
445
|
return clipped;
|
|
@@ -1000,16 +448,16 @@ var Saturon = (() => {
|
|
|
1000
448
|
function RGB_to_XYZD65(rgb) {
|
|
1001
449
|
const lin_sRGB = rgb.map((v) => {
|
|
1002
450
|
const n = v / 255;
|
|
1003
|
-
const
|
|
1004
|
-
return
|
|
451
|
+
const sign2 = n < 0 ? -1 : 1, abs2 = Math.abs(n);
|
|
452
|
+
return abs2 <= 0.04045 ? n / 12.92 : sign2 * ((abs2 + 0.055) / 1.055) ** 2.4;
|
|
1005
453
|
});
|
|
1006
454
|
return multiplyMatrices(MATRICES.SRGB_to_XYZD65, lin_sRGB);
|
|
1007
455
|
}
|
|
1008
456
|
function XYZD65_to_RGB(xyz) {
|
|
1009
457
|
const linRGB = multiplyMatrices(MATRICES.XYZD65_to_SRGB, xyz);
|
|
1010
458
|
const gammaRGB = linRGB.map((v) => {
|
|
1011
|
-
const
|
|
1012
|
-
return
|
|
459
|
+
const sign2 = v < 0 ? -1 : 1, abs2 = Math.abs(v);
|
|
460
|
+
return abs2 > 31308e-7 ? sign2 * (1.055 * abs2 ** (1 / 2.4) - 0.055) : 12.92 * v;
|
|
1013
461
|
});
|
|
1014
462
|
return gammaRGB.map((v) => v * 255);
|
|
1015
463
|
}
|
|
@@ -1025,16 +473,18 @@ var Saturon = (() => {
|
|
|
1025
473
|
}
|
|
1026
474
|
function RGB_to_HSL(rgb) {
|
|
1027
475
|
const [r, g, b] = rgb.map((v) => v / 255);
|
|
1028
|
-
const
|
|
1029
|
-
const L = (
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
if (d
|
|
476
|
+
const max2 = Math.max(r, g, b), min2 = Math.min(r, g, b);
|
|
477
|
+
const L = (max2 + min2) / 2;
|
|
478
|
+
const d = max2 - min2;
|
|
479
|
+
let H = NaN, S = 0;
|
|
480
|
+
if (d > 0) {
|
|
1033
481
|
S = d / (1 - Math.abs(2 * L - 1));
|
|
1034
|
-
H =
|
|
1035
|
-
H
|
|
482
|
+
H = max2 === r ? (g - b) / d + (g < b ? 6 : 0) : max2 === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
483
|
+
H = H * 60 % 360;
|
|
1036
484
|
}
|
|
1037
|
-
|
|
485
|
+
const S_out = S * 100, L_out = L * 100;
|
|
486
|
+
const invalid = L_out <= EPSILON || 100 - L_out <= EPSILON || S_out <= EPSILON;
|
|
487
|
+
return [invalid ? NaN : H, S_out, L_out];
|
|
1038
488
|
}
|
|
1039
489
|
function HWB_to_RGB([h, w, b]) {
|
|
1040
490
|
w /= 100;
|
|
@@ -1048,16 +498,14 @@ var Saturon = (() => {
|
|
|
1048
498
|
}
|
|
1049
499
|
function RGB_to_HWB(rgb) {
|
|
1050
500
|
const [r, g, b] = rgb.map((v) => v / 255);
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
if (d
|
|
1055
|
-
H = 0;
|
|
1056
|
-
else {
|
|
1057
|
-
H = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
501
|
+
const max2 = Math.max(r, g, b), min2 = Math.min(r, g, b);
|
|
502
|
+
const d = max2 - min2;
|
|
503
|
+
let H = NaN;
|
|
504
|
+
if (d > EPSILON) {
|
|
505
|
+
H = max2 === r ? (g - b) / d + (g < b ? 6 : 0) : max2 === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
1058
506
|
H = H * 60 % 360;
|
|
1059
507
|
}
|
|
1060
|
-
return [H,
|
|
508
|
+
return [H, min2 * 100, (1 - max2) * 100];
|
|
1061
509
|
}
|
|
1062
510
|
function LAB_to_XYZD50([L, a, b]) {
|
|
1063
511
|
const D50 = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];
|
|
@@ -1085,7 +533,7 @@ var Saturon = (() => {
|
|
|
1085
533
|
let H = Math.atan2(b, a) * 180 / Math.PI;
|
|
1086
534
|
if (H < 0)
|
|
1087
535
|
H += 360;
|
|
1088
|
-
return [L, C, H];
|
|
536
|
+
return [L, C, C <= EPSILON ? NaN : H];
|
|
1089
537
|
}
|
|
1090
538
|
function OKLAB_to_XYZD65(oklab) {
|
|
1091
539
|
const { LMS_to_XYZD65, OKLAB_to_LMS } = MATRICES;
|
|
@@ -1105,7 +553,7 @@ var Saturon = (() => {
|
|
|
1105
553
|
let H = Math.atan2(b, a) * 180 / Math.PI;
|
|
1106
554
|
if (H < 0)
|
|
1107
555
|
H += 360;
|
|
1108
|
-
return [L, C, H];
|
|
556
|
+
return [L, C, C <= EPSILON ? NaN : H];
|
|
1109
557
|
}
|
|
1110
558
|
|
|
1111
559
|
// dist/converters.js
|
|
@@ -1264,18 +712,18 @@ var Saturon = (() => {
|
|
|
1264
712
|
components: ["r", "g", "b"],
|
|
1265
713
|
bridge: "xyz-d65",
|
|
1266
714
|
toLinear: (c) => {
|
|
1267
|
-
const
|
|
1268
|
-
const
|
|
1269
|
-
if (
|
|
1270
|
-
return
|
|
1271
|
-
return
|
|
715
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
716
|
+
const abs2 = Math.abs(c);
|
|
717
|
+
if (abs2 <= 0.04045)
|
|
718
|
+
return sign2 * (abs2 / 12.92);
|
|
719
|
+
return sign2 * Math.pow((abs2 + 0.055) / 1.055, 2.4);
|
|
1272
720
|
},
|
|
1273
721
|
fromLinear: (c) => {
|
|
1274
|
-
const
|
|
1275
|
-
const
|
|
1276
|
-
if (
|
|
1277
|
-
return
|
|
1278
|
-
return
|
|
722
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
723
|
+
const abs2 = Math.abs(c);
|
|
724
|
+
if (abs2 > 31308e-7)
|
|
725
|
+
return sign2 * (1.055 * Math.pow(abs2, 1 / 2.4) - 0.055);
|
|
726
|
+
return sign2 * (12.92 * abs2);
|
|
1279
727
|
},
|
|
1280
728
|
toBridgeMatrix: MATRICES.SRGB_to_XYZD65,
|
|
1281
729
|
fromBridgeMatrix: MATRICES.XYZD65_to_SRGB
|
|
@@ -1290,18 +738,18 @@ var Saturon = (() => {
|
|
|
1290
738
|
components: ["r", "g", "b"],
|
|
1291
739
|
bridge: "xyz-d65",
|
|
1292
740
|
toLinear: (c) => {
|
|
1293
|
-
const
|
|
1294
|
-
const
|
|
1295
|
-
if (
|
|
1296
|
-
return
|
|
1297
|
-
return
|
|
741
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
742
|
+
const abs2 = Math.abs(c);
|
|
743
|
+
if (abs2 <= 0.04045)
|
|
744
|
+
return sign2 * (abs2 / 12.92);
|
|
745
|
+
return sign2 * Math.pow((abs2 + 0.055) / 1.055, 2.4);
|
|
1298
746
|
},
|
|
1299
747
|
fromLinear: (c) => {
|
|
1300
|
-
const
|
|
1301
|
-
const
|
|
1302
|
-
if (
|
|
1303
|
-
return
|
|
1304
|
-
return
|
|
748
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
749
|
+
const abs2 = Math.abs(c);
|
|
750
|
+
if (abs2 > 31308e-7)
|
|
751
|
+
return sign2 * (1.055 * Math.pow(abs2, 1 / 2.4) - 0.055);
|
|
752
|
+
return sign2 * (12.92 * abs2);
|
|
1305
753
|
},
|
|
1306
754
|
toBridgeMatrix: MATRICES.P3_to_XYZD65,
|
|
1307
755
|
fromBridgeMatrix: MATRICES.XYZD65_to_P3
|
|
@@ -1312,20 +760,20 @@ var Saturon = (() => {
|
|
|
1312
760
|
toLinear: (c) => {
|
|
1313
761
|
const \u03B1 = 1.09929682680944;
|
|
1314
762
|
const \u03B2 = 0.018053968510807;
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
1317
|
-
if (
|
|
1318
|
-
return
|
|
1319
|
-
return
|
|
763
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
764
|
+
const abs2 = Math.abs(c);
|
|
765
|
+
if (abs2 < \u03B2 * 4.5)
|
|
766
|
+
return sign2 * (abs2 / 4.5);
|
|
767
|
+
return sign2 * Math.pow((abs2 + \u03B1 - 1) / \u03B1, 1 / 0.45);
|
|
1320
768
|
},
|
|
1321
769
|
fromLinear: (c) => {
|
|
1322
770
|
const \u03B1 = 1.09929682680944;
|
|
1323
771
|
const \u03B2 = 0.018053968510807;
|
|
1324
|
-
const
|
|
1325
|
-
const
|
|
1326
|
-
if (
|
|
1327
|
-
return
|
|
1328
|
-
return
|
|
772
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
773
|
+
const abs2 = Math.abs(c);
|
|
774
|
+
if (abs2 > \u03B2)
|
|
775
|
+
return sign2 * (\u03B1 * Math.pow(abs2, 0.45) - (\u03B1 - 1));
|
|
776
|
+
return sign2 * (4.5 * abs2);
|
|
1329
777
|
},
|
|
1330
778
|
toBridgeMatrix: MATRICES.REC2020_to_XYZD65,
|
|
1331
779
|
fromBridgeMatrix: MATRICES.XYZD65_to_REC2020
|
|
@@ -1334,14 +782,14 @@ var Saturon = (() => {
|
|
|
1334
782
|
components: ["r", "g", "b"],
|
|
1335
783
|
bridge: "xyz-d65",
|
|
1336
784
|
toLinear: (c) => {
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1339
|
-
return
|
|
785
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
786
|
+
const abs2 = Math.abs(c);
|
|
787
|
+
return sign2 * Math.pow(abs2, 563 / 256);
|
|
1340
788
|
},
|
|
1341
789
|
fromLinear: (c) => {
|
|
1342
|
-
const
|
|
1343
|
-
const
|
|
1344
|
-
return
|
|
790
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
791
|
+
const abs2 = Math.abs(c);
|
|
792
|
+
return sign2 * Math.pow(abs2, 256 / 563);
|
|
1345
793
|
},
|
|
1346
794
|
toBridgeMatrix: MATRICES.A98_to_XYZD65,
|
|
1347
795
|
fromBridgeMatrix: MATRICES.XYZD65_to_A98
|
|
@@ -1351,19 +799,19 @@ var Saturon = (() => {
|
|
|
1351
799
|
bridge: "xyz-d50",
|
|
1352
800
|
toLinear: (c) => {
|
|
1353
801
|
const Et2 = 16 / 512;
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
if (
|
|
1357
|
-
return
|
|
1358
|
-
return
|
|
802
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
803
|
+
const abs2 = Math.abs(c);
|
|
804
|
+
if (abs2 <= Et2)
|
|
805
|
+
return sign2 * (abs2 / 16);
|
|
806
|
+
return sign2 * Math.pow(abs2, 1.8);
|
|
1359
807
|
},
|
|
1360
808
|
fromLinear: (c) => {
|
|
1361
809
|
const Et = 1 / 512;
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1364
|
-
if (
|
|
1365
|
-
return
|
|
1366
|
-
return
|
|
810
|
+
const sign2 = c < 0 ? -1 : 1;
|
|
811
|
+
const abs2 = Math.abs(c);
|
|
812
|
+
if (abs2 >= Et)
|
|
813
|
+
return sign2 * Math.pow(abs2, 1 / 1.8);
|
|
814
|
+
return sign2 * (16 * abs2);
|
|
1367
815
|
},
|
|
1368
816
|
toBridgeMatrix: MATRICES.ProPhoto_to_XYZD50,
|
|
1369
817
|
fromBridgeMatrix: MATRICES.XYZD50_to_ProPhoto
|
|
@@ -1413,7 +861,8 @@ var Saturon = (() => {
|
|
|
1413
861
|
components: {
|
|
1414
862
|
r: { index: 0, value: [0, 255], precision: 0 },
|
|
1415
863
|
g: { index: 1, value: [0, 255], precision: 0 },
|
|
1416
|
-
b: { index: 2, value: [0, 255], precision: 0 }
|
|
864
|
+
b: { index: 2, value: [0, 255], precision: 0 },
|
|
865
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1417
866
|
},
|
|
1418
867
|
bridge: "xyz-d65",
|
|
1419
868
|
toBridge: RGB_to_XYZD65,
|
|
@@ -1423,9 +872,10 @@ var Saturon = (() => {
|
|
|
1423
872
|
supportsLegacy: true,
|
|
1424
873
|
alphaVariant: "hsla",
|
|
1425
874
|
components: {
|
|
1426
|
-
h: { index: 0, value: "
|
|
875
|
+
h: { index: 0, value: "hue", precision: 0 },
|
|
1427
876
|
s: { index: 1, value: "percentage", precision: 0 },
|
|
1428
|
-
l: { index: 2, value: "percentage", precision: 0 }
|
|
877
|
+
l: { index: 2, value: "percentage", precision: 0 },
|
|
878
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1429
879
|
},
|
|
1430
880
|
bridge: "rgb",
|
|
1431
881
|
toBridge: HSL_to_RGB,
|
|
@@ -1433,9 +883,10 @@ var Saturon = (() => {
|
|
|
1433
883
|
},
|
|
1434
884
|
hwb: {
|
|
1435
885
|
components: {
|
|
1436
|
-
h: { index: 0, value: "
|
|
886
|
+
h: { index: 0, value: "hue", precision: 0 },
|
|
1437
887
|
w: { index: 1, value: "percentage", precision: 0 },
|
|
1438
|
-
b: { index: 2, value: "percentage", precision: 0 }
|
|
888
|
+
b: { index: 2, value: "percentage", precision: 0 },
|
|
889
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1439
890
|
},
|
|
1440
891
|
bridge: "rgb",
|
|
1441
892
|
toBridge: HWB_to_RGB,
|
|
@@ -1446,7 +897,8 @@ var Saturon = (() => {
|
|
|
1446
897
|
components: {
|
|
1447
898
|
l: { index: 0, value: "percentage", precision: 5 },
|
|
1448
899
|
a: { index: 1, value: [-125, 125], precision: 5 },
|
|
1449
|
-
b: { index: 2, value: [-125, 125], precision: 5 }
|
|
900
|
+
b: { index: 2, value: [-125, 125], precision: 5 },
|
|
901
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1450
902
|
},
|
|
1451
903
|
bridge: "xyz-d50",
|
|
1452
904
|
toBridge: LAB_to_XYZD50,
|
|
@@ -1457,7 +909,8 @@ var Saturon = (() => {
|
|
|
1457
909
|
components: {
|
|
1458
910
|
l: { index: 0, value: "percentage", precision: 5 },
|
|
1459
911
|
c: { index: 1, value: [0, 150], precision: 5 },
|
|
1460
|
-
h: { index: 2, value: "
|
|
912
|
+
h: { index: 2, value: "hue", precision: 5 },
|
|
913
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1461
914
|
},
|
|
1462
915
|
bridge: "lab",
|
|
1463
916
|
toBridge: LCH_to_LAB,
|
|
@@ -1468,7 +921,8 @@ var Saturon = (() => {
|
|
|
1468
921
|
components: {
|
|
1469
922
|
l: { index: 0, value: [0, 1], precision: 5 },
|
|
1470
923
|
a: { index: 1, value: [-0.4, 0.4], precision: 5 },
|
|
1471
|
-
b: { index: 2, value: [-0.4, 0.4], precision: 5 }
|
|
924
|
+
b: { index: 2, value: [-0.4, 0.4], precision: 5 },
|
|
925
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1472
926
|
},
|
|
1473
927
|
bridge: "xyz-d65",
|
|
1474
928
|
toBridge: OKLAB_to_XYZD65,
|
|
@@ -1479,7 +933,8 @@ var Saturon = (() => {
|
|
|
1479
933
|
components: {
|
|
1480
934
|
l: { index: 0, value: [0, 1], precision: 5 },
|
|
1481
935
|
c: { index: 1, value: [0, 0.4], precision: 5 },
|
|
1482
|
-
h: { index: 2, value: "
|
|
936
|
+
h: { index: 2, value: "hue", precision: 5 },
|
|
937
|
+
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
1483
938
|
},
|
|
1484
939
|
bridge: "oklab",
|
|
1485
940
|
toBridge: OKLCH_to_OKLAB,
|
|
@@ -1487,401 +942,1516 @@ var Saturon = (() => {
|
|
|
1487
942
|
},
|
|
1488
943
|
...colorSpaces
|
|
1489
944
|
};
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
945
|
+
|
|
946
|
+
// dist/engine.js
|
|
947
|
+
var grammar = `
|
|
948
|
+
/* ==========================================================================
|
|
949
|
+
Core Color Types
|
|
950
|
+
========================================================================== */
|
|
951
|
+
<color> = <color-base> | currentColor | <system-color> | <contrast-color()> | <device-cmyk()> | <light-dark-color>
|
|
952
|
+
|
|
953
|
+
<color-base> = <hex-color> | <color-function> | <named-color> | <color-mix()> | transparent
|
|
954
|
+
|
|
955
|
+
<color-function> = <rgb()> | <rgba()> | <hsl()> | <hsla()> | <hwb()> | <lab()> | <lch()> | <oklab()> | <oklch()> | <alpha()> | <color()> | <custom-color-function>
|
|
956
|
+
|
|
957
|
+
/* ==========================================================================
|
|
958
|
+
RGB & RGBA Syntax
|
|
959
|
+
========================================================================== */
|
|
960
|
+
<rgb()> = [ <legacy-rgb-syntax> | <modern-rgb-syntax> | <relative-rgb-syntax> ]
|
|
961
|
+
<rgba()> = [ <legacy-rgba-syntax> | <modern-rgba-syntax> | <relative-rgba-syntax> ]
|
|
962
|
+
|
|
963
|
+
<legacy-rgb-syntax> = rgb( <percentage>#{3} [ , <alpha-value> ]? ) | rgb( <number>#{3} [ , <alpha-value> ]? )
|
|
964
|
+
<legacy-rgba-syntax> = rgba( <percentage>#{3} [ , <alpha-value> ]? ) | rgba( <number>#{3} [ , <alpha-value> ]? )
|
|
965
|
+
|
|
966
|
+
<modern-rgb-syntax> = rgb( <rgb-absolute-component>{3} [ / <alpha-absolute-value> ]? )
|
|
967
|
+
<modern-rgba-syntax> = rgba( <rgb-absolute-component>{3} [ / <alpha-absolute-value> ]? )
|
|
968
|
+
<rgb-absolute-component> = <number> | <percentage> | none
|
|
969
|
+
<alpha-absolute-value> = <alpha-value> | none
|
|
970
|
+
|
|
971
|
+
<relative-rgb-syntax> = rgb( from <color> <rgb-relative-component>{3} [ / <rgb-relative-alpha> ]? )
|
|
972
|
+
<relative-rgba-syntax> = rgba( from <color> <rgb-relative-component>{3} [ / <rgb-relative-alpha> ]? )
|
|
973
|
+
|
|
974
|
+
/* ==========================================================================
|
|
975
|
+
HSL & HSLA Syntax
|
|
976
|
+
========================================================================== */
|
|
977
|
+
<hsl()> = [ <legacy-hsl-syntax> | <modern-hsl-syntax> | <relative-hsl-syntax> ]
|
|
978
|
+
<hsla()> = [ <legacy-hsla-syntax> | <modern-hsla-syntax> | <relative-hsla-syntax> ]
|
|
979
|
+
|
|
980
|
+
<legacy-hsl-syntax> = hsl( <hue> , <percentage> , <percentage> [ , <alpha-value> ]? )
|
|
981
|
+
<legacy-hsla-syntax> = hsla( <hue> , <percentage> , <percentage> [ , <alpha-value> ]? )
|
|
982
|
+
|
|
983
|
+
<modern-hsl-syntax> = hsl( [ <hue> | none ] [ <percentage> | <number> | none ]{2} [ / <alpha-absolute-value> ]? )
|
|
984
|
+
<modern-hsla-syntax> = hsla( [ <hue> | none ] [ <percentage> | <number> | none ]{2} [ / <alpha-absolute-value> ]? )
|
|
985
|
+
|
|
986
|
+
<relative-hsl-syntax> = hsl( from <color> <hsl-relative-hue> <hsl-relative-component>{2} [ / <hsl-relative-alpha> ]? )
|
|
987
|
+
<relative-hsla-syntax> = hsla( from <color> <hsl-relative-hue> <hsl-relative-component>{2} [ / <hsl-relative-alpha> ]? )
|
|
988
|
+
<hsl-relative-hue> = <hue> | none | h | s | l | alpha
|
|
989
|
+
<hsl-relative-component> = <percentage> | <number> | none | h | s | l | alpha
|
|
990
|
+
<hsl-relative-alpha> = <alpha-value> | none | h | s | l | alpha
|
|
991
|
+
|
|
992
|
+
/* ==========================================================================
|
|
993
|
+
HWB Syntax
|
|
994
|
+
========================================================================== */
|
|
995
|
+
<hwb()> = [ <absolute-hwb-syntax> | <relative-hwb-syntax> ]
|
|
996
|
+
<absolute-hwb-syntax> = hwb( [ <hue> | none ] [ <percentage> | <number> | none ]{2} [ / <alpha-absolute-value> ]? )
|
|
997
|
+
<relative-hwb-syntax> = hwb( from <color> <hwb-relative-hue> <hwb-relative-component>{2} [ / <hwb-relative-alpha> ]? )
|
|
998
|
+
<hwb-relative-hue> = <hue> | none | h | w | b | alpha
|
|
999
|
+
<hwb-relative-component> = <percentage> | <number> | none | h | w | b | alpha
|
|
1000
|
+
<hwb-relative-alpha> = <alpha-value> | none | h | w | b | alpha
|
|
1001
|
+
|
|
1002
|
+
/* ==========================================================================
|
|
1003
|
+
LAB & LCH Syntax
|
|
1004
|
+
========================================================================== */
|
|
1005
|
+
<lab()> = [ <absolute-lab-syntax> | <relative-lab-syntax> ]
|
|
1006
|
+
<absolute-lab-syntax> = lab( [ <percentage> | <number> | none ]{3} [ / <alpha-absolute-value> ]? )
|
|
1007
|
+
<relative-lab-syntax> = lab( from <color> <lab-relative-component>{3} [ / <lab-relative-alpha> ]? )
|
|
1008
|
+
<lab-relative-component> = <percentage> | <number> | none | l | a | b | alpha
|
|
1009
|
+
<lab-relative-alpha> = <alpha-value> | none | l | a | b | alpha
|
|
1010
|
+
|
|
1011
|
+
<lch()> = [ <absolute-lch-syntax> | <relative-lch-syntax> ]
|
|
1012
|
+
<absolute-lch-syntax> = lch( [ <percentage> | <number> | none ]{2} [ <hue> | none ] [ / <alpha-absolute-value> ]? )
|
|
1013
|
+
<relative-lch-syntax> = lch( from <color> <lch-relative-component>{2} <lch-relative-hue> [ / <lch-relative-alpha> ]? )
|
|
1014
|
+
<lch-relative-component> = <percentage> | <number> | none | l | c | h | alpha
|
|
1015
|
+
<lch-relative-hue> = <hue> | none | l | c | h | alpha
|
|
1016
|
+
<lch-relative-alpha> = <alpha-value> | none | l | c | h | alpha
|
|
1017
|
+
|
|
1018
|
+
/* ==========================================================================
|
|
1019
|
+
OKLAB & OKLCH Syntax
|
|
1020
|
+
========================================================================== */
|
|
1021
|
+
<oklab()> = [ <absolute-oklab-syntax> | <relative-oklab-syntax> ]
|
|
1022
|
+
<absolute-oklab-syntax> = oklab( [ <percentage> | <number> | none ]{3} [ / <alpha-absolute-value> ]? )
|
|
1023
|
+
<relative-oklab-syntax> = oklab( from <color> <oklab-relative-component>{3} [ / <oklab-relative-alpha> ]? )
|
|
1024
|
+
<oklab-relative-component> = <percentage> | <number> | none | l | a | b | alpha
|
|
1025
|
+
<oklab-relative-alpha> = <alpha-value> | none | l | a | b | alpha
|
|
1026
|
+
|
|
1027
|
+
<oklch()> = [ <absolute-oklch-syntax> | <relative-oklch-syntax> ]
|
|
1028
|
+
<absolute-oklch-syntax> = oklch( [ <percentage> | <number> | none ]{2} [ <hue> | none ] [ / <alpha-absolute-value> ]? )
|
|
1029
|
+
<relative-oklch-syntax> = oklch( from <color> <oklch-relative-component>{2} <oklch-relative-hue> [ / <oklch-relative-alpha> ]? )
|
|
1030
|
+
<oklch-relative-component> = <percentage> | <number> | none | l | c | h | alpha
|
|
1031
|
+
<oklch-relative-hue> = <hue> | none | l | c | h | alpha
|
|
1032
|
+
<oklch-relative-alpha> = <alpha-value> | none | l | c | h | alpha
|
|
1033
|
+
|
|
1034
|
+
/* ==========================================================================
|
|
1035
|
+
ALPHA Syntax
|
|
1036
|
+
========================================================================== */
|
|
1037
|
+
<alpha()> = alpha( from <color> [ / [ <alpha-value> | none | alpha ] ]? )
|
|
1038
|
+
|
|
1039
|
+
/* ==========================================================================
|
|
1040
|
+
COLOR Space Function Syntax
|
|
1041
|
+
========================================================================== */
|
|
1042
|
+
<color()> = [ <absolute-color-syntax> | <relative-color-syntax> ]
|
|
1043
|
+
<absolute-color-syntax> = color( [ <predefined-rgb> | <xyz-space> | <custom-color-space> ] [ <number> | <percentage> | none ]{3} [ / [ <alpha-value> | none ] ]? )
|
|
1044
|
+
<relative-color-syntax> = [ <relative-rgb-color-syntax> | <relative-xyz-color-syntax> | <relative-custom-color-syntax> ]
|
|
1045
|
+
|
|
1046
|
+
<relative-rgb-color-syntax> = color( from <color> <predefined-rgb> <rgb-relative-component>{3} [ / <rgb-relative-alpha> ]? )
|
|
1047
|
+
|
|
1048
|
+
<relative-xyz-color-syntax> = color( from <color> <xyz-space> <xyz-relative-component>{3} [ / <xyz-relative-alpha> ]? )
|
|
1049
|
+
<xyz-relative-component> = <number> | <percentage> | none | x | y | z | alpha
|
|
1050
|
+
<xyz-relative-alpha> = <alpha-value> | none | x | y | z | alpha
|
|
1051
|
+
|
|
1052
|
+
<predefined-rgb> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020
|
|
1053
|
+
<xyz-space> = xyz | xyz-d50 | xyz-d65
|
|
1054
|
+
|
|
1055
|
+
<relative-custom-color-syntax> = %unregistered-sentinal-value%
|
|
1056
|
+
|
|
1057
|
+
/* ==========================================================================
|
|
1058
|
+
Functional Utilities (Light/Dark, Contrast, CMYK, Mix)
|
|
1059
|
+
========================================================================== */
|
|
1060
|
+
<light-dark-color> = light-dark( <color> , <color> )
|
|
1061
|
+
|
|
1062
|
+
<contrast-color()> = contrast-color( <color> )
|
|
1063
|
+
|
|
1064
|
+
<device-cmyk()> = <legacy-device-cmyk-syntax> | <modern-device-cmyk-syntax>
|
|
1065
|
+
<legacy-device-cmyk-syntax> = device-cmyk( <number>#{4} )
|
|
1066
|
+
<modern-device-cmyk-syntax> = device-cmyk( <cmyk-component>{4} [ / [ <alpha-value> | none ] ]? )
|
|
1067
|
+
<cmyk-component> = <number> | <percentage> | none
|
|
1068
|
+
|
|
1069
|
+
<color-mix()> = color-mix( [ <color-interpolation-method> , ]? [ <color> && <percentage [0,100]>? ]# )
|
|
1070
|
+
<color-interpolation-method> = in [ <rectangular-color-space> | <polar-color-space> <hue-interpolation-method>? ]
|
|
1071
|
+
<color-space> = <rectangular-color-space> | <polar-color-space>
|
|
1072
|
+
<rectangular-color-space> = srgb | srgb-linear | display-p3 | display-p3-linear | a98-rgb | prophoto-rgb | rec2020 | lab | oklab | <xyz-space> | <custom-color-space> | <custom-rectangular-space>
|
|
1073
|
+
<polar-color-space> = hsl | hwb | lch | oklch | <custom-polar-space>
|
|
1074
|
+
<hue-interpolation-method> = [ shorter | longer | increasing | decreasing ] hue
|
|
1075
|
+
|
|
1076
|
+
<custom-color-function> = %unregistered-sentinel-value%
|
|
1077
|
+
<custom-rectangular-space> = %unregistered-sentinel-value%
|
|
1078
|
+
<custom-polar-space> = %unregistered-sentinel-value%
|
|
1079
|
+
|
|
1080
|
+
/* ==========================================================================
|
|
1081
|
+
Shared Value Components
|
|
1082
|
+
========================================================================== */
|
|
1083
|
+
<alpha-value> = <number> | <percentage>
|
|
1084
|
+
<hue> = <number> | <angle>
|
|
1085
|
+
<rgb-relative-component> = <number> | <percentage> | none | r | g | b | alpha
|
|
1086
|
+
<rgb-relative-alpha> = <alpha-value> | none | r | g | b | alpha
|
|
1087
|
+
<custom-color-space> = %unregistered-sentinel-value%
|
|
1088
|
+
`;
|
|
1089
|
+
var RULE_START_RE = /^<[^>]+>\s*=/;
|
|
1090
|
+
var HASH_REPEAT_RE = /^(.*)#\{(\d+)\}$/;
|
|
1091
|
+
var REPEAT_RE = /^(.*)\{(\d+)\}$/;
|
|
1092
|
+
var REF_RE = /^<([^>\s]+)(?:\s+([^>]+))?>$/;
|
|
1093
|
+
var ARG_RE = /\[[^\]]+\]|[^\s]+/g;
|
|
1094
|
+
var grammarMap = parseGrammar(grammar);
|
|
1095
|
+
function concatNodes(a, b) {
|
|
1096
|
+
const lenA = a.length;
|
|
1097
|
+
const lenB = b.length;
|
|
1098
|
+
const result = new Array(lenA + lenB);
|
|
1099
|
+
for (let i = 0; i < lenA; i++)
|
|
1100
|
+
result[i] = a[i];
|
|
1101
|
+
for (let i = 0; i < lenB; i++)
|
|
1102
|
+
result[lenA + i] = b[i];
|
|
1103
|
+
return result;
|
|
1104
|
+
}
|
|
1105
|
+
function parseCmykComponent(n) {
|
|
1106
|
+
if (!n?.value)
|
|
1107
|
+
return 0;
|
|
1108
|
+
const raw = Array.isArray(n.value) ? n.value[0]?.value : n.value;
|
|
1109
|
+
return parseFloat(raw.trim());
|
|
1110
|
+
}
|
|
1111
|
+
function getLiteralValue(n) {
|
|
1112
|
+
if (!n)
|
|
1113
|
+
return "";
|
|
1114
|
+
if (n.type === "literal")
|
|
1115
|
+
return n.value;
|
|
1116
|
+
if (Array.isArray(n.value))
|
|
1117
|
+
return getLiteralValue(n.value[0]);
|
|
1118
|
+
return "";
|
|
1119
|
+
}
|
|
1120
|
+
function validateTokens(tokens, rootRule) {
|
|
1121
|
+
const memo = /* @__PURE__ */ new Map();
|
|
1122
|
+
const rulesToTest = rootRule ? [rootRule] : Object.keys(grammarMap);
|
|
1123
|
+
for (let i = 0; i < rulesToTest.length; i++) {
|
|
1124
|
+
const ruleName = rulesToTest[i];
|
|
1125
|
+
const results = matchRule(ruleName, tokens, 0, grammarMap, memo, null);
|
|
1126
|
+
for (let j = 0; j < results.length; j++) {
|
|
1127
|
+
const result = results[j];
|
|
1128
|
+
if (result.success && result.nextIndex === tokens.length) {
|
|
1129
|
+
return result.nodes[0];
|
|
1539
1130
|
}
|
|
1540
1131
|
}
|
|
1541
|
-
}
|
|
1542
|
-
"
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const { expression: expr, end: e } = extractBalancedExpression(remaining, 0);
|
|
1561
|
-
if (expr) {
|
|
1562
|
-
colorExpression = expr;
|
|
1563
|
-
rest = remaining.slice(e).trim();
|
|
1564
|
-
} else {
|
|
1565
|
-
const m = remaining.match(/^([^\s]+)(.*)$/);
|
|
1566
|
-
colorExpression = m ? m[1] : remaining;
|
|
1567
|
-
rest = m ? m[2].trim() : "";
|
|
1568
|
-
}
|
|
1569
|
-
} else {
|
|
1570
|
-
const m = remaining.match(/^([^\s]+)(.*)$/);
|
|
1571
|
-
colorExpression = m ? m[1] : remaining;
|
|
1572
|
-
rest = m ? m[2].trim() : "";
|
|
1132
|
+
}
|
|
1133
|
+
throw new Error("Invalid syntax");
|
|
1134
|
+
}
|
|
1135
|
+
function matchRule(ruleName, tokens, index, grammar2, memo, currentFunction, args) {
|
|
1136
|
+
const key = `${ruleName}:${index}:${currentFunction || ""}:${args ? args.join(",") : ""}`;
|
|
1137
|
+
const cached = memo.get(key);
|
|
1138
|
+
if (cached)
|
|
1139
|
+
return cached;
|
|
1140
|
+
const token = tokens[index];
|
|
1141
|
+
const validator = validators[ruleName];
|
|
1142
|
+
if (validator) {
|
|
1143
|
+
if (!token)
|
|
1144
|
+
return [];
|
|
1145
|
+
if (validator(token, args)) {
|
|
1146
|
+
const result = [
|
|
1147
|
+
{
|
|
1148
|
+
success: true,
|
|
1149
|
+
nextIndex: index + 1,
|
|
1150
|
+
nodes: [{ type: ruleName, value: token }]
|
|
1573
1151
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1152
|
+
];
|
|
1153
|
+
memo.set(key, result);
|
|
1154
|
+
return result;
|
|
1155
|
+
}
|
|
1156
|
+
memo.set(key, []);
|
|
1157
|
+
return [];
|
|
1158
|
+
}
|
|
1159
|
+
const rule = grammar2[ruleName];
|
|
1160
|
+
if (!rule) {
|
|
1161
|
+
memo.set(key, []);
|
|
1162
|
+
return [];
|
|
1163
|
+
}
|
|
1164
|
+
const results = matchNode(rule, tokens, index, grammar2, memo, currentFunction);
|
|
1165
|
+
const wrapped = new Array(results.length);
|
|
1166
|
+
for (let i = 0; i < results.length; i++) {
|
|
1167
|
+
wrapped[i] = {
|
|
1168
|
+
success: true,
|
|
1169
|
+
nextIndex: results[i].nextIndex,
|
|
1170
|
+
nodes: [{ type: ruleName, value: results[i].nodes }]
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
memo.set(key, wrapped);
|
|
1174
|
+
return wrapped;
|
|
1175
|
+
}
|
|
1176
|
+
function matchNode(node, tokens, index, grammar2, memo, currentFunction) {
|
|
1177
|
+
switch (node.kind) {
|
|
1178
|
+
case "literal": {
|
|
1179
|
+
const token = tokens[index];
|
|
1180
|
+
if (!token || token !== node.value.toLowerCase())
|
|
1181
|
+
return [];
|
|
1182
|
+
return [
|
|
1183
|
+
{
|
|
1184
|
+
success: true,
|
|
1185
|
+
nextIndex: index + 1,
|
|
1186
|
+
nodes: [
|
|
1187
|
+
{
|
|
1188
|
+
type: token === "(" || token === ")" || token === "," || token === "/" ? "symbol" : "literal",
|
|
1189
|
+
value: token
|
|
1589
1190
|
}
|
|
1590
|
-
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
if (rest.length > 0) {
|
|
1594
|
-
throw new Error(`Unexpected extra tokens after color: '${rest}'.`);
|
|
1595
|
-
}
|
|
1596
|
-
const color = Color.from(colorExpression);
|
|
1597
|
-
return { color, weight };
|
|
1598
|
-
};
|
|
1599
|
-
const getWeight2Prime = (weight12, weight22) => {
|
|
1600
|
-
if (weight12 === void 0 && typeof weight22 === "number") {
|
|
1601
|
-
weight12 = 1 - weight22;
|
|
1602
|
-
} else if (typeof weight12 === "number" && weight22 === void 0) {
|
|
1603
|
-
weight22 = 1 - weight12;
|
|
1604
|
-
} else if (weight12 === void 0 && weight22 === void 0) {
|
|
1605
|
-
weight12 = weight22 = 0.5;
|
|
1606
|
-
} else {
|
|
1607
|
-
weight12 = weight12;
|
|
1608
|
-
weight22 = weight22;
|
|
1609
|
-
if (weight12 + weight22 <= 0) {
|
|
1610
|
-
throw new Error("Sum of percengates cannot be 0%.");
|
|
1611
|
-
}
|
|
1191
|
+
]
|
|
1612
1192
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1193
|
+
];
|
|
1194
|
+
}
|
|
1195
|
+
case "ref": {
|
|
1196
|
+
const token = tokens[index];
|
|
1197
|
+
if (!token)
|
|
1198
|
+
return [];
|
|
1199
|
+
if (currentFunction && colorModels[currentFunction]) {
|
|
1200
|
+
const component = colorModels[currentFunction].components[token];
|
|
1201
|
+
if (component && component.value === node.name) {
|
|
1202
|
+
return [
|
|
1203
|
+
{
|
|
1204
|
+
success: true,
|
|
1205
|
+
nextIndex: index + 1,
|
|
1206
|
+
nodes: [{ type: node.name, value: token }]
|
|
1207
|
+
}
|
|
1208
|
+
];
|
|
1622
1209
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
let current = "";
|
|
1633
|
-
while (i < inner.length) {
|
|
1634
|
-
const char = inner[i];
|
|
1635
|
-
if (char === ",") {
|
|
1636
|
-
parts.push(current.trim());
|
|
1637
|
-
current = "";
|
|
1638
|
-
i++;
|
|
1639
|
-
continue;
|
|
1210
|
+
}
|
|
1211
|
+
return matchRule(node.name, tokens, index, grammar2, memo, currentFunction, node.args);
|
|
1212
|
+
}
|
|
1213
|
+
case "choice": {
|
|
1214
|
+
const results = [];
|
|
1215
|
+
for (let i = 0; i < node.nodes.length; i++) {
|
|
1216
|
+
const childMatches = matchNode(node.nodes[i], tokens, index, grammar2, memo, currentFunction);
|
|
1217
|
+
for (let j = 0; j < childMatches.length; j++) {
|
|
1218
|
+
results.push(childMatches[j]);
|
|
1640
1219
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1220
|
+
}
|
|
1221
|
+
return results;
|
|
1222
|
+
}
|
|
1223
|
+
case "sequence": {
|
|
1224
|
+
let states = [{ success: true, nextIndex: index, nodes: [] }];
|
|
1225
|
+
for (let i = 0; i < node.nodes.length; i++) {
|
|
1226
|
+
const child = node.nodes[i];
|
|
1227
|
+
const nextStates = [];
|
|
1228
|
+
for (let j = 0; j < states.length; j++) {
|
|
1229
|
+
const state = states[j];
|
|
1230
|
+
const matches = matchNode(child, tokens, state.nextIndex, grammar2, memo, currentFunction);
|
|
1231
|
+
for (let k = 0; k < matches.length; k++) {
|
|
1232
|
+
nextStates.push({
|
|
1233
|
+
success: true,
|
|
1234
|
+
nextIndex: matches[k].nextIndex,
|
|
1235
|
+
nodes: concatNodes(state.nodes, matches[k].nodes)
|
|
1236
|
+
});
|
|
1647
1237
|
}
|
|
1648
1238
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1239
|
+
states = nextStates;
|
|
1240
|
+
if (states.length === 0)
|
|
1241
|
+
return [];
|
|
1651
1242
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1243
|
+
return states;
|
|
1244
|
+
}
|
|
1245
|
+
case "optional": {
|
|
1246
|
+
const matches = matchNode(node.node, tokens, index, grammar2, memo, currentFunction);
|
|
1247
|
+
const results = new Array(matches.length + 1);
|
|
1248
|
+
results[0] = { success: true, nextIndex: index, nodes: [] };
|
|
1249
|
+
for (let i = 0; i < matches.length; i++) {
|
|
1250
|
+
results[i + 1] = matches[i];
|
|
1655
1251
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1252
|
+
return results;
|
|
1253
|
+
}
|
|
1254
|
+
case "repeat": {
|
|
1255
|
+
let states = [{ success: true, nextIndex: index, nodes: [] }];
|
|
1256
|
+
for (let i = 0; i < node.min; i++) {
|
|
1257
|
+
const nextStates = [];
|
|
1258
|
+
for (let j = 0; j < states.length; j++) {
|
|
1259
|
+
const state = states[j];
|
|
1260
|
+
const matches = matchNode(node.node, tokens, state.nextIndex, grammar2, memo, currentFunction);
|
|
1261
|
+
for (let k = 0; k < matches.length; k++) {
|
|
1262
|
+
nextStates.push({
|
|
1263
|
+
success: true,
|
|
1264
|
+
nextIndex: matches[k].nextIndex,
|
|
1265
|
+
nodes: concatNodes(state.nodes, matches[k].nodes)
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
states = nextStates;
|
|
1270
|
+
if (states.length === 0)
|
|
1271
|
+
return [];
|
|
1660
1272
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1273
|
+
return states;
|
|
1274
|
+
}
|
|
1275
|
+
case "permutation": {
|
|
1276
|
+
const numNodes = node.nodes.length;
|
|
1277
|
+
const fullMask = (1 << numNodes) - 1;
|
|
1278
|
+
let states = [{ nextIndex: index, nodes: [], remainingMask: fullMask }];
|
|
1279
|
+
const finalResults = [];
|
|
1280
|
+
while (states.length > 0) {
|
|
1281
|
+
const nextStates = [];
|
|
1282
|
+
for (let i = 0; i < states.length; i++) {
|
|
1283
|
+
const state = states[i];
|
|
1284
|
+
let allRemainingOptional = true;
|
|
1285
|
+
for (let bit = 0; bit < numNodes; bit++) {
|
|
1286
|
+
if ((state.remainingMask & 1 << bit) !== 0) {
|
|
1287
|
+
if (node.nodes[bit].kind !== "optional") {
|
|
1288
|
+
allRemainingOptional = false;
|
|
1289
|
+
break;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (allRemainingOptional) {
|
|
1294
|
+
finalResults.push({
|
|
1295
|
+
success: true,
|
|
1296
|
+
nextIndex: state.nextIndex,
|
|
1297
|
+
nodes: state.nodes
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
for (let bit = 0; bit < numNodes; bit++) {
|
|
1301
|
+
if ((state.remainingMask & 1 << bit) !== 0) {
|
|
1302
|
+
const child = node.nodes[bit];
|
|
1303
|
+
const nodeToMatch = child.kind === "optional" ? child.node : child;
|
|
1304
|
+
const matches = matchNode(nodeToMatch, tokens, state.nextIndex, grammar2, memo, currentFunction);
|
|
1305
|
+
const nextMask = state.remainingMask & ~(1 << bit);
|
|
1306
|
+
for (let m = 0; m < matches.length; m++) {
|
|
1307
|
+
nextStates.push({
|
|
1308
|
+
nextIndex: matches[m].nextIndex,
|
|
1309
|
+
nodes: concatNodes(state.nodes, matches[m].nodes),
|
|
1310
|
+
remainingMask: nextMask
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1669
1315
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1316
|
+
states = nextStates;
|
|
1317
|
+
}
|
|
1318
|
+
return finalResults;
|
|
1319
|
+
}
|
|
1320
|
+
case "hashList": {
|
|
1321
|
+
let currentStates = matchNode(node.node, tokens, index, grammar2, memo, currentFunction);
|
|
1322
|
+
const allSuccessfulStates = [];
|
|
1323
|
+
while (currentStates.length > 0) {
|
|
1324
|
+
const nextStates = [];
|
|
1325
|
+
for (let i = 0; i < currentStates.length; i++) {
|
|
1326
|
+
const state = currentStates[i];
|
|
1327
|
+
allSuccessfulStates.push(state);
|
|
1328
|
+
const commaIdx = state.nextIndex;
|
|
1329
|
+
if (commaIdx < tokens.length && tokens[commaIdx] === ",") {
|
|
1330
|
+
const commaNode = { type: "symbol", value: "," };
|
|
1331
|
+
const matches = matchNode(node.node, tokens, commaIdx + 1, grammar2, memo, currentFunction);
|
|
1332
|
+
for (let m = 0; m < matches.length; m++) {
|
|
1333
|
+
const match = matches[m];
|
|
1334
|
+
if (match.nextIndex > commaIdx) {
|
|
1335
|
+
const newNodes = new Array(state.nodes.length + 1 + match.nodes.length);
|
|
1336
|
+
let idx = 0;
|
|
1337
|
+
for (let n = 0; n < state.nodes.length; n++)
|
|
1338
|
+
newNodes[idx++] = state.nodes[n];
|
|
1339
|
+
newNodes[idx++] = commaNode;
|
|
1340
|
+
for (let n = 0; n < match.nodes.length; n++)
|
|
1341
|
+
newNodes[idx++] = match.nodes[n];
|
|
1342
|
+
nextStates.push({
|
|
1343
|
+
success: true,
|
|
1344
|
+
nextIndex: match.nextIndex,
|
|
1345
|
+
nodes: newNodes
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1673
1350
|
}
|
|
1674
|
-
|
|
1675
|
-
hue = "shorter";
|
|
1351
|
+
currentStates = nextStates;
|
|
1676
1352
|
}
|
|
1677
|
-
|
|
1678
|
-
const { color: color2, weight: weight2 } = extractColorAndWeight(parts[2]);
|
|
1679
|
-
const { amount, alphaMultiplier = 1 } = getWeight2Prime(weight1, weight2);
|
|
1680
|
-
return color1.with({ alpha: (a) => a * alphaMultiplier }).in(model).mix(color2.with({ alpha: (a) => a * alphaMultiplier }), { amount, hue }).in("rgb").toArray({ fit: "none", precision: null });
|
|
1353
|
+
return allSuccessfulStates;
|
|
1681
1354
|
}
|
|
1682
|
-
},
|
|
1683
|
-
transparent: {
|
|
1684
|
-
isValid: (str) => str === "transparent",
|
|
1685
|
-
bridge: "rgb",
|
|
1686
|
-
toBridge: (coords) => coords,
|
|
1687
|
-
parse: (str) => [0, 0, 0, 0]
|
|
1688
|
-
// eslint-disable-line no-unused-vars
|
|
1689
1355
|
}
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
parse: (str) => {
|
|
1705
|
-
const { systemColors: systemColors2 } = config;
|
|
1706
|
-
const key = Object.keys(systemColors2).find((k) => k.toLowerCase() === str);
|
|
1707
|
-
const rgbArr = systemColors2[key][config.theme === "light" ? 0 : 1];
|
|
1708
|
-
return [...rgbArr, 1];
|
|
1356
|
+
}
|
|
1357
|
+
function tokenize(input) {
|
|
1358
|
+
const lower = input.toLowerCase();
|
|
1359
|
+
const tokens = [];
|
|
1360
|
+
let i = 0;
|
|
1361
|
+
while (i < lower.length) {
|
|
1362
|
+
if (lower.slice(i, i + 2) === "/*") {
|
|
1363
|
+
const endIndex = lower.indexOf("*/", i + 2);
|
|
1364
|
+
if (endIndex !== -1) {
|
|
1365
|
+
i = endIndex + 2;
|
|
1366
|
+
} else {
|
|
1367
|
+
i = lower.length;
|
|
1368
|
+
}
|
|
1369
|
+
continue;
|
|
1709
1370
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
toBridge: (coords) => coords,
|
|
1715
|
-
parse: (str) => {
|
|
1716
|
-
const inner = str.slice(15, -1);
|
|
1717
|
-
const [, luminance] = Color.from(inner).in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
1718
|
-
return luminance > 0.5 ? [0, 0, 0, 1] : [255, 255, 255, 1];
|
|
1371
|
+
const ch = lower[i];
|
|
1372
|
+
if (/\s/.test(ch)) {
|
|
1373
|
+
i++;
|
|
1374
|
+
continue;
|
|
1719
1375
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1376
|
+
if (lower.slice(i, i + 5) === "calc(") {
|
|
1377
|
+
const start2 = i;
|
|
1378
|
+
let parenCount = 0;
|
|
1379
|
+
while (i < lower.length) {
|
|
1380
|
+
if (lower[i] === "(")
|
|
1381
|
+
parenCount++;
|
|
1382
|
+
else if (lower[i] === ")") {
|
|
1383
|
+
parenCount--;
|
|
1384
|
+
if (parenCount === 0) {
|
|
1385
|
+
i++;
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
i++;
|
|
1390
|
+
}
|
|
1391
|
+
tokens.push(lower.slice(start2, i));
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if ("(),/".includes(ch)) {
|
|
1395
|
+
tokens.push(ch);
|
|
1396
|
+
i++;
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
const start = i;
|
|
1400
|
+
while (i < lower.length && !/\s/.test(lower[i]) && !"(),/".includes(lower[i])) {
|
|
1401
|
+
i++;
|
|
1402
|
+
}
|
|
1403
|
+
tokens.push(lower.slice(start, i));
|
|
1404
|
+
}
|
|
1405
|
+
return tokens;
|
|
1406
|
+
}
|
|
1407
|
+
function parseGrammar(grammar2) {
|
|
1408
|
+
const rules = {};
|
|
1409
|
+
const lines = grammar2.replace(/\/\*[\s\S]*?\*\//g, "").split("\n");
|
|
1410
|
+
let currentRuleName = "";
|
|
1411
|
+
let currentDef = "";
|
|
1412
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1413
|
+
const line = lines[i].trim();
|
|
1414
|
+
if (!line)
|
|
1415
|
+
continue;
|
|
1416
|
+
if (RULE_START_RE.test(line)) {
|
|
1417
|
+
if (currentRuleName)
|
|
1418
|
+
rules[currentRuleName] = parseExpression(currentDef);
|
|
1419
|
+
const eqIndex = line.indexOf("=");
|
|
1420
|
+
currentRuleName = line.slice(0, eqIndex).trim();
|
|
1421
|
+
currentDef = line.slice(eqIndex + 1).trim();
|
|
1422
|
+
} else if (currentRuleName) {
|
|
1423
|
+
currentDef += " " + line;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
if (currentRuleName)
|
|
1427
|
+
rules[currentRuleName] = parseExpression(currentDef);
|
|
1428
|
+
return rules;
|
|
1429
|
+
}
|
|
1430
|
+
function parseExpression(expr) {
|
|
1431
|
+
const parts = splitTopLevel(expr, "|");
|
|
1432
|
+
if (parts.length > 1)
|
|
1433
|
+
return { kind: "choice", nodes: parts.map(parsePermutation) };
|
|
1434
|
+
return parsePermutation(expr);
|
|
1435
|
+
}
|
|
1436
|
+
function splitTopLevel(str, separator) {
|
|
1437
|
+
const result = [];
|
|
1438
|
+
let depth = 0;
|
|
1439
|
+
let start = 0;
|
|
1440
|
+
const sepLen = separator.length;
|
|
1441
|
+
const len = str.length;
|
|
1442
|
+
for (let i = 0; i < len; i++) {
|
|
1443
|
+
const ch = str.charCodeAt(i);
|
|
1444
|
+
if (ch === 91 || ch === 40) {
|
|
1445
|
+
depth++;
|
|
1446
|
+
} else if (ch === 93 || ch === 41) {
|
|
1447
|
+
depth--;
|
|
1448
|
+
} else if (depth === 0 && str.startsWith(separator, i)) {
|
|
1449
|
+
const part = str.slice(start, i).trim();
|
|
1450
|
+
if (part)
|
|
1451
|
+
result.push(part);
|
|
1452
|
+
i += sepLen - 1;
|
|
1453
|
+
start = i + 1;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
const last = str.slice(start).trim();
|
|
1457
|
+
if (last)
|
|
1458
|
+
result.push(last);
|
|
1459
|
+
return result;
|
|
1460
|
+
}
|
|
1461
|
+
function splitSequence(str) {
|
|
1462
|
+
const result = [];
|
|
1463
|
+
let i = 0;
|
|
1464
|
+
const len = str.length;
|
|
1465
|
+
while (i < len) {
|
|
1466
|
+
const ch = str.charCodeAt(i);
|
|
1467
|
+
if (ch === 32 || ch === 9 || ch === 10 || ch === 13) {
|
|
1468
|
+
i++;
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if (ch === 40 || ch === 41 || ch === 44 || ch === 47 || ch === 124) {
|
|
1472
|
+
result.push(str[i]);
|
|
1473
|
+
i++;
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
const start = i;
|
|
1477
|
+
if (ch === 60) {
|
|
1478
|
+
while (i < len && str.charCodeAt(i) !== 62)
|
|
1479
|
+
i++;
|
|
1480
|
+
i++;
|
|
1481
|
+
while (i < len) {
|
|
1482
|
+
const c = str.charCodeAt(i);
|
|
1483
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 40 || c === 41 || c === 44 || c === 47 || c === 124)
|
|
1484
|
+
break;
|
|
1485
|
+
i++;
|
|
1486
|
+
}
|
|
1487
|
+
result.push(str.slice(start, i));
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
if (ch === 91) {
|
|
1491
|
+
let depth = 1;
|
|
1492
|
+
i++;
|
|
1493
|
+
while (i < len && depth > 0) {
|
|
1494
|
+
const c = str.charCodeAt(i);
|
|
1495
|
+
if (c === 91)
|
|
1496
|
+
depth++;
|
|
1497
|
+
else if (c === 93)
|
|
1498
|
+
depth--;
|
|
1499
|
+
i++;
|
|
1500
|
+
}
|
|
1501
|
+
while (i < len) {
|
|
1502
|
+
const c = str.charCodeAt(i);
|
|
1503
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 40 || c === 41 || c === 44 || c === 47 || c === 124)
|
|
1504
|
+
break;
|
|
1505
|
+
i++;
|
|
1506
|
+
}
|
|
1507
|
+
result.push(str.slice(start, i));
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
while (i < len) {
|
|
1511
|
+
const c = str.charCodeAt(i);
|
|
1512
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 40 || c === 41 || c === 44 || c === 47 || c === 124)
|
|
1513
|
+
break;
|
|
1514
|
+
i++;
|
|
1515
|
+
}
|
|
1516
|
+
result.push(str.slice(start, i));
|
|
1517
|
+
}
|
|
1518
|
+
return result;
|
|
1519
|
+
}
|
|
1520
|
+
function parsePermutation(targetExpr) {
|
|
1521
|
+
const parts = splitTopLevel(targetExpr, "&&");
|
|
1522
|
+
if (parts.length > 1)
|
|
1523
|
+
return { kind: "permutation", nodes: parts.map(parseSequence) };
|
|
1524
|
+
return parseSequence(targetExpr);
|
|
1525
|
+
}
|
|
1526
|
+
function parseSequence(targetExpr) {
|
|
1527
|
+
const tokens = splitSequence(targetExpr);
|
|
1528
|
+
return { kind: "sequence", nodes: tokens.map(parseTerm) };
|
|
1529
|
+
}
|
|
1530
|
+
function parseTerm(term) {
|
|
1531
|
+
term = term.trim();
|
|
1532
|
+
const len = term.length;
|
|
1533
|
+
if (len === 1) {
|
|
1534
|
+
const c = term.charCodeAt(0);
|
|
1535
|
+
if (c === 40 || c === 41 || c === 44 || c === 47) {
|
|
1536
|
+
return { kind: "literal", value: term };
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
const lastChar = term.charCodeAt(len - 1);
|
|
1540
|
+
if (lastChar === 35 && term.indexOf("{") === -1) {
|
|
1541
|
+
return { kind: "hashList", node: parseTerm(term.slice(0, -1)) };
|
|
1542
|
+
}
|
|
1543
|
+
if (lastChar === 63) {
|
|
1544
|
+
return { kind: "optional", node: parseTerm(term.slice(0, -1)) };
|
|
1545
|
+
}
|
|
1546
|
+
const hashRepeatMatch = HASH_REPEAT_RE.exec(term);
|
|
1547
|
+
if (hashRepeatMatch) {
|
|
1548
|
+
const innerNode = parseTerm(hashRepeatMatch[1]);
|
|
1549
|
+
const count = Number(hashRepeatMatch[2]);
|
|
1550
|
+
const commaNode = { kind: "literal", value: "," };
|
|
1551
|
+
const nodes = [];
|
|
1552
|
+
for (let i = 0; i < count; i++) {
|
|
1553
|
+
nodes.push(innerNode);
|
|
1554
|
+
if (i < count - 1)
|
|
1555
|
+
nodes.push(commaNode);
|
|
1556
|
+
}
|
|
1557
|
+
return { kind: "sequence", nodes };
|
|
1558
|
+
}
|
|
1559
|
+
const repeatMatch = REPEAT_RE.exec(term);
|
|
1560
|
+
if (repeatMatch) {
|
|
1561
|
+
const num = Number(repeatMatch[2]);
|
|
1562
|
+
return {
|
|
1563
|
+
kind: "repeat",
|
|
1564
|
+
node: parseTerm(repeatMatch[1]),
|
|
1565
|
+
min: num,
|
|
1566
|
+
max: num
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
const firstChar = term.charCodeAt(0);
|
|
1570
|
+
if (firstChar === 91 && lastChar === 93) {
|
|
1571
|
+
return parseExpression(term.slice(1, -1));
|
|
1572
|
+
}
|
|
1573
|
+
if (firstChar === 60 && lastChar === 62) {
|
|
1574
|
+
const match = REF_RE.exec(term);
|
|
1575
|
+
if (match) {
|
|
1576
|
+
let argsArray = void 0;
|
|
1577
|
+
if (match[2]) {
|
|
1578
|
+
const argMatches = match[2].match(ARG_RE);
|
|
1579
|
+
if (argMatches) {
|
|
1580
|
+
argsArray = argMatches.map((arg) => arg.trim());
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return { kind: "ref", name: `<${match[1]}>`, args: argsArray };
|
|
1584
|
+
}
|
|
1585
|
+
return { kind: "ref", name: term };
|
|
1586
|
+
}
|
|
1587
|
+
return { kind: "literal", value: term };
|
|
1588
|
+
}
|
|
1589
|
+
var { PI: pi, E: e, pow, sqrt, sin, cos, tan, asin, acos, atan, atan2, exp, log, log10, log2, abs, min, max, hypot, round, ceil, floor, sign, trunc, random } = Math;
|
|
1590
|
+
var BASE_MATH_ENV = {
|
|
1591
|
+
pi,
|
|
1592
|
+
e,
|
|
1593
|
+
tau: pi * 2,
|
|
1594
|
+
pow,
|
|
1595
|
+
sqrt,
|
|
1596
|
+
sin,
|
|
1597
|
+
cos,
|
|
1598
|
+
tan,
|
|
1599
|
+
asin,
|
|
1600
|
+
acos,
|
|
1601
|
+
atan,
|
|
1602
|
+
atan2,
|
|
1603
|
+
exp,
|
|
1604
|
+
log,
|
|
1605
|
+
log10,
|
|
1606
|
+
log2,
|
|
1607
|
+
abs,
|
|
1608
|
+
min,
|
|
1609
|
+
max,
|
|
1610
|
+
hypot,
|
|
1611
|
+
round,
|
|
1612
|
+
ceil,
|
|
1613
|
+
floor,
|
|
1614
|
+
sign,
|
|
1615
|
+
trunc,
|
|
1616
|
+
random
|
|
1617
|
+
};
|
|
1618
|
+
function extractToken(n) {
|
|
1619
|
+
if (typeof n === "string")
|
|
1620
|
+
return n;
|
|
1621
|
+
if (n.type === "<percentage>")
|
|
1622
|
+
return String(n.value || "");
|
|
1623
|
+
if (Array.isArray(n.value))
|
|
1624
|
+
return extractToken(n.value[0]);
|
|
1625
|
+
return String(n.value || "");
|
|
1626
|
+
}
|
|
1627
|
+
function parsePercent(str, type, min2, max2) {
|
|
1628
|
+
const percent = parseFloat(str);
|
|
1629
|
+
if (isNaN(percent))
|
|
1630
|
+
throw new Error(`Invalid percentage: '${str}'.`);
|
|
1631
|
+
if (type === "percentage")
|
|
1632
|
+
return percent;
|
|
1633
|
+
if (min2 < 0 && max2 > 0)
|
|
1634
|
+
return percent / 100 * (max2 - min2) / 2;
|
|
1635
|
+
return percent / 100 * (max2 - min2) + min2;
|
|
1636
|
+
}
|
|
1637
|
+
function parseHue(str) {
|
|
1638
|
+
const val = parseFloat(str);
|
|
1639
|
+
if (isNaN(val))
|
|
1640
|
+
throw new Error(`Invalid hue: '${str}'.`);
|
|
1641
|
+
if (str[str.length - 1] === "deg")
|
|
1642
|
+
return val;
|
|
1643
|
+
if (str[str.length - 1] === "rad")
|
|
1644
|
+
return val * (180 / pi);
|
|
1645
|
+
if (str[str.length - 1] === "grad")
|
|
1646
|
+
return val * 0.9;
|
|
1647
|
+
if (str[str.length - 1] === "turn")
|
|
1648
|
+
return val * 360;
|
|
1649
|
+
return val;
|
|
1650
|
+
}
|
|
1651
|
+
function parseCalcExpression(expr, type, base, min2, max2) {
|
|
1652
|
+
if (expr === "infinity")
|
|
1653
|
+
return Infinity;
|
|
1654
|
+
if (expr === "-infinity")
|
|
1655
|
+
return -Infinity;
|
|
1656
|
+
if (expr === "nan")
|
|
1657
|
+
return NaN;
|
|
1658
|
+
const UNIT_NUM = 1;
|
|
1659
|
+
const UNIT_PCT = 2;
|
|
1660
|
+
const UNIT_ANG = 4;
|
|
1661
|
+
const getUnitName = (u) => u === UNIT_ANG ? "angle" : u === UNIT_PCT ? "percent" : "number";
|
|
1662
|
+
const tokenize2 = (s) => {
|
|
1663
|
+
const out = [];
|
|
1664
|
+
for (let i = 0; i < s.length; ) {
|
|
1665
|
+
const c = s[i];
|
|
1666
|
+
if (/\s/.test(c)) {
|
|
1667
|
+
i++;
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
if (s.slice(i, i + 2) === "**") {
|
|
1671
|
+
out.push({ type: "operator", value: "**" });
|
|
1672
|
+
i += 2;
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (/[0-9]/.test(c) || c === "." && /[0-9]/.test(s[i + 1] || "")) {
|
|
1676
|
+
let numStr = "";
|
|
1677
|
+
while (i < s.length && /[0-9.]/.test(s[i]))
|
|
1678
|
+
numStr += s[i++];
|
|
1679
|
+
if (i < s.length && /[eE]/.test(s[i])) {
|
|
1680
|
+
numStr += s[i++];
|
|
1681
|
+
if (/[+-]/.test(s[i]))
|
|
1682
|
+
numStr += s[i++];
|
|
1683
|
+
while (i < s.length && /[0-9]/.test(s[i]))
|
|
1684
|
+
numStr += s[i++];
|
|
1685
|
+
}
|
|
1686
|
+
let unitType = UNIT_NUM;
|
|
1687
|
+
let rawUnit = "";
|
|
1688
|
+
if (s[i] === "%") {
|
|
1689
|
+
unitType = UNIT_PCT;
|
|
1690
|
+
rawUnit = "%";
|
|
1739
1691
|
i++;
|
|
1740
|
-
|
|
1692
|
+
} else if (s.slice(i, i + 3) === "deg") {
|
|
1693
|
+
unitType = UNIT_ANG;
|
|
1694
|
+
rawUnit = "deg";
|
|
1695
|
+
i += 3;
|
|
1696
|
+
} else if (s.slice(i, i + 3) === "rad") {
|
|
1697
|
+
unitType = UNIT_ANG;
|
|
1698
|
+
rawUnit = "rad";
|
|
1699
|
+
i += 3;
|
|
1700
|
+
} else if (s.slice(i, i + 4) === "grad") {
|
|
1701
|
+
unitType = UNIT_ANG;
|
|
1702
|
+
rawUnit = "grad";
|
|
1703
|
+
i += 4;
|
|
1704
|
+
} else if (s.slice(i, i + 4) === "turn") {
|
|
1705
|
+
unitType = UNIT_ANG;
|
|
1706
|
+
rawUnit = "turn";
|
|
1707
|
+
i += 4;
|
|
1741
1708
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
continue;
|
|
1748
|
-
}
|
|
1709
|
+
let val = parseFloat(numStr);
|
|
1710
|
+
if (unitType === UNIT_PCT) {
|
|
1711
|
+
val = parsePercent(numStr + rawUnit, type, min2, max2);
|
|
1712
|
+
} else if (unitType === UNIT_ANG) {
|
|
1713
|
+
val = parseHue(numStr + rawUnit);
|
|
1749
1714
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1715
|
+
out.push({ type: "number", value: val, unit: unitType });
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
if (/[a-zA-Z_]/.test(c)) {
|
|
1719
|
+
let id = "";
|
|
1720
|
+
while (i < s.length && /[a-zA-Z0-9_]/.test(s[i]))
|
|
1721
|
+
id += s[i++];
|
|
1722
|
+
out.push({ type: "identifier", value: id });
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
if ("+-*/%(),".includes(c)) {
|
|
1726
|
+
out.push({ type: "operator", value: c });
|
|
1727
|
+
i++;
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
throw new Error(`Unexpected character: ${c}`);
|
|
1731
|
+
}
|
|
1732
|
+
return out;
|
|
1733
|
+
};
|
|
1734
|
+
const parse = (tokens) => {
|
|
1735
|
+
let pos = 0;
|
|
1736
|
+
const cur = () => pos < tokens.length ? tokens[pos] : null;
|
|
1737
|
+
const nxt = () => {
|
|
1738
|
+
if (pos >= tokens.length)
|
|
1739
|
+
throw new Error("Unexpected end of input");
|
|
1740
|
+
return tokens[pos++];
|
|
1741
|
+
};
|
|
1742
|
+
const expect = (v) => {
|
|
1743
|
+
const t = cur();
|
|
1744
|
+
if (!t || t.value !== v) {
|
|
1745
|
+
throw new Error(`Expected "${v}" but got "${t ? t.value : "end of input"}"`);
|
|
1746
|
+
}
|
|
1747
|
+
nxt();
|
|
1748
|
+
};
|
|
1749
|
+
const parsePrimary = () => {
|
|
1750
|
+
const t = cur();
|
|
1751
|
+
if (!t)
|
|
1752
|
+
throw new Error("Unexpected end of input");
|
|
1753
|
+
if (t.type === "number") {
|
|
1754
|
+
nxt();
|
|
1755
|
+
return { type: "number", value: t.value, unit: t.unit };
|
|
1756
|
+
}
|
|
1757
|
+
if (t.type === "identifier") {
|
|
1758
|
+
nxt();
|
|
1759
|
+
if (cur() && cur().value === "(") {
|
|
1760
|
+
nxt();
|
|
1761
|
+
const args = [];
|
|
1762
|
+
if (cur() && cur().value !== ")") {
|
|
1763
|
+
args.push(parseAdd());
|
|
1764
|
+
while (cur() && cur().value === ",") {
|
|
1765
|
+
nxt();
|
|
1766
|
+
args.push(parseAdd());
|
|
1763
1767
|
}
|
|
1764
1768
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1769
|
+
expect(")");
|
|
1770
|
+
return { type: "call", func: t.value, args };
|
|
1767
1771
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
+
return { type: "var", name: t.value };
|
|
1773
|
+
}
|
|
1774
|
+
if (t.value === "(") {
|
|
1775
|
+
nxt();
|
|
1776
|
+
const e2 = parseAdd();
|
|
1777
|
+
expect(")");
|
|
1778
|
+
return e2;
|
|
1779
|
+
}
|
|
1780
|
+
throw new Error(`Unexpected token: ${t.value}`);
|
|
1781
|
+
};
|
|
1782
|
+
const parseUnary = () => {
|
|
1783
|
+
if (cur() && (cur().value === "+" || cur().value === "-")) {
|
|
1784
|
+
const op = nxt().value;
|
|
1785
|
+
return { type: "unary", op, arg: parseUnary() };
|
|
1786
|
+
}
|
|
1787
|
+
return parsePrimary();
|
|
1788
|
+
};
|
|
1789
|
+
const parsePower = () => {
|
|
1790
|
+
let left = parseUnary();
|
|
1791
|
+
while (cur() && cur().value === "**") {
|
|
1792
|
+
const op = nxt().value;
|
|
1793
|
+
left = { type: "binary", op, left, right: parseUnary() };
|
|
1794
|
+
}
|
|
1795
|
+
return left;
|
|
1796
|
+
};
|
|
1797
|
+
const parseMul = () => {
|
|
1798
|
+
let left = parsePower();
|
|
1799
|
+
while (cur() && ["*", "/", "%"].includes(String(cur().value))) {
|
|
1800
|
+
const op = nxt().value;
|
|
1801
|
+
left = { type: "binary", op, left, right: parsePower() };
|
|
1802
|
+
}
|
|
1803
|
+
return left;
|
|
1804
|
+
};
|
|
1805
|
+
const parseAdd = () => {
|
|
1806
|
+
let left = parseMul();
|
|
1807
|
+
while (cur() && (cur().value === "+" || cur().value === "-")) {
|
|
1808
|
+
const op = nxt().value;
|
|
1809
|
+
left = { type: "binary", op, left, right: parseMul() };
|
|
1810
|
+
}
|
|
1811
|
+
return left;
|
|
1812
|
+
};
|
|
1813
|
+
const ast = parseAdd();
|
|
1814
|
+
if (pos < tokens.length) {
|
|
1815
|
+
throw new Error(`Extra tokens after expression: ${tokens.slice(pos).map((t) => t.value).join(" ")}`);
|
|
1816
|
+
}
|
|
1817
|
+
return ast;
|
|
1818
|
+
};
|
|
1819
|
+
const evaluate = (ast, env) => {
|
|
1820
|
+
switch (ast.type) {
|
|
1821
|
+
case "number":
|
|
1822
|
+
return { value: ast.value, unit: ast.unit };
|
|
1823
|
+
case "var": {
|
|
1824
|
+
const v = env[ast.name];
|
|
1825
|
+
if (v === void 0)
|
|
1826
|
+
throw new Error(`Unknown variable: ${ast.name}`);
|
|
1827
|
+
if (typeof v === "function") {
|
|
1828
|
+
throw new Error(`Expected variable but found function: ${ast.name}`);
|
|
1772
1829
|
}
|
|
1773
|
-
|
|
1830
|
+
return { value: v, unit: UNIT_NUM };
|
|
1774
1831
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1832
|
+
case "binary": {
|
|
1833
|
+
const L = evaluate(ast.left, env);
|
|
1834
|
+
const R = evaluate(ast.right, env);
|
|
1835
|
+
switch (ast.op) {
|
|
1836
|
+
case "+":
|
|
1837
|
+
case "-": {
|
|
1838
|
+
const val = ast.op === "+" ? L.value + R.value : L.value - R.value;
|
|
1839
|
+
const unitMask = L.unit | R.unit;
|
|
1840
|
+
if (L.unit === R.unit)
|
|
1841
|
+
return { value: val, unit: L.unit };
|
|
1842
|
+
if (unitMask === 5)
|
|
1843
|
+
return { value: val, unit: UNIT_ANG };
|
|
1844
|
+
throw new Error(`Cannot ${ast.op} mismatched units: ${getUnitName(L.unit)} and ${getUnitName(R.unit)}`);
|
|
1845
|
+
}
|
|
1846
|
+
case "*": {
|
|
1847
|
+
const val = L.value * R.value;
|
|
1848
|
+
if (L.unit === UNIT_NUM)
|
|
1849
|
+
return { value: val, unit: R.unit };
|
|
1850
|
+
if (R.unit === UNIT_NUM)
|
|
1851
|
+
return { value: val, unit: L.unit };
|
|
1852
|
+
throw new Error(`Cannot multiply units: ${getUnitName(L.unit)} and ${getUnitName(R.unit)}`);
|
|
1853
|
+
}
|
|
1854
|
+
case "/": {
|
|
1855
|
+
if (R.unit === UNIT_NUM)
|
|
1856
|
+
return { value: L.value / R.value, unit: L.unit };
|
|
1857
|
+
if (L.unit === R.unit)
|
|
1858
|
+
return { value: L.value / R.value, unit: UNIT_NUM };
|
|
1859
|
+
throw new Error(`Cannot divide units: ${getUnitName(L.unit)} by ${getUnitName(R.unit)}`);
|
|
1860
|
+
}
|
|
1861
|
+
case "%": {
|
|
1862
|
+
if (R.unit === UNIT_NUM)
|
|
1863
|
+
return { value: L.value % R.value, unit: L.unit };
|
|
1864
|
+
if (L.unit === R.unit)
|
|
1865
|
+
return { value: L.value % R.value, unit: L.unit };
|
|
1866
|
+
throw new Error(`Cannot modulo units: ${getUnitName(L.unit)} by ${getUnitName(R.unit)}`);
|
|
1867
|
+
}
|
|
1868
|
+
case "**": {
|
|
1869
|
+
if (R.unit !== UNIT_NUM)
|
|
1870
|
+
throw new Error(`Exponent must be a number, got ${getUnitName(R.unit)}`);
|
|
1871
|
+
return { value: L.value ** R.value, unit: L.unit };
|
|
1872
|
+
}
|
|
1873
|
+
default:
|
|
1874
|
+
throw new Error(`Unknown binary operator: ${ast.op}`);
|
|
1786
1875
|
}
|
|
1787
|
-
throw new Error("Invalid number of components for comma-separated device-cmyk");
|
|
1788
1876
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1877
|
+
case "unary": {
|
|
1878
|
+
const res = evaluate(ast.arg, env);
|
|
1879
|
+
switch (ast.op) {
|
|
1880
|
+
case "+":
|
|
1881
|
+
return { value: +res.value, unit: res.unit };
|
|
1882
|
+
case "-":
|
|
1883
|
+
return { value: -res.value, unit: res.unit };
|
|
1884
|
+
default:
|
|
1885
|
+
throw new Error(`Unknown unary operator: ${ast.op}`);
|
|
1886
|
+
}
|
|
1794
1887
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1888
|
+
case "call": {
|
|
1889
|
+
const fn = env[ast.func];
|
|
1890
|
+
if (typeof fn !== "function")
|
|
1891
|
+
throw new Error(`Unknown function: ${ast.func}`);
|
|
1892
|
+
const evaluatedArgs = ast.args.map((a) => evaluate(a, env));
|
|
1893
|
+
const numArgs = evaluatedArgs.map((a) => a.value);
|
|
1894
|
+
const val = fn(...numArgs);
|
|
1895
|
+
const preserveUnitFuncs = ["min", "max", "round", "ceil", "floor", "abs", "sign", "trunc"];
|
|
1896
|
+
if (preserveUnitFuncs.includes(ast.func)) {
|
|
1897
|
+
let combinedMask = 0;
|
|
1898
|
+
for (let i = 0; i < evaluatedArgs.length; i++) {
|
|
1899
|
+
combinedMask |= evaluatedArgs[i].unit;
|
|
1900
|
+
}
|
|
1901
|
+
if ((combinedMask & 6) === 6)
|
|
1902
|
+
throw new Error(`Function ${ast.func} cannot mix angle and percent`);
|
|
1903
|
+
if ((combinedMask & 3) === 3)
|
|
1904
|
+
throw new Error(`Function ${ast.func} cannot mix number and percent`);
|
|
1905
|
+
const finalUnit = combinedMask & UNIT_ANG ? UNIT_ANG : combinedMask & UNIT_PCT ? UNIT_PCT : UNIT_NUM;
|
|
1906
|
+
return { value: val, unit: finalUnit };
|
|
1907
|
+
}
|
|
1908
|
+
return { value: val, unit: UNIT_NUM };
|
|
1797
1909
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1910
|
+
default: {
|
|
1911
|
+
throw new Error(`Unknown AST node type: ${ast.type}`);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
const calcEnv = Object.assign({}, BASE_MATH_ENV, base);
|
|
1916
|
+
return evaluate(parse(tokenize2(expr)), calcEnv).value;
|
|
1917
|
+
}
|
|
1918
|
+
function evaluateComponent(node, expectedType, base = {}) {
|
|
1919
|
+
if (!node)
|
|
1920
|
+
return NaN;
|
|
1921
|
+
const token = extractToken(node);
|
|
1922
|
+
return evaluateCSSValue(token, expectedType, base);
|
|
1923
|
+
}
|
|
1924
|
+
function evaluateCSSValue(tokenString, expectedType, base = {}) {
|
|
1925
|
+
const token = tokenString.trim();
|
|
1926
|
+
if (token === "none")
|
|
1927
|
+
return NaN;
|
|
1928
|
+
if (token in base)
|
|
1929
|
+
return base[token];
|
|
1930
|
+
const [tMin, tMax] = Array.isArray(expectedType) ? expectedType : expectedType === "percentage" ? [0, 100] : [0, 1];
|
|
1931
|
+
if (/^-?(?:\d+|\d*\.\d+)$/.test(token)) {
|
|
1932
|
+
return parseFloat(token);
|
|
1933
|
+
}
|
|
1934
|
+
if (token[token.length - 1] === "%" && /^-?(?:\d+|\d*\.\d+)%$/.test(token)) {
|
|
1935
|
+
return parsePercent(token, expectedType, tMin, tMax);
|
|
1936
|
+
}
|
|
1937
|
+
if (/^-?(?:\d+|\d*\.\d+)(deg|rad|grad|turn)$/.test(token)) {
|
|
1938
|
+
return parseHue(token);
|
|
1939
|
+
}
|
|
1940
|
+
if (token.slice(0, 5) === "calc(" && token[token.length - 1] === ")") {
|
|
1941
|
+
return parseCalcExpression(token.slice(5, -1).trim(), expectedType, base, tMin, tMax);
|
|
1942
|
+
}
|
|
1943
|
+
throw new Error(`Unable to parse component token: ${token}`);
|
|
1944
|
+
}
|
|
1945
|
+
function createLegacyColorParser(model, componentKeys) {
|
|
1946
|
+
return (node) => {
|
|
1947
|
+
const t = node.value;
|
|
1948
|
+
const config2 = colorModels[model].components;
|
|
1949
|
+
const c1 = evaluateComponent(t[2], config2[componentKeys[0]].value);
|
|
1950
|
+
const c2 = evaluateComponent(t[4], config2[componentKeys[1]].value);
|
|
1951
|
+
const c3 = evaluateComponent(t[6], config2[componentKeys[2]].value);
|
|
1952
|
+
let alpha = 1;
|
|
1953
|
+
if (t[7]?.type === "symbol" && t[7]?.value === ",")
|
|
1954
|
+
alpha = evaluateComponent(t[8], [0, 1]);
|
|
1955
|
+
return { model, coords: [c1, c2, c3, alpha] };
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
function createModernColorParser(model, componentKeys) {
|
|
1959
|
+
return (node) => {
|
|
1960
|
+
const t = node.value;
|
|
1961
|
+
const config2 = colorModels[model].components;
|
|
1962
|
+
const c1 = evaluateComponent(t[2], config2[componentKeys[0]].value);
|
|
1963
|
+
const c2 = evaluateComponent(t[3], config2[componentKeys[1]].value);
|
|
1964
|
+
const c3 = evaluateComponent(t[4], config2[componentKeys[2]].value);
|
|
1965
|
+
let alpha = 1;
|
|
1966
|
+
if (t[5]?.type === "symbol" && t[5]?.value === "/")
|
|
1967
|
+
alpha = evaluateComponent(t[6], [0, 1]);
|
|
1968
|
+
return { model, coords: [c1, c2, c3, alpha] };
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function createRelativeColorParser(model, componentKeys) {
|
|
1972
|
+
return (node) => {
|
|
1973
|
+
const t = node.value;
|
|
1974
|
+
const config2 = colorModels[model].components;
|
|
1975
|
+
const originColorNode = t[3];
|
|
1976
|
+
const originParsed = parseNode(originColorNode);
|
|
1977
|
+
const originInstance = new Color(originParsed.model, originParsed.coords).in(model);
|
|
1978
|
+
const baseEnv = originInstance.toObject({ fit: "none", precision: null });
|
|
1979
|
+
const c1 = evaluateComponent(t[4], config2[componentKeys[0]].value, baseEnv);
|
|
1980
|
+
const c2 = evaluateComponent(t[5], config2[componentKeys[1]].value, baseEnv);
|
|
1981
|
+
const c3 = evaluateComponent(t[6], config2[componentKeys[2]].value, baseEnv);
|
|
1982
|
+
let alpha = 1;
|
|
1983
|
+
if (t[7]?.type === "symbol" && t[7]?.value === "/") {
|
|
1984
|
+
alpha = evaluateComponent(t[8], [0, 1], baseEnv);
|
|
1985
|
+
}
|
|
1986
|
+
return {
|
|
1987
|
+
model,
|
|
1988
|
+
coords: [c1, c2, c3, alpha]
|
|
1989
|
+
};
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
function createRelativeColorFunctionParser(node) {
|
|
1993
|
+
const t = node.value;
|
|
1994
|
+
const { model, coords } = parseNode(t[3]);
|
|
1995
|
+
const spaceNode = t[4];
|
|
1996
|
+
const targetModel = Array.isArray(spaceNode.value) ? spaceNode.value[0]?.value : spaceNode.value;
|
|
1997
|
+
const originInstance = new Color(model, coords).in(targetModel);
|
|
1998
|
+
const baseEnv = originInstance.toObject({ fit: "none", precision: null });
|
|
1999
|
+
const c1 = evaluateComponent(t[5], [0, 1], baseEnv);
|
|
2000
|
+
const c2 = evaluateComponent(t[6], [0, 1], baseEnv);
|
|
2001
|
+
const c3 = evaluateComponent(t[7], [0, 1], baseEnv);
|
|
2002
|
+
let alpha = 1;
|
|
2003
|
+
if (t[8]?.type === "symbol" && t[8]?.value === "/") {
|
|
2004
|
+
alpha = evaluateComponent(t[9], [0, 1], baseEnv);
|
|
2005
|
+
}
|
|
2006
|
+
return {
|
|
2007
|
+
model: targetModel,
|
|
2008
|
+
coords: [c1, c2, c3, alpha]
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
var validators = (() => {
|
|
2012
|
+
const NUMBER_REGEX = /^-?(?:\d+(?:\.\d+)?|\.\d+)$/;
|
|
2013
|
+
const PERCENT_REGEX = /^-?(?:\d+(?:\.\d+)?|\.\d+)%$/;
|
|
2014
|
+
const ANGLE_REGEX = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|rad|turn|grad)$/;
|
|
2015
|
+
const RANGE_REGEX = /^\[([^,]+),([^\]]+)\]$/;
|
|
2016
|
+
const systemColorsSet = new Set(Object.keys(systemColors).map((key) => key.toLowerCase()));
|
|
2017
|
+
const rangeCache = /* @__PURE__ */ new Map();
|
|
2018
|
+
const getRange = (args) => {
|
|
2019
|
+
if (!args)
|
|
2020
|
+
return void 0;
|
|
2021
|
+
for (let i = 0; i < args.length; i++) {
|
|
2022
|
+
if (RANGE_REGEX.test(args[i]))
|
|
2023
|
+
return args[i];
|
|
2024
|
+
}
|
|
2025
|
+
return void 0;
|
|
2026
|
+
};
|
|
2027
|
+
const checkRange = (val, rangeStr) => {
|
|
2028
|
+
let bounds = rangeCache.get(rangeStr);
|
|
2029
|
+
if (!bounds) {
|
|
2030
|
+
const match = rangeStr.match(RANGE_REGEX);
|
|
2031
|
+
bounds = {
|
|
2032
|
+
min: match[1].trim() === "-INF" ? -Infinity : parseFloat(match[1]),
|
|
2033
|
+
max: match[2].trim() === "INF" || match[2].trim() === "+INF" ? Infinity : parseFloat(match[2])
|
|
2034
|
+
};
|
|
2035
|
+
rangeCache.set(rangeStr, bounds);
|
|
2036
|
+
}
|
|
2037
|
+
if (bounds.min !== -Infinity && val < bounds.min)
|
|
2038
|
+
return false;
|
|
2039
|
+
if (bounds.max !== Infinity && val > bounds.max)
|
|
2040
|
+
return false;
|
|
2041
|
+
return true;
|
|
2042
|
+
};
|
|
2043
|
+
return {
|
|
2044
|
+
"<named-color>": (str) => Object.hasOwn(namedColors, str),
|
|
2045
|
+
"<system-color>": (str) => systemColorsSet.has(str),
|
|
2046
|
+
"<hex-color>": (str) => {
|
|
2047
|
+
return str[0] === "#" && (str.length === 4 || str.length === 5 || str.length === 7 || str.length === 9) && /^[0-9a-fA-F]+$/.test(str.slice(1));
|
|
2048
|
+
},
|
|
2049
|
+
"<number>": (str, args) => {
|
|
2050
|
+
if (str.startsWith("calc(") && str.endsWith(")"))
|
|
2051
|
+
return true;
|
|
2052
|
+
if (!NUMBER_REGEX.test(str))
|
|
2053
|
+
return false;
|
|
2054
|
+
if (args !== void 0) {
|
|
2055
|
+
const rangeArg = getRange(args);
|
|
2056
|
+
if (rangeArg)
|
|
2057
|
+
return checkRange(parseFloat(str), rangeArg);
|
|
1806
2058
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2059
|
+
return true;
|
|
2060
|
+
},
|
|
2061
|
+
"<percentage>": (str, args) => {
|
|
2062
|
+
if (str.startsWith("calc(") && str.endsWith(")"))
|
|
2063
|
+
return true;
|
|
2064
|
+
if (!PERCENT_REGEX.test(str))
|
|
2065
|
+
return false;
|
|
2066
|
+
if (args !== void 0) {
|
|
2067
|
+
const rangeArg = getRange(args);
|
|
2068
|
+
if (rangeArg)
|
|
2069
|
+
return checkRange(parseFloat(str), rangeArg);
|
|
1811
2070
|
}
|
|
1812
|
-
|
|
1813
|
-
const green = 1 - Math.min(1, m * (1 - k) + k);
|
|
1814
|
-
const blue = 1 - Math.min(1, y * (1 - k) + k);
|
|
1815
|
-
return [red * 255, green * 255, blue * 255, alpha];
|
|
2071
|
+
return true;
|
|
1816
2072
|
},
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
return Number(precision === null ? value : value.toFixed(precision)).toString();
|
|
1827
|
-
};
|
|
1828
|
-
const c = formatComponent(k === 1 ? 0 : (1 - r - k) / (1 - k));
|
|
1829
|
-
const m = formatComponent(k === 1 ? 0 : (1 - g - k) / (1 - k));
|
|
1830
|
-
const y = formatComponent(k === 1 ? 0 : (1 - b - k) / (1 - k));
|
|
1831
|
-
const kFormatted = formatComponent(k);
|
|
1832
|
-
const alphaFormatted = Number(alpha.toFixed(3)).toString();
|
|
1833
|
-
if (legacy) {
|
|
1834
|
-
return `device-cmyk(${c}, ${m}, ${y}, ${kFormatted}${alpha < 1 ? `, ${alphaFormatted}` : ""})`;
|
|
2073
|
+
"<angle>": (str, args) => {
|
|
2074
|
+
if (str.startsWith("calc(") && str.endsWith(")"))
|
|
2075
|
+
return true;
|
|
2076
|
+
if (!ANGLE_REGEX.test(str))
|
|
2077
|
+
return false;
|
|
2078
|
+
if (args !== void 0) {
|
|
2079
|
+
const rangeArg = getRange(args);
|
|
2080
|
+
if (rangeArg)
|
|
2081
|
+
return checkRange(parseFloat(str), rangeArg);
|
|
1835
2082
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
2083
|
+
return true;
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
})();
|
|
2087
|
+
var parsers = {
|
|
2088
|
+
"<color>": (node) => {
|
|
2089
|
+
const child = node.value[0];
|
|
2090
|
+
const { type, value } = child;
|
|
2091
|
+
if (type === "literal") {
|
|
2092
|
+
if (value === "currentcolor")
|
|
2093
|
+
return { model: "rgb", coords: [0, 0, 0, 1] };
|
|
2094
|
+
else
|
|
2095
|
+
throw new Error(`Unknown <color>: ${value}`);
|
|
1838
2096
|
}
|
|
2097
|
+
return parseNode(child);
|
|
1839
2098
|
},
|
|
1840
|
-
"
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2099
|
+
"<color-base>": (node) => {
|
|
2100
|
+
const child = node.value[0];
|
|
2101
|
+
const { type, value } = child;
|
|
2102
|
+
if (type === "literal") {
|
|
2103
|
+
if (value === "transparent")
|
|
2104
|
+
return { model: "rgb", coords: [0, 0, 0, 0] };
|
|
2105
|
+
else
|
|
2106
|
+
throw new Error(`Unknown <color-base>: ${value}`);
|
|
2107
|
+
}
|
|
2108
|
+
return parseNode(child);
|
|
2109
|
+
},
|
|
2110
|
+
"<named-color>": (node) => {
|
|
2111
|
+
const { value: name } = node;
|
|
2112
|
+
const rgb = namedColors[name];
|
|
2113
|
+
if (!rgb)
|
|
2114
|
+
throw new Error(`Unknown <named-color>: ${name}`);
|
|
2115
|
+
return { model: "rgb", coords: [...rgb, 1] };
|
|
2116
|
+
},
|
|
2117
|
+
"<system-color>": (node) => {
|
|
2118
|
+
const { value: name } = node;
|
|
2119
|
+
const index = config.theme === "light" ? 0 : 1;
|
|
2120
|
+
const key = Object.keys(systemColors).find((k) => k.toLowerCase() === name);
|
|
2121
|
+
const rgb = systemColors[key][index];
|
|
2122
|
+
if (!rgb)
|
|
2123
|
+
throw new Error(`Unknown <system-color>: ${name}`);
|
|
2124
|
+
return { model: "rgb", coords: [...rgb, 1] };
|
|
2125
|
+
},
|
|
2126
|
+
"<contrast-color()>": (node) => {
|
|
2127
|
+
const colorNode = node.value[2];
|
|
2128
|
+
const { model, coords } = parseNode(colorNode);
|
|
2129
|
+
const [, luminance] = new Color(model, coords).in("xyz-d65").coords;
|
|
2130
|
+
return { model: "rgb", coords: luminance > 0.5 ? [0, 0, 0, 1] : [255, 255, 255, 1] };
|
|
2131
|
+
},
|
|
2132
|
+
"<light-dark-color>": (node) => {
|
|
2133
|
+
const children = node.value;
|
|
2134
|
+
const lightNode = children[2];
|
|
2135
|
+
const darkNode = children[4];
|
|
2136
|
+
const { model, coords } = parseNode(config?.theme === "light" ? lightNode : darkNode);
|
|
2137
|
+
const { coords: rgbCoords } = new Color(model, coords).in("rgb");
|
|
2138
|
+
return { model: "rgb", coords: rgbCoords };
|
|
2139
|
+
},
|
|
2140
|
+
"<hex-color>": (node) => {
|
|
2141
|
+
const hex = node.value;
|
|
2142
|
+
const len = hex.length;
|
|
2143
|
+
let r = 0, g = 0, b = 0, a = 1;
|
|
2144
|
+
if (len === 4 || len === 5) {
|
|
2145
|
+
const rCh = hex.charCodeAt(1);
|
|
2146
|
+
const gCh = hex.charCodeAt(2);
|
|
2147
|
+
const bCh = hex.charCodeAt(3);
|
|
2148
|
+
const rVal = rCh > 64 ? (rCh & 7) + 9 : rCh & 15;
|
|
2149
|
+
const gVal = gCh > 64 ? (gCh & 7) + 9 : gCh & 15;
|
|
2150
|
+
const bVal = bCh > 64 ? (bCh & 7) + 9 : bCh & 15;
|
|
2151
|
+
r = rVal << 4 | rVal;
|
|
2152
|
+
g = gVal << 4 | gVal;
|
|
2153
|
+
b = bVal << 4 | bVal;
|
|
2154
|
+
if (len === 5) {
|
|
2155
|
+
const aCh = hex.charCodeAt(4);
|
|
2156
|
+
const aVal = aCh > 64 ? (aCh & 7) + 9 : aCh & 15;
|
|
2157
|
+
a = (aVal << 4 | aVal) / 255;
|
|
2158
|
+
}
|
|
2159
|
+
} else {
|
|
2160
|
+
const num = parseInt(hex.slice(1), 16);
|
|
2161
|
+
if (len === 7) {
|
|
2162
|
+
r = num >> 16 & 255;
|
|
2163
|
+
g = num >> 8 & 255;
|
|
2164
|
+
b = num & 255;
|
|
2165
|
+
} else if (len === 9) {
|
|
2166
|
+
r = num >> 24 & 255;
|
|
2167
|
+
g = num >> 16 & 255;
|
|
2168
|
+
b = num >> 8 & 255;
|
|
2169
|
+
a = (num & 255) / 255;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return {
|
|
2173
|
+
model: "rgb",
|
|
2174
|
+
coords: [r, g, b, a]
|
|
2175
|
+
};
|
|
2176
|
+
},
|
|
2177
|
+
"<device-cmyk()>": (node) => parseNode(node.value[0]),
|
|
2178
|
+
"<legacy-device-cmyk-syntax>": (node) => {
|
|
2179
|
+
const t = node.value;
|
|
2180
|
+
const [c, m, y, k] = [t[2], t[4], t[6], t[8]].map(parseCmykComponent);
|
|
2181
|
+
return {
|
|
2182
|
+
model: "rgb",
|
|
2183
|
+
coords: [(1 - (c * (1 - k) + k)) * 255, (1 - (m * (1 - k) + k)) * 255, (1 - (y * (1 - k) + k)) * 255, 1]
|
|
2184
|
+
};
|
|
2185
|
+
},
|
|
2186
|
+
"<modern-device-cmyk-syntax>": (node) => {
|
|
2187
|
+
const t = node.value;
|
|
2188
|
+
const c = evaluateComponent(t[2], [0, 1]);
|
|
2189
|
+
const m = evaluateComponent(t[3], [0, 1]);
|
|
2190
|
+
const y = evaluateComponent(t[4], [0, 1]);
|
|
2191
|
+
const k = evaluateComponent(t[5], [0, 1]);
|
|
2192
|
+
const alphaNode = t.find((c2, idx) => idx === 7 && (c2.type === "<alpha-value>" || c2.type === "literal" && c2.value === "none"));
|
|
2193
|
+
const alpha = alphaNode ? evaluateComponent(alphaNode, [0, 1]) : 1;
|
|
2194
|
+
const cMath = normalize(c, [0, 1]);
|
|
2195
|
+
const mMath = normalize(m, [0, 1]);
|
|
2196
|
+
const yMath = normalize(y, [0, 1]);
|
|
2197
|
+
const kMath = normalize(k, [0, 1]);
|
|
2198
|
+
const alphaMath = isNaN(alpha) ? alpha : Math.max(0, Math.min(1, alpha));
|
|
2199
|
+
return {
|
|
2200
|
+
model: "rgb",
|
|
2201
|
+
coords: [
|
|
2202
|
+
(1 - (cMath * (1 - kMath) + kMath)) * 255,
|
|
2203
|
+
(1 - (mMath * (1 - kMath) + kMath)) * 255,
|
|
2204
|
+
(1 - (yMath * (1 - kMath) + kMath)) * 255,
|
|
2205
|
+
alphaMath
|
|
2206
|
+
]
|
|
2207
|
+
};
|
|
2208
|
+
},
|
|
2209
|
+
"<color-mix()>": (node) => {
|
|
2210
|
+
let model = "oklab";
|
|
2211
|
+
let hue = "shorter";
|
|
2212
|
+
const children = node.value;
|
|
2213
|
+
let i = 2;
|
|
2214
|
+
if (children[i]?.type === "<color-interpolation-method>") {
|
|
2215
|
+
const methodParts = children[i].value;
|
|
2216
|
+
i++;
|
|
2217
|
+
if (methodParts[1]) {
|
|
2218
|
+
model = getLiteralValue(methodParts[1]);
|
|
2219
|
+
}
|
|
2220
|
+
if (methodParts[2] && methodParts[2].type !== "symbol") {
|
|
2221
|
+
hue = getLiteralValue(methodParts[2]);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
const items = [];
|
|
2225
|
+
while (i < children.length) {
|
|
2226
|
+
const current = children[i];
|
|
2227
|
+
if (current.type === "symbol" && (current.value === "," || current.value === ")")) {
|
|
2228
|
+
i++;
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
let percentage = void 0;
|
|
2232
|
+
let colorNode;
|
|
2233
|
+
if (current.type === "<percentage>") {
|
|
2234
|
+
const tokenStr = String(current.value || "");
|
|
2235
|
+
percentage = evaluateCSSValue(tokenStr, "percentage");
|
|
2236
|
+
i++;
|
|
2237
|
+
colorNode = children[i];
|
|
2238
|
+
} else {
|
|
2239
|
+
colorNode = current;
|
|
2240
|
+
const nextNode = children[i + 1];
|
|
2241
|
+
if (nextNode?.type === "<percentage>") {
|
|
2242
|
+
const tokenStr = String(nextNode.value || "");
|
|
2243
|
+
percentage = evaluateCSSValue(tokenStr, "percentage");
|
|
1861
2244
|
i++;
|
|
1862
|
-
continue;
|
|
1863
2245
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2246
|
+
}
|
|
2247
|
+
const colorData = colorNode ? parseNode(colorNode) : null;
|
|
2248
|
+
if (colorData && colorData.coords) {
|
|
2249
|
+
i++;
|
|
2250
|
+
items.push({
|
|
2251
|
+
color: new Color(colorData.model, colorData.coords),
|
|
2252
|
+
percentage
|
|
2253
|
+
});
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
i++;
|
|
2257
|
+
}
|
|
2258
|
+
if (items.length === 0) {
|
|
2259
|
+
throw new Error("color-mix() must contain at least one color component.");
|
|
2260
|
+
}
|
|
2261
|
+
const mixedResult = Color.mix(items, { in: model, hue });
|
|
2262
|
+
return {
|
|
2263
|
+
model: mixedResult.model,
|
|
2264
|
+
coords: mixedResult.coords
|
|
2265
|
+
};
|
|
2266
|
+
},
|
|
2267
|
+
"<color-function>": (node) => parseNode(node.value[0]),
|
|
2268
|
+
"<custom-color-function>": (node) => parseNode(node.value[0]),
|
|
2269
|
+
"<rgb()>": (node) => parseNode(node.value[0]),
|
|
2270
|
+
"<rgba()>": (node) => parseNode(node.value[0]),
|
|
2271
|
+
"<hsl()>": (node) => parseNode(node.value[0]),
|
|
2272
|
+
"<hsla()>": (node) => parseNode(node.value[0]),
|
|
2273
|
+
"<hwb()>": (node) => parseNode(node.value[0]),
|
|
2274
|
+
"<lab()>": (node) => parseNode(node.value[0]),
|
|
2275
|
+
"<lch()>": (node) => parseNode(node.value[0]),
|
|
2276
|
+
"<oklab()>": (node) => parseNode(node.value[0]),
|
|
2277
|
+
"<oklch()>": (node) => parseNode(node.value[0]),
|
|
2278
|
+
"<legacy-rgb-syntax>": createLegacyColorParser("rgb", ["r", "g", "b"]),
|
|
2279
|
+
"<legacy-rgba-syntax>": createLegacyColorParser("rgb", ["r", "g", "b"]),
|
|
2280
|
+
"<legacy-hsl-syntax>": createLegacyColorParser("hsl", ["h", "s", "l"]),
|
|
2281
|
+
"<legacy-hsla-syntax>": createLegacyColorParser("hsl", ["h", "s", "l"]),
|
|
2282
|
+
"<modern-rgb-syntax>": createModernColorParser("rgb", ["r", "g", "b"]),
|
|
2283
|
+
"<modern-rgba-syntax>": createModernColorParser("rgb", ["r", "g", "b"]),
|
|
2284
|
+
"<modern-hsl-syntax>": createModernColorParser("hsl", ["h", "s", "l"]),
|
|
2285
|
+
"<modern-hsla-syntax>": createModernColorParser("hsl", ["h", "s", "l"]),
|
|
2286
|
+
"<absolute-hwb-syntax>": createModernColorParser("hwb", ["h", "w", "b"]),
|
|
2287
|
+
"<absolute-lab-syntax>": createModernColorParser("lab", ["l", "a", "b"]),
|
|
2288
|
+
"<absolute-lch-syntax>": createModernColorParser("lch", ["l", "c", "h"]),
|
|
2289
|
+
"<absolute-oklab-syntax>": createModernColorParser("oklab", ["l", "a", "b"]),
|
|
2290
|
+
"<absolute-oklch-syntax>": createModernColorParser("oklch", ["l", "c", "h"]),
|
|
2291
|
+
"<relative-rgb-syntax>": createRelativeColorParser("rgb", ["r", "g", "b"]),
|
|
2292
|
+
"<relative-rgba-syntax>": createRelativeColorParser("rgb", ["r", "g", "b"]),
|
|
2293
|
+
"<relative-hsl-syntax>": createRelativeColorParser("hsl", ["h", "s", "l"]),
|
|
2294
|
+
"<relative-hsla-syntax>": createRelativeColorParser("hsl", ["h", "s", "l"]),
|
|
2295
|
+
"<relative-hwb-syntax>": createRelativeColorParser("hwb", ["h", "w", "b"]),
|
|
2296
|
+
"<relative-lab-syntax>": createRelativeColorParser("lab", ["l", "a", "b"]),
|
|
2297
|
+
"<relative-lch-syntax>": createRelativeColorParser("lch", ["l", "c", "h"]),
|
|
2298
|
+
"<relative-oklab-syntax>": createRelativeColorParser("oklab", ["l", "a", "b"]),
|
|
2299
|
+
"<relative-oklch-syntax>": createRelativeColorParser("oklch", ["l", "c", "h"]),
|
|
2300
|
+
"<alpha()>": (node) => {
|
|
2301
|
+
const children = node.value;
|
|
2302
|
+
const colorNode = children[3];
|
|
2303
|
+
const originColor = parseNode(colorNode);
|
|
2304
|
+
if (!originColor)
|
|
2305
|
+
throw new Error("Missing or invalid origin color in alpha() function");
|
|
2306
|
+
const originInstance = new Color(originColor.model, originColor.coords);
|
|
2307
|
+
const baseEnv = originInstance.toObject({ fit: "none", precision: null });
|
|
2308
|
+
const alphaNode = children.find((c, idx) => c.type === "<alpha-value>" || idx === 5 && c.type === "literal" && c.value === "none");
|
|
2309
|
+
const targetAlpha = alphaNode ? evaluateComponent(alphaNode, [0, 1], baseEnv) : originColor.coords[3];
|
|
2310
|
+
return {
|
|
2311
|
+
model: originColor.model,
|
|
2312
|
+
coords: [...originColor.coords.slice(0, 3), targetAlpha]
|
|
2313
|
+
};
|
|
2314
|
+
},
|
|
2315
|
+
"<color()>": (node) => parseNode(node.value[0]),
|
|
2316
|
+
"<relative-color-syntax>": (node) => parseNode(node.value[0]),
|
|
2317
|
+
"<absolute-color-syntax>": (node) => {
|
|
2318
|
+
const t = node.value;
|
|
2319
|
+
const spaceNode = t[2];
|
|
2320
|
+
const model = Array.isArray(spaceNode.value) ? spaceNode.value[0]?.value : spaceNode.value;
|
|
2321
|
+
const c1 = evaluateComponent(t[3], [0, 1]);
|
|
2322
|
+
const c2 = evaluateComponent(t[4], [0, 1]);
|
|
2323
|
+
const c3 = evaluateComponent(t[5], [0, 1]);
|
|
2324
|
+
const alphaNode = t.find((c, idx) => idx > 5 && (c.type === "<alpha-value>" || c.type === "literal" && c.value === "none"));
|
|
2325
|
+
const alpha = alphaNode ? evaluateComponent(alphaNode, [0, 1]) : 1;
|
|
2326
|
+
return {
|
|
2327
|
+
model,
|
|
2328
|
+
coords: [c1, c2, c3, alpha]
|
|
2329
|
+
};
|
|
2330
|
+
},
|
|
2331
|
+
"<relative-rgb-color-syntax>": createRelativeColorFunctionParser,
|
|
2332
|
+
"<relative-xyz-color-syntax>": createRelativeColorFunctionParser,
|
|
2333
|
+
"<relative-custom-color-syntax>": (node) => parseNode(node.value[0])
|
|
2334
|
+
};
|
|
2335
|
+
var formatters = (() => {
|
|
2336
|
+
const colorFunctionFormatters = (() => {
|
|
2337
|
+
const out = {};
|
|
2338
|
+
for (const model of Object.keys(colorModels)) {
|
|
2339
|
+
const { bridge, fromBridge, supportsLegacy, components, alphaVariant } = colorModels[model];
|
|
2340
|
+
out[model] = {
|
|
2341
|
+
bridge,
|
|
2342
|
+
fromBridge: (coords) => [...fromBridge(coords), coords[3] ?? 1],
|
|
2343
|
+
format: ([c1, c2, c3, a = 1], options = {}) => {
|
|
2344
|
+
const { legacy = false, fit: fitMethod = config.defaults.fit, precision, units = false } = options;
|
|
2345
|
+
const fitted = fit([c1, c2, c3], model, { method: fitMethod, precision });
|
|
2346
|
+
const formatted = [...fitted, a].map((c, index) => {
|
|
2347
|
+
const norm = normalize(c, Object.values(components)[index].value);
|
|
2348
|
+
if ((units || legacy) && components) {
|
|
2349
|
+
const def = Object.values(components).find((comp) => comp.index === index);
|
|
2350
|
+
if (def?.value === "percentage")
|
|
2351
|
+
return `${norm}%`;
|
|
2352
|
+
if (def?.value === "hue" && units)
|
|
2353
|
+
return `${norm}deg`;
|
|
2354
|
+
}
|
|
2355
|
+
return norm.toString();
|
|
2356
|
+
});
|
|
2357
|
+
const f = formatted.slice(0, 3);
|
|
2358
|
+
const alpha = formatted[3];
|
|
2359
|
+
if (model in colorSpaces)
|
|
2360
|
+
return `color(${model} ${f.join(" ")}${a !== 1 ? ` / ${alpha}` : ""})`;
|
|
2361
|
+
if (legacy && supportsLegacy) {
|
|
2362
|
+
return a === 1 ? `${model}(${f.join(", ")})` : `${alphaVariant || model}(${f.join(", ")}, ${alpha})`;
|
|
1870
2363
|
}
|
|
2364
|
+
return `${model}(${f.join(" ")}${a !== 1 ? ` / ${alpha}` : ""})`;
|
|
1871
2365
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
return out;
|
|
2369
|
+
})();
|
|
2370
|
+
return {
|
|
2371
|
+
...colorFunctionFormatters,
|
|
2372
|
+
"hex-color": {
|
|
2373
|
+
bridge: "rgb",
|
|
2374
|
+
fromBridge: (coords) => coords,
|
|
2375
|
+
format: ([r, g, b, a = 1]) => {
|
|
2376
|
+
const toHex = (v) => v.toString(16).padStart(2, "0");
|
|
2377
|
+
const hex = [r, g, b].map((v) => toHex(Math.round(Math.max(0, Math.min(255, v))))).join("");
|
|
2378
|
+
return `#${hex}${a < 1 ? toHex(Math.round(a * 255)) : ""}`;
|
|
1874
2379
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2380
|
+
},
|
|
2381
|
+
"named-color": {
|
|
2382
|
+
bridge: "rgb",
|
|
2383
|
+
fromBridge: (coords) => coords,
|
|
2384
|
+
format: (rgb) => {
|
|
2385
|
+
const [r, g, b] = rgb.map((v, i) => i < 3 ? Math.round(Math.min(255, Math.max(0, v))) : v);
|
|
2386
|
+
for (const [name, [nr, ng, nb]] of Object.entries(namedColors)) {
|
|
2387
|
+
if (r === nr && g === ng && b === nb)
|
|
2388
|
+
return name;
|
|
2389
|
+
}
|
|
1878
2390
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2391
|
+
},
|
|
2392
|
+
"device-cmyk": {
|
|
2393
|
+
bridge: "rgb",
|
|
2394
|
+
fromBridge: (coords) => coords,
|
|
2395
|
+
format: ([red, green, blue, alpha = 1], options = {}) => {
|
|
2396
|
+
const { legacy = false, precision = 3, fit: fitMethod = config.defaults.fit } = options;
|
|
2397
|
+
const [fr, fg, fb] = fit([red, green, blue], "rgb", { method: fitMethod });
|
|
2398
|
+
const r = fr / 255;
|
|
2399
|
+
const g = fg / 255;
|
|
2400
|
+
const b = fb / 255;
|
|
2401
|
+
const k = 1 - Math.max(r, g, b);
|
|
2402
|
+
const formatComponent = (value) => {
|
|
2403
|
+
return Number(precision === null ? value : value.toFixed(precision)).toString();
|
|
2404
|
+
};
|
|
2405
|
+
const c = formatComponent(k === 1 ? 0 : (1 - r - k) / (1 - k));
|
|
2406
|
+
const m = formatComponent(k === 1 ? 0 : (1 - g - k) / (1 - k));
|
|
2407
|
+
const y = formatComponent(k === 1 ? 0 : (1 - b - k) / (1 - k));
|
|
2408
|
+
const kFormatted = formatComponent(k);
|
|
2409
|
+
const alphaFormatted = Number(alpha.toFixed(3)).toString();
|
|
2410
|
+
if (legacy) {
|
|
2411
|
+
return `device-cmyk(${c}, ${m}, ${y}, ${kFormatted}${alpha < 1 ? `, ${alphaFormatted}` : ""})`;
|
|
2412
|
+
}
|
|
2413
|
+
const rgbPart = colorFunctionFormatters.rgb.format?.([fr, fg, fb, alpha], options);
|
|
2414
|
+
return `device-cmyk(${c} ${m} ${y} ${kFormatted}${alpha < 1 ? ` / ${alphaFormatted}` : ""}, ${rgbPart})`;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
})();
|
|
2419
|
+
function parseNode(node) {
|
|
2420
|
+
const parser = parsers[node.type];
|
|
2421
|
+
if (!parser)
|
|
2422
|
+
throw new Error(`No parser for rule: ${node.type}`);
|
|
2423
|
+
return parser(node);
|
|
2424
|
+
}
|
|
2425
|
+
function isValid(input, rule) {
|
|
2426
|
+
const cleaned = input.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));
|
|
2427
|
+
const tokens = tokenize(cleaned);
|
|
2428
|
+
const memo = /* @__PURE__ */ new Map();
|
|
2429
|
+
if (rule) {
|
|
2430
|
+
if (grammarMap[rule]) {
|
|
2431
|
+
const results = matchRule(rule, tokens, 0, grammarMap, memo, null);
|
|
2432
|
+
return results.some((result) => result.success && result.nextIndex === tokens.length);
|
|
2433
|
+
}
|
|
2434
|
+
if (validators[rule]) {
|
|
2435
|
+
const results = matchRule(rule, tokens, 0, grammarMap, memo, null);
|
|
2436
|
+
return results.some((result) => result.success && result.nextIndex === tokens.length);
|
|
2437
|
+
}
|
|
2438
|
+
try {
|
|
2439
|
+
const inlineNode = parseExpression(rule);
|
|
2440
|
+
const results = matchNode(inlineNode, tokens, 0, grammarMap, memo, null);
|
|
2441
|
+
return results.some((result) => result.success && result.nextIndex === tokens.length);
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
throw new Error(`Failed to resolve rule or parse inline expression: "${rule}"`, { cause: error });
|
|
1882
2444
|
}
|
|
1883
2445
|
}
|
|
1884
|
-
|
|
2446
|
+
const allRules = /* @__PURE__ */ new Set([...Object.keys(grammarMap), ...Object.keys(validators)]);
|
|
2447
|
+
for (const ruleName of allRules) {
|
|
2448
|
+
const results = matchRule(ruleName, tokens, 0, grammarMap, memo, null);
|
|
2449
|
+
const isMatch = results.some((result) => result.success && result.nextIndex === tokens.length);
|
|
2450
|
+
if (isMatch)
|
|
2451
|
+
return true;
|
|
2452
|
+
}
|
|
2453
|
+
return false;
|
|
2454
|
+
}
|
|
1885
2455
|
|
|
1886
2456
|
// dist/Color.js
|
|
1887
2457
|
var Color = class _Color {
|
|
@@ -1899,59 +2469,26 @@ var Saturon = (() => {
|
|
|
1899
2469
|
this.coords = c;
|
|
1900
2470
|
}
|
|
1901
2471
|
static from(color) {
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
const {
|
|
1906
|
-
if (!isValid(c))
|
|
1907
|
-
continue;
|
|
1908
|
-
const parsed = parse(c);
|
|
1909
|
-
const coords = t in colorModels ? parsed : toBridge(parsed);
|
|
1910
|
-
const model = t in colorModels ? t : bridge;
|
|
2472
|
+
const tokens = tokenize(color);
|
|
2473
|
+
const tree = validateTokens(tokens);
|
|
2474
|
+
try {
|
|
2475
|
+
const { model, coords } = parseNode(tree);
|
|
1911
2476
|
return new _Color(model, coords);
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
throw new Error(`Failed to parse color string '${color}'`, { cause: error });
|
|
1912
2479
|
}
|
|
1913
|
-
throw new Error(`Unsupported or invalid color format: '${color}'.`);
|
|
1914
2480
|
}
|
|
1915
2481
|
/**
|
|
1916
|
-
*
|
|
2482
|
+
* Validates a color string.
|
|
1917
2483
|
*
|
|
1918
|
-
* @param color - Color string to
|
|
1919
|
-
* @param
|
|
2484
|
+
* @param color - Color string to check.
|
|
2485
|
+
* @param options - Validation options.
|
|
2486
|
+
* @returns `true` if valid, otherwise `false`.
|
|
1920
2487
|
*/
|
|
1921
|
-
static
|
|
1922
|
-
const
|
|
1923
|
-
for (const type in colorTypes) {
|
|
1924
|
-
const t = type;
|
|
1925
|
-
const { isValid, bridge, parse, toBridge } = colorTypes[t];
|
|
1926
|
-
if (!isValid(c))
|
|
1927
|
-
continue;
|
|
1928
|
-
if (!strict)
|
|
1929
|
-
return t;
|
|
1930
|
-
try {
|
|
1931
|
-
const parsed = parse(c);
|
|
1932
|
-
const coords = t in colorModels ? parsed : toBridge(parsed);
|
|
1933
|
-
const model = type in colorModels ? type : bridge;
|
|
1934
|
-
return typeof new _Color(model, coords) === "object" ? t : void 0;
|
|
1935
|
-
} catch {
|
|
1936
|
-
return void 0;
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
return void 0;
|
|
1940
|
-
}
|
|
1941
|
-
static isValid(color, type) {
|
|
2488
|
+
static isValid(color, options = {}) {
|
|
2489
|
+
const { rule } = options;
|
|
1942
2490
|
try {
|
|
1943
|
-
|
|
1944
|
-
const t = type?.trim().toLowerCase();
|
|
1945
|
-
const c = clean(color);
|
|
1946
|
-
const { isValid, bridge, parse, toBridge } = colorTypes[t];
|
|
1947
|
-
if (!isValid(c))
|
|
1948
|
-
return false;
|
|
1949
|
-
const parsed = parse(c);
|
|
1950
|
-
const coords = t in colorModels ? parsed : toBridge(parsed);
|
|
1951
|
-
const model = t in colorModels ? t : bridge;
|
|
1952
|
-
return !!new _Color(model, coords);
|
|
1953
|
-
}
|
|
1954
|
-
return !!_Color.from(color);
|
|
2491
|
+
return isValid(color, rule) && !!_Color.from(color);
|
|
1955
2492
|
} catch {
|
|
1956
2493
|
return false;
|
|
1957
2494
|
}
|
|
@@ -1968,7 +2505,7 @@ var Saturon = (() => {
|
|
|
1968
2505
|
const models = Object.keys(colorModels);
|
|
1969
2506
|
const model = options.model ?? models[Math.floor(Math.random() * models.length)];
|
|
1970
2507
|
const { components } = colorModels[model];
|
|
1971
|
-
const valid =
|
|
2508
|
+
const valid = new Set(Object.keys(components));
|
|
1972
2509
|
for (const section of ["limits", "bias", "base", "deviation"]) {
|
|
1973
2510
|
const record = options[section];
|
|
1974
2511
|
if (!record)
|
|
@@ -1989,36 +2526,91 @@ var Saturon = (() => {
|
|
|
1989
2526
|
const v = Math.random() || 1e-9;
|
|
1990
2527
|
value = base + Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) * dev;
|
|
1991
2528
|
} else {
|
|
1992
|
-
let [
|
|
2529
|
+
let [min2, max2] = comp.value === "hue" ? [0, 360] : comp.value === "percentage" ? [0, 100] : comp.value;
|
|
1993
2530
|
const limits = options.limits?.[name];
|
|
1994
2531
|
if (limits) {
|
|
1995
2532
|
const [lMin, lMax] = limits;
|
|
1996
|
-
|
|
1997
|
-
|
|
2533
|
+
min2 = Math.max(min2, lMin);
|
|
2534
|
+
max2 = Math.min(max2, lMax);
|
|
1998
2535
|
}
|
|
1999
2536
|
let r = Math.random();
|
|
2000
2537
|
const biasFn = options.bias?.[name];
|
|
2001
2538
|
if (biasFn)
|
|
2002
2539
|
r = biasFn(r);
|
|
2003
|
-
value =
|
|
2540
|
+
value = min2 + r * (max2 - min2);
|
|
2004
2541
|
}
|
|
2005
|
-
if (comp.value === "
|
|
2542
|
+
if (comp.value === "hue")
|
|
2006
2543
|
value = (value % 360 + 360) % 360;
|
|
2007
2544
|
else if (comp.value === "percentage")
|
|
2008
2545
|
value = Math.min(100, Math.max(0, value));
|
|
2009
2546
|
else if (Array.isArray(comp.value)) {
|
|
2010
|
-
const [
|
|
2011
|
-
value = Math.min(
|
|
2547
|
+
const [min2, max2] = comp.value;
|
|
2548
|
+
value = Math.min(max2, Math.max(min2, value));
|
|
2012
2549
|
} else
|
|
2013
2550
|
throw new Error(`Invalid component value definition for "${name}".`);
|
|
2014
2551
|
coords[comp.index] = value;
|
|
2015
2552
|
}
|
|
2016
2553
|
return new _Color(model, coords);
|
|
2017
2554
|
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Statically mixes multiple colors together by specified percentages.
|
|
2557
|
+
* Fully compliant with CSS Color Module Level 5 specification constraints.
|
|
2558
|
+
*
|
|
2559
|
+
* @param colors - Array of MixItems containing colors and optional percentages.
|
|
2560
|
+
* @param options - Options containing the interpolation space and hue policy.
|
|
2561
|
+
* @returns A new `Color` instance representing the mixed color.
|
|
2562
|
+
*/
|
|
2563
|
+
static mix(colors, options) {
|
|
2564
|
+
const { in: model = "oklab", hue = "shorter" } = options;
|
|
2565
|
+
if (!colors || colors.length === 0) {
|
|
2566
|
+
throw new Error("At least one color must be provided in the colors array.");
|
|
2567
|
+
}
|
|
2568
|
+
if (colors.length === 1)
|
|
2569
|
+
return colors[0].color.in(model);
|
|
2570
|
+
const weights = colors.map((c) => c.percentage !== void 0 ? c.percentage / 100 : void 0);
|
|
2571
|
+
const definedSum = weights.reduce((sum, w) => sum + (w || 0), 0);
|
|
2572
|
+
const missingCount = weights.filter((w) => w === void 0).length;
|
|
2573
|
+
let alphaMult = 1;
|
|
2574
|
+
if (weights.length > 0 && weights.every((w) => w === 0)) {
|
|
2575
|
+
alphaMult = 0;
|
|
2576
|
+
for (let i = 0; i < weights.length; i++)
|
|
2577
|
+
weights[i] = 1 / weights.length;
|
|
2578
|
+
} else if (missingCount === 0 && definedSum < 1) {
|
|
2579
|
+
alphaMult = definedSum;
|
|
2580
|
+
}
|
|
2581
|
+
if (alphaMult > 0) {
|
|
2582
|
+
if (missingCount > 0) {
|
|
2583
|
+
const remaining = Math.max(0, 1 - definedSum);
|
|
2584
|
+
const share = remaining / missingCount;
|
|
2585
|
+
for (let i = 0; i < weights.length; i++) {
|
|
2586
|
+
if (weights[i] === void 0)
|
|
2587
|
+
weights[i] = share;
|
|
2588
|
+
}
|
|
2589
|
+
} else if (definedSum > 0 && definedSum !== 1) {
|
|
2590
|
+
for (let i = 0; i < weights.length; i++) {
|
|
2591
|
+
weights[i] = weights[i] / definedSum;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
let accColor = colors[0].color.in(model);
|
|
2596
|
+
let accWeight = weights[0];
|
|
2597
|
+
for (let i = 1; i < colors.length; i++) {
|
|
2598
|
+
const currentWeight = weights[i];
|
|
2599
|
+
accWeight += currentWeight;
|
|
2600
|
+
const t = accWeight === 0 ? 0 : currentWeight / accWeight;
|
|
2601
|
+
accColor = mixTwo(accColor, colors[i].color.in(model), t, model, hue);
|
|
2602
|
+
}
|
|
2603
|
+
if (alphaMult !== 1) {
|
|
2604
|
+
const finalCoords = [...accColor.coords];
|
|
2605
|
+
finalCoords[3] = (finalCoords[3] ?? 1) * alphaMult;
|
|
2606
|
+
return new _Color(model, finalCoords);
|
|
2607
|
+
}
|
|
2608
|
+
return accColor;
|
|
2609
|
+
}
|
|
2018
2610
|
to(type, options = {}) {
|
|
2019
2611
|
const t = type.toLowerCase();
|
|
2020
2612
|
const { legacy = false, fit: fit2 = config.defaults.fit, precision, units = false } = options;
|
|
2021
|
-
const conv =
|
|
2613
|
+
const conv = formatters[t];
|
|
2022
2614
|
if (!conv)
|
|
2023
2615
|
throw new Error(`Unsupported color type: '${t}'.`);
|
|
2024
2616
|
const { fromBridge, bridge, format } = conv;
|
|
@@ -2052,7 +2644,6 @@ var Saturon = (() => {
|
|
|
2052
2644
|
cache.set("paths", paths);
|
|
2053
2645
|
}
|
|
2054
2646
|
const key = `${from}-${to}`;
|
|
2055
|
-
const coords = this.coords.slice(0, 3);
|
|
2056
2647
|
if (!paths.has(key)) {
|
|
2057
2648
|
const queue = [from], parent = { [from]: null };
|
|
2058
2649
|
for (let i = 0; i < queue.length; i++) {
|
|
@@ -2075,7 +2666,7 @@ var Saturon = (() => {
|
|
|
2075
2666
|
}
|
|
2076
2667
|
paths.set(key, path2);
|
|
2077
2668
|
}
|
|
2078
|
-
let value = [...coords];
|
|
2669
|
+
let value = [...this.coords.map((v) => isNaN(v) ? 0 : v)];
|
|
2079
2670
|
const path = paths.get(key);
|
|
2080
2671
|
for (let i = 0; i < path.length - 1; i++) {
|
|
2081
2672
|
const a = path[i], b = path[i + 1];
|
|
@@ -2097,7 +2688,7 @@ var Saturon = (() => {
|
|
|
2097
2688
|
* @returns The formatted color string.
|
|
2098
2689
|
*/
|
|
2099
2690
|
toString(options = {}) {
|
|
2100
|
-
const { format } =
|
|
2691
|
+
const { format } = formatters[this.model];
|
|
2101
2692
|
const { legacy = false, fit: fit2 = config.defaults.fit, precision, units = false } = options;
|
|
2102
2693
|
return format?.(this.coords, { legacy, fit: fit2, precision, units });
|
|
2103
2694
|
}
|
|
@@ -2105,20 +2696,17 @@ var Saturon = (() => {
|
|
|
2105
2696
|
* Returns the color as an object of component values.
|
|
2106
2697
|
*
|
|
2107
2698
|
* @param options - Optional retrieval options.
|
|
2108
|
-
* @returns An object mapping each component
|
|
2699
|
+
* @returns An object mapping each component to its numeric value.
|
|
2109
2700
|
* @throws If the model has no defined components.
|
|
2110
2701
|
*/
|
|
2702
|
+
// eslint-disable-next-line no-unused-vars
|
|
2111
2703
|
toObject(options = {}) {
|
|
2112
2704
|
const coords = this.toArray(options);
|
|
2113
2705
|
const { components } = colorModels[this.model];
|
|
2114
2706
|
if (!components)
|
|
2115
2707
|
throw new Error(`Model ${this.model} does not have defined components.`);
|
|
2116
|
-
const fullComponents = {
|
|
2117
|
-
...components,
|
|
2118
|
-
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
2119
|
-
};
|
|
2120
2708
|
const result = {};
|
|
2121
|
-
for (const [name, { index }] of Object.entries(
|
|
2709
|
+
for (const [name, { index }] of Object.entries(components))
|
|
2122
2710
|
result[name] = coords[index];
|
|
2123
2711
|
return result;
|
|
2124
2712
|
}
|
|
@@ -2126,7 +2714,7 @@ var Saturon = (() => {
|
|
|
2126
2714
|
* Returns the color as an array of component values, optionally normalized and fitted.
|
|
2127
2715
|
*
|
|
2128
2716
|
* @param options - Conversion configuration.
|
|
2129
|
-
* @returns An array of normalized color components
|
|
2717
|
+
* @returns An array of normalized color components.
|
|
2130
2718
|
* @throws If the model has no defined components.
|
|
2131
2719
|
*/
|
|
2132
2720
|
toArray(options = {}) {
|
|
@@ -2135,30 +2723,15 @@ var Saturon = (() => {
|
|
|
2135
2723
|
const { components } = colorModels[model];
|
|
2136
2724
|
if (!components)
|
|
2137
2725
|
throw new Error(`Model ${model} does not have defined components.`);
|
|
2138
|
-
const
|
|
2139
|
-
...components,
|
|
2140
|
-
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
2141
|
-
};
|
|
2142
|
-
const normalize = (c, i) => {
|
|
2143
|
-
const v = Object.values(defs)[i]?.value;
|
|
2144
|
-
const [min, max] = Array.isArray(v) ? v : v === "angle" ? [0, 360] : [0, 100];
|
|
2145
|
-
if (Number.isNaN(c))
|
|
2146
|
-
return 0;
|
|
2147
|
-
if (c === Infinity)
|
|
2148
|
-
return max;
|
|
2149
|
-
if (c === -Infinity)
|
|
2150
|
-
return min;
|
|
2151
|
-
return typeof c === "number" ? c : 0;
|
|
2152
|
-
};
|
|
2153
|
-
const norm = coords.slice(0, 3).map(normalize);
|
|
2726
|
+
const norm = coords.slice(0, 3).map((c, i) => normalize(c, Object.values(components)[i].value));
|
|
2154
2727
|
const fitted = fit(norm, model, {
|
|
2155
2728
|
method,
|
|
2156
2729
|
precision
|
|
2157
2730
|
});
|
|
2158
|
-
return [...fitted
|
|
2731
|
+
return [...fitted, coords[3]];
|
|
2159
2732
|
}
|
|
2160
2733
|
/**
|
|
2161
|
-
*
|
|
2734
|
+
* Creates a new Color instance with modified component values.
|
|
2162
2735
|
*
|
|
2163
2736
|
* This method supports several flexible update styles:
|
|
2164
2737
|
*
|
|
@@ -2197,143 +2770,58 @@ var Saturon = (() => {
|
|
|
2197
2770
|
* color.with(({ r, g, b }) => [r * 0.5, g * 0.5, b * 0.5, 1]);
|
|
2198
2771
|
* ```
|
|
2199
2772
|
*
|
|
2200
|
-
* @
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2205
|
-
* @
|
|
2773
|
+
* @template M - The color model type
|
|
2774
|
+
* @param values - The new component values to apply. Can be:
|
|
2775
|
+
* - A partial object mapping component names to numbers or update functions
|
|
2776
|
+
* - A function that receives current components and returns partial updates or an array of values
|
|
2777
|
+
* - An array of new values corresponding to component indices
|
|
2778
|
+
* @param normalized - Whether to normalize component values to their valid ranges. Defaults to `true`.
|
|
2779
|
+
* When `false`, values are not clamped or validated against their ranges.
|
|
2780
|
+
* @returns A new Color instance with the updated component values
|
|
2206
2781
|
*/
|
|
2207
2782
|
/* eslint-disable no-unused-vars */
|
|
2208
2783
|
with(values) {
|
|
2209
2784
|
const { model } = this;
|
|
2210
|
-
const coords = this.
|
|
2785
|
+
const coords = this.coords.slice();
|
|
2211
2786
|
const { components } = colorModels[model];
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
};
|
|
2218
|
-
const names = Object.keys(defs);
|
|
2219
|
-
let newValues;
|
|
2220
|
-
if (typeof values === "function") {
|
|
2221
|
-
const result = values(Object.fromEntries(names.map((c) => [c, coords[defs[c].index]])));
|
|
2222
|
-
newValues = result;
|
|
2223
|
-
} else {
|
|
2224
|
-
newValues = values;
|
|
2225
|
-
}
|
|
2787
|
+
const names = Object.keys(components);
|
|
2788
|
+
const componentDefs = Object.values(components);
|
|
2789
|
+
const newValues = typeof values === "function" ? values(Object.fromEntries(names.map((k) => {
|
|
2790
|
+
const def = components[k];
|
|
2791
|
+
return [k, normalize(coords[def.index], def.value)];
|
|
2792
|
+
}))) : values;
|
|
2226
2793
|
if (Array.isArray(newValues)) {
|
|
2227
2794
|
const adjusted = coords.map((curr, i) => {
|
|
2228
2795
|
const incoming = newValues[i];
|
|
2229
|
-
const { value } = Object.values(defs).find((d) => d.index === i);
|
|
2230
2796
|
if (typeof incoming !== "number")
|
|
2231
2797
|
return curr;
|
|
2232
|
-
const
|
|
2233
|
-
|
|
2234
|
-
return 0;
|
|
2235
|
-
if (incoming === Infinity)
|
|
2236
|
-
return max;
|
|
2237
|
-
if (incoming === -Infinity)
|
|
2238
|
-
return min;
|
|
2239
|
-
return incoming;
|
|
2798
|
+
const def = componentDefs.find((d) => d.index === i);
|
|
2799
|
+
return normalize(incoming, def.value);
|
|
2240
2800
|
});
|
|
2241
|
-
return new _Color(model, [...adjusted.slice(0, 3), coords[3]]);
|
|
2801
|
+
return new _Color(model, [...adjusted.slice(0, 3), coords[3] ?? 1]);
|
|
2242
2802
|
}
|
|
2243
2803
|
const next = [...coords];
|
|
2244
2804
|
for (const name of names) {
|
|
2245
2805
|
if (!(name in newValues))
|
|
2246
2806
|
continue;
|
|
2247
|
-
const { index, value } =
|
|
2248
|
-
const current = coords[index];
|
|
2807
|
+
const { index, value } = components[name];
|
|
2249
2808
|
const raw = newValues[name];
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const [min, max] = Array.isArray(value) ? value : value === "angle" ? [0, 360] : [0, 100];
|
|
2253
|
-
if (Number.isNaN(val))
|
|
2254
|
-
val = 0;
|
|
2255
|
-
else if (val === Infinity)
|
|
2256
|
-
val = max;
|
|
2257
|
-
else if (val === -Infinity)
|
|
2258
|
-
val = min;
|
|
2259
|
-
}
|
|
2809
|
+
const prev = normalize(coords[index], value);
|
|
2810
|
+
const val = typeof raw === "function" ? normalize(raw(prev), value) : typeof raw === "number" ? normalize(raw, value) : coords[index];
|
|
2260
2811
|
next[index] = val;
|
|
2261
2812
|
}
|
|
2262
2813
|
return new _Color(model, [...next.slice(0, 3), next[3] ?? coords[3]]);
|
|
2263
2814
|
}
|
|
2264
2815
|
/**
|
|
2265
|
-
*
|
|
2816
|
+
* Creates a new Color instance with values fitted to the color model's gamut.
|
|
2266
2817
|
*
|
|
2267
|
-
* @param
|
|
2268
|
-
* @
|
|
2269
|
-
* @returns A new `Color` instance representing the mixed color.
|
|
2270
|
-
* @throws If the color model does not have defined components.
|
|
2818
|
+
* @param options - Configuration options for fitting
|
|
2819
|
+
* @returns A new Color instance with fitted values
|
|
2271
2820
|
*/
|
|
2272
|
-
|
|
2273
|
-
const {
|
|
2274
|
-
const
|
|
2275
|
-
|
|
2276
|
-
if (!components)
|
|
2277
|
-
throw new Error(`Model ${model} does not have defined components.`);
|
|
2278
|
-
const { hue = "shorter", amount = 0.5, easing = "linear", gamma = 1 } = options;
|
|
2279
|
-
const defs = {
|
|
2280
|
-
...components,
|
|
2281
|
-
alpha: { index: 3, value: [0, 1], precision: 3 }
|
|
2282
|
-
};
|
|
2283
|
-
const hueDelta = (a, b) => {
|
|
2284
|
-
const d = ((b - a) % 360 + 360) % 360;
|
|
2285
|
-
return d > 180 ? d - 360 : d;
|
|
2286
|
-
};
|
|
2287
|
-
const hueDeltaLong = (a, b) => hueDelta(a, b) >= 0 ? hueDelta(a, b) - 360 : hueDelta(a, b) + 360;
|
|
2288
|
-
const interpolateHue = (a, b, t2, method) => {
|
|
2289
|
-
const wrapped = (v) => (v % 360 + 360) % 360;
|
|
2290
|
-
switch (method) {
|
|
2291
|
-
case "shorter":
|
|
2292
|
-
return wrapped(a + t2 * hueDelta(a, b));
|
|
2293
|
-
case "longer":
|
|
2294
|
-
return wrapped(a + t2 * hueDeltaLong(a, b));
|
|
2295
|
-
case "increasing":
|
|
2296
|
-
return wrapped(a * (1 - t2) + (b < a ? b + 360 : b) * t2);
|
|
2297
|
-
case "decreasing":
|
|
2298
|
-
return wrapped(a * (1 - t2) + (b > a ? b - 360 : b) * t2);
|
|
2299
|
-
default:
|
|
2300
|
-
throw new Error(`Invalid hue interpolation method: ${method}`);
|
|
2301
|
-
}
|
|
2302
|
-
};
|
|
2303
|
-
const t = Math.max(0, Math.min(1, amount));
|
|
2304
|
-
const ease = typeof easing === "function" ? easing : EASINGS[easing];
|
|
2305
|
-
const tt = Math.pow(ease(t), 1 / gamma);
|
|
2306
|
-
const otherColor = typeof other === "string" ? _Color.from(other) : other;
|
|
2307
|
-
const thisCoords = coords.slice(0, 3);
|
|
2308
|
-
const otherCoords = otherColor.in(model).toArray({ fit: "none", precision: null }).slice(0, 3);
|
|
2309
|
-
const thisAlpha = coords[3];
|
|
2310
|
-
const otherAlpha = otherColor.coords[3];
|
|
2311
|
-
const hueIndex = Object.entries(defs).find(([k]) => k === "h")?.[1].index;
|
|
2312
|
-
if (t === 0)
|
|
2313
|
-
return new _Color(model, [...thisCoords, thisAlpha]);
|
|
2314
|
-
if (t === 1)
|
|
2315
|
-
return new _Color(model, [...otherCoords, otherAlpha]);
|
|
2316
|
-
if (thisAlpha < 1 || otherAlpha < 1) {
|
|
2317
|
-
const premixed = thisCoords.map((start, i) => {
|
|
2318
|
-
const end = otherCoords[i];
|
|
2319
|
-
if (i === hueIndex)
|
|
2320
|
-
return interpolateHue(start, end, tt, hue);
|
|
2321
|
-
const a = start * thisAlpha;
|
|
2322
|
-
const b = end * otherAlpha;
|
|
2323
|
-
return a * (1 - tt) + b * tt;
|
|
2324
|
-
});
|
|
2325
|
-
const mixedAlpha = thisAlpha * (1 - tt) + otherAlpha * tt;
|
|
2326
|
-
const mixed = mixedAlpha > 0 ? premixed.map((c, i) => i === hueIndex ? c : c / mixedAlpha) : thisCoords.map((_, i) => i === hueIndex ? premixed[i] : 0);
|
|
2327
|
-
return new _Color(model, [...mixed, mixedAlpha]);
|
|
2328
|
-
}
|
|
2329
|
-
const mixedCoords = thisCoords.map((start, i) => {
|
|
2330
|
-
const entry = Object.values(defs).find((d) => d.index === i);
|
|
2331
|
-
if (!entry)
|
|
2332
|
-
return start;
|
|
2333
|
-
const end = otherCoords[i];
|
|
2334
|
-
return entry.value === "angle" ? interpolateHue(start, end, tt, hue) : start + (end - start) * tt;
|
|
2335
|
-
});
|
|
2336
|
-
return new _Color(model, [...mixedCoords, 1]);
|
|
2821
|
+
fit(options = {}) {
|
|
2822
|
+
const { fit: method = config.defaults.fit, precision } = options;
|
|
2823
|
+
const fitted = this.toArray({ fit: method, precision });
|
|
2824
|
+
return new _Color(this.model, fitted);
|
|
2337
2825
|
}
|
|
2338
2826
|
/**
|
|
2339
2827
|
* Fits this color within the specified gamut using a given method.
|
|
@@ -2353,7 +2841,7 @@ var Saturon = (() => {
|
|
|
2353
2841
|
/**
|
|
2354
2842
|
* Calculates the WCAG 2.1 contrast ratio between this color and another.
|
|
2355
2843
|
*
|
|
2356
|
-
* @param other - The comparison
|
|
2844
|
+
* @param other - The comparison Color instance.
|
|
2357
2845
|
* @returns Contrast ratio from 1 to 21.
|
|
2358
2846
|
*
|
|
2359
2847
|
* @remarks
|
|
@@ -2361,15 +2849,14 @@ var Saturon = (() => {
|
|
|
2361
2849
|
* - For perceptual accuracy, consider using APCA instead.
|
|
2362
2850
|
*/
|
|
2363
2851
|
contrast(other) {
|
|
2364
|
-
const o = typeof other === "string" ? _Color.from(other) : other;
|
|
2365
2852
|
const [, L1] = this.in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
2366
|
-
const [, L2] =
|
|
2853
|
+
const [, L2] = other.in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
2367
2854
|
return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
|
|
2368
2855
|
}
|
|
2369
2856
|
/**
|
|
2370
2857
|
* Calculates the color difference (ΔEOK) between the current color and another color using the OKLAB color space.
|
|
2371
2858
|
*
|
|
2372
|
-
* @param other - The other
|
|
2859
|
+
* @param other - The other Color instance to compare against.
|
|
2373
2860
|
* @returns A number in range (0-1) (smaller indicates more similar colors).
|
|
2374
2861
|
*
|
|
2375
2862
|
* @remarks
|
|
@@ -2380,7 +2867,7 @@ var Saturon = (() => {
|
|
|
2380
2867
|
deltaEOK(other) {
|
|
2381
2868
|
const coordsOptions = { fit: "none", precision: null };
|
|
2382
2869
|
const [L1, a1, b1] = this.in("oklab").toArray(coordsOptions);
|
|
2383
|
-
const [L2, a2, b2] =
|
|
2870
|
+
const [L2, a2, b2] = other.in("oklab").toArray(coordsOptions);
|
|
2384
2871
|
const \u0394L = L1 - L2;
|
|
2385
2872
|
const \u0394A = a1 - a2;
|
|
2386
2873
|
const \u0394B = b1 - b2;
|
|
@@ -2391,13 +2878,13 @@ var Saturon = (() => {
|
|
|
2391
2878
|
* Calculates the color difference (ΔE) between two colors using the CIE76 formula.
|
|
2392
2879
|
* This is a simple Euclidean distance in LAB color space.
|
|
2393
2880
|
*
|
|
2394
|
-
* @param other - The other
|
|
2881
|
+
* @param other - The other Color instance to compare against.
|
|
2395
2882
|
* @returns A number in range (0-1) (smaller indicates more similar colors).
|
|
2396
2883
|
*/
|
|
2397
2884
|
deltaE76(other) {
|
|
2398
2885
|
const coordsOptions = { fit: "none", precision: null };
|
|
2399
2886
|
const [L1, a1, b1] = this.in("lab").toArray(coordsOptions);
|
|
2400
|
-
const [L2, a2, b2] =
|
|
2887
|
+
const [L2, a2, b2] = other.in("lab").toArray(coordsOptions);
|
|
2401
2888
|
const \u0394L = L1 - L2;
|
|
2402
2889
|
const \u0394A = a1 - a2;
|
|
2403
2890
|
const \u0394B = b1 - b2;
|
|
@@ -2407,13 +2894,13 @@ var Saturon = (() => {
|
|
|
2407
2894
|
* Calculates the color difference (ΔE) between two colors using the CIE94 formula.
|
|
2408
2895
|
* This method improves perceptual accuracy over CIE76 by applying weighting factors.
|
|
2409
2896
|
*
|
|
2410
|
-
* @param other - The other
|
|
2897
|
+
* @param other - The other Color instance to compare against.
|
|
2411
2898
|
* @returns A number in range (0-1) (smaller indicates more similar colors).
|
|
2412
2899
|
*/
|
|
2413
2900
|
deltaE94(other) {
|
|
2414
2901
|
const coordsOptions = { fit: "none", precision: null };
|
|
2415
2902
|
const [L1, a1, b1] = this.in("lab").toArray(coordsOptions);
|
|
2416
|
-
const [L2, a2, b2] =
|
|
2903
|
+
const [L2, a2, b2] = other.in("lab").toArray(coordsOptions);
|
|
2417
2904
|
const \u0394L = L1 - L2;
|
|
2418
2905
|
const \u0394A = a1 - a2;
|
|
2419
2906
|
const \u0394B = b1 - b2;
|
|
@@ -2431,13 +2918,13 @@ var Saturon = (() => {
|
|
|
2431
2918
|
* Calculates the color difference (ΔE) between two colors using the CIEDE2000 formula.
|
|
2432
2919
|
* This is the most perceptually accurate method, accounting for interactions between hue, chroma, and lightness.
|
|
2433
2920
|
*
|
|
2434
|
-
* @param other - The other
|
|
2921
|
+
* @param other - The other Color instance to compare against.
|
|
2435
2922
|
* @returns A number in range (0-1) (smaller indicates more similar colors).
|
|
2436
2923
|
*/
|
|
2437
2924
|
deltaE2000(other) {
|
|
2438
2925
|
const coordsOptions = { fit: "none", precision: null };
|
|
2439
2926
|
const [L1, a1, b1] = this.in("lab").toArray(coordsOptions);
|
|
2440
|
-
const [L2, a2, b2] =
|
|
2927
|
+
const [L2, a2, b2] = other.in("lab").toArray(coordsOptions);
|
|
2441
2928
|
const \u03C0 = Math.PI, d2r = \u03C0 / 180, r2d = 180 / \u03C0;
|
|
2442
2929
|
const C1 = Math.sqrt(a1 ** 2 + b1 ** 2);
|
|
2443
2930
|
const C2 = Math.sqrt(a2 ** 2 + b2 ** 2);
|
|
@@ -2475,7 +2962,7 @@ var Saturon = (() => {
|
|
|
2475
2962
|
const Cdash = (Cdash1 + Cdash2) / 2;
|
|
2476
2963
|
const Cdash7 = Math.pow(Cdash, 7);
|
|
2477
2964
|
const hsum = h1 + h2;
|
|
2478
|
-
let hdash
|
|
2965
|
+
let hdash;
|
|
2479
2966
|
if (Cdash1 === 0 && Cdash2 === 0) {
|
|
2480
2967
|
hdash = hsum;
|
|
2481
2968
|
} else if (habs <= 180) {
|
|
@@ -2506,8 +2993,8 @@ var Saturon = (() => {
|
|
|
2506
2993
|
/**
|
|
2507
2994
|
* Checks numeric equality with another color within a tolerance.
|
|
2508
2995
|
*
|
|
2509
|
-
* @param other - Color
|
|
2510
|
-
* @param epsilon - Allowed floating-point difference (
|
|
2996
|
+
* @param other - Color instnace to compare.
|
|
2997
|
+
* @param epsilon - Allowed floating-point difference (defaults to the value of `EPSILON` in `"saturon/math"`).
|
|
2511
2998
|
* @returns `true` if equal within tolerance.
|
|
2512
2999
|
*
|
|
2513
3000
|
* @remarks
|
|
@@ -2521,22 +3008,23 @@ var Saturon = (() => {
|
|
|
2521
3008
|
* - {@link deltaE94} (weighted improvements over LAB)
|
|
2522
3009
|
* - {@link deltaE2000} (most accurate, accounts for perceptual interactions)
|
|
2523
3010
|
*/
|
|
2524
|
-
equals(other, epsilon =
|
|
2525
|
-
const
|
|
2526
|
-
|
|
2527
|
-
|
|
3011
|
+
equals(other, epsilon = EPSILON) {
|
|
3012
|
+
const thisCoords = this.toArray({ fit: "none", precision: null });
|
|
3013
|
+
const otherCoords = other.toArray({ fit: "none", precision: null });
|
|
3014
|
+
if (other.model === this.model)
|
|
3015
|
+
return thisCoords.every((v, i) => Math.abs(v - otherCoords[i]) <= epsilon);
|
|
2528
3016
|
const a = this.in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
2529
|
-
const b =
|
|
3017
|
+
const b = other.in("xyz-d65").toArray({ fit: "none", precision: null });
|
|
2530
3018
|
return a.every((v, i) => Math.abs(v - b[i]) <= epsilon);
|
|
2531
3019
|
}
|
|
2532
3020
|
/**
|
|
2533
3021
|
* Determines whether this color lies within a given gamut.
|
|
2534
3022
|
*
|
|
2535
3023
|
* @param gamut - Target color space.
|
|
2536
|
-
* @param epsilon - Floating-point tolerance (
|
|
3024
|
+
* @param epsilon - Floating-point tolerance (defaults to the value of `EPSILON` in `"saturon/math"`).
|
|
2537
3025
|
* @returns `true` if inside gamut, else `false`.
|
|
2538
3026
|
*/
|
|
2539
|
-
inGamut(gamut, epsilon =
|
|
3027
|
+
inGamut(gamut, epsilon = EPSILON) {
|
|
2540
3028
|
const g = gamut.trim().toLowerCase();
|
|
2541
3029
|
if (!(g in colorSpaces))
|
|
2542
3030
|
throw new Error(`Unsupported color gamut: '${g}'.`);
|
|
@@ -2546,8 +3034,8 @@ var Saturon = (() => {
|
|
|
2546
3034
|
const coords = this.in(g).toArray({ fit: "none", precision: null });
|
|
2547
3035
|
return Object.values(components).every(({ index, value }) => {
|
|
2548
3036
|
const v = coords[index];
|
|
2549
|
-
const [
|
|
2550
|
-
return v >=
|
|
3037
|
+
const [min2, max2] = Array.isArray(value) ? value : value === "hue" ? [0, 360] : [0, 100];
|
|
3038
|
+
return v >= min2 - epsilon && v <= max2 + epsilon;
|
|
2551
3039
|
});
|
|
2552
3040
|
}
|
|
2553
3041
|
};
|