feat(editor): add CJK fallback fonts to monospace stacks

This commit is contained in:
lxk955
2026-10-04 09:55:44 +08:00
committed by GitHub
parent 72a25f6e9f
commit b10ecd096c
7 changed files with 126 additions and 18 deletions
+72
View File
@@ -0,0 +1,72 @@
import { describe, expect, it } from "vitest";
import { cssFontFamilyForName, DEFAULT_MONO_FALLBACK_FONTS, DEFAULT_MONO_FONT_FAMILY, FONT_FAMILIES, LEGACY_PRESET_FONT_MAP, normalizeCustomFontFamilyInput, readableFontFamily } from "../appFonts";
import { displayFontFamily, isPresetFontFamily } from "../fontFamilyOptions";
describe("appFonts", () => {
it("includes CJK fallbacks in DEFAULT_MONO_FALLBACK_FONTS", () => {
expect(DEFAULT_MONO_FALLBACK_FONTS).toContain("PingFang SC");
expect(DEFAULT_MONO_FALLBACK_FONTS).toContain("Hiragino Sans GB");
expect(DEFAULT_MONO_FALLBACK_FONTS).toContain("Microsoft YaHei");
expect(DEFAULT_MONO_FALLBACK_FONTS).toMatch(/monospace$/);
});
it("appends CJK fallbacks to DEFAULT_MONO_FONT_FAMILY", () => {
expect(DEFAULT_MONO_FONT_FAMILY).toContain("'Fira Code'");
expect(DEFAULT_MONO_FONT_FAMILY).toContain(DEFAULT_MONO_FALLBACK_FONTS);
});
it("appends CJK fallbacks to every preset font family", () => {
for (const preset of FONT_FAMILIES) {
expect(preset.value).toContain(DEFAULT_MONO_FALLBACK_FONTS);
}
});
it("maps legacy preset values to updated stacks with CJK fallbacks", () => {
expect(LEGACY_PRESET_FONT_MAP["'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace"]).toBe(DEFAULT_MONO_FONT_FAMILY);
expect(LEGACY_PRESET_FONT_MAP["'JetBrains Mono', 'Fira Code', monospace"]).toContain(DEFAULT_MONO_FALLBACK_FONTS);
expect(LEGACY_PRESET_FONT_MAP["'Consolas', 'Courier New', monospace"]).toContain(DEFAULT_MONO_FALLBACK_FONTS);
expect(LEGACY_PRESET_FONT_MAP["monospace"]).toBe(DEFAULT_MONO_FALLBACK_FONTS);
});
it("constructs CSS font family for system font name with CJK fallbacks", () => {
expect(cssFontFamilyForName("Ubuntu Mono")).toBe(`'Ubuntu Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`);
expect(cssFontFamilyForName("JetBrains Mono")).toBe(`'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`);
expect(cssFontFamilyForName("Font's Name")).toBe(`'Font\\'s Name', ${DEFAULT_MONO_FALLBACK_FONTS}`);
});
it("extracts readable font family name from font stacks", () => {
expect(readableFontFamily(DEFAULT_MONO_FONT_FAMILY)).toBe("Fira Code");
expect(readableFontFamily(`'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`)).toBe("JetBrains Mono");
expect(readableFontFamily("Consolas")).toBe("Consolas");
});
it("normalizes custom font family input", () => {
expect(normalizeCustomFontFamilyInput("")).toBe("");
expect(normalizeCustomFontFamilyInput(" ")).toBe("");
expect(normalizeCustomFontFamilyInput("Hack")).toBe(`'Hack', ${DEFAULT_MONO_FALLBACK_FONTS}`);
// Custom font stacks with quotes or commas are preserved verbatim
expect(normalizeCustomFontFamilyInput("'My Custom Font', monospace")).toBe("'My Custom Font', monospace");
expect(normalizeCustomFontFamilyInput("Fira Code, PingFang SC, monospace")).toBe("Fira Code, PingFang SC, monospace");
});
it("recognizes preset font labels for both current and legacy preset values", () => {
expect(displayFontFamily(DEFAULT_MONO_FONT_FAMILY)).toBe("Fira Code");
expect(displayFontFamily(DEFAULT_MONO_FALLBACK_FONTS)).toBe("System Monospace");
// Legacy preset values
expect(displayFontFamily("'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace")).toBe("Fira Code");
expect(displayFontFamily("'JetBrains Mono', 'Fira Code', monospace")).toBe("JetBrains Mono");
expect(displayFontFamily("monospace")).toBe("System Monospace");
// Custom font fallback
expect(displayFontFamily("'My Font', monospace")).toBe("My Font");
});
it("identifies preset font values for both current and legacy presets", () => {
expect(isPresetFontFamily(DEFAULT_MONO_FONT_FAMILY)).toBe(true);
expect(isPresetFontFamily(DEFAULT_MONO_FALLBACK_FONTS)).toBe(true);
expect(isPresetFontFamily("'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace")).toBe(true);
expect(isPresetFontFamily("monospace")).toBe(true);
expect(isPresetFontFamily("'Unknown Font', monospace")).toBe(false);
});
});
+24 -9
View File
@@ -3,26 +3,41 @@ export const FONT_MONO_CSS_VAR = "--font-mono";
export const DATA_GRID_FONT_FAMILY_CSS_VAR = "--dbx-data-grid-font-family";
export const DEFAULT_UI_FONT_FAMILY = `"Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif`;
// CJK and system fallback fonts appended to monospace font stacks so Chinese/CJK
// glyphs fall back smoothly to sans-serif CJK fonts (e.g. PingFang SC on macOS,
// Microsoft YaHei on Windows) instead of ugly SimSun/NSimSun or bitmap fonts (dbx#674, dbx#1510).
export const DEFAULT_MONO_FALLBACK_FONTS = `"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", monospace`;
// Keep in sync with the editor font default in settingsStore; also the baseline
// `--font-mono` value in tokens.css, which plugin sandboxes consume via the bridge.
export const DEFAULT_MONO_FONT_FAMILY = `'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace`;
export const DEFAULT_MONO_FONT_FAMILY = `'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`;
export const DEFAULT_DATA_GRID_FONT_FAMILY = `"Geist Variable Tabular", "Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif`;
// Native-feeling UI option without DBX's bundled/brand font at the front of the stack.
export const SYSTEM_UI_FONT_FAMILY = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
export const FONT_FAMILIES: { value: string; label: string }[] = [
{ value: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace", label: "Fira Code" },
{ value: "'JetBrains Mono', 'Fira Code', monospace", label: "JetBrains Mono" },
{ value: "'Cascadia Code', 'Cascadia Mono', monospace", label: "Cascadia Code" },
{ value: "'Source Code Pro', monospace", label: "Source Code Pro" },
{ value: "'SF Mono', 'Menlo', monospace", label: "SF Mono / Menlo" },
{ value: "'Consolas', 'Courier New', monospace", label: "Consolas" },
{ value: "monospace", label: "System Monospace" },
{ value: `'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Fira Code" },
{ value: `'JetBrains Mono', 'Fira Code', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "JetBrains Mono" },
{ value: `'Cascadia Code', 'Cascadia Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Cascadia Code" },
{ value: `'Source Code Pro', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Source Code Pro" },
{ value: `'SF Mono', 'Menlo', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "SF Mono / Menlo" },
{ value: `'Consolas', 'Courier New', ${DEFAULT_MONO_FALLBACK_FONTS}`, label: "Consolas" },
{ value: DEFAULT_MONO_FALLBACK_FONTS, label: "System Monospace" },
];
export const LEGACY_PRESET_FONT_MAP: Record<string, string> = {
"'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace": DEFAULT_MONO_FONT_FAMILY,
"'JetBrains Mono', 'Fira Code', monospace": `'JetBrains Mono', 'Fira Code', ${DEFAULT_MONO_FALLBACK_FONTS}`,
"'Cascadia Code', 'Cascadia Mono', monospace": `'Cascadia Code', 'Cascadia Mono', ${DEFAULT_MONO_FALLBACK_FONTS}`,
"'Source Code Pro', monospace": `'Source Code Pro', ${DEFAULT_MONO_FALLBACK_FONTS}`,
"'SF Mono', 'Menlo', monospace": `'SF Mono', 'Menlo', ${DEFAULT_MONO_FALLBACK_FONTS}`,
"'Consolas', 'Courier New', monospace": `'Consolas', 'Courier New', ${DEFAULT_MONO_FALLBACK_FONTS}`,
monospace: DEFAULT_MONO_FALLBACK_FONTS,
};
export function cssFontFamilyForName(name: string): string {
return `'${name.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', monospace`;
return `'${name.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', ${DEFAULT_MONO_FALLBACK_FONTS}`;
}
export function readableFontFamily(value: string): string {
@@ -1,11 +1,17 @@
import { cssFontFamilyForName, FONT_FAMILIES, readableFontFamily } from "@/lib/app/appFonts";
import { cssFontFamilyForName, FONT_FAMILIES, LEGACY_PRESET_FONT_MAP, readableFontFamily } from "@/lib/app/appFonts";
import { listSystemFonts } from "@/lib/backend/api";
let cachedSystemFontNames: string[] | null = null;
let pendingSystemFontNames: Promise<string[]> | null = null;
const presetFontLabels = new Map(FONT_FAMILIES.map((font) => [font.value, font.label]));
const presetFontValues = new Set(FONT_FAMILIES.map((font) => font.value));
const presetFontLabels = new Map<string, string>([
...FONT_FAMILIES.map((font) => [font.value, font.label] as const),
...Object.entries(LEGACY_PRESET_FONT_MAP).map(([legacyVal, newVal]) => {
const label = FONT_FAMILIES.find((f) => f.value === newVal)?.label ?? "System Monospace";
return [legacyVal, label] as const;
}),
]);
const presetFontValues = new Set<string>([...FONT_FAMILIES.map((font) => font.value), ...Object.keys(LEGACY_PRESET_FONT_MAP)]);
export function buildFontFamilyOptions(systemFontNames: readonly string[], selectedValues: readonly string[] = [], leadingValues: readonly string[] = []): string[] {
return [...new Set([...leadingValues, ...FONT_FAMILIES.map((font) => font.value), ...systemFontNames.map(cssFontFamilyForName), ...selectedValues.filter(Boolean)])];
+12
View File
@@ -21,6 +21,7 @@ import {
} from "@/stores/settingsStore";
import type { AiConfigItem } from "@/types/ai";
import { DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION } from "@/lib/dataGrid/dataGridCopyExtractor";
import { DEFAULT_MONO_FONT_FAMILY } from "@/lib/app/appFonts";
describe("normalizeEditorSettings", () => {
it("preserves comment-first naming for existing settings and permits opting out", () => {
@@ -647,6 +648,17 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ tableFontFamily: " " }).tableFontFamily).toBe(defaultFontFamily);
});
it("defaults the editor font with CJK fallbacks and migrates legacy default presets", () => {
expect(normalizeEditorSettings({}).fontFamily).toBe(DEFAULT_MONO_FONT_FAMILY);
expect(
normalizeEditorSettings({
fontFamily: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
}).fontFamily,
).toBe(DEFAULT_MONO_FONT_FAMILY);
expect(normalizeEditorSettings({ fontFamily: "'Custom Mono', monospace" }).fontFamily).toBe("'Custom Mono', monospace");
expect(normalizeEditorSettings({ fontFamily: " " }).fontFamily).toBe(DEFAULT_MONO_FONT_FAMILY);
});
it("shows cell detail metadata by default and preserves collapsed state", () => {
expect(normalizeEditorSettings({}).cellDetailMetadataCollapsed).toBe(false);
expect(normalizeEditorSettings({ cellDetailMetadataCollapsed: true }).cellDetailMetadataCollapsed).toBe(true);
+6 -3
View File
@@ -3,7 +3,7 @@ import { defineStore } from "pinia";
import { computed, ref } from "vue";
import { aiConfigToItem, generateId, getConfigKey } from "@/lib/ai/aiConfigList";
import { AI_CONVERSATION_FONT_FAMILY_DEFAULT, AI_CONVERSATION_FONT_SIZE_DEFAULT, normalizeAiConversationFontFamily, normalizeAiConversationFontSize } from "@/lib/ai/aiTypography";
import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts";
import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_MONO_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts";
import { emitAlwaysOnTopToolbarVisibilityChanged } from "@/lib/app/windowAlwaysOnTop";
import { defaultBackgroundImageSettings, normalizeBackgroundImageSettings, type BackgroundImageSettings } from "@/lib/app/appBackgroundImage";
import * as api from "@/lib/backend/api";
@@ -1176,7 +1176,7 @@ export const EXECUTE_MODE_CURRENT_DEFAULT_VERSION = 1;
export const SIDEBAR_BROWSE_OBJECTS_MIGRATION_VERSION = 1;
export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
fontFamily: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
fontFamily: DEFAULT_MONO_FONT_FAMILY,
fontSize: 13,
uiFontFamily: DEFAULT_UI_FONT_FAMILY,
aiFontFamily: AI_CONVERSATION_FONT_FAMILY_DEFAULT,
@@ -1392,10 +1392,13 @@ function normalizeUiScale(value: unknown): number {
return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(value * 100) / 100));
}
const LEGACY_DEFAULT_MONO_FONT_FAMILY = "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace";
function normalizeFontFamily(value: unknown, fallback: string): string {
if (typeof value !== "string") return fallback;
const trimmed = value.trim();
return trimmed || fallback;
if (!trimmed || trimmed === LEGACY_DEFAULT_MONO_FONT_FAMILY) return fallback;
return trimmed;
}
function normalizeDrawerWidth(value: unknown, min: number, fallback: number): number {
+1 -1
View File
@@ -42,7 +42,7 @@
--font-heading: var(--font-sans);
/* Baseline mono token; App.vue overrides it inline with the editor font
setting, and plugin sandboxes receive the resolved value via the bridge. */
--font-mono: 'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace;
--font-mono: 'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", monospace;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+2 -2
View File
@@ -105,7 +105,7 @@ const themes = () => ({
"--color-destructive": "#f3625f",
"--color-destructive-foreground": "#18181b",
"--font-sans": '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", monospace",
"--radius-md": "6px",
"--radius-lg": "8px",
}
@@ -124,7 +124,7 @@ const themes = () => ({
"--color-destructive": "#e7000b",
"--color-destructive-foreground": "#ffffff",
"--font-sans": '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
"--font-mono": "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", monospace",
"--radius-md": "6px",
"--radius-lg": "8px",
},