Skip to content

Commit

Permalink
Merge pull request #2847 from LabhanshAgrawal/ligatures-addon
Browse files Browse the repository at this point in the history
Merge ligatures addon into core repo
  • Loading branch information
Tyriar authored May 3, 2020
2 parents 6f1f4ba + 36e241c commit fbdb01d
Show file tree
Hide file tree
Showing 19 changed files with 1,184 additions and 5 deletions.
6 changes: 4 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
"addons/xterm-addon-web-links/src/tsconfig.json",
"addons/xterm-addon-webgl/src/tsconfig.json",
"addons/xterm-addon-serialize/src/tsconfig.json",
"addons/xterm-addon-serialize/benchmark/tsconfig.json"
"addons/xterm-addon-serialize/benchmark/tsconfig.json",
"addons/xterm-addon-ligatures/src/tsconfig.json"
],
"sourceType": "module"
},
"ignorePatterns": [
"**/typings/*.d.ts"
"**/typings/*.d.ts",
"**/node_modules"
],
"plugins": [
"@typescript-eslint"
Expand Down
13 changes: 13 additions & 0 deletions addons/xterm-addon-ligatures/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules/
.nyc_output/
coverage/

lib/
fonts/

.env
.vscode/
*.swp
*.tgz
npm-debug.log*
yarn-error.log*
48 changes: 48 additions & 0 deletions addons/xterm-addon-ligatures/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/

# Whitelist - entries to be included must be negated with "!"
!*.js
!*.json

# Whitelist - lib/
!lib/**/*.d.ts

!lib/**/*.js
!lib/**/*.js.map

# Whitelist - out/
!out/**/*.d.ts

!out/**/*.js
!out/**/*.js.map

# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts

!src/**/*.js
!src/**/*.js.map

# Whitelist - typings/
!typings/**/*.d.ts

# Blacklist - (normal behavior) these will override any whitelist
*.test.ts
*.test.d.ts
*.test.js
*.test.js.map

docs/
/.idea/
.vscode/
coverage/
.nyc_output/
fonts/

**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
21 changes: 21 additions & 0 deletions addons/xterm-addon-ligatures/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018

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.
53 changes: 53 additions & 0 deletions addons/xterm-addon-ligatures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## xterm-addon-ligatures

Add support for programming ligatures to [xterm.js] when running in environments with access to [Node.js] APIs (such as [Electron]).

### Requirements

* [Node.js] 8.x or higher (present in [Electron] 1.8.3 or higher)
* [xterm.js] 4.0.0 or higher using the default canvas renderer

### Install

```bash
npm install --save xterm-addon-ligatures
```

### Usage

```ts
import { Terminal } from 'xterm';
import { LigaturesAddon } from 'xterm-addon-ligatures';

const terminal = new Terminal();
const ligaturesAddon = new LigaturesAddon();
terminal.open(containerElement);
terminal.loadAddon(ligaturesAddon);
```

### How It Works

In a browser environment, font ligature information is read directly by the web browser and used to render text correctly without any intervention from the developer. As of version 3, xterm.js uses the canvas to render characters individually, resulting in a significant performance boost. However, this means that it can no longer lean on the browser to determine when to draw font ligatures.

This package locates the font file on disk for the font currently in use by the terminal and parses the ligature information out of it (via the [font-ligatures] package). As text is rendered in xterm.js, this package annotates it with the locations of ligatures, allowing xterm.js to render it correctly.

Since this package depends on being able to find and resolve a system font from disk, it has to have system access that isn't available in the web browser. As a result, this package is mainly useful in environments that combine browser and Node.js runtimes (such as [Electron]).

### Fonts

This package makes use of the following fonts for testing:

* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita
Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and
Fira Sans
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with
reserved name Iosevka

[xterm.js]: https://github.com/xtermjs/xterm.js
[Electron]: https://electronjs.org/
[Node.js]: https://nodejs.org/
[font-ligatures]: https://github.com/princjef/font-ligatures
[Fira Code]: https://github.com/tonsky/FiraCode
[Fira Code License]: https://github.com/tonsky/FiraCode/blob/master/LICENSE
[Iosevka]: https://github.com/be5invis/Iosevka
[Iosevka License]: https://github.com/be5invis/Iosevka/blob/master/LICENSE.md
75 changes: 75 additions & 0 deletions addons/xterm-addon-ligatures/bin/download-fonts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/

const fs = require('fs');
const path = require('path');
const util = require('util');

const axios = require('axios').default;
const mkdirp = require('mkdirp');
const yauzl = require('yauzl');

const urls = {
fira: 'https://github.com/tonsky/FiraCode/raw/master/distr/otf/FiraCode-Regular.otf',
iosevka: 'https://github.com/be5invis/Iosevka/releases/download/v1.14.3/01-iosevka-1.14.3.zip'
};

const writeFile = util.promisify(fs.writeFile);
const fontsFolder = path.join(__dirname, '../fonts');

async function download() {
await mkdirp(fontsFolder);

await downloadFiraCode();
await downloadIosevka();

console.log('Loaded all fonts for testing')
}

async function downloadFiraCode() {
const file = path.join(fontsFolder, 'firaCode.otf');
if (await util.promisify(fs.exists)(file)) {
console.log('Fira Code already loaded');
} else {
console.log('Downloading Fira Code...');
await writeFile(
file,
(await axios.get(urls.fira, { responseType: 'arraybuffer' })).data
);
}
}

async function downloadIosevka() {
const file = path.join(fontsFolder, 'iosevka.ttf');
if (await util.promisify(fs.exists)(file)) {
console.log('Iosevka already loaded');
} else {
console.log('Downloading Iosevka...');
const iosevkaContents = (await axios.get(urls.iosevka, { responseType: 'arraybuffer' })).data;
const iosevkaZipfile = await util.promisify(yauzl.fromBuffer)(iosevkaContents);
await new Promise((resolve, reject) => {
iosevkaZipfile.on('entry', entry => {
if (entry.fileName === 'ttf/iosevka-regular.ttf') {
iosevkaZipfile.openReadStream(entry, (err, stream) => {
if (err) {
return reject(err);
}

const writeStream = fs.createWriteStream(file);
stream.pipe(writeStream);
writeStream.on('close', () => resolve());
});
}
});
});
}
}

download();

process.on('unhandledRejection', e => {
console.error(e);
process.exit(1);
});
47 changes: 47 additions & 0 deletions addons/xterm-addon-ligatures/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "xterm-addon-ligatures",
"version": "0.2.1",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/xterm-addon-ligatures.js",
"types": "typings/xterm-addon-ligatures.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"engines": {
"node": ">8.0.0"
},
"scripts": {
"prepare": "node bin/download-fonts.js",
"build": "tsc -p src",
"watch": "tsc -w -p src",
"prepackage": "npm run build",
"package": "webpack",
"pretest": "npm run build",
"test": "nyc mocha out/**/*.test.js",
"prepublish": "npm run package"
},
"keywords": [
"font",
"ligature",
"xterm",
"xterm.js",
"terminal"
],
"license": "MIT",
"dependencies": {
"font-finder": "^1.0.4",
"font-ligatures": "^1.3.2"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
"axios": "^0.18.0",
"mkdirp": "^0.5.1",
"sinon": "^6.1.3",
"yauzl": "^2.10.0"
},
"peerDependencies": {
"xterm": "^4.0.0"
}
}
23 changes: 23 additions & 0 deletions addons/xterm-addon-ligatures/src/LigaturesAddon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/

