diff --git a/scripts/subsetFonts.py b/scripts/subsetFonts.py index 78fee145d..14f17d864 100755 --- a/scripts/subsetFonts.py +++ b/scripts/subsetFonts.py @@ -57,11 +57,18 @@ BASE_VAR = """\ # got a chance to pick the right CJK font. The names below cover the defaults # of macOS / Windows / Linux (fonts-noto-cjk); families that are not installed # are simply skipped, and sites can splice a CJK webfont between 'Inter Core' -# and the system families. Bare zh means Hans per BCP 47 likely subtags, and -# with no Traditional Chinese rule it also covers zh-Hant/zh-TW/zh-HK/zh-MO -# pages. If Hant handling is ever requested, add after the zh rule (same -# specificity, so the later rule wins; the region tags must be enumerated -# because `:lang(zh-Hant)` cannot match e.g. `lang="zh-TW"`): +# and the system families. Unlike the base stack, these do not name +# -apple-system: WebKit expands it into the system font plus CoreText's whole +# cascade list and walks that list with each font's full character map, which +# on macOS 26 / iOS 26 hands the symbols missing from the reduced PingFang +# (U+FF5C, ①, ★, ※, ...) to Apple Symbols or the Japanese UI font at a +# single weight. The CJK_FALLBACK_ANCHOR faces route them through the +# language-aware system fallback instead. Bare zh means Hans per BCP 47 +# likely subtags, and with no Traditional Chinese rule it also covers +# zh-Hant/zh-TW/zh-HK/zh-MO pages. If Hant handling is ever requested, add +# after the zh rule (same specificity, so the later rule wins; the region +# tags must be enumerated because `:lang(zh-Hant)` cannot match e.g. +# `lang="zh-TW"`): # [lang]:where(:lang(zh-Hant), :lang(zh-TW), :lang(zh-HK), :lang(zh-MO)) { # --vp-font-family-base: # 'Inter Core', 'PingFang TC', 'Microsoft JhengHei', 'Noto Sans CJK TC', @@ -72,22 +79,55 @@ CJK_BASE_VAR = """\ [lang]:where(:lang(zh)) { --vp-font-family-base: 'Inter Core', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', - -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; } [lang]:where(:lang(ja)) { --vp-font-family-base: 'Inter Core', 'Hiragino Sans', 'Meiryo', 'Yu Gothic', 'Noto Sans CJK JP', - -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; } [lang]:where(:lang(ko)) { --vp-font-family-base: 'Inter Core', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans CJK KR', - -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; +} +""" + +# macOS 26 / iOS 26 ship two copies of PingFang: the full one is an on-demand +# asset that CoreText flags as user-installed, which Safari keeps away from +# web content, so pages get the reduced one, from which some 1,400 glyphs - +# U+FF5C |, ①, ★, ※, kana, ... - moved to a hidden ".CJK Symbols Fallback" +# font. Only CoreText's language-aware system fallback knows about that font, +# and WebKit performs this fallback relative to the first face of the first +# family in the stack (FontCascadeFonts::findBestFallbackFont), i.e. relative +# to Inter, whose fallback chain reaches Hiragino, Helvetica and Lucida Grande +# first. The faces below make the system font that base instead: U+10FFFF is +# a noncharacter, so they never draw anything themselves and do not affect +# the primary font used for metrics (the face covering U+0020), and other +# engines never load them. WebKit orders the faces of a family last-declared- +# first, so these must remain the last 'Inter Core' rules. The result matches +# native apps: SF for the symbols it has, the CJK symbols font for the rest, +# both at the requested weight. +CJK_FALLBACK_ANCHOR = """\ +@font-face { + font-family: 'Inter Core'; + font-style: normal; + font-weight: 100 900; + src: local(-apple-system); + unicode-range: U+10FFFF; +} + +@font-face { + font-family: 'Inter Core'; + font-style: italic; + font-weight: 100 900; + src: local(-apple-system); + unicode-range: U+10FFFF; } """ @@ -256,6 +296,7 @@ def write_css(subsets: dict[str, str], cjk_exclusions: set[int]) -> None: + "\n".join(inter) + f"\n{CJK_COMMENT}\n" + "\n".join(cjk) + + f"\n{CJK_FALLBACK_ANCHOR}" + f"\n{BASE_VAR}\n{CJK_BASE_VAR}" ) diff --git a/src/client/theme-default/styles/fonts.css b/src/client/theme-default/styles/fonts.css index ed6d01df4..6b2764673 100644 --- a/src/client/theme-default/styles/fonts.css +++ b/src/client/theme-default/styles/fonts.css @@ -301,6 +301,22 @@ unicode-range: U+0302, U+0306-0307, U+030A, U+030C, U+030F, U+0313, U+0315, U+031B, U+0326-0328, U+032C, U+0337-0338, U+0342-0343, U+0346-036F, U+0E3F, U+1DC0-1DF5, U+1DFC-1DFF, U+2070-2071, U+2075-207E, U+2080, U+2085-208E, U+2090-209C, U+20DB-20DE, U+20E8, U+20F0, U+2100-2101, U+2106, U+2117, U+211E-2120, U+212A, U+212E, U+2132, U+213B, U+214D, U+2150-2152, U+2155-215A, U+215F, U+216C-216F, U+217A-217F, U+2183-2186, U+21AA, U+21B0-21B1, U+21B3-21B5, U+21BA-21BB, U+21D0, U+21DE-21DF, U+21E4-21E5, U+21EA, U+2205-2206, U+2296-2298, U+2303-2305, U+2325-2327, U+232B, U+2380, U+2387, U+238B, U+23CE-23CF, U+2423, U+24EA, U+25A2, U+25AA, U+25BA-25BB, U+25C4-25C5, U+25CA, U+25E6, U+2600, U+263C, U+266B, U+26A0, U+2713, U+2717, U+2756, U+2764, U+2780-2788, U+27EF, U+27F5-27FA, U+2913, U+2A38, U+2B06, U+2B12-2B13, U+2B24, U+2E18, U+A92E, U+1F12F, U+1F16A-1F16B, U+1F850, U+1F852; } +@font-face { + font-family: 'Inter Core'; + font-style: normal; + font-weight: 100 900; + src: local(-apple-system); + unicode-range: U+10FFFF; +} + +@font-face { + font-family: 'Inter Core'; + font-style: italic; + font-weight: 100 900; + src: local(-apple-system); + unicode-range: U+10FFFF; +} + :root { --vp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', @@ -310,20 +326,20 @@ [lang]:where(:lang(zh)) { --vp-font-family-base: 'Inter Core', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', - -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; } [lang]:where(:lang(ja)) { --vp-font-family-base: 'Inter Core', 'Hiragino Sans', 'Meiryo', 'Yu Gothic', 'Noto Sans CJK JP', - -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; } [lang]:where(:lang(ko)) { --vp-font-family-base: 'Inter Core', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans CJK KR', - -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + BlinkMacSystemFont, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; }