diff --git a/addons/xterm-addon-ligatures/package.json b/addons/xterm-addon-ligatures/package.json index 4e2b4056..f96ce167 100644 --- a/addons/xterm-addon-ligatures/package.json +++ b/addons/xterm-addon-ligatures/package.json @@ -32,7 +32,7 @@ "license": "MIT", "dependencies": { "font-finder": "^1.1.0", - "font-ligatures": "^1.3.3" + "font-ligatures": "^1.4.0" }, "devDependencies": { "@types/sinon": "^5.0.1", diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 825fc797..0d01d3cc 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -3,12 +3,19 @@ * @license MIT */ -import * as fontFinder from 'font-finder'; -import * as fontLigatures from 'font-ligatures'; +import {FontList} from 'font-finder'; +import {Font, loadBuffer, loadFile} from 'font-ligatures'; import parse from './parse'; -let fontsPromise: Promise | undefined = undefined; +interface IFontMetadata { + family: string; + fullName: string; + postscriptName: string; + blob: () => Promise; +} + +let fontsPromise: Promise> | undefined = undefined; /** * Loads the font ligature wrapper for the specified font family if it could be @@ -16,9 +23,43 @@ let fontsPromise: Promise | undefined = undefined; * @param fontFamily The CSS font family definition to resolve * @param cacheSize The size of the ligature cache to maintain if the font is resolved */ -export default async function load(fontFamily: string, cacheSize: number): Promise { +export default async function load(fontFamily: string, cacheSize: number): Promise { + if (!fontsPromise && 'fonts' in navigator) { + try { + const status = await (navigator as any).permissions.request?.({ + name: 'local-fonts' + }); + if (status && status.state !== 'granted') { + throw new Error('Permission to access local fonts not granted.'); + } + } catch (err) { + // A `TypeError` indicates the 'local-fonts' + // permission is not yet implemented, so + // only `throw` if this is _not_ the problem. + if (err.name !== 'TypeError') { + throw err; + } + } + const fonts: Record = {}; + try { + const fontsIterator: AsyncIterableIterator = (navigator as any).fonts.query(); + for await (const metadata of fontsIterator) { + if (!fonts.hasOwnProperty(metadata.family)) { + fonts[metadata.family] = []; + } + fonts[metadata.family].push(metadata); + } + fontsPromise = Promise.resolve(fonts); + } catch (err) { + console.error(err.name, err.message); + } + } if (!fontsPromise) { - fontsPromise = fontFinder.list(); + try { + fontsPromise = (await import('font-finder')).list(); + } catch (err) { + fontsPromise = Promise.resolve({}); + } } const fonts = await fontsPromise; @@ -31,7 +72,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi } if (fonts.hasOwnProperty(family) && fonts[family].length > 0) { - return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize }); + const font = fonts[family][0]; + if ('blob' in font) { + return loadBuffer(await (await font.blob()).arrayBuffer(), {cacheSize}); + } + return await loadFile(font.path, {cacheSize}); } } diff --git a/addons/xterm-addon-ligatures/webpack.config.js b/addons/xterm-addon-ligatures/webpack.config.js index 253e1207..6bdbd156 100644 --- a/addons/xterm-addon-ligatures/webpack.config.js +++ b/addons/xterm-addon-ligatures/webpack.config.js @@ -30,7 +30,17 @@ module.exports = { }, mode: 'production', externals: { - 'font-finder':'font-finder', - 'font-ligatures':'font-ligatures' + 'font-finder': 'font-finder', + 'stream': 'stream', + 'os': 'os', + 'util': 'util' + }, + resolve: { + fallback: { + stream: false, + util: false, + os: false, + path: false + } } }; diff --git a/addons/xterm-addon-ligatures/yarn.lock b/addons/xterm-addon-ligatures/yarn.lock index 049b798a..2191ce37 100644 --- a/addons/xterm-addon-ligatures/yarn.lock +++ b/addons/xterm-addon-ligatures/yarn.lock @@ -87,19 +87,19 @@ font-finder@^1.0.3: font-finder@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858" + resolved "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858" integrity sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw== dependencies: get-system-fonts "^2.0.0" promise-stream-reader "^1.0.1" -font-ligatures@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.3.tgz#63fff18dc8adb3a11fe5eec1f4e8d7edfa8075b9" - integrity sha512-NSGpHgVNX81M7AWS1XylK1UZbN3QllfUIDAAuPv6TUcl5O2b781JcKS5L2RopAU0AqlTyX3ZuX/04eaMpbVrHA== +font-ligatures@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.0.tgz#6a7b370d96be1358dddfad67830e82fbfd59e6dc" + integrity sha512-n7DFnnEpJ0NrVoLqZIL4tMGVs+CnFwQc92m80LWyrbgAFO4x234+t2/H9o4eOYA1eh6ta9dZAEEsJAwsBdNezA== dependencies: font-finder "^1.0.3" - lru-cache "^4.1.3" + lru-cache "^6.0.0" opentype.js "^0.8.0" get-system-fonts@^2.0.0: @@ -144,12 +144,12 @@ lolex@^5.0.1: dependencies: "@sinonjs/commons" "^1.7.0" -lru-cache@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" + yallist "^4.0.0" minimist@^1.2.5: version "1.2.5" @@ -198,10 +198,6 @@ promise-stream-reader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649" -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - sinon@6.3.5: version "6.3.5" resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0" @@ -232,9 +228,10 @@ type-detect@4.0.8, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yauzl@^2.10.0: version "2.10.0" diff --git a/demo/client.ts b/demo/client.ts index 93b7c26c..1f5eb49a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -16,6 +16,7 @@ import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAdd import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon'; import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon'; +import { LigaturesAddon } from '../addons/xterm-addon-ligatures/out/LigaturesAddon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -26,6 +27,7 @@ import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Add // import { WebLinksAddon } from 'xterm-addon-web-links'; // import { WebglAddon } from 'xterm-addon-webgl'; // import { Unicode11Addon } from 'xterm-addon-unicode11'; +// import { LigaturesAddon } from 'xterm-addon-ligatures'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -41,6 +43,7 @@ export interface IWindowWithTerminal extends Window { WebLinksAddon?: typeof WebLinksAddon; WebglAddon?: typeof WebglAddon; Unicode11Addon?: typeof Unicode11Addon; + LigaturesAddon?: typeof LigaturesAddon; } declare let window: IWindowWithTerminal; @@ -50,7 +53,7 @@ let socketURL; let socket; let pid; -type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl'; +type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -62,8 +65,9 @@ interface IDemoAddon { T extends 'serialize' ? typeof SerializeAddon : T extends 'web-links' ? typeof WebLinksAddon : T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : typeof WebglAddon; - instance?: + instance?: T extends 'attach' ? AttachAddon : T extends 'fit' ? FitAddon : T extends 'search' ? SearchAddon : @@ -71,6 +75,7 @@ interface IDemoAddon { T extends 'web-links' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : never; } @@ -81,7 +86,8 @@ const addons: { [T in AddonType]: IDemoAddon} = { serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true }, 'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true }, webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, - unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true } + unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }, + ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true } }; const terminalContainer = document.getElementById('terminal-container'); @@ -117,6 +123,7 @@ const disposeRecreateButtonHandler = () => { addons.search.instance = undefined; addons.serialize.instance = undefined; addons.unicode11.instance = undefined; + addons.ligatures.instance = undefined; addons['web-links'].instance = undefined; addons.webgl.instance = undefined; document.getElementById('dispose').innerHTML = 'Recreate Terminal'; @@ -133,6 +140,7 @@ if (document.location.pathname === '/test') { window.SearchAddon = SearchAddon; window.SerializeAddon = SerializeAddon; window.Unicode11Addon = Unicode11Addon; + window.LigaturesAddon = LigaturesAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; } else { @@ -158,6 +166,7 @@ function createTerminal(): void { addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.unicode11.instance = new Unicode11Addon(); + addons.ligatures.instance = new LigaturesAddon(); // TODO: Remove arguments when link provider API is the default addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true); typedTerm.loadAddon(addons.fit.instance); @@ -181,6 +190,7 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); + typedTerm.loadAddon(addons.ligatures.instance); addons.fit.instance!.fit(); term.focus(); diff --git a/demo/start.js b/demo/start.js index a14627c1..c4caea77 100644 --- a/demo/start.js +++ b/demo/start.js @@ -49,6 +49,13 @@ const clientConfig = { alias: { common: path.resolve('./out/common'), browser: path.resolve('./out/browser') + }, + fallback: { + stream: false, + util: false, + os: false, + path: false, + fs: false } }, output: {