@thi.ng/text-canvas
Version:
Text based canvas, drawing, plotting, tables with arbitrary formatting (incl. ANSI/HTML)
191 lines (190 loc) • 4.97 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
import {
nomixin
} from "@thi.ng/api";
import { IGrid2DMixin } from "@thi.ng/api/mixins/igrid";
import { peek } from "@thi.ng/arrays/peek";
import { assert } from "@thi.ng/errors/assert";
import { clamp } from "@thi.ng/math/interval";
import { NONE } from "@thi.ng/text-format";
import { map } from "@thi.ng/transducers/map";
import { max } from "@thi.ng/transducers/max";
import { transduce } from "@thi.ng/transducers/transduce";
import { STYLE_ASCII } from "./api.js";
import { charCode, intersectRect } from "./utils.js";
let Canvas = class {
data;
size;
stride;
format;
defaultFormat;
styles;
clipRects;
constructor(width, height, format = NONE, style = STYLE_ASCII) {
this.size = [width, height];
this.stride = [1, this.width];
this.format = this.defaultFormat = format;
this.data = new Uint32Array(width * height).fill(
charCode(32, format)
);
this.styles = [style];
this.clipRects = [
{ x1: 0, y1: 0, x2: width, y2: height, w: width, h: height }
];
}
get width() {
return this.size[0];
}
get height() {
return this.size[1];
}
get offset() {
return 0;
}
get dim() {
return 2;
}
copy() {
const res = new Canvas(this.width, this.height, this.format);
res.data.set(this.data);
res.stride = this.stride.slice();
res.styles = this.styles.slice();
res.clipRects = this.clipRects.slice();
return res;
}
empty() {
return new Canvas(this.width, this.height);
}
clear() {
this.data.fill(32);
}
// @ts-ignore mixin
order() {
}
// @ts-ignore mixin
includes(d0, d1) {
}
// @ts-ignore mixin
indexAt(d0, d1) {
}
// @ts-ignore mixin
indexAtUnsafe(d0, d1) {
}
// @ts-ignore mixin
getAt(x, y) {
}
// @ts-ignore mixin
getAtUnsafe(x, y) {
}
setAt(x, y, val) {
return this.includes(x, y) ? (this.data[this.indexAtUnsafe(x, y)] = charCode(
val,
this.format
), true) : false;
}
// @ts-ignore mixin
setAtUnsafe(x, y, col) {
}
};
__decorateClass([
nomixin
], Canvas.prototype, "setAt", 1);
Canvas = __decorateClass([
IGrid2DMixin
], Canvas);
const canvas = (width, height, format, style) => new Canvas(width, height, format, style);
const canvasFromText = (lines, format = NONE, fill) => {
const height = lines.length;
assert(height > 0, "require at least 1 line of text");
const width = transduce(
map((x) => x.length),
max(),
lines
);
const res = canvas(width, height, format);
fill !== void 0 && res.data.fill(charCode(fill, format));
format <<= 16;
for (let y = 0; y < height; y++) {
const line = lines[y];
for (let x = 0, i = y * width, n = Math.min(width, line.length); x < n; x++, i++) {
res.data[i] = line.charCodeAt(x) | format;
}
}
return res;
};
const beginClip = (canvas2, x, y, w, h) => {
x |= 0;
y |= 0;
w |= 0;
h |= 0;
const { width, height } = canvas2;
const x2 = clamp(x + w, 0, width);
const y2 = clamp(y + h, 0, height);
const x1 = clamp(x, 0, width);
const y1 = clamp(y, 0, height);
canvas2.clipRects.push(
intersectRect(
{ x1, y1, x2, y2, w: x2 - x1, h: y2 - y1 },
peek(canvas2.clipRects)
)
);
};
const __pop = (stack) => stack.length > 1 && stack.pop();
const endClip = (canvas2) => __pop(canvas2.clipRects);
const withClip = (canvas2, x, y, w, h, fn) => {
beginClip(canvas2, x, y, w, h);
fn();
canvas2.clipRects.pop();
};
const beginStyle = (canvas2, style) => {
canvas2.styles.push(style);
};
const endStyle = (canvas2) => __pop(canvas2.styles);
const withStyle = (canvas2, style, fn) => {
canvas2.styles.push(style);
fn();
canvas2.styles.pop();
};
const setFormat = (canvas2, format) => canvas2.format = format;
const withFormat = (canvas2, format, fn) => {
const prev = canvas2.format;
canvas2.format = format;
fn();
canvas2.format = prev;
};
const getAt = (canvas2, x, y) => {
x |= 0;
y |= 0;
return x >= 0 && y >= 0 && x < canvas2.width && y < canvas2.height ? canvas2.data[x + y * canvas2.width] : 0;
};
const setAt = (canvas2, x, y, code, format = canvas2.format) => {
x |= 0;
y |= 0;
const { x1, y1, x2, y2 } = peek(canvas2.clipRects);
if (x < x1 || y < y1 || x >= x2 || y >= y2) return;
canvas2.data[x + y * canvas2.width] = charCode(code, format);
};
export {
Canvas,
beginClip,
beginStyle,
canvas,
canvasFromText,
endClip,
endStyle,
getAt,
setAt,
setFormat,
withClip,
withFormat,
withStyle
};