summaryrefslogtreecommitdiff
path: root/src/utils
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-10-04 18:36:10 -0700
committerElizabeth Hunt <me@liz.coffee>2025-10-04 18:36:10 -0700
commit35add63ec4dce39710095f17abd86777de9e5b49 (patch)
tree1afdf952f310e09a663e85541474efdc95155a73 /src/utils
parent507c972ecafeceaf4f8962ad881f8fb50c9b86c1 (diff)
downloadansicolor-35add63ec4dce39710095f17abd86777de9e5b49.tar.gz
ansicolor-35add63ec4dce39710095f17abd86777de9e5b49.zip
Working history state
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/ansi.ts68
-rw-r--r--src/utils/grid.ts50
2 files changed, 118 insertions, 0 deletions
diff --git a/src/utils/ansi.ts b/src/utils/ansi.ts
new file mode 100644
index 0000000..551a365
--- /dev/null
+++ b/src/utils/ansi.ts
@@ -0,0 +1,68 @@
+import type { AnsiTermColor, AnsiColorVal, AnsiRgb } from '@/types/grid';
+import type { NumericRange } from '@/types/num';
+import type { CSSProperties } from 'react';
+
+const byteToAnsiValRatio = 255 / 5;
+
+export const ansiRgbToHex = ({ r, g, b }: AnsiRgb): string => {
+ // Scale ANSI colors (0-5) to RGB range (0-255)
+ const scaleToRgb = (val: number) => Math.round(val * byteToAnsiValRatio);
+ const rHex = scaleToRgb(r).toString(16).padStart(2, '0');
+ const gHex = scaleToRgb(g).toString(16).padStart(2, '0');
+ const bHex = scaleToRgb(b).toString(16).padStart(2, '0');
+ return `#${rHex}${gHex}${bHex}`;
+};
+
+export const hexToAnsi = (_hex: string): AnsiRgb => {
+ const hex = _hex.split('#').at(-1)!;
+ const rgb = [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map(
+ (val) => parseInt(val, 16) || 0,
+ );
+
+ const [r, g, b] = rgb.map(
+ (value) =>
+ Math.floor(
+ Math.min(value, 255) * (1 / byteToAnsiValRatio),
+ ) as AnsiColorVal,
+ );
+ return { r, g, b };
+};
+
+export const getAnsiColorEscape = (ansi: AnsiTermColor) => {
+ const [fg, bg] = [ansi.foreground, ansi.background].map((color, i) => {
+ if (color === null) return `\x1b[39;49m`;
+ const { r, g, b } = color;
+ const id = (16 + 36 * r + 6 * g + b) as NumericRange<16, 231>;
+ return `\x1b[${3 + i}8;5;${id}m`;
+ });
+ return { fg, bg };
+};
+
+export const getAnsiEscapeCodeFromDiff = (
+ currentColor: AnsiTermColor,
+ newColor: AnsiTermColor,
+): string => {
+ const { fg: currentFg, bg: currentBg } = getAnsiColorEscape(currentColor);
+ const { fg: newFg, bg: newBg } = getAnsiColorEscape(newColor);
+
+ const fg = currentFg === newFg ? '' : newFg;
+ const bg = currentBg === newBg ? '' : newBg;
+
+ return `${fg}${bg}`;
+};
+
+export const ansiColorsEqual = (
+ color1: AnsiTermColor,
+ color2: AnsiTermColor,
+): boolean => {
+ // eh.
+ return JSON.stringify(color1) === JSON.stringify(color2);
+};
+
+export const getStyleForAnsiColor = (color: AnsiTermColor): CSSProperties => {
+ const { background, foreground } = color;
+ return {
+ backgroundColor: background ? ansiRgbToHex(background) : undefined,
+ color: foreground ? ansiRgbToHex(foreground) : undefined,
+ };
+};
diff --git a/src/utils/grid.ts b/src/utils/grid.ts
new file mode 100644
index 0000000..71370da
--- /dev/null
+++ b/src/utils/grid.ts
@@ -0,0 +1,50 @@
+import type { AnsiTermColor, Grid } from '@/types/grid';
+import { getAnsiColorEscape, getAnsiEscapeCodeFromDiff } from './ansi';
+
+const defaultColor: AnsiTermColor = { foreground: null, background: null };
+export const gridFromAscii = (
+ ascii: string,
+ color: AnsiTermColor = defaultColor,
+): Grid => {
+ const lineWidth = Math.max(...ascii.split('\n').map((line) => line.length));
+ return ascii.split('\n').map((line, y) =>
+ line
+ .split('')
+ .map((char, x) => ({
+ char,
+ color,
+ x,
+ y,
+ }))
+ .concat(
+ Array(lineWidth - line.length)
+ .fill(0)
+ .map((_, x) => ({
+ char: ' ',
+ color,
+ x: x + line.length,
+ y,
+ })),
+ ),
+ );
+};
+
+export const gridToAnsi = (grid: Grid) => {
+ const reset: AnsiTermColor = { foreground: null, background: null };
+ const { fg, bg } = getAnsiColorEscape(reset);
+ const resetCode = `${fg}${bg}`;
+
+ const rows = [];
+ for (let y = 0; y < grid.length; y++) {
+ let row = '';
+ for (let x = 0; x < grid[y].length; x++) {
+ const cell = grid[y][x];
+ const previousColor = x > 0 ? grid[y][x - 1].color : reset;
+ row +=
+ getAnsiEscapeCodeFromDiff(previousColor, cell.color) +
+ cell.char;
+ }
+ rows.push(row);
+ }
+ return resetCode + rows.join(resetCode + '\n');
+};