import { Terminal } from 'xterm';
import { enableLigatures } from '.';

export interface ITerminalAddon {
activate(terminal: Terminal): void;
dispose(): void;
}

export class LigaturesAddon implements ITerminalAddon {
constructor() {}

public activate(terminal: Terminal): void {
enableLigatures(terminal);
}

public dispose(): void {}
}

53 changes: 53 additions & 0 deletions addons/xterm-addon-ligatures/src/font.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/

import * as fontFinder from 'font-finder';
import * as fontLigatures from 'font-ligatures';

import parse from './parse';

let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;

/**
* Loads the font ligature wrapper for the specified font family if it could be
* resolved, throwing if it is unable to find a suitable match.
* @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<fontLigatures.Font | undefined> {
if (!fontsPromise) {
fontsPromise = fontFinder.list();
}

const fonts = await fontsPromise;
for (const family of parse(fontFamily)) {
// If we reach one of the generic font families, the font resolution
// will end for the browser and we can't determine the specific font
// used. Throw.
if (genericFontFamilies.includes(family)) {
return undefined;
}

if (fonts.hasOwnProperty(family) && fonts[family].length > 0) {
return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize });
}
}

// If none of the fonts could resolve, throw an error
return undefined;
}

// https://drafts.csswg.org/css-fonts-4/#generic-font-families
const genericFontFamilies = [
'serif',
'sans-serif',
'cursive',
'fantasy',
'monospace',
'system-ui',
'emoji',
'math',
'fangsong'
];
Loading

0 comments on commit fbdb01d

Please sign in to comment.