mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Add support for SGR 53, 55 overline
Specced in ECMA-48
This commit is contained in:
@@ -170,6 +170,16 @@
|
||||
.xterm-underline-4 { text-decoration: dotted underline; }
|
||||
.xterm-underline-5 { text-decoration: dashed underline; }
|
||||
|
||||
.xterm-overline {
|
||||
text-decoration: overline;
|
||||
}
|
||||
|
||||
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
|
||||
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
|
||||
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
|
||||
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
|
||||
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
|
||||
|
||||
.xterm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
+5
-2
@@ -965,7 +965,9 @@ function sgrTest(): void {
|
||||
{ ps: 45, name: 'Background Magenta' },
|
||||
{ ps: 46, name: 'Background Cyan' },
|
||||
{ ps: 47, name: 'Background White' },
|
||||
{ ps: 49, name: 'Background default' }
|
||||
{ ps: 49, name: 'Background default' },
|
||||
{ ps: 53, name: 'Overlined' },
|
||||
{ ps: 55, name: 'Not overlined' }
|
||||
];
|
||||
const maxNameLength = entries.reduce<number>((p, c) => Math.max(c.name.length, p), 0);
|
||||
for (const e of entries) {
|
||||
@@ -977,7 +979,8 @@ function sgrTest(): void {
|
||||
}
|
||||
const comboEntries: { ps: number[] }[] = [
|
||||
{ ps: [1, 2, 3, 4, 5, 6, 7, 9] },
|
||||
{ ps: [2, 41] }
|
||||
{ ps: [2, 41] },
|
||||
{ ps: [4, 53] }
|
||||
];
|
||||
term.write('\n\n\r');
|
||||
term.writeln(`Combinations`);
|
||||
|
||||
@@ -167,6 +167,16 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should add class for overline', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE;
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20, EMPTY_ELEM_MAPPING);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-overline">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add class for strikethrough', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH;
|
||||
|
||||
@@ -19,6 +19,7 @@ export const BOLD_CLASS = 'xterm-bold';
|
||||
export const DIM_CLASS = 'xterm-dim';
|
||||
export const ITALIC_CLASS = 'xterm-italic';
|
||||
export const UNDERLINE_CLASS = 'xterm-underline';
|
||||
export const OVERLINE_CLASS = 'xterm-overline';
|
||||
export const STRIKETHROUGH_CLASS = 'xterm-strikethrough';
|
||||
export const CURSOR_CLASS = 'xterm-cursor';
|
||||
export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';
|
||||
@@ -186,6 +187,13 @@ export class DomRendererRowFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.isOverline()) {
|
||||
charElement.classList.add(OVERLINE_CLASS);
|
||||
if (charElement.textContent === ' ') {
|
||||
charElement.textContent = '\xa0'; // =
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.isStrikethrough()) {
|
||||
charElement.classList.add(STRIKETHROUGH_CLASS);
|
||||
}
|
||||
|
||||
@@ -455,6 +455,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
const italic = !!this._workAttributeData.isItalic();
|
||||
const underline = !!this._workAttributeData.isUnderline();
|
||||
const strikethrough = !!this._workAttributeData.isStrikethrough();
|
||||
const overline = !!this._workAttributeData.isOverline();
|
||||
let fgColor = this._workAttributeData.getFgColor();
|
||||
let fgColorMode = this._workAttributeData.getFgColorMode();
|
||||
let bgColor = this._workAttributeData.getBgColor();
|
||||
@@ -638,12 +639,83 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
// Overline
|
||||
if (overline) {
|
||||
this._tmpCtx.save();
|
||||
const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 15));
|
||||
// When the line width is odd, draw at a 0.5 position
|
||||
const yOffset = lineWidth % 2 === 1 ? 0.5 : 0;
|
||||
this._tmpCtx.lineWidth = lineWidth;
|
||||
|
||||
// Overline color
|
||||
if (this._workAttributeData.isUnderlineColorDefault()) {
|
||||
this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle;
|
||||
} else if (this._workAttributeData.isUnderlineColorRGB()) {
|
||||
enableClearThresholdCheck = false;
|
||||
this._tmpCtx.strokeStyle = `rgb(${AttributeData.toColorRGB(this._workAttributeData.getUnderlineColor()).join(',')})`;
|
||||
} else {
|
||||
enableClearThresholdCheck = false;
|
||||
let fg = this._workAttributeData.getUnderlineColor();
|
||||
if (this._config.drawBoldTextInBrightColors && this._workAttributeData.isBold() && fg < 8) {
|
||||
fg += 8;
|
||||
}
|
||||
this._tmpCtx.strokeStyle = this._getColorFromAnsiIndex(fg).css;
|
||||
}
|
||||
|
||||
// Overline style/stroke
|
||||
this._tmpCtx.beginPath();
|
||||
const xLeft = padding;
|
||||
const yTop = padding + yOffset;
|
||||
const yBot = Math.ceil(padding + lineWidth * 2) + yOffset;
|
||||
|
||||
for (let i = 0; i < chWidth; i++) {
|
||||
this._tmpCtx.save();
|
||||
const xChLeft = xLeft + i * this._config.deviceCellWidth;
|
||||
const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth;
|
||||
this._tmpCtx.moveTo(xChLeft, yTop);
|
||||
this._tmpCtx.lineTo(xChRight, yTop);
|
||||
this._tmpCtx.stroke();
|
||||
this._tmpCtx.restore();
|
||||
}
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// Draw stroke in the background color for non custom characters in order to give an outline
|
||||
// between the text and the underline. Only do this when font size is >= 12 as the underline
|
||||
// looks odd when the font size is too small
|
||||
if (!customGlyph && this._config.fontSize >= 12) {
|
||||
// This only works when transparency is disabled because it's not clear how to clear stroked
|
||||
// text
|
||||
if (!this._config.allowTransparency && chars !== ' ') {
|
||||
// Measure the text, only draw the stroke if there is a descent beyond an alphabetic text
|
||||
// baseline
|
||||
this._tmpCtx.save();
|
||||
this._tmpCtx.textBaseline = 'alphabetic';
|
||||
const metrics = this._tmpCtx.measureText(chars);
|
||||
this._tmpCtx.restore();
|
||||
if ('actualBoundingBoxDescent' in metrics && metrics.actualBoundingBoxDescent > 0) {
|
||||
// This translates to 1/2 the line width in either direction
|
||||
this._tmpCtx.save();
|
||||
// Clip the region to only draw in valid pixels near the underline to avoid a slight
|
||||
// outline around the whole glyph, as well as additional pixels in the glyph at the top
|
||||
// which would increase GPU memory demands
|
||||
const clipRegion = new Path2D();
|
||||
clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth * chWidth, yBot - yTop + Math.ceil(lineWidth / 2));
|
||||
this._tmpCtx.clip(clipRegion);
|
||||
this._tmpCtx.lineWidth = this._config.devicePixelRatio * 3;
|
||||
this._tmpCtx.strokeStyle = backgroundColor.css;
|
||||
this._tmpCtx.strokeText(chars, padding, padding + this._config.deviceCharHeight);
|
||||
this._tmpCtx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
if (!customGlyph) {
|
||||
this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight);
|
||||
}
|
||||
|
||||
// If this charcater is underscore and beyond the cell bounds, shift it up until it is visible
|
||||
// If this character is underscore and beyond the cell bounds, shift it up until it is visible
|
||||
// even on the bottom row, try for a maximum of 5 pixels.
|
||||
if (chars === '_' && !this._config.allowTransparency) {
|
||||
let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck);
|
||||
|
||||
@@ -2551,6 +2551,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
} else if (p === 38 || p === 48 || p === 58) {
|
||||
// fg color 256 and RGB
|
||||
i += this._extractColor(params, i, attr);
|
||||
} else if (p === 53) {
|
||||
// overline
|
||||
attr.bg |= BgFlags.OVERLINE;
|
||||
} else if (p === 55) {
|
||||
// not overline
|
||||
attr.bg &= ~BgFlags.OVERLINE;
|
||||
} else if (p === 59) {
|
||||
attr.extended = attr.extended.clone();
|
||||
attr.extended.underlineColor = -1;
|
||||
|
||||
Vendored
+1
@@ -165,6 +165,7 @@ export interface IAttributeData {
|
||||
isDim(): number;
|
||||
isStrikethrough(): number;
|
||||
isProtected(): number;
|
||||
isOverline(): number;
|
||||
|
||||
/**
|
||||
* The color mode of the foreground color which determines how to decode {@link getFgColor},
|
||||
|
||||
@@ -47,6 +47,7 @@ export class AttributeData implements IAttributeData {
|
||||
public isDim(): number { return this.bg & BgFlags.DIM; }
|
||||
public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; }
|
||||
public isProtected(): number { return this.bg & BgFlags.PROTECTED; }
|
||||
public isOverline(): number { return this.bg & BgFlags.OVERLINE; }
|
||||
|
||||
// color modes
|
||||
public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }
|
||||
|
||||
@@ -128,7 +128,8 @@ export const enum BgFlags {
|
||||
ITALIC = 0x4000000,
|
||||
DIM = 0x8000000,
|
||||
HAS_EXTENDED = 0x10000000,
|
||||
PROTECTED = 0x20000000
|
||||
PROTECTED = 0x20000000,
|
||||
OVERLINE = 0x40000000
|
||||
}
|
||||
|
||||
export const enum ExtFlags {
|
||||
|
||||
Reference in New Issue
Block a user