Merge branch 'master' of https://github.com/xtermjs/xterm.js into linkParser

This commit is contained in:
Jon Bockhorst
2020-02-04 11:40:48 -06:00
120 changed files with 6740 additions and 1637 deletions
+280
View File
@@ -0,0 +1,280 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'browser/Color';
describe('Color', () => {
describe('channels', () => {
describe('toCss', () => {
it('should convert an rgb array to css hex string', () => {
assert.equal(channels.toCss(0x00, 0x00, 0x00), '#000000');
assert.equal(channels.toCss(0x10, 0x10, 0x10), '#101010');
assert.equal(channels.toCss(0x20, 0x20, 0x20), '#202020');
assert.equal(channels.toCss(0x30, 0x30, 0x30), '#303030');
assert.equal(channels.toCss(0x40, 0x40, 0x40), '#404040');
assert.equal(channels.toCss(0x50, 0x50, 0x50), '#505050');
assert.equal(channels.toCss(0x60, 0x60, 0x60), '#606060');
assert.equal(channels.toCss(0x70, 0x70, 0x70), '#707070');
assert.equal(channels.toCss(0x80, 0x80, 0x80), '#808080');
assert.equal(channels.toCss(0x90, 0x90, 0x90), '#909090');
assert.equal(channels.toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');
assert.equal(channels.toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');
assert.equal(channels.toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');
assert.equal(channels.toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');
assert.equal(channels.toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');
assert.equal(channels.toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');
assert.equal(channels.toCss(0xff, 0xff, 0xff), '#ffffff');
});
});
describe('toRgba', () => {
it('should convert an rgb array to an rgba number', () => {
assert.equal(channels.toRgba(0x00, 0x00, 0x00), 0x000000FF);
assert.equal(channels.toRgba(0x10, 0x10, 0x10), 0x101010FF);
assert.equal(channels.toRgba(0x20, 0x20, 0x20), 0x202020FF);
assert.equal(channels.toRgba(0x30, 0x30, 0x30), 0x303030FF);
assert.equal(channels.toRgba(0x40, 0x40, 0x40), 0x404040FF);
assert.equal(channels.toRgba(0x50, 0x50, 0x50), 0x505050FF);
assert.equal(channels.toRgba(0x60, 0x60, 0x60), 0x606060FF);
assert.equal(channels.toRgba(0x70, 0x70, 0x70), 0x707070FF);
assert.equal(channels.toRgba(0x80, 0x80, 0x80), 0x808080FF);
assert.equal(channels.toRgba(0x90, 0x90, 0x90), 0x909090FF);
assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);
assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);
assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);
assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);
assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);
assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);
assert.equal(channels.toRgba(0xff, 0xff, 0xff), 0xffffffFF);
});
it('should convert an rgba array to an rgba number', () => {
assert.equal(channels.toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);
assert.equal(channels.toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);
assert.equal(channels.toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);
assert.equal(channels.toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);
assert.equal(channels.toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);
assert.equal(channels.toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);
assert.equal(channels.toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);
assert.equal(channels.toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);
assert.equal(channels.toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);
assert.equal(channels.toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);
assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);
assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);
assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);
assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);
assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);
assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);
assert.equal(channels.toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);
});
});
});
describe('color', () => {
describe('blend', () => {
it('should blend colors based on the alpha channel', () => {
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });
});
});
describe('opaque', () => {
it('should make the color opaque', () => {
assert.deepEqual(color.opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(color.opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(color.opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(color.opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(color.opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(color.opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(color.opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(color.opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(color.opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(color.opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(color.opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(color.opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(color.opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(color.opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(color.opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(color.opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(color.opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });
});
});
});
describe('css', () => {
describe('toColor', () => {
it('should covert a CSS string to an IColor', () => {
assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(css.toColor('#202020'), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(css.toColor('#303030'), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(css.toColor('#404040'), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(css.toColor('#505050'), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(css.toColor('#606060'), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(css.toColor('#707070'), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(css.toColor('#808080'), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(css.toColor('#909090'), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(css.toColor('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(css.toColor('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(css.toColor('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(css.toColor('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(css.toColor('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(css.toColor('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(css.toColor('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
});
});
});
describe('rgb', () => {
describe('relativeLuminance', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(rgb.relativeLuminance(0x000000), 0);
assert.equal(rgb.relativeLuminance(0x101010).toFixed(4), '0.0052');
assert.equal(rgb.relativeLuminance(0x202020).toFixed(4), '0.0144');
assert.equal(rgb.relativeLuminance(0x303030).toFixed(4), '0.0296');
assert.equal(rgb.relativeLuminance(0x404040).toFixed(4), '0.0513');
assert.equal(rgb.relativeLuminance(0x505050).toFixed(4), '0.0802');
assert.equal(rgb.relativeLuminance(0x606060).toFixed(4), '0.1170');
assert.equal(rgb.relativeLuminance(0x707070).toFixed(4), '0.1620');
assert.equal(rgb.relativeLuminance(0x808080).toFixed(4), '0.2159');
assert.equal(rgb.relativeLuminance(0x909090).toFixed(4), '0.2789');
assert.equal(rgb.relativeLuminance(0xA0A0A0).toFixed(4), '0.3515');
assert.equal(rgb.relativeLuminance(0xB0B0B0).toFixed(4), '0.4342');
assert.equal(rgb.relativeLuminance(0xC0C0C0).toFixed(4), '0.5271');
assert.equal(rgb.relativeLuminance(0xD0D0D0).toFixed(4), '0.6308');
assert.equal(rgb.relativeLuminance(0xE0E0E0).toFixed(4), '0.7454');
assert.equal(rgb.relativeLuminance(0xF0F0F0).toFixed(4), '0.8714');
assert.equal(rgb.relativeLuminance(0xFFFFFF), 1);
});
});
});
describe('rgba', () => {
describe('ensureContrastRatio', () => {
it('should return undefined if the color already meets the contrast ratio (black bg)', () => {
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 1), undefined);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 2), undefined);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 3), undefined);
});
it('should return a color that meets the contrast ratio (black bg)', () => {
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 4), 0x707070ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 5), 0x7f7f7fff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 6), 0x8c8c8cff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 7), 0x989898ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 9), 0xadadadff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 11), 0xbebebeff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 15), 0xdbdbdbff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 18), 0xeeeeeeff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 20), 0xfafafaff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 21), 0xffffffff);
});
it('should return undefined if the color already meets the contrast ratio (white bg)', () => {
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 1), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 2), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 3), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 4), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 5), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 6), undefined);
});
it('should return a color that meets the contrast ratio (white bg)', () => {
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 7), 0x565656ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 8), 0x4d4d4dff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 9), 0x454545ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 10), 0x3e3e3eff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 11), 0x373737ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 12), 0x313131ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 13), 0x313131ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 14), 0x272727ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 15), 0x232323ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 16), 0x1f1f1fff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 17), 0x1b1b1bff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 18), 0x151515ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 19), 0x101010ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 20), 0x080808ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 21), 0x000000ff);
});
});
describe('toChannels', () => {
it('should convert an rgba number to an rgba array', () => {
assert.deepEqual(rgba.toChannels(0x00000000), [0x00, 0x00, 0x00, 0x00]);
assert.deepEqual(rgba.toChannels(0x10101010), [0x10, 0x10, 0x10, 0x10]);
assert.deepEqual(rgba.toChannels(0x20202020), [0x20, 0x20, 0x20, 0x20]);
assert.deepEqual(rgba.toChannels(0x30303030), [0x30, 0x30, 0x30, 0x30]);
assert.deepEqual(rgba.toChannels(0x40404040), [0x40, 0x40, 0x40, 0x40]);
assert.deepEqual(rgba.toChannels(0x50505050), [0x50, 0x50, 0x50, 0x50]);
assert.deepEqual(rgba.toChannels(0x60606060), [0x60, 0x60, 0x60, 0x60]);
assert.deepEqual(rgba.toChannels(0x70707070), [0x70, 0x70, 0x70, 0x70]);
assert.deepEqual(rgba.toChannels(0x80808080), [0x80, 0x80, 0x80, 0x80]);
assert.deepEqual(rgba.toChannels(0x90909090), [0x90, 0x90, 0x90, 0x90]);
assert.deepEqual(rgba.toChannels(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]);
assert.deepEqual(rgba.toChannels(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]);
assert.deepEqual(rgba.toChannels(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]);
assert.deepEqual(rgba.toChannels(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]);
assert.deepEqual(rgba.toChannels(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]);
assert.deepEqual(rgba.toChannels(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]);
assert.deepEqual(rgba.toChannels(0xffffffff), [0xff, 0xff, 0xff, 0xff]);
});
});
});
describe('toPaddedHex', () => {
it('should convert numbers to 2-digit hex values', () => {
assert.equal(toPaddedHex(0x00), '00');
assert.equal(toPaddedHex(0x10), '10');
assert.equal(toPaddedHex(0x20), '20');
assert.equal(toPaddedHex(0x30), '30');
assert.equal(toPaddedHex(0x40), '40');
assert.equal(toPaddedHex(0x50), '50');
assert.equal(toPaddedHex(0x60), '60');
assert.equal(toPaddedHex(0x70), '70');
assert.equal(toPaddedHex(0x80), '80');
assert.equal(toPaddedHex(0x90), '90');
assert.equal(toPaddedHex(0xa0), 'a0');
assert.equal(toPaddedHex(0xb0), 'b0');
assert.equal(toPaddedHex(0xc0), 'c0');
assert.equal(toPaddedHex(0xd0), 'd0');
assert.equal(toPaddedHex(0xe0), 'e0');
assert.equal(toPaddedHex(0xf0), 'f0');
assert.equal(toPaddedHex(0xff), 'ff');
});
});
describe('contrastRatio', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(contrastRatio(0, 0), 1);
assert.equal(contrastRatio(0, 0.5), 11);
assert.equal(contrastRatio(0, 1), 21);
});
it('should work regardless of the parameter order', () => {
assert.equal(contrastRatio(0, 1), 21);
assert.equal(contrastRatio(1, 0), 21);
});
});
});
+206
View File
@@ -0,0 +1,206 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IColor } from 'browser/Types';
/**
* Helper functions where the source type is "channels" (individual color channels as numbers).
*/
export namespace channels {
export function toCss(r: number, g: number, b: number, a?: number): string {
if (a !== undefined) {
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`;
}
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`;
}
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
// >>> 0 forces an unsigned int
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
}
}
/**
* Helper functions where the source type is `IColor`.
*/
export namespace color {
export function blend(bg: IColor, fg: IColor): IColor {
const a = (fg.rgba & 0xFF) / 255;
if (a === 1) {
return {
css: fg.css,
rgba: fg.rgba
};
}
const fgR = (fg.rgba >> 24) & 0xFF;
const fgG = (fg.rgba >> 16) & 0xFF;
const fgB = (fg.rgba >> 8) & 0xFF;
const bgR = (bg.rgba >> 24) & 0xFF;
const bgG = (bg.rgba >> 16) & 0xFF;
const bgB = (bg.rgba >> 8) & 0xFF;
const r = bgR + Math.round((fgR - bgR) * a);
const g = bgG + Math.round((fgG - bgG) * a);
const b = bgB + Math.round((fgB - bgB) * a);
const css = channels.toCss(r, g, b);
const rgba = channels.toRgba(r, g, b);
return { css, rgba };
}
export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined {
const result = rgba.ensureContrastRatio(bg.rgba, fg.rgba, ratio);
if (!result) {
return undefined;
}
return rgba.toColor(
(result >> 24 & 0xFF),
(result >> 16 & 0xFF),
(result >> 8 & 0xFF)
);
}
export function opaque(color: IColor): IColor {
const rgbaColor = (color.rgba | 0xFF) >>> 0;
const [r, g, b] = rgba.toChannels(rgbaColor);
return {
css: channels.toCss(r, g, b),
rgba: rgbaColor
};
}
}
/**
* Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa').
*/
export namespace css {
export function toColor(css: string): IColor {
return {
css,
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
};
}
}
/**
* Helper functions where the source type is "rgb" (number: 0xrrggbb).
*/
export namespace rgb {
/**
* Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
* between two colors.
* @param rgb The color to use.
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
*/
export function relativeLuminance(rgb: number): number {
return relativeLuminance2(
(rgb >> 16) & 0xFF,
(rgb >> 8 ) & 0xFF,
(rgb ) & 0xFF);
}
/**
* Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
* between two colors.
* @param r The red channel (0x00 to 0xFF).
* @param g The green channel (0x00 to 0xFF).
* @param b The blue channel (0x00 to 0xFF).
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
*/
export function relativeLuminance2(r: number, g: number, b: number): number {
const rs = r / 255;
const gs = g / 255;
const bs = b / 255;
const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);
const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);
const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);
return rr * 0.2126 + rg * 0.7152 + rb * 0.0722;
}
}
/**
* Helper functions where the source type is "rgba" (number: 0xrrggbbaa).
*/
export namespace rgba {
export function ensureContrastRatio(bgRgba: number, fgRgba: number, ratio: number): number | undefined {
const bgL = rgb.relativeLuminance(bgRgba >> 8);
const fgL = rgb.relativeLuminance(fgRgba >> 8);
const cr = contrastRatio(bgL, fgL);
if (cr < ratio) {
if (fgL < bgL) {
return reduceLuminance(bgRgba, fgRgba, ratio);
}
return increaseLuminance(bgRgba, fgRgba, ratio);
}
return undefined;
}
export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
// This is a naive but fast approach to reducing luminance as converting to
// HSL and back is expensive
const bgR = (bgRgba >> 24) & 0xFF;
const bgG = (bgRgba >> 16) & 0xFF;
const bgB = (bgRgba >> 8) & 0xFF;
let fgR = (fgRgba >> 24) & 0xFF;
let fgG = (fgRgba >> 16) & 0xFF;
let fgB = (fgRgba >> 8) & 0xFF;
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) {
// Reduce by 10% until the ratio is hit
fgR -= Math.max(0, Math.ceil(fgR * 0.1));
fgG -= Math.max(0, Math.ceil(fgG * 0.1));
fgB -= Math.max(0, Math.ceil(fgB * 0.1));
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
}
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
// This is a naive but fast approach to increasing luminance as converting to
// HSL and back is expensive
const bgR = (bgRgba >> 24) & 0xFF;
const bgG = (bgRgba >> 16) & 0xFF;
const bgB = (bgRgba >> 8) & 0xFF;
let fgR = (fgRgba >> 24) & 0xFF;
let fgG = (fgRgba >> 16) & 0xFF;
let fgB = (fgRgba >> 8) & 0xFF;
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) {
// Increase by 10% until the ratio is hit
fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1));
fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1));
fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1));
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
}
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
export function toChannels(value: number): [number, number, number, number] {
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
}
export function toColor(r: number, g: number, b: number): IColor {
return {
css: channels.toCss(r, g, b),
rgba: channels.toRgba(r, g, b)
};
}
}
export function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
/**
* Gets the contrast ratio between two relative luminance values.
* @param l1 The first relative luminance.
* @param l2 The first relative luminance.
* @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
*/
export function contrastRatio(l1: number, l2: number): number {
if (l1 < l2) {
return (l2 + 0.05) / (l1 + 0.05);
}
return (l1 + 0.05) / (l2 + 0.05);
}
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { ColorContrastCache } from 'browser/ColorContrastCache';
describe('ColorContrastCache', () => {
let cache: ColorContrastCache;
beforeEach(() => {
cache = new ColorContrastCache();
});
it('should save and get color values', () => {
assert.strictEqual(cache.getColor(0x01, 0x00), undefined);
cache.setColor(0x01, 0x01, null);
assert.strictEqual(cache.getColor(0x01, 0x01), null);
cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});
assert.deepEqual(cache.getColor(0x01, 0x02), { css: '#030303', rgba: 0x030303ff});
});
it('should save and get css values', () => {
assert.strictEqual(cache.getCss(0x01, 0x00), undefined);
cache.setCss(0x01, 0x01, null);
assert.strictEqual(cache.getCss(0x01, 0x01), null);
cache.setCss(0x01, 0x02, '#030303');
assert.deepEqual(cache.getCss(0x01, 0x02), '#030303');
});
it('should clear all values on clear', () => {
cache.setColor(0x01, 0x01, null);
cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});
cache.setCss(0x01, 0x01, null);
cache.setCss(0x01, 0x02, '#030303');
cache.clear();
assert.strictEqual(cache.getColor(0x01, 0x01), undefined);
assert.strictEqual(cache.getColor(0x01, 0x02), undefined);
assert.strictEqual(cache.getCss(0x01, 0x01), undefined);
assert.strictEqual(cache.getCss(0x01, 0x02), undefined);
});
});
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IColor, IColorContrastCache } from 'browser/Types';
export class ColorContrastCache implements IColorContrastCache {
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {};
public clear(): void {
this._color = {};
this._rgba = {};
}
public setCss(bg: number, fg: number, value: string | null): void {
if (!this._rgba[bg]) {
this._rgba[bg] = {};
}
this._rgba[bg]![fg] = value;
}
public getCss(bg: number, fg: number): string | null | undefined {
return this._rgba[bg] ? this._rgba[bg]![fg] : undefined;
}
public setColor(bg: number, fg: number, value: IColor | null): void {
if (!this._color[bg]) {
this._color[bg] = {};
}
this._color[bg]![fg] = value;
}
public getColor(bg: number, fg: number): IColor | null | undefined {
return this._color[bg] ? this._color[bg]![fg] : undefined;
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ describe('ColorManager', () => {
describe('constructor', () => {
it('should fill all colors with values', () => {
for (const key of Object.keys(cm.colors)) {
if (key !== 'ansi') {
if (key !== 'ansi' && key !== 'contrastCache') {
// A #rrggbb or rgba(...)
assert.ok((<any>cm.colors)[key].css.length >= 7);
}
+81 -65
View File
@@ -3,16 +3,18 @@
* @license MIT
*/
import { IColorManager, IColor, IColorSet } from 'browser/Types';
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
import { ITheme } from 'common/services/Services';
import { channels, color, css } from 'browser/Color';
import { ColorContrastCache } from 'browser/ColorContrastCache';
const DEFAULT_FOREGROUND = fromHex('#ffffff');
const DEFAULT_BACKGROUND = fromHex('#000000');
const DEFAULT_CURSOR = fromHex('#ffffff');
const DEFAULT_CURSOR_ACCENT = fromHex('#000000');
const DEFAULT_FOREGROUND = css.toColor('#ffffff');
const DEFAULT_BACKGROUND = css.toColor('#000000');
const DEFAULT_CURSOR = css.toColor('#ffffff');
const DEFAULT_CURSOR_ACCENT = css.toColor('#000000');
const DEFAULT_SELECTION = {
css: 'rgba(255, 255, 255, 0.3)',
rgba: 0xFFFFFF77
rgba: 0xFFFFFF4D
};
// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
@@ -20,23 +22,23 @@ const DEFAULT_SELECTION = {
export const DEFAULT_ANSI_COLORS = (() => {
const colors = [
// dark:
fromHex('#2e3436'),
fromHex('#cc0000'),
fromHex('#4e9a06'),
fromHex('#c4a000'),
fromHex('#3465a4'),
fromHex('#75507b'),
fromHex('#06989a'),
fromHex('#d3d7cf'),
css.toColor('#2e3436'),
css.toColor('#cc0000'),
css.toColor('#4e9a06'),
css.toColor('#c4a000'),
css.toColor('#3465a4'),
css.toColor('#75507b'),
css.toColor('#06989a'),
css.toColor('#d3d7cf'),
// bright:
fromHex('#555753'),
fromHex('#ef2929'),
fromHex('#8ae234'),
fromHex('#fce94f'),
fromHex('#729fcf'),
fromHex('#ad7fa8'),
fromHex('#34e2e2'),
fromHex('#eeeeec')
css.toColor('#555753'),
css.toColor('#ef2929'),
css.toColor('#8ae234'),
css.toColor('#fce94f'),
css.toColor('#729fcf'),
css.toColor('#ad7fa8'),
css.toColor('#34e2e2'),
css.toColor('#eeeeec')
];
// Fill in the remaining 240 ANSI colors.
@@ -47,37 +49,23 @@ export const DEFAULT_ANSI_COLORS = (() => {
const g = v[(i / 6) % 6 | 0];
const b = v[i % 6];
colors.push({
css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,
// Use >>> 0 to force a conversion to an unsigned int
rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0
css: channels.toCss(r, g, b),
rgba: channels.toRgba(r, g, b)
});
}
// Generate greys (232-255)
for (let i = 0; i < 24; i++) {
const c = 8 + i * 10;
const ch = toPaddedHex(c);
colors.push({
css: `#${ch}${ch}${ch}`,
rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0
css: channels.toCss(c, c, c),
rgba: channels.toRgba(c, c, c)
});
}
return colors;
})();
function fromHex(css: string): IColor {
return {
css,
rgba: parseInt(css.slice(1), 16) << 8 | 0xFF
};
}
function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
/**
* Manages the source of truth for a terminal's colors.
*/
@@ -85,6 +73,7 @@ export class ColorManager implements IColorManager {
public colors: IColorSet;
private _ctx: CanvasRenderingContext2D;
private _litmusColor: CanvasGradient;
private _contrastCache: IColorContrastCache;
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
@@ -97,16 +86,25 @@ export class ColorManager implements IColorManager {
this._ctx = ctx;
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
this._contrastCache = new ColorContrastCache();
this.colors = {
foreground: DEFAULT_FOREGROUND,
background: DEFAULT_BACKGROUND,
cursor: DEFAULT_CURSOR,
cursorAccent: DEFAULT_CURSOR_ACCENT,
selection: DEFAULT_SELECTION,
ansi: DEFAULT_ANSI_COLORS.slice()
selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
ansi: DEFAULT_ANSI_COLORS.slice(),
contrastCache: this._contrastCache
};
}
public onOptionsChange(key: string): void {
if (key === 'minimumContrastRatio') {
this._contrastCache.clear();
}
}
/**
* Sets the terminal's theme.
* @param theme The theme to use. If a partial theme is provided then default
@@ -118,6 +116,7 @@ export class ColorManager implements IColorManager {
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selection);
this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
@@ -134,6 +133,8 @@ export class ColorManager implements IColorManager {
this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
// Clear our the cache
this._contrastCache.clear();
}
private _parseColor(
@@ -158,33 +159,48 @@ export class ColorManager implements IColorManager {
this._ctx.fillRect(0, 0, 1, 1);
const data = this._ctx.getImageData(0, 0, 1, 1).data;
if (!allowTransparency && data[3] !== 0xFF) {
// Ideally we'd just ignore the alpha channel, but...
//
// Browsers may not give back exactly the same RGB values we put in, because most/all
// convert the color to a pre-multiplied representation. getImageData converts that back to
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
// on their own.
//
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
// into 0x00000000.
//
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
// pixels that have just been set using putImageData() might be returned to an equivalent
// getImageData() as different values."
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
//
// So let's just use the fallback color in this case instead.
console.warn(
`Color: ${css} is using transparency, but allowTransparency is false. ` +
`Using fallback ${fallback.css}.`
);
return fallback;
// Check if the printed color was transparent
if (data[3] !== 0xFF) {
if (!allowTransparency) {
// Ideally we'd just ignore the alpha channel, but...
//
// Browsers may not give back exactly the same RGB values we put in, because most/all
// convert the color to a pre-multiplied representation. getImageData converts that back to
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
// on their own.
//
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
// into 0x00000000.
//
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
// pixels that have just been set using putImageData() might be returned to an equivalent
// getImageData() as different values."
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
//
// So let's just use the fallback color in this case instead.
console.warn(
`Color: ${css} is using transparency, but allowTransparency is false. ` +
`Using fallback ${fallback.css}.`
);
return fallback;
}
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
// the color value has alpha less than 1.0, and the string is the color value in the CSS rgba()
const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component));
const alpha = Math.round(a * 255);
const rgba: number = channels.toRgba(r, g, b, alpha);
return {
rgba,
css: channels.toCss(r, g, b, alpha)
};
}
return {
css,
rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
// if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character
css: this._ctx.fillStyle,
rgba: channels.toRgba(data[0], data[1], data[2], data[3])
};
}
}
+3 -2
View File
@@ -17,12 +17,13 @@ export function addDisposableDomListener(
useCapture?: boolean
): IDisposable {
node.addEventListener(type, handler, useCapture);
let disposed = false;
return {
dispose: () => {
if (!handler) {
// Already disposed
if (!disposed) {
return;
}
disposed = true;
node.removeEventListener(type, handler, useCapture);
}
};
+3 -2
View File
@@ -9,12 +9,13 @@ import { IBufferLine } from 'common/Types';
import { Linkifier } from 'browser/Linkifier';
import { BufferLine } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { MockLogService, MockBufferService } from 'common/TestUtils.test';
import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
import { IBufferService } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
class TestLinkifier extends Linkifier {
constructor(bufferService: IBufferService) {
super(bufferService, new MockLogService());
super(bufferService, new MockLogService(), new MockOptionsService(), new UnicodeService());
Linkifier._timeBeforeLatency = 0;
}
+13 -6
View File
@@ -5,9 +5,8 @@
import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types';
import { IBufferStringIteratorResult } from 'common/buffer/Types';
import { getStringCellWidth } from 'common/CharWidth';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { ILogService, IBufferService } from 'common/services/Services';
import { ILogService, IBufferService, IOptionsService, IUnicodeService } from 'common/services/Services';
/**
* Limit of the unwrapping line expansion (overscan) at the top and bottom
@@ -45,7 +44,9 @@ export class Linkifier implements ILinkifier {
constructor(
protected readonly _bufferService: IBufferService,
private readonly _logService: ILogService
private readonly _logService: ILogService,
private readonly _optionsService: IOptionsService,
private readonly _unicodeService: IUnicodeService
) {
this._rowsToLinkify = {
start: undefined,
@@ -277,8 +278,8 @@ export class Linkifier implements ILinkifier {
if (!this._mouseZoneManager || !this._element) {
return;
}
const width = getStringCellWidth(uri);
// FIXME: get cell length from buffer to avoid mismatch after Unicode version change
const width = this._unicodeService.getStringCellWidth(uri);
const x1 = x % this._bufferService.cols;
const y1 = y + Math.floor(x / this._bufferService.cols);
let x2 = (x1 + width) % this._bufferService.cols;
@@ -297,7 +298,13 @@ export class Linkifier implements ILinkifier {
if (matcher.handler) {
return matcher.handler(e, uri);
}
window.open(uri, '_blank');
const newWindow = window.open();
if (newWindow) {
newWindow.opener = null;
newWindow.location.href = uri;
} else {
console.warn('Opening link blocked as opener could not be cleared');
}
},
() => {
this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
+12
View File
@@ -9,6 +9,7 @@ import { IMouseService, IRenderService } from './services/Services';
export interface IColorManager {
colors: IColorSet;
onOptionsChange(key: string): void;
}
export interface IColor {
@@ -22,7 +23,18 @@ export interface IColorSet {
cursor: IColor;
cursorAccent: IColor;
selection: IColor;
/** The selection blended on top of background. */
selectionOpaque: IColor;
ansi: IColor[];
contrastCache: IColorContrastCache;
}
export interface IColorContrastCache {
clear(): void;
setCss(bg: number, fg: number, value: string | null): void;
getCss(bg: number, fg: number): string | null | undefined;
setColor(bg: number, fg: number, value: IColor | null): void;
getColor(bg: number, fg: number): IColor | null | undefined;
}
export interface IPartialColorSet {
+29 -8
View File
@@ -19,19 +19,40 @@ describe('MoveToCell', () => {
describe('normal buffer', () => {
it('should use the right directional escape sequences', () => {
assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D');
assert.equal(moveToCellSequence(2, 3, bufferService, false), '\x1b[D');
assert.equal(moveToCellSequence(4, 3, bufferService, false), '\x1b[C');
assert.equal(moveToCellSequence(5, 3, bufferService, false), '\x1b[C\x1b[C');
});
it('should ignore the Y value', () => {
assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D');
assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D');
assert.equal(moveToCellSequence(1, 3, bufferService, false), '\x1b[D\x1b[D');
assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[D\x1b[D');
assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[D\x1b[D');
it('should wrap around entire row instead of doing up and down when the Y value differs', () => {
assert.equal(moveToCellSequence(1, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(3, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(4, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(5, 1, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(1, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(2, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(3, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(4, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(5, 2, bufferService, false), '\x1b[D\x1b[D\x1b[D');
assert.equal(moveToCellSequence(1, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(2, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(3, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(4, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(5, 4, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(1, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(2, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(3, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(4, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
assert.equal(moveToCellSequence(5, 5, bufferService, false), '\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C');
});
it('should use the correct character for application cursor', () => {
assert.equal(moveToCellSequence(2, 1, bufferService, false), '\x1b[D');
assert.equal(moveToCellSequence(2, 1, bufferService, true), '\x1bOD');
assert.equal(moveToCellSequence(3, 1, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD');
assert.equal(moveToCellSequence(3, 2, bufferService, true), '\x1bOD\x1bOD\x1bOD\x1bOD\x1bOD');
assert.equal(moveToCellSequence(2, 3, bufferService, true), '\x1bOD');
assert.equal(moveToCellSequence(4, 3, bufferService, true), '\x1bOC');
assert.equal(moveToCellSequence(3, 4, bufferService, true), '\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC');
assert.equal(moveToCellSequence(3, 5, bufferService, true), '\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC\x1bOC');
});
});
+25 -1
View File
@@ -30,7 +30,31 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi
}
// Only move horizontally for the normal buffer
return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor);
let direction;
if (startY === targetY) {
direction = startX > targetX ? Direction.LEFT : Direction.RIGHT;
return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor));
}
direction = startY > targetY ? Direction.LEFT : Direction.RIGHT;
const rowDifference = Math.abs(startY - targetY);
const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) +
(rowDifference - 1) * bufferService.cols + 1 /*wrap around 1 row*/ +
colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService);
return repeat(cellsToMove, sequence(direction, applicationCursor));
}
/**
* Find the number of cols from a row beginning to a col.
*/
function colsFromRowBeginning(currX: number, bufferService: IBufferService): number {
return currX - 1;
}
/**
* Find the number of cols from a col to row end.
*/
function colsFromRowEnd(currX: number, bufferService: IBufferService): number {
return bufferService.cols - currX;
}
/**
+105 -12
View File
@@ -5,16 +5,17 @@
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorSet } from 'browser/Types';
import { IColorSet, IColor } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { channels, color, rgba } from 'browser/Color';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
@@ -170,11 +171,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillLeftLineAtCell(x: number, y: number): void {
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio,
window.devicePixelRatio * width,
this._scaledCellHeight);
}
@@ -262,13 +263,14 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param bold Whether the text is bold.
*/
protected _drawChars(cell: ICellData, x: number, y: number): void {
const contrastColor = this._getContrastColor(cell);
// skip cache right away if we draw in RGB
// Note: to avoid bad runtime JoinedCellData will be skipped
// in the cache handler itself (atlasDidDraw == false) and
// fall through to uncached later down below
if (cell.isFgRGB() || cell.isBgRGB()) {
this._drawUncachedChars(cell, x, y);
if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) {
this._drawUncachedChars(cell, x, y, contrastColor);
return;
}
@@ -282,7 +284,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
}
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8;
fg += drawInBrightColor ? 8 : 0;
this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
@@ -314,21 +316,29 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param x The column to draw at.
* @param y The row to draw at.
*/
private _drawUncachedChars(cell: ICellData, x: number, y: number): void {
private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void {
this._ctx.save();
this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
this._ctx.textBaseline = 'middle';
if (cell.isInverse()) {
if (cell.isBgDefault()) {
this._ctx.fillStyle = this._colors.background.css;
if (fgOverride) {
this._ctx.fillStyle = fgOverride.css;
} else if (cell.isBgDefault()) {
this._ctx.fillStyle = color.opaque(this._colors.background).css;
} else if (cell.isBgRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else {
this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
let bg = cell.getBgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
bg += 8;
}
this._ctx.fillStyle = this._colors.ansi[bg].css;
}
} else {
if (cell.isFgDefault()) {
if (fgOverride) {
this._ctx.fillStyle = fgOverride.css;
} else if (cell.isFgDefault()) {
this._ctx.fillStyle = this._colors.foreground.css;
} else if (cell.isFgRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
@@ -379,5 +389,88 @@ export abstract class BaseRenderLayer implements IRenderLayer {
return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`;
}
private _getContrastColor(cell: CellData): IColor | undefined {
if (this._optionsService.options.minimumContrastRatio === 1) {
return undefined;
}
// Try get from cache first
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
if (adjustedColor !== undefined) {
return adjustedColor || undefined;
}
let fgColor = cell.getFgColor();
let fgColorMode = cell.getFgColorMode();
let bgColor = cell.getBgColor();
let bgColorMode = cell.getBgColorMode();
const isInverse = !!cell.isInverse();
const isBold = !!cell.isInverse();
if (isInverse) {
const temp = fgColor;
fgColor = bgColor;
bgColor = temp;
const temp2 = fgColorMode;
fgColorMode = bgColorMode;
bgColorMode = temp2;
}
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio);
if (!result) {
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
return undefined;
}
const color: IColor = {
css: channels.toCss(
(result >> 24) & 0xFF,
(result >> 16) & 0xFF,
(result >> 8) & 0xFF
),
rgba: result
};
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
return color;
}
private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
return this._colors.ansi[bgColor].rgba;
case Attributes.CM_RGB:
return bgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._colors.foreground.rgba;
}
return this._colors.background.rgba;
}
}
private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) {
fgColor += 8;
}
return this._colors.ansi[fgColor].rgba;
case Attributes.CM_RGB:
return fgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._colors.background.rgba;
}
return this._colors.foreground.rgba;
}
}
}
+1 -1
View File
@@ -209,7 +209,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _renderBarCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this._fillLeftLineAtCell(x, y);
this._fillLeftLineAtCell(x, y, this._optionsService.options.cursorWidth);
this._ctx.restore();
}
+5 -1
View File
@@ -227,7 +227,11 @@ export class TextRenderLayer extends BaseRenderLayer {
} else if (cell.isBgRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else {
this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
let bg = cell.getBgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
bg += 8;
}
this._ctx.fillStyle = this._colors.ansi[bg].css;
}
} else {
if (cell.isFgDefault()) {
@@ -11,6 +11,7 @@ import { LRUMap } from 'browser/renderer/atlas/LRUMap';
import { isFirefox, isSafari } from 'common/Platform';
import { IColor } from 'browser/Types';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { color } from 'browser/Color';
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
@@ -222,7 +223,7 @@ export class DynamicCharAtlas extends BaseCharAtlas {
private _getForegroundColor(glyph: IGlyphIdentifier): IColor {
if (glyph.fg === INVERTED_DEFAULT_COLOR) {
return this._config.colors.background;
return color.opaque(this._config.colors.background);
} else if (glyph.fg < 256) {
// 256 color support
return this._getColorFromAnsiIndex(glyph.fg);
+8 -8
View File
@@ -11,6 +11,7 @@ import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Typ
import { ICharSizeService } from 'browser/services/Services';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { color } from 'browser/Color';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
const ROW_CONTAINER_CLASS = 'xterm-rows';
@@ -80,7 +81,7 @@ export class DomRenderer extends Disposable implements IRenderer {
this._updateDimensions();
this._injectCss();
this._rowFactory = new DomRendererRowFactory(document, this._optionsService);
this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors);
this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._screenElement.appendChild(this._rowContainer);
@@ -176,13 +177,13 @@ export class DomRenderer extends Disposable implements IRenderer {
`}`;
// Blink animation
styles +=
`@keyframes blink_box_shadow {` +
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
` 50% {` +
` box-shadow: none;` +
` }` +
`}`;
styles +=
`@keyframes blink_block {` +
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
` 0% {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
@@ -199,17 +200,17 @@ export class DomRenderer extends Disposable implements IRenderer {
` outline-offset: -1px;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
` animation: blink_box_shadow 1s step-end infinite;` +
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` animation: blink_block 1s step-end infinite;` +
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` +
` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
@@ -234,7 +235,7 @@ export class DomRenderer extends Disposable implements IRenderer {
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
});
styles +=
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this._colors.background.css}; }` +
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
this._themeStyleElement.innerHTML = styles;
@@ -344,7 +345,6 @@ export class DomRenderer extends Disposable implements IRenderer {
// Force a refresh
this._updateDimensions();
this._injectCss();
this._onRequestRefreshRows.fire({ start: 0, end: this._bufferService.rows - 1 });
}
public clear(): void {
@@ -11,6 +11,7 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { MockOptionsService } from 'common/TestUtils.test';
import { css } from 'browser/Color';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
@@ -19,7 +20,30 @@ describe('DomRendererRowFactory', () => {
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }));
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {
background: css.toColor('#010101'),
foreground: css.toColor('#020202'),
ansi: [
// dark:
css.toColor('#2e3436'),
css.toColor('#cc0000'),
css.toColor('#4e9a06'),
css.toColor('#c4a000'),
css.toColor('#3465a4'),
css.toColor('#75507b'),
css.toColor('#06989a'),
css.toColor('#d3d7cf'),
// bright:
css.toColor('#555753'),
css.toColor('#ef2929'),
css.toColor('#8ae234'),
css.toColor('#fce94f'),
css.toColor('#729fcf'),
css.toColor('#ad7fa8'),
css.toColor('#34e2e2'),
css.toColor('#eeeeec')
]
} as any);
lineData = createEmptyLineData(2);
});
@@ -142,7 +166,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
);
});
@@ -153,7 +177,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
);
});
@@ -163,7 +187,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
);
});
@@ -188,7 +212,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="color:rgb(1,2,3);background-color:rgb(4,5,6);">a</span>'
'<span style="color:#010203;background-color:#040506;">a</span>'
);
});
@@ -199,7 +223,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="background-color:rgb(1,2,3);color:rgb(4,5,6);">a</span>'
'<span style="color:#040506;background-color:#010203;">a</span>'
);
});
});
+105 -30
View File
@@ -5,10 +5,11 @@
import { IBufferLine } from 'common/Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services';
import { color, rgba } from 'browser/Color';
import { IColorSet, IColor } from 'browser/Types';
export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim';
@@ -24,11 +25,16 @@ export class DomRendererRowFactory {
private _workCell: CellData = new CellData();
constructor(
private _document: Document,
private _optionsService: IOptionsService
private readonly _document: Document,
private readonly _optionsService: IOptionsService,
private _colors: IColorSet
) {
}
public setColors(colors: IColorSet): void {
this._colors = colors;
}
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
@@ -95,38 +101,107 @@ export class DomRendererRowFactory {
charElement.classList.add(UNDERLINE_CLASS);
}
charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
const swapColor = this._workCell.isInverse();
// fg
if (this._workCell.isFgRGB()) {
let style = charElement.getAttribute('style') || '';
style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`;
charElement.setAttribute('style', style);
} else if (this._workCell.isFgPalette()) {
let fg = this._workCell.getFgColor();
if (this._workCell.isBold() && fg < 8 && !swapColor && this._optionsService.options.drawBoldTextInBrightColors) {
fg += 8;
}
charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`);
} else if (swapColor) {
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
if (this._workCell.isInvisible()) {
charElement.textContent = WHITESPACE_CELL_CHAR;
} else {
charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
}
// bg
if (this._workCell.isBgRGB()) {
let style = charElement.getAttribute('style') || '';
style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`;
charElement.setAttribute('style', style);
} else if (this._workCell.isBgPalette()) {
charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`);
} else if (swapColor) {
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
let fg = this._workCell.getFgColor();
let fgColorMode = this._workCell.getFgColorMode();
let bg = this._workCell.getBgColor();
let bgColorMode = this._workCell.getBgColorMode();
const isInverse = !!this._workCell.isInverse();
if (isInverse) {
const temp = fg;
fg = bg;
bg = temp;
const temp2 = fgColorMode;
fgColorMode = bgColorMode;
bgColorMode = temp2;
}
// Foreground
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) {
fg += 8;
}
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
charElement.classList.add(`xterm-fg-${fg}`);
}
break;
case Attributes.CM_RGB:
const color = rgba.toColor(
(fg >> 16) & 0xFF,
(fg >> 8) & 0xFF,
(fg ) & 0xFF
);
if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
}
break;
case Attributes.CM_DEFAULT:
default:
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) {
if (isInverse) {
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
}
}
}
// Background
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
charElement.classList.add(`xterm-bg-${bg}`);
break;
case Attributes.CM_RGB:
this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
break;
case Attributes.CM_DEFAULT:
default:
if (isInverse) {
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
}
}
fragment.appendChild(charElement);
}
return fragment;
}
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
if (this._optionsService.options.minimumContrastRatio === 1) {
return false;
}
// Try get from cache first
let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
// Calculate and store in cache
if (adjustedColor === undefined) {
adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio);
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
}
if (adjustedColor) {
this._addStyle(element, `color:${adjustedColor.css}`);
return true;
}
return false;
}
private _addStyle(element: HTMLElement, style: string): void {
element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
}
}
function padStart(text: string, padChar: string, length: number): string {
while (text.length < length) {
text = padChar + text;
}
return text;
}
+1
View File
@@ -95,6 +95,7 @@ export class RenderService extends Disposable implements IRenderService {
public changeOptions(): void {
this._renderer.onOptionsChanged();
this.refreshRows(0, this._rowCount - 1);
this._fireOnCanvasResize();
}