fix(theme): route CJK symbol fallback through the system font on WebKit

macOS 26 / iOS 26 ship the PingFang that web content gets as a reduced
font: some 1,400 glyphs (U+FF5C |, ①, ★, ※, kana, ...) moved to a
hidden ".CJK Symbols Fallback" font that only CoreText's language-aware
system fallback knows about. The full font is an on-demand asset flagged
as user-installed, which Safari keeps away from web content, while Orion
and Chrome still see it.

WebKit expands -apple-system into the system font plus CoreText's cascade
list and walks that list with each font's full character map, so those
characters were drawn by Apple Symbols or the Japanese UI font at a
single weight before the system fallback ever ran. Drop -apple-system
from the CJK stacks and add two empty 'Inter Core' faces backed by
local(-apple-system): WebKit performs system fallback relative to the
first face of the first family, so the system font becomes that base
without rendering anything (U+10FFFF), and the missing symbols now come
from the same fonts native apps use, at the requested weight. Other
engines never load the faces, and the primary font used for metrics is
still the face covering U+0020.

Follow-up to #5428.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pull/5434/head
Divyansh Singh 3 weeks ago
parent 74f9d1d309
commit ae3a548e7f

@ -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}"
)

@ -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';
}

Loading…
Cancel
Save