mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Implement running man 1FBB2 and 1FBB3
This commit is contained in:
@@ -541,8 +541,8 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
// Terminal graphic characters (1FBB0-1FBB3)
|
||||
'\u{1FBB0}': { type: CustomGlyphDefinitionType.PATH, data: 'M0.1,0.2 L0.1,.8 L.4,.6 L.9,0.6 Z' }, // ARROWHEAD-SHAPED POINTER
|
||||
'\u{1FBB1}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.1,.55 L.35,.85 L.9,.2', type: CustomGlyphVectorType.STROKE } }, // INVERSE CHECK MARK
|
||||
// 1FBB2 LEFT HALF RUNNING MAN
|
||||
// 1FBB3 RIGHT HALF RUNNING MAN
|
||||
'\u{1FBB2}': { type: CustomGlyphDefinitionType.PATH, data: 'M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13' }, // LEFT HALF RUNNING MAN
|
||||
'\u{1FBB3}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6' }, // RIGHT HALF RUNNING MAN
|
||||
|
||||
// Arrows (1FBB4-1FBB8)
|
||||
'\u{1FBB4}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.15,.6 L.5,.4 L.5,.5 L.75,.5 L.75,.2 L.85,.2 L.85,.7 L.5,.7 L.5,.8 Z', type: CustomGlyphVectorType.FILL } }, // INVERSE DOWNWARDS ARROW WITH TIP LEFTWARDS
|
||||
|
||||
@@ -91,6 +91,8 @@ function drawPathDefinitionCharacter(
|
||||
): void {
|
||||
const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);
|
||||
ctx.beginPath();
|
||||
let currentX = 0;
|
||||
let currentY = 0;
|
||||
for (const instruction of instructions.split(' ')) {
|
||||
const type = instruction[0];
|
||||
const args: string[] = instruction.substring(1).split(',');
|
||||
@@ -100,6 +102,20 @@ function drawPathDefinitionCharacter(
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (type === 'A') {
|
||||
// SVG arc: A rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y
|
||||
const rx = parseFloat(args[0]) * deviceCellWidth;
|
||||
const ry = parseFloat(args[1]) * deviceCellHeight;
|
||||
const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
|
||||
const largeArcFlag = parseInt(args[3]);
|
||||
const sweepFlag = parseInt(args[4]);
|
||||
const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
|
||||
const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
|
||||
drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
|
||||
currentX = x;
|
||||
currentY = y;
|
||||
continue;
|
||||
}
|
||||
const translatedArgs = args.map((e, i) => {
|
||||
const val = parseFloat(e);
|
||||
return i % 2 === 0
|
||||
@@ -108,13 +124,94 @@ function drawPathDefinitionCharacter(
|
||||
});
|
||||
if (type === 'M') {
|
||||
ctx.moveTo(translatedArgs[0], translatedArgs[1]);
|
||||
currentX = translatedArgs[0];
|
||||
currentY = translatedArgs[1];
|
||||
} else if (type === 'L') {
|
||||
ctx.lineTo(translatedArgs[0], translatedArgs[1]);
|
||||
currentX = translatedArgs[0];
|
||||
currentY = translatedArgs[1];
|
||||
}
|
||||
}
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts SVG arc parameters to canvas arc/ellipse calls.
|
||||
* Based on the SVG spec's endpoint to center parameterization conversion.
|
||||
*/
|
||||
function drawSvgArc(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x1: number, y1: number,
|
||||
rx: number, ry: number,
|
||||
phi: number,
|
||||
largeArcFlag: number,
|
||||
sweepFlag: number,
|
||||
x2: number, y2: number
|
||||
): void {
|
||||
// Handle degenerate cases
|
||||
if (rx === 0 || ry === 0) {
|
||||
ctx.lineTo(x2, y2);
|
||||
return;
|
||||
}
|
||||
|
||||
rx = Math.abs(rx);
|
||||
ry = Math.abs(ry);
|
||||
|
||||
const cosPhi = Math.cos(phi);
|
||||
const sinPhi = Math.sin(phi);
|
||||
|
||||
// Step 1: Compute (x1', y1')
|
||||
const dx = (x1 - x2) / 2;
|
||||
const dy = (y1 - y2) / 2;
|
||||
const x1p = cosPhi * dx + sinPhi * dy;
|
||||
const y1p = -sinPhi * dx + cosPhi * dy;
|
||||
|
||||
// Step 2: Compute (cx', cy')
|
||||
let rxSq = rx * rx;
|
||||
let rySq = ry * ry;
|
||||
const x1pSq = x1p * x1p;
|
||||
const y1pSq = y1p * y1p;
|
||||
|
||||
// Correct radii if necessary
|
||||
const lambda = x1pSq / rxSq + y1pSq / rySq;
|
||||
if (lambda > 1) {
|
||||
const lambdaSqrt = Math.sqrt(lambda);
|
||||
rx *= lambdaSqrt;
|
||||
ry *= lambdaSqrt;
|
||||
rxSq = rx * rx;
|
||||
rySq = ry * ry;
|
||||
}
|
||||
|
||||
let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
|
||||
if (sq < 0) sq = 0;
|
||||
const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);
|
||||
const cxp = coef * (rx * y1p / ry);
|
||||
const cyp = coef * -(ry * x1p / rx);
|
||||
|
||||
// Step 3: Compute (cx, cy) from (cx', cy')
|
||||
const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
|
||||
const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
|
||||
|
||||
// Step 4: Compute angles
|
||||
const ux = (x1p - cxp) / rx;
|
||||
const uy = (y1p - cyp) / ry;
|
||||
const vx = (-x1p - cxp) / rx;
|
||||
const vy = (-y1p - cyp) / ry;
|
||||
|
||||
const startAngle = Math.atan2(uy, ux);
|
||||
let dTheta = Math.atan2(vy, vx) - startAngle;
|
||||
|
||||
if (sweepFlag === 0 && dTheta > 0) {
|
||||
dTheta -= 2 * Math.PI;
|
||||
} else if (sweepFlag === 1 && dTheta < 0) {
|
||||
dTheta += 2 * Math.PI;
|
||||
}
|
||||
|
||||
const endAngle = startAngle + dTheta;
|
||||
|
||||
ctx.ellipse(cx, cy, rx, ry, phi, startAngle, endAngle, sweepFlag === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a "negative" path where the background color is used to draw the shape on top of a
|
||||
* foreground-filled cell. This creates the appearance of a cutout without using actual
|
||||
|
||||
+3
-2
@@ -835,8 +835,9 @@ function customGlyphAlignmentHandler(): void {
|
||||
term.write('ππππ¬Ώ πππππ𬽠ππππ¬Ό ππ π
π π¨πͺ\n\r');
|
||||
term.write('π₯πππ π£π§ππππ π’ππ π ππ ππ‘ πͺπ¨\n\r');
|
||||
term.write(' π’π π€π π¦π\n\r');
|
||||
|
||||
term.write('\x1b[0mFill tests:\x1b[34m\n\r');
|
||||
term.write('\x1b[0mComposite characters:\x1b[34m\n\r');
|
||||
term.write('\u{1FBB2}\u{1FBB3}\n\r');
|
||||
term.write('\x1b[0mFill tests:\x1b[35m\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) {
|
||||
const batch = fillChars.splice(0, 10);
|
||||
|
||||
Reference in New Issue
Block a user