Merge pull request #5486 from Tyriar/tyriar/powerline_extra

Implement remaining powerline extra symbols
This commit is contained in:
Daniel Imms
2025-12-24 08:23:39 -08:00
committed by GitHub
7 changed files with 601 additions and 23 deletions
File diff suppressed because one or more lines are too long
@@ -484,10 +484,12 @@ function drawPathFunctionCharacter(
} else {
actualInstructions = charDefinition;
}
const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };
for (const instruction of actualInstructions.split(' ')) {
const type = instruction[0];
if (type === 'Z') {
ctx.closePath();
state.lastCommand = type;
continue;
}
const f = svgToCanvasInstructionMap[type];
@@ -499,7 +501,8 @@ function drawPathFunctionCharacter(
if (!args[0] || !args[1]) {
continue;
}
f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio));
f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio), state);
state.lastCommand = type;
}
if (strokeWidth !== undefined) {
ctx.strokeStyle = ctx.fillStyle;
@@ -563,10 +566,12 @@ function drawVectorShape(
// Scale the stroke with DPR and font size
const cssLineWidth = fontSize / 12;
ctx.lineWidth = devicePixelRatio * cssLineWidth;
const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };
for (const instruction of charDefinition.d.split(' ')) {
const type = instruction[0];
if (type === 'Z') {
ctx.closePath();
state.lastCommand = type;
continue;
}
const f = svgToCanvasInstructionMap[type];
@@ -588,7 +593,8 @@ function drawVectorShape(
devicePixelRatio,
(charDefinition.leftPadding ?? 0) * (cssLineWidth / 2),
(charDefinition.rightPadding ?? 0) * (cssLineWidth / 2)
));
), state);
state.lastCommand = type;
}
if (charDefinition.type === CustomGlyphVectorType.STROKE) {
ctx.strokeStyle = ctx.fillStyle;
@@ -603,11 +609,55 @@ function clamp(value: number, max: number, min: number = 0): number {
return Math.max(Math.min(value, max), min);
}
const svgToCanvasInstructionMap: { [index: string]: any } = {
'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]),
'Q': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.quadraticCurveTo(args[0], args[1], args[2], args[3])
interface ISvgPathState {
currentX: number;
currentY: number;
lastControlX: number;
lastControlY: number;
lastCommand: string;
}
const svgToCanvasInstructionMap: { [index: string]: (ctx: CanvasRenderingContext2D, args: number[], state: ISvgPathState) => void } = {
'C': (ctx, args, state) => {
ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
state.lastControlX = args[2];
state.lastControlY = args[3];
state.currentX = args[4];
state.currentY = args[5];
},
'L': (ctx, args, state) => {
ctx.lineTo(args[0], args[1]);
state.lastControlX = state.currentX = args[0];
state.lastControlY = state.currentY = args[1];
},
'M': (ctx, args, state) => {
ctx.moveTo(args[0], args[1]);
state.lastControlX = state.currentX = args[0];
state.lastControlY = state.currentY = args[1];
},
'Q': (ctx, args, state) => {
ctx.quadraticCurveTo(args[0], args[1], args[2], args[3]);
state.lastControlX = args[0];
state.lastControlY = args[1];
state.currentX = args[2];
state.currentY = args[3];
},
'T': (ctx, args, state) => {
let cpX: number;
let cpY: number;
if (state.lastCommand === 'Q' || state.lastCommand === 'T') {
cpX = 2 * state.currentX - state.lastControlX;
cpY = 2 * state.currentY - state.lastControlY;
} else {
cpX = state.currentX;
cpY = state.currentY;
}
ctx.quadraticCurveTo(cpX, cpY, args[0], args[1]);
state.lastControlX = cpX;
state.lastControlY = cpY;
state.currentX = args[0];
state.currentY = args[1];
}
};
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
+457
View File
@@ -0,0 +1,457 @@
/**
* Converts an SVG file as exported by fontforge into the SVG-like format as expected by the custom
* glyph rasterizer.
*
* Usage: node convert_svg_to_custom_glyph.js <svg-file-or-folder>
*/
const fs = require('fs');
const path = require('path');
const input = process.argv[2];
if (!input) {
console.error('Usage: node convert_svg_to_custom_glyph.js <svg-file-or-folder>');
process.exit(1);
}
const inputPath = path.resolve(process.cwd(), input);
const stat = fs.statSync(inputPath);
const files = stat.isDirectory()
? fs.readdirSync(inputPath).filter(f => f.endsWith('.svg')).map(f => path.join(inputPath, f))
: [inputPath];
if (files.length === 0) {
console.error('No SVG files found');
process.exit(1);
}
for (const file of files) {
console.log(`\n${'='.repeat(60)}\nProcessing: ${path.basename(file)}\n${'='.repeat(60)}`);
processFile(file);
}
function processFile(filePath) {
// Get file content
const content = fs.readFileSync(filePath, 'utf8');
// Get viewBox
const viewBoxMatch = content.match(/viewBox="([^"]+)"/);
if (!viewBoxMatch) {
console.error('No viewBox found in SVG');
return;
}
const [minX, minY, width, height] = viewBoxMatch[1].split(/\s+/).map(Number);
console.log(`ViewBox: ${minX} ${minY} ${width} ${height}`);
// Get path `d` property
const pathMatch = content.match(/<path[^>]*\sd="([^"]+)"/);
if (!pathMatch) {
console.error('No path d attribute found in SVG');
return;
}
const originalPath = pathMatch[1].replace(/\s+/g, ' ').trim();
console.log(`\nOriginal path length: ${originalPath.length} chars`);
// Parse path into commands
function parsePath(d) {
const commands = [];
const regex = /([MmLlHhVvCcSsQqTtAaZz])([^MmLlHhVvCcSsQqTtAaZz]*)/g;
let match;
while ((match = regex.exec(d)) !== null) {
const cmd = match[1];
const argsStr = match[2].trim();
const args = argsStr ? argsStr.split(/[\s,]+/).map(Number) : [];
commands.push({ cmd, args });
}
return commands;
}
// Convert relative commands to absolute and expand T/S to Q/C
function toAbsolute(commands) {
const result = [];
let x = 0, y = 0; // Current position
let startX = 0, startY = 0; // Start of current subpath
let lastControlX = 0, lastControlY = 0; // Last control point for T/S
let lastCmd = '';
for (const { cmd, args } of commands) {
const isRelative = cmd === cmd.toLowerCase();
const absCmd = cmd.toUpperCase();
switch (absCmd) {
case 'M': {
// MoveTo: M x y (or m dx dy)
const absArgs = [];
for (let i = 0; i < args.length; i += 2) {
const newX = isRelative ? x + args[i] : args[i];
const newY = isRelative ? y + args[i + 1] : args[i + 1];
absArgs.push(newX, newY);
x = newX;
y = newY;
if (i === 0) {
startX = x;
startY = y;
}
}
lastControlX = x;
lastControlY = y;
result.push({ cmd: 'M', args: absArgs });
break;
}
case 'L': {
// LineTo: L x y (or l dx dy)
const absArgs = [];
for (let i = 0; i < args.length; i += 2) {
const newX = isRelative ? x + args[i] : args[i];
const newY = isRelative ? y + args[i + 1] : args[i + 1];
absArgs.push(newX, newY);
x = newX;
y = newY;
}
lastControlX = x;
lastControlY = y;
result.push({ cmd: 'L', args: absArgs });
break;
}
case 'H': {
// Horizontal LineTo - convert to L
for (let i = 0; i < args.length; i++) {
const newX = isRelative ? x + args[i] : args[i];
result.push({ cmd: 'L', args: [newX, y] });
x = newX;
}
lastControlX = x;
lastControlY = y;
break;
}
case 'V': {
// Vertical LineTo - convert to L
for (let i = 0; i < args.length; i++) {
const newY = isRelative ? y + args[i] : args[i];
result.push({ cmd: 'L', args: [x, newY] });
y = newY;
}
lastControlX = x;
lastControlY = y;
break;
}
case 'C': {
// CurveTo: C x1 y1 x2 y2 x y (or c dx1 dy1 dx2 dy2 dx dy)
const absArgs = [];
for (let i = 0; i < args.length; i += 6) {
const x1 = isRelative ? x + args[i] : args[i];
const y1 = isRelative ? y + args[i + 1] : args[i + 1];
const x2 = isRelative ? x + args[i + 2] : args[i + 2];
const y2 = isRelative ? y + args[i + 3] : args[i + 3];
const newX = isRelative ? x + args[i + 4] : args[i + 4];
const newY = isRelative ? y + args[i + 5] : args[i + 5];
absArgs.push(x1, y1, x2, y2, newX, newY);
lastControlX = x2;
lastControlY = y2;
x = newX;
y = newY;
}
result.push({ cmd: 'C', args: absArgs });
break;
}
case 'S': {
// Smooth CurveTo - expand to C
for (let i = 0; i < args.length; i += 4) {
// Reflect last control point
let x1, y1;
if (lastCmd === 'C' || lastCmd === 'S') {
x1 = 2 * x - lastControlX;
y1 = 2 * y - lastControlY;
} else {
x1 = x;
y1 = y;
}
const x2 = isRelative ? x + args[i] : args[i];
const y2 = isRelative ? y + args[i + 1] : args[i + 1];
const newX = isRelative ? x + args[i + 2] : args[i + 2];
const newY = isRelative ? y + args[i + 3] : args[i + 3];
result.push({ cmd: 'C', args: [x1, y1, x2, y2, newX, newY] });
lastControlX = x2;
lastControlY = y2;
x = newX;
y = newY;
}
break;
}
case 'Q': {
// Quadratic CurveTo: Q x1 y1 x y (or q dx1 dy1 dx dy)
const absArgs = [];
for (let i = 0; i < args.length; i += 4) {
const x1 = isRelative ? x + args[i] : args[i];
const y1 = isRelative ? y + args[i + 1] : args[i + 1];
const newX = isRelative ? x + args[i + 2] : args[i + 2];
const newY = isRelative ? y + args[i + 3] : args[i + 3];
absArgs.push(x1, y1, newX, newY);
lastControlX = x1;
lastControlY = y1;
x = newX;
y = newY;
}
result.push({ cmd: 'Q', args: absArgs });
break;
}
case 'T': {
// Smooth Quadratic CurveTo - keep as T
const absArgs = [];
for (let i = 0; i < args.length; i += 2) {
// Reflect last control point for tracking
let cpX, cpY;
if (lastCmd === 'Q' || lastCmd === 'T') {
cpX = 2 * x - lastControlX;
cpY = 2 * y - lastControlY;
} else {
cpX = x;
cpY = y;
}
const newX = isRelative ? x + args[i] : args[i];
const newY = isRelative ? y + args[i + 1] : args[i + 1];
absArgs.push(newX, newY);
lastControlX = cpX;
lastControlY = cpY;
x = newX;
y = newY;
lastCmd = 'T'; // For chained T commands
}
result.push({ cmd: 'T', args: absArgs });
break;
}
case 'A': {
// Arc: A rx ry x-axis-rotation large-arc-flag sweep-flag x y
const absArgs = [];
for (let i = 0; i < args.length; i += 7) {
const rx = args[i];
const ry = args[i + 1];
const rotation = args[i + 2];
const largeArc = args[i + 3];
const sweep = args[i + 4];
const newX = isRelative ? x + args[i + 5] : args[i + 5];
const newY = isRelative ? y + args[i + 6] : args[i + 6];
absArgs.push(rx, ry, rotation, largeArc, sweep, newX, newY);
x = newX;
y = newY;
}
lastControlX = x;
lastControlY = y;
result.push({ cmd: 'A', args: absArgs });
break;
}
case 'Z': {
// ClosePath
x = startX;
y = startY;
lastControlX = x;
lastControlY = y;
result.push({ cmd: 'Z', args: [] });
break;
}
}
if (absCmd !== 'T') {
lastCmd = absCmd;
}
}
return result;
}
// Scale coordinates to 0-1 range
function scaleToNormalized(commands, minX, minY, width, height) {
function scaleX(val) {
return (val - minX) / width;
}
function scaleY(val) {
return (val - minY) / height;
}
function scaleRx(val) {
return val / width;
}
function scaleRy(val) {
return val / height;
}
const result = [];
for (const { cmd, args } of commands) {
const scaledArgs = [];
switch (cmd) {
case 'M':
case 'L':
case 'T': {
for (let i = 0; i < args.length; i += 2) {
scaledArgs.push(scaleX(args[i]), scaleY(args[i + 1]));
}
break;
}
case 'H': {
for (let i = 0; i < args.length; i++) {
scaledArgs.push(scaleX(args[i]));
}
break;
}
case 'V': {
for (let i = 0; i < args.length; i++) {
scaledArgs.push(scaleY(args[i]));
}
break;
}
case 'C': {
for (let i = 0; i < args.length; i += 6) {
scaledArgs.push(
scaleX(args[i]), scaleY(args[i + 1]),
scaleX(args[i + 2]), scaleY(args[i + 3]),
scaleX(args[i + 4]), scaleY(args[i + 5])
);
}
break;
}
case 'S':
case 'Q': {
for (let i = 0; i < args.length; i += 4) {
scaledArgs.push(
scaleX(args[i]), scaleY(args[i + 1]),
scaleX(args[i + 2]), scaleY(args[i + 3])
);
}
break;
}
case 'A': {
for (let i = 0; i < args.length; i += 7) {
// rx, ry need to be scaled; rotation and flags stay the same
scaledArgs.push(
scaleRx(args[i]), // rx
scaleRy(args[i + 1]), // ry
args[i + 2], // rotation
args[i + 3], // large-arc
args[i + 4], // sweep
scaleX(args[i + 5]), // x
scaleY(args[i + 6]) // y
);
}
break;
}
case 'Z': {
// No args
break;
}
}
result.push({ cmd, args: scaledArgs });
}
return result;
}
// Format number to reasonable precision
function formatNum(n, precision = 4) {
const rounded = Number(n.toFixed(precision));
return String(rounded);
}
// Convert commands back to path string
function commandsToPath(commands) {
return commands.map(({ cmd, args }, i) => {
const prefix = i === 0 ? '' : ' ';
if (args.length === 0) return prefix + cmd;
return prefix + cmd + args.map(a => formatNum(a)).join(',');
}).join('');
}
// Main conversion
const parsed = parsePath(originalPath);
const absolute = toAbsolute(parsed);
// Calculate actual bounding box from path data
function getBoundingBox(commands) {
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
for (const { cmd, args } of commands) {
switch (cmd) {
case 'M':
case 'L':
case 'T': {
for (let i = 0; i < args.length; i += 2) {
minX = Math.min(minX, args[i]);
maxX = Math.max(maxX, args[i]);
minY = Math.min(minY, args[i + 1]);
maxY = Math.max(maxY, args[i + 1]);
}
break;
}
case 'H': {
for (let i = 0; i < args.length; i++) {
minX = Math.min(minX, args[i]);
maxX = Math.max(maxX, args[i]);
}
break;
}
case 'V': {
for (let i = 0; i < args.length; i++) {
minY = Math.min(minY, args[i]);
maxY = Math.max(maxY, args[i]);
}
break;
}
case 'C': {
for (let i = 0; i < args.length; i += 6) {
// Include control points and endpoint
minX = Math.min(minX, args[i], args[i + 2], args[i + 4]);
maxX = Math.max(maxX, args[i], args[i + 2], args[i + 4]);
minY = Math.min(minY, args[i + 1], args[i + 3], args[i + 5]);
maxY = Math.max(maxY, args[i + 1], args[i + 3], args[i + 5]);
}
break;
}
case 'S':
case 'Q': {
for (let i = 0; i < args.length; i += 4) {
minX = Math.min(minX, args[i], args[i + 2]);
maxX = Math.max(maxX, args[i], args[i + 2]);
minY = Math.min(minY, args[i + 1], args[i + 3]);
maxY = Math.max(maxY, args[i + 1], args[i + 3]);
}
break;
}
case 'A': {
for (let i = 0; i < args.length; i += 7) {
minX = Math.min(minX, args[i + 5]);
maxX = Math.max(maxX, args[i + 5]);
minY = Math.min(minY, args[i + 6]);
maxY = Math.max(maxY, args[i + 6]);
}
break;
}
}
}
return { minX, minY, width: maxX - minX, height: maxY - minY };
}
const bbox = getBoundingBox(absolute);
console.log(`Path bounding box: x=${bbox.minX}, y=${bbox.minY}, w=${bbox.width}, h=${bbox.height}`);
// Use path bounding box for normalization
const normalized = scaleToNormalized(absolute, bbox.minX, bbox.minY, bbox.width, bbox.height);
const result = commandsToPath(normalized);
console.log(`\nConverted path (${result.length} chars):\n`);
console.log(result);
console.log(`\n\nFor CustomGlyphDefinitions.ts:\n`);
console.log(`'\\u{E0C0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: '${result}', type: CustomGlyphVectorType.FILL } },`);
// Write output file
const ext = path.extname(filePath);
const outputPath = filePath.replace(ext, `_output${ext}`);
const svgOutput = `<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1 1">
<path fill="currentColor" d="${result}" />
</svg>
`;
fs.writeFileSync(outputPath, svgOutput, 'utf8');
console.log(`\nOutput written to: ${outputPath}`);
}
+23 -3
View File
@@ -795,11 +795,13 @@ function customGlyphAlignmentHandler(): void {
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤\n\r');
term.write('└─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘\n\r');
term.write('\n\r');
term.write('Other:\n\r');
term.write('╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈\n\r');
term.write('│ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉\n\r');
term.write('╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋\n\r');
term.write('\n\r');
term.write('All box drawing characters:\n\r');
term.write('─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏\n\r');
term.write('┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟\n\r');
@@ -809,6 +811,7 @@ function customGlyphAlignmentHandler(): void {
term.write('═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟\n\r');
term.write('╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯\n\r');
term.write('╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿\n\r');
term.write('Box drawing alignment tests:\x1b[31m █\n\r');
term.write(' ▉\n\r');
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
@@ -827,6 +830,7 @@ function customGlyphAlignmentHandler(): void {
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
term.write('\x1b[0mSmooth mosaic terminal graphic characters alignment tests:\x1b[33m\n\r');
term.write(' 🭇🬼 🭈🬽 🭉🬾 🭊🬿 🭋🭀 🭁🭌 🭂🭍 🭃🭎 🭄🭏 🭅🭐 🭆🭑 🭨🭪 🭩 🭯 🭮🭬\n\r');
term.write(' 🭢🭗 🭣🭘 🭤🭙 🭥🭚 🭦🭛 🭒🭝 🭓🭞 🭔🭟 🭕🭠 🭖🭡 🭧🭜 🭫 🭭\n\r');
@@ -834,20 +838,24 @@ function customGlyphAlignmentHandler(): void {
term.write(' 🭊🭁🭌🬿 🭈🭆🭂🭍🭑🬽 🭇🭄🭏🬼 🭃🭎 🭅🭐 🭨🭪\n\r');
term.write(' 🭥🭒🭝🭚 🭣🭧🭓🭞🭜🭘 🭢🭕🭠🭗 🭔🭟 🭖🭡 🭪🭨\n\r');
term.write(' 🭢🭗 🭤🭙 🭦🭛\n\r');
term.write('\x1b[0mCharacter cell diagonals (1FBA0-1FBAE) alignment tests:\x1b[34m\n\r');
term.write(' \u{1FBA3}\u{1FBA7}\u{1FBA2} \u{1FBA3}\u{1FBA8}\u{1FBA0} \u{1FBAD}\u{1FBA2} \u{1FBA3}\u{1FBAC} \u{1FBAE}\n\r');
term.write(' \u{1FBA3}\u{1FBA0} \u{1FBA1}\u{1FBA2} \u{1FBA1}\u{1FBA9}\u{1FBA2} \u{1FBA1}\u{1FBAA} \u{1FBAB}\u{1FBA0}\n\r');
term.write(' \u{1FBA4} \u{1FBA5}\n\r');
term.write(' \u{1FBA1}\u{1FBA2} \u{1FBA3}\u{1FBA0}\n\r');
term.write(' \u{1FBA1}\u{1FBA6}\u{1FBA0}\n\r');
term.write('\x1b[0mCharacter cell diagonals (1FBD0-1FBDF) alignment tests:\x1b[34m\n\r');
term.write(' \u{1FBD6}\u{1FBD4} \u{1FBD0}\u{1FBD1}\u{1FBD2}\u{1FBD3} \u{1FBDA} \u{1FBD9}\u{1FBDB} \u{1FBDE} \u{1FBDD}\u{1FBDF}\n\r');
term.write(' \u{1FBD7}\u{1FBD5} \u{1FBD2}\u{1FBD3}\u{1FBD0}\u{1FBD1} \u{1FBD8} \u{1FBDC}\n\r');
term.write(' \u{1FBD4}\u{1FBD6}\n\r');
term.write(' \u{1FBD5}\u{1FBD7}\n\r');
term.write('');
term.write('\x1b[0mComposite terminal graphics characters:\x1b[35m\n\r');
term.write('\u{1FBB2}\u{1FBB3} \u{1FBB9}\u{1FBBA} \u{1FBC1}\u{1FBC2}\u{1FBC3}\n\r');
term.write('\x1b[0mFill tests:\x1b[36m\n\r');
const fillChars = ['\u{2591}', '\u{2592}', '\u{2593}', '\u{1FB8C}', '\u{1FB8D}', '\u{1FB8E}', '\u{1FB8F}', '\u{1FB90}', '\u{1FB91}', '\u{1FB92}', '\u{1FB94}', '\u{1FB95}', '\u{1FB96}', '\u{1FB97}', '\u{1FB98}', '\u{1FB99}'];
while (fillChars.length > 0) {
@@ -865,7 +873,19 @@ function customGlyphAlignmentHandler(): void {
}
}
term.write('\x1b[0mPowerline alignment tests:\n\r');
const powerlineLeftChars = ['\u{E0B2}', '\u{E0B3}', '\u{E0B6}', '\u{E0B7}', '\u{E0BA}', '\u{E0BB}', '\u{E0BE}', '\u{E0BF}', '\u{E0C2}', '\u{E0C3}', '\u{E0C5}', '\u{E0C7}', '\u{E0CA}', '\u{E0D4}'];
const powerlineRightChars = ['\u{E0B0}', '\u{E0B1}', '\u{E0B4}', '\u{E0B5}', '\u{E0B8}', '\u{E0B9}', '\u{E0BC}', '\u{E0BD}', '\u{E0C0}', '\u{E0C1}', '\u{E0C4}', '\u{E0C6}', '\u{E0C8}', '\u{E0D2}', '\u{E0CC}', '\u{E0CD}', '\u{E0CE}', '\u{E0CF}', '\u{E0D0}', '\u{E0D1}'];
for (const char of powerlineLeftChars) {
term.write(`\x1b[31m${char}\x1b[0;41m \x1b[0m `);
}
term.write('\n\r');
for (const char of powerlineRightChars) {
term.write(`\x1b[41m \x1b[0;31m${char}\x1b[0m `);
}
term.write('\x1b[0m');
term.write('\n\r');
window.scrollTo(0, 0);
}
@@ -901,11 +921,11 @@ function customGlyphRangesHandler(): void {
['Braille patterns', 0x2800, 0x28FF],
]);
// Powerline Symbols
// Range: E0A0E0BF
// Range: E0A0E0D4
// https://github.com/ryanoasis/nerd-fonts
writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0BF, [
writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0D4, [
['Powerline Symbols', 0xE0A0, 0xE0B3, [0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF]],
['Powerline Extra Symbols', 0xE0B4, 0xE0BF],
['Powerline Extra Symbols', 0xE0B4, 0xE0D4, [0xE0C9, 0xE0CB, 0xE0D3]],
]);
// Symbols for Legacy Computing
// Range: 1FB001FBFF
+39 -11
View File
@@ -218,22 +218,50 @@ export function writeUnicodeTable(term: Terminal, name: string, start: number, e
term.write('\n\r');
// Render reserved labels that appear after the first label (below the row)
// Only show one label pointing to the first reserved item when there are multiple
// Show one label per non-contiguous reserved range
const lateReserved = rowLabels.length > 0
? rowReserved.filter(r => r.col >= rowLabels[0].col)
: rowReserved;
if (lateReserved.length > 0) {
const prefix = ' '.repeat(8);
const firstReserved = lateReserved[0];
const colPos = firstReserved.col * 2 + 1;
const padding = ' '.repeat(colPos);
let line: string;
if (firstReserved.colorIndex >= 0) {
line = padding + color('└<reserved>', firstReserved.colorIndex);
} else {
line = padding + '└<reserved>';
// Group contiguous reserved ranges
const reservedGroups: { startCol: number, colorIndex: number }[] = [];
for (let i = 0; i < lateReserved.length; i++) {
const curr = lateReserved[i];
const prev = lateReserved[i - 1];
// Start a new group if not contiguous (gap of more than 1 column)
if (i === 0 || curr.col > prev.col + 1) {
reservedGroups.push({ startCol: curr.col, colorIndex: curr.colorIndex });
}
}
// Render from bottom to top (last group at bottom with └, earlier groups with │)
for (let i = reservedGroups.length - 1; i >= 0; i--) {
const prefix = ' '.repeat(8);
let line = '';
let visualLen = 0;
for (let g = 0; g <= i; g++) {
const group = reservedGroups[g];
const colPos = group.startCol * 2 + 1;
const padding = ' '.repeat(colPos - visualLen);
if (g === i) {
// This is the label for this line
if (group.colorIndex >= 0) {
line += padding + color('└<reserved>', group.colorIndex);
} else {
line += padding + '└<reserved>';
}
} else {
// Vertical connector for groups below
if (group.colorIndex >= 0) {
line += padding + color('│', group.colorIndex);
} else {
line += padding + '│';
}
visualLen = colPos + 1;
}
}
term.write(faint(prefix + line) + '\n\r');
}
term.write(faint(prefix + line) + '\n\r');
}
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ declare module '@xterm/headless' {
* - Box Drawing (U+2500-U+257F)
* - Box Elements (U+2580-U+259F)
* - Braille Patterns (U+2800-U+28FF)
* - Powerline Symbols (U+E0A0U+E0BF)
* - Powerline Symbols (U+E0A0U+E0D4)
* - Symbols for Legacy Computing (U+1FB00U+1FBFF)
*
* This will typically result in better rendering with continuous lines,
+1 -1
View File
@@ -84,7 +84,7 @@ declare module '@xterm/xterm' {
* - Box Drawing (U+2500-U+257F)
* - Box Elements (U+2580-U+259F)
* - Braille Patterns (U+2800-U+28FF)
* - Powerline Symbols (U+E0A0U+E0BF)
* - Powerline Symbols (U+E0A0U+E0D4)
* - Symbols for Legacy Computing (U+1FB00U+1FBFF)
*
* This will typically result in better rendering with continuous lines,