Xterm.js: Terminal font letter spacing is huge on Arch Linux (not on Windows or Ubuntu)

Created on 4 Jun 2020  路  14Comments  路  Source: xtermjs/xterm.js

Details

I've only tested this on Windows 10, Ubuntu, and Arch Linux, but I've only had the problem on Arch, in Chrome, Konqueror, Vivaldi, and GNOME Web.
Essentially, the each character in the terminal is its normal width, but the space around it is doubled, so each character takes up the space of two. Here's some images from Cockpit on Chrome, and some more from xtermjs.org, also on Chrome.
And as I said, on Windows it's not an issue.

  • Browser and browser version: Chrome 81.0.4044.138 (Official Build) (64-bit), Konqueror 4.0.97, Vivaldi 3.0.1874.38 (Stable channel) (64-bit), and GNOME Web 3.36.2
  • OS version: Arch Linux (last pacman -Syu was a day ago)
  • xterm.js version: 4.6.0 within the Cockpit terminal on Fedora Server 32, and even on the xtermjs.org website (!).

Steps to reproduce

  1. Open any of the browsers listed (and I don't think it's limited to just those, but curiously it's not an issue in VS Code...) on Arch Linux.
  2. Look at an xterm.js terminal and observe the extremely wide letter spacing.
needs more info

Most helpful comment

Here's an addon that waits for fonts to load and then opens: https://github.com/CoderPad/xterm-webfont

All 14 comments

It looks like the font is not monospace, which is odd as xtermjs.org should be using monospace as a fallback:

https://github.com/xtermjs/xterm.js/blob/d57e099e6ed328bd52ea17eb2d4685070591904b/src/common/services/OptionsService.ts#L29

Maybe you have either a non-monospace font configured as the system monospace font or a different courier font? 馃

I have Roboto Mono as the monospace font. Other websites are able to use it. I agree with you about it not looking monospace; it looks like Lato, my system font. Maybe I should install Courier New?

@mattfbacon does it work if you run the demo and change rendererType to dom?

For years, this has been my experience too, especially with a clean cache. Today, it occurs frequently using Firefox (example at bottom), but not Chrome. I had to put in some workarounds that has helped avoid the startup 'ugliness', such as forcing a resize window.dispatchEvent(new Event('resize')) and locally serving up fonts in <head>, i.e.,

  <link rel="preload" href="notoemoji.woff2" as="font" type="font/woff2" crossorigin="anonymous">
  <link rel="preload" href="notomono.woff2" as="font" type="font/woff2" crossorigin="anonymous">
  <link rel="preload" href="vt220.woff2" as="font" type="font/woff2" crossorigin="anonymous">

I do change font depending on different session states, i.e., if (carrier) term.setOption('fontFamily', 'notomono,notoemoji') and recalculate font size in window resize events which always clears the character spacing issue (a smaller font sized used in a larger sized cell) for me. My startup xterm settings:

options (
    const options: ITerminalOptions = {
        bellSound: BELL_SOUND, bellStyle: 'sound', cursorBlink: false, drawBoldTextInBrightColors: true,
        cols: cols, rows: rows, scrollback: 500,
        fontFamily: 'tty,notoemoji', fontSize: 24, fontWeight: '400', fontWeightBold: '500',
        theme: {
            foreground: 'Silver', background: 'Black', cursor: 'PowderBlue',
            black: 'Black', red: 'DarkRed', green: 'ForestGreen', yellow: 'SandyBrown',
            blue: 'MediumBlue', magenta: 'MediumOrchid', cyan: 'DarkCyan', white: 'Silver',
            brightBlack: 'DimGray', brightRed: 'Red', brightGreen: 'LightGreen', brightYellow: 'Gold',
            brightBlue: 'RoyalBlue', brightMagenta: 'Violet', brightCyan: 'Cyan', brightWhite: 'Snow'
        },
        wordSeparator: ` .:;?!"'<>(/)[=]`
    }

$ firefox https://play.ddgame.us
xterm-issues-2963

$ chromium-browser https://play.ddgame.us
$ google-chrome https://play.ddgame.us
... both render fine at startup using the served font.

Okay, I've downloaded and run the demo. I have the same issue in DOM and Canvas mode. Here are the screenshots: https://imgur.com/a/wEPmSGE

Interestingly, if I modify the font-family string in DOM node, the terminal goes black until I switch back to Canvas mode.

Also, I opened the DDGame linked above, and it looked fine (See the same link for a screenshot).

I setting 'letterSpacing: 0'
In Chrome, the letter spacing is huge.
2020-06-08 10-19-12灞忓箷鎴浘-chrome

But in Firefox, it is ok.
2020-06-08 10-19-42灞忓箷鎴浘firefox

@Luokun2016 that's not a monospace font in your first screenshot either, easy way to tell is how the i is left aligned.

Thinking out loud, do new variable fonts provide a solution here over resize and fit twiddling tasks? Another read follows.
If so, is it possible this helps simplify support for DEC double width-height-both and blink (animate weight and/or bold) attributes?

I have the same double-spacing issue:

The terminal works and is singlespaced when it loads a system monospaced font (in this case consola.ttf on Windows 10). But I can load that exact same font as a webfont using @font-face and then fontFamily, and it comes out doublespaced.

A mitigation is to use a negative letterSpacing (e.g. -8 for a size 16 font), but then the characters are cropped on the left side and the highlighting does not line up with the characters.

It happens whether the terminal is rendered using DOM or canvas, and whether the font is changed later or sooner.

Also, it happens the same way in the latest versions of both Chrome and Edge.

As noted above, I was having this occur in Firefox (consistently) and the new Edge and Chrome (sporadically).

FWIIW, I modified its index.html to do:
<body onload="window.dispatchEvent(new Event('resize'));">

This is addition to having the XTerm initialize as:

    term = new Terminal(options)
    term.setOption('fontFamily', 'notomono,notoemoji')

    term.loadAddon(new Unicode11Addon())
    term.loadAddon(new WebLinksAddon())
    term.loadAddon(fit)

    term.unicode.activeVersion = '11'
    term.open(document.getElementById('terminal'))
    fit.fit()
    window.dispatchEvent(new Event('resize'))   // gratuituous

I then wrap my title splash sequence to allow for the custom fonts & sizes to 'settle up' -- perhaps not best practice, but better than having to occasionally have to hit the refresh key. A snippet follows:

setTimeout(() => {
    ....
    term.focus()
    term.writeln('\t\t馃敟 馃尐\r\x1b[23C\x1b[1;36mW\x1b[22melcome to 茒 \x1b[2man茩 \x1b[22m茒 \x1b[2momai茷 \x1b[m馃寵 馃挮')
    ....
}, 600)

Here's an addon that waits for fonts to load and then opens: https://github.com/CoderPad/xterm-webfont

Many thanks for the suggestions.

I had used document.fonts.ready.then(function () { stuff here }) to ensure that fonts were loaded before terminal starts, and in any case it loads with the correct webfont, but double spaced.

Also, I was able to load the terminal with a (single spaced) system font, then after a 5 second delay, use setOption to change to a webfont. The webfont again writes with double spacing this way.

I am in the process of tracing back the problem in the executing javascript in my browser, and so far I have narrowed it down to the character width being wrong in this._charSizeService.width. I continue to work back to find out how that is calculated... I have read that system fonts and webfonts are not rendered identically in browsers and suspect that this may be the root problem.

Things may vary depending on the Linux distro you are using.
I figured out this font config patch:
https://gist.github.com/ShikiSuen/2489e06d994bb48e798005773953d4aa

Please apply this patch and see whether this issue still exists.
Note that this patch requires not only noto-CJK (for Noto Serif CJK support) but also the following fonts:
https://github.com/rsms/inter
https://github.com/be5invis/Sarasa-Gothic

(Sarasa Gothic is an enhanced version of Noto Sans CJK + embeded Iosevka terminal font support.)
(You may install PingFang if you wish, and this patch will read PingFang as the most prioritized Chinese fallback fonts.)
(Inter font can be treated as an open-source alternative of "Apple San Francisco Pro Display".)

Daniel, I finally understand the wisdom in your comment.

xterm.js creates a element (xterm-char-measure-element) containing the letter "W" and measures its width using getBoundingClientRect() to determine the character width. However, if the webfont is not loaded before this, getBoundingClientRect returns an overly conservative width instead. Even if there is an @font-face in the style sheet, the browser does not load the font until it is used.

xterm-webfont uses fontfaceobserver to solve the font loading problem and generate an event when the font is loaded. Unfortunately, I never could get xterm-webfont to work because my project cannot use javascript modules. The workaround was simply to use fontfaceobserver directly.

So now I have the following in my html webpage, which gives me a single-spaced webfont:

<style>
        @font-face {
            font-family: EIEFont;
            src: url(EIE.ttf);
        }
</style>
<script src="node_modules/xterm/lib/xterm.js"></script>
<script src="node_modules/fontfaceobserver/fontfaceobserver.js"></script>    
<script src="node_modules/xterm-addon-fit/lib/xterm-addon-fit.js"></script>
<script src="node_modules/xterm-addon-attach/lib/xterm-addon-attach.js"></script>
...
<script>
        var regular = new FontFaceObserver('EIEFont').load();
        var bold = new FontFaceObserver('EIEFont', {
            weight: "bold"
        }).load();
        Promise.all([regular, bold]).then(() => {
            const term = new Terminal({
                fontFamily: 'EIEFont',
                fontSize: 16, 
                theme: {
                    blue: '#0000ff'
                },
                cursorBlink: true,
                convertEol: true
            });
            const fitAddon = new FitAddon.FitAddon();
            term.loadAddon(fitAddon);
            term.open(document.getElementById('terminal'));
            fitAddon.fit();
            term.focus();

            and so on...
</script>

Hopefully this helps someone in future.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tandatle picture tandatle  路  3Comments

jestapinski picture jestapinski  路  3Comments

Mlocik97-issues picture Mlocik97-issues  路  3Comments

parisk picture parisk  路  3Comments

albinekb picture albinekb  路  4Comments