From 23bef9913290707e65323ad1ee620ebe0c310e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 4 Oct 2024 02:06:02 +0200 Subject: [PATCH] cleanup API --- .github/workflows/ci.yml | 2 + addons/addon-web-fonts/README.md | 12 +- addons/addon-web-fonts/src/WebFontsAddon.ts | 136 +++++++++--------- .../typings/addon-web-fonts.d.ts | 55 ++++++- 4 files changed, 127 insertions(+), 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb9066d9..f7616b7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,6 +223,8 @@ jobs: run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode-graphemes - name: Integration tests (addon-unicode11) run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode11 + - name: Integration tests (addon-web-fonts) + run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-fonts - name: Integration tests (addon-web-links) run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-links - name: Integration tests (addon-webgl) diff --git a/addons/addon-web-fonts/README.md b/addons/addon-web-fonts/README.md index bd1dedf4..e910c21e 100644 --- a/addons/addon-web-fonts/README.md +++ b/addons/addon-web-fonts/README.md @@ -72,7 +72,7 @@ const webFontsAddon = new WebFontsAddon(); terminal.loadAddon(webFontsAddon); // wait for webfonts to be fully loaded -WebFontsAddon.loadFonts(['Web Mono 1', 'Super Powerline']).then(() => { +webFontsAddon.loadFonts(['Web Mono 1', 'Super Powerline']).then(() => { terminal.open(your_terminal_div_element); // more boostrapping goes here ... }); @@ -83,13 +83,13 @@ on the initial document load (more precise - by the time this code runs). Please note, that this code cannot run synchronous anymore, so you will have to split your bootstrapping code into several stages. If thats too much of a hassle, you can also move the whole -bootstrapping under that waiting condition (`loadFonts` is actually a static method): +bootstrapping under that waiting condition (import `loadFonts` for a static variant): ```typescript import { Terminal } from '@xterm/xterm'; import { XYAddon } from '@xterm/addon-xy'; -import { WebFontsAddon } from '@xterm/addon-web-fonts'; +import { WebFontsAddon, loadFonts } from '@xterm/addon-web-fonts'; -WebFontsAddon.loadFonts(['Web Mono 1', 'Super Powerline']).then(() => { +loadFonts(['Web Mono 1', 'Super Powerline']).then(() => { // create a `Terminal` instance, now with webfonts const terminal = new Terminal({fontFamily: '"Web Mono 1", "Super Powerline", monospace'}); const xyAddon = new XYAddon(); @@ -112,7 +112,7 @@ That can be achieved like this: const ff1 = new FontFace('New Web Mono', url1, ...); const ff2 = new FontFace('New Web Mono', url2, ...); // and await their loading -WebFontsAddon.loadFonts([ff1, ff2]).then(() => { +loadFonts([ff1, ff2]).then(() => { // apply new webfont to terminal terminal.options.fontFamily = 'New Web Mono'; // since the new font might have slighly different metrics, @@ -124,7 +124,7 @@ WebFontsAddon.loadFonts([ff1, ff2]).then(() => { document.styleSheets[0].insertRule( "@font-face { font-family: 'New Web Mono'; src: url(newfont.woff); }", 0); // and await the new font family name -WebFontsAddon.loadFonts(['New Web Mono']).then(() => { +loadFonts(['New Web Mono']).then(() => { // apply new webfont to terminal terminal.options.fontFamily = 'New Web Mono'; // since the new font might have slighly different metrics, diff --git a/addons/addon-web-fonts/src/WebFontsAddon.ts b/addons/addon-web-fonts/src/WebFontsAddon.ts index e3132602..219a64af 100644 --- a/addons/addon-web-fonts/src/WebFontsAddon.ts +++ b/addons/addon-web-fonts/src/WebFontsAddon.ts @@ -8,7 +8,7 @@ import type { WebFontsAddon as IWebFontsApi } from '@xterm/addon-web-fonts'; /** - * Unquote family name. + * Unquote a font family name. */ function unquote(s: string): string { if (s[0] === '"' && s[s.length - 1] === '"') return s.slice(1, -1); @@ -18,7 +18,7 @@ function unquote(s: string): string { /** - * Quote family name. + * Quote a font family name conditionally. * @see https://mathiasbynens.be/notes/unquoted-font-family */ function quote(s: string): string { @@ -40,16 +40,46 @@ function createFamily(families: string[]): string { } +/** + * Hash a font face from it properties. + * Used in `loadFonts` to avoid bloating + * `document.fonts` from multiple calls. + */ +function hashFontFace(ff: FontFace): string { + return JSON.stringify([ + unquote(ff.family), + ff.stretch, + ff.style, + ff.unicodeRange, + ff.weight + ]); +} + + +/** + * Wait for webfont resources to be loaded. + * + * Without any argument, all fonts currently listed in + * `document.fonts` will be loaded. + * For a more fine-grained loading strategy you can populate + * the `fonts` argument with: + * - font families : loads all fontfaces in `document.fonts` + * matching the family names + * - fontface objects : loads given fontfaces and adds them to + * `document.fonts` + * + * The returned promise will resolve, when all loading is done. + */ function _loadFonts(fonts?: (string | FontFace)[]): Promise { const ffs = Array.from(document.fonts); if (!fonts || !fonts.length) { return Promise.all(ffs.map(ff => ff.load())); } let toLoad: FontFace[] = []; - const ffsHashed = ffs.map(ff => WebFontsAddon.hashFontFace(ff)); + const ffsHashed = ffs.map(ff => hashFontFace(ff)); for (const font of fonts) { if (font instanceof FontFace) { - const fontHashed = WebFontsAddon.hashFontFace(font); + const fontHashed = hashFontFace(font); const idx = ffsHashed.indexOf(fontHashed); if (idx === -1) { document.fonts.add(font); @@ -72,85 +102,53 @@ function _loadFonts(fonts?: (string | FontFace)[]): Promise { } +export async function loadFonts(fonts?: (string | FontFace)[]): Promise { + await document.fonts.ready; + return _loadFonts(fonts); +} + export class WebFontsAddon implements ITerminalAddon, IWebFontsApi { - constructor(public forceInitialRelayout: boolean = true) { } - public dispose(): void { } + private _term: Terminal | undefined; - public activate(terminal: Terminal): void { + constructor(public forceInitialRelayout: boolean = true) { } + + public dispose(): void { + this._term = undefined; + } + + public activate(term: Terminal): void { + this._term = term; if (this.forceInitialRelayout) { - document.fonts.ready.then(() => this.relayout(terminal)); + document.fonts.ready.then(() => this.relayout()); } } - /** - * Force a terminal re-layout by altering `options.FontFamily`. - * - * Found webfonts in `fontFamily` are temporarily removed until the webfont - * resources are fully loaded. - * - * This method is meant as a fallback fix for sloppy integrations, - * that wrongly placed a webfont at the terminal contructor options. - * It is likely to lead to terminal flickering in all browsers (FOUT). - * - * To avoid triggering this fallback in your integration, make sure to have - * the needed webfonts loaded at the time `terminal.open` is called. - */ - public relayout(terminal: Terminal): void { - const family = terminal.options.fontFamily; + public async loadFonts(fonts?: (string | FontFace)[]): Promise { + return loadFonts(fonts); + } + + public async relayout(): Promise { + if (!this._term) { + return; + } + await document.fonts.ready; + const family = this._term.options.fontFamily; const families = splitFamily(family); - const webFamilies = WebFontsAddon.getFontFamilies(); + const webFamilies = Array.from(new Set(Array.from(document.fonts).map(e => unquote(e.family)))); const dirty: string[] = []; const clean: string[] = []; for (const fam of families) { (webFamilies.indexOf(fam) !== -1 ? dirty : clean).push(fam); } - if (dirty.length) { - _loadFonts(dirty).then(() => { - terminal.options.fontFamily = clean.length ? createFamily(clean) : 'monospace'; - terminal.options.fontFamily = family; - }); + if (!dirty.length) { + return; + } + await _loadFonts(dirty); + if (this._term) { + this._term.options.fontFamily = clean.length ? createFamily(clean) : 'monospace'; + this._term.options.fontFamily = family; } - } - - /** - * Hash a font face from it properties. - * Used in `loadFonts` to avoid bloating - * `document.fonts` from multiple calls. - */ - public static hashFontFace(ff: FontFace): string { - return JSON.stringify([ - unquote(ff.family), - ff.stretch, - ff.style, - ff.unicodeRange, - ff.weight - ]); - } - - /** - * Return font families known in `document.fonts`. - */ - public static getFontFamilies(): string[] { - return Array.from(new Set(Array.from(document.fonts).map(e => unquote(e.family)))); - } - - /** - * Wait for webfont resources to be loaded. - * - * Without any argument, all fonts currently listed in - * `document.fonts` will be loaded. - * For a more fine-grained loading strategy you can populate - * the `fonts` argument with: - * - font families : loads all fontfaces in `document.fonts` - * matching the family names - * - fontface objects : loads given fontfaces and adds them to - * `document.fonts` - * - * The returned promise will resolve, when all loading is done. - */ - public static loadFonts(fonts?: (string | FontFace)[]): Promise { - return document.fonts.ready.then(() => _loadFonts(fonts)); } } diff --git a/addons/addon-web-fonts/typings/addon-web-fonts.d.ts b/addons/addon-web-fonts/typings/addon-web-fonts.d.ts index 42cab9b6..c353a421 100644 --- a/addons/addon-web-fonts/typings/addon-web-fonts.d.ts +++ b/addons/addon-web-fonts/typings/addon-web-fonts.d.ts @@ -4,15 +4,64 @@ */ -import { Terminal, ITerminalAddon, IViewportRange } from '@xterm/xterm'; +import { Terminal, ITerminalAddon } from '@xterm/xterm'; declare module '@xterm/addon-web-fonts' { + /** - * An xterm.js addon that enables web links. + * Addon to use webfonts in xterm.js */ export class WebFontsAddon implements ITerminalAddon { - constructor(); + /** + * @param forceInitialRelayout Force an initial relayout, if a webfont was found. + */ + constructor(forceInitialRelayout?: boolean); public activate(terminal: Terminal): void; public dispose(): void; + + /** + * Wait for webfont resources to be loaded. + * + * Without any argument, all fonts currently listed in + * `document.fonts` will be loaded. + * For a more fine-grained loading strategy you can populate + * the `fonts` argument with: + * - font families : loads all fontfaces in `document.fonts` + * matching the family names + * - fontface objects : loads given fontfaces and adds them to + * `document.fonts` + * + * The returned promise will resolve, when all loading is done. + */ + public loadFonts(fonts?: (string | FontFace)[]): Promise; + + /** + * Force a terminal relayout by altering `options.FontFamily`. + * + * Found webfonts in `fontFamily` are temporarily removed until the webfont + * resources are fully loaded. + * + * Call this method, if a terminal with webfonts is stuck with broken + * glyph metrics. + * + * Returns a promise on completion. + */ + public relayout(): Promise; } + + /** + * Wait for webfont resources to be loaded. + * + * Without any argument, all fonts currently listed in + * `document.fonts` will be loaded. + * For a more fine-grained loading strategy you can populate + * the `fonts` argument with: + * - font families : loads all fontfaces in `document.fonts` + * matching the family names + * - fontface objects : loads given fontfaces and adds them to + * `document.fonts` + * + * The returned promise will resolve, when all loading is done. + */ + function loadFonts(fonts?: (string | FontFace)[]): Promise; }