fix(fonts): render unavailable macOS user fonts through binary IPC (#643)

On macOS versions where WKWebView refuses user-installed fonts by family name (fonts in ~/Library/Fonts or /Library/Fonts), the editor, terminal, Markdown and UI fell back to the default font and Nerd Font icons showed as boxes; only a webfont loaded from the file's bytes renders.

Root cause: the shared font resolver only ever referred to fonts by local family name, so every consumer depended on the WebView resolving user fonts by name. A URL-based webfont is blocked by the existing font-src policy, which stays unchanged.

Fix: the shared resolver probes whether a family renders by name (on a document-attached canvas; a detached canvas falls back even when the font works) and only when it does not, fetches the enumerated user font files over a raw-bytes IPC (user fonts only, looked up by family, no paths accepted) and registers them with the FontFace API under a stable alias. Only the faces nearest regular/bold and their italics are loaded, once per family. Covers editor and editor fallback, diff/merge views, terminal font, fallback and built-in Nerd fallback (re-measured after loading), embedded terminal, settings preview, custom UI font, Markdown and Mermaid. A debug-only environment switch reproduces the WebView restriction for testing.

Verified on macOS 26 with the restriction simulated (icons boxes before, correct after) and without it (no font bytes transferred, no regression). Not run on the reporter's macOS version, Windows or Linux; other platforms keep the previous behaviour.

Fixes #592
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-01 12:23:08 +08:00
1 джерело 7a58d9f253
коміт 1e898367a5
14 files changed
+544 -22

No files matched your search

+4 -5
Переглянути файл
@@ -6,7 +6,7 @@
*/
import type { ITheme, Terminal } from "@xterm/xterm";
import { fontWeightOf } from "@/platform/editorSettings";
import { BUNDLED_MONO_FAMILY, fontsLoaded, monoFontFamily, monoStack } from "@/platform/fonts";
import { BUNDLED_MONO_FAMILY, familyFaces, fontsLoaded, monoFontFamily, monoStack } from "@/platform/fonts";
import { parseColor, type Rgba } from "@/platform/cssColor";
export { parseColor, type Rgba };
@@ -152,9 +152,7 @@ const NERD_FONT_FALLBACKS = [
"FiraCode Nerd Font",
"Hasklug Nerd Font",
"CaskaydiaCove Nerd Font",
]
.map((f) => `"${f}"`)
.join(", ");
];
export interface TerminalFontSettings {
useEditorFont: boolean;
@@ -189,7 +187,8 @@ export interface TerminalFont {
* common installs before the generic family so per-glyph fallback finds them.
*/
function withNerdFonts(family: string): string {
return `${family.replace(/,\s*monospace\s*$/i, "")}, ${NERD_FONT_FALLBACKS}, monospace`;
const fallbacks = NERD_FONT_FALLBACKS.flatMap(familyFaces).join(", ");
return `${family.replace(/,\s*monospace\s*$/i, "")}, ${fallbacks}, monospace`;
}
function cssNumber(name: string): number {
+5 -1
Переглянути файл
@@ -3,6 +3,7 @@
* `[data-mermaid]` placeholder holding the source in `pre > code`; this module
* turns placeholders into SVG. The library is imported on first use only.
*/
import { fontsLoaded } from "@/platform/fonts";
import type { Mermaid } from "mermaid";
import { resolveTokenColor, toRgb } from "@/platform/cssColor";
import { attachPanZoom } from "../panZoom";
@@ -32,7 +33,9 @@ function loadMermaid(): Promise<Mermaid> {
export function themeKey(): string {
const root = document.documentElement;
const { theme = "", contrast = "", colorTheme = "", pluginTheme = "" } = root.dataset;
return `${theme}|${contrast}|${colorTheme}|${pluginTheme}|${root.style.getPropertyValue("--accent")}`;
const accent = root.style.getPropertyValue("--accent");
const font = getComputedStyle(root).getPropertyValue("--font-ui");
return `${theme}|${contrast}|${colorTheme}|${pluginTheme}|${accent}|${font}`;
}
/** Calls `cb` when the theme attributes (a plugin theme's included) or the accent on the root change. */
@@ -102,6 +105,7 @@ function removeTemp(id: string) {
async function renderOne(source: string, key: string): Promise<Result> {
const mermaid = await loadMermaid();
await fontsLoaded(getComputedStyle(document.documentElement).getPropertyValue("--font-ui").trim(), 16);
configure(mermaid, key);
const id = `openidea-mermaid-${++seq}`;
try {