@thi.ng/text-canvas
Version:
Text based canvas, drawing, plotting, tables with arbitrary formatting (incl. ANSI/HTML)
149 lines (148 loc) • 4.24 kB
JavaScript
import { peek } from "@thi.ng/arrays/peek";
import { isString } from "@thi.ng/checks/is-string";
import { wordWrapLines } from "@thi.ng/strings/word-wrap";
import { Border } from "./api.js";
import {
beginClip,
beginStyle,
canvas,
endClip,
endStyle,
setAt
} from "./canvas.js";
import { hline, vline } from "./hvline.js";
import { fillRect, strokeRect } from "./rect.js";
import { horizontalOnly, verticalOnly } from "./style.js";
import { textLines } from "./text.js";
const initTable = (opts, cells) => {
const b = opts.border !== void 0 ? opts.border : Border.ALL;
const bH = b & Border.H ? 1 : 0;
const bV = b & Border.V ? 1 : 0;
const bF = bH && bV || b & Border.FRAME ? 1 : 0;
const bFH = bF | bH;
const bFV = bF | bV;
const [padH, padV] = (opts.padding || [0, 0]).map((x) => x << 1);
const cols = opts.cols;
const numCols = cols.length - 1;
const numRows = cells.length - 1;
const rowHeights = new Array(numRows + 1).fill(0);
const wrapped = [];
for (let i = 0; i <= numRows; i++) {
const row = cells[i];
const wrappedRow = [];
for (let j = 0; j <= numCols; j++) {
const cell = isString(row[j]) ? { body: row[j] } : row[j];
const lines = cell.wrap !== false ? wordWrapLines(cell.body, {
width: cols[j].width,
hard: cell.hard || opts.hard
}).map((l) => l.toString()) : cell.body.split(/\r?\n/g);
wrappedRow.push({
body: lines,
format: cell.format
});
rowHeights[i] = Math.max(
rowHeights[i],
lines.length,
cell.height || 0
);
}
wrapped.push(wrappedRow);
}
return {
style: opts.style,
format: opts.format,
formatHead: opts.formatHead,
width: cols.reduce((acc, x) => acc + x.width, 0) + 2 * bFV + numCols * bV + (numCols + 1) * padH,
height: rowHeights.reduce((acc, x) => acc + x, 0) + 2 * bFH + numRows * bH + (numRows + 1) * padV,
cells: wrapped,
rowHeights,
cols,
numCols,
numRows,
padH,
padV,
b,
bH,
bV,
bFH,
bFV
};
};
const drawTable = (canvas2, x, y, opts) => {
const {
cells,
cols,
numCols,
numRows,
rowHeights,
width,
height,
padH,
padV,
bH,
bV,
bFH,
bFV
} = opts;
const fmt = opts.format !== void 0 ? opts.format : canvas2.format;
const fmtHd = opts.formatHead !== void 0 ? opts.formatHead : fmt;
const currFormat = canvas2.format;
canvas2.format = fmt;
let style = opts.style || peek(canvas2.styles);
style = opts.b === Border.H ? horizontalOnly(style) : opts.b === Border.V ? verticalOnly(style) : style;
beginStyle(canvas2, style);
fillRect(canvas2, x + bFV, y + bFH, width - 2 * bFV, height - 2 * bFH, " ");
opts.b && strokeRect(canvas2, x, y, width, height);
if (bV) {
for (let i = 1, xx = x + cols[0].width + padH + 1; i <= numCols; xx += cols[i].width + padH + 1, i++) {
vline(canvas2, xx, y, height, style.tjt, style.tjb, style.vl);
}
}
for (let i = 0, yy = y + bFH; i <= numRows; i++) {
const row = cells[i];
const rowH = rowHeights[i];
const y2 = yy + rowH + padV;
if (bH && i < numRows) {
hline(canvas2, x, y2, width, style.tjl, style.tjr, style.hl);
}
for (let j = 0, xx = x + bFV; j <= numCols; j++) {
const col = cols[j];
const curr = row[j];
if (curr.body) {
beginClip(canvas2, xx, yy, col.width + padH, rowH + padV);
textLines(
canvas2,
xx + padH / 2,
yy + padV / 2,
curr.body,
curr.format || (i ? fmt : fmtHd)
);
endClip(canvas2);
}
if (bH && bV && j > 0 && i < numRows) {
setAt(canvas2, xx - 1, y2, style.jct);
}
xx += col.width + bV + padH;
}
yy = y2 + bH;
}
endStyle(canvas2);
canvas2.format = currFormat;
};
const table = (canvas2, x, y, opts, cells) => {
const spec = initTable(opts, cells);
drawTable(canvas2, x, y, spec);
return [spec.width, spec.height];
};
const tableCanvas = (opts, cells) => {
const tbl = initTable(opts, cells);
const result = canvas(tbl.width, tbl.height);
drawTable(result, 0, 0, tbl);
return result;
};
export {
drawTable,
initTable,
table,
tableCanvas
};