diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 865060f8..c40e86ff 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -62,6 +62,9 @@ jobs:
./addons/addon-web-links/lib/* \
./addons/addon-web-links/out/* \
./addons/addon-web-links/out-*/* \
+ ./addons/addon-web-fonts/lib/* \
+ ./addons/addon-web-fonts/out/* \
+ ./addons/addon-web-fonts/out-*/* \
./addons/addon-webgl/lib/* \
./addons/addon-webgl/out/* \
./addons/addon-webgl/out-*st/*
@@ -223,6 +226,8 @@ jobs:
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode-graphemes
- name: Integration tests (addon-unicode11)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode11
+ - name: Integration tests (addon-web-fonts)
+ run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-fonts
- name: Integration tests (addon-web-links)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-links
- name: Integration tests (addon-webgl)
diff --git a/addons/addon-web-fonts/LICENSE b/addons/addon-web-fonts/LICENSE
new file mode 100644
index 00000000..447eb79f
--- /dev/null
+++ b/addons/addon-web-fonts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2024, The xterm.js authors (https://github.com/xtermjs/xterm.js)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/addons/addon-web-fonts/README.md b/addons/addon-web-fonts/README.md
new file mode 100644
index 00000000..ce43cbe5
--- /dev/null
+++ b/addons/addon-web-fonts/README.md
@@ -0,0 +1,220 @@
+## @xterm/addon-web-fonts
+
+Addon to use webfonts with [xterm.js](https://github.com/xtermjs/xterm.js).
+This addon requires xterm.js v5+.
+
+### Install
+
+```bash
+npm install --save @xterm/addon-web-fonts
+```
+
+### Issue with Webfonts
+
+Webfonts are announced by CSS `font-face` rules (or its Javascript `FontFace` counterparts).
+Since font files tend to be quite big assets, browser engines often postpone their loading
+to an actual styling request of a codepoint matching a font file's `unicode-range`.
+In short - font files will not be loaded until really needed.
+
+xterm.js on the other hand heavily relies on exact measurement of character glyphs
+to layout its output. This is done by determining the glyph width (DOM renderer) or
+by creating a glyph texture (WebGl renderer) for every output character.
+For performance reasons both is done in synchronous code and cached.
+This logic only works properly, if a font glyph is available on its first usage,
+otherwise the browser will pick a glyph from a fallback font messing up the metrics.
+
+For webfonts and xterm.js this means that we cannot rely on the default loading strategy
+of the browser, but have to preload the font files before using that font in xterm.js.
+
+
+### Static Preloading for the Rescue?
+
+If you dont mind higher initial loading times with a white page shown,
+you can tell the browser to preload the needed font files by placing the following
+link elements in the document's head above any other CSS/Javascript:
+```html
+
+
+ ...
+
+```
+Browsers also will resort to system fonts, if the preloading takes too long.
+So this solution has only a very limited scope.
+
+
+### Loading with WebFontsAddon
+
+The webfonts addon offers several ways to deal with font assets loading
+without leaving the terminal in an unusable state.
+
+Recap - normally boostrapping of a new terminal involves these basic steps (Typescript):
+
+```typescript
+import { Terminal } from '@xterm/xterm';
+import { XYAddon } from '@xterm/addon-xy';
+
+// create a `Terminal` instance with some options, e.g. a custom font family
+const terminal = new Terminal({fontFamily: 'monospace'});
+
+// create and load all addons you want to use, e.g. fit addon
+const xyInstance = new XYAddon();
+terminal.loadAddon(xyInstance);
+
+// finally: call `open` of the terminal instance
+terminal.open(your_terminal_div_element); // <-- critical path for webfonts
+// more boostrapping goes here ...
+```
+This code is guaranteed to work in all browsers synchronously, as the identifier `monospace`
+will always be available. It will also work synchronously with any installed system font,
+but breaks horribly with webfonts. The actual culprit here is the call to `terminal.open`,
+which attaches the terminal to the DOM and starts the renderer with all the glyph caching
+mentioned above, while the webfont is not yet fully available.
+
+To fix that, the webfonts addon provides a waiting condition (Typescript):
+```typescript
+import { Terminal } from '@xterm/xterm';
+import { XYAddon } from '@xterm/addon-xy';
+import { WebFontsAddon } from '@xterm/addon-web-fonts';
+
+// create a `Terminal` instance, now with webfonts
+const terminal = new Terminal({fontFamily: '"Web Mono 1", "Super Powerline", monospace'});
+const xyInstance = new XYAddon();
+terminal.loadAddon(xyInstance);
+
+const webFontsInstance = new WebFontsAddon();
+terminal.loadAddon(webFontsInstance);
+
+// wait for webfonts to be fully loaded
+webFontsInstance.loadFonts(['Web Mono 1', 'Super Powerline']).then(() => {
+ terminal.open(your_terminal_div_element);
+ // more boostrapping goes here ...
+});
+```
+Here `loadFonts` will look up the font face objects in `document.fonts`
+and load them before continuing. For this to work, you have to make sure,
+that the CSS `font-face` rules for these webfonts are loaded beforehand,
+otherwise `loadFonts` will not find the font family names (promise will be
+rejected for missing font family names).
+
+Please note, that this cannot run synchronous anymore, so you will have to split your
+bootstrapping code into several stages. If that is too much of a hassle,
+you can also move the whole bootstrapping under the waiting condition by using
+the static loader instead (Typescript):
+```typescript
+import { Terminal } from '@xterm/xterm';
+import { XYAddon } from '@xterm/addon-xy';
+// import static loader
+import { loadFonts } from '@xterm/addon-web-fonts';
+
+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 xyInstance = new XYAddon();
+ terminal.loadAddon(xyInstance);
+
+ // optional when using static loader
+ const webfontsInstance = new WebFontsAddon();
+ terminal.loadAddon(webfontsInstance);
+
+ terminal.open(your_terminal_div_element);
+ // more boostrapping goes here ...
+});
+```
+With the static loader creating and loading of the actual addon can be omitted,
+as fonts are already loaded before any terminal setup happens.
+
+### Webfont Loading at Runtime
+
+Given you have a terminal already running and want to change the font family
+to a different not yet loaded webfont:
+```typescript
+// either create font face objects in javascript
+const ff1 = new FontFace('New Web Mono', url1, ...);
+const ff2 = new FontFace('New Web Mono', url2, ...);
+// and await their loading
+loadFonts([ff1, ff2]).then(() => {
+ // apply new webfont to terminal
+ terminal.options.fontFamily = 'New Web Mono';
+ // since the new font might have slighly different metrics,
+ // also run the fit addon here (or any other custom resize logic)
+ fitAddon.fit();
+});
+
+// or alternatively use CSS to add new font-face rules, e.g.
+document.styleSheets[0].insertRule(
+ "@font-face { font-family: 'New Web Mono'; src: url(newfont.woff); }", 0);
+// and await the new font family name
+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,
+ // also run the fit addon here (or any other custom resize logic)
+ fitAddon.fit();
+});
+```
+
+### Forced Layout Update
+
+If you have the addon loaded into your terminal, you can force the terminal to update
+the layout with the method `WebFontsAddon.relayout`. This might come handy,
+if the terminal shows webfont related output issue for unknown reasons:
+```typescript
+...
+// given - terminal shows weird font issues, run:
+webFontsInstance.relayout().then(() => {
+ // also run resize logic here, e.g. fit addon
+ fitAddon.fit();
+});
+```
+Note that this method is only meant as a quickfix on a running terminal to keep it
+in a working condition. A production-ready integration should never rely on it,
+better fix the real root cause (most likely not properly awaiting the font loader
+higher up in the code).
+
+
+### Webfonts from Fontsource
+
+The addon has been tested to work with webfonts from fontsource.
+Javascript example for `vite` with ESM import:
+```javascript
+import { Terminal } from '@xterm/xterm';
+import { FitAddon } from '@xterm/addon-fit';
+import { loadFonts } from '@xterm/addon-web-fonts';
+import '@xterm/xterm/css/xterm.css';
+import '@fontsource/roboto-mono';
+import '@fontsource/roboto-mono/400.css';
+import '@fontsource/roboto-mono/400-italic.css';
+import '@fontsource/roboto-mono/700.css';
+import '@fontsource/roboto-mono/700-italic.css';
+
+async function main() {
+ let fontFamily = '"Roboto Mono", monospace';
+ try {
+ await loadFonts(['Roboto Mono']);
+ } catch (e) {
+ fontFamily = 'monospace';
+ }
+
+ const terminal = new Terminal({ fontFamily });
+ const fitAddon = new FitAddon();
+ terminal.loadAddon(fitAddon);
+ terminal.open(document.getElementById('your-xterm-container-div'));
+ fitAddon.fit();
+
+ // sync writing shows up in Roboto Mono w'o FOUT
+ // and a fallback to monospace
+ terminal.write('put any unicode char here');
+}
+
+main();
+```
+The fontsource packages download the font files to your project folder to be delivered
+from there later on. For security sensitive projects this should be the preferred way,
+as it brings the font files under your control.
+
+The example furthermore contains proper exception handling with a fallback
+(skipped in all other examples for better readability).
+
+---
+
+Also see the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-web-fonts/typings/addon-web-fonts.d.ts).
diff --git a/addons/addon-web-fonts/package.json b/addons/addon-web-fonts/package.json
new file mode 100644
index 00000000..856c7e25
--- /dev/null
+++ b/addons/addon-web-fonts/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@xterm/addon-web-fonts",
+ "version": "0.1.0",
+ "author": {
+ "name": "The xterm.js authors",
+ "url": "https://xtermjs.org/"
+ },
+ "main": "lib/addon-web-fonts.js",
+ "module": "lib/addon-web-fonts.mjs",
+ "types": "typings/addon-web-fonts.d.ts",
+ "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-fonts",
+ "license": "MIT",
+ "keywords": [
+ "terminal",
+ "xterm",
+ "xterm.js"
+ ],
+ "scripts": {
+ "build": "../../node_modules/.bin/tsc -p .",
+ "prepackage": "npm run build",
+ "package": "../../node_modules/.bin/webpack",
+ "prepublishOnly": "npm run package",
+ "start": "node ../../demo/start"
+ },
+ "dependencies": {}
+}
diff --git a/addons/addon-web-fonts/src/WebFontsAddon.ts b/addons/addon-web-fonts/src/WebFontsAddon.ts
new file mode 100644
index 00000000..cc0ccd1f
--- /dev/null
+++ b/addons/addon-web-fonts/src/WebFontsAddon.ts
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2024 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import type { Terminal, ITerminalAddon } from '@xterm/xterm';
+import type { WebFontsAddon as IWebFontsApi } from '@xterm/addon-web-fonts';
+
+
+/**
+ * Unquote a font family name.
+ */
+function unquote(s: string): string {
+ if (s[0] === '"' && s[s.length - 1] === '"') return s.slice(1, -1);
+ if (s[0] === '\'' && s[s.length - 1] === '\'') return s.slice(1, -1);
+ return s;
+}
+
+
+/**
+ * Quote a font family name conditionally.
+ * @see https://mathiasbynens.be/notes/unquoted-font-family
+ */
+function quote(s: string): string {
+ const pos = s.match(/([-_a-zA-Z0-9\xA0-\u{10FFFF}]+)/u);
+ const neg = s.match(/^(-?\d|--)/m);
+ if (!neg && pos && pos[1] === s) return s;
+ return `"${s.replace(/"/g, '\\"')}"`;
+}
+
+
+function splitFamily(family: string | undefined): string[] {
+ if (!family) return [];
+ return family.split(',').map(e => unquote(e.trim()));
+}
+
+
+function createFamily(families: string[]): string {
+ return families.map(quote).join(', ');
+}
+
+
+/**
+ * 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 => hashFontFace(ff));
+ for (const font of fonts) {
+ if (font instanceof FontFace) {
+ const fontHashed = hashFontFace(font);
+ const idx = ffsHashed.indexOf(fontHashed);
+ if (idx === -1) {
+ document.fonts.add(font);
+ ffs.push(font);
+ ffsHashed.push(fontHashed);
+ toLoad.push(font);
+ } else {
+ toLoad.push(ffs[idx]);
+ }
+ } else {
+ // string as font
+ const familyFiltered = ffs.filter(ff => font === unquote(ff.family));
+ toLoad = toLoad.concat(familyFiltered);
+ if (!familyFiltered.length) {
+ return Promise.reject(`font family "${font}" not registered in document.fonts`);
+ }
+ }
+ }
+ return Promise.all(toLoad.map(ff => ff.load()));
+}
+
+
+export function loadFonts(fonts?: (string | FontFace)[]): Promise {
+ return document.fonts.ready.then(() => _loadFonts(fonts));
+}
+
+
+export class WebFontsAddon implements ITerminalAddon, IWebFontsApi {
+ private _term: Terminal | undefined;
+
+ constructor(public initialRelayout: boolean = true) { }
+
+ public dispose(): void {
+ this._term = undefined;
+ }
+
+ public activate(term: Terminal): void {
+ this._term = term;
+ if (this.initialRelayout) {
+ document.fonts.ready.then(() => this.relayout());
+ }
+ }
+
+ public 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 = 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) {
+ return;
+ }
+ await _loadFonts(dirty);
+ if (this._term) {
+ this._term.options.fontFamily = clean.length ? createFamily(clean) : 'monospace';
+ this._term.options.fontFamily = family;
+ }
+ }
+}
diff --git a/addons/addon-web-fonts/src/tsconfig.json b/addons/addon-web-fonts/src/tsconfig.json
new file mode 100644
index 00000000..cd297308
--- /dev/null
+++ b/addons/addon-web-fonts/src/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es2021",
+ "lib": [
+ "dom",
+ "es2015",
+ "dom.iterable"
+ ],
+ "rootDir": ".",
+ "outDir": "../out",
+ "sourceMap": true,
+ "removeComments": true,
+ "strict": true,
+ "types": [
+ "../../../node_modules/@types/mocha"
+ ],
+ "paths": {
+ "@xterm/addon-web-fonts": [
+ "../typings/addon-web-fonts.d.ts"
+ ]
+ }
+ },
+ "include": [
+ "./**/*",
+ "../../../typings/xterm.d.ts"
+ ]
+}
diff --git a/addons/addon-web-fonts/test/WebFontsAddon.test.ts b/addons/addon-web-fonts/test/WebFontsAddon.test.ts
new file mode 100644
index 00000000..1315b1ee
--- /dev/null
+++ b/addons/addon-web-fonts/test/WebFontsAddon.test.ts
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2024 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+import test from '@playwright/test';
+import { deepStrictEqual, strictEqual } from 'assert';
+import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';
+
+let ctx: ITestContext;
+test.beforeAll(async ({ browser }) => {
+ ctx = await createTestContext(browser);
+ await openTerminal(ctx, { cols: 40 });
+});
+test.afterAll(async () => await ctx.page.close());
+
+test.describe('WebFontsAddon', () => {
+
+ test.beforeEach(async () => {
+ // make sure that we start with no webfonts in the document
+ const empty = await await getDocumentFonts();
+ deepStrictEqual(empty, []);
+ });
+ test.afterEach(async () => {
+ // for font loading tests to work, we have to remove added rules and fonts
+ // to work around the quite aggressive font caching done by the browsers
+ await ctx.page.evaluate(`
+ document.styleSheets[0].deleteRule(1);
+ document.styleSheets[0].deleteRule(0);
+ document.fonts.clear();
+ `);
+ });
+
+ test.describe('font loading at runtime', () => {
+ test('loadFonts (JS)', async () => {
+ await ctx.page.evaluate(`
+ const ff1 = new FontFace('Kongtext', "url(/kongtext.regular.ttf) format('truetype')");
+ const ff2 = new FontFace('BPdots', "url(/bpdots.regular.otf) format('opentype')");
+ loadFonts([ff1, ff2]);
+ `);
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);
+ });
+ test('loadFonts (CSS, unquoted)', async () => {
+ await ctx.page.evaluate(`
+ document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/kongtext.regular.ttf) format('truetype')}", 0);
+ document.styleSheets[0].insertRule("@font-face {font-family: BPdots; src: url(/bpdots.regular.otf) format('opentype')}", 1);
+ loadFonts(['Kongtext', 'BPdots']);
+ `);
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);
+ });
+ test('loadFonts (CSS, quoted)', async ({ browser }) => {
+ // NOTE: firefox preserves family quotes from CSS rules in fontface, all other browsers unquote them
+ await ctx.page.evaluate(`
+ document.styleSheets[0].insertRule("@font-face {font-family: 'Kongtext'; src: url(/kongtext.regular.ttf) format('truetype')}", 0);
+ document.styleSheets[0].insertRule("@font-face {font-family: 'BPdots'; src: url(/bpdots.regular.otf) format('opentype')}", 1);
+ loadFonts(['Kongtext', 'BPdots']);
+ `);
+ if (browser.browserType().name() === 'firefox') {
+ deepStrictEqual(await getDocumentFonts(), [{ family: '"Kongtext"', status: 'loaded' }, { family: '"BPdots"', status: 'loaded' }]);
+ } else {
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);
+ }
+ });
+ test('FontFace hashing', async () => {
+ // multiple calls of `loadFonts` with the same objects shall not bloat document.fonts
+ await ctx.page.evaluate(`
+ const ff1 = new FontFace('Kongtext', "url(/kongtext.regular.ttf) format('truetype')");
+ const ff2 = new FontFace('BPdots', "url(/bpdots.regular.otf) format('opentype')");
+ loadFonts([ff1, ff2]);
+ loadFonts([ff1, ff2]);
+ loadFonts([ff1, ff2]).then(() => loadFonts([ff1, ff2]));
+ `);
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }, { family: 'BPdots', status: 'loaded' }]);
+ });
+
+ test('autoload & relayout from ctor', async ({ browser }) => {
+ // to make this test work, we exclude the default measurement char W (x57) by restricting unicode-range
+ // now the browser will postpone font loading until codepoint is hit --> wrong glyph metrics on first usage
+ const data = await ctx.page.evaluate(`
+ document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
+ `);
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
+
+ // broken case: webfont in ctor without addon usage
+ await ctx.page.evaluate(`
+ window.helperTerm = new Terminal({fontFamily: '"Kongtext", ' + term.options.fontFamily});
+ window.helperTerm.open(term.element);
+ `);
+
+ // safari loads the font, firefox & chrome dont
+ if (browser.browserType().name() === 'webkit') {
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
+ } else {
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
+ }
+
+ // good case: addon fixes layout for webfont in ctor
+ // the relayout happens async, so wait a bit with a promise
+ await ctx.page.evaluate(`
+ window.helperTerm.dispose();
+ window.helperTerm = new Terminal({fontFamily: '"Kongtext", ' + term.options.fontFamily});
+ window._webfontsAddon = new WebFontsAddon();
+ window.helperTerm.loadAddon(window._webfontsAddon);
+ window.helperTerm.open(term.element);
+ `);
+ await timeout(100);
+ deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
+
+ // cleanup this messy test case
+ await ctx.page.evaluate(`
+ window.helperTerm.dispose();
+ window._webfontsAddon.dispose();
+ `);
+ });
+ });
+
+});
+
+async function getDocumentFonts(): Promise {
+ return ctx.page.evaluate(`Array.from(document.fonts).map(ff => ({family: ff.family, status: ff.status}))`);
+}
diff --git a/addons/addon-web-fonts/test/playwright.config.ts b/addons/addon-web-fonts/test/playwright.config.ts
new file mode 100644
index 00000000..22834be1
--- /dev/null
+++ b/addons/addon-web-fonts/test/playwright.config.ts
@@ -0,0 +1,35 @@
+import { PlaywrightTestConfig } from '@playwright/test';
+
+const config: PlaywrightTestConfig = {
+ testDir: '.',
+ timeout: 10000,
+ projects: [
+ {
+ name: 'ChromeStable',
+ use: {
+ browserName: 'chromium',
+ channel: 'chrome'
+ }
+ },
+ {
+ name: 'FirefoxStable',
+ use: {
+ browserName: 'firefox'
+ }
+ },
+ {
+ name: 'WebKit',
+ use: {
+ browserName: 'webkit'
+ }
+ }
+ ],
+ reporter: 'list',
+ webServer: {
+ command: 'npm run start',
+ port: 3000,
+ timeout: 120000,
+ reuseExistingServer: !process.env.CI
+ }
+};
+export default config;
diff --git a/addons/addon-web-fonts/test/tsconfig.json b/addons/addon-web-fonts/test/tsconfig.json
new file mode 100644
index 00000000..120fccdc
--- /dev/null
+++ b/addons/addon-web-fonts/test/tsconfig.json
@@ -0,0 +1,42 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es2021",
+ "lib": [
+ "es2021",
+ ],
+ // "downlevelIteration": true,
+ "rootDir": ".",
+ "outDir": "../out-test",
+ "sourceMap": true,
+ "removeComments": true,
+ "baseUrl": ".",
+ "paths": {
+ "common/*": [
+ "../../../src/common/*"
+ ],
+ "browser/*": [
+ "../../../src/browser/*"
+ ]
+ },
+ "strict": true,
+ "types": [
+ "../../../node_modules/@types/node"
+ ]
+ },
+ "include": [
+ "./**/*",
+ "../../../typings/xterm.d.ts"
+ ],
+ "references": [
+ {
+ "path": "../../../src/common"
+ },
+ {
+ "path": "../../../src/browser"
+ },
+ {
+ "path": "../../../test/playwright"
+ }
+ ]
+}
diff --git a/addons/addon-web-fonts/tsconfig.json b/addons/addon-web-fonts/tsconfig.json
new file mode 100644
index 00000000..2d820dd1
--- /dev/null
+++ b/addons/addon-web-fonts/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "files": [],
+ "include": [],
+ "references": [
+ { "path": "./src" },
+ { "path": "./test" }
+ ]
+}
diff --git a/addons/addon-web-fonts/typings/addon-web-fonts.d.ts b/addons/addon-web-fonts/typings/addon-web-fonts.d.ts
new file mode 100644
index 00000000..c7a159ed
--- /dev/null
+++ b/addons/addon-web-fonts/typings/addon-web-fonts.d.ts
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2024 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+
+import { Terminal, ITerminalAddon } from '@xterm/xterm';
+
+declare module '@xterm/addon-web-fonts' {
+
+ /**
+ * Addon to use webfonts in xterm.js
+ */
+ export class WebFontsAddon implements ITerminalAddon {
+ /**
+ * @param initialRelayout Force initial relayout, if a webfont was found (default true).
+ */
+ constructor(initialRelayout?: 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.
+ *
+ * The returned promise will resolve, when font loading and layouting are done.
+ */
+ 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;
+}
diff --git a/addons/addon-web-fonts/webpack.config.js b/addons/addon-web-fonts/webpack.config.js
new file mode 100644
index 00000000..f75d2d50
--- /dev/null
+++ b/addons/addon-web-fonts/webpack.config.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2024 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+const path = require('path');
+
+const addonName = 'WebFontsAddon';
+const mainFile = 'addon-web-fonts.js';
+
+module.exports = {
+ entry: `./out/${addonName}.js`,
+ devtool: 'source-map',
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: ["source-map-loader"],
+ enforce: "pre",
+ exclude: /node_modules/
+ }
+ ]
+ },
+ output: {
+ filename: mainFile,
+ path: path.resolve('./lib'),
+ library: addonName,
+ libraryTarget: 'umd',
+ // Force usage of globalThis instead of global / self. (This is cross-env compatible)
+ globalObject: 'globalThis',
+ },
+ mode: 'production'
+};
diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs
index 23303781..5e0a370c 100644
--- a/bin/esbuild.mjs
+++ b/bin/esbuild.mjs
@@ -140,6 +140,7 @@ if (config.addon) {
"@xterm/addon-progress": "./addons/addon-progress/lib/addon-progress.mjs",
"@xterm/addon-search": "./addons/addon-search/lib/addon-search.mjs",
"@xterm/addon-serialize": "./addons/addon-serialize/lib/addon-serialize.mjs",
+ "@xterm/addon-web-fonts": "./addons/addon-web-fonts/lib/addon-web-fonts.mjs",
"@xterm/addon-web-links": "./addons/addon-web-links/lib/addon-web-links.mjs",
"@xterm/addon-webgl": "./addons/addon-webgl/lib/addon-webgl.mjs",
"@xterm/addon-unicode11": "./addons/addon-unicode11/lib/addon-unicode11.mjs",
diff --git a/bin/publish.js b/bin/publish.js
index fd676b3a..0fa2fadd 100644
--- a/bin/publish.js
+++ b/bin/publish.js
@@ -51,6 +51,7 @@ const addonPackageDirs = [
path.resolve(__dirname, '../addons/addon-serialize'),
path.resolve(__dirname, '../addons/addon-unicode11'),
path.resolve(__dirname, '../addons/addon-unicode-graphemes'),
+ path.resolve(__dirname, '../addons/addon-web-fonts'),
path.resolve(__dirname, '../addons/addon-web-links'),
path.resolve(__dirname, '../addons/addon-webgl')
];
diff --git a/bin/test_integration.js b/bin/test_integration.js
index 68f3ffdb..37e6aca7 100644
--- a/bin/test_integration.js
+++ b/bin/test_integration.js
@@ -30,6 +30,7 @@ const addons = [
'serialize',
'unicode-graphemes',
'unicode11',
+ 'web-fonts',
'web-links',
'webgl',
];
diff --git a/demo/client/client.ts b/demo/client/client.ts
index 6a0cdead..68202ad7 100644
--- a/demo/client/client.ts
+++ b/demo/client/client.ts
@@ -18,6 +18,7 @@ import { AttachAddon } from '@xterm/addon-attach';
import { AddonImageWindow } from './components/window/addonImageWindow';
import { AddonSearchWindow } from './components/window/addonSearchWindow';
import { AddonSerializeWindow } from './components/window/addonSerializeWindow';
+import { AddonWebFontsWindow } from './components/window/addonWebFontsWindow';
import { AddonsWindow } from './components/window/addonsWindow';
import { CellInspectorWindow } from './components/window/cellInspectorWindow';
import { ControlBar } from './components/controlBar';
@@ -32,6 +33,7 @@ import { LigaturesAddon } from '@xterm/addon-ligatures';
import { ProgressAddon } from '@xterm/addon-progress';
import { SearchAddon, ISearchOptions } from '@xterm/addon-search';
import { SerializeAddon } from '@xterm/addon-serialize';
+import { WebFontsAddon } from '@xterm/addon-web-fonts';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { WebglAddon } from '@xterm/addon-webgl';
import { Unicode11Addon } from '@xterm/addon-unicode11';
@@ -75,6 +77,7 @@ const addons: AddonCollection = {
progress: { name: 'progress', ctor: ProgressAddon, canChange: true },
search: { name: 'search', ctor: SearchAddon, canChange: true },
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
+ webFonts: { name: 'webFonts', ctor: WebFontsAddon, canChange: true },
webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true },
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true },
@@ -213,7 +216,8 @@ if (document.location.pathname === '/test') {
addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true });
controlBar.registerWindow(new AddonSerializeWindow(typedTerm, addons), { afterId: 'addon-search', hidden: true, italics: true });
controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true });
- addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });
+ controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });
+ addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true });
controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler }), { afterId: 'options' });
actionElements = {
findNext: addonSearchWindow.findNextInput,
@@ -229,6 +233,7 @@ if (document.location.pathname === '/test') {
controlBar.setTabVisible('addon-search', true);
controlBar.setTabVisible('addon-serialize', true);
controlBar.setTabVisible('addon-image', true);
+ controlBar.setTabVisible('addon-web-fonts', true);
addonWebglWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
addons.webgl.instance.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));
addons.webgl.instance.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));
@@ -295,6 +300,7 @@ function createTerminal(): Terminal {
console.warn(e);
}
addons.webLinks.instance = new WebLinksAddon();
+ addons.webFonts.instance = new WebFontsAddon();
typedTerm.loadAddon(addons.fit.instance);
typedTerm.loadAddon(addons.image.instance);
typedTerm.loadAddon(addons.progress.instance);
@@ -302,6 +308,7 @@ function createTerminal(): Terminal {
typedTerm.loadAddon(addons.serialize.instance);
typedTerm.loadAddon(addons.unicodeGraphemes.instance);
typedTerm.loadAddon(addons.webLinks.instance);
+ typedTerm.loadAddon(addons.webFonts.instance);
typedTerm.loadAddon(addons.clipboard.instance);
window.term = term; // Expose `term` to window for debugging purposes
diff --git a/demo/client/components/window/addonWebFontsWindow.ts b/demo/client/components/window/addonWebFontsWindow.ts
new file mode 100644
index 00000000..c8fb288d
--- /dev/null
+++ b/demo/client/components/window/addonWebFontsWindow.ts
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2024 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { loadFonts } from '@xterm/addon-web-fonts';
+import { BaseWindow } from './baseWindow';
+import type { IControlWindow } from '../controlBar';
+
+export class AddonWebFontsWindow extends BaseWindow implements IControlWindow {
+ public readonly id = 'addon-web-fonts';
+ public readonly label = 'web-fonts';
+
+ public build(container: HTMLElement): void {
+ const dl = document.createElement('dl');
+
+ // Kongtext font button
+ const dtKongtext = document.createElement('dt');
+ dtKongtext.textContent = 'Kongtext';
+ dl.appendChild(dtKongtext);
+
+ const ddKongtext = document.createElement('dd');
+ const btnKongtext = document.createElement('button');
+ btnKongtext.textContent = 'Load Kongtext';
+ btnKongtext.title = 'Load Kongtext font and apply C64 style';
+ btnKongtext.addEventListener('click', async () => {
+ const ff = new FontFace('Kongtext', 'url(/fonts/kongtext.regular.ttf) format(\'truetype\')');
+ await loadFonts([ff]);
+ this._terminal.options.fontFamily = 'Kongtext';
+ this._terminal.options.lineHeight = 1.3;
+ this._addons.fit.instance?.fit();
+ setTimeout(() => this._terminal.write('\x1b[?12h\x1b]12;#776CF9\x07\x1b[38;2;119;108;249;48;2;21;8;150m\x1b[2J\x1b[2;5H**** COMMODORE 64 BASIC V2 ****\r\n\r\n 64K RAM SYSTEM 38911 BASIC BYTES FREE\r\n\r\nREADY.\r\nLOAD '), 1000);
+ setTimeout(() => { this._terminal.write('🤣\x1b[m\x1b[99;1H'); this._terminal.input('\r'); }, 5000);
+ });
+ ddKongtext.appendChild(btnKongtext);
+ dl.appendChild(ddKongtext);
+
+ // BPdots font button
+ const dtBpdots = document.createElement('dt');
+ dtBpdots.textContent = 'BPdots';
+ dl.appendChild(dtBpdots);
+
+ const ddBpdots = document.createElement('dd');
+ const btnBpdots = document.createElement('button');
+ btnBpdots.textContent = 'Load BPdots';
+ btnBpdots.title = 'Load BPdots font';
+ btnBpdots.addEventListener('click', async () => {
+ document.styleSheets[0].insertRule('@font-face { font-family: "BPdots"; src: url(/fonts/bpdots.regular.otf) format("opentype"); weight: 400 }', 0);
+ await loadFonts(['BPdots']);
+ this._terminal.options.fontFamily = 'BPdots';
+ this._terminal.options.lineHeight = 1.3;
+ this._terminal.options.fontSize = 20;
+ this._addons.fit.instance?.fit();
+ });
+ ddBpdots.appendChild(btnBpdots);
+ dl.appendChild(ddBpdots);
+
+ container.appendChild(dl);
+ }
+}
diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json
index 66a068bc..17aae876 100644
--- a/demo/client/tsconfig.json
+++ b/demo/client/tsconfig.json
@@ -14,6 +14,7 @@
"@xterm/addon-progress": ["../../addons/addon-progress"],
"@xterm/addon-search": ["../../addons/addon-search"],
"@xterm/addon-serialize": ["../../addons/addon-serialize"],
+ "@xterm/addon-web-fonts": ["../../addons/addon-web-fonts"],
"@xterm/addon-web-links": ["../../addons/addon-web-links"],
"@xterm/addon-webgl": ["../../addons/addon-webgl"],
"@xterm/addon-unicode11": ["../../addons/addon-unicode11"],
diff --git a/demo/client/types.ts b/demo/client/types.ts
index bc73ae0e..4790fd4f 100644
--- a/demo/client/types.ts
+++ b/demo/client/types.ts
@@ -15,10 +15,11 @@ import type { SearchAddon } from '@xterm/addon-search';
import type { SerializeAddon } from '@xterm/addon-serialize';
import type { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';
import type { Unicode11Addon } from '@xterm/addon-unicode11';
+import type { WebFontsAddon } from '@xterm/addon-web-fonts';
import type { WebLinksAddon } from '@xterm/addon-web-links';
import type { WebglAddon } from '@xterm/addon-webgl';
-export type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures';
+export type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webFonts' | 'webLinks' | 'webgl' | 'ligatures';
export interface IDemoAddon {
name: T;
@@ -32,11 +33,12 @@ export interface IDemoAddon {
T extends 'progress' ? typeof ProgressAddon :
T extends 'search' ? typeof SearchAddon :
T extends 'serialize' ? typeof SerializeAddon :
- T extends 'webLinks' ? typeof WebLinksAddon :
- T extends 'unicode11' ? typeof Unicode11Addon :
- T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
- T extends 'webgl' ? typeof WebglAddon :
- never
+ T extends 'webFonts' ? typeof WebFontsAddon :
+ T extends 'webLinks' ? typeof WebLinksAddon :
+ T extends 'unicode11' ? typeof Unicode11Addon :
+ T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
+ T extends 'webgl' ? typeof WebglAddon :
+ never
);
instance?: (
T extends 'attach' ? AttachAddon :
@@ -47,11 +49,12 @@ export interface IDemoAddon {
T extends 'progress' ? ProgressAddon :
T extends 'search' ? SearchAddon :
T extends 'serialize' ? SerializeAddon :
- T extends 'webLinks' ? WebLinksAddon :
- T extends 'unicode11' ? Unicode11Addon :
- T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
- T extends 'webgl' ? WebglAddon :
- never
+ T extends 'webFonts' ? WebFontsAddon :
+ T extends 'webLinks' ? WebLinksAddon :
+ T extends 'unicode11' ? Unicode11Addon :
+ T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
+ T extends 'webgl' ? WebglAddon :
+ never
);
}
diff --git a/demo/fonts/bpdots.regular.otf b/demo/fonts/bpdots.regular.otf
new file mode 100644
index 00000000..e5b5d1dc
Binary files /dev/null and b/demo/fonts/bpdots.regular.otf differ
diff --git a/demo/fonts/font-licenses.txt b/demo/fonts/font-licenses.txt
new file mode 100644
index 00000000..5b1b5ba6
--- /dev/null
+++ b/demo/fonts/font-licenses.txt
@@ -0,0 +1,41 @@
+
+BPdots is licensed under the Creative Commons Attribution-NoDerivs License (CC BY-ND).
+
+
+kongtext license text:
+
+Thanks for downloading one of codeman38's retro video game fonts,
+as seen on Memepool, BoingBoing, and all around the blogosphere.
+
+So, you're wondering what the license is for these fonts? Pretty simple;
+it's based upon that used for Bitstream's Vera font set .
+
+Basically, here are the key points summarized, in as little legalese as possible;
+I hate reading license agreements as much as you probably do:
+
+With one specific exception, you have full permission to bundle these fonts in
+your own free or commercial projects-- and by projects, I'm referring to not
+just software but also electronic documents and print publications.
+
+So what's the exception? Simple: you can't re-sell these fonts
+in a commercial font collection. I've seen too many font CDs for sale in stores
+that are just a repackaging of thousands of freeware fonts found on the internet,
+and in my mind, that's quite a bit like highway robbery. Note that this *only*
+applies to products that are font collections in and of themselves;
+you may freely bundle these fonts with an operating system, application program,
+or the like.
+
+Feel free to modify these fonts and even to release the modified versions,
+as long as you change the original font names (to ensure consistency among
+people with the font installed) and as long as you give credit somewhere
+in the font file to codeman38 or zone38.net. I may even incorporate these changes
+into a later version of my fonts if you wish to send me the modifed fonts via e-mail.
+
+Also, feel free to mirror these fonts on your own site, as long as you make it
+reasonably clear that these fonts are not your own work. I'm not asking for much;
+linking to zone38.net or even just mentioning the nickname codeman38 should be enough.
+
+Well, that pretty much sums it up... so without further ado,
+install and enjoy these fonts from the golden age of video games.
+
+[ codeman38 | cody@zone38.net | http://www.zone38.net/ ]
diff --git a/demo/fonts/kongtext.regular.ttf b/demo/fonts/kongtext.regular.ttf
new file mode 100644
index 00000000..5e4d65fd
Binary files /dev/null and b/demo/fonts/kongtext.regular.ttf differ
diff --git a/demo/server/server.ts b/demo/server/server.ts
index 97a91243..0f4a79b3 100644
--- a/demo/server/server.ts
+++ b/demo/server/server.ts
@@ -45,6 +45,8 @@ function startServer(): void {
res.sendFile(demoRoot + '/index.css');
});
+ app.use('/fonts', express.static(demoRoot + '/fonts'));
+
app.use('/dist', express.static(demoRoot + '/dist'));
app.use('/src', express.static(demoRoot + '/src'));
diff --git a/package-lock.json b/package-lock.json
index 2e55f3de..4c218b70 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -145,6 +145,11 @@
"version": "0.9.0",
"license": "MIT"
},
+ "addons/addon-web-fonts": {
+ "name": "@xterm/addon-web-fonts",
+ "version": "0.1.0",
+ "license": "MIT"
+ },
"addons/addon-web-links": {
"name": "@xterm/addon-web-links",
"version": "0.12.0",
@@ -239,7 +244,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -553,7 +557,6 @@
"url": "https://opencollective.com/csstools"
}
],
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -595,7 +598,6 @@
"url": "https://opencollective.com/csstools"
}
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1897,7 +1899,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz",
"integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==",
"dev": true,
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.50.1",
"@typescript-eslint/types": "8.50.1",
@@ -2297,6 +2298,10 @@
"resolved": "addons/addon-unicode11",
"link": true
},
+ "node_modules/@xterm/addon-web-fonts": {
+ "resolved": "addons/addon-web-fonts",
+ "link": true
+ },
"node_modules/@xterm/addon-web-links": {
"resolved": "addons/addon-web-links",
"link": true
@@ -2342,7 +2347,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2732,7 +2736,6 @@
"url": "https://github.com/sponsors/ai"
}
],
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3734,7 +3737,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -7090,7 +7092,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -7958,7 +7959,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8142,7 +8142,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz",
"integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
"dev": true,
- "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -8191,7 +8190,6 @@
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
"integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"dev": true,
- "peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.2.0",
diff --git a/tsconfig.all.json b/tsconfig.all.json
index 5983deec..b0ed0279 100644
--- a/tsconfig.all.json
+++ b/tsconfig.all.json
@@ -17,6 +17,7 @@
{ "path": "./addons/addon-serialize" },
{ "path": "./addons/addon-unicode11" },
{ "path": "./addons/addon-unicode-graphemes" },
+ { "path": "./addons/addon-web-fonts" },
{ "path": "./addons/addon-web-links" },
{ "path": "./addons/addon-webgl" }
]