Merge branch 'master' into fix-caps-lock-ime
This commit is contained in:
@@ -1,331 +0,0 @@
|
||||
/**
|
||||
* 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('isOpaque', () => {
|
||||
it('should return true for opaque colors', () => {
|
||||
assert.ok(color.isOpaque(css.toColor('#000000')));
|
||||
assert.ok(color.isOpaque(css.toColor('#000000ff')));
|
||||
assert.ok(color.isOpaque(css.toColor('#808080')));
|
||||
assert.ok(color.isOpaque(css.toColor('#808080ff')));
|
||||
assert.ok(color.isOpaque(css.toColor('#ffffff')));
|
||||
assert.ok(color.isOpaque(css.toColor('#ffffffff')));
|
||||
});
|
||||
it('should return false for transparent colors', () => {
|
||||
assert.ok(!color.isOpaque(css.toColor('#00000000')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#00000080')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#000000fe')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#80808000')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#80808080')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#808080fe')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#ffffff00')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#ffffff80')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#fffffffe')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('opacity', () => {
|
||||
it('should make the color transparent', () => {
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0), { css: '#00000000', rgba: 0x00000000 });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0.25), { css: '#00000040', rgba: 0x00000040 });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0.5), { css: '#00000080', rgba: 0x00000080 });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0.75), { css: '#000000bf', rgba: 0x000000bf });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 1), { css: '#000000ff', rgba: 0x000000ff });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('css', () => {
|
||||
describe('toColor', () => {
|
||||
it('should convert the #rrggbb format 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 });
|
||||
});
|
||||
it('should convert the #rrggbbaa format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('#00000000'), { css: '#00000000', rgba: 0x00000000 });
|
||||
assert.deepEqual(css.toColor('#10101010'), { css: '#10101010', rgba: 0x10101010 });
|
||||
assert.deepEqual(css.toColor('#20202020'), { css: '#20202020', rgba: 0x20202020 });
|
||||
assert.deepEqual(css.toColor('#30303030'), { css: '#30303030', rgba: 0x30303030 });
|
||||
assert.deepEqual(css.toColor('#40404040'), { css: '#40404040', rgba: 0x40404040 });
|
||||
assert.deepEqual(css.toColor('#50505050'), { css: '#50505050', rgba: 0x50505050 });
|
||||
assert.deepEqual(css.toColor('#60606060'), { css: '#60606060', rgba: 0x60606060 });
|
||||
assert.deepEqual(css.toColor('#70707070'), { css: '#70707070', rgba: 0x70707070 });
|
||||
assert.deepEqual(css.toColor('#80808080'), { css: '#80808080', rgba: 0x80808080 });
|
||||
assert.deepEqual(css.toColor('#90909090'), { css: '#90909090', rgba: 0x90909090 });
|
||||
assert.deepEqual(css.toColor('#a0a0a0a0'), { css: '#a0a0a0a0', rgba: 0xa0a0a0a0 });
|
||||
assert.deepEqual(css.toColor('#b0b0b0b0'), { css: '#b0b0b0b0', rgba: 0xb0b0b0b0 });
|
||||
assert.deepEqual(css.toColor('#c0c0c0c0'), { css: '#c0c0c0c0', rgba: 0xc0c0c0c0 });
|
||||
assert.deepEqual(css.toColor('#d0d0d0d0'), { css: '#d0d0d0d0', rgba: 0xd0d0d0d0 });
|
||||
assert.deepEqual(css.toColor('#e0e0e0e0'), { css: '#e0e0e0e0', rgba: 0xe0e0e0e0 });
|
||||
assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 });
|
||||
assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,236 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColorRGB } from 'common/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 {
|
||||
// Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
|
||||
// on LE systems, before it can be used for direct 32-bit buffer writes.
|
||||
// >>> 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 isOpaque(color: IColor): boolean {
|
||||
return (color.rgba & 0xFF) === 0xFF;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
export function opacity(color: IColor, opacity: number): IColor {
|
||||
const a = Math.round(opacity * 0xFF);
|
||||
const [r, g, b] = rgba.toChannels(color.rgba);
|
||||
return {
|
||||
css: channels.toCss(r, g, b, a),
|
||||
rgba: channels.toRgba(r, g, b, a)
|
||||
};
|
||||
}
|
||||
|
||||
export function toColorRGB(color: IColor): IColorRGB {
|
||||
return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa').
|
||||
*/
|
||||
export namespace css {
|
||||
export function toColor(css: string): IColor {
|
||||
switch (css.length) {
|
||||
case 7: // #rrggbb
|
||||
return {
|
||||
css,
|
||||
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
|
||||
};
|
||||
case 9: // #rrggbbaa
|
||||
return {
|
||||
css,
|
||||
rgba: parseInt(css.slice(1), 16) >>> 0
|
||||
};
|
||||
}
|
||||
throw new Error('css.toColor: Unsupported css format');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// FIXME: Move this to channels NS?
|
||||
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);
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
|
||||
export class ColorContrastCache implements IColorContrastCache {
|
||||
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { ITheme } from 'common/services/Services';
|
||||
import { channels, color, css } from 'browser/Color';
|
||||
import { channels, color, css } from 'common/Color';
|
||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
||||
import { ColorIndex } from 'common/Types';
|
||||
import { ColorIndex, IColor } from 'common/Types';
|
||||
|
||||
|
||||
interface IRestoreColorSet {
|
||||
|
||||
@@ -52,11 +52,11 @@ import { MouseService } from 'browser/services/MouseService';
|
||||
import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { color, rgba } from 'common/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
import { toRgbString } from 'common/input/XParseColor';
|
||||
import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer';
|
||||
import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer';
|
||||
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
|
||||
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
|
||||
import { DecorationService } from 'common/services/DecorationService';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
|
||||
@@ -1367,6 +1367,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._setup();
|
||||
super.reset();
|
||||
this._selectionService?.reset();
|
||||
this._decorationService.reset();
|
||||
|
||||
// reattach
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
|
||||
Vendored
+1
-7
@@ -5,11 +5,10 @@
|
||||
|
||||
import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
|
||||
import { ICoreTerminal, CharData, ITerminalOptions, IColor } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
|
||||
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
|
||||
element: HTMLElement | undefined;
|
||||
@@ -113,11 +112,6 @@ export interface IColorManager {
|
||||
onOptionsChange(key: string): void;
|
||||
}
|
||||
|
||||
export interface IColor {
|
||||
css: string;
|
||||
rgba: number; // 32-bit int with rgba in each byte
|
||||
}
|
||||
|
||||
export interface IColorSet {
|
||||
foreground: IColor;
|
||||
background: IColor;
|
||||
|
||||
+16
-14
@@ -60,23 +60,13 @@ export class BufferDecorationRenderer extends Disposable {
|
||||
}
|
||||
|
||||
private _renderDecoration(decoration: IInternalDecoration): void {
|
||||
let element = this._decorationElements.get(decoration);
|
||||
if (!element) {
|
||||
element = this._createElement(decoration);
|
||||
decoration.onDispose(() => this._removeDecoration(decoration));
|
||||
decoration.marker.onDispose(() => decoration.dispose());
|
||||
decoration.element = element;
|
||||
this._decorationElements.set(decoration, element);
|
||||
this._container.appendChild(element);
|
||||
}
|
||||
this._refreshStyle(decoration, element);
|
||||
decoration.onRenderEmitter.fire(element);
|
||||
this._refreshStyle(decoration);
|
||||
}
|
||||
|
||||
private _createElement(decoration: IInternalDecoration): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add('xterm-decoration');
|
||||
element.style.width = `${(decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth}px`;
|
||||
element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth)}px`;
|
||||
element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`;
|
||||
@@ -95,14 +85,26 @@ export class BufferDecorationRenderer extends Disposable {
|
||||
return element;
|
||||
}
|
||||
|
||||
private _refreshStyle(decoration: IInternalDecoration, element: HTMLElement): void {
|
||||
private _refreshStyle(decoration: IInternalDecoration): void {
|
||||
const line = decoration.marker.line - this._bufferService.buffers.active.ydisp;
|
||||
if (line < 0 || line >= this._bufferService.rows) {
|
||||
// outside of viewport
|
||||
element.style.display = 'none';
|
||||
if (decoration.element) {
|
||||
decoration.element.style.display = 'none';
|
||||
decoration.onRenderEmitter.fire(decoration.element);
|
||||
}
|
||||
} else {
|
||||
let element = this._decorationElements.get(decoration);
|
||||
if (!element) {
|
||||
decoration.onDispose(() => this._removeDecoration(decoration));
|
||||
element = this._createElement(decoration);
|
||||
decoration.element = element;
|
||||
this._decorationElements.set(decoration, element);
|
||||
this._container.appendChild(element);
|
||||
}
|
||||
element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.display = this._altBufferIsActive ? 'none' : 'block';
|
||||
decoration.onRenderEmitter.fire(element);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { ColorZoneStore } from 'browser/Decorations/ColorZoneStore';
|
||||
import { ColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
||||
|
||||
const optionsRedFull = {
|
||||
overviewRulerOptions: {
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/Decorations/ColorZoneStore';
|
||||
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
@@ -4,18 +4,18 @@
|
||||
*/
|
||||
|
||||
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { ICellData, IColor } from 'common/Types';
|
||||
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, TEXT_BASELINE } 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, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { channels, color, rgba } from 'browser/Color';
|
||||
import { channels, color, rgba } from 'common/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
|
||||
@@ -52,7 +52,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _colors: IColorSet,
|
||||
private _rendererId: number,
|
||||
protected readonly _bufferService: IBufferService,
|
||||
protected readonly _optionsService: IOptionsService
|
||||
protected readonly _optionsService: IOptionsService,
|
||||
protected readonly _decorationService: IDecorationService
|
||||
) {
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
@@ -294,7 +295,7 @@ 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);
|
||||
const contrastColor = this._getContrastColor(cell, x, y);
|
||||
|
||||
// skip cache right away if we draw in RGB
|
||||
// Note: to avoid bad runtime JoinedCellData will be skipped
|
||||
@@ -325,7 +326,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._currentGlyphIdentifier.bold = !!cell.isBold();
|
||||
this._currentGlyphIdentifier.dim = !!cell.isDim();
|
||||
this._currentGlyphIdentifier.italic = !!cell.isItalic();
|
||||
const atlasDidDraw = this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
|
||||
|
||||
// Don't try cache the glyph if it uses any decoration foreground/background override.
|
||||
let hasOverrides = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.backgroundColorRGB || d.foregroundColorRGB) {
|
||||
hasOverrides = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
|
||||
|
||||
if (!atlasDidDraw) {
|
||||
this._drawUncachedChars(cell, x, y);
|
||||
@@ -427,15 +438,35 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`;
|
||||
}
|
||||
|
||||
private _getContrastColor(cell: CellData): IColor | undefined {
|
||||
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) {
|
||||
private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined {
|
||||
// Get any decoration foreground/background overrides, this must be fetched before the early
|
||||
// exist but applied after inverse
|
||||
let bgOverride: number | undefined;
|
||||
let fgOverride: number | undefined;
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
bgOverride = d.backgroundColorRGB.rgba;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgOverride = d.foregroundColorRGB.rgba;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
|
||||
if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
if (!bgOverride && !fgOverride) {
|
||||
// Try get from cache
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let fgColor = cell.getFgColor();
|
||||
@@ -453,13 +484,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
|
||||
const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
if (!fgOverride) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
}
|
||||
// If it was an override and there was no contrast change, set as the result
|
||||
result = fgOverride;
|
||||
}
|
||||
|
||||
const color: IColor = {
|
||||
@@ -470,7 +505,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
),
|
||||
rgba: result
|
||||
};
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
if (!bgOverride && !fgOverride) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { ICoreBrowserService } from 'browser/services/Services';
|
||||
|
||||
@@ -40,9 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkifierEvent | undefined;
|
||||
@@ -21,9 +21,10 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
linkifier: ILinkifier,
|
||||
linkifier2: ILinkifier2,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
|
||||
linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
|
||||
let nextRendererId = 1;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
interface ISelectionState {
|
||||
start?: [number, number];
|
||||
@@ -24,9 +24,10 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
colors: IColorSet,
|
||||
rendererId: number,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._clearState();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
|
||||
@@ -37,9 +37,10 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
rendererId: number,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._state = new GridCache<CharData>();
|
||||
}
|
||||
|
||||
@@ -176,6 +177,19 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
nextFillStyle = this._colors.ansi[cell.getBgColor()].css;
|
||||
}
|
||||
|
||||
// Get any decoration foreground/background overrides, this must be fetched before the early
|
||||
// exist but applied after inverse
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, this._bufferService.buffer.ydisp + y)) {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
nextFillStyle = d.backgroundColorRGB.css;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
|
||||
if (prevFillStyle === null) {
|
||||
// This is either the first iteration, or the default background was set. Either way, we
|
||||
// don't need to draw anything.
|
||||
|
||||
@@ -9,9 +9,9 @@ import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
|
||||
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
|
||||
import { LRUMap } from 'browser/renderer/atlas/LRUMap';
|
||||
import { isFirefox, isSafari } from 'common/Platform';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { color } from 'browser/Color';
|
||||
import { color } from 'common/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.
|
||||
|
||||
@@ -9,9 +9,9 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { color } from 'browser/Color';
|
||||
import { color } from 'common/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
||||
@@ -87,11 +87,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this._screenElement.appendChild(this._rowContainer);
|
||||
this._screenElement.appendChild(this._selectionContainer);
|
||||
|
||||
this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e));
|
||||
this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e));
|
||||
this.register(this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e)));
|
||||
this.register(this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e)));
|
||||
|
||||
this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e));
|
||||
this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e));
|
||||
this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e)));
|
||||
this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e)));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -361,7 +361,6 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
for (let y = start; y <= end; y++) {
|
||||
const rowElement = this._rowElements[y];
|
||||
rowElement.innerText = '';
|
||||
|
||||
const row = y + this._bufferService.buffer.ydisp;
|
||||
const lineData = this._bufferService.buffer.lines.get(row);
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
|
||||
@@ -10,8 +10,8 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags,
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { MockCoreService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { css } from 'browser/Color';
|
||||
import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { css } from 'common/Color';
|
||||
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
|
||||
|
||||
describe('DomRendererRowFactory', () => {
|
||||
@@ -49,7 +49,8 @@ describe('DomRendererRowFactory', () => {
|
||||
} as any,
|
||||
new MockCharacterJoinerService(),
|
||||
new MockOptionsService({ drawBoldTextInBrightColors: true }),
|
||||
new MockCoreService()
|
||||
new MockCoreService(),
|
||||
new MockDecorationService()
|
||||
);
|
||||
lineData = createEmptyLineData(2);
|
||||
});
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferLine, ICellData } from 'common/Types';
|
||||
import { IBufferLine, ICellData, IColor } from 'common/Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { ICoreService, IOptionsService } from 'common/services/Services';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { color, rgba } from 'common/Color';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
import { isPowerlineGlyph } from 'browser/renderer/RendererUtils';
|
||||
@@ -33,7 +33,8 @@ export class DomRendererRowFactory {
|
||||
private _colors: IColorSet,
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ICoreService private readonly _coreService: ICoreService
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@IDecorationService private readonly _decorationService: IDecorationService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -172,6 +173,28 @@ export class DomRendererRowFactory {
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
// Apply any decoration foreground/background overrides, this must happen after inverse has
|
||||
// been applied
|
||||
let bgOverride: IColor | undefined;
|
||||
let fgOverride: IColor | undefined;
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, row)) {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
bgColorMode = Attributes.CM_RGB;
|
||||
bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
bgOverride = d.backgroundColorRGB;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgColorMode = Attributes.CM_RGB;
|
||||
fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
fgOverride = d.foregroundColorRGB;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
|
||||
// Foreground
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
@@ -179,7 +202,7 @@ export class DomRendererRowFactory {
|
||||
if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) {
|
||||
fg += 8;
|
||||
}
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell)) {
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell, undefined, undefined)) {
|
||||
charElement.classList.add(`xterm-fg-${fg}`);
|
||||
}
|
||||
break;
|
||||
@@ -189,13 +212,13 @@ export class DomRendererRowFactory {
|
||||
(fg >> 8) & 0xFF,
|
||||
(fg ) & 0xFF
|
||||
);
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, color, cell)) {
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, color, cell, bgOverride, fgOverride)) {
|
||||
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, cell)) {
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground, cell, undefined, undefined)) {
|
||||
if (isInverse) {
|
||||
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
@@ -209,7 +232,7 @@ export class DomRendererRowFactory {
|
||||
charElement.classList.add(`xterm-bg-${bg}`);
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
|
||||
this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`);
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
@@ -225,18 +248,23 @@ export class DomRendererRowFactory {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData): boolean {
|
||||
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean {
|
||||
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
|
||||
// Try get from cache first, only use the cache when there are no decoration overrides
|
||||
let adjustedColor: IColor | undefined | null = undefined;
|
||||
if (!bgOverride || !fgOverride) {
|
||||
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.rawOptions.minimumContrastRatio);
|
||||
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
|
||||
adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
if (!bgOverride || !fgOverride) {
|
||||
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustedColor) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet, IRenderDebouncer } from 'browser/Types';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
|
||||
interface ISelectionState {
|
||||
@@ -54,6 +54,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
screenElement: HTMLElement,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IDecorationService decorationService: IDecorationService,
|
||||
@IBufferService bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
@@ -72,6 +73,12 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
|
||||
this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
|
||||
|
||||
// Do a full refresh whenever any decoration is added or removed. This may not actually result
|
||||
// in changes but since decorations should be used sparingly or added/removed all in the same
|
||||
// frame this should have minimal performance impact.
|
||||
this.register(decorationService.onDecorationRegistered(() => this._fullRefresh()));
|
||||
this.register(decorationService.onDecorationRemoved(() => this._fullRefresh()));
|
||||
|
||||
// No need to register this as renderer is explicitly disposed in RenderService.dispose
|
||||
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user