Files
xterm.js/src/browser/renderer/dom/WidthCache.ts
T
2023-07-29 17:25:49 +02:00

123 lines
3.6 KiB
TypeScript

/**
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from 'common/Types';
const enum CacheSettings {
FLAT_UNSET = -9999, // sentinel for unset values in flat cache
FLAT_SIZE = 256, // codepoint upper bound to handle in flat cache
REPEAT = 32 // char repeat for measuring
}
export class WidthCache implements IDisposable {
// flat cache for regular
private _flat = new Float32Array(CacheSettings.FLAT_SIZE);
// holey cache for bold, italic and bold&italic for any string
private _holey = new Map<string, number>();
private _font = '';
private _fontSize = 0;
private _container: HTMLDivElement;
private _measureElements: HTMLSpanElement[] = [];
constructor(
private readonly _document: Document
) {
this._container = _document.createElement('div');
this._container.style.position = 'absolute';
this._container.style.top = '-50000px';
this._container.style.width = '50000px';
this._container.style.whiteSpace = 'pre';
// avoid undercuts in non-monospace fonts from kerning
this._container.style.fontKerning = 'none';
const regular = _document.createElement('span');
const bold = _document.createElement('span');
bold.style.fontWeight = 'bold';
const italic = _document.createElement('span');
italic.style.fontStyle = 'italic';
const boldItalic = _document.createElement('span');
boldItalic.style.fontWeight = 'bold';
boldItalic.style.fontStyle = 'italic';
// note: must be in order of variant in _measure
this._measureElements = [regular, bold, italic, boldItalic];
this._container.appendChild(regular);
this._container.appendChild(bold);
this._container.appendChild(italic);
this._container.appendChild(boldItalic);
_document.body.appendChild(this._container);
this.clear();
}
public dispose(): void {
this._container.remove();
this._measureElements.length = 0;
this._holey.clear();
}
/**
* Clear the spacing cache.
*/
public clear(): void {
this._flat.fill(CacheSettings.FLAT_UNSET);
this._holey.clear();
}
/**
* Set the font for measuring.
* Must be called for any fontFamily or fontSize changes.
* Also clears the cache.
*/
public setFont(font: string, fontSize: number): void {
if (font !== this._font || fontSize !== this._fontSize) {
this._font = font;
this._fontSize = fontSize;
this.clear();
this._container.style.fontFamily = this._font;
this._container.style.fontSize = `${this._fontSize}px`;
}
}
/**
* Get the render width for cell content `c` with current font settings.
* `variant` denotes the font variant to be used.
*/
public get(c: string, bold: boolean | number, italic: boolean | number): number {
let cp = 0;
if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < CacheSettings.FLAT_SIZE) {
return this._flat[cp] !== CacheSettings.FLAT_UNSET
? this._flat[cp]
: (this._flat[cp] = this._measure(c, 0));
}
let key = c;
if (bold) key += 'B';
if (italic) key += 'I';
let width = this._holey.get(key);
if (width === undefined) {
let variant = 0;
if (bold) variant |= 1;
if (italic) variant |= 2;
width = this._measure(c, variant);
this._holey.set(key, width);
}
return width;
}
private _measure(c: string, variant: number): number {
const el = this._measureElements[variant];
el.textContent = c.repeat(CacheSettings.REPEAT);
return el.getBoundingClientRect().width / CacheSettings.REPEAT;
}
}