дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
fix(terminal): render the terminal in the bundled JetBrains Mono (#44)
Root cause: the terminal built its own font-family ("JetBrains Mono", Nerd Fonts, monospace) without the bundled "JetBrains Mono Variable", so on Windows (no JetBrains Mono installed) xterm fell back to the system monospace (SimSun on Chinese Windows); xterm also measured cells before the webfont loaded.
Fix: one resolver (src/platform/fonts.ts monoFontFamily) maps JetBrains Mono to the bundled face; xterm re-measures once fonts load; "Use editor font" also takes the editor's fallback font (IDEA).
Siblings covered: embedded run terminal, editor default family + fallback, Settings › Editor › Font preview.
Verified in-app on macOS (terminal renders JetBrains Mono Variable). Not verified on Windows.
Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
1 джерело
5670c2e8dd
коміт
b4a3e866c6
8 files changed
+185
-66
No files matched your search
@@ -26,6 +26,7 @@ import { highlightSelectionMatches } from "@codemirror/search";
|
||||
import type { EditorConfigProps } from "@/bindings";
|
||||
import type { EditorSettings } from "@/platform/ipc";
|
||||
import { editorPrefs, lineCommentStyleFor, type EditorExtras } from "@/platform/editorSettings";
|
||||
import { BUNDLED_MONO_FAMILY, monoFontFamily } from "@/platform/fonts";
|
||||
import { editorContributions, type EditorDocContext } from "@/editor/api";
|
||||
import { distractionFreeWidth, isDistractionFree } from "@/workbench/distractionFree";
|
||||
import { openideaHighlightStyle } from "./highlight";
|
||||
@@ -160,9 +161,8 @@ export function fontExtension(settings: EditorSettings | undefined): Extension {
|
||||
const prefs = prefsOf(settings);
|
||||
const family = settings?.fontFamily;
|
||||
const fallback = prefs.fallbackFont.trim();
|
||||
if ((family && family !== "JetBrains Mono") || fallback) {
|
||||
const fam = (family || "JetBrains Mono").replace(/"/g, "");
|
||||
vars.push(`--openidea-editor-font: "${fam}"${fallback ? `, "${fallback.replace(/"/g, "")}"` : ""}, var(--font-mono)`);
|
||||
if ((family && family !== BUNDLED_MONO_FAMILY) || fallback) {
|
||||
vars.push(`--openidea-editor-font: ${monoFontFamily(family, fallback, "var(--font-mono)")}`);
|
||||
}
|
||||
if (settings?.fontSize) vars.push(`--openidea-editor-font-size: ${settings.fontSize}px`, `--openidea-editor-base-size: ${settings.fontSize}px`);
|
||||
if (settings?.lineHeight) vars.push(`--openidea-editor-line-height: ${settings.lineHeight}px`, `--openidea-editor-base-line: ${settings.lineHeight}px`);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Editor › Font: family (installed fonts, monospaced filter), size, line height, ligatures, fallback, editable preview. */
|
||||
import { useMemo, useState } from "react";
|
||||
import { Checkbox, type SelectOption, T } from "@/ui";
|
||||
import { monoFontFamily } from "@/platform/fonts";
|
||||
import { NumberInput, Row, SearchLabel, Select } from "../../components/Form";
|
||||
import { useLabelMatch, useSetting } from "../../components/context";
|
||||
import { LIMITS } from "../../model/schema";
|
||||
@@ -149,7 +150,7 @@ function Preview() {
|
||||
const [line] = useSetting<number>("editor.lineHeight");
|
||||
const [ligatures] = useSetting<boolean>("editor.ligatures");
|
||||
const [text, setText] = useState(SAMPLE);
|
||||
const fam = `"${family.replace(/"/g, "")}"${fallback ? `, "${fallback.replace(/"/g, "")}"` : ""}, var(--font-mono)`;
|
||||
const fam = monoFontFamily(family, fallback, "var(--font-mono)");
|
||||
return (
|
||||
<>
|
||||
<div className={styles.previewTitle}>
|
||||
|
||||
@@ -11,8 +11,8 @@ import type { EmbeddedTerminal, EmbeddedTerminalOptions } from "@/workbench/cont
|
||||
import { useProject } from "@/platform/project";
|
||||
import { workflowSettings } from "@/platform/workflowSettings";
|
||||
import { findTerminalLinks } from "./links";
|
||||
import { openExternal, openFileLink } from "./session";
|
||||
import { readTerminalTheme, terminalFont, withFontExtras } from "./theme";
|
||||
import { fontFor, openExternal, openFileLink } from "./session";
|
||||
import { readTerminalTheme, remeasureWhenFontsLoad } from "./theme";
|
||||
|
||||
const live = new Set<Terminal>();
|
||||
|
||||
@@ -24,9 +24,7 @@ export function applyThemeToEmbedded() {
|
||||
|
||||
export function createEmbeddedTerminal(host: HTMLElement, opts: EmbeddedTerminalOptions): EmbeddedTerminal {
|
||||
const settings = workflowSettings().terminal;
|
||||
const editor = useProject.getState().settings?.editor as
|
||||
{ fontFamily?: string; fontSize?: number; lineHeight?: number } | undefined;
|
||||
const font = withFontExtras(terminalFont(settings, editor), settings);
|
||||
const font = fontFor(settings);
|
||||
const term = new Terminal({
|
||||
fontFamily: font.family,
|
||||
fontSize: font.size,
|
||||
@@ -89,6 +87,7 @@ export function createEmbeddedTerminal(host: HTMLElement, opts: EmbeddedTerminal
|
||||
const observer = new ResizeObserver(scheduleFit);
|
||||
observer.observe(host);
|
||||
scheduleFit();
|
||||
remeasureWhenFontsLoad(term, font, () => live.has(term), scheduleFit);
|
||||
// The owner learns the initial size even when it matches xterm's default.
|
||||
queueMicrotask(() => opts.onResize(term.cols, term.rows));
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ import { findTerminalLinks, resolvePath, type TerminalLink } from "./links";
|
||||
import { splitCommandLine } from "@/platform/commandLine";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { workflowSettings, type TerminalSettingsValid } from "@/platform/workflowSettings";
|
||||
import { readSearchDecorations, readTerminalTheme, terminalFont, withFontExtras } from "./theme";
|
||||
import { editorPrefs } from "@/platform/editorSettings";
|
||||
import { readSearchDecorations, readTerminalTheme, remeasureWhenFontsLoad, terminalFont, type TerminalFontSettings } from "./theme";
|
||||
|
||||
export interface TerminalSearchOptions {
|
||||
caseSensitive: boolean;
|
||||
@@ -77,9 +78,10 @@ function beep() {
|
||||
}
|
||||
}
|
||||
|
||||
function fontFor(t: TerminalSettingsValid) {
|
||||
/** The terminal font for `t`, resolved against the current editor font (also used by embedded terminals). */
|
||||
export function fontFor(t: TerminalFontSettings) {
|
||||
const e = useProject.getState().settings?.editor as { fontFamily?: string; fontSize?: number; lineHeight?: number } | undefined;
|
||||
return withFontExtras(terminalFont(t, e), t);
|
||||
return terminalFont(t, { ...e, fallbackFont: editorPrefs().fallbackFont });
|
||||
}
|
||||
|
||||
const CURSOR: Record<TerminalSettingsValid["cursorShape"], "block" | "underline" | "bar"> = {
|
||||
@@ -237,7 +239,10 @@ export class TerminalSession {
|
||||
applySettings(t: TerminalSettingsValid) {
|
||||
const font = fontFor(t);
|
||||
const o = this.term.options;
|
||||
if (o.fontFamily !== font.family) o.fontFamily = font.family;
|
||||
if (o.fontFamily !== font.family) {
|
||||
o.fontFamily = font.family;
|
||||
if (this.opened) remeasureWhenFontsLoad(this.term, font, () => !this.disposed, () => this.scheduleFit());
|
||||
}
|
||||
if (o.fontSize !== font.size) o.fontSize = font.size;
|
||||
if (o.lineHeight !== font.lineHeight) o.lineHeight = font.lineHeight;
|
||||
if (o.letterSpacing !== font.letterSpacing) o.letterSpacing = font.letterSpacing;
|
||||
@@ -288,6 +293,7 @@ export class TerminalSession {
|
||||
// xterm's DOM renderer: the WebGL one rasterizes glyphs per background, so
|
||||
// selected text rendered at a visibly different weight than the rest.
|
||||
this.term.open(this.host);
|
||||
remeasureWhenFontsLoad(this.term, fontFor(workflowSettings().terminal), () => !this.disposed, () => this.scheduleFit());
|
||||
this.term.textarea?.addEventListener("focus", () => this.opts.onFocus(this.key));
|
||||
}
|
||||
this.resizeObserver?.disconnect();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { buildSearchDecorations } from "./theme";
|
||||
import { findTerminalLinks, resolvePath, trimUrl } from "./links";
|
||||
import { layoutPanes, nextTabName, removeFromLayout, splitLayout, tabFullTitle, tabTitle, trimMiddle, type TerminalTab } from "./store";
|
||||
import type { SessionSnapshot } from "./session";
|
||||
import { buildTheme, mix, parseColor, toHex, withFontExtras } from "./theme";
|
||||
import { buildTheme, mix, parseColor, terminalFont, toHex } from "./theme";
|
||||
|
||||
describe("theme", () => {
|
||||
it("parses colour formats", () => {
|
||||
@@ -208,19 +208,36 @@ describe("split layout", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("withFontExtras", () => {
|
||||
const font = { family: '"JetBrains Mono", "Symbols Nerd Font", monospace', size: 10, lineHeight: 1 };
|
||||
describe("terminalFont", () => {
|
||||
const BASE = '"JetBrains Mono Variable", "JetBrains Mono", ui-monospace, Menlo, monospace';
|
||||
const t = { useEditorFont: true, fontFamily: "JetBrains Mono", fontSize: 10, lineHeight: 1, fallbackFontFamily: "", columnWidth: 1 };
|
||||
const editor = { fontFamily: "JetBrains Mono", fontSize: 10, lineHeight: 12 };
|
||||
const head = (family: string) => family.slice(0, family.indexOf('"Symbols Nerd Font Mono"'));
|
||||
|
||||
it("puts the fallback family right after the main one", () => {
|
||||
expect(withFontExtras(font, { fallbackFontFamily: "PingFang SC", columnWidth: 1 }).family).toBe(
|
||||
'"JetBrains Mono", "PingFang SC", "Symbols Nerd Font", monospace',
|
||||
);
|
||||
expect(withFontExtras(font, { fallbackFontFamily: "", columnWidth: 1 }).family).toBe(font.family);
|
||||
it.each([
|
||||
// #26: "Use editor font" with the default family must reach the bundled face, not the system monospace.
|
||||
["editor font, default family", t, editor, `"JetBrains Mono Variable", "JetBrains Mono", ${BASE.replace(", monospace", "")}, `],
|
||||
["editor font, no editor settings", t, undefined, `"JetBrains Mono Variable", "JetBrains Mono", ${BASE.replace(", monospace", "")}, `],
|
||||
["editor font, custom family", t, { ...editor, fontFamily: "Fira Code" }, `"Fira Code", ${BASE.replace(", monospace", "")}, `],
|
||||
["editor font, editor fallback", t, { ...editor, fallbackFont: "PingFang SC" }, `"JetBrains Mono Variable", "JetBrains Mono", "PingFang SC", ${BASE.replace(", monospace", "")}, `],
|
||||
["own font, default family", { ...t, useEditorFont: false }, editor, `"JetBrains Mono Variable", "JetBrains Mono", ${BASE.replace(", monospace", "")}, `],
|
||||
["own font, custom + fallback", { ...t, useEditorFont: false, fontFamily: "Hack", fallbackFontFamily: "PingFang SC" }, editor, `"Hack", "PingFang SC", ${BASE.replace(", monospace", "")}, `],
|
||||
])("%s resolves through the bundled stack", (_, settings, ed, expected) => {
|
||||
const font = terminalFont(settings, ed, BASE);
|
||||
expect(head(font.family)).toBe(expected);
|
||||
expect(font.family.endsWith(", monospace")).toBe(true);
|
||||
expect(font.family).not.toContain("var(");
|
||||
});
|
||||
|
||||
it("prefers the terminal's fallback over the editor's", () => {
|
||||
const font = terminalFont({ ...t, fallbackFontFamily: "Sarasa Mono SC" }, { ...editor, fallbackFont: "PingFang SC" }, BASE);
|
||||
expect(font.family).toContain('"Sarasa Mono SC"');
|
||||
expect(font.family).not.toContain("PingFang SC");
|
||||
});
|
||||
|
||||
it("turns the column width into letter spacing", () => {
|
||||
expect(withFontExtras(font, { fallbackFontFamily: "", columnWidth: 1 }).letterSpacing).toBe(0);
|
||||
expect(withFontExtras(font, { fallbackFontFamily: "", columnWidth: 1.5 }).letterSpacing).toBe(3);
|
||||
expect(terminalFont({ ...t, useEditorFont: false }, editor, BASE).letterSpacing).toBe(0);
|
||||
expect(terminalFont({ ...t, useEditorFont: false, columnWidth: 1.5 }, editor, BASE).letterSpacing).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* tokens (PyCharm's console colours per theme); when a theme lacks them it is
|
||||
* derived from the status + syntax tokens.
|
||||
*/
|
||||
import type { ITheme } from "@xterm/xterm";
|
||||
import type { ITheme, Terminal } from "@xterm/xterm";
|
||||
import { BUNDLED_MONO_FAMILY, fontsLoaded, monoFontFamily, monoStack } from "@/platform/fonts";
|
||||
|
||||
export type Rgba = [number, number, number, number];
|
||||
|
||||
@@ -177,56 +178,82 @@ const NERD_FONT_FALLBACKS = [
|
||||
.map((f) => `"${f}"`)
|
||||
.join(", ");
|
||||
|
||||
export function readTerminalFont(): { family: string; size: number; lineHeight: number } {
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
const base = style.getPropertyValue("--font-mono").trim() || "'JetBrains Mono', monospace";
|
||||
// Prompt themes (powerlevel10k, starship) use Nerd Font private-use glyphs;
|
||||
// list common installs so per-glyph fallback finds them.
|
||||
const family = `${base}, ${NERD_FONT_FALLBACKS}`;
|
||||
const size = parseFloat(style.getPropertyValue("--fs-code")) || 13;
|
||||
const lh = parseFloat(style.getPropertyValue("--lh-code")) || 21;
|
||||
// xterm's lineHeight is a multiplier of the cell height (≈ 1.2 × font size); aim for the editor's line height.
|
||||
return { family, size, lineHeight: Math.max(1, Math.round((lh / (size * 1.2)) * 100) / 100) };
|
||||
export interface TerminalFontSettings {
|
||||
useEditorFont: boolean;
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
lineHeight: number;
|
||||
fallbackFontFamily: string;
|
||||
columnWidth: number;
|
||||
}
|
||||
|
||||
export interface EditorFont {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fallbackFont?: string;
|
||||
}
|
||||
|
||||
export interface TerminalFont {
|
||||
family: string;
|
||||
size: number;
|
||||
lineHeight: number;
|
||||
letterSpacing: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font from Settings › Tools › Terminal: the editor font (Editor › Font) or the
|
||||
* terminal's own family / size / line height (a multiple of the cell height).
|
||||
* Prompt themes (powerlevel10k, starship) use Nerd Font private-use glyphs; list
|
||||
* common installs before the generic family so per-glyph fallback finds them.
|
||||
*/
|
||||
export function terminalFont(
|
||||
t: { useEditorFont: boolean; fontFamily: string; fontSize: number; lineHeight: number },
|
||||
editor: { fontFamily?: string; fontSize?: number; lineHeight?: number } | undefined,
|
||||
): { family: string; size: number; lineHeight: number } {
|
||||
function withNerdFonts(family: string): string {
|
||||
return `${family.replace(/,\s*monospace\s*$/i, "")}, ${NERD_FONT_FALLBACKS}, monospace`;
|
||||
}
|
||||
|
||||
function cssNumber(name: string): number {
|
||||
if (typeof document === "undefined") return NaN;
|
||||
return parseFloat(getComputedStyle(document.documentElement).getPropertyValue(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Font from Settings › Tools › Terminal: the editor font (Editor › Font, with its
|
||||
* fallback) or the terminal's own family / size / line height (a multiple of the
|
||||
* cell height). The family resolves like the editor's (`monoFontFamily`), so the
|
||||
* bundled JetBrains Mono is used where it isn't installed; `base` is the resolved
|
||||
* `--font-mono` stack, since xterm measures on a canvas and can't use `var()`.
|
||||
* The column width (a multiple of the cell width) becomes xterm's letter spacing,
|
||||
* from the ≈ 0.6 em advance of monospace fonts.
|
||||
*/
|
||||
export function terminalFont(t: TerminalFontSettings, editor: EditorFont | undefined, base = monoStack()): TerminalFont {
|
||||
let family: string, fallback: string, size: number, lineHeight: number;
|
||||
if (!t.useEditorFont) {
|
||||
return { family: `"${t.fontFamily.replace(/"/g, "")}", ${NERD_FONT_FALLBACKS}, monospace`, size: t.fontSize, lineHeight: t.lineHeight };
|
||||
family = t.fontFamily;
|
||||
fallback = t.fallbackFontFamily;
|
||||
size = t.fontSize;
|
||||
lineHeight = t.lineHeight;
|
||||
} else {
|
||||
family = editor?.fontFamily || BUNDLED_MONO_FAMILY;
|
||||
fallback = t.fallbackFontFamily.trim() || editor?.fallbackFont || "";
|
||||
size = editor?.fontSize || cssNumber("--fs-code") || 13;
|
||||
const lh = editor?.lineHeight ?? (editor?.fontSize ? size * 1.6 : cssNumber("--lh-code") || 21);
|
||||
// xterm's lineHeight is a multiplier of the cell height (≈ 1.2 × font size); aim for the editor's line height.
|
||||
lineHeight = Math.max(1, Math.round((lh / (size * 1.2)) * 100) / 100);
|
||||
}
|
||||
if (!editor?.fontFamily || !editor.fontSize) return readTerminalFont();
|
||||
const size = editor.fontSize;
|
||||
const lh = editor.lineHeight ?? size * 1.6;
|
||||
return {
|
||||
family: `"${editor.fontFamily.replace(/"/g, "")}", ${NERD_FONT_FALLBACKS}, monospace`,
|
||||
size,
|
||||
lineHeight: Math.max(1, Math.round((lh / (size * 1.2)) * 100) / 100),
|
||||
};
|
||||
const letterSpacing = Math.round((t.columnWidth - 1) * size * 0.6 * 10) / 10;
|
||||
return { family: withNerdFonts(monoFontFamily(family, fallback, base)), size, lineHeight, letterSpacing };
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings › Tools › Terminal extras over `terminalFont`: the fallback family
|
||||
* goes right after the main one, and the column width (a multiple of the cell
|
||||
* width) becomes xterm's letter spacing, from the ≈ 0.6 em advance of
|
||||
* monospace fonts.
|
||||
* xterm measures its cell once per font change; a webfont still loading (the
|
||||
* bundled JetBrains Mono on first open) leaves cells sized for the swap font.
|
||||
* Re-measures once the faces load (unless `alive()` turned false), then calls `after` (refit).
|
||||
*/
|
||||
export function withFontExtras(
|
||||
font: { family: string; size: number; lineHeight: number },
|
||||
t: { fallbackFontFamily: string; columnWidth: number },
|
||||
): { family: string; size: number; lineHeight: number; letterSpacing: number } {
|
||||
const fallback = t.fallbackFontFamily.replace(/"/g, "").trim();
|
||||
const comma = font.family.indexOf(",");
|
||||
const family = !fallback
|
||||
? font.family
|
||||
: comma < 0
|
||||
? `${font.family}, "${fallback}"`
|
||||
: `${font.family.slice(0, comma)}, "${fallback}"${font.family.slice(comma)}`;
|
||||
const letterSpacing = Math.round((t.columnWidth - 1) * font.size * 0.6 * 10) / 10;
|
||||
return { ...font, family, letterSpacing };
|
||||
export function remeasureWhenFontsLoad(term: Terminal, font: TerminalFont, alive: () => boolean, after: () => void) {
|
||||
void fontsLoaded(font.family, font.size).then(() => {
|
||||
if (!alive()) return;
|
||||
const o = term.options;
|
||||
if (o.fontFamily !== font.family) return;
|
||||
o.fontFamily = "monospace";
|
||||
o.fontFamily = font.family;
|
||||
after();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { monoFontFamily } from "./fonts";
|
||||
|
||||
const BASE = "var(--font-mono)";
|
||||
|
||||
describe("monoFontFamily", () => {
|
||||
it.each([
|
||||
// #26: the default family names the bundled face, so it renders without a system install.
|
||||
["JetBrains Mono", "", `"JetBrains Mono Variable", "JetBrains Mono", ${BASE}`],
|
||||
["", "", `"JetBrains Mono Variable", "JetBrains Mono", ${BASE}`],
|
||||
[undefined, "", `"JetBrains Mono Variable", "JetBrains Mono", ${BASE}`],
|
||||
["JetBrains Mono", "PingFang SC", `"JetBrains Mono Variable", "JetBrains Mono", "PingFang SC", ${BASE}`],
|
||||
["Fira Code", "", `"Fira Code", ${BASE}`],
|
||||
['"Cascadia Code"', " Microsoft YaHei ", `"Cascadia Code", "Microsoft YaHei", ${BASE}`],
|
||||
])("%j + fallback %j", (family, fallback, expected) => {
|
||||
expect(monoFontFamily(family, fallback, BASE)).toBe(expected);
|
||||
});
|
||||
|
||||
it("always ends with the bundled stack, so a missing family never reaches the system default", () => {
|
||||
expect(monoFontFamily("Not Installed", "", '"JetBrains Mono Variable", monospace')).toBe(
|
||||
'"Not Installed", "JetBrains Mono Variable", monospace',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Monospace font-family lists shared by every consumer of the editor / terminal
|
||||
* font. "JetBrains Mono" is the bundled default, but the bundle registers it as
|
||||
* "JetBrains Mono Variable" (`@fontsource-variable/jetbrains-mono`); a list that
|
||||
* only names "JetBrains Mono" falls through to the system's generic monospace
|
||||
* wherever the font isn't installed (SimSun on Chinese Windows).
|
||||
*/
|
||||
|
||||
export const BUNDLED_MONO_FAMILY = "JetBrains Mono";
|
||||
const BUNDLED_MONO_FACES = ['"JetBrains Mono Variable"', '"JetBrains Mono"'];
|
||||
const MONO_STACK_FALLBACK = '"JetBrains Mono Variable", "JetBrains Mono", ui-monospace, Menlo, monospace';
|
||||
|
||||
const quote = (f: string) => `"${f.replace(/"/g, "").trim()}"`;
|
||||
|
||||
/** The `--font-mono` stack, resolved (canvas / xterm can't use `var()`). */
|
||||
export function monoStack(): string {
|
||||
if (typeof document === "undefined") return MONO_STACK_FALLBACK;
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-mono").trim() || MONO_STACK_FALLBACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* `family`, then `fallback`, then `base` (the `--font-mono` stack: `var(--font-mono)`
|
||||
* for CSS, `monoStack()` for canvas measurement). The bundled family maps to its
|
||||
* bundled face, so it renders without a system install.
|
||||
*/
|
||||
export function monoFontFamily(family: string | undefined, fallback = "", base = monoStack()): string {
|
||||
const name = (family ?? "").replace(/"/g, "").trim();
|
||||
const fb = fallback.replace(/"/g, "").trim();
|
||||
const head = !name || name === BUNDLED_MONO_FAMILY ? BUNDLED_MONO_FACES : [quote(name)];
|
||||
return [...head, ...(fb ? [quote(fb)] : []), base].join(", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves once the faces `family` uses at `size` are loaded, so code that measures
|
||||
* glyphs (xterm's cell size) measures the real font rather than the swap fallback.
|
||||
*/
|
||||
export async function fontsLoaded(family: string, size: number): Promise<void> {
|
||||
const fonts = typeof document !== "undefined" ? document.fonts : undefined;
|
||||
if (!fonts?.load) return;
|
||||
try {
|
||||
await Promise.all([fonts.load(`${size}px ${family}`), fonts.load(`bold ${size}px ${family}`)]);
|
||||
} catch {
|
||||
/* an unparsable list measures with whatever is available */
|
||||
}
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача