Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

font issues in DOM renderer #5164

Closed
jerch opened this issue Sep 19, 2024 · 29 comments
Closed

font issues in DOM renderer #5164

jerch opened this issue Sep 19, 2024 · 29 comments

Comments

@jerch
Copy link
Member

jerch commented Sep 19, 2024

Moved from #4881

image

Also this kind of things. Just why? I use Roboto Mono, which is a mono-width font. Just why we need any corrections ?

image

Originally posted by @socketpair in #4881 (comment)

@jerch
Copy link
Member Author

jerch commented Sep 19, 2024

@socketpair:

Which browser and which xtermjs version is that? There were some corrections applied to the width measuring in the newer xtermjs versions.

Can you retry with these global patches applied:

// width measure container
document.querySelector('.xterm-width-cache-measure-container').style.fontVariantLigatures='none';
// terminal output
document.querySelector('.xterm-rows').style.fontVariantLigatures='none';

Just why we need any corrections ?

Because, other than in the canvas/webgl renderer, we have no control in the DOM renderer how the browser's font renderer gonna output a certain glyph on the screen. It may pick the glyph from a different font or apply different widths for bold/italics. Furthermore some monospace fonts are not perfectly monospaced for all their glyphs. Btw - in your mc output it looks like the bold glyphs cause the misalignment.

Originally posted by @jerch in #4881 (comment)

@jerch
Copy link
Member Author

jerch commented Sep 19, 2024

@jerch are there any tunable to turn off these corrections ? If a font is not perfect we will not use it!

It would also be nice to get your recommendations on which font is the most trouble-free. And possibly well-known bad monospace fonts (like Roboto Mono).

My colleague will write answer to your questions above in few minutes.

Originally posted by @socketpair in #4881 (comment)

@jerch
Copy link
Member Author

jerch commented Sep 19, 2024

are there any tunable to turn off these corrections ? If a font is not perfect we will not use it!

Nope, thats not configurable from outside, as half of the renderer logic relies on the measurement results.

I just tried to repro it under ubuntu 22 with chrome and firefox and Roboto Mono. Its all fine here with the repo demo, even if I switch to russian locale in mc. So my guess - its either a version issue (which xtermjs version you are using?) or some CSS font settings on the terminal container in your integration not being compatible.

It would also be nice to get your recommendations on which font is the most trouble-free. And possibly well-known bad monospace fonts (like Roboto Mono).

Roboto Mono behaves correctly for me. Also I dont have any list of bad or good fonts. The letter-spacing trick should level out single glyph metrics issues (thats why even Arial will work there), but it cannot address higher level font features like certain ligatures out of the box. So in general try to avoid monospace fonts using ligature trickery, that are not grid stable (see #5156 for some context).

Originally posted by @jerch in #4881 (comment)

@jerch
Copy link
Member Author

jerch commented Sep 19, 2024

@jerch, hello! We use @xterm/xterm 5.5.0, @xterm/addon-attach 0.11.0 and @xterm/addon-fit 0.10.0.
I reproduced the bug with Roboto Mono on Google Chrome 126.0.6478.182 and Firefox 128.0 on Linux Mint, @socketpair use Fedora.

One more example with Юж letters:
image

Originally posted by @ithrforu in #4881 (comment)

@jerch
Copy link
Member Author

jerch commented Sep 19, 2024

@ithrforu Can you repro it with the demo of the repo?

Edit: Looks correct to me here with Roboto Mono
image

Originally posted by @jerch in #4881 (comment)

@socketpair
Copy link

socketpair commented Sep 20, 2024

@ithrforu please take a look on this issue

@ithrforu
Copy link

@jerch can you help with the sandbox for reproduction?
Roboto Mono won't load in the codesandbox :(

@jerch
Copy link
Member Author

jerch commented Sep 21, 2024

@jerch can you help with the sandbox for reproduction? Roboto Mono won't load in the codesandbox :(

Nope, I have never done anything with codesandbox, no clue about it.

@Tyriar Tyriar self-assigned this Sep 23, 2024
@ithrforu
Copy link

@jerch, hello. I reproduced the bug with Юж letters here: https://github.com/ithrforu/xterm-sandbox.

@jerch
Copy link
Member Author

jerch commented Sep 23, 2024

@ithrforu Thats not valid javascript. Whats import 'some.css'; supposed to mean? Is this some sort of vite magic? Also you put webfonts there. This most likely creates a race condition between the terminal initialization and the fonts being loaded. For the char measuring to work it is important, that the font is fully available at the time terminal.open is called.

@Tyriar
Copy link
Member

Tyriar commented Sep 26, 2024

This most likely creates a race condition between the terminal initialization and the fonts being loaded.

👍 good catch, there definitely are sync problems when using webfonts. This is why https://www.npmjs.com/package/xterm-webfont was created by a community member.

@jerch
Copy link
Member Author

jerch commented Sep 27, 2024

@ithrforu I tried running your example with the vite tasks - it is as I expected, you run into a race condition between font loading and terminal bootstrapping. The vite css magic does not place a waiting condition on the css loads, thus the terminal initialization runs too early.
Idk if that is fixable within vite config itself, all I can say - nope it never gonna work that way, as the assumptions, vite does with the css loading shims, does not meet the needs for xtermjs here.

@ithrforu
Copy link

ithrforu commented Oct 1, 2024

@jerch, @Tyriar, i tried to load fonts and css from index.html with Roboto Mono from Google Fonts:
https://github.com/ithrforu/xterm-sandbox/blob/another-example-with-preload/index.html

It works correctly without xterm-webfont.

In the first example i used Roboto Mono from fontsource (1, 2). I think problem with Юж was here.

I also tried to use xterm-webfont and it doesn't work:
https://github.com/ithrforu/xterm-sandbox/blob/preload-with-xterm-webfont/index.js

Uncaught TypeError: this.getOption is not a function
    at terminal.loadWebfontAndOpen (xterm-webfont.js?v=9060b432:172:33)
    at index.js:16:10

@jerch
Copy link
Member Author

jerch commented Oct 1, 2024

@ithrforu It seems xterm-webfont is quite outdated and still relies on an old API call.

If you want to use a webfont - does document.fonts.ready work for you? It returns a promise, so you could place your fonts in early loaded css and then put the whole terminal init code behind that promise.

@ithrforu
Copy link

ithrforu commented Oct 1, 2024

@jerch, yes, with document.fonts.ready i can use something like this:

const loadApplicationFonts = async (): Promise<void> => {
  try {
    const applicationFontsSet = await document.fonts.ready;
    const fontsLoadingState = [...applicationFontsSet].map((font) => font.load());
    const fontsLoadingResult = await Promise.all(fontsLoadingState);
    const failedFonts = fontsLoadingResult.filter((font) => font.status !== 'loaded');

    if (failedFonts.length > 0) {
      throw new Error(`These fonts failed to load: ${failedFonts.join(', ')}.`);
    }
  } catch (error) {
    throw new Error(`Some fonts can not be loaded:\n${error}.`);
  }
};

....
await loadApplicationFonts()
terminal.open()

The peculiarity is that the await document.fonts.ready does not mean that all fonts have loaded and return FontFaceSet:
https://developer.mozilla.org/en-US/docs/Web/API/Document/fonts

@jerch
Copy link
Member Author

jerch commented Oct 1, 2024

@ithrforu Hmm, idk anything about that interface, but a comment on your linked page says:

// some fonts may still be unloaded if they aren't used on the site

Maybe style a hidden element in the document with your webfonts to force the browser to fully load them? Just a guess ...

@jerch
Copy link
Member Author

jerch commented Oct 1, 2024

@ithrforu Here is a variant of your sandbox example above, which seems to work correctly (also added bold and some test output):

import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
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";


function initTerminal() {
  const terminal = new Terminal({
    fontFamily: "Roboto Mono",
  });

  const fitAddon = new FitAddon();

  terminal.loadAddon(fitAddon);

  const terminalContainer = document.getElementById('xterm-container');

  terminal.open(terminalContainer);

  terminal.onKey(({ key }) => {
    terminal.write(key);
  });

  const resizeObserver = new ResizeObserver(() => {
    requestAnimationFrame(() => fitAddon.fit());
  });

  resizeObserver.observe(terminalContainer);

  // write test after some time...
  setTimeout(() => {
    terminal.write('0123456789\r\n');
    terminal.write('\x1b[1m0123456789\r\n');
    terminal.write('\x1b[0;3m0123456789\r\n');
    terminal.write('\x1b[1;3m0123456789\r\n');
    terminal.write('\x1b[mЮжЮжЮжЮжЮж\r\n');
    terminal.write('\x1b[1mЮжЮжЮжЮжЮж\r\n');
    terminal.write('\x1b[0;3mЮжЮжЮжЮжЮж\r\n');
    terminal.write('\x1b[1;3mЮжЮжЮжЮжЮж\r\n');
  }, 100);
}

document.fonts.ready.then(
  fontFaceSet => Promise.all(Array.from(fontFaceSet).map(el => el.load())).then(initTerminal)
);

The issue with the fontsource webfont is, that they splitted the font across multiple font files. Each of these font files are not loaded until really needed by the browser, but by then it is already too late for xtermjs. The Promise.all instead forces the browser to really load every font file, before it continues with the terminal setup.

Edit: Tested with chrome && firefox, no clue about safari.


Would be nice, if xterm-webfont addon would get an upgrade to reflect the newer APIs (@vincentwoo).

@ithrforu
Copy link

ithrforu commented Oct 1, 2024

@jerch thank you!
I tried to used combination of your and mines examples and replace Roboto Mono with Cascadia Mono.

It works correctly.

@ithrforu
Copy link

ithrforu commented Oct 1, 2024

@jerch offtop: where can i find a @xterm/addon-canvas source code?
https://github.com/xtermjs/xterm.js/tree/master/addons

@jerch
Copy link
Member Author

jerch commented Oct 1, 2024

@ithrforu The canvas addon got removed for the next major version and you prolly should not rely on it anymore. v5.5 still contains the source (https://github.com/xtermjs/xterm.js/tree/5.5.0/addons).

Gonna close this issue as resolved, plz open questions not directly related to issues or code bugs under discussions.

@jerch jerch closed this as completed Oct 1, 2024
@KernelDeimos
Copy link

KernelDeimos commented Oct 1, 2024

Does anyone know a powerline-supporting font that works? I'm really struggling, every font I've tried has too much space between letters and setting letterSpacing to negative values to fix this reprods #4881

@jerch jerch mentioned this issue Oct 3, 2024
4 tasks
@jerch
Copy link
Member Author

jerch commented Oct 4, 2024

@ithrforu FYI - created a new addon, which hopefully makes webfonts more easy to deal with, see #5178.

@devhaozi
Copy link

@ithrforu Here is a variant of your sandbox example above, which seems to work correctly (also added bold and some test output):

import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
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";


function initTerminal() {
  const terminal = new Terminal({
    fontFamily: "Roboto Mono",
  });

  const fitAddon = new FitAddon();

  terminal.loadAddon(fitAddon);

  const terminalContainer = document.getElementById('xterm-container');

  terminal.open(terminalContainer);

  terminal.onKey(({ key }) => {
    terminal.write(key);
  });

  const resizeObserver = new ResizeObserver(() => {
    requestAnimationFrame(() => fitAddon.fit());
  });

  resizeObserver.observe(terminalContainer);

  // write test after some time...
  setTimeout(() => {
    terminal.write('0123456789\r\n');
    terminal.write('\x1b[1m0123456789\r\n');
    terminal.write('\x1b[0;3m0123456789\r\n');
    terminal.write('\x1b[1;3m0123456789\r\n');
    terminal.write('\x1b[mЮжЮжЮжЮжЮж\r\n');
    terminal.write('\x1b[1mЮжЮжЮжЮжЮж\r\n');
    terminal.write('\x1b[0;3mЮжЮжЮжЮжЮж\r\n');
    terminal.write('\x1b[1;3mЮжЮжЮжЮжЮж\r\n');
  }, 100);
}

document.fonts.ready.then(
  fontFaceSet => Promise.all(Array.from(fontFaceSet).map(el => el.load())).then(initTerminal)
);

The issue with the fontsource webfont is, that they splitted the font across multiple font files. Each of these font files are not loaded until really needed by the browser, but by then it is already too late for xtermjs. The Promise.all instead forces the browser to really load every font file, before it continues with the terminal setup.

Edit: Tested with chrome && firefox, no clue about safari.

Would be nice, if xterm-webfont addon would get an upgrade to reflect the newer APIs (@vincentwoo).

It working for me, but seems not support variable fonts.

@jerch
Copy link
Member Author

jerch commented Oct 23, 2024

@devhaozi What do you mean by "variable fonts"? Non monospace fonts? Those should also work, but ofc will look quite ugly due their glyphs getting grid positioned. In general it is not a good idea to run a terminal with a variable width font.

Or do you mean variable as of changing the font of an already running terminal? Thats also possible, but there you have to make sure, that all webfont assets are readily loaded before doing the font switch.

@devhaozi
Copy link

@devhaozi What do you mean by "variable fonts"? Non monospace fonts? Those should also work, but ofc will look quite ugly due their glyphs getting grid positioned. In general it is not a good idea to run a terminal with a variable width font.

Or do you mean variable as of changing the font of an already running terminal? Thats also possible, but there you have to make sure, that all webfont assets are readily loaded before doing the font switch.

Like this: https://fontsource.org/fonts/jetbrains-mono/install
It has two types: variable and static.

@jerch
Copy link
Member Author

jerch commented Oct 23, 2024

Idk what variable vs. static means in this context. Care to explain?

@devhaozi
Copy link

devhaozi commented Oct 24, 2024

Idk what variable vs. static means in this context. Care to explain?

Variable fonts contain all weights from 100 to 800, which can be adjusted at will without repeated import.
Static fonts, on the other hand, need to be imported separately for each weight.

@devhaozi
Copy link

devhaozi commented Oct 24, 2024

Idk what variable vs. static means in this context. Care to explain?

Variable fonts contain all weights from 100 to 800, which can be adjusted at will without repeated import. Static fonts, on the other hand, need to be imported separately for each weight.

Oh I found the reason, the variable font needs to use the JetBrains Mono Variable family name, I was wrong, it is actually support.

@jerch
Copy link
Member Author

jerch commented Oct 24, 2024

@devhaozi
I just read it up here: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide

This is indeed not fully supported yet, as it needs font variation settings, which are currently not exposed/configurable from terminal API. So yes it will work, if you await a font-face object to be loaded with those subspecs and only have that one as only font-family member in document.fonts. It is not possible to actually switch to a variation currently, instead the browser would pick whatever was the default in that font asset file. This also applies to the new webfont addon in #5178.

To further track this --> #5197

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants