feat(theme): cover all Inter glyphs, generate font subsets from a script

scripts/subsetFonts.py now regenerates the woff2 subsets and fonts.css
from the ranges in scripts/fontSubsets.json (pinned Inter release,
byte-reproducible builds):

- a new lazily-loaded `symbols` subset covers every glyph of the full
  font that the existing subsets skipped (arrows, math, geometric
  shapes, enclosed alphanumerics, keyboard symbols, ...); U+2318 and
  U+21A9 live in the latin subset instead as the theme's chrome uses
  them
- Inter4CJK faces are generated as the subset ranges minus East Asian
  Ambiguous punctuation/symbols so that CJK fonts render those in CJK
  documents, and now also apply to Korean
- the base typography variables move into fonts.css next to the faces;
  webfont mode no longer declares the Inter4CJK override (there is no
  such family on Google Fonts), and the redundant
  `font-optical-sizing: auto` is gone
- fix: the `:lang(zh, ja, ko)` selector list syntax is not supported in
  Chrome and silently matched nothing inside the forgiving `:where()`,
  so the CJK font handling and `line-break: strict` never applied there

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5335/head
Divyansh Singh 1 month ago
parent c2be0bf936
commit 705c1be0f8

@ -7,3 +7,6 @@ template
temp
!CHANGELOG.md
.temp
# generated by scripts/subsetFonts.py
src/client/theme-default/styles/fonts.css

@ -0,0 +1,11 @@
{
"cyrillic-ext": "U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F",
"cyrillic": "U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116",
"greek-ext": "U+1F00-1FFF",
"greek": "U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF",
"vietnamese": "U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB",
"latin-ext": "U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF",
"latin": "U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+21A9,U+2212,U+2215,U+2318,U+FEFF,U+FFFD",
"symbols": "U+02CD,U+02D8-02D9,U+02DB,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+2074-208E,U+2090-209C,U+20DB-20DE,U+20E8,U+20F0,U+2100-2101,U+2103,U+2105-2106,U+2109,U+2117,U+211E-2121,U+2126,U+212A-212B,U+212E,U+2132,U+213B,U+214D,U+2150-217F,U+2183-2186,U+2189,U+2190,U+2192,U+2194-2199,U+21AA,U+21B0-21B1,U+21B3-21B5,U+21BA-21BB,U+21D0,U+21D2,U+21D4,U+21DE-21DF,U+21E4-21E5,U+21E7,U+21EA,U+2202,U+2205-2206,U+220F,U+2211,U+221A,U+221E,U+222B,U+2236,U+2248,U+2260,U+2264-2265,U+2295-2298,U+2303-2305,U+2325-2327,U+232B,U+2380,U+2387,U+238B,U+23CE-23CF,U+2423,U+2460-2468,U+24B6-24CF,U+24EA,U+25A0-25A2,U+25AA,U+25B2-25B3,U+25B6-25B7,U+25BA-25BD,U+25C0-25C1,U+25C4-25C7,U+25CA-25CB,U+25CF,U+25E6,U+25EF,U+2600,U+2605-2606,U+263C,U+2661,U+2665,U+266A-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+2B1C,U+2B24,U+2E18,U+A92E,U+1F12F-1F149,U+1F16A-1F16B,U+1F850,U+1F852",
"cjkExclusions": "U+007E,U+00B7,U+02C4,U+02C7,U+02C9-02CB,U+02CD,U+02D0,U+02D8-02D9,U+02DB,U+02DD,U+02DF,U+2013-2015,U+2018-2019,U+201C-201D,U+2026,U+203B,U+2074,U+207F,U+2081-2084,U+2103,U+2105,U+2109,U+2121,U+2126,U+212B,U+2153-2154,U+215B-215E,U+2160-216B,U+2170-2179,U+2189,U+2190,U+2192,U+2194-2199,U+21D2,U+21D4,U+21E7,U+2202,U+220F,U+2211,U+221A,U+221E,U+222B,U+2236,U+2248,U+2260,U+2264-2265,U+2295,U+2460-2468,U+24B6-24CF,U+25A0-25A1,U+25B2-25B3,U+25B6-25B7,U+25BC-25BD,U+25C0-25C1,U+25C6-25C7,U+25CB,U+25CF,U+25EF,U+2605-2606,U+2661,U+2665,U+266A,U+2B1C,U+1F130-1F149"
}

