191 lines
4.3 KiB
TypeScript
191 lines
4.3 KiB
TypeScript
|
|
/**
|
|||
|
|
* UI Fonts Configuration
|
|||
|
|
* Includes general-purpose fonts suitable for application interface
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
export interface UIFont {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
family: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export type UiPlatform = 'darwin' | 'win32' | 'linux';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Bundled Noto Color Emoji subset (regional indicators U+1F1E6–1F1FF).
|
|||
|
|
* Windows system fonts omit flag glyphs and show ISO country codes instead —
|
|||
|
|
* see #1589, #1604.
|
|||
|
|
*/
|
|||
|
|
export const WINDOWS_FLAG_EMOJI_FONT = '"Noto Color Emoji Flags"';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Windows bundles UI fonts via @font-face. Their regional-indicator glyphs
|
|||
|
|
* render as separate letters instead of composed flag emoji unless a color
|
|||
|
|
* emoji font is consulted first — see #1589.
|
|||
|
|
*/
|
|||
|
|
export const WINDOWS_UI_EMOJI_FONTS = WINDOWS_FLAG_EMOJI_FONT;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Fallback fonts for CJK (Chinese, Japanese, Korean) support
|
|||
|
|
*/
|
|||
|
|
const CJK_FALLBACK_FONTS = [
|
|||
|
|
'"PingFang SC"',
|
|||
|
|
'"Hiragino Sans GB"',
|
|||
|
|
'"Microsoft YaHei UI"',
|
|||
|
|
'"Microsoft YaHei"',
|
|||
|
|
'"Noto Sans CJK SC"',
|
|||
|
|
'"Source Han Sans SC"',
|
|||
|
|
'sans-serif',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const CJK_FALLBACK_STACK = CJK_FALLBACK_FONTS.join(', ');
|
|||
|
|
|
|||
|
|
export const withUiCjkFallback = (family: string) => {
|
|||
|
|
const trimmed = family.trim();
|
|||
|
|
// Avoid double-appending if a custom stack already includes one of these fonts.
|
|||
|
|
if (CJK_FALLBACK_FONTS.some((f) => trimmed.includes(f.replace(/"/g, '')))) {
|
|||
|
|
return trimmed;
|
|||
|
|
}
|
|||
|
|
return `${trimmed}, ${CJK_FALLBACK_STACK}`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export function detectUiPlatform(userAgent: string): UiPlatform {
|
|||
|
|
if (/Win/i.test(userAgent)) return 'win32';
|
|||
|
|
if (/Mac|iPod|iPhone|iPad/i.test(userAgent)) return 'darwin';
|
|||
|
|
return 'linux';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function withWindowsEmojiFallback(
|
|||
|
|
family: string,
|
|||
|
|
platform: UiPlatform = typeof navigator !== 'undefined'
|
|||
|
|
? detectUiPlatform(navigator.userAgent)
|
|||
|
|
: 'linux',
|
|||
|
|
): string {
|
|||
|
|
if (platform !== 'win32') return family;
|
|||
|
|
const trimmed = family.trim();
|
|||
|
|
if (trimmed.includes('Noto Color Emoji Flags') || trimmed.includes('Segoe UI Emoji')) {
|
|||
|
|
return trimmed;
|
|||
|
|
}
|
|||
|
|
return `${WINDOWS_UI_EMOJI_FONTS}, ${trimmed}, "Segoe UI Emoji", "Segoe UI Symbol"`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const BASE_UI_FONTS: UIFont[] = [
|
|||
|
|
{
|
|||
|
|
id: 'mona-sans',
|
|||
|
|
name: 'Mona Sans',
|
|||
|
|
family: '"Mona Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'space-grotesk',
|
|||
|
|
name: 'Space Grotesk',
|
|||
|
|
family: '"Space Grotesk", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'system-ui',
|
|||
|
|
name: 'System UI',
|
|||
|
|
family: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'inter',
|
|||
|
|
name: 'Inter',
|
|||
|
|
family: '"Inter", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'roboto',
|
|||
|
|
name: 'Roboto',
|
|||
|
|
family: '"Roboto", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'open-sans',
|
|||
|
|
name: 'Open Sans',
|
|||
|
|
family: '"Open Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'lato',
|
|||
|
|
name: 'Lato',
|
|||
|
|
family: '"Lato", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'nunito',
|
|||
|
|
name: 'Nunito',
|
|||
|
|
family: '"Nunito", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'poppins',
|
|||
|
|
name: 'Poppins',
|
|||
|
|
family: '"Poppins", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'source-sans-pro',
|
|||
|
|
name: 'Source Sans Pro',
|
|||
|
|
family: '"Source Sans Pro", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'ubuntu',
|
|||
|
|
name: 'Ubuntu',
|
|||
|
|
family: '"Ubuntu", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'noto-sans',
|
|||
|
|
name: 'Noto Sans',
|
|||
|
|
family: '"Noto Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'work-sans',
|
|||
|
|
name: 'Work Sans',
|
|||
|
|
family: '"Work Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'dm-sans',
|
|||
|
|
name: 'DM Sans',
|
|||
|
|
family: '"DM Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'montserrat',
|
|||
|
|
name: 'Montserrat',
|
|||
|
|
family: '"Montserrat", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'raleway',
|
|||
|
|
name: 'Raleway',
|
|||
|
|
family: '"Raleway", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'quicksand',
|
|||
|
|
name: 'Quicksand',
|
|||
|
|
family: '"Quicksand", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'ibm-plex-sans',
|
|||
|
|
name: 'IBM Plex Sans',
|
|||
|
|
family: '"IBM Plex Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'outfit',
|
|||
|
|
name: 'Outfit',
|
|||
|
|
family: '"Outfit", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'plus-jakarta-sans',
|
|||
|
|
name: 'Plus Jakarta Sans',
|
|||
|
|
family: '"Plus Jakarta Sans", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'segoe-ui',
|
|||
|
|
name: 'Segoe UI',
|
|||
|
|
family: '"Segoe UI", system-ui',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 'sf-pro',
|
|||
|
|
name: 'SF Pro',
|
|||
|
|
family: '-apple-system, BlinkMacSystemFont, "SF Pro Display", system-ui',
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export const UI_FONTS: UIFont[] = BASE_UI_FONTS.map((font) => ({
|
|||
|
|
...font,
|
|||
|
|
family: withUiCjkFallback(font.family),
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
export const DEFAULT_UI_FONT_ID = 'mona-sans';
|