дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
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
79 рядки
2.3 KiB
TypeScript
79 рядки
2.3 KiB
TypeScript
/**
|
|
* Bounded legacy fallback, never the primary desktop catalog. Width comparisons
|
|
* can miss installed families and are only hints; they say nothing about coverage.
|
|
*/
|
|
import type { FontFamily } from "@/bindings";
|
|
import { createFontProbeCanvas, quoteFontFamily } from "./fonts";
|
|
|
|
// Keep the former editor's 31 candidates for recovery, not as an installed-font whitelist.
|
|
const CANDIDATES = [
|
|
"JetBrains Mono",
|
|
"SF Mono",
|
|
"Menlo",
|
|
"Monaco",
|
|
"Fira Code",
|
|
"Fira Mono",
|
|
"Source Code Pro",
|
|
"Cascadia Code",
|
|
"Cascadia Mono",
|
|
"IBM Plex Mono",
|
|
"Hack",
|
|
"Inconsolata",
|
|
"Roboto Mono",
|
|
"Ubuntu Mono",
|
|
"DejaVu Sans Mono",
|
|
"Consolas",
|
|
"Courier New",
|
|
"PT Mono",
|
|
"Andale Mono",
|
|
"Victor Mono",
|
|
"Iosevka",
|
|
"Sarasa Mono SC",
|
|
"Noto Sans Mono",
|
|
"Noto Sans Mono CJK SC",
|
|
"PingFang SC",
|
|
"Hiragino Sans GB",
|
|
"Microsoft YaHei",
|
|
"Helvetica Neue",
|
|
"Arial",
|
|
"Inter",
|
|
"Georgia",
|
|
];
|
|
|
|
export function detectCandidateFonts(): FontFamily[] {
|
|
let canvas: HTMLCanvasElement | undefined;
|
|
try {
|
|
if (typeof document === "undefined") return [];
|
|
canvas = createFontProbeCanvas();
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return [];
|
|
const width = (font: string, text: string) => {
|
|
ctx.font = `32px ${font}`;
|
|
return ctx.measureText(text).width;
|
|
};
|
|
const sample = "mmmmmmmmmmlli1WQ@";
|
|
const out: FontFamily[] = [];
|
|
for (const name of CANDIDATES) {
|
|
const present = ["monospace", "serif", "sans-serif"].some((base) => {
|
|
const measured = width(`${quoteFontFamily(name)}, ${base}`, sample);
|
|
const baseline = width(base, sample);
|
|
return Number.isFinite(measured) && Number.isFinite(baseline) && measured !== baseline;
|
|
});
|
|
if (!present) continue;
|
|
const narrow = width(`${quoteFontFamily(name)}, monospace`, "iiiiiiiiii");
|
|
const wide = width(`${quoteFontFamily(name)}, monospace`, "WWWWWWWWWW");
|
|
out.push({
|
|
name,
|
|
monospaced: Number.isFinite(narrow) && Number.isFinite(wide) && Math.abs(narrow - wide) < 0.5,
|
|
});
|
|
}
|
|
return out;
|
|
} catch {
|
|
// Canvas can be absent or disabled in browser development / hardened webviews.
|
|
// Never mark unmeasurable candidates as installed. The service adds bundled faces.
|
|
return [];
|
|
} finally {
|
|
canvas?.remove();
|
|
}
|
|
}
|