@ -0,0 +1,214 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["fonttools>=4.63", "brotli"]
# ///
"""Regenerate the default theme's Inter webfont subsets.
Reads the subset definitions from scripts/fontSubsets.json, downloads the
pinned Inter release (cached in node_modules/.cache/inter), verifies that
every non-PUA codepoint of the full font is covered by some subset, writes
the woff2 files into src/client/theme-default/fonts, and regenerates the
@font-face declarations in the theme's fonts.css.
Usage: scripts/subsetFonts.py
"""
import io
import json
import sys
import urllib.request
import zipfile
from pathlib import Path
from fontTools import subset
from fontTools.ttLib import TTFont
INTER_VERSION = "4.1"
ROOT = Path(__file__).resolve().parent.parent
SUBSETS_JSON = ROOT / "scripts" / "fontSubsets.json"
FONTS_DIR = ROOT / "src" / "client" / "theme-default" / "fonts"
FONTS_CSS = ROOT / "src" / "client" / "theme-default" / "styles" / "fonts.css"
CACHE_DIR = ROOT / "node_modules" / ".cache" / "inter"
STYLES = {
"italic": ("InterVariable-Italic.ttf", "italic"),
"roman": ("InterVariable.ttf", "normal"),
}
# Inter maps its internal alternate glyphs into the Private Use Area. These
# are intentionally not shipped: they are not real text, and declaring PUA
# unicode-ranges would let Inter shadow icon fonts that live there.
PUA = range(0xE000, 0xF8FF + 1)
BASE_VAR = """\
:root {
--vp-font-family-base:
'Inter', -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
"""
CJK_BASE_VAR = """\
[lang]:where(:lang(zh), :lang(ja), :lang(ko)) {
--vp-font-family-base:
'Inter4CJK', -apple-system, BlinkMacSystemFont, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
"""
# used instead of everything else in this file when `useWebFonts` is enabled
# (only the content between the markers survives) - see
# src/node/plugins/webFontsPlugin.ts. There is no Inter4CJK on Google Fonts,
# so CJK documents use the regular Inter there.
WEBFONT_IMPORT = f"""\
/* webfont-marker-begin */
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
{BASE_VAR}\
/* webfont-marker-end */
"""
# The `cjkExclusions` key of the json lists characters that have both a
# Western (proportional) and an East Asian (em-square) form with no
# encoding-level distinction - mostly East Asian Ambiguous punctuation and
# symbols (UAX #11). The generated Inter4CJK faces reuse the same font files
# but leave these out of their unicode-ranges so that CJK fonts render them
# in CJK documents. References:
# https://www.unicode.org/L2/L2014/14006-sv-western-vs-cjk.pdf
# https://www.unicode.org/L2/L2018/18013-svs-proposal.pdf
# https://www.unicode.org/L2/L2018/18073-svs-proposal.pdf
# https://www.unicode.org/L2/L2023/23212r-quotes-svs-proposal.pdf
# https://github.com/w3c/clreq/blob/gh-pages/local.css
# & U+2015 (used like U+2014 in Japanese), U+203B (Japanese reference mark),
# U+007E (zh's request; both forms are fine in ja because it is unused there)
CJK_COMMENT = """\
/* Inter4CJK reuses the files above, but leaves out characters that should
be rendered by CJK fonts in CJK documents - see scripts/subsetFonts.py */
"""
def parse_ranges(value: str) -> set[int]:
cps: set[int] = set()
for part in value.split(","):
lo, _, hi = part.strip().removeprefix("U+").partition("-")
cps.update(range(int(lo, 16), int(hi or lo, 16) + 1))
return cps
def format_ranges(cps: set[int]) -> str:
ranges: list[tuple[int, int]] = []
start = prev = -2
for cp in sorted(cps):
if cp != prev + 1:
if start >= 0:
ranges.append((start, prev))
start = cp
prev = cp
ranges.append((start, prev))
return ", ".join(
f"U+{lo:04X}" if lo == hi else f"U+{lo:04X}-{hi:04X}" for lo, hi in ranges
)
def download_release() -> Path:
release_dir = CACHE_DIR / f"Inter-{INTER_VERSION}"
zip_path = CACHE_DIR / f"Inter-{INTER_VERSION}.zip"
if not zip_path.exists():
url = f"https://github.com/rsms/inter/releases/download/v{INTER_VERSION}/Inter-{INTER_VERSION}.zip"
print(f"downloading {url}")
CACHE_DIR.mkdir(parents=True, exist_ok=True)
with urllib.request.urlopen(url) as res:
zip_path.write_bytes(res.read())
with zipfile.ZipFile(zip_path) as zf:
for file, _ in STYLES.values():
if not (release_dir / file).exists():
zf.extract(file, release_dir)
return release_dir
def check_coverage(release: Path, subsets: dict[str, str]) -> None:
covered = set().union(*(parse_ranges(v) for v in subsets.values()))
ok = True
for file, _ in STYLES.values():
cmap = set(TTFont(release / file).getBestCmap())
missing = sorted(cp for cp in cmap - covered if cp not in PUA)
if missing:
ok = False
print(f"{file}: {len(missing)} codepoints not covered by any subset:")
print(" " + ", ".join(f"U+{cp:04X}" for cp in missing))
if not ok:
sys.exit("add the missing codepoints to a subset in scripts/fontSubsets.json")
def build_subsets(release: Path, subsets: dict[str, str]) -> None:
for style, (file, _) in STYLES.items():
for name, value in subsets.items():
options = subset.Options()
options.flavor = "woff2"
options.layout_features = [*options.layout_features, "pnum", "tnum"]
font = subset.load_font(release / file, options)
subsetter = subset.Subsetter(options)
subsetter.populate(unicodes=parse_ranges(value))
subsetter.subset(font)
buf = io.BytesIO()
subset.save_font(font, buf, options)
out = FONTS_DIR / f"inter-{style}-{name}.woff2"
changed = not out.exists() or out.read_bytes() != buf.getvalue()
if changed:
out.write_bytes(buf.getvalue())
print(
f"{out.name:36} {font['maxp'].numGlyphs:4} glyphs "
f"{buf.tell() / 1024:6.1f} KB{'' if changed else ' (unchanged)'}"
)
def face(family: str, css_style: str, file: str, ranges: str) -> str:
return (
"@font-face {\n"
f" font-family: {family};\n"
f" font-style: {css_style};\n"
" font-weight: 100 900;\n"
" font-display: swap;\n"
f" src: url('../fonts/{file}') format('woff2');\n"
f" unicode-range: {ranges};\n"
"}\n"
)
def write_css(subsets: dict[str, str], cjk_exclusions: set[int]) -> None:
inter = []
cjk = []
for style, (_, css_style) in STYLES.items():
for name, value in subsets.items():
file = f"inter-{style}-{name}.woff2"
inter.append(face("Inter", css_style, file, value.replace(",", ", ")))
cjk_cps = parse_ranges(value) - cjk_exclusions
if cjk_cps:
cjk.append(
face("Inter4CJK", css_style, file, format_ranges(cjk_cps))
)
FONTS_CSS.write_text(
f"{WEBFONT_IMPORT}\n"
"/* Generated by scripts/subsetFonts.py from scripts/fontSubsets.json */\n\n"
+ "\n".join(inter)
+ f"\n{CJK_COMMENT}\n"
+ "\n".join(cjk)
+ f"\n{BASE_VAR}\n{CJK_BASE_VAR}"
)
def main() -> None:
subsets: dict[str, str] = json.loads(SUBSETS_JSON.read_text())
cjk_exclusions = parse_ranges(subsets.pop("cjkExclusions"))
release = download_release()
check_coverage(release, subsets)
build_subsets(release, subsets)
write_css(subsets, cjk_exclusions)
print(f"\nwrote {len(subsets) * len(STYLES)} subsets and {FONTS_CSS.relative_to(ROOT)}")
if __name__ == "__main__":
main()

