UNPKG

@thi.ng/text-canvas

Version:

Text based canvas, drawing, plotting, tables with arbitrary formatting (incl. ANSI/HTML)

191 lines (190 loc) 4.97 kB
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 };