From 1a241ea565807097ce91d061af8de14129fd1282 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 18 Mar 2022 12:26:48 +0100 Subject: [PATCH] initial commit --- .gitignore | 3 + esbuild.d.ts | 4 ++ example/example.ts | 38 +++++++++++++ example/index.html | 1 + font.png | Bin 0 -> 1088 bytes index.ts | 129 +++++++++++++++++++++++++++++++++++++++++++ package.json | 10 ++++ scripts/build | 22 ++++++++ scripts/dev | 14 +++++ tsconfig.json | 14 +++++ yarn.lock | 134 +++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 369 insertions(+) create mode 100644 .gitignore create mode 100644 esbuild.d.ts create mode 100644 example/example.ts create mode 100644 example/index.html create mode 100644 font.png create mode 100644 index.ts create mode 100644 package.json create mode 100755 scripts/build create mode 100755 scripts/dev create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc4c38f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.log +node_modules +dist \ No newline at end of file diff --git a/esbuild.d.ts b/esbuild.d.ts new file mode 100644 index 0000000..56b7c17 --- /dev/null +++ b/esbuild.d.ts @@ -0,0 +1,4 @@ +declare module "*.png" { + const content: string; + export default content; +} diff --git a/example/example.ts b/example/example.ts new file mode 100644 index 0000000..e1ae16d --- /dev/null +++ b/example/example.ts @@ -0,0 +1,38 @@ +import { Terminal } from ".."; + +let t = new Terminal(30, 30); +let m = { x: 0, y: 0 }; + +t.canvas.style.width = t.canvas.width * 3 + "px"; +t.canvas.style.height = t.canvas.height * 3 + "px"; +document.body.append(t.canvas); + +t.ready().then(() => { + update(); + onmousemove = event => { + m = t.screenToGrid(event.clientX, event.clientY); + update(); + }; +}); + +const BOX_DRAWING_EXAMPLE = ` +┌─┬┐ ╔═╦╗ ╓─╥╖ ╒═╤╕ +│ ││ ║ ║║ ║ ║║ │ ││ +├─┼┤ ╠═╬╣ ╟─╫╢ ╞═╪╡ +└─┴┘ ╚═╩╝ ╙─╨╜ ╘═╧╛ +┌───────────────────┐ +│ ╔═══╗ Some Text │▒ +│ ╚═╦═╝ in the box │▒ +╞═╤══╩══╤═══════════╡▒ +│ ├──┬──┤ │▒ +│ └──┴──┘ │▒ +└───────────────────┘▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +`; + +function update() { + t.clear(); + t.write(1, 1, BOX_DRAWING_EXAMPLE, "blue"); + t.put(m.x, m.y, 0x40, "black", "white"); +} + diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..6d75d7f --- /dev/null +++ b/example/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/font.png b/font.png new file mode 100644 index 0000000000000000000000000000000000000000..53890dcae33b6299a4b50bf45743f1586761b290 GIT binary patch literal 1088 zcmV-G1i$-Px#22e~?MF0Q*|NsA`*`M720007XQchC<|67qFwEzGE+DSw~RA_z)_jJ*xos+Uxn32U)CJrA@jM2N{~;>d20g&`0W2vbUn9pZ{CTk}`IT_Ol$6!+#Y zIO3Ei&H>8zH1kf1+5q{BhADYGfO$3JmPzGu6sA&0y_R}W@P5Vj6Oy^~Q93|9&3U<1 zQ)Ae+1$DamdySxqH3WkyXk<%1c%wAsK(L}1xO0p90V=DFdkR7(ae(^hvQhffF?(kE z3#MxWM)^SMGhN+5l2Y}}0PmDa5{&(#BA}~cSq(((4P~aPOLX*V%|2>OB})Ac;YG=@ z`JFN%!s)ba>{^beTbu~A#g5ZB@Tdl7;!f3AkM+lORd{00ta_vns5>qUbd*x}Y50YK zTOm>es0P5NV=!M)kN#hk;eFxWH>{zACB~Fkk8Vqq+$UebQY zK)D=K8XQWdDB?;xArRFPyF^gdPJJ`dYFaEAvrgGREEWHs0pug4CIWnm*mjnHN)77Z z&#y~CR8$nrIr)Z14qC>T zj+6sRt1zvL`J}IXF8O*hp`a2U{VZm1^eRf`XeLxspvbCLT9@Wq5jXo00qNCBAAGDi zz|b0_P*Di-9tt)p3I_z-lLU#{Bsz@^q-J&Q8wkFGb)rAuuLv_Lznwq;0000; +export const CP437: CodePage = { 9786: 1, 9787: 2, 9829: 3, 9830: 4, 9827: 5, 9824: 6, 8226: 7, 9688: 8, 9675: 9, 9689: 10, 9794: 11, 9792: 12, 9834: 13, 9835: 14, 9788: 15, 9658: 16, 9668: 17, 8597: 18, 8252: 19, 182: 20, 167: 21, 9644: 22, 8616: 23, 8593: 24, 8595: 25, 8594: 26, 8592: 27, 8735: 28, 8596: 29, 9650: 30, 9660: 31, 8962: 127, 199: 128, 252: 129, 233: 130, 226: 131, 228: 132, 224: 133, 229: 134, 231: 135, 234: 136, 235: 137, 232: 138, 239: 139, 238: 140, 236: 141, 196: 142, 197: 143, 201: 144, 230: 145, 198: 146, 244: 147, 246: 148, 242: 149, 251: 150, 249: 151, 255: 152, 214: 153, 220: 154, 162: 155, 163: 156, 165: 157, 8359: 158, 402: 159, 225: 160, 237: 161, 243: 162, 250: 163, 241: 164, 209: 165, 170: 166, 186: 167, 191: 168, 8976: 169, 172: 170, 189: 171, 188: 172, 161: 173, 171: 174, 187: 175, 9617: 176, 9618: 177, 9619: 178, 9474: 179, 9508: 180, 9569: 181, 9570: 182, 9558: 183, 9557: 184, 9571: 185, 9553: 186, 9559: 187, 9565: 188, 9564: 189, 9563: 190, 9488: 191, 9492: 192, 9524: 193, 9516: 194, 9500: 195, 9472: 196, 9532: 197, 9566: 198, 9567: 199, 9562: 200, 9556: 201, 9577: 202, 9574: 203, 9568: 204, 9552: 205, 9580: 206, 9575: 207, 9576: 208, 9572: 209, 9573: 210, 9561: 211, 9560: 212, 9554: 213, 9555: 214, 9579: 215, 9578: 216, 9496: 217, 9484: 218, 9608: 219, 9604: 220, 9612: 221, 9616: 222, 9600: 223, 945: 224, 223: 225, 915: 226, 960: 227, 931: 228, 963: 229, 181: 230, 964: 231, 934: 232, 920: 233, 937: 234, 948: 235, 8734: 236, 966: 237, 949: 238, 8745: 239, 8801: 240, 177: 241, 8805: 242, 8804: 243, 8992: 244, 8993: 245, 247: 246, 8776: 247, 176: 248, 8729: 249, 183: 250, 8730: 251, 8319: 252, 178: 253, 9632: 254 }; + +export class Terminal { + width: number; + height: number; + canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; + codepage: CodePage = CP437; + + private font: Font; + private tintCache = new Map(); + + constructor(width: number, height: number, font: Font = defaultFont) { + let canvas = document.createElement("canvas"); + let ctx = canvas.getContext("2d")!; + canvas.width = width * font.charWidth; + canvas.height = height * font.charHeight; + canvas.style.imageRendering = "pixelated"; + ctx.imageSmoothingEnabled = false; + + this.width = width; + this.height = height; + this.font = font; + this.canvas = canvas; + this.ctx = ctx; + } + + private tint(color: string) { + let cached = this.tintCache.get(color); + if (cached) return cached; + + let canvas = document.createElement("canvas"); + let ctx = canvas.getContext("2d")!; + canvas.width = this.font.image.width; + canvas.height = this.font.image.height; + ctx.imageSmoothingEnabled = false; + ctx.drawImage(this.font.image, 0, 0); + ctx.fillStyle = color; + ctx.globalCompositeOperation = "source-atop"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + this.tintCache.set(color, canvas); + return canvas; + } + + ready() { + return new Promise((resolve, reject) => { + this.font.image.addEventListener("load", () => resolve()); + this.font.image.addEventListener("error", reject); + }); + } + + put(x: number, y: number, code: number, fg?: string, bg?: string) { + // If the image hasn't loaded yet, we need to bail out of rendering + if (this.font.image.width === 0 || this.font.image.height === 0) return; + + code = this.codepage[code] ?? code; + let img: HTMLImageElement | HTMLCanvasElement = this.font.image; + let cw = this.font.charWidth; + let ch = this.font.charHeight; + let cols = img.width / cw; + let sx = (code % cols) * cw; + let sy = (code / cols | 0) * ch; + let dx = x * cw; + let dy = y * ch; + + if (fg) { + img = this.tint(fg); + } + + if (bg) { + this.ctx.fillStyle = bg; + this.ctx.fillRect(dx, dy, cw, ch); + } + + this.ctx.drawImage(img, sx, sy, cw, ch, dx, dy, cw, ch); + } + + write(x: number, y: number, text: string, fg?: string, bg?: string) { + let tx = x; + let ty = y; + for (let i = 0; i < text.length; i++) { + if (text[i] === "\n") { + tx = x; + ty++; + } else { + this.put(tx++, ty, text.charCodeAt(i), fg, bg); + } + } + } + + clear( + x: number = 0, + y: number = 0, + width: number = this.width, + height: number = this.height + ) { + let cw = this.font.charWidth; + let ch = this.font.charHeight; + this.ctx.clearRect(x * cw, y * ch, width * cw, height * ch); + } + + screenToGrid(x: number, y: number) { + let { charWidth, charHeight } = this.font; + let rect = this.canvas.getBoundingClientRect(); + let scaleX = rect.width / charWidth / this.width; + let scaleY = rect.height / charHeight / this.height; + let gridX = (x - rect.x) / charWidth / scaleX; + let gridY = (y - rect.y) / charHeight / scaleY; + return { x: Math.floor(gridX), y: Math.floor(gridY) }; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..89356be --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "esbuild": "^0.14.27", + "typescript": "^4.6.2" + }, + "scripts": { + "dev": "./scripts/dev", + "build": "./scripts/build" + } +} diff --git a/scripts/build b/scripts/build new file mode 100755 index 0000000..31d22cb --- /dev/null +++ b/scripts/build @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +let esbuild = require("esbuild"); + +/** + * @type {esbuild.BuildOptions} + */ +let options = { + entryPoints: ["index.ts"], + bundle: true, + minify: true, + sourcemap: true, + loader: { ".png": "dataurl" }, + format: "esm", + outfile: "dist/telerin.js", +}; + +if (require.main) { + esbuild.buildSync(options); +} + +module.exports = options; \ No newline at end of file diff --git a/scripts/dev b/scripts/dev new file mode 100755 index 0000000..8dd5391 --- /dev/null +++ b/scripts/dev @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +let esbuild = require("esbuild"); +let buildOptions = require("./build"); + +esbuild.serve({ + servedir: "example" +}, { + ...buildOptions, + entryPoints: ["example/example.ts"], + outfile: "example/example.js", +}).then(result => { + console.log(`http://localhost:${result.port}`); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ecd4829 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noImplicitReturns": true + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..7408c95 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,134 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +esbuild-android-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz#b868bbd9955a92309c69df628d8dd1945478b45c" + integrity sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ== + +esbuild-android-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz#e7d6430555e8e9c505fd87266bbc709f25f1825c" + integrity sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ== + +esbuild-darwin-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz#4dc7484127564e89b4445c0a560a3cb50b3d68e1" + integrity sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g== + +esbuild-darwin-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz#469e59c665f84a8ed323166624c5e7b9b2d22ac1" + integrity sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ== + +esbuild-freebsd-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz#895df03bf5f87094a56c9a5815bf92e591903d70" + integrity sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA== + +esbuild-freebsd-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz#0b72a41a6b8655e9a8c5608f2ec1afdcf6958441" + integrity sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA== + +esbuild-linux-32@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz#43b8ba3803b0bbe7f051869c6a8bf6de1e95de28" + integrity sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw== + +esbuild-linux-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz#dc8072097327ecfadba1735562824ce8c05dd0bd" + integrity sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg== + +esbuild-linux-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz#c52b58cbe948426b1559910f521b0a3f396f10b8" + integrity sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ== + +esbuild-linux-arm@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz#df869dbd67d4ee3a04b3c7273b6bd2b233e78a18" + integrity sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw== + +esbuild-linux-mips64le@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz#a2b646d9df368b01aa970a7b8968be6dd6b01d19" + integrity sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A== + +esbuild-linux-ppc64le@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz#9a21af766a0292578a3009c7408b8509cac7cefd" + integrity sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA== + +esbuild-linux-riscv64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz#344a27f91568056a5903ad5841b447e00e78d740" + integrity sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg== + +esbuild-linux-s390x@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz#73a7309bd648a07ef58f069658f989a5096130db" + integrity sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg== + +esbuild-netbsd-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz#482a587cdbd18a6c264a05136596927deb46c30a" + integrity sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q== + +esbuild-openbsd-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz#e99f8cdc63f1628747b63edd124d53cf7796468d" + integrity sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw== + +esbuild-sunos-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz#8611d825bcb8239c78d57452e83253a71942f45c" + integrity sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg== + +esbuild-windows-32@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz#c06374206d4d92dd31d4fda299b09f51a35e82f6" + integrity sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw== + +esbuild-windows-64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz#756631c1d301dfc0d1a887deed2459ce4079582f" + integrity sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg== + +esbuild-windows-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz#ad7e187193dcd18768b16065a950f4441d7173f4" + integrity sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg== + +esbuild@^0.14.27: + version "0.14.27" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.27.tgz#41fe0f1b6b68b9f77cac025009bc54bb96e616f1" + integrity sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q== + optionalDependencies: + esbuild-android-64 "0.14.27" + esbuild-android-arm64 "0.14.27" + esbuild-darwin-64 "0.14.27" + esbuild-darwin-arm64 "0.14.27" + esbuild-freebsd-64 "0.14.27" + esbuild-freebsd-arm64 "0.14.27" + esbuild-linux-32 "0.14.27" + esbuild-linux-64 "0.14.27" + esbuild-linux-arm "0.14.27" + esbuild-linux-arm64 "0.14.27" + esbuild-linux-mips64le "0.14.27" + esbuild-linux-ppc64le "0.14.27" + esbuild-linux-riscv64 "0.14.27" + esbuild-linux-s390x "0.14.27" + esbuild-netbsd-64 "0.14.27" + esbuild-openbsd-64 "0.14.27" + esbuild-sunos-64 "0.14.27" + esbuild-windows-32 "0.14.27" + esbuild-windows-64 "0.14.27" + esbuild-windows-arm64 "0.14.27" + +typescript@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" + integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==