diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index b1c136f2..4349b222 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -108,7 +108,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._dimensionsStyleElement.remove(); })); - this._widthCache = new WidthCache(this._document, this._helperContainer); + this._widthCache = new WidthCache(); this._widthCache.setFont( this._optionsService.rawOptions.fontFamily, this._optionsService.rawOptions.fontSize, diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 14c4ade1..ad95037c 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -15,13 +15,13 @@ import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } import { TestWidthCache } from 'browser/renderer/dom/WidthCache.test'; const dom = new jsdom.JSDOM(''); -const EMPTY_WIDTH = new TestWidthCache(dom.window.document, dom.window.document.createElement('div')); describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; let rowFactory: DomRendererRowFactory; let lineData: IBufferLine; + let widthCache: TestWidthCache; beforeEach(() => { dom = new jsdom.JSDOM(''); @@ -35,22 +35,23 @@ describe('DomRendererRowFactory', () => { new MockThemeService() ); lineData = createEmptyLineData(2); + widthCache = new TestWidthCache(); }); describe('createRow', () => { it('should not create anything for an empty row', () => { - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), '' ); }); it('should set correct attributes for double width characters', () => { - EMPTY_WIDTH.widths['語'] = [10, 10, 10, 10]; + widthCache.setWidths({ '語': 10 }); lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)])); // There should be no element for the following "empty" cell lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), '' ); @@ -58,7 +59,7 @@ describe('DomRendererRowFactory', () => { it('should add class for cursor and cursor style', () => { for (const style of ['block', 'bar', 'underline']) { - const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ` ` ); @@ -66,7 +67,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for cursor blink', () => { - const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ` ` ); @@ -85,7 +86,7 @@ describe('DomRendererRowFactory', () => { new MockThemeService() ); for (const inactiveStyle of ['outline', 'block', 'bar', 'underline', 'none']){ - const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, 5, widthCache, -1, -1); if (inactiveStyle === 'none') { assert.equal(extractHtml(spans), ` `); @@ -108,7 +109,7 @@ describe('DomRendererRowFactory', () => { new MockDecorationService(), new MockThemeService() ); - const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ` ` ); @@ -119,7 +120,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -129,7 +130,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -139,7 +140,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -152,7 +153,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.SINGLE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -163,7 +164,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DOUBLE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -174,7 +175,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.CURLY; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -185,7 +186,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DOTTED; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -196,7 +197,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DASHED; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -207,7 +208,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -217,7 +218,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -230,7 +231,7 @@ describe('DomRendererRowFactory', () => { cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg |= i; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), `a` ); @@ -244,7 +245,7 @@ describe('DomRendererRowFactory', () => { cell.bg &= ~Attributes.PCOLOR_MASK; cell.bg |= i; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), `a` ); @@ -256,7 +257,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -267,7 +268,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -277,7 +278,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -290,7 +291,7 @@ describe('DomRendererRowFactory', () => { cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg |= i; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), `a` ); @@ -302,7 +303,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -313,7 +314,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -325,7 +326,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); rowFactory.handleSelectionChanged([1, 0], [2, 0], false); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'ab' ); @@ -333,7 +334,7 @@ describe('DomRendererRowFactory', () => { it('should force whitespace cells to be rendered above the background', () => { lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); rowFactory.handleSelectionChanged([0, 0], [2, 0], false); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' a' ); @@ -350,7 +351,7 @@ describe('DomRendererRowFactory', () => { }); it('should not create anything for an empty row', () => { - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), '' ); @@ -360,18 +361,18 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'abc' ); }); it('should not merge codepoints with different spacing', () => { - EMPTY_WIDTH.widths['€'] = [2, 2, 2, 2]; + widthCache.setWidths({ '€': 2 }); lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'ac' ); @@ -386,7 +387,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(1, aColor1); lineData.setCell(2, bColor2); lineData.setCell(3, bColor2); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'aabb' ); @@ -398,7 +399,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'X', 1, 'X'.charCodeAt(0)])); lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'aaXbb' ); @@ -411,7 +412,7 @@ describe('DomRendererRowFactory', () => { nullCell.bg = Attributes.CM_P16 | 2; lineData.setCell(3, nullCell); lineData.setCell(4, nullCell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' ' ); @@ -421,38 +422,36 @@ describe('DomRendererRowFactory', () => { const nullCell = lineData.loadCell(0, new CellData()); nullCell.bg = Attributes.CM_P16 | 1; lineData.setCell(0, nullCell); - let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' ' ); lineData.setCell(1, nullCell); - spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' ' ); lineData.setCell(2, nullCell); lineData.setCell(3, nullCell); - spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' ' ); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' a' ); }); it('should apply correct positive or negative spacing', () => { - EMPTY_WIDTH.widths['€'] = [2, 2, 2, 2]; // too small, should add 3px - EMPTY_WIDTH.widths['語'] = [10, 10, 10, 10]; // exact match for its width, should merge - EMPTY_WIDTH.widths['𝄞'] = [7, 7, 7, 7]; // too wide, should subtract -2px + widthCache.setWidths({ '€': 2, '語': 10, '𝄞': 7 }); // €: too small (+3px), 語: exact, 𝄞: too wide (-2px) lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, '語', 2, 'c'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, '𝄞', 1, 'c'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'ac語𝄞' ); @@ -466,7 +465,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); lineData.setCell(5, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); lineData.setCell(6, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, 2, 4); assert.equal(extractHtml(spans), 'aaxxxbb' ); @@ -477,7 +476,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, 2, 4); assert.equal(extractHtml(spans), 'aax x' ); @@ -487,7 +486,7 @@ describe('DomRendererRowFactory', () => { for (let i = 0; i < 10; ++i) { lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); } - const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -100, 100); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -100, 100); assert.equal(extractHtml(spans), 'aaaaaaaaaa' ); diff --git a/src/browser/renderer/dom/WidthCache.test.ts b/src/browser/renderer/dom/WidthCache.test.ts index fc524437..c1e511c9 100644 --- a/src/browser/renderer/dom/WidthCache.test.ts +++ b/src/browser/renderer/dom/WidthCache.test.ts @@ -4,10 +4,20 @@ */ import * as assert from 'assert'; -import { WidthCache, WidthCacheSettings } from 'browser/renderer/dom/WidthCache'; -import jsdom = require('jsdom'); +import { IWidthCacheFontVariantCanvas, WidthCache, WidthCacheSettings } from 'browser/renderer/dom/WidthCache'; +class MockWidthCacheFontVariantCanvas implements IWidthCacheFontVariantCanvas { + public widths: { [key: string]: number } = {}; + + public setFont(_fontFamily: string, _fontSize: number, _fontWeight: unknown, _italic: boolean): void { + } + + public measure(c: string): number { + return this.widths[c] ?? 5; + } +} + export class TestWidthCache extends WidthCache { public get flat(): Float32Array { return (this as any)._flat; @@ -15,13 +25,18 @@ export class TestWidthCache extends WidthCache { public get holey(): Map | undefined { return (this as any)._holey; } + public get canvasElements(): MockWidthCacheFontVariantCanvas[] { + return (this as any)._canvasElements; + } - public widths: {[key: string]: [number, number, number, number]} = {}; - protected _measure(c: string, variant: number): number { - if (this.widths[c] !== undefined) { - return this.widths[c][variant]; + constructor() { + super(() => new MockWidthCacheFontVariantCanvas()); + } + + public setWidths(widths: { [key: string]: number }): void { + for (const canvas of this.canvasElements) { + canvas.widths = widths; } - return 5; // 5 is default width in tests in DomRendererRowFactory.test.ts } } @@ -36,8 +51,7 @@ function castf32(v: number): number { describe('WidthCache', () => { let wc: TestWidthCache; beforeEach(() => { - const dom = new jsdom.JSDOM(''); - wc = new TestWidthCache(dom.window.document, dom.window.document.createElement('div')); + wc = new TestWidthCache(); wc.setFont('monospace', 15, 'normal', 'bold'); }); describe('cache invalidation', () => { diff --git a/src/browser/renderer/dom/WidthCache.ts b/src/browser/renderer/dom/WidthCache.ts index 8c083926..3a8b1506 100644 --- a/src/browser/renderer/dom/WidthCache.ts +++ b/src/browser/renderer/dom/WidthCache.ts @@ -25,6 +25,10 @@ const enum FontVariant { BOLD_ITALIC = 3 } +export interface IWidthCacheFontVariantCanvas { + setFont(fontFamily: string, fontSize: number, fontWeight: FontWeight, italic: boolean): void; + measure(c: string): number; +} export class WidthCache implements IDisposable { // flat cache for regular variant up to CacheSettings.FLAT_SIZE @@ -43,14 +47,16 @@ export class WidthCache implements IDisposable { private _fontSize = 0; private _weight: FontWeight = 'normal'; private _weightBold: FontWeight = 'bold'; - private _canvasElements: WidthCacheFontVariantCanvas[] = []; + private _canvasElements: IWidthCacheFontVariantCanvas[] = []; - constructor(_document: Document, _helperContainer: HTMLElement) { + constructor( + canvasFactory: () => IWidthCacheFontVariantCanvas = () => new WidthCacheFontVariantCanvas() + ) { this._canvasElements = [ - new WidthCacheFontVariantCanvas(), - new WidthCacheFontVariantCanvas(), - new WidthCacheFontVariantCanvas(), - new WidthCacheFontVariantCanvas() + canvasFactory(), + canvasFactory(), + canvasFactory(), + canvasFactory() ]; this.clear(); @@ -136,7 +142,7 @@ export class WidthCache implements IDisposable { } } -class WidthCacheFontVariantCanvas { +class WidthCacheFontVariantCanvas implements IWidthCacheFontVariantCanvas { private _canvas: OffscreenCanvas; private _ctx: OffscreenCanvasRenderingContext2D;