@ -1,3 +1,4 @@
import './without-fonts'
import './styles/fonts.css'
export * from './without-fonts'

@ -1,79 +1,76 @@
/* webfont-marker-begin */
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
:root {
--vp-font-family-base:
'Inter', -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
/* webfont-marker-end */
/* Generated by scripts/subsetFonts.py from scripts/fontSubsets.json */
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-cyrillic-ext.woff2') format('woff2');
unicode-range:
U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
src: url('../fonts/inter-italic-cyrillic-ext.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-cyrillic.woff2') format('woff2');
src: url('../fonts/inter-italic-cyrillic.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-greek-ext.woff2') format('woff2');
src: url('../fonts/inter-italic-greek-ext.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-greek.woff2') format('woff2');
unicode-range:
U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
src: url('../fonts/inter-italic-greek.woff2') format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-vietnamese.woff2') format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
U+1EA0-1EF9, U+20AB;
src: url('../fonts/inter-italic-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-latin-ext.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
src: url('../fonts/inter-italic-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: Inter;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-latin.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
src: url('../fonts/inter-italic-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+21A9, U+2212, U+2215, U+2318, U+FEFF, U+FFFD;
}
@font-face {
@ -81,164 +78,146 @@
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-cyrillic-ext.woff2') format('woff2');
unicode-range:
U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
src: url('../fonts/inter-italic-symbols.woff2') format('woff2');
unicode-range: U+02CD, U+02D8-02D9, U+02DB, 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+2074-208E, U+2090-209C, U+20DB-20DE, U+20E8, U+20F0, U+2100-2101, U+2103, U+2105-2106, U+2109, U+2117, U+211E-2121, U+2126, U+212A-212B, U+212E, U+2132, U+213B, U+214D, U+2150-217F, U+2183-2186, U+2189, U+2190, U+2192, U+2194-2199, U+21AA, U+21B0-21B1, U+21B3-21B5, U+21BA-21BB, U+21D0, U+21D2, U+21D4, U+21DE-21DF, U+21E4-21E5, U+21E7, U+21EA, U+2202, U+2205-2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2236, U+2248, U+2260, U+2264-2265, U+2295-2298, U+2303-2305, U+2325-2327, U+232B, U+2380, U+2387, U+238B, U+23CE-23CF, U+2423, U+2460-2468, U+24B6-24CF, U+24EA, U+25A0-25A2, U+25AA, U+25B2-25B3, U+25B6-25B7, U+25BA-25BD, U+25C0-25C1, U+25C4-25C7, U+25CA-25CB, U+25CF, U+25E6, U+25EF, U+2600, U+2605-2606, U+263C, U+2661, U+2665, U+266A-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+2B1C, U+2B24, U+2E18, U+A92E, U+1F12F-1F149, U+1F16A-1F16B, U+1F850, U+1F852;
}
@font-face {
font-family: Inter;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-cyrillic.woff2') format('woff2');
src: url('../fonts/inter-roman-cyrillic-ext.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-cyrillic.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: Inter;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-greek-ext.woff2') format('woff2');
src: url('../fonts/inter-roman-greek-ext.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: Inter;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-greek.woff2') format('woff2');
unicode-range:
U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
src: url('../fonts/inter-roman-greek.woff2') format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
font-family: Inter;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-vietnamese.woff2') format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
U+1EA0-1EF9, U+20AB;
src: url('../fonts/inter-roman-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: Inter;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-latin-ext.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
src: url('../fonts/inter-roman-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: Inter;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-latin.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
src: url('../fonts/inter-roman-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+21A9, U+2212, U+2215, U+2318, U+FEFF, U+FFFD;
}
/*
Some code points in Inter are not compatible with CJK languages.
https://www.unicode.org/L2/L2014/14006-sv-western-vs-cjk.pdf
https://www.unicode.org/L2/L2018/18013-svs-proposal.pdf
https://www.unicode.org/L2/L2018/18073-svs-proposal.pdf
https://www.unicode.org/L2/L2023/23212r-quotes-svs-proposal.pdf
https://github.com/w3c/clreq/blob/f837ffe4a5501e72c17bd02961ce1b440916624f/local.css#L4
& U+2015 (similar to U+2014 in Japanese)
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-symbols.woff2') format('woff2');
unicode-range: U+02CD, U+02D8-02D9, U+02DB, 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+2074-208E, U+2090-209C, U+20DB-20DE, U+20E8, U+20F0, U+2100-2101, U+2103, U+2105-2106, U+2109, U+2117, U+211E-2121, U+2126, U+212A-212B, U+212E, U+2132, U+213B, U+214D, U+2150-217F, U+2183-2186, U+2189, U+2190, U+2192, U+2194-2199, U+21AA, U+21B0-21B1, U+21B3-21B5, U+21BA-21BB, U+21D0, U+21D2, U+21D4, U+21DE-21DF, U+21E4-21E5, U+21E7, U+21EA, U+2202, U+2205-2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2236, U+2248, U+2260, U+2264-2265, U+2295-2298, U+2303-2305, U+2325-2327, U+232B, U+2380, U+2387, U+238B, U+23CE-23CF, U+2423, U+2460-2468, U+24B6-24CF, U+24EA, U+25A0-25A2, U+25AA, U+25B2-25B3, U+25B6-25B7, U+25BA-25BD, U+25C0-25C1, U+25C4-25C7, U+25CA-25CB, U+25CF, U+25E6, U+25EF, U+2600, U+2605-2606, U+263C, U+2661, U+2665, U+266A-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+2B1C, U+2B24, U+2E18, U+A92E, U+1F12F-1F149, U+1F16A-1F16B, U+1F850, U+1F852;
}
All CJK languages: U+00B7, U+2013, U+2014, U+2015, U+2018, U+2019, U+201C, U+201D, U+2026, U+2E3A
zh's request: U+007E (Both are fine in ja because unused there)
zh's request but decision postponed: U+002F (Both are fine in ja; include it into Inter for the time being)
*/
/* Inter4CJK reuses the files above, but leaves out characters that should
be rendered by CJK fonts in CJK documents - see scripts/subsetFonts.py */
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-cyrillic-ext.woff2') format('woff2');
unicode-range:
U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
src: url('../fonts/inter-italic-cyrillic-ext.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-cyrillic.woff2') format('woff2');
src: url('../fonts/inter-italic-cyrillic.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-greek-ext.woff2') format('woff2');
src: url('../fonts/inter-italic-greek-ext.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-greek.woff2') format('woff2');
unicode-range:
U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
src: url('../fonts/inter-italic-greek.woff2') format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-vietnamese.woff2') format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
U+1EA0-1EF9, U+20AB;
src: url('../fonts/inter-italic-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-latin-ext.woff2') format('woff2');
unicode-range:
U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
src: url('../fonts/inter-italic-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C3, U+02C5, U+02C8, U+02CC, U+02CE-02CF, U+02D1-02D7, U+02DE, U+02E0-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Exclude some ambiguous CJK characters. */
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-latin.woff2') format('woff2');
unicode-range:
U+0000-007D, U+007F-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02BB-02BC,
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-2012, U+2016-2017,
U+201A-201B, U+201E-2025, U+2027-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url('../fonts/inter-italic-latin.woff2') format('woff2');
unicode-range: U+0000-007D, U+007F-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-2012, U+2016-2017, U+201A-201B, U+201E-2025, U+2027-203A, U+203C-206F, U+20AC, U+2122, U+2191, U+2193, U+21A9, U+2212, U+2215, U+2318, U+FEFF, U+FFFD;
}
@font-face {
@ -246,74 +225,90 @@ zh's request but decision postponed: U+002F (Both are fine in ja; include it int
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-cyrillic-ext.woff2') format('woff2');
unicode-range:
U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
src: url('../fonts/inter-italic-symbols.woff2') format('woff2');
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: Inter4CJK;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-cyrillic.woff2') format('woff2');
src: url('../fonts/inter-roman-cyrillic-ext.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-cyrillic.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: Inter4CJK;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-greek-ext.woff2') format('woff2');
src: url('../fonts/inter-roman-greek-ext.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: Inter4CJK;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-greek.woff2') format('woff2');
unicode-range:
U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
src: url('../fonts/inter-roman-greek.woff2') format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
font-family: Inter4CJK;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-vietnamese.woff2') format('woff2');
unicode-range:
U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
U+1EA0-1EF9, U+20AB;
src: url('../fonts/inter-roman-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: Inter4CJK;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-latin-ext.woff2') format('woff2');
unicode-range:
U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
src: url('../fonts/inter-roman-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C3, U+02C5, U+02C8, U+02CC, U+02CE-02CF, U+02D1-02D7, U+02DE, U+02E0-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Exclude some ambiguous CJK characters. */
@font-face {
font-family: Inter4CJK;
font-style: italic;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-italic-latin.woff2') format('woff2');
unicode-range:
U+0000-007D, U+007F-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02BB-02BC,
U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-2012, U+2016-2017,
U+201A-201B, U+201E-2025, U+2027-206F, U+2074, U+20AC, U+2122, U+2191,
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url('../fonts/inter-roman-latin.woff2') format('woff2');
unicode-range: U+0000-007D, U+007F-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-2012, U+2016-2017, U+201A-201B, U+201E-2025, U+2027-203A, U+203C-206F, U+20AC, U+2122, U+2191, U+2193, U+21A9, U+2212, U+2215, U+2318, U+FEFF, U+FFFD;
}
/* Generate the subsetted fonts using: `pyftsubset <file>.woff2 --unicodes="<range>" --layout-features+=pnum,tnum --flavor=woff2 --output-file="inter-<style>-<subset>.woff2"` */
@font-face {
font-family: Inter4CJK;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('../fonts/inter-roman-symbols.woff2') format('woff2');
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;
}
:root {
--vp-font-family-base:
'Inter', -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
[lang]:where(:lang(zh), :lang(ja), :lang(ko)) {
--vp-font-family-base:
'Inter4CJK', -apple-system, BlinkMacSystemFont, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}

@ -262,25 +262,14 @@
:root {
--vp-font-family-base:
'Inter', -apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji',
-apple-system, BlinkMacSystemFont, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--vp-font-family-mono:
ui-monospace, 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono',
'Courier New', monospace;
font-optical-sizing: auto;
}
/* :root and [lang] have the same specificity.
[lang] covers the case of e.g. <div lang="zh-CN"> in a non-Chinese page.
*/
/* Korean compatibility is unverified, so application of custom Inter to Korean is pending. */
[lang]:where(:lang(zh, ja)) {
--vp-font-family-base:
'Inter4CJK', -apple-system, BlinkMacSystemFont, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
[lang]:where(:lang(zh, ja, ko)) {
[lang]:where(:lang(zh), :lang(ja), :lang(ko)) {
h1,
h2,
h3,

Loading…
Cancel
Save