commit bb3ae84444a2f98f64331dc4fc0e70c16de3b39a Author: yuki Date: Sun Jul 27 16:31:22 2025 -0400 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..594cc74 --- /dev/null +++ b/LICENCE @@ -0,0 +1,70 @@ +Licence Libre du Québec – Permissive (LiLiQ-P) + +Version 1.1 + +1. Préambule +Cette licence s'applique à tout logiciel distribué dont le titulaire du droit d'auteur précise qu'il est sujet aux termes de la Licence Libre du Québec – Permissive (LiLiQ-P) (ci-après appelée la « licence »). + +2. Définitions +Dans la présente licence, à moins que le contexte n'indique un sens différent, on entend par: + + « concédant » : le titulaire du droit d'auteur sur le logiciel, ou toute personne dûment autorisée par ce dernier à accorder la présente licence; + « contributeur » : le titulaire du droit d'auteur ou toute personne autorisée par ce dernier à soumettre au concédant une contribution. Un contributeur dont sa contribution est incorporée au logiciel est considéré comme un concédant en regard de sa contribution; + « contribution » : tout logiciel original, ou partie de logiciel original soumis et destiné à être incorporé dans le logiciel; + « distribution » : le fait de délivrer une copie du logiciel; + « licencié » : toute personne qui possède une copie du logiciel et qui exerce les droits concédés par la licence; + « logiciel » : une œuvre protégée par le droit d'auteur, telle qu'un programme d'ordinateur et sa documentation, pour laquelle le titulaire du droit d'auteur a précisé qu'elle est sujette aux termes de la présente licence; + « logiciel dérivé » : tout logiciel original réalisé par un licencié, autre que le logiciel ou un logiciel modifié, qui produit ou reproduit la totalité ou une partie importante du logiciel; + « logiciel modifié » : toute modification par un licencié de l'un des fichiers source du logiciel ou encore tout nouveau fichier source qui incorpore le logiciel ou une partie importante de ce dernier. + +3. Licence de droit d'auteur +Sous réserve des termes de la licence, le concédant accorde au licencié une licence non exclusive et libre de redevances lui permettant d’exercer les droits suivants sur le logiciel : + + 1 Produire ou reproduire la totalité ou une partie importante; + 2 Exécuter ou représenter la totalité ou une partie importante en public; + 3 Publier la totalité ou une partie importante; + 4 Sous-licencier sous une autre licence libre, approuvée ou certifiée par la Free Software Foundation ou l'Open Source Initiative. + +Cette licence est accordée sans limite territoriale et sans limite de temps. + +L'exercice complet de ces droits est sujet à la distribution par le concédant du code source du logiciel, lequel doit être sous une forme permettant d'y apporter des modifications. Le concédant peut aussi distribuer le logiciel accompagné d'une offre de distribuer le code source du logiciel, sans frais supplémentaires, autres que ceux raisonnables afin de permettre la livraison du code source. Cette offre doit être valide pendant une durée raisonnable. + +4. Distribution +Le licencié peut distribuer des copies du logiciel, d'un logiciel modifié ou dérivé, sous réserve de respecter les conditions suivantes : + + 1 Le logiciel doit être accompagné d'un exemplaire de cette licence; + 2 Si le logiciel a été modifié, le licencié doit en faire la mention, de préférence dans chacun des fichiers modifiés dont la nature permet une telle mention; + 3 Les étiquettes ou mentions faisant état des droits d'auteur, des marques de commerce, des garanties ou de la paternité concernant le logiciel ne doivent pas être modifiées ou supprimées, à moins que ces étiquettes ou mentions ne soient inapplicables à un logiciel modifié ou dérivé donné. + +5. Contributions +Sous réserve d'une entente distincte, toute contribution soumise par un contributeur au concédant pour inclusion dans le logiciel sera soumise aux termes de cette licence. + +6. Marques de commerce +La licence n'accorde aucune permission particulière qui permettrait d'utiliser les marques de commerce du concédant, autre que celle requise permettant d'identifier la provenance du logiciel. + +7. Garanties +Sauf mention contraire, le concédant distribue le logiciel sans aucune garantie, aux risques et périls de l'acquéreur de la copie du logiciel, et ce, sans assurer que le logiciel puisse répondre à un besoin particulier ou puisse donner un résultat quelconque. + +Sans lier le concédant d'une quelconque manière, rien n'empêche un licencié d'offrir ou d'exclure des garanties ou du support. + +8. Responsabilité +Le licencié est responsable de tout préjudice résultant de l'exercice des droits accordés par la licence. + +Le concédant ne saurait être tenu responsable de dommages subis par le licencié ou par des tiers, pour quelque cause que ce soit en lien avec la licence et les droits qui y sont accordés. + +9. Résiliation +La présente licence est automatiquement résiliée dès que les droits qui y sont accordés ne sont pas exercés conformément aux termes qui y sont stipulés. + +Toutefois, si le défaut est corrigé dans un délai de 30 jours de sa prise de connaissance par la personne en défaut, et qu'il s'agit du premier défaut, la licence est accordée de nouveau. + +Pour tout défaut subséquent, le consentement exprès du concédant est nécessaire afin que la licence soit accordée de nouveau. + +10. Version de la licence +Le Centre de services partagés du Québec, ses ayants cause ou toute personne qu'il désigne, peuvent diffuser des versions révisées ou modifiées de cette licence. Chaque version recevra un numéro unique. Si un logiciel est déjà soumis aux termes d'une version spécifique, c'est seulement cette version qui liera les parties à la licence. + +Le concédant peut aussi choisir de concéder la licence sous la version actuelle ou toute version ultérieure, auquel cas le licencié peut choisir sous quelle version la licence lui est accordée. + +11. Divers +Dans la mesure où le concédant est un ministère, un organisme public ou une personne morale de droit public, créés en vertu d'une loi de l'Assemblée nationale du Québec, la licence est régie par le droit applicable au Québec et en cas de contestation, les tribunaux du Québec seront seuls compétents. + +La présente licence peut être distribuée sans conditions particulières. Toutefois, une version modifiée doit être distribuée sous un nom différent. Toute référence au Centre de services partagés du Québec, et, le cas échéant, ses ayant cause, doit être retirée, autre que celle permettant d'identifier la provenance de la licence. diff --git a/README.md b/README.md new file mode 100644 index 0000000..009a4a2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# node-telidon + +This is a framework for creating servers using the Telidon over IP protocol. Supports a lot of the NAPLPS drawing commands. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..aa96f65 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1161 @@ +{ + "name": "telidon", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "telidon", + "version": "0.1.0", + "license": "LiLiQ-P-1.1", + "dependencies": { + "color-rgba": "^3.0.0", + "iconv": "^3.0.1", + "jsdom": "^26.1.0", + "sharp": "^0.34.3" + }, + "devDependencies": { + "@types/color-rgba": "^2.1.3", + "@types/jsdom": "^21.1.7", + "@types/node": "^24.0.15" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", + "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", + "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", + "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", + "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", + "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", + "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", + "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", + "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", + "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", + "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", + "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", + "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", + "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", + "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", + "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", + "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", + "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", + "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.4" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", + "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", + "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", + "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@types/color-rgba": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/color-rgba/-/color-rgba-2.1.3.tgz", + "integrity": "sha512-JOqpRixFF2D9Uy9osxJxzUP3lmdQdp7rpj4eMz0mYcZH2yhHwyyY4nMdYumCYWJT0ygLC/kMlkQW5LPZB2cTQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.0.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz", + "integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-convert/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==", + "license": "MIT" + }, + "node_modules/color-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.0.tgz", + "integrity": "sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-parse": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-2.0.2.tgz", + "integrity": "sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + } + }, + "node_modules/color-rgba": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-3.0.0.tgz", + "integrity": "sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==", + "license": "MIT", + "dependencies": { + "color-parse": "^2.0.0", + "color-space": "^2.0.0" + } + }, + "node_modules/color-space": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/color-space/-/color-space-2.3.2.tgz", + "integrity": "sha512-BcKnbOEsOarCwyoLstcoEztwT0IJxqqQkNwDuA3a65sICvvHL2yoeV13psoDFh5IuiOMnIOKdQDwB4Mk3BypiA==", + "license": "Unlicense" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-string/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==", + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/iconv/-/iconv-3.0.1.tgz", + "integrity": "sha512-lJnFLxVc0d82R7GfU7a9RujKVUQ3Eee19tPKWZWBJtAEGRHVEyFzCtbNl3GPKuDnHBBRT4/nDS4Ru9AIDT72qA==", + "hasInstallScript": true, + "license": "ISC", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", + "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.3", + "@img/sharp-darwin-x64": "0.34.3", + "@img/sharp-libvips-darwin-arm64": "1.2.0", + "@img/sharp-libvips-darwin-x64": "1.2.0", + "@img/sharp-libvips-linux-arm": "1.2.0", + "@img/sharp-libvips-linux-arm64": "1.2.0", + "@img/sharp-libvips-linux-ppc64": "1.2.0", + "@img/sharp-libvips-linux-s390x": "1.2.0", + "@img/sharp-libvips-linux-x64": "1.2.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", + "@img/sharp-libvips-linuxmusl-x64": "1.2.0", + "@img/sharp-linux-arm": "0.34.3", + "@img/sharp-linux-arm64": "0.34.3", + "@img/sharp-linux-ppc64": "0.34.3", + "@img/sharp-linux-s390x": "0.34.3", + "@img/sharp-linux-x64": "0.34.3", + "@img/sharp-linuxmusl-arm64": "0.34.3", + "@img/sharp-linuxmusl-x64": "0.34.3", + "@img/sharp-wasm32": "0.34.3", + "@img/sharp-win32-arm64": "0.34.3", + "@img/sharp-win32-ia32": "0.34.3", + "@img/sharp-win32-x64": "0.34.3" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..055a3e1 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "telidon", + "version": "0.1.0", + "description": "A framework to build interactive Telidon servers", + "keywords": [ + "telidon", + "naplps" + ], + "repository": { + "type": "git", + "url": "https://a39.dev/VideowayRevival/node-telidon" + }, + "license": "LiLiQ-P-1.1", + "author": "JP Savard ", + "type": "module", + "main": "telidon.ts", + "scripts": { + "install": "npm explore iconv -- node-gyp configure --node_iconv_use_system_libiconv=1 build" + }, + "dependencies": { + "color-rgba": "^3.0.0", + "iconv": "^3.0.1", + "jsdom": "^26.1.0", + "sharp": "^0.34.3" + }, + "devDependencies": { + "@types/color-rgba": "^2.1.3", + "@types/jsdom": "^21.1.7", + "@types/node": "^24.0.15" + } +} diff --git a/telidon.ts b/telidon.ts new file mode 100644 index 0000000..d9fbe89 --- /dev/null +++ b/telidon.ts @@ -0,0 +1,1032 @@ +import net from "net"; +import EventEmitter from 'node:events'; +import Domain from "node:domain"; +import { JSDOM } from "jsdom"; +import rgba from "color-rgba"; +import sharp from "sharp"; +import { Iconv } from "iconv"; + +const iconv = new Iconv("UTF-8", "NAPLPS//TRANSLIT//IGNORE"); + +class ResetOptions { + /** + * If true, domain parameters are reset. + */ + domain?: boolean; + /** + * 0: nothing + * 1: set color mode 0, reset to default palette and set drawing color to white + * 2: set color mode and reset to default palette. If current color mode is 0 then treat same as 3 + * 3: set color mode 1, reset to default palette and set drawing color to white + */ + color?: number; + /** + * 0: nothing + * 1: clear screen to nominal black + * 2: clear screen to current drawing color + * 3: set border to nominal black + * 4: set border to current drawing color + * 5: clear screen/border to drawing color + * 6: clear screen to drawing color and set border to nominal black + * 7: clear screen/border to nominal black + */ + screen?: number; + /** + * If true, the cursor is sent home to the top left of thedisplay area and all text parameters from the text command, the C1 set and the active field are reset to their defaults. + */ + text?: boolean; + /** + * If true, all blink processes are terminated. + */ + blink?: boolean; + fields?: boolean; + texture?: boolean; + macro?: boolean; + drcs?: boolean; +} + +interface Value { + encode(width: number): Uint8Array; +} + +export class SingleValue implements Value { + value: number; + constructor(value: number) { + this.value = value; + } + encode(width: number) { + var data = new Uint8Array(Array(width).fill(64)); + for(var i = 0; i < width; i++) + { + data[i] |= (this.value >> ((width*6-6)-i*6)) & 63 + } + return data; + } +} + +export class Point implements Value { + x: number; + y: number; + z?: number; + constructor(x: number, y: number, z: number | undefined = undefined) { + this.x = x; + this.y = y; + this.z = z; + } + encode(width: number, force3d: boolean = false) { + var threeD = this.z !== undefined || force3d; + var thisz = this.z || 0; + var data = new Uint8Array(Array(width).fill(64)); + var bits = width*(threeD?2:3)-1; + + var xx = this.x < 0 ? -this.x-1 : this.x; + var yy = this.y < 0 ? -this.y-1 : this.y; + var zz = thisz < 0 ? -thisz-1 : thisz; + + var x = Math.abs(xx*(2**bits-1)); + var y = Math.abs(yy*(2**bits-1)); + var z = Math.abs(zz*(2**bits-1)); + + if(threeD) + { + + data[0] |= this.x < 0 ? 32 : 0; + data[0] |= this.y < 0 ? 8 : 0; + data[0] |= thisz < 0 ? 2 : 0; + + for(var i = 0; i < width; i++) + { + data[i] |= x >> ((width*2-2)-i*2) << 4 & 48; + data[i] |= y >> ((width*2-2)-i*2) << 2 & 12; + data[i] |= z >> ((width*2-2)-i*2) & 3; + } + } + else + { + data[0] |= this.x < 0 ? 32 : 0; + data[0] |= this.y < 0 ? 4 : 0; + + for(var i = 0; i < width; i++) + { + data[i] |= x >> ((width*3-3)-i*3) << 3 & 56; + data[i] |= y >> ((width*3-3)-i*3) & 7; + } + } + + return data; + } +} + +export class Color implements Value { + r: number; + g: number; + b: number; + constructor(r: number, g: number, b: number) { + this.r = r; + this.g = g; + this.b = b; + } + encode(width: number) { + var data = new Uint8Array(Array(width).fill(64)); + var bits = width*2; + var r = Math.abs(this.r*(2**bits-1)); + var g = Math.abs(this.g*(2**bits-1)); + var b = Math.abs(this.b*(2**bits-1)); + + for(var i = 0; i < width; i++) + { + // x1GRBGRB + data[i] |= [0,1,8,9][b >> ((width*2-2)-i*2) & 3]; + data[i] |= [0,2,16,18][r >> ((width*2-2)-i*2) & 3]; + data[i] |= [0,4,32,36][g >> ((width*2-2)-i*2) & 3]; + } + + return data; + } +} + +class Options { + /** + * Dumps a bunch of useless info in the console. + */ + debug!: boolean; + + pdiGr: boolean; + + /** + * Enables use of the third dimension. + */ + thirdDimension!: boolean; + /** + * Length of a multi-value operand in bytes (1-8) + */ + multiLength!: number; // [1,8] + /** + * Length of a single-value operand in bytes (1-4) + */ + singleLength!: number; // [1,4] + /** + * Length of a color in the incremental point command in bytes (1-8) + */ + incrementalLength: number; // [1-8] + /** + * Sets width and height of the logical pel + */ + pelSize!: Point; + + /** + * How far to move the cursor after displaying a character or a space + * 0: x1 (no gap) + * 1: x1.25 (quarter-character gap) + * 2: x1.5 (half-character gap) + * 3: proportional spacing + */ + textSpacing!: number; // [0,3] + /** + * After a character is displayed, the cursor moves in this direction + * 0: right + * 1: left + * 2: up + * 3: down + */ + textDirection!: number; // [0,3] + /** + * Characters are rotated + * 0: 0 degrees + * 1: 90 degrees + * 2: 180 degrees + * 3: 270 degrees + */ + textRotation!: number; // [0,3] + /** + * Cursor style + * 0: underscore + * 1: block + * 2: crosshair + * 3: custom + */ + cursorStyle!: number; // [0,3] + /** + * How the graphical drawing point is related to the text cursor + * 0: move together + * 1: cursor leads + * 2: drawing point leads + * 3: move independently + */ + drawingPoint!: number; // [0,3] + /** + * Row spacing + * 0: 1 + * 1: 1.25 + * 2: 1.5 + * 3: 2 + */ + rowSpacing!: number; // [0,3] + /** + * Sets the size of a character + */ + characterSize!: Point; + + /** + * Fill pattern + * 0: solid + * 1: vertical hatching + * 2: horizontal hatching + * 3: vertical and horizontal cross-hatching + * 4: programmable mask A + * 5: programmable mask B + * 6: programmable mask C + * 7: programmable mask D + */ + fillPattern!: number; // [0,7] + /** + * If true, draw outlines of filled objects using current pel size + */ + drawOutline!: boolean; + /** + * Outline style + * 0: solid + * 1: dotted + * 2: dashed + * 3: dot-dash + */ + lineTexture!: number; // [0,3] + /** + * Mask size used in pattern fills for the programmable masks + */ + maskSize!: Point; +} + +const DefaultOptions: Options = { + debug: true, + pdiGr: false, + + thirdDimension: false, + multiLength: 3, + singleLength: 1, + incrementalLength: 3, + pelSize: new Point(0,0), + + textSpacing: 0, + textDirection: 0, + textRotation: 0, + cursorStyle: 0, + drawingPoint: 0, + rowSpacing: 0, + characterSize: new Point(1/40, 5/128), + + fillPattern: 0, + drawOutline: false, + lineTexture: 0, + maskSize: new Point(1/40, 5/128) +} + +class DrawImageOptions +{ + background?: string | object; + resize?: [number, number] | boolean; + mirrory?: boolean; +} + +export class Naplps extends EventEmitter { + socket: net.Socket; + encoder: any; + + options: Options = DefaultOptions; + + constructor(socket: net.Socket, options = {}) { + super(); + this.socket = socket; + this.options = {...this.options, ...options}; + + if(this.options.debug)console.log("> <%s:%d connected>", socket.remoteAddress, socket.remotePort); + + socket.on("end", ()=>{ + if(this.options.debug)console.log("> <%s:%d disconnected>", socket.remoteAddress, socket.remotePort); + this.emit("end"); + }); + socket.on("close", hadError=>this.emit("close", hadError)); + socket.on("error", err=>this.emit("error", err)); + + socket.on("data", data=>{ + if(this.options.debug)console.log("<", data); + for(var byte of data) + this.emit("data", byte); + }); + } + + write(data: string | Uint8Array) { + if(this.options.debug)console.log(">", data); + return this.socket.write(data, "latin1"); + } + + writeBytes(...bytes: number[]) { + return this.write(new Uint8Array(bytes)); + } + + text(data: string) { + if(!this.options.pdiGr) this.textMode(); + this.supplementaryModeGr(); + this.write(iconv.convert(data)); + if(!this.options.pdiGr) this.pdiMode(); + this.pdiModeGr(); + } + + init() { + this.writeBytes(0x1b, 0x31); + this.writeBytes(0x1b, 0x22, 0x46); + } + + uninit() { + return this.writeBytes(0x1b, 0x32); + } + + escape() { + return this.writeBytes(0x1b, 0x25, 0x40); + } + + unescape() { + return this.writeBytes(0x1b, 0x25, 0x41); + } + + /** + * Shifts to text mode + */ + textMode() { + return this.writeBytes(0x0f); + } + + /** + * Shifts to command mode + */ + pdiMode() { + return this.writeBytes(0x0e); + } + + supplementaryMode() { + return this.writeBytes(0x1b, 0x6e); + } + + mosaicsMode() { + return this.writeBytes(0x1b, 0x6f); + } + + pdiModeGr() { + return this.writeBytes(0x1b, 0x7e); + } + + supplementaryModeGr() { + return this.writeBytes(0x1b, 0x7d); + } + + mosaicsModeGr() { + return this.writeBytes(0x1b, 0x7c); + } + + /** + * Non-selective reset: resets most of the settings to their default state + * @param row Reposition cursor to given row + * @param column Reposition cursor to given column + */ + nsr(row: number | undefined = undefined, column: number | undefined = undefined) { + var r = row || 0 & 63 | 64; + var c = column || 0 & 63 | 64; + return this.writeBytes(0x1f, r, c); + } + + /** + * Immediately terminate the processing of currently executing macros + */ + cancel() { + return this.writeBytes(0x18); + } + + /** + * beep + */ + bell() { + return this.writeBytes(0x07); + } + + defineMacro(number: number) { + return this.writeBytes(0x80, number); + } + + defineExecMacro(number: number) { + return this.writeBytes(0x81, number); + } + + defineTransmitMacro(number: number) { + return this.writeBytes(0x82, number); + } + + defineDrcs(number: number) { + return this.writeBytes(0x83, number); + } + + defineTexture(number: number) { + return this.writeBytes(0x84, number-0x40); + } + + defineEnd() { + return this.writeBytes(0x85); + } + + protect(protect: boolean) { + return this.writeBytes(protect?0x90:0x9f); + } + + repeat(times: number) { + this.writeBytes(0x86); + this.write(new SingleValue(times).encode(1)); + } + + repeatToEol(times: number) { + return this.writeBytes(0x87); + } + + reverseVideo(reverse: boolean) { + return this.writeBytes(reverse?0x88:0x89); + } + + smallText() { + return this.writeBytes(0x8a); + } + + mediumText() { + return this.writeBytes(0x8b); + } + + normalText() { + return this.writeBytes(0x8c); + } + + doubleHeightText() { + return this.writeBytes(0x8d); + } + + doubleSizeText() { + return this.writeBytes(0x8f); + } + + wordWrap(wrap: boolean) { + return this.writeBytes(wrap?0x95:0x96); + } + + scroll(scroll: boolean) { + return this.writeBytes(scroll?0x97:0x98); + } + + underline(underline: boolean) { + return this.writeBytes(underline?0x99:0x9a); + } + + flashCursor() { + return this.writeBytes(0x9b); + } + + steadyCursor() { + return this.writeBytes(0x9c); + } + + cursorOff() { + return this.writeBytes(0x9d); + } + + simpleBlink(blink: boolean) { + return this.writeBytes(blink?0x8e:0x9e); + } + + /** + * Selectively reset part of graphics to their default values + * @param options Reset options + */ + reset(options: ResetOptions = {}) { + var data = new Uint8Array([0x20 | (this.options.pdiGr?0x80:0), 64, 64]); + data[1] |= options.domain ? 1 : 0; + data[1] |= options.color||0 << 1; + data[1] |= options.screen||0 << 3; + data[2] |= options.text ? 1 : 0; + data[2] |= options.blink ? 2 : 0; + data[2] |= options.fields ? 4 : 0; + data[2] |= options.texture ? 8 : 0; + data[2] |= options.macro ? 16 : 0; + data[2] |= options.drcs ? 32 : 0; + return this.write(data); + } + + setDomain() { + var data = new Uint8Array([0x21 | (this.options.pdiGr?0x80:0), 64]); + data[1] |= this.options.singleLength-1; + data[1] |= (this.options.multiLength-1) << 2; + data[1] |= this.options.thirdDimension ? 32 : 0; + this.write(data); + this.write(this.options.pelSize.encode(this.options.multiLength)); + } + + setText() { + var data = new Uint8Array([0x22 | (this.options.pdiGr?0x80:0), 64, 64]); + data[1] |= this.options.textSpacing << 4; + data[1] |= this.options.textDirection << 2; + data[1] |= this.options.textRotation; + data[2] |= this.options.cursorStyle << 4; + data[2] |= this.options.drawingPoint << 2; + data[2] |= this.options.rowSpacing; + this.write(data); + this.write(this.options.characterSize.encode(this.options.multiLength)); + } + + setTexture() { + var data = new Uint8Array([0x23 | (this.options.pdiGr?0x80:0), 64]); + data[1] |= this.options.fillPattern << 3; + data[1] |= this.options.drawOutline ? 4 : 0; + data[1] |= this.options.lineTexture; + this.write(data); + this.write(this.options.maskSize.encode(this.options.multiLength)); + } + + pointSetAbs(point: Point) { + this.writeBytes(0x24 | (this.options.pdiGr?0x80:0)); + this.write(point.encode(this.options.multiLength)); + } + pointSetRel(point: Point) { + this.writeBytes(0x25 | (this.options.pdiGr?0x80:0)); + this.write(point.encode(this.options.multiLength)); + } + pointAbs(points: Point[]) { + this.writeBytes(0x26 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + pointRel(points: Point[]) { + this.writeBytes(0x27 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + lineAbs(points: Point[]) { + this.writeBytes(0x28 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + lineRel(points: Point[]) { + this.writeBytes(0x29 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setLineAbs(points: Point[]) { + this.writeBytes(0x2a | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setLineRel(points: Point[]) { + this.writeBytes(0x2b | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + arcOutlined(points: Point[]) { + this.writeBytes(0x2c | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + arcFilled(points: Point[]) { + this.writeBytes(0x2d | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setArcOutlined(points: Point[]) { + this.writeBytes(0x2e | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setArcFilled(points: Point[]) { + this.writeBytes(0x2f | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + rectOutlined(points: Point[]) { + this.writeBytes(0x30 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + rectFilled(points: Point[]) { + this.writeBytes(0x31 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setRectOutlined(points: Point[]) { + this.writeBytes(0x32 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setRectFilled(points: Point[]) { + this.writeBytes(0x33 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + polyOutlined(points: Point[]) { + this.writeBytes(0x34 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + polyFilled(points: Point[]) { + this.writeBytes(0x35 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setPolyOutlined(points: Point[]) { + this.writeBytes(0x36 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setPolyFilled(points: Point[]) { + this.writeBytes(0x37 | (this.options.pdiGr?0x80:0)); + points.forEach(p=>this.write(p.encode(this.options.multiLength))); + } + setField(size: Point | undefined = undefined, origin: Point | undefined = undefined) { + this.writeBytes(0x38 | (this.options.pdiGr?0x80:0)); + if(origin !== undefined) this.write(origin.encode(this.options.multiLength)); + if(size !== undefined) this.write(size.encode(this.options.multiLength)); + } + incrementalPoint(data: Color[]) { + this.writeBytes(0x39 | (this.options.pdiGr?0x80:0)); + this.write(new SingleValue(this.options.incrementalLength*6).encode(1)); + data.forEach(d=>this.write(d.encode(this.options.incrementalLength))); + } + // TODO: 0x3a, 0x3b + setColor(color: Color) { + this.writeBytes(0x3c | (this.options.pdiGr?0x80:0)); + this.write(color.encode(this.options.multiLength)); + } + wait(timeout: number){ + this.writeBytes(0x3d | (this.options.pdiGr?0x80:0), 0x5c); + var secs = Math.floor(timeout*10) & 63 | 64; + // TODO: timeout >= 6.4s + this.writeBytes(secs); + } + selectColor(fg: SingleValue | undefined = undefined, bg: SingleValue | undefined = undefined) { + this.writeBytes(0x3e | (this.options.pdiGr?0x80:0)); + if(fg !== undefined) this.write(fg.encode(this.options.singleLength)); + if(bg !== undefined) this.write(bg.encode(this.options.singleLength)); + } + blink(blinkto: SingleValue, on: number, off: number, delay: number) { + this.writeBytes(0x3f | (this.options.pdiGr?0x80:0)); + this.write(blinkto.encode(this.options.singleLength)); + this.writeBytes( + Math.floor(on*10) & 63 | 64, + Math.floor(off*10) & 63 | 64, + Math.floor(delay*10) & 63 | 64, + ); + } + + async drawImage(file: string, size = new Point(1,0.75), origin = new Point(0,0), options: DrawImageOptions = {}) { + var pelSize = this.options.pelSize; + // TODO: paletted images + var image = sharp(file); + if(options.resize) image = image.resize(options.resize[0], options.resize[1]) + var { data, info } = await image.flatten({ background: options.background }).raw().toBuffer({ resolveWithObject: true }); + this.options.pelSize = new Point(size.x/(info.width), size.y/(info.height+1)); + this.setDomain(); + var s = new Point(size.x, -size.y) + var o = new Point(origin.x, size.y+origin.y/*-size.y/info.height*/); + //TODO: options.mirrory + this.setField(size,origin); + var points: Color[] = []; + for(var i = 0; i < info.size; i += 3) + { + var pt = new Color(data[i]/255, data[i+1]/255, data[i+2]/255); + points.push(pt); + } + this.selectColor(); + this.pointSetAbs(origin); + this.incrementalPoint(points); + //console.log(this.options.pelSize, points.length); + this.options.pelSize = pelSize; + this.setDomain(); + } + + async drawSvg(file: string, size = new Point(1,0.75), origin = new Point(0,0)) { + var win = (await JSDOM.fromFile(file, { contentType: "text/svg+xml" })).window + var node = win.document; + var viewbox = [0,0,100,100]; + + var conv = (x: number, y: number, add: boolean = true) => new Point( + (x+viewbox[0])/viewbox[2]*size.x + (add?origin.x:0), + (add?size.y:0) - (y+viewbox[1])/viewbox[3]*size.y - (add?origin.y:0) + ); + + var rgbColor = (color: string) => { + var c = rgba(color); + if(c.length < 4 || c[3] == 0) return false; + return new Color((c[0]||0)/255, (c[1]||0)/255, (c[2]||0)/255); + }; + + var drawChildren = async (children: HTMLCollection)=>{ + for(const n of children) + { + //console.log(n.nodeName); + var style = win.getComputedStyle(n); + var fill = rgbColor(style.fill || n.getAttribute("fill") || ""); + var stroke = rgbColor(style.stroke || n.getAttribute("stroke") || ""); + switch(n.nodeName){ + case "svg": { + var vbox = n.getAttribute("viewBox") || "0 0 100 100" + viewbox = vbox.split(" ").map(v=>parseFloat(v)); + //console.log("viewbox",viewbox); + if(fill) + { + this.setColor(fill); + this.setRectFilled([origin,size]); + } + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + this.setRectOutlined([origin,size]); + } + } break; + case "line": { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + var x1 = parseFloat(n.getAttribute("x1")||"0"); + var y1 = parseFloat(n.getAttribute("y1")||"0"); + var x2 = parseFloat(n.getAttribute("x2")||"0"); + var y2 = parseFloat(n.getAttribute("y2")||"0"); + this.setColor(stroke || new Color(0,0,0)); + //console.log(conv(x1,y1),conv(x2, y2)); + this.setLineAbs([conv(x1,y1),conv(x2, y2)]); + } break; + case "rect": { + var x = parseFloat(n.getAttribute("x")||"0"); + var y = parseFloat(n.getAttribute("y")||"0"); + var width = parseFloat(n.getAttribute("width")||"0"); + var height = parseFloat(n.getAttribute("height")||"0"); + //console.log(conv(x,y),conv(width, height, false)); + if(fill) + { + this.setColor(fill); + this.setRectFilled([conv(x,y),conv(width, height, false)]); + } + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + this.setRectOutlined([conv(x,y),conv(width, height, false)]); + } + } break; + case "circle": { + var cx = parseFloat(n.getAttribute("cx")||"0"); + var cy = parseFloat(n.getAttribute("cy")||"0"); + var r = parseFloat(n.getAttribute("r")||"0"); + var pts: Point[] = []; + pts.push(conv(cx,cy-r)); + pts.push(conv(0,2*r, false)); + if(fill) + { + this.setColor(fill); + //this.setRectFilled([conv(x,y),conv(width, height, false)]); + this.setArcFilled(pts); + } + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + //this.setRectOutlined([conv(x,y),conv(width, height, false)]); + this.setArcOutlined(pts); + } + } break; + case "ellipse": { + var cx = parseFloat(n.getAttribute("cx")||"0"); + var cy = parseFloat(n.getAttribute("cy")||"0"); + var rx = parseFloat(n.getAttribute("rx")||"0"); + var ry = parseFloat(n.getAttribute("ry")||"0"); + var pts: Point[] = []; + pts.push(conv(cx,cy-ry)); + pts.push(conv(rx,ry, false)); + pts.push(conv(-rx,ry, false)); + pts.push(conv(-rx,-ry, false)); + pts.push(conv(rx,-ry, false)); + if(fill) + { + this.setColor(fill); + //this.setRectFilled([conv(x,y),conv(width, height, false)]); + this.setArcFilled(pts); + } + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + //this.setRectOutlined([conv(x,y),conv(width, height, false)]); + this.setArcOutlined(pts); + } + } break; + case "polyline": + case "polygon": { + var points = n.getAttribute("points")?.split(" ")||[]; + if(points.length > 0) + { + /*var pts = points.map(v=>{ + var pt = v.split(",").map(v=>parseFloat(v)); + var co = conv(pt[0], pt[1]); + console.log(pt, co) + return co; + });*/ + var pts: Point[] = []; + var polypts: Point[] = []; + var parsed = points.map(v=>parseFloat(v)); + //console.log(conv(parsed[0], parsed[1])); + polypts.push(conv(parsed[0], parsed[1])); + for(var i = 2; i < parsed.length; i+=2) + { + var u = conv(parsed[i], parsed[i+1]); + var v = conv(parsed[i-2], parsed[i-1]); + pts.push(v); + pts.push(new Point(u.x-v.x, u.y-v.y)); + polypts.push(new Point(u.x-v.x, u.y-v.y)); + } + //console.log(pts); + if(fill) + { + this.setColor(fill); + this.setPolyFilled(polypts); + } + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + if(n.nodeName == "polyline") + this.setLineRel(pts); + else + this.setPolyOutlined(polypts); + } + } + } break; + case "path": { + // TODO: use arcs + //this.setColor(rgbColor(style.fill)); + var points = n.getAttribute("d")?.split(" ")||[]; + var mode = 'M'; + var why = false; + var pts: Point[] = []; + var ptss: [number,number][] = []; + for(var pt of points) + { + var val = parseFloat(pt); + if(Number.isNaN(val)) + { + mode = pt; + } + else + { + if(why) + { + ptss[ptss.length-1][1] = val; + } + else + { + ptss.push([val,0]); + } + why = !why; + } + } + for(var i = 0; i < ptss.length; i++) + { + if(i==0) pts.push(conv(ptss[i][0], ptss[i][1])); + else pts.push(conv(ptss[i][0]-ptss[i-1][0], ptss[i][1]-ptss[i-1][1], false)); + } + //console.log(pts); + //if(pts.length <= 2){ + if(fill) + { + this.setColor(fill); + this.setPolyFilled(pts); + } + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + this.setPolyOutlined(pts); + } + /*} + else { + this.setColor(rgbColor(style.fill)); + this.setArcFilled(pts); + }*/ + } break; + case "text": { + if(n.textContent) + { + var x = parseFloat(n.getAttribute("x")||"0"); + var y = parseFloat(n.getAttribute("y")||"0"); + var width = parseFloat(n.getAttribute("width")||viewbox[2].toString()); + var height = parseFloat(n.getAttribute("height")||viewbox[3].toString()); + var fontstyle = style.fontSize || n.getAttribute("font-size") || "normal"; + var wordwrap = n.getAttribute("word-wrap") != null; + switch(fontstyle) { + case "small": + this.smallText(); + break; + case "medium": + this.mediumText(); + break; + case "double": + this.doubleSizeText(); + break; + case "doubleheight": + this.doubleHeightText(); + break; + case "normal": + default: + this.normalText(); + break; + } + if(n.getAttribute("width") || n.getAttribute("height")) + this.setField(conv(x,y),conv(width,height,false)); + this.wordWrap(wordwrap); + this.pointSetAbs(conv(x,y)); + this.setColor(fill || new Color(0,0,0)); + this.text(n.textContent.replaceAll("\n","\r\n")); + this.wordWrap(false); + switch(fontstyle) { + case "small": + case "medium": + case "double": + case "doubleheight": + this.normalText(); + break; + } + } + } break; + case "image": { + var x = parseFloat(n.getAttribute("x")||"0"); + var y = parseFloat(n.getAttribute("y")||"0"); + var width = parseFloat(n.getAttribute("width")||"0"); + var height = parseFloat(n.getAttribute("height")||"0"); + var href = n.getAttribute("href"); + if(href) + await this.drawImage(href, conv(width,height,false), conv(x,y), { background: style.fill || n.getAttribute("fill") || undefined }); + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + this.setRectOutlined([conv(x,y),conv(width,height,false)]); + } + } break; + case "use": { + var x = parseFloat(n.getAttribute("x")||"0"); + var y = parseFloat(n.getAttribute("y")||"0"); + var width = parseFloat(n.getAttribute("width")||"0"); + var height = parseFloat(n.getAttribute("height")||"0"); + var href = n.getAttribute("href"); + if(fill) + { + this.setColor(fill); + this.setRectFilled([conv(x,y),conv(width,height,false)]); + } + if(href) await this.drawSvg(href, conv(width,height,false), conv(x,y)); + if(stroke) + { + var strokeWidth = parseInt(style.strokeWidth)||0; + this.options.pelSize = new Point(strokeWidth/viewbox[2], strokeWidth/viewbox[3]); + this.setDomain(); + this.setColor(stroke); + this.setRectOutlined([conv(x,y),conv(width,height,false)]); + } + } break; + case "script": { + if(n.textContent) + { + var fun = new Function("window", "document", "naplps", n.textContent); + fun(win, node, this); + } + } break; + default: break; + } + if(n.children.length > 0) + await drawChildren(n.children); + } + } + if(node.children.length > 0) + await drawChildren(node.children); + } +} + +export class Telidon { + server: net.Server; + connectionListener: (c: Naplps) => void; + constructor(options: net.ServerOpts | undefined, connectionListener: (c: Naplps) => void) { + var domain = Domain.create(); + this.connectionListener = connectionListener; + domain.run(_=>{ + this.server = net.createServer(options, socket=>{ + var dom = Domain.create(); + var naplps = new Naplps(socket); + dom.add(socket); + dom.add(naplps); + dom.on("error", err=>naplps.emit("error", err)); + dom.run(this.connectionListener, naplps); + }); + }); + } + + listen(...args: number[]) { + this.server.listen(...args); + } +} \ No newline at end of file