diff --git a/.prettierignore b/.prettierignore index 32f2a7ad..d23ab1d0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,6 @@ template temp !CHANGELOG.md .temp + +# generated by scripts/subsetFonts.py +src/client/theme-default/styles/fonts.css diff --git a/scripts/fontSubsets.json b/scripts/fontSubsets.json new file mode 100644 index 00000000..1890fae9 --- /dev/null +++ b/scripts/fontSubsets.json @@ -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" +} diff --git a/scripts/subsetFonts.py b/scripts/subsetFonts.py new file mode 100755 index 00000000..f3be549e --- /dev/null +++ b/scripts/subsetFonts.py @@ -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() diff --git a/src/client/theme-default/fonts/inter-italic-latin.woff2 b/src/client/theme-default/fonts/inter-italic-latin.woff2 index fdc2aa22..94a1ae94 100644 Binary files a/src/client/theme-default/fonts/inter-italic-latin.woff2 and b/src/client/theme-default/fonts/inter-italic-latin.woff2 differ diff --git a/src/client/theme-default/fonts/inter-italic-symbols.woff2 b/src/client/theme-default/fonts/inter-italic-symbols.woff2 new file mode 100644 index 00000000..d8425105 Binary files /dev/null and b/src/client/theme-default/fonts/inter-italic-symbols.woff2 differ diff --git a/src/client/theme-default/fonts/inter-roman-latin.woff2 b/src/client/theme-default/fonts/inter-roman-latin.woff2 index 18a8987f..c428d326 100644 Binary files a/src/client/theme-default/fonts/inter-roman-latin.woff2 and b/src/client/theme-default/fonts/inter-roman-latin.woff2 differ diff --git a/src/client/theme-default/fonts/inter-roman-symbols.woff2 b/src/client/theme-default/fonts/inter-roman-symbols.woff2 new file mode 100644 index 00000000..ba86b3dc Binary files /dev/null and b/src/client/theme-default/fonts/inter-roman-symbols.woff2 differ diff --git a/src/client/theme-default/index.ts b/src/client/theme-default/index.ts index fe52cd65..ff26e67f 100644 --- a/src/client/theme-default/index.ts +++ b/src/client/theme-default/index.ts @@ -1,3 +1,4 @@ +import './without-fonts' import './styles/fonts.css' export * from './without-fonts' diff --git a/src/client/theme-default/styles/fonts.css b/src/client/theme-default/styles/fonts.css index 03b72458..c0ae2ae6 100644 --- a/src/client/theme-default/styles/fonts.css +++ b/src/client/theme-default/styles/fonts.css @@ -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 .woff2 --unicodes="" --layout-features+=pnum,tnum --flavor=woff2 --output-file="inter-