diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d109e37 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": "airbnb-base", + "overrides": [ + ], + "parserOptions": { + "ecmaVersion": "latest" + }, + "rules": { + } +} diff --git a/index.html b/index.html index e2dea15..3cfdfb5 100644 --- a/index.html +++ b/index.html @@ -496,7 +496,7 @@
- + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..71554e8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2141 @@ +{ + "name": "image2cpp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "image2cpp", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "eslint": "^8.39.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c9a2257 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "image2cpp", + "version": "1.0.0", + "description": "Use this tool online at http://javl.github.io/image2cpp/ image2cpp is a simple tool to change images into byte arrays (or your array back into an image) for use with (monochrome) displays suchs as OLEDs, like those from Adafruit or Sparkfun. While searching for a way to generate these arrays, I mostly found links to a piece of Windows software. Both the flakey results and the hassle of having to boot a virtual machine just to convert an image lead to me writing this pure html + javascript solution.", + "main": "dithering.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "eslint": "^8.39.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5" + } +} diff --git a/script.js b/script.js index fd4b32c..4520d80 100644 --- a/script.js +++ b/script.js @@ -1,958 +1,1133 @@ - - var ConversionFunctions = { - // Output the image as a string for horizontally drawing displays - horizontal1bit: function (data, canvasWidth, canvasHeight){ - var output_string = ""; - var output_index = 0; - - var byteIndex = 7; - var number = 0; - - // format is RGBA, so move 4 steps per pixel - for(var index = 0; index < data.length; index += 4){ - // Get the average of the RGB (we ignore A) - var avg = (data[index] + data[index + 1] + data[index + 2]) / 3; - if(avg > settings["ditheringThreshold"]){ - number += Math.pow(2, byteIndex); - } - byteIndex--; - - // if this was the last pixel of a row or the last pixel of the - // image, fill up the rest of our byte with zeros so it always contains 8 bits - if ((index != 0 && (((index/4)+1)%(canvasWidth)) == 0 ) || (index == data.length-4)) { - // for(var i=byteIndex;i>-1;i--){ - // number += Math.pow(2, i); - // } - byteIndex = -1; - } - - // When we have the complete 8 bits, combine them into a hex value - if(byteIndex < 0){ - var byteSet = bitswap(number).toString(16); - if(byteSet.length == 1){ byteSet = "0"+byteSet; } - if (!settings.removeZeroesCommas) { - output_string += "0x" + byteSet + ", "; - } else { - output_string += byteSet; - } - output_index++; - if(output_index >= 16){ - if (!settings.removeZeroesCommas) { - output_string += "\n"; - } - output_index = 0; - } - number = 0; - byteIndex = 7; - } - } - return output_string; - }, - - - // Output the image as a string for vertically drawing displays - vertical1bit: function (data, canvasWidth, canvasHeight){ - var output_string = ""; - var output_index = 0; - - for(var p=0; p < Math.ceil(settings["screenHeight"] / 8); p++){ - for(var x = 0; x < settings["screenWidth"]; x++){ - var byteIndex = 7; - var number = 0; - - for (var y = 7; y >= 0; y--){ - var index = ((p*8)+y)*(settings["screenWidth"]*4)+x*4; - var avg = (data[index] + data[index +1] + data[index +2]) / 3; - if (avg > settings["ditheringThreshold"]){ - number += Math.pow(2, byteIndex); - } - byteIndex--; - } - var byteSet = bitswap(number).toString(16); - if (byteSet.length == 1){ byteSet = "0"+byteSet; } - if (!settings.removeZeroesCommas) { - output_string += "0x" + byteSet.toString(16) + ", "; - } else { - output_string += byteSet.toString(16); - } - output_index++; - if(output_index >= 16){ - output_string += "\n"; - output_index = 0; - } - } - } - return output_string; - }, - - // Output the image as a string for 565 displays (horizontally) - horizontal565: function (data, canvasWidth, canvasHeight){ - var output_string = ""; - var output_index = 0; - - // format is RGBA, so move 4 steps per pixel - for(var index = 0; index < data.length; index += 4){ - // Get the RGB values - var r = data[index]; - var g = data[index + 1]; - var b = data[index + 2]; - // calculate the 565 color value - var rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3); - // Split up the color value in two bytes - var firstByte = (rgb >> 8) & 0xff; - var secondByte = rgb & 0xff; - - var byteSet = bitswap(rgb).toString(16); - while(byteSet.length < 4){ byteSet = "0" + byteSet; } - if (!settings.removeZeroesCommas) { - output_string += "0x" + byteSet + ", "; - } else { - output_string += byteSet; - } - // add newlines every 16 bytes - output_index++; - if(output_index >= 16){ - output_string += "\n"; - output_index = 0; - } - } - return output_string; - }, - // Output the image as a string for rgb888 displays (horizontally) - horizontal888: function (data, canvasWidth, canvasHeight){ - var output_string = ""; - var output_index = 0; - - // format is RGBA, so move 4 steps per pixel - for(var index = 0; index < data.length; index += 4){ - // Get the RGB values - var r = data[index]; - var g = data[index + 1]; - var b = data[index + 2]; - // calculate the 565 color value - var rgb = (r << 16) | (g << 8) | (b); - // Split up the color value in two bytes - var firstByte = (rgb >> 8) & 0xff; - var secondByte = rgb & 0xff; - - var byteSet = bitswap(rgb).toString(16); - while(byteSet.length < 8){ byteSet = "0" + byteSet; } - if (!settings.removeZeroesCommas) { - output_string += "0x" + byteSet + ", "; - } else { - output_string += byteSet; - } - - // add newlines every 16 bytes - output_index++; - if(output_index >= canvasWidth){ - output_string += "\n"; - output_index = 0; - } - } - return output_string; - }, - // Output the alpha mask as a string for horizontally drawing displays - horizontalAlpha: function (data, canvasWidth, canvasHeight){ - var output_string = ""; - var output_index = 0; - - var byteIndex = 7; - var number = 0; - - // format is RGBA, so move 4 steps per pixel - for(var index = 0; index < data.length; index += 4){ - // Get alpha part of the image data - var alpha = data[index + 3]; - if(alpha > settings["ditheringThreshold"]){ - number += Math.pow(2, byteIndex); - } - byteIndex--; - - // if this was the last pixel of a row or the last pixel of the - // image, fill up the rest of our byte with zeros so it always contains 8 bits - if ((index != 0 && (((index/4)+1)%(canvasWidth)) == 0 ) || (index == data.length-4)) { - byteIndex = -1; - } - - // When we have the complete 8 bits, combine them into a hex value - if(byteIndex < 0){ - var byteSet = bitswap(number).toString(16); - if(byteSet.length == 1){ byteSet = "0"+byteSet; } - if (!settings.removeZeroesCommas) { - output_string += "0x"+byteSet + ", "; - } else { - output_string += byteSet; - } - output_index++; - if(output_index >= 16){ - output_string += "\n"; - output_index = 0; - } - number = 0; - byteIndex = 7; - } - } - return output_string; - } - }; - - +/* eslint-disable no-console */ +/* eslint-disable radix */ +/* eslint-disable max-len */ +/* eslint-disable no-plusplus */ // A bunch of settings used when converting - var settings = { - screenWidth: 128, - screenHeight: 64, - scaleToFit: true, - preserveRatio: true, - centerHorizontally: false, - centerVertically: false, - flipHorizontally: false, - flipVertically: false, - backgroundColor: "white", - scale: 1, - drawMode: "horizontal", - removeZeroesCommas: false, - ditheringThreshold: 128, - ditheringMode: 0, - outputFormat: "plain", - invertColors: false, - rotation: 0, - conversionFunction: ConversionFunctions.horizontal1bit - }; +const settings = { + screenWidth: 128, + screenHeight: 64, + scaleToFit: true, + preserveRatio: true, + centerHorizontally: false, + centerVertically: false, + flipHorizontally: false, + flipVertically: false, + backgroundColor: 'white', + scale: 1, + drawMode: 'horizontal', + removeZeroesCommas: false, + ditheringThreshold: 128, + ditheringMode: 0, + outputFormat: 'plain', + invertColors: false, + rotation: 0, +}; - function bitswap(b) { - if(settings["bitswap"]) { - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - } - return b; +let outputString = ''; + +function bitswap(b) { + if (settings.bitswap) { + // eslint-disable-next-line no-param-reassign, no-bitwise, no-mixed-operators + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + // eslint-disable-next-line no-param-reassign, no-bitwise, no-mixed-operators + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + // eslint-disable-next-line no-param-reassign, no-bitwise, no-mixed-operators + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; } + return b; +} - // get the type (in arduino code) of the output image - // this is a bit of a hack, it's better to make this a property of the conversion function (should probably turn it into objects) - function getType() { - if (settings.conversionFunction == ConversionFunctions.horizontal565) { - return "uint16_t"; - }else if(settings.conversionFunction == ConversionFunctions.horizontal888){ - return "unsigned long"; - }else{ - return "unsigned char"; - } - } +const ConversionFunctions = { + // Output the image as a string for horizontally drawing displays + // eslint-disable-next-line no-unused-vars + horizontal1bit(data, canvasWidth, canvasHeight) { + // eslint-disable-next-line no-shadow + outputString = ''; + let outputIndex = 0; + let byteIndex = 7; + let number = 0; - // Invert the colors of the canvas - function invert(canvas, ctx) { - var imageData = ctx.getImageData(0,0,canvas.width, canvas.height); - var data = imageData.data; - for (var i = 0; i < data.length; i += 4) { - data[i] = 255 - data[i]; // red - data[i + 1] = 255 - data[i + 1]; // green - data[i + 2] = 255 - data[i + 2]; // blue + // format is RGBA, so move 4 steps per pixel + for (let index = 0; index < data.length; index += 4) { + // Get the average of the RGB (we ignore A) + const avg = (data[index] + data[index + 1] + data[index + 2]) / 3; + if (avg > settings.ditheringThreshold) { + number += 2 ** byteIndex; } - ctx.putImageData(imageData, 0, 0); - } + byteIndex--; - // Draw the image onto the canvas, taking into account color and scaling - function place_image(image){ - - var img = image.img; - var canvas = image.canvas; - var ctx = canvas.getContext("2d"); - image.ctx = ctx; - ctx.save(); - - const flipHorizontal = settings["flipHorizontally"] ? -1 : 1; - const flipVertical = settings["flipVertically"] ? -1 : 1; - - if (settings['rotation'] == 0) { - canvas.width = settings['screenWidth']; - canvas.height = settings['screenHeight']; - const xTranslate = settings["flipHorizontally"] ? canvas.width : 0; - const yTranslate = settings["flipVertically"] ? canvas.height : 0; - ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); - - } else if (settings['rotation'] == 90) { - canvas.width = settings['screenHeight']; - canvas.height = settings['screenWidth']; - const xTranslate = settings["flipHorizontally"] ? canvas.width : 0; - const yTranslate = settings["flipVertically"] ? canvas.height : 0; - ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); - // ctx = canvas.getContext("2d"); - ctx.translate(canvas.width, 0); - ctx.rotate(Math.PI/2); - // ctx.translate(-canvas.width/2, 0); - - } else if (settings['rotation'] == 180) { - canvas.width = settings['screenWidth']; - canvas.height = settings['screenHeight']; - const xTranslate = settings["flipHorizontally"] ? canvas.width : 0; - const yTranslate = settings["flipVertically"] ? canvas.height : 0; - ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); - // Matrix transformation - ctx.translate(canvas.width/2.0, canvas.height/2.0); - ctx.rotate(Math.PI); - ctx.translate(-canvas.width/2.0, -canvas.height/2.0); - - } else if (settings['rotation'] == 270) { - canvas.width = settings['screenHeight']; - canvas.height = settings['screenWidth']; - const xTranslate = settings["flipHorizontally"] ? canvas.width : 0; - const yTranslate = settings["flipVertically"] ? canvas.height : 0; - ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); - ctx.translate(0, canvas.height); - ctx.rotate(-Math.PI*0.5); + // if this was the last pixel of a row or the last pixel of the + // image, fill up the rest of our byte with zeros so it always contains 8 bits + if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) { + // for(var i=byteIndex;i>-1;i--){ + // number += Math.pow(2, i); + // } + byteIndex = -1; } - // Draw background - if (settings["backgroundColor"] == "transparent") { - ctx.fillStyle = "rgba(0,0,0,0.0)"; - ctx.globalCompositeOperation = 'copy'; - } else { - if (settings["invertColors"]){ - settings["backgroundColor"] == "white" ? ctx.fillStyle = "black" : ctx.fillStyle = "white"; + // When we have the complete 8 bits, combine them into a hex value + if (byteIndex < 0) { + let byteSet = bitswap(number).toString(16); + if (byteSet.length === 1) { byteSet = `0${byteSet}`; } + if (!settings.removeZeroesCommas) { + outputString += `0x${byteSet}, `; } else { - ctx.fillStyle = settings["backgroundColor"]; + outputString += byteSet; } - ctx.globalCompositeOperation = 'source-over'; - } - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Offset used for centering the image when requested - var offset_x = 0; - var offset_y = 0; - // var offset_x_dir = 1; - // var offset_y_dir = 1; - - var imgW = img.width; - var imgH = img.height; - // if (settings['flipHorizontally']) { - // imgW = -img.width; - // offset_x_dir = -1; - // } - // if (settings['flipVertically']) { - // imgH = -img.height; - // offset_y_dir = -1; - // } - - switch(settings["scale"]){ - case 1: // Original - if(settings["centerHorizontally"]){ offset_x = Math.round((canvas.width - imgW) / 2); } - if(settings["centerVertically"]){ offset_y = Math.round((canvas.height - imgH) / 2); } - ctx.drawImage(img, 0, 0, imgW, imgH, - offset_x, offset_y, imgW, imgH); - break; - case 2: // Fit (make as large as possible without changing ratio) - var horRatio = canvas.width / imgW; - var verRatio = canvas.height / imgH; - var useRatio = Math.min(horRatio, verRatio); - - if(settings["centerHorizontally"]){ offset_x = Math.round((canvas.width - imgW*useRatio) / 2); } - if(settings["centerVertically"]){ offset_y = Math.round((canvas.height - imgH*useRatio) / 2); } - // offset_x *= offset_x_dir; - // offset_y *= offset_y_dir; - ctx.drawImage(img, 0, 0, imgW, imgH, - offset_x, offset_y, imgW * useRatio, imgH * useRatio); - break; - case 3: // Stretch x+y (make as large as possible without keeping ratio) - ctx.drawImage(img, 0, 0, imgW, imgH, - offset_x, offset_y, canvas.width, canvas.height); - break; - case 4: // Stretch x (make as wide as possible) - offset_x = 0; - if(settings["centerVertically"]){ Math.round(offset_y = (canvas.height - imgH) / 2); } - // offset_y *= offset_y_dir; - ctx.drawImage(img, 0, 0, imgW, imgH, - offset_x, offset_y, canvas.width, imgH); - break; - case 5: // Stretch y (make as tall as possible) - if(settings["centerHorizontally"]){ offset_x = Math.round((canvas.width - imgW) / 2); } - // offset_x *= offset_x_dir; - offset_y = 0; - ctx.drawImage(img, 0, 0, imgW, imgH, - offset_x, offset_y, imgW, canvas.height); - break; - } - ctx.restore(); - - // Make sure the image is black and white - if(settings.conversionFunction == ConversionFunctions.horizontal1bit - || settings.conversionFunction == ConversionFunctions.vertical1bit) { - dithering(ctx, canvas.width, canvas.height, settings.ditheringThreshold, settings.ditheringMode);(canvas, ctx); - if(settings["invertColors"]){ - invert(canvas, ctx); - } - } - } - - // Handle inserting an image by pasting code - function handleTextInput(drawMode){ - - var canvasContainer = document.getElementById("images-canvas-container"); - var canvas = document.createElement("canvas"); - - canvas.width = parseInt(document.getElementById("text-input-width").value); - canvas.height = parseInt(document.getElementById("text-input-height").value); - settings["screenWidth"] = canvas.width; - settings["screenHeight"] = canvas.height; - - if(canvasContainer.children.length) { - canvasContainer.removeChild(canvasContainer.firstChild); - } - canvasContainer.appendChild(canvas); - - var image = new Image(); - images.setByIndex(0, {"img": image, "canvas" : canvas}); - - var input = document.getElementById("byte-input").value; - - // Remove Arduino code - input = input.replace(/const\s+(unsigned\s+char|uint8_t)\s+[a-zA-Z0-9]+\s*\[\]\s*(PROGMEM\s*)?=\s*/g, ""); - input = input.replace(/\};|\{/g, ""); - - // Convert newlines to comma (helps to remove comments later) - input = input.replace(/\r\n|\r|\n/g, ","); - // Convert multiple commas in a row into a single one - input = input.replace(/,{2,}/g, ","); - // Remove whitespace - input = input.replace(/\s/g, ""); - //Remove comments - input = input.replace(/\/\/(.+?),/g, ""); - // Remove "0x" - input = input.replace(/0[xX]/g, ""); - // Split into list - var list = input.split(","); - - if(drawMode == "horizontal"){ - listToImageHorizontal(list, canvas); - }else{ - listToImageVertical(list, canvas); - } - } - - function allSameSize(images, files) { - if(images.length() > 1 && images.length() == files.length) { - // multiple images settings container - // var imageSizeSettings = document.getElementById("image-size-settings"); - var inputs = document.querySelectorAll("#image-size-settings input"); - allSameSizeButton.onclick = function() { - for(var i = 2; i < inputs.length; i++) { - if(inputs[i].name == "width") { - inputs[i].value = inputs[0].value; - inputs[i].oninput(); - } - if(inputs[i].name == "height") { - inputs[i].value = inputs[1].value; - inputs[i].oninput(); - } - + outputIndex++; + if (outputIndex >= 16) { + if (!settings.removeZeroesCommas) { + outputString += '\n'; } - }; - allSameSizeButton.style.display = "block"; + outputIndex = 0; + } + number = 0; + byteIndex = 7; } } + return outputString; + }, - // Handle selecting an image with the file picker - function handleImageSelection(evt){ + // Output the image as a string for vertically drawing displays + // eslint-disable-next-line no-unused-vars + vertical1bit(data, canvasWidth, canvasHeight) { + // eslint-disable-next-line no-shadow + outputString = ''; + let outputIndex = 0; - var files = Array.from(evt.target.files); - files.sort((a, b) => a.name > b.name); - // error message - var onlyImagesFileError = document.getElementById("only-images-file-error"); + for (let p = 0; p < Math.ceil(settings.screenHeight / 8); p++) { + for (let x = 0; x < settings.screenWidth; x++) { + let byteIndex = 7; + let number = 0; - onlyImagesFileError.style.display = "none"; - - // initial message - var noFileSelected = document.getElementById("no-file-selected"); - files.length > 0 ? noFileSelected.style.display = "none" : noFileSelected.style.display = "block"; - - for (var i = 0, f; f = files[i]; i++) { - - // Only process image files. - if(!f.type.match("image.*")) { - onlyImagesFileError.style.display = "block"; - continue; - } - - var reader = new FileReader(); - - reader.onload = (function(file) { - return function(e) { - // Render thumbnail. - var img = new Image(); - - img.onload = function(){ - const fileInputColumnEntry = document.createElement("div"); - fileInputColumnEntry.className = "file-input-entry"; - - const fileInputColumnEntryLabel = document.createElement("span"); - fileInputColumnEntryLabel.textContent = file.name; - - const fileInputColumnEntryRemoveButton = document.createElement("button"); - fileInputColumnEntryRemoveButton.className = "remove-button"; - fileInputColumnEntryRemoveButton.innerHTML = "remove"; - - var canvas = document.createElement("canvas"); - - var imageEntry = document.createElement("li"); - imageEntry.setAttribute("data-img", file.name); - - var w = document.createElement("input"); - w.type = "number"; - w.name = "width"; - w.id = "screenWidth"; - w.min = 0; - w.className = "size-input"; - w.value = img.width; - settings["screenWidth"] = img.width; - w.oninput = function() { canvas.width = this.value; update(); updateInteger('screenWidth'); }; - - var h = document.createElement("input"); - h.type = "number"; - h.name = "height"; - h.id = "screenHeight"; - h.min = 0; - h.className = "size-input"; - h.value = img.height; - settings["screenHeight"] = img.height; - h.oninput = function() { canvas.height = this.value; update(); updateInteger('screenHeight'); }; - - var gil = document.createElement("span"); - gil.innerHTML = "glyph"; - gil.className = "file-info"; - - var gi = document.createElement("input"); - gi.type = "text"; - gi.name = "glyph"; - gi.className = "glyph-input"; - gi.onchange = function() { - var image = images.get(img); - image.glyph = gi.value; - }; - - var fn = document.createElement("span"); - fn.className = "file-info"; - fn.innerHTML = file.name + " (file resolution: " + img.width + " x " + img.height + ")"; - fn.innerHTML += "
"; - - var rb = document.createElement("button"); - rb.className = "remove-button"; - rb.innerHTML = "remove"; - - var fileInputColumn = document.getElementById("file-input-column"); - var imageSizeSettings = document.getElementById("image-size-settings"); - var canvasContainer = document.getElementById("images-canvas-container"); - - const removeButtonOnClick = function() { - var image = images.get(img); - canvasContainer.removeChild(image.canvas); - images.remove(image); - imageSizeSettings.removeChild(imageEntry); - - - fileInputColumn.removeChild(fileInputColumnEntry); - if(imageSizeSettings.children.length == 1) { - allSameSizeButton.style.display = "none"; - } - if(images.length() == 0) noFileSelected.style.display = "block"; - update(); - }; - - rb.onclick = removeButtonOnClick; - fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; - - fileInputColumnEntry.appendChild(fileInputColumnEntryLabel); - fileInputColumnEntry.appendChild(fileInputColumnEntryRemoveButton); - fileInputColumn.appendChild(fileInputColumnEntry); - - imageEntry.appendChild(fn); - imageEntry.appendChild(w); - imageEntry.appendChild(document.createTextNode(" x ")); - imageEntry.appendChild(h); - imageEntry.appendChild(gil); - imageEntry.appendChild(gi); - imageEntry.appendChild(rb); - - imageSizeSettings.appendChild(imageEntry); - - canvas.width = img.width; - canvas.height = img.height; - canvasContainer.appendChild(canvas); - - images.push(img, canvas, file.name.split(".")[0]); - place_image(images.last()); - allSameSize(images, files); - }; - img.src = e.target.result; - }; - })(f); - reader.readAsDataURL(f); - } - } - - function imageToString(image){ - // extract raw image data - var ctx = image.ctx; - var canvas = image.canvas; - - var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - var data = imageData.data; - return settings.conversionFunction(data, canvas.width, canvas.height); - } - - // Get the custom arduino output variable name, if any - function getIdentifier() { - var vn = document.getElementById("identifier"); - return vn && vn.value.length ? vn.value : identifier; - } - - // Output the image string to the textfield - function outputString(){ - - var output_string = "", count = 1; - var code = ""; - - switch(settings["outputFormat"]) { - - case "arduino": { - let varQuickArray = []; - let bytesUsed = 0; - // -- - images.each(function(image) { - code = imageToString(image); - - // Trim whitespace from end and remove trailing comma - code = code.replace(/,\s*$/,""); - - code = "\t" + code.split("\n").join("\n\t") + "\n"; - var variableCount = images.length() > 1 ? count++ : ""; - var comment = "// '" + image.glyph + "', "+image.canvas.width+"x"+image.canvas.height+"px\n"; - bytesUsed += code.split("\n").length * 16; // 16 bytes per line. - - let varname = getIdentifier() + image.glyph.replace(/[^a-zA-Z0-9]/g,"_"); - varQuickArray.push(varname); - code = comment + "const " + getType() + " " + varname + " [] PROGMEM = {\n" + code + "};\n"; - output_string += code; - }); - // -- - varQuickArray.sort(); - // -- - output_string += "\n// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = "+bytesUsed+")\n" - output_string += "const int " + (getIdentifier()+"allArray_LEN") + " = " + varQuickArray.length + ";\n" - output_string += "const " + getType() + "* " + (getIdentifier()+"allArray") + "["+varQuickArray.length+"] = {\n\t"+varQuickArray.join(",\n\t")+"\n};\n"; - // -- - break; - } - - case "arduino_single": { - var comment = ""; - images.each(function(image) { - code = imageToString(image); - code = "\t" + code.split("\n").join("\n\t") + "\n"; - comment = "\t// '" + image.glyph + ", " + image.canvas.width+"x"+image.canvas.height+"px\n"; - output_string += comment + code; - }); - - output_string = output_string.replace(/,\s*$/,""); - - output_string = "const " + getType() + " " + - + getIdentifier() - + " [] PROGMEM = {" - + "\n" + output_string + "\n};"; - break; - } - - case "adafruit_gfx": { // bitmap - var comment = ""; - var useGlyphs = 0; - images.each(function(image) { - code = imageToString(image); - code = "\t" + code.split("\n").join("\n\t") + "\n"; - comment = "\t// '" + image.glyph + ", " + image.canvas.width+"x"+image.canvas.height+"px\n"; - output_string += comment + code; - if(image.glyph.length == 1) useGlyphs++; - }); - - output_string = output_string.replace(/,\s*$/,""); - output_string = "const unsigned char " - + getIdentifier() - + "Bitmap" - + " [] PROGMEM = {" - + "\n" + output_string + "\n};\n\n" - + "const GFXbitmapGlyph " - + getIdentifier() - + "Glyphs [] PROGMEM = {\n"; - - var firstAschiiChar = document.getElementById("first-ascii-char").value; - var xAdvance = parseInt(document.getElementById("x-advance").value); - var offset = 0; - code = ""; - - // GFXbitmapGlyph - images.each(function(image) { - code += "\t{ " - + offset + ", " - + image.canvas.width + ", " - + image.canvas.height + ", " - + xAdvance + ", " - + "'" + (images.length() == useGlyphs ? - image.glyph : - String.fromCharCode(firstAschiiChar++)) + "'" - + " }"; - if(image != images.last()){ code += ","; } - code += "// '" + image.glyph + "'\n"; - offset += image.canvas.width; - }); - code += "};\n"; - output_string += code; - - // GFXbitmapFont - output_string += "\nconst GFXbitmapFont " - + getIdentifier() - + "Font PROGMEM = {\n" - + "\t(uint8_t *)" - + getIdentifier() + "Bitmap,\n" - + "\t(GFXbitmapGlyph *)" - + getIdentifier() - + "Glyphs,\n" - + "\t" + images.length() - + "\n};\n"; - break; - } - default: { // plain - images.each(function(image) { - code = imageToString(image); - var comment = image.glyph ? ("// '" + image.glyph + "', " + image.canvas.width+"x"+image.canvas.height+"px\n") : ""; - if(image.img != images.first().img) comment = "\n" + comment; - code = comment + code; - output_string += code; - }); - // Trim whitespace from end and remove trailing comma - output_string = output_string.replace(/,\s*$/g,""); - } - } - - document.getElementById("code-output").value = output_string; - __output = output_string; - document.getElementById("copy-button").disabled = false; - } - - function copyOutput() { - navigator.clipboard.writeText(__output); - } - - function downloadFile() { - raw = []; - images.each((image) => { - data = imageToString(image) - .split(',') - .map(s => s.trim()) - .filter(Boolean) - .map((byte) => parseInt(byte, 16)); - raw = raw.concat(data); - }); - data = new Uint8Array(raw); - a = document.createElement("a"); - a.style = "display: none"; - document.body.appendChild(a); - blob = new Blob([data], {type: "octet/stream"}); - url = window.URL.createObjectURL(blob); - a.href = url; - a.download = getIdentifier() + ".bin"; - a.click(); - window.URL.revokeObjectURL(url); - } - - // Use the horizontally oriented list to draw the image - function listToImageHorizontal(list, canvas){ - - var ctx = canvas.getContext("2d"); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - var imgData = ctx.createImageData(canvas.width, canvas.height); - - var index = 0; - - var page = 0; - var x = 0; - var y = 7; - // round the width up to the next byte - var widthRoundedUp = Math.floor(canvas.width / 8 + (canvas.width % 8 ? 1 : 0)) * 8; - var widthCounter = 0; - - // Move the list into the imageData object - for (var i=0;i= widthRoundedUp) { - widthCounter = 0; + for (let y = 7; y >= 0; y--) { + const index = ((p * 8) + y) * (settings.screenWidth * 4) + x * 4; + const avg = (data[index] + data[index + 1] + data[index + 2]) / 3; + if (avg > settings.ditheringThreshold) { + number += 2 ** byteIndex; } - // skip 'artifact' pixels due to rounding up to a byte - if(widthCounter >= canvas.width) { - continue; - } - var color = 0; - if(binString.charAt(k) == "1"){ - color = 255; - } - imgData.data[index] = color; - imgData.data[index+1] = color; - imgData.data[index+2] = color; - imgData.data[index+3] = 255; - - index += 4; + byteIndex--; + } + let byteSet = bitswap(number).toString(16); + if (byteSet.length === 1) { byteSet = `0${byteSet}`; } + if (!settings.removeZeroesCommas) { + outputString += `0x${byteSet.toString(16)}, `; + } else { + outputString += byteSet.toString(16); + } + outputIndex++; + if (outputIndex >= 16) { + outputString += '\n'; + outputIndex = 0; } } - - // Draw the image onto the canvas, then save the canvas contents - // inside the img object. This way we can reuse the img object when - // we want to scale / invert, etc. - ctx.putImageData(imgData, 0, 0); - var img = new Image(); - img.src = canvas.toDataURL("image/png"); - images.first().img = img; } + return outputString; + }, + // Output the image as a string for 565 displays (horizontally) + // eslint-disable-next-line no-unused-vars + horizontal565(data, canvasWidth, canvasHeight) { + // eslint-disable-next-line no-shadow + outputString = ''; + let outputIndex = 0; - function updateDrawMode(elm) { - // var note = document.getElementById("note1bit"); - // if(elm.value == "horizontal1bit" || elm.value == "vertical1bit") { - // note.style.display = "block"; - // } else { - // note.style.display = "none"; - // } + // format is RGBA, so move 4 steps per pixel + for (let index = 0; index < data.length; index += 4) { + // Get the RGB values + const r = data[index]; + const g = data[index + 1]; + const b = data[index + 2]; + // calculate the 565 color value + // eslint-disable-next-line no-bitwise + const rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3); + // Split up the color value in two bytes + // const firstByte = (rgb >> 8) & 0xff; + // const secondByte = rgb & 0xff; - var conversionFunction = ConversionFunctions[elm.value]; - if(conversionFunction) { - settings.conversionFunction = conversionFunction; + let byteSet = bitswap(rgb).toString(16); + while (byteSet.length < 4) { byteSet = `0${byteSet}`; } + if (!settings.removeZeroesCommas) { + outputString += `0x${byteSet}, `; + } else { + outputString += byteSet; + } + // add newlines every 16 bytes + outputIndex++; + if (outputIndex >= 16) { + outputString += '\n'; + outputIndex = 0; + } } + return outputString; + }, + // Output the image as a string for rgb888 displays (horizontally) + // eslint-disable-next-line no-unused-vars + horizontal888(data, canvasWidth, canvasHeight) { + // eslint-disable-next-line no-shadow + outputString = ''; + let outputIndex = 0; + + // format is RGBA, so move 4 steps per pixel + for (let index = 0; index < data.length; index += 4) { + // Get the RGB values + const r = data[index]; + const g = data[index + 1]; + const b = data[index + 2]; + // calculate the 565 color value + // eslint-disable-next-line no-bitwise + const rgb = (r << 16) | (g << 8) | (b); + // Split up the color value in two bytes + // const firstByte = (rgb >> 8) & 0xff; + // const secondByte = rgb & 0xff; + + let byteSet = bitswap(rgb).toString(16); + while (byteSet.length < 8) { byteSet = `0${byteSet}`; } + if (!settings.removeZeroesCommas) { + outputString += `0x${byteSet}, `; + } else { + outputString += byteSet; + } + + // add newlines every 16 bytes + outputIndex++; + if (outputIndex >= canvasWidth) { + outputString += '\n'; + outputIndex = 0; + } + } + return outputString; + }, + // Output the alpha mask as a string for horizontally drawing displays + // eslint-disable-next-line no-unused-vars + horizontalAlpha(data, canvasWidth, canvasHeight) { + // eslint-disable-next-line no-shadow + outputString = ''; + let outputIndex = 0; + + let byteIndex = 7; + let number = 0; + + // format is RGBA, so move 4 steps per pixel + for (let index = 0; index < data.length; index += 4) { + // Get alpha part of the image data + const alpha = data[index + 3]; + if (alpha > settings.ditheringThreshold) { + number += 2 ** byteIndex; + } + byteIndex--; + + // if this was the last pixel of a row or the last pixel of the + // image, fill up the rest of our byte with zeros so it always contains 8 bits + if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) { + byteIndex = -1; + } + + // When we have the complete 8 bits, combine them into a hex value + if (byteIndex < 0) { + let byteSet = bitswap(number).toString(16); + if (byteSet.length === 1) { byteSet = `0${byteSet}`; } + if (!settings.removeZeroesCommas) { + outputString += `0x${byteSet}, `; + } else { + outputString += byteSet; + } + outputIndex++; + if (outputIndex >= 16) { + outputString += '\n'; + outputIndex = 0; + } + number = 0; + byteIndex = 7; + } + } + return outputString; + }, +}; +settings.ConversionFunction = ConversionFunctions.horizontal1bit; + +// An images collection with helper methods +function Images() { + const collection = []; + this.push = (img, canvas, glyph) => { collection.push({ img, canvas, glyph }); }; + this.remove = (image) => { + const i = collection.indexOf(image); + if (i !== -1) collection.splice(i, 1); + }; + this.each = (f) => { collection.forEach(f); }; + this.length = () => collection.length; + this.first = () => collection[0]; + this.last = () => collection[collection.length - 1]; + this.getByIndex = (index) => collection[index]; + this.setByIndex = (index, img) => { collection[index] = img; }; + this.get = (img) => { + if (img) { + for (let i = 0; i < collection.length; i++) { + if (collection[i].img === img) { + return collection[i]; + } + } + } + return collection; + }; + return this; +} + +const images = new Images(); +let output = ''; +// Filetypes accepted by the file picker +// const fileTypes = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg']; +// Variable name, when "arduino code" is required +const identifier = 'myBitmap'; + +// Invert the colors of the canvas +function invert(canvas, ctx) { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let i = 0; i < data.length; i += 4) { + data[i] = 255 - data[i]; // red + data[i + 1] = 255 - data[i + 1]; // green + data[i + 2] = 255 - data[i + 2]; // blue + } + ctx.putImageData(imageData, 0, 0); +} + +// Draw the image onto the canvas, taking into account color and scaling +function placeImage(_image) { + const { img } = _image; + const { canvas } = _image; + const ctx = canvas.getContext('2d'); + // eslint-disable-next-line no-param-reassign + _image.ctx = ctx; + ctx.save(); + + const flipHorizontal = settings.flipHorizontally ? -1 : 1; + const flipVertical = settings.flipVertically ? -1 : 1; + + if (settings.rotation === 0) { + canvas.width = settings.screenWidth; + canvas.height = settings.screenHeight; + const xTranslate = settings.flipHorizontally ? canvas.width : 0; + const yTranslate = settings.flipVertically ? canvas.height : 0; + ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); + } else if (settings.rotation === 90) { + canvas.width = settings.screenHeight; + canvas.height = settings.screenWidth; + const xTranslate = settings.flipHorizontally ? canvas.width : 0; + const yTranslate = settings.flipVertically ? canvas.height : 0; + ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); + // ctx = canvas.getContext("2d"); + ctx.translate(canvas.width, 0); + ctx.rotate(Math.PI / 2); + // ctx.translate(-canvas.width/2, 0); + } else if (settings.rotation === 180) { + canvas.width = settings.screenWidth; + canvas.height = settings.screenHeight; + const xTranslate = settings.flipHorizontally ? canvas.width : 0; + const yTranslate = settings.flipVertically ? canvas.height : 0; + ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); + // Matrix transformation + ctx.translate(canvas.width / 2.0, canvas.height / 2.0); + ctx.rotate(Math.PI); + ctx.translate(-canvas.width / 2.0, -canvas.height / 2.0); + } else if (settings.rotation === 270) { + canvas.width = settings.screenHeight; + canvas.height = settings.screenWidth; + const xTranslate = settings.flipHorizontally ? canvas.width : 0; + const yTranslate = settings.flipVertically ? canvas.height : 0; + ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); + ctx.translate(0, canvas.height); + ctx.rotate(-Math.PI * 0.5); } - - // Updates Arduino code check-box - function updateOutputFormat(elm) { - var caption = document.getElementById("format-caption-container"); - var adafruitGfx = document.getElementById("adafruit-gfx-settings"); - var arduino = document.getElementById("arduino-identifier"); - var removeZeroesCommasContainer = document.getElementById("remove-zeroes-commas-container"); - - for(var i = 0; i < caption.children.length; i++) { - caption.children[i].style.display = "none"; - } - caption = document.querySelector("div[data-caption='" + elm.value + "']"); - if(caption) caption.style.display = "block"; - - if (elm.value != "plain") { - arduino.style.display = "block"; - removeZeroesCommasContainer.style.display = "none"; - settings.removeZeroesCommas = false; + // Draw background + if (settings.backgroundColor === 'transparent') { + ctx.fillStyle = 'rgba(0,0,0,0.0)'; + ctx.globalCompositeOperation = 'copy'; + } else { + if (settings.invertColors) { + if (settings.backgroundColor === 'white') { + ctx.fillStyle = 'black'; + } else { + ctx.fillStyle = 'white'; + } } else { - arduino.style.display = "none"; - removeZeroesCommasContainer.style.display = "table-row"; + ctx.fillStyle = settings.backgroundColor; } - elm.value == "adafruit_gfx" ? adafruitGfx.style.display = "block" : adafruitGfx.style.display = "none"; + ctx.globalCompositeOperation = 'source-over'; + } + ctx.fillRect(0, 0, canvas.width, canvas.height); - settings["outputFormat"] = elm.value; + // Offset used for centering the image when requested + let offsetX = 0; + let offsetY = 0; + + const imgW = img.width; + const imgH = img.height; + + switch (settings.scale) { + case 1: // Original + if (settings.centerHorizontally) { offsetX = Math.round((canvas.width - imgW) / 2); } + if (settings.centerVertically) { offsetY = Math.round((canvas.height - imgH) / 2); } + ctx.drawImage( + img, + 0, + 0, + imgW, + imgH, + offsetX, + offsetY, + imgW, + imgH, + ); + break; + case 2: // Fit (make as large as possible without changing ratio) + // eslint-disable-next-line no-case-declarations + const horRatio = canvas.width / imgW; + // eslint-disable-next-line no-case-declarations + const verRatio = canvas.height / imgH; + // eslint-disable-next-line no-case-declarations + const useRatio = Math.min(horRatio, verRatio); + + if (settings.centerHorizontally) { offsetX = Math.round((canvas.width - imgW * useRatio) / 2); } + if (settings.centerVertically) { offsetY = Math.round((canvas.height - imgH * useRatio) / 2); } + // offsetX *= offsetX_dir; + // offsetY *= offsetY_dir; + ctx.drawImage( + img, + 0, + 0, + imgW, + imgH, + offsetX, + offsetY, + imgW * useRatio, + imgH * useRatio, + ); + break; + case 3: // Stretch x+y (make as large as possible without keeping ratio) + ctx.drawImage( + img, + 0, + 0, + imgW, + imgH, + offsetX, + offsetY, + canvas.width, + canvas.height, + ); + break; + case 4: // Stretch x (make as wide as possible) + offsetX = 0; + if (settings.centerVertically) { Math.round(offsetY = (canvas.height - imgH) / 2); } + // offsetY *= offsetY_dir; + ctx.drawImage( + img, + 0, + 0, + imgW, + imgH, + offsetX, + offsetY, + canvas.width, + imgH, + ); + break; + case 5: // Stretch y (make as tall as possible) + if (settings.centerHorizontally) { offsetX = Math.round((canvas.width - imgW) / 2); } + // offsetX *= offsetX_dir; + offsetY = 0; + ctx.drawImage( + img, + 0, + 0, + imgW, + imgH, + offsetX, + offsetY, + imgW, + canvas.height, + ); + break; + default: + // console.log('unknown scale'); + break; + } + ctx.restore(); + + // Make sure the image is black and white + console.log('dithering: ', dithering); + if (settings.conversionFunction === ConversionFunctions.horizontal1bit + || settings.conversionFunction === ConversionFunctions.vertical1bit) { + dithering(ctx, canvas.width, canvas.height, settings.ditheringThreshold, settings.ditheringMode); + if (settings.invertColors) { + invert(canvas, ctx); + } + } +} + +function update() { + images.each((image) => { placeImage(image); }); +} + +// Easy way to update settings controlled by a textfield +function updateInteger(fieldName) { + settings[fieldName] = parseInt(document.getElementById(fieldName).value); + update(); +} + +// Easy way to update settings controlled by a checkbox +// eslint-disable-next-line no-unused-vars +function updateBoolean(fieldName) { + settings[fieldName] = document.getElementById(fieldName).checked; + update(); +} + +// Convert hex to binary +function hexToBinary(s) { + let i; + let ret = ''; + // lookup table for easier conversion. "0" characters are + // padded for "1" to "7" + const lookupTable = { + 0: '0000', + 1: '0001', + 2: '0010', + 3: '0011', + 4: '0100', + 5: '0101', + 6: '0110', + 7: '0111', + 8: '1000', + 9: '1001', + a: '1010', + b: '1011', + c: '1100', + d: '1101', + e: '1110', + f: '1111', + A: '1010', + B: '1011', + C: '1100', + D: '1101', + E: '1110', + F: '1111', + }; + for (i = 0; i < s.length; i += 1) { + // eslint-disable-next-line no-prototype-builtins + if (lookupTable.hasOwnProperty(s[i])) { + ret += lookupTable[s[i]]; + } else { + return { valid: false, s }; + } + } + return { valid: true, result: ret }; +} + +// get the type (in arduino code) of the output image +// this is a bit of a hack, it's better to make this a property of the conversion function (should probably turn it into objects) +function getType() { + if (settings.conversionFunction === ConversionFunctions.horizontal565) { + return 'uint16_t'; + } if (settings.conversionFunction === ConversionFunctions.horizontal888) { + return 'unsigned long'; + } + return 'unsigned char'; +} + +// Use the horizontally oriented list to draw the image +function listToImageHorizontal(list, canvas) { + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const imgData = ctx.createImageData(canvas.width, canvas.height); + + let index = 0; + + // round the width up to the next byte + const widthRoundedUp = Math.floor(canvas.width / 8 + (canvas.width % 8 ? 1 : 0)) * 8; + let widthCounter = 0; + + // Move the list into the imageData object + for (let i = 0; i < list.length; i++) { + let binString = hexToBinary(list[i]); + if (!binString.valid) { + // eslint-disable-next-line no-alert + alert('Something went wrong converting the string. Did you forget to remove any comments from the input?'); + console.log('invalid hexToBinary: ', binString.s); + return; + } + binString = binString.result; + if (binString.length === 4) { + binString += '0000'; + } + + // Check if pixel is white or black + for (let k = 0; k < binString.length; k++, widthCounter++) { + // if we've counted enough bits, reset counter for next line + if (widthCounter >= widthRoundedUp) { + widthCounter = 0; + } + // skip 'artifact' pixels due to rounding up to a byte + if (widthCounter >= canvas.width) { + // eslint-disable-next-line no-continue + continue; + } + let color = 0; + if (binString.charAt(k) === '1') { + color = 255; + } + imgData.data[index] = color; + imgData.data[index + 1] = color; + imgData.data[index + 2] = color; + imgData.data[index + 3] = 255; + + index += 4; + } } + // Draw the image onto the canvas, then save the canvas contents + // inside the img object. This way we can reuse the img object when + // we want to scale / invert, etc. + ctx.putImageData(imgData, 0, 0); + const img = new Image(); + img.src = canvas.toDataURL('image/png'); + images.first().img = img; +} - // An images collection with helper methods - function Images() { - var collection = []; - this.push = function(img, canvas, glyph) { - collection.push({ "img" : img, "canvas" : canvas, "glyph" : glyph }); - }; - this.remove = function(image) { - var i = collection.indexOf(image); - if(i != -1) collection.splice(i, 1); - }; - this.each = function(f) { collection.forEach(f); }; - this.length = function() { return collection.length; }; - this.first = function() { return collection[0]; }; - this.last = function() { return collection[collection.length - 1]; }; - this.getByIndex = function(index) { return collection[index]; }; - this.setByIndex = function(index, img) { collection[index] = img; }; - this.get = function(img) { - if(img) { - for(var i = 0; i < collection.length; i++) { - if(collection[i].img == img) { - return collection[i]; - } - } - } - return collection; - }; - return this; +// Quick and effective way to draw single pixels onto the canvas +// using a global 1x1px large canvas +function drawPixel(ctx, x, y, color) { + const singlePixel = ctx.createImageData(1, 1); + const d = singlePixel.data; + + d[0] = color; + d[1] = color; + d[2] = color; + d[3] = 255; + ctx.putImageData(singlePixel, x, y); +} + +// Use the vertically oriented list to draw the image +function listToImageVertical(list, canvas) { + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + let page = 0; + let x = 0; + let y = 7; + + // Move the list into the imageData object + for (let i = 0; i < list.length; i++) { + let binString = hexToBinary(list[i]); + if (!binString.valid) { + // eslint-disable-next-line no-alert + alert('Something went wrong converting the string. Did you forget to remove any comments from the input?'); + console.log('invalid hexToBinary: ', binString.s); + return; + } + binString = binString.result; + if (binString.length === 4) { + binString += '0000'; } - function update() { - images.each(function(image) { place_image(image); }); - } - - // Easy way to update settings controlled by a textfield - function updateInteger(fieldName){ - settings[fieldName] = parseInt(document.getElementById(fieldName).value); - update(); - } - - // Easy way to update settings controlled by a checkbox - function updateBoolean(fieldName){ - settings[fieldName] = document.getElementById(fieldName).checked; - update(); - } - - // Easy way to update settings controlled by a radiobutton - function updateRadio(fieldName){ - var radioGroup = document.getElementsByName(fieldName); - for (var i = 0; i < radioGroup.length; i++) { - if (radioGroup[i].checked) { - settings[fieldName] = radioGroup[i].value; + // Check if pixel is white or black + for (let k = 0; k < binString.length; k++) { + let color = 0; + if (binString.charAt(k) === '1') { + color = 255; + } + drawPixel(ctx, x, (page * 8) + y, color); + y--; + if (y < 0) { + y = 7; + x++; + if (x >= settings.screenWidth) { + x = 0; + page++; } } - update(); + } + } + // Save the canvas contents inside the img object. This way we can + // reuse the img object when we want to scale / invert, etc. + const img = new Image(); + img.src = canvas.toDataURL('image/png'); + images.first().img = img; +} + +// Handle inserting an image by pasting code +// eslint-disable-next-line no-unused-vars +function handleTextInput(drawMode) { + const canvasContainer = document.getElementById('images-canvas-container'); + const canvas = document.createElement('canvas'); + + canvas.width = parseInt(document.getElementById('text-input-width').value); + canvas.height = parseInt(document.getElementById('text-input-height').value); + settings.screenWidth = canvas.width; + settings.screenHeight = canvas.height; + + if (canvasContainer.children.length) { + canvasContainer.removeChild(canvasContainer.firstChild); + } + canvasContainer.appendChild(canvas); + + const image = new Image(); + images.setByIndex(0, { img: image, canvas }); + + let input = document.getElementById('byte-input').value; + + // Remove Arduino code + input = input.replace(/const\s+(unsigned\s+char|uint8_t)\s+[a-zA-Z0-9]+\s*\[\]\s*(PROGMEM\s*)?=\s*/g, ''); + input = input.replace(/\};|\{/g, ''); + + // Convert newlines to comma (helps to remove comments later) + input = input.replace(/\r\n|\r|\n/g, ','); + // Convert multiple commas in a row into a single one + input = input.replace(/,{2,}/g, ','); + // Remove whitespace + input = input.replace(/\s/g, ''); + // Remove comments + input = input.replace(/\/\/(.+?),/g, ''); + // Remove "0x" + input = input.replace(/0[xX]/g, ''); + // Split into list + const list = input.split(','); + + if (drawMode === 'horizontal') { + listToImageHorizontal(list, canvas); + } else { + listToImageVertical(list, canvas); + } +} + +function allSameSize(_images, files) { + const allSameSizeButton = document.getElementById('all-same-size'); + if (_images.length() > 1 && _images.length() === files.length) { + // multiple images settings container + // var imageSizeSettings = document.getElementById("image-size-settings"); + const inputs = document.querySelectorAll('#image-size-settings input'); + // all images same size button + allSameSizeButton.onclick = () => { + for (let i = 2; i < inputs.length; i++) { + if (inputs[i].name === 'width') { + inputs[i].value = inputs[0].value; + inputs[i].oninput(); + } + if (inputs[i].name === 'height') { + inputs[i].value = inputs[1].value; + inputs[i].oninput(); + } + } + }; + allSameSizeButton.style.display = 'block'; + } +} + +// Handle selecting an image with the file picker +function handleImageSelection(evt) { + const files = Array.from(evt.target.files); + files.sort((a, b) => a.name > b.name); + // error message + const onlyImagesFileError = document.getElementById('only-images-file-error'); + + onlyImagesFileError.style.display = 'none'; + + // initial message + const noFileSelected = document.getElementById('no-file-selected'); + if (files.length > 0) { + noFileSelected.style.display = 'none'; + } else { + noFileSelected.style.display = 'block'; + } + + for (let i = 0; files[i]; i++) { + // Only process image files. + if (!files[i].type.match('image.*')) { + onlyImagesFileError.style.display = 'block'; + // eslint-disable-next-line no-continue + continue; } - var images = new Images(); + const reader = new FileReader(); - window.onload = function () { + // eslint-disable-next-line no-loop-func + reader.onload = (function (file) { + return (e) => { + // Render thumbnail. + const img = new Image(); - document.getElementById("copy-button").disabled = true; - var __output; + img.onload = () => { + const fileInputColumnEntry = document.createElement('div'); + fileInputColumnEntry.className = 'file-input-entry'; - // Add events to the file input button - var fileInput = document.getElementById("file-input"); - fileInput.addEventListener("click", function(){this.value = null;}, false); - fileInput.addEventListener("change", handleImageSelection, false); + const fileInputColumnEntryLabel = document.createElement('span'); + fileInputColumnEntryLabel.textContent = file.name; - // Filetypes accepted by the file picker - var fileTypes = ["jpg", "jpeg", "png", "bmp", "gif", "svg"]; + const fileInputColumnEntryRemoveButton = document.createElement('button'); + fileInputColumnEntryRemoveButton.className = 'remove-button'; + fileInputColumnEntryRemoveButton.innerHTML = 'remove'; - // all images same size button - var allSameSizeButton = document.getElementById("all-same-size"); + const canvas = document.createElement('canvas'); + + const imageEntry = document.createElement('li'); + imageEntry.setAttribute('data-img', file.name); + + const w = document.createElement('input'); + w.type = 'number'; + w.name = 'width'; + w.id = 'screenWidth'; + w.min = 0; + w.className = 'size-input'; + w.value = img.width; + settings.screenWidth = img.width; + w.oninput = () => { + canvas.width = this.value; + update(); + updateInteger('screenWidth'); + }; + + const h = document.createElement('input'); + h.type = 'number'; + h.name = 'height'; + h.id = 'screenHeight'; + h.min = 0; + h.className = 'size-input'; + h.value = img.height; + settings.screenHeight = img.height; + h.oninput = () => { + canvas.height = this.value; + update(); + updateInteger('screenHeight'); + }; + + const gil = document.createElement('span'); + gil.innerHTML = 'glyph'; + gil.className = 'file-info'; + + const gi = document.createElement('input'); + gi.type = 'text'; + gi.name = 'glyph'; + gi.className = 'glyph-input'; + gi.onchange = () => { + const image = images.get(img); + image.glyph = gi.value; + }; + + const fn = document.createElement('span'); + fn.className = 'file-info'; + fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; + fn.innerHTML += '
'; + + const rb = document.createElement('button'); + rb.className = 'remove-button'; + rb.innerHTML = 'remove'; + + const fileInputColumn = document.getElementById('file-input-column'); + const imageSizeSettings = document.getElementById('image-size-settings'); + const canvasContainer = document.getElementById('images-canvas-container'); + const allSameSizeButton = document.getElementById('all-same-size'); + + const removeButtonOnClick = () => { + const image = images.get(img); + canvasContainer.removeChild(image.canvas); + images.remove(image); + imageSizeSettings.removeChild(imageEntry); + + fileInputColumn.removeChild(fileInputColumnEntry); + if (imageSizeSettings.children.length === 1) { + allSameSizeButton.style.display = 'none'; + } + if (images.length() === 0) noFileSelected.style.display = 'block'; + update(); + }; + + rb.onclick = removeButtonOnClick; + fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; + + fileInputColumnEntry.appendChild(fileInputColumnEntryLabel); + fileInputColumnEntry.appendChild(fileInputColumnEntryRemoveButton); + fileInputColumn.appendChild(fileInputColumnEntry); + + imageEntry.appendChild(fn); + imageEntry.appendChild(w); + imageEntry.appendChild(document.createTextNode(' x ')); + imageEntry.appendChild(h); + imageEntry.appendChild(gil); + imageEntry.appendChild(gi); + imageEntry.appendChild(rb); + + imageSizeSettings.appendChild(imageEntry); + + canvas.width = img.width; + canvas.height = img.height; + canvasContainer.appendChild(canvas); + + images.push(img, canvas, file.name.split('.')[0]); + placeImage(images.last()); + allSameSize(images, files); + }; + img.src = e.target.result; + }; + }(files[i])); + reader.readAsDataURL(files[i]); + } +} + +function imageToString(image) { + // extract raw image data + const { ctx } = image; + const { canvas } = image; + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + return settings.conversionFunction(data, canvas.width, canvas.height); +} + +// Get the custom arduino output variable name, if any +function getIdentifier() { + const vn = document.getElementById('identifier'); + return vn && vn.value.length ? vn.value : identifier; +} + +// Output the image string to the textfield +// eslint-disable-next-line no-unused-vars +function generateOutputString() { + outputString = ''; + let code = ''; + + switch (settings.outputFormat) { + case 'arduino': { + const varQuickArray = []; + let bytesUsed = 0; + // -- + images.each((image) => { + code = imageToString(image); + + // Trim whitespace from end and remove trailing comma + code = code.replace(/,\s*$/, ''); + + code = `\t${code.split('\n').join('\n\t')}\n`; + // const variableCount = images.length() > 1 ? count++ : ''; + const comment = `// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; + bytesUsed += code.split('\n').length * 16; // 16 bytes per line. + + const varname = getIdentifier() + image.glyph.replace(/[^a-zA-Z0-9]/g, '_'); + varQuickArray.push(varname); + code = `${comment}const ${getType()} ${varname} [] PROGMEM = {\n${code}};\n`; + outputString += code; + }); + // -- + varQuickArray.sort(); + // -- + outputString += `\n// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = ${bytesUsed})\n`; + outputString += `const int ${getIdentifier()}allArray_LEN = ${varQuickArray.length};\n`; + outputString += `const ${getType()}* ${getIdentifier()}allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; + // -- + break; + } + + case 'arduino_single': { + let comment = ''; + images.each((image) => { + code = imageToString(image); + code = `\t${code.split('\n').join('\n\t')}\n`; + comment = `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n`; + outputString += comment + code; + }); + + outputString = outputString.replace(/,\s*$/, ''); + + outputString = `const ${getType()} ${ + +getIdentifier() + } [] PROGMEM = {` + + `\n${outputString}\n};`; + break; + } + + case 'adafruit_gfx': { // bitmap + let comment = ''; + let useGlyphs = 0; + images.each((image) => { + code = imageToString(image); + code = `\t${code.split('\n').join('\n\t')}\n`; + comment = `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n`; + outputString += comment + code; + if (image.glyph.length === 1) { + useGlyphs++; + } + }); + + outputString = outputString.replace(/,\s*$/, ''); + outputString = `const unsigned char ${ + getIdentifier() + }Bitmap` + + ' [] PROGMEM = {' + + `\n${outputString}\n};\n\n` + + `const GFXbitmapGlyph ${ + getIdentifier() + }Glyphs [] PROGMEM = {\n`; + + let firstAschiiChar = document.getElementById('first-ascii-char').value; + const xAdvance = parseInt(document.getElementById('x-advance').value); + let offset = 0; + code = ''; + + // GFXbitmapGlyph + images.each((image) => { + code += `\t{ ${ + offset}, ${ + image.canvas.width}, ${ + image.canvas.height}, ${ + xAdvance}, ` + + `'${images.length() === useGlyphs + ? image.glyph + : String.fromCharCode(firstAschiiChar++)}'` + + ' }'; + if (image !== images.last()) { + code += ','; + } + code += `// '${image.glyph}'\n`; + offset += image.canvas.width; + }); + code += '};\n'; + outputString += code; + + // GFXbitmapFont + outputString += `\nconst GFXbitmapFont ${ + getIdentifier() + }Font PROGMEM = {\n` + + `\t(uint8_t *)${ + getIdentifier()}Bitmap,\n` + + `\t(GFXbitmapGlyph *)${ + getIdentifier() + }Glyphs,\n` + + `\t${images.length() + }\n};\n`; + break; + } + default: { // plain + images.each((image) => { + code = imageToString(image); + let comment = ''; + if (image.glyph) { + comment = (`// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`); + } + if (image.img !== images.first().img) { + comment = `\n${comment}`; + } + code = comment + code; + outputString += code; + }); + // Trim whitespace from end and remove trailing comma + outputString = outputString.replace(/,\s*$/g, ''); + } + } + + document.getElementById('code-output').value = outputString; + output = outputString; + document.getElementById('copy-button').disabled = false; +} + +// eslint-disable-next-line no-unused-vars +function copyOutput() { + navigator.clipboard.writeText(output); +} + +// eslint-disable-next-line no-unused-vars +function downloadFile() { + let raw = []; + images.each((image) => { + const data = imageToString(image) + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + .map((byte) => parseInt(byte, 16)); + raw = raw.concat(data); + }); + const data = new Uint8Array(raw); + const a = document.createElement('a'); + a.style = 'display: none'; + document.body.appendChild(a); + const blob = new Blob([data], { type: 'octet/stream' }); + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = `${getIdentifier()}.bin`; + a.click(); + window.URL.revokeObjectURL(url); +} + +// eslint-disable-next-line no-unused-vars +function updateDrawMode(elm) { + // var note = document.getElementById("note1bit"); + // if(elm.value == "horizontal1bit" || elm.value == "vertical1bit") { + // note.style.display = "block"; + // } else { + // note.style.display = "none"; + // } + + const conversionFunction = ConversionFunctions[elm.value]; + if (conversionFunction) { + settings.conversionFunction = conversionFunction; + } +} + +// Updates Arduino code check-box +// eslint-disable-next-line no-unused-vars +function updateOutputFormat(elm) { + let caption = document.getElementById('format-caption-container'); + const adafruitGfx = document.getElementById('adafruit-gfx-settings'); + const arduino = document.getElementById('arduino-identifier'); + const removeZeroesCommasContainer = document.getElementById('remove-zeroes-commas-container'); + + for (let i = 0; i < caption.children.length; i++) { + caption.children[i].style.display = 'none'; + } + caption = document.querySelector(`div[data-caption='${elm.value}']`); + if (caption) caption.style.display = 'block'; + + if (elm.value !== 'plain') { + arduino.style.display = 'block'; + removeZeroesCommasContainer.style.display = 'none'; + settings.removeZeroesCommas = false; + } else { + arduino.style.display = 'none'; + removeZeroesCommasContainer.style.display = 'table-row'; + } + if (elm.value === 'adafruit_gfx') { + adafruitGfx.style.display = 'block'; + } else { + adafruitGfx.style.display = 'none'; + } + + settings.outputFormat = elm.value; +} + +// Easy way to update settings controlled by a radiobutton +// eslint-disable-next-line no-unused-vars +function updateRadio(fieldName) { + const radioGroup = document.getElementsByName(fieldName); + for (let i = 0; i < radioGroup.length; i++) { + if (radioGroup[i].checked) { + settings[fieldName] = radioGroup[i].value; + } + } + update(); +} + +window.onload = () => { + document.getElementById('copy-button').disabled = true; + + // Add events to the file input button + const fileInput = document.getElementById('file-input'); + fileInput.addEventListener('click', () => { this.value = null; }, false); + fileInput.addEventListener('change', handleImageSelection, false); // The variable to hold our images. Global so we can easily reuse it when the // user updates the settings (change canvas size, scale, invert, etc) - - // Variable name, when "arduino code" is required - var identifier = "myBitmap"; - - - // // Make the canvas black and white // function blackAndWhite(canvas, ctx){ // var imageData = ctx.getImageData(0,0,canvas.width, canvas.height); @@ -967,95 +1142,6 @@ // ctx.putImageData(imageData, 0, 0); // } - // Use the vertically oriented list to draw the image - function listToImageVertical(list, canvas){ - - var ctx = canvas.getContext("2d"); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - var index = 0; - - var page = 0; - var x = 0; - var y = 7; - - // Move the list into the imageData object - for (var i=0;i= settings["screenWidth"]){ - x = 0; - page++; - } - } - - } - } - // Save the canvas contents inside the img object. This way we can - // reuse the img object when we want to scale / invert, etc. - var img = new Image(); - img.src = canvas.toDataURL("image/png"); - images.first().img = img; - } - - // Convert hex to binary - function hexToBinary(s) { - - var i, k, part, ret = ""; - // lookup table for easier conversion. "0" characters are - // padded for "1" to "7" - var lookupTable = { - "0": "0000", "1": "0001", "2": "0010", "3": "0011", "4": "0100", - "5": "0101", "6": "0110", "7": "0111", "8": "1000", "9": "1001", - "a": "1010", "b": "1011", "c": "1100", "d": "1101", "e": "1110", - "f": "1111", "A": "1010", "B": "1011", "C": "1100", "D": "1101", - "E": "1110", "F": "1111" - }; - for (i = 0; i < s.length; i += 1) { - if (lookupTable.hasOwnProperty(s[i])) { - ret += lookupTable[s[i]]; - } else { - return { valid: false, s: s }; - } - } - return { valid: true, result: ret }; - } - - // Quick and effective way to draw single pixels onto the canvas - // using a global 1x1px large canvas - function drawPixel(ctx, x, y, color) { - var single_pixel = ctx.createImageData(1,1); - var d = single_pixel.data; - - d[0] = color; - d[1] = color; - d[2] = color; - d[3] = 255; - ctx.putImageData(single_pixel, x, y); - } - - document.getElementById("outputFormat").value = "arduino"; - document.getElementById("outputFormat").onchange(); - + document.getElementById('outputFormat').value = 'arduino'; + document.getElementById('outputFormat').onchange(); };