Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
danprince committed Mar 18, 2022
0 parents commit 1a241ea
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.log
node_modules
dist
4 changes: 4 additions & 0 deletions esbuild.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "*.png" {
const content: string;
export default content;
}
38 changes: 38 additions & 0 deletions example/example.ts
Original file line number Diff line number Diff line change
@@ -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");
}

1 change: 1 addition & 0 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script defer src="example.js"></script>
Binary file added font.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 129 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import defaultFontSrc from "./font.png";

export interface Font {
image: HTMLImageElement;
charWidth: number;
charHeight: number;
}

const defaultFontImage = new Image();
defaultFontImage.src = defaultFontSrc;

const defaultFont: Font = {
image: defaultFontImage,
charWidth: 6,
charHeight: 6,
};

type CodePage = Record<number, number>;
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<string, HTMLCanvasElement>();

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<void>((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) };
}
}
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"devDependencies": {
"esbuild": "^0.14.27",
"typescript": "^4.6.2"
},
"scripts": {
"dev": "./scripts/dev",
"build": "./scripts/build"
}
}
22 changes: 22 additions & 0 deletions scripts/build
Original file line number Diff line number Diff line change
@@ -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;
14 changes: 14 additions & 0 deletions scripts/dev
Original file line number Diff line number Diff line change
@@ -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}`);
});
14 changes: 14 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -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
}
}
134 changes: 134 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


[email protected]:
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==

[email protected]:
version "0.14.27"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz#e7d6430555e8e9c505fd87266bbc709f25f1825c"
integrity sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

[email protected]:
version "0.14.27"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz#df869dbd67d4ee3a04b3c7273b6bd2b233e78a18"
integrity sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==

[email protected]:
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==

[email protected]:
version "0.14.27"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz#9a21af766a0292578a3009c7408b8509cac7cefd"
integrity sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==

[email protected]:
version "0.14.27"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz#344a27f91568056a5903ad5841b447e00e78d740"
integrity sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

[email protected]:
version "0.14.27"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz#8611d825bcb8239c78d57452e83253a71942f45c"
integrity sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==

[email protected]:
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==

[email protected]:
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==

[email protected]:
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==

0 comments on commit 1a241ea

Please sign in to comment.