UNPKG

@maplibre/maplibre-gl-style-spec

Version:
1,531 lines (1,433 loc) 319 kB
#!/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