diff --git a/demo/client.ts b/demo/client.ts
index 00d0bf67..e5779b78 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -29,6 +29,8 @@ import { WebglAddon } from '@xterm/addon-webgl';
import { Unicode11Addon } from '@xterm/addon-unicode11';
import { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';
+import { writeUnicodeTable, type UnicodeRangeDefinition } from './unicodeTable';
+
export interface IWindowWithTerminal extends Window {
term: typeof Terminal;
Terminal: typeof Terminal;
@@ -233,6 +235,7 @@ if (document.location.pathname === '/test') {
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
+ document.getElementById('symbols-for-legacy-computing').addEventListener('click', writeSymbolsForLegacyComputing);
document.getElementById('load-test').addEventListener('click', loadTest);
document.getElementById('load-test-long-lines').addEventListener('click', loadTestLongLines);
document.getElementById('print-cjk').addEventListener('click', addCjk);
@@ -829,6 +832,38 @@ function writeCustomGlyphHandler(): void {
window.scrollTo(0, 0);
}
+function writeSymbolsForLegacyComputing(): void {
+ // Symbols for Legacy Computing
+ // Range: 1FB00–1FBFF
+ // Source: The Unicode Standard, Version 17.0 https://www.unicode.org/charts/PDF/U1FB00.pdf
+ writeUnicodeTable(term, 'Symbols for Legacy Computing', 0x1FB00, 0x1FBFF, [
+ ['Block mosaic terminal graphic characters (Sextants)', 0x1FB00, 0x1FB3B],
+ ['Smooth mosaic terminal graphic characters', 0x1FB3C, 0x1FB6F],
+ ['Block elements', 0x1FB70, 0x1FB80],
+ ['Window title bar', 0x1FB81, 0x1FB81],
+ ['Block elements', 0x1FB82, 0x1FB8B],
+ ['Rectangular shade characters', 0x1FB8C, 0x1FB94],
+ ['Fill characters', 0x1FB95, 0x1FB97],
+ ['Diagonal fill characters', 0x1FB98, 0x1FB99],
+ ['Smooth mosaic terminal graphic characters', 0x1FB9A, 0x1FB9B],
+ ['Triangular shade characters', 0x1FB9C, 0x1FB9F],
+ ['Character cell diagonals', 0x1FBA0, 0x1FBAE],
+ ['Light solid line with stroke', 0x1FBAF, 0x1FBAF],
+ ['Terminal graphic characters', 0x1FBB0, 0x1FBB3],
+ ['Arrows', 0x1FBB4, 0x1FBB8],
+ ['Terminal graphic characters', 0x1FBB9, 0x1FBBC],
+ ['Negative terminal graphic characters', 0x1FBBD, 0x1FBBF],
+ ['Terminal graphic characters', 0x1FBC0, 0x1FBCA],
+ ['Terminal graphic characters', 0x1FBCB, 0x1FBCD],
+ ['Block elements', 0x1FBCE, 0x1FBCF],
+ ['Character cell diagonals', 0x1FBD0, 0x1FBDF],
+ ['Geometrics shapes', 0x1FBE0, 0x1FBEF],
+ ['Segmented digits', 0x1FBF0, 0x1FBF9],
+ ['Terminal graphic character', 0x1FBFA, 0x1FBFA],
+ ]);
+}
+
+
function loadTest(): void {
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
const testData = [];
diff --git a/demo/index.html b/demo/index.html
index cb63b174..4f0a6d0c 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -93,6 +93,7 @@
Styles
+
diff --git a/demo/unicodeTable.ts b/demo/unicodeTable.ts
new file mode 100644
index 00000000..2e254ab6
--- /dev/null
+++ b/demo/unicodeTable.ts
@@ -0,0 +1,162 @@
+///
+
+import { Terminal } from '@xterm/xterm';
+
+export type UnicodeRangeDefinition = [
+ label: string,
+ start: number,
+ end: number,
+]
+
+/**
+ * Write a unicode table to the terminal from start to end code points.
+ *
+ * Beware: Vibe coding ahead
+ */
+export function writeUnicodeTable(term: Terminal, name: string, start: number, end: number, definitions?: UnicodeRangeDefinition[]): void {
+ function bold(text: string) {
+ return '\x1b[1m' + text + '\x1b[22m';
+ }
+ function faint(text: string) {
+ return '\x1b[2m' + text + '\x1b[22m';
+ }
+ // Rotating colors: Red, Green, Yellow, Blue, Magenta, Cyan
+ const colors = [31, 32, 33, 34, 35, 36];
+ function color(text: string, colorIndex: number) {
+ const c = colors[colorIndex % colors.length];
+ return '\x1b[' + c + 'm' + text + '\x1b[39m';
+ }
+
+ term.write('\n\r');
+ term.write('\n\r');
+ term.write(bold(name) + '\n\r');
+ term.write('\n\r');
+ term.write(bold(' 0 1 2 3 4 5 6 7 8 9 A B C D E F') + '\n\r');
+
+ const startRow = Math.floor(start / 16);
+
+ // Build a map of codepoint -> label and colorIndex for start positions
+ const labelStartMap = new Map();
+ // Build a map of codepoint -> colorIndex for all codepoints in each range
+ const codePointColorMap = new Map();
+ let lastDefinitionEnd = start; // Track the last definition's end to stop printing there
+ if (definitions) {
+ for (let i = 0; i < definitions.length; i++) {
+ const [label, labelStart, labelEnd] = definitions[i];
+ labelStartMap.set(labelStart, { label, colorIndex: i });
+ // Map all codepoints in the range to this color
+ for (let cp = labelStart; cp <= labelEnd; cp++) {
+ codePointColorMap.set(cp, i);
+ }
+ if (labelEnd > lastDefinitionEnd) {
+ lastDefinitionEnd = labelEnd;
+ }
+ }
+ }
+
+ const effectiveEnd = definitions ? lastDefinitionEnd : end;
+ const endRow = Math.floor(effectiveEnd / 16);
+
+ for (let row = startRow; row <= endRow; row++) {
+ // Collect labels for this row with their column positions and color
+ const rowLabels: { col: number; label: string; colorIndex: number }[] = [];
+ for (let col = 0; col < 16; col++) {
+ const codePoint = row * 16 + col;
+ const labelInfo = labelStartMap.get(codePoint);
+ if (labelInfo) {
+ rowLabels.push({ col, label: labelInfo.label, colorIndex: labelInfo.colorIndex });
+ }
+ }
+
+ // If labels exist and don't start at column 0, first output chars before first label
+ if (rowLabels.length > 0 && rowLabels[0].col > 0) {
+ const rowHex = row.toString(16).toUpperCase();
+ term.write(bold('U+' + rowHex + 'x '));
+ for (let col = 0; col < rowLabels[0].col; col++) {
+ const codePoint = row * 16 + col;
+ term.write(' ');
+ if (codePoint >= start && codePoint <= end) {
+ const charColorIndex = codePointColorMap.get(codePoint);
+ if (charColorIndex !== undefined) {
+ term.write(color(String.fromCodePoint(codePoint), charColorIndex));
+ } else {
+ term.write(String.fromCodePoint(codePoint));
+ }
+ } else {
+ term.write(' ');
+ }
+ }
+ term.write('\n\r');
+ }
+
+ // If labels exist, render them above the row
+ if (rowLabels.length > 0) {
+ // Render label lines from top to bottom (first label on top, last label closest to row)
+ for (let i = 0; i < rowLabels.length; i++) {
+ const prefix = ' '; // Same width as "U+1FB0x "
+ let line = '';
+ let visualLen = 0; // Track visual length separately from string length (escape codes don't count)
+ for (let col = 0; col < 16; col++) {
+ const colPos = col * 2; // Each char takes 2 positions (char + space)
+ const labelAtCol = rowLabels.findIndex(l => l.col === col);
+ if (labelAtCol === i) {
+ // This is where we show the label
+ const padding = ' '.repeat(colPos - visualLen);
+ line += padding + color('┌' + rowLabels[i].label, rowLabels[i].colorIndex);
+ break;
+ } else if (labelAtCol !== -1 && labelAtCol < i) {
+ // Show vertical line for labels that were already rendered above
+ const padding = ' '.repeat(colPos - visualLen);
+ line += padding + color('│', rowLabels[labelAtCol].colorIndex);
+ visualLen = colPos + 1;
+ }
+ }
+ term.write(faint(prefix + line) + '\n\r');
+ }
+ }
+
+ // Row prefix (e.g., "U+1FB0x ")
+ const rowHex = row.toString(16).toUpperCase();
+ const rowPrefix = 'U+' + rowHex + 'x ';
+ const isSecondPrint = rowLabels.length > 0 && rowLabels[0].col > 0;
+ term.write(isSecondPrint ? ' '.repeat(rowPrefix.length) : bold(rowPrefix));
+
+ // Determine starting column (skip chars already output if we had labels not at col 0)
+ const startCol = isSecondPrint ? rowLabels[0].col : 0;
+
+ // Determine ending column (stop at effectiveEnd on the last row)
+ const endCol = (row === endRow) ? (effectiveEnd % 16) + 1 : 16;
+
+ // Pad for skipped columns
+ for (let col = 0; col < startCol; col++) {
+ term.write(' ');
+ }
+
+ // Characters in this row
+ for (let col = startCol; col < endCol; col++) {
+ const codePoint = row * 16 + col;
+
+ // Check if a label starts here
+ const labelInfo = labelStartMap.get(codePoint);
+ if (labelInfo) {
+ term.write(faint(color('└', labelInfo.colorIndex)));
+ } else {
+ term.write(' ');
+ }
+
+ if (codePoint >= start && codePoint <= effectiveEnd) {
+ // Color the character if it's part of a definition range
+ const charColorIndex = codePointColorMap.get(codePoint);
+ if (charColorIndex !== undefined) {
+ term.write(color(String.fromCodePoint(codePoint), charColorIndex));
+ } else {
+ term.write(String.fromCodePoint(codePoint));
+ }
+ } else {
+ term.write(' ');
+ }
+ }
+
+ term.write('\n\r');
+ }
+}