@maplibre/maplibre-gl-style-spec
Version:
a specification for maplibre styles
1,531 lines (1,433 loc) • 319 kB
JavaScript
#!/usr/bin/env node
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('fs')) :
typeof define === 'function' && define.amd ? define(['fs'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.fs));
})(this, (function (require$$0) { 'use strict';
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var minimist$1;
var hasRequiredMinimist;
function requireMinimist () {
if (hasRequiredMinimist) return minimist$1;
hasRequiredMinimist = 1;
function hasKey(obj, keys) {
var o = obj;
keys.slice(0, -1).forEach(function (key) {
o = o[key] || {};
});
var key = keys[keys.length - 1];
return key in o;
}
function isNumber(x) {
if (typeof x === 'number') { return true; }
if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
}
function isConstructorOrProto(obj, key) {
return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
}
minimist$1 = function (args, opts) {
if (!opts) { opts = {}; }
var flags = {
bools: {},
strings: {},
unknownFn: null,
};
if (typeof opts.unknown === 'function') {
flags.unknownFn = opts.unknown;
}
if (typeof opts.boolean === 'boolean' && opts.boolean) {
flags.allBools = true;
} else {
[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
}
var aliases = {};
function aliasIsBoolean(key) {
return aliases[key].some(function (x) {
return flags.bools[x];
});
}
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
[].concat(aliases[key]).forEach(function (k) {
flags.strings[k] = true;
});
}
});
var defaults = opts.default || {};
var argv = { _: [] };
function argDefined(key, arg) {
return (flags.allBools && (/^--[^=]+$/).test(arg))
|| flags.strings[key]
|| flags.bools[key]
|| aliases[key];
}
function setKey(obj, keys, value) {
var o = obj;
for (var i = 0; i < keys.length - 1; i++) {
var key = keys[i];
if (isConstructorOrProto(o, key)) { return; }
if (o[key] === undefined) { o[key] = {}; }
if (
o[key] === Object.prototype
|| o[key] === Number.prototype
|| o[key] === String.prototype
) {
o[key] = {};
}
if (o[key] === Array.prototype) { o[key] = []; }
o = o[key];
}
var lastKey = keys[keys.length - 1];
if (isConstructorOrProto(o, lastKey)) { return; }
if (
o === Object.prototype
|| o === Number.prototype
|| o === String.prototype
) {
o = {};
}
if (o === Array.prototype) { o = []; }
if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
o[lastKey] = value;
} else if (Array.isArray(o[lastKey])) {
o[lastKey].push(value);
} else {
o[lastKey] = [o[lastKey], value];
}
}
function setArg(key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) { return; }
}
var value = !flags.strings[key] && isNumber(val)
? Number(val)
: val;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--') + 1);
args = args.slice(0, args.indexOf('--'));
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var key;
var next;
if ((/^--.+=/).test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
key = m[1];
var value = m[2];
if (flags.bools[key]) {
value = value !== 'false';
}
setArg(key, value, arg);
} else if ((/^--no-.+/).test(arg)) {
key = arg.match(/^--no-(.+)/)[1];
setArg(key, false, arg);
} else if ((/^--.+/).test(arg)) {
key = arg.match(/^--(.+)/)[1];
next = args[i + 1];
if (
next !== undefined
&& !(/^(-|--)[^-]/).test(next)
&& !flags.bools[key]
&& !flags.allBools
&& (aliases[key] ? !aliasIsBoolean(key) : true)
) {
setArg(key, next, arg);
i += 1;
} else if ((/^(true|false)$/).test(next)) {
setArg(key, next === 'true', arg);
i += 1;
} else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
} else if ((/^-[^-]+/).test(arg)) {
var letters = arg.slice(1, -1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
next = arg.slice(j + 2);
if (next === '-') {
setArg(letters[j], next, arg);
continue;
}
if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
setArg(letters[j], next.slice(1), arg);
broken = true;
break;
}
if (
(/[A-Za-z]/).test(letters[j])
&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (
args[i + 1]
&& !(/^(-|--)[^-]/).test(args[i + 1])
&& !flags.bools[key]
&& (aliases[key] ? !aliasIsBoolean(key) : true)
) {
setArg(key, args[i + 1], arg);
i += 1;
} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
setArg(key, args[i + 1] === 'true', arg);
i += 1;
} else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
} else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
}
if (opts.stopEarly) {
argv._.push.apply(argv._, args.slice(i + 1));
break;
}
}
}
Object.keys(defaults).forEach(function (k) {
if (!hasKey(argv, k.split('.'))) {
setKey(argv, k.split('.'), defaults[k]);
(aliases[k] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[k]);
});
}
});
if (opts['--']) {
argv['--'] = notFlags.slice();
} else {
notFlags.forEach(function (k) {
argv._.push(k);
});
}
return argv;
};
return minimist$1;
}
var minimistExports = requireMinimist();
var minimist = /*@__PURE__*/getDefaultExportFromCjs(minimistExports);
var rw$1 = {};
var dash = {};
var decode;
var hasRequiredDecode;
function requireDecode () {
if (hasRequiredDecode) return decode;
hasRequiredDecode = 1;
decode = function(options) {
if (options) {
if (typeof options === "string") return encoding(options);
if (options.encoding !== null) return encoding(options.encoding);
}
return identity();
};
function identity() {
var chunks = [];
return {
push: function(chunk) { chunks.push(chunk); },
value: function() { return Buffer.concat(chunks); }
};
}
function encoding(encoding) {
var chunks = [];
return {
push: function(chunk) { chunks.push(chunk); },
value: function() { return Buffer.concat(chunks).toString(encoding); }
};
}
return decode;
}
var readFile;
var hasRequiredReadFile;
function requireReadFile () {
if (hasRequiredReadFile) return readFile;
hasRequiredReadFile = 1;
var fs = require$$0,
decode = requireDecode();
readFile = function(path, options, callback) {
if (arguments.length < 3) callback = options, options = null;
switch (path) {
case "/dev/stdin": return readStream(process.stdin, options, callback);
}
fs.stat(path, function(error, stat) {
if (error) return callback(error);
if (stat.isFile()) return fs.readFile(path, options, callback);
readStream(fs.createReadStream(path, options ? {flags: options.flag || "r"} : {}), options, callback); // N.B. flag / flags
});
};
function readStream(stream, options, callback) {
var decoder = decode(options);
stream.on("error", callback);
stream.on("data", function(d) { decoder.push(d); });
stream.on("end", function() { callback(null, decoder.value()); });
}
return readFile;
}
var readFileSync;
var hasRequiredReadFileSync;
function requireReadFileSync () {
if (hasRequiredReadFileSync) return readFileSync;
hasRequiredReadFileSync = 1;
var fs = require$$0,
decode = requireDecode();
readFileSync = function(filename, options) {
if (fs.statSync(filename).isFile()) {
return fs.readFileSync(filename, options);
} else {
var fd = fs.openSync(filename, options && options.flag || "r"),
decoder = decode(options);
while (true) { // eslint-disable-line no-constant-condition
try {
var buffer = new Buffer(bufferSize),
bytesRead = fs.readSync(fd, buffer, 0, bufferSize);
} catch (e) {
if (e.code === "EOF") break;
fs.closeSync(fd);
throw e;
}
if (bytesRead === 0) break;
decoder.push(buffer.slice(0, bytesRead));
}
fs.closeSync(fd);
return decoder.value();
}
};
var bufferSize = 1 << 16;
return readFileSync;
}
var encode;
var hasRequiredEncode;
function requireEncode () {
if (hasRequiredEncode) return encode;
hasRequiredEncode = 1;
encode = function(data, options) {
return typeof data === "string"
? new Buffer(data, typeof options === "string" ? options
: options && options.encoding !== null ? options.encoding
: "utf8")
: data;
};
return encode;
}
var writeFile;
var hasRequiredWriteFile;
function requireWriteFile () {
if (hasRequiredWriteFile) return writeFile;
hasRequiredWriteFile = 1;
var fs = require$$0,
encode = requireEncode();
writeFile = function(path, data, options, callback) {
if (arguments.length < 4) callback = options, options = null;
switch (path) {
case "/dev/stdout": return writeStream(process.stdout, "write", data, options, callback);
case "/dev/stderr": return writeStream(process.stderr, "write", data, options, callback);
}
fs.stat(path, function(error, stat) {
if (error && error.code !== "ENOENT") return callback(error);
if (stat && stat.isFile()) return fs.writeFile(path, data, options, callback);
writeStream(fs.createWriteStream(path, options ? {flags: options.flag || "w"} : {}), "end", data, options, callback); // N.B. flag / flags
});
};
function writeStream(stream, send, data, options, callback) {
stream.on("error", function(error) { callback(error.code === "EPIPE" ? null : error); }); // ignore broken pipe, e.g., | head
stream[send](encode(data, options), function(error) { callback(error && error.code === "EPIPE" ? null : error); });
}
return writeFile;
}
var writeFileSync;
var hasRequiredWriteFileSync;
function requireWriteFileSync () {
if (hasRequiredWriteFileSync) return writeFileSync;
hasRequiredWriteFileSync = 1;
var fs = require$$0,
encode = requireEncode();
writeFileSync = function(filename, data, options) {
var stat;
try {
stat = fs.statSync(filename);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
if (!stat || stat.isFile()) {
fs.writeFileSync(filename, data, options);
} else {
var fd = fs.openSync(filename, options && options.flag || "w"),
bytesWritten = 0,
bytesTotal = (data = encode(data, options)).length;
while (bytesWritten < bytesTotal) {
try {
bytesWritten += fs.writeSync(fd, data, bytesWritten, bytesTotal - bytesWritten, null);
} catch (error) {
if (error.code === "EPIPE") break; // ignore broken pipe, e.g., | head
fs.closeSync(fd);
throw error;
}
}
fs.closeSync(fd);
}
};
return writeFileSync;
}
var hasRequiredDash;
function requireDash () {
if (hasRequiredDash) return dash;
hasRequiredDash = 1;
var slice = Array.prototype.slice;
function dashify(method, file) {
return function(path) {
var argv = arguments;
if (path == "-") (argv = slice.call(argv)).splice(0, 1, file);
return method.apply(null, argv);
};
}
dash.readFile = dashify(requireReadFile(), "/dev/stdin");
dash.readFileSync = dashify(requireReadFileSync(), "/dev/stdin");
dash.writeFile = dashify(requireWriteFile(), "/dev/stdout");
dash.writeFileSync = dashify(requireWriteFileSync(), "/dev/stdout");
return dash;
}
var hasRequiredRw;
function requireRw () {
if (hasRequiredRw) return rw$1;
hasRequiredRw = 1;
rw$1.dash = requireDash();
rw$1.readFile = requireReadFile();
rw$1.readFileSync = requireReadFileSync();
rw$1.writeFile = requireWriteFile();
rw$1.writeFileSync = requireWriteFileSync();
return rw$1;
}
var rwExports = requireRw();
var rw = /*@__PURE__*/getDefaultExportFromCjs(rwExports);
// Note: Do not inherit from Error. It breaks when transpiling to ES5.
class ValidationError {
constructor(key, value, message, identifier) {
this.message = (key ? `${key}: ` : '') + message;
if (identifier)
this.identifier = identifier;
if (value !== null && value !== undefined && value.__line__) {
this.line = value.__line__;
}
}
}
function validateConstants(options) {
const key = options.key;
const constants = options.value;
if (constants) {
return [new ValidationError(key, constants, 'constants have been deprecated as of v8')];
}
else {
return [];
}
}
function extendBy(output, ...inputs) {
for (const input of inputs) {
for (const k in input) {
output[k] = input[k];
}
}
return output;
}
// Turn jsonlint-lines-primitives objects into primitive objects
function unbundle(value) {
if (value instanceof Number || value instanceof String || value instanceof Boolean) {
return value.valueOf();
}
else {
return value;
}
}
function deepUnbundle(value) {
if (Array.isArray(value)) {
return value.map(deepUnbundle);
}
else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) {
const unbundledValue = {};
for (const key in value) {
unbundledValue[key] = deepUnbundle(value[key]);
}
return unbundledValue;
}
return unbundle(value);
}
class ExpressionParsingError extends Error {
constructor(key, message) {
super(message);
this.message = message;
this.key = key;
}
}
/**
* Tracks `let` bindings during expression parsing.
* @private
*/
class Scope {
constructor(parent, bindings = []) {
this.parent = parent;
this.bindings = {};
for (const [name, expression] of bindings) {
this.bindings[name] = expression;
}
}
concat(bindings) {
return new Scope(this, bindings);
}
get(name) {
if (this.bindings[name]) {
return this.bindings[name];
}
if (this.parent) {
return this.parent.get(name);
}
throw new Error(`${name} not found in scope.`);
}
has(name) {
if (this.bindings[name])
return true;
return this.parent ? this.parent.has(name) : false;
}
}
const NullType = { kind: 'null' };
const NumberType = { kind: 'number' };
const StringType = { kind: 'string' };
const BooleanType = { kind: 'boolean' };
const ColorType = { kind: 'color' };
const ProjectionDefinitionType = { kind: 'projectionDefinition' };
const ObjectType = { kind: 'object' };
const ValueType = { kind: 'value' };
const ErrorType = { kind: 'error' };
const CollatorType = { kind: 'collator' };
const FormattedType = { kind: 'formatted' };
const PaddingType = { kind: 'padding' };
const ResolvedImageType = { kind: 'resolvedImage' };
const VariableAnchorOffsetCollectionType = { kind: 'variableAnchorOffsetCollection' };
function array(itemType, N) {
return {
kind: 'array',
itemType,
N
};
}
function typeToString(type) {
if (type.kind === 'array') {
const itemType = typeToString(type.itemType);
return typeof type.N === 'number' ?
`array<${itemType}, ${type.N}>` :
type.itemType.kind === 'value' ? 'array' : `array<${itemType}>`;
}
else {
return type.kind;
}
}
const valueMemberTypes = [
NullType,
NumberType,
StringType,
BooleanType,
ColorType,
ProjectionDefinitionType,
FormattedType,
ObjectType,
array(ValueType),
PaddingType,
ResolvedImageType,
VariableAnchorOffsetCollectionType
];
/**
* Returns null if `t` is a subtype of `expected`; otherwise returns an
* error message.
* @private
*/
function checkSubtype(expected, t) {
if (t.kind === 'error') {
// Error is a subtype of every type
return null;
}
else if (expected.kind === 'array') {
if (t.kind === 'array' &&
((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) &&
(typeof expected.N !== 'number' || expected.N === t.N)) {
return null;
}
}
else if (expected.kind === t.kind) {
return null;
}
else if (expected.kind === 'value') {
for (const memberType of valueMemberTypes) {
if (!checkSubtype(memberType, t)) {
return null;
}
}
}
return `Expected ${typeToString(expected)} but found ${typeToString(t)} instead.`;
}
function isValidType(provided, allowedTypes) {
return allowedTypes.some(t => t.kind === provided.kind);
}
function isValidNativeType(provided, allowedTypes) {
return allowedTypes.some(t => {
if (t === 'null') {
return provided === null;
}
else if (t === 'array') {
return Array.isArray(provided);
}
else if (t === 'object') {
return provided && !Array.isArray(provided) && typeof provided === 'object';
}
else {
return t === typeof provided;
}
});
}
/**
* Verify whether the specified type is of the same type as the specified sample.
*
* @param provided Type to verify
* @param sample Sample type to reference
* @returns `true` if both objects are of the same type, `false` otherwise
* @example basic types
* if (verifyType(outputType, ValueType)) {
* // type narrowed to:
* outputType.kind; // 'value'
* }
* @example array types
* if (verifyType(outputType, array(NumberType))) {
* // type narrowed to:
* outputType.kind; // 'array'
* outputType.itemType; // NumberTypeT
* outputType.itemType.kind; // 'number'
* }
*/
function verifyType(provided, sample) {
if (provided.kind === 'array' && sample.kind === 'array') {
return provided.itemType.kind === sample.itemType.kind && typeof provided.N === 'number';
}
return provided.kind === sample.kind;
}
// See https://observablehq.com/@mbostock/lab-and-rgb
const Xn = 0.96422, Yn = 1, Zn = 0.82521, t0 = 4 / 29, t1 = 6 / 29, t2 = 3 * t1 * t1, t3 = t1 * t1 * t1, deg2rad = Math.PI / 180, rad2deg = 180 / Math.PI;
function constrainAngle(angle) {
angle = angle % 360;
if (angle < 0) {
angle += 360;
}
return angle;
}
function rgbToLab([r, g, b, alpha]) {
r = rgb2xyz(r);
g = rgb2xyz(g);
b = rgb2xyz(b);
let x, z;
const y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn);
if (r === g && g === b) {
x = z = y;
}
else {
x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);
z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);
}
const l = 116 * y - 16;
return [(l < 0) ? 0 : l, 500 * (x - y), 200 * (y - z), alpha];
}
function rgb2xyz(x) {
return (x <= 0.04045) ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
}
function xyz2lab(t) {
return (t > t3) ? Math.pow(t, 1 / 3) : t / t2 + t0;
}
function labToRgb([l, a, b, alpha]) {
let y = (l + 16) / 116, x = isNaN(a) ? y : y + a / 500, z = isNaN(b) ? y : y - b / 200;
y = Yn * lab2xyz(y);
x = Xn * lab2xyz(x);
z = Zn * lab2xyz(z);
return [
xyz2rgb(3.1338561 * x - 1.6168667 * y - 0.4906146 * z), // D50 -> sRGB
xyz2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z),
xyz2rgb(0.0719453 * x - 0.2289914 * y + 1.4052427 * z),
alpha,
];
}
function xyz2rgb(x) {
x = (x <= 0.00304) ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
return (x < 0) ? 0 : (x > 1) ? 1 : x; // clip to 0..1 range
}
function lab2xyz(t) {
return (t > t1) ? t * t * t : t2 * (t - t0);
}
function rgbToHcl(rgbColor) {
const [l, a, b, alpha] = rgbToLab(rgbColor);
const c = Math.sqrt(a * a + b * b);
const h = Math.round(c * 10000) ? constrainAngle(Math.atan2(b, a) * rad2deg) : NaN;
return [h, c, l, alpha];
}
function hclToRgb([h, c, l, alpha]) {
h = isNaN(h) ? 0 : h * deg2rad;
return labToRgb([l, Math.cos(h) * c, Math.sin(h) * c, alpha]);
}
// https://drafts.csswg.org/css-color-4/#hsl-to-rgb
function hslToRgb([h, s, l, alpha]) {
h = constrainAngle(h);
s /= 100;
l /= 100;
function f(n) {
const k = (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
}
return [f(0), f(8), f(4), alpha];
}
/**
* CSS color parser compliant with CSS Color 4 Specification.
* Supports: named colors, `transparent` keyword, all rgb hex notations,
* rgb(), rgba(), hsl() and hsla() functions.
* Does not round the parsed values to integers from the range 0..255.
*
* Syntax:
*
* <alpha-value> = <number> | <percentage>
* <hue> = <number> | <angle>
*
* rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) | rgb( <number>{3} [ / <alpha-value> ]? )
* rgb() = rgb( <percentage>#{3} , <alpha-value>? ) | rgb( <number>#{3} , <alpha-value>? )
*
* hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? )
* hsl() = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
*
* Caveats:
* - <angle> - <number> with optional `deg` suffix; `grad`, `rad`, `turn` are not supported
* - `none` keyword is not supported
* - comments inside rgb()/hsl() are not supported
* - legacy color syntax rgba() is supported with an identical grammar and behavior to rgb()
* - legacy color syntax hsla() is supported with an identical grammar and behavior to hsl()
*
* @param input CSS color string to parse.
* @returns Color in sRGB color space, with `red`, `green`, `blue`
* and `alpha` channels normalized to the range 0..1,
* or `undefined` if the input is not a valid color string.
*/
function parseCssColor(input) {
input = input.toLowerCase().trim();
if (input === 'transparent') {
return [0, 0, 0, 0];
}
// 'white', 'black', 'blue'
const namedColorsMatch = namedColors[input];
if (namedColorsMatch) {
const [r, g, b] = namedColorsMatch;
return [r / 255, g / 255, b / 255, 1];
}
// #f0c, #f0cf, #ff00cc, #ff00ccff
if (input.startsWith('#')) {
const hexRegexp = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/;
if (hexRegexp.test(input)) {
const step = input.length < 6 ? 1 : 2;
let i = 1;
return [
parseHex(input.slice(i, i += step)),
parseHex(input.slice(i, i += step)),
parseHex(input.slice(i, i += step)),
parseHex(input.slice(i, i + step) || 'ff'),
];
}
}
// rgb(128 0 0), rgb(50% 0% 0%), rgba(255,0,255,0.6), rgb(255 0 255 / 60%), rgb(100% 0% 100% /.6)
if (input.startsWith('rgb')) {
const rgbRegExp = /^rgba?\(\s*([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/;
const rgbMatch = input.match(rgbRegExp);
if (rgbMatch) {
const [_, // eslint-disable-line @typescript-eslint/no-unused-vars
r, // <numeric>
rp, // % (optional)
f1, // , (optional)
g, // <numeric>
gp, // % (optional)
f2, // , (optional)
b, // <numeric>
bp, // % (optional)
f3, // ,|/ (optional)
a, // <numeric> (optional)
ap, // % (optional)
] = rgbMatch;
const argFormat = [f1 || ' ', f2 || ' ', f3].join('');
if (argFormat === ' ' ||
argFormat === ' /' ||
argFormat === ',,' ||
argFormat === ',,,') {
const valFormat = [rp, gp, bp].join('');
const maxValue = (valFormat === '%%%') ? 100 :
(valFormat === '') ? 255 : 0;
if (maxValue) {
const rgba = [
clamp(+r / maxValue, 0, 1),
clamp(+g / maxValue, 0, 1),
clamp(+b / maxValue, 0, 1),
a ? parseAlpha(+a, ap) : 1,
];
if (validateNumbers(rgba)) {
return rgba;
}
// invalid numbers
}
// values must be all numbers or all percentages
}
return; // comma optional syntax requires no commas at all
}
}
// hsl(120 50% 80%), hsla(120deg,50%,80%,.9), hsl(12e1 50% 80% / 90%)
const hslRegExp = /^hsla?\(\s*([\de.+-]+)(?:deg)?(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/;
const hslMatch = input.match(hslRegExp);
if (hslMatch) {
const [_, // eslint-disable-line @typescript-eslint/no-unused-vars
h, // <numeric>
f1, // , (optional)
s, // <numeric>
f2, // , (optional)
l, // <numeric>
f3, // ,|/ (optional)
a, // <numeric> (optional)
ap, // % (optional)
] = hslMatch;
const argFormat = [f1 || ' ', f2 || ' ', f3].join('');
if (argFormat === ' ' ||
argFormat === ' /' ||
argFormat === ',,' ||
argFormat === ',,,') {
const hsla = [
+h,
clamp(+s, 0, 100),
clamp(+l, 0, 100),
a ? parseAlpha(+a, ap) : 1,
];
if (validateNumbers(hsla)) {
return hslToRgb(hsla);
}
// invalid numbers
}
// comma optional syntax requires no commas at all
}
}
function parseHex(hex) {
return parseInt(hex.padEnd(2, hex), 16) / 255;
}
function parseAlpha(a, asPercentage) {
return clamp(asPercentage ? (a / 100) : a, 0, 1);
}
function clamp(n, min, max) {
return Math.min(Math.max(min, n), max);
}
/**
* The regular expression for numeric values is not super specific, and it may
* happen that it will accept a value that is not a valid number. In order to
* detect and eliminate such values this function exists.
*
* @param array Array of uncertain numbers.
* @returns `true` if the specified array contains only valid numbers, `false` otherwise.
*/
function validateNumbers(array) {
return !array.some(Number.isNaN);
}
/**
* To generate:
* - visit {@link https://www.w3.org/TR/css-color-4/#named-colors}
* - run in the console:
* @example
* copy(`{\n${[...document.querySelector('.named-color-table tbody').children].map((tr) => `${tr.cells[2].textContent.trim()}: [${tr.cells[4].textContent.trim().split(/\s+/).join(', ')}],`).join('\n')}\n}`);
*/
const namedColors = {
aliceblue: [240, 248, 255],
antiquewhite: [250, 235, 215],
aqua: [0, 255, 255],
aquamarine: [127, 255, 212],
azure: [240, 255, 255],
beige: [245, 245, 220],
bisque: [255, 228, 196],
black: [0, 0, 0],
blanchedalmond: [255, 235, 205],
blue: [0, 0, 255],
blueviolet: [138, 43, 226],
brown: [165, 42, 42],
burlywood: [222, 184, 135],
cadetblue: [95, 158, 160],
chartreuse: [127, 255, 0],
chocolate: [210, 105, 30],
coral: [255, 127, 80],
cornflowerblue: [100, 149, 237],
cornsilk: [255, 248, 220],
crimson: [220, 20, 60],
cyan: [0, 255, 255],
darkblue: [0, 0, 139],
darkcyan: [0, 139, 139],
darkgoldenrod: [184, 134, 11],
darkgray: [169, 169, 169],
darkgreen: [0, 100, 0],
darkgrey: [169, 169, 169],
darkkhaki: [189, 183, 107],
darkmagenta: [139, 0, 139],
darkolivegreen: [85, 107, 47],
darkorange: [255, 140, 0],
darkorchid: [153, 50, 204],
darkred: [139, 0, 0],
darksalmon: [233, 150, 122],
darkseagreen: [143, 188, 143],
darkslateblue: [72, 61, 139],
darkslategray: [47, 79, 79],
darkslategrey: [47, 79, 79],
darkturquoise: [0, 206, 209],
darkviolet: [148, 0, 211],
deeppink: [255, 20, 147],
deepskyblue: [0, 191, 255],
dimgray: [105, 105, 105],
dimgrey: [105, 105, 105],
dodgerblue: [30, 144, 255],
firebrick: [178, 34, 34],
floralwhite: [255, 250, 240],
forestgreen: [34, 139, 34],
fuchsia: [255, 0, 255],
gainsboro: [220, 220, 220],
ghostwhite: [248, 248, 255],
gold: [255, 215, 0],
goldenrod: [218, 165, 32],
gray: [128, 128, 128],
green: [0, 128, 0],
greenyellow: [173, 255, 47],
grey: [128, 128, 128],
honeydew: [240, 255, 240],
hotpink: [255, 105, 180],
indianred: [205, 92, 92],
indigo: [75, 0, 130],
ivory: [255, 255, 240],
khaki: [240, 230, 140],
lavender: [230, 230, 250],
lavenderblush: [255, 240, 245],
lawngreen: [124, 252, 0],
lemonchiffon: [255, 250, 205],
lightblue: [173, 216, 230],
lightcoral: [240, 128, 128],
lightcyan: [224, 255, 255],
lightgoldenrodyellow: [250, 250, 210],
lightgray: [211, 211, 211],
lightgreen: [144, 238, 144],
lightgrey: [211, 211, 211],
lightpink: [255, 182, 193],
lightsalmon: [255, 160, 122],
lightseagreen: [32, 178, 170],
lightskyblue: [135, 206, 250],
lightslategray: [119, 136, 153],
lightslategrey: [119, 136, 153],
lightsteelblue: [176, 196, 222],
lightyellow: [255, 255, 224],
lime: [0, 255, 0],
limegreen: [50, 205, 50],
linen: [250, 240, 230],
magenta: [255, 0, 255],
maroon: [128, 0, 0],
mediumaquamarine: [102, 205, 170],
mediumblue: [0, 0, 205],
mediumorchid: [186, 85, 211],
mediumpurple: [147, 112, 219],
mediumseagreen: [60, 179, 113],
mediumslateblue: [123, 104, 238],
mediumspringgreen: [0, 250, 154],
mediumturquoise: [72, 209, 204],
mediumvioletred: [199, 21, 133],
midnightblue: [25, 25, 112],
mintcream: [245, 255, 250],
mistyrose: [255, 228, 225],
moccasin: [255, 228, 181],
navajowhite: [255, 222, 173],
navy: [0, 0, 128],
oldlace: [253, 245, 230],
olive: [128, 128, 0],
olivedrab: [107, 142, 35],
orange: [255, 165, 0],
orangered: [255, 69, 0],
orchid: [218, 112, 214],
palegoldenrod: [238, 232, 170],
palegreen: [152, 251, 152],
paleturquoise: [175, 238, 238],
palevioletred: [219, 112, 147],
papayawhip: [255, 239, 213],
peachpuff: [255, 218, 185],
peru: [205, 133, 63],
pink: [255, 192, 203],
plum: [221, 160, 221],
powderblue: [176, 224, 230],
purple: [128, 0, 128],
rebeccapurple: [102, 51, 153],
red: [255, 0, 0],
rosybrown: [188, 143, 143],
royalblue: [65, 105, 225],
saddlebrown: [139, 69, 19],
salmon: [250, 128, 114],
sandybrown: [244, 164, 96],
seagreen: [46, 139, 87],
seashell: [255, 245, 238],
sienna: [160, 82, 45],
silver: [192, 192, 192],
skyblue: [135, 206, 235],
slateblue: [106, 90, 205],
slategray: [112, 128, 144],
slategrey: [112, 128, 144],
snow: [255, 250, 250],
springgreen: [0, 255, 127],
steelblue: [70, 130, 180],
tan: [210, 180, 140],
teal: [0, 128, 128],
thistle: [216, 191, 216],
tomato: [255, 99, 71],
turquoise: [64, 224, 208],
violet: [238, 130, 238],
wheat: [245, 222, 179],
white: [255, 255, 255],
whitesmoke: [245, 245, 245],
yellow: [255, 255, 0],
yellowgreen: [154, 205, 50],
};
function interpolateNumber(from, to, t) {
return from + t * (to - from);
}
function interpolateArray(from, to, t) {
return from.map((d, i) => {
return interpolateNumber(d, to[i], t);
});
}
/**
* Color representation used by WebGL.
* Defined in sRGB color space and pre-blended with alpha.
* @private
*/
class Color {
/**
* @param r Red component premultiplied by `alpha` 0..1
* @param g Green component premultiplied by `alpha` 0..1
* @param b Blue component premultiplied by `alpha` 0..1
* @param [alpha=1] Alpha component 0..1
* @param [premultiplied=true] Whether the `r`, `g` and `b` values have already
* been multiplied by alpha. If `true` nothing happens if `false` then they will
* be multiplied automatically.
*/
constructor(r, g, b, alpha = 1, premultiplied = true) {
this.r = r;
this.g = g;
this.b = b;
this.a = alpha;
if (!premultiplied) {
this.r *= alpha;
this.g *= alpha;
this.b *= alpha;
if (!alpha) {
// alpha = 0 erases completely rgb channels. This behavior is not desirable
// if this particular color is later used in color interpolation.
// Because of that, a reference to original color is saved.
this.overwriteGetter('rgb', [r, g, b, alpha]);
}
}
}
/**
* Parses CSS color strings and converts colors to sRGB color space if needed.
* Officially supported color formats:
* - keyword, e.g. 'aquamarine' or 'steelblue'
* - hex (with 3, 4, 6 or 8 digits), e.g. '#f0f' or '#e9bebea9'
* - rgb and rgba, e.g. 'rgb(0,240,120)' or 'rgba(0%,94%,47%,0.1)' or 'rgb(0 240 120 / .3)'
* - hsl and hsla, e.g. 'hsl(0,0%,83%)' or 'hsla(0,0%,83%,.5)' or 'hsl(0 0% 83% / 20%)'
*
* @param input CSS color string to parse.
* @returns A `Color` instance, or `undefined` if the input is not a valid color string.
*/
static parse(input) {
// in zoom-and-property function input could be an instance of Color class
if (input instanceof Color) {
return input;
}
if (typeof input !== 'string') {
return;
}
const rgba = parseCssColor(input);
if (rgba) {
return new Color(...rgba, false);
}
}
/**
* Used in color interpolation and by 'to-rgba' expression.
*
* @returns Gien color, with reversed alpha blending, in sRGB color space.
*/
get rgb() {
const { r, g, b, a } = this;
const f = a || Infinity; // reverse alpha blending factor
return this.overwriteGetter('rgb', [r / f, g / f, b / f, a]);
}
/**
* Used in color interpolation.
*
* @returns Gien color, with reversed alpha blending, in HCL color space.
*/
get hcl() {
return this.overwriteGetter('hcl', rgbToHcl(this.rgb));
}
/**
* Used in color interpolation.
*
* @returns Gien color, with reversed alpha blending, in LAB color space.
*/
get lab() {
return this.overwriteGetter('lab', rgbToLab(this.rgb));
}
/**
* Lazy getter pattern. When getter is called for the first time lazy value
* is calculated and then overwrites getter function in given object instance.
*
* @example:
* const redColor = Color.parse('red');
* let x = redColor.hcl; // this will invoke `get hcl()`, which will calculate
* // the value of red in HCL space and invoke this `overwriteGetter` function
* // which in turn will set a field with a key 'hcl' in the `redColor` object.
* // In other words it will override `get hcl()` from its `Color` prototype
* // with its own property: hcl = [calculated red value in hcl].
* let y = redColor.hcl; // next call will no longer invoke getter but simply
* // return the previously calculated value
* x === y; // true - `x` is exactly the same object as `y`
*
* @param getterKey Getter key
* @param lazyValue Lazily calculated value to be memoized by current instance
* @private
*/
overwriteGetter(getterKey, lazyValue) {
Object.defineProperty(this, getterKey, { value: lazyValue });
return lazyValue;
}
/**
* Used by 'to-string' expression.
*
* @returns Serialized color in format `rgba(r,g,b,a)`
* where r,g,b are numbers within 0..255 and alpha is number within 1..0
*
* @example
* var purple = new Color.parse('purple');
* purple.toString; // = "rgba(128,0,128,1)"
* var translucentGreen = new Color.parse('rgba(26, 207, 26, .73)');
* translucentGreen.toString(); // = "rgba(26,207,26,0.73)"
*/
toString() {
const [r, g, b, a] = this.rgb;
return `rgba(${[r, g, b].map(n => Math.round(n * 255)).join(',')},${a})`;
}
static interpolate(from, to, t, spaceKey = 'rgb') {
switch (spaceKey) {
case 'rgb': {
const [r, g, b, alpha] = interpolateArray(from.rgb, to.rgb, t);
return new Color(r, g, b, alpha, false);
}
case 'hcl': {
const [hue0, chroma0, light0, alphaF] = from.hcl;
const [hue1, chroma1, light1, alphaT] = to.hcl;
// https://github.com/gka/chroma.js/blob/cd1b3c0926c7a85cbdc3b1453b3a94006de91a92/src/interpolator/_hsx.js
let hue, chroma;
if (!isNaN(hue0) && !isNaN(hue1)) {
let dh = hue1 - hue0;
if (hue1 > hue0 && dh > 180) {
dh -= 360;
}
else if (hue1 < hue0 && hue0 - hue1 > 180) {
dh += 360;
}
hue = hue0 + t * dh;
}
else if (!isNaN(hue0)) {
hue = hue0;
if (light1 === 1 || light1 === 0)
chroma = chroma0;
}
else if (!isNaN(hue1)) {
hue = hue1;
if (light0 === 1 || light0 === 0)
chroma = chroma1;
}
else {
hue = NaN;
}
const [r, g, b, alpha] = hclToRgb([
hue,
chroma !== null && chroma !== undefined ? chroma : interpolateNumber(chroma0, chroma1, t),
interpolateNumber(light0, light1, t),
interpolateNumber(alphaF, alphaT, t),
]);
return new Color(r, g, b, alpha, false);
}
case 'lab': {
const [r, g, b, alpha] = labToRgb(interpolateArray(from.lab, to.lab, t));
return new Color(r, g, b, alpha, false);
}
}
}
}
Color.black = new Color(0, 0, 0, 1);
Color.white = new Color(1, 1, 1, 1);
Color.transparent = new Color(0, 0, 0, 0);
Color.red = new Color(1, 0, 0, 1);
// Flow type declarations for Intl cribbed from
// https://github.com/facebook/flow/issues/1270
class Collator {
constructor(caseSensitive, diacriticSensitive, locale) {
if (caseSensitive)
this.sensitivity = diacriticSensitive ? 'variant' : 'case';
else
this.sensitivity = diacriticSensitive ? 'accent' : 'base';
this.locale = locale;
this.collator = new Intl.Collator(this.locale ? this.locale : [], { sensitivity: this.sensitivity, usage: 'search' });
}
compare(lhs, rhs) {
return this.collator.compare(lhs, rhs);
}
resolvedLocale() {
// We create a Collator without "usage: search" because we don't want
// the search options encoded in our result (e.g. "en-u-co-search")
return new Intl.Collator(this.locale ? this.locale : [])
.resolvedOptions().locale;
}
}
const VERTICAL_ALIGN_OPTIONS = ['bottom', 'center', 'top'];
class FormattedSection {
constructor(text, image, scale, fontStack, textColor, verticalAlign) {
this.text = text;
this.image = image;
this.scale = scale;
this.fontStack = fontStack;
this.textColor = textColor;
this.verticalAlign = verticalAlign;
}
}
class Formatted {
constructor(sections) {
this.sections = sections;
}
static fromString(unformatted) {
return new Formatted([new FormattedSection(unformatted, null, null, null, null, null)]);
}
isEmpty() {
if (this.sections.length === 0)
return true;
return !this.sections.some(section => section.text.length !== 0 ||
(section.image && section.image.name.length !== 0));
}
static factory(text) {
if (text instanceof Formatted) {
return text;
}
else {
return Formatted.fromString(text);
}
}
toString() {
if (this.sections.length === 0)
return '';
return this.sections.map(section => section.text).join('');
}
}
/**
* A set of four numbers representing padding around a box. Create instances from
* bare arrays or numeric values using the static method `Padding.parse`.
* @private
*/
class Padding {
constructor(values) {
this.values = values.slice();
}
/**
* Numeric padding values
* @param input A padding value
* @returns A `Padding` instance, or `undefined` if the input is not a valid padding value.
*/
static parse(input) {
if (input instanceof Padding) {
return input;
}
// Backwards compatibility: bare number is treated the same as array with single value.
// Padding applies to all four sides.
if (typeof input === 'number') {
return new Padding([input, input, input, input]);
}
if (!Array.isArray(input)) {
return undefined;
}
if (input.length < 1 || input.length > 4) {
return undefined;
}
for (const val of input) {
if (typeof val !== 'number') {
return undefined;
}
}
// Expand shortcut properties into explicit 4-sided values
switch (input.length) {
case 1:
input = [input[0], input[0], input[0], input[0]];
break;
case 2:
input = [input[0], input[1], input[0], input[1]];
break;
case 3:
input = [input[0], input[1], input[2], input[1]];
break;
}
return new Padding(input);
}
toString() {
return JSON.stringify(this.values);
}
static interpolate(from, to, t) {
return new Padding(interpolateArray(from.values, to.values, t));
}
}
class RuntimeError {
constructor(message) {
this.name = 'ExpressionEvaluationError';
this.message = message;
}
toJSON() {
return this.message;
}
}
/** Set of valid anchor positions, as a set for validation */
const anchors = new Set(['center', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right']);
/**
* Utility class to assist managing values for text-variable-anchor-offset property. Create instances from
* bare arrays using the static method `VariableAnchorOffsetCollection.parse`.
* @private
*/
class VariableAnchorOffsetCollection {
constructor(values) {
this.values = values.slice();
}
static parse(input) {
if (input instanceof VariableAnchorOffsetCollection) {
return input;
}
if (!Array.isArray(input) ||
input.length < 1 ||
input.length % 2 !== 0) {
return undefined;
}
for (let i = 0; i < input.length; i += 2) {
// Elements in even positions should be anchor positions; Elements in odd positions should be offset values
const anchorValue = input[i];
const offsetValue = input[i + 1];
if (typeof anchorValue !== 'string' || !anchors.has(anchorValue)) {
return undefined;
}
if (!Array.isArray(offsetValue) || offsetValue.length !== 2 || typeof offsetValue[0] !== 'number' || typeof offsetValue[1] !== 'number') {
return undefined;
}
}
return new VariableAnchorOffsetCollection(input);
}
toString() {
return JSON.stringify(this.values);
}
static interpolate(from, to, t) {
const fromValues = from.values;
const toValues = to.values;
if (fromValues.length !== toValues.length) {
throw new RuntimeError(`Cannot interpolate values of different length. from: ${from.toString()}, to: ${to.toString()}`);
}
const output = [];
for (let i = 0; i < fromValues.length; i += 2) {
// Anchor entries must match
if (fromValues[i] !== toValues[i]) {
throw new RuntimeError(`Cannot interpolate values containing mismatched anchors. from[${i}]: ${fromValues[i]}, to[${i}]: ${toValues[i]}`);
}
output.push(fromValues[i]);
// Interpolate the offset values for each anchor
const [fx, fy] = fromValues[i + 1];
const [tx, ty] = toValues[i + 1];
output.push([interpolateNumber(fx, tx, t), interpolateNumber(fy, ty, t)]);
}
return new VariableAnchorOffsetCollection(output);
}
}
class ResolvedImage {
constructor(options) {
this.name = options.name;
this.available = options.available;
}
toString() {
return this.name;
}
static fromString(name) {
if (!name)
return null; // treat empty values as no image
return new ResolvedImage({ name, available: false });
}
}
class ProjectionDefinition {
constructor(from, to, transition) {
this.from = from;
this.to = to;
this.transition = transition;
}
static interpolate(from, to, t) {
return new ProjectionDefinition(from, to, t);
}
static parse(input) {
if (input instanceof ProjectionDefinition) {
return input;
}
if (Array.isArray(input) && input.length === 3 && typeof input[0] === 'string' && typeof input[1] === 'string' && typeof input[2] === 'number') {
return new ProjectionDefinition(input[0], input[1], input[2]);
}
if (typeof input === 'object' && typeof input.from === 'string' && typeof input.to === 'string' && typeof input.transition === 'number') {
return new ProjectionDefinition(input.from, input.to, input.transition);
}
if (typeof input === 'string') {
return new ProjectionDefinition(input, input, 1);
}
return undefined;
}
}
function validateRGBA(r, g, b, a) {
if (!(typeof r === 'number' && r >= 0 && r <= 255 &&
typeof g === 'number' && g >= 0 && g <= 255 &&
typeof b === 'number' && b >= 0 && b <= 255)) {
const value = typeof a === 'number' ? [r, g, b, a] : [r, g, b];
return `Invalid rgba value [${value.join(', ')}]: 'r', 'g', and 'b' must be between 0 and 255.`;
}
if (!(typeof a === 'undefined' || (typeof a === 'number' && a >= 0 && a <= 1))) {
re