@nebutra/fonts 0.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/NOTICE-FONTS.md +19 -0
- package/README.md +150 -7
- package/dist/chunk-GRE7GS6W.js +27 -0
- package/dist/chunk-GRE7GS6W.js.map +1 -0
- package/dist/generated/index.d.ts +36 -0
- package/dist/generated/index.js +19 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/next-cjk.d.ts +53 -0
- package/dist/next-cjk.js +9 -0
- package/dist/next-cjk.js.map +1 -0
- package/dist/next.d.ts +13 -0
- package/dist/next.js +123 -0
- package/dist/next.js.map +1 -0
- package/generated/index.ts +25 -0
- package/generated/noto-sans-sc.css +43 -0
- package/generated/subset-manifest.json +10 -0
- package/package.json +45 -9
- package/src/index.test.ts +39 -0
- package/src/index.ts +32 -0
- package/src/next-cjk.test.ts +49 -0
- package/src/next-cjk.ts +70 -0
- package/src/next.ts +30 -0
- package/vendor/noto-sans-sc/OFL.txt +92 -0
package/CHANGELOG.md
ADDED
package/NOTICE-FONTS.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Font redistribution notice
|
|
2
|
+
|
|
3
|
+
This package's MIT licence covers **first-party code only**.
|
|
4
|
+
|
|
5
|
+
## Noto Sans SC
|
|
6
|
+
|
|
7
|
+
本软件使用了 **Noto Sans SC** 字体。
|
|
8
|
+
This software uses the **Noto Sans SC** typeface.
|
|
9
|
+
|
|
10
|
+
Noto Sans SC is licensed under the SIL Open Font License 1.1. The licence
|
|
11
|
+
text is `vendor/noto-sans-sc/OFL.txt`.
|
|
12
|
+
|
|
13
|
+
Generated `.woff2` subsets stay in this workspace for first-party apps so
|
|
14
|
+
`next/font/local` can load them offline. They are **not** included in the npm
|
|
15
|
+
`files` list and must not be published. Downstream npm consumers do not receive
|
|
16
|
+
font binaries from this package.
|
|
17
|
+
|
|
18
|
+
Do not add `generated/*.woff2` or other font binaries to a publishable `files`
|
|
19
|
+
glob.
|
package/README.md
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
# @nebutra/fonts
|
|
2
2
|
|
|
3
|
-
Status: WIP —
|
|
3
|
+
Status: **WIP** — not yet published to npm.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
The Simplified-Chinese face is wired into every Next app that loads Geist (web,
|
|
6
|
+
landing, design-docs, sailor-docs, admin, forge, router, sleptons, typelens,
|
|
7
|
+
mail-preview). The theme / DESIGN.md registry (the ~16 Google faces) is still used
|
|
8
|
+
only by `apps/web`.
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
Self-hosted fonts for Nebutra: the CJK body face, plus an OSS font registry for
|
|
11
|
+
themes and imported DESIGN.md font families.
|
|
12
|
+
|
|
13
|
+
The package has three entries:
|
|
9
14
|
|
|
10
15
|
- `@nebutra/fonts` is client-safe and maps a CSS font-family stack to the
|
|
11
16
|
registry CSS variable that should be prepended.
|
|
12
|
-
- `@nebutra/fonts/next` is server-only and declares
|
|
13
|
-
|
|
17
|
+
- `@nebutra/fonts/next/cjk` is server-only and declares the self-hosted
|
|
18
|
+
Simplified-Chinese face via `next/font/local` — no network at build time, which
|
|
19
|
+
also keeps it working in a network-sandboxed dev server. **This is the one every
|
|
20
|
+
app needs.**
|
|
21
|
+
- `@nebutra/fonts/next` is server-only and declares the build-time
|
|
22
|
+
`next/font/google` registry faces plus the combined registry class name. It
|
|
23
|
+
re-exports the CJK face, but importing it just for that would drag ~16 Google
|
|
24
|
+
font downloads into the app — use `./next/cjk`.
|
|
14
25
|
|
|
15
26
|
## Installation
|
|
16
27
|
|
|
@@ -43,6 +54,137 @@ const stack = withRegistryFont("Space Grotesk, sans-serif");
|
|
|
43
54
|
// "var(--font-space-grotesk), Space Grotesk, sans-serif"
|
|
44
55
|
```
|
|
45
56
|
|
|
57
|
+
## Simplified Chinese — self-hosted Noto Sans SC
|
|
58
|
+
|
|
59
|
+
Geist has no CJK coverage at all, so without a CJK face every Chinese character
|
|
60
|
+
falls back to whatever the OS supplies: PingFang on macOS, Microsoft YaHei on
|
|
61
|
+
Windows, something else again on Android. Chinese copy is a first-class surface
|
|
62
|
+
in this product (see `docs/microcopy/`), so the CJK face is self-hosted and
|
|
63
|
+
subset here.
|
|
64
|
+
|
|
65
|
+
### Wiring an app
|
|
66
|
+
|
|
67
|
+
Two lines in the root layout, beside the Geist loaders:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { cjkFontClassName } from "@nebutra/fonts/next/cjk";
|
|
71
|
+
import { GeistMono } from "geist/font/mono";
|
|
72
|
+
import { GeistSans } from "geist/font/sans";
|
|
73
|
+
|
|
74
|
+
<html className={`${GeistSans.variable} ${GeistMono.variable} ${cjkFontClassName}`}>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That defines `--font-noto-sans-sc`. Nothing else is needed: the token stacks in
|
|
78
|
+
`@nebutra/tokens` (`--font-sans`, `--font-cn`, `--font-display`, `--font-heading`)
|
|
79
|
+
already reference the variable in the right position. The app also needs
|
|
80
|
+
`"@nebutra/fonts"` in `dependencies` and in `transpilePackages` (the package ships
|
|
81
|
+
TypeScript source).
|
|
82
|
+
|
|
83
|
+
Do **not** re-declare `--font-sans` and friends downstream. `@nebutra/ui`'s
|
|
84
|
+
`typography/fonts.css` used to, and being the later import its Geist-only copies
|
|
85
|
+
won, so the CJK half of the token stack never reached `--font-sans` at all. That
|
|
86
|
+
duplication is gone; the file now only carries derived aliases.
|
|
87
|
+
|
|
88
|
+
### Stack order is the design decision
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
font-family: var(--font-geist-sans), "Geist", var(--font-noto-sans-sc), "Noto Sans SC", …;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Geist comes **first** and keeps Latin and the numerals — its tabular figures and
|
|
95
|
+
tighter x-height are what dense dashboard tables need, and it is the locked UI
|
|
96
|
+
face. Noto Sans SC takes CJK. Both faces cover Latin, so the *order* is what
|
|
97
|
+
decides: reversed, Noto would take the Latin too, and its Latin is not as
|
|
98
|
+
good as Geist's for UI.
|
|
99
|
+
|
|
100
|
+
Belt and braces: the generated `@font-face` rules carry a `unicode-range` with
|
|
101
|
+
no Latin, no ASCII and no general-punctuation codepoints in it, so a Latin-only
|
|
102
|
+
page can never trigger a CJK download even if a stack somewhere is written the
|
|
103
|
+
wrong way round. Geist Mono remains the code face.
|
|
104
|
+
|
|
105
|
+
### Building the subsets
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
FONTTOOLS_PYTHON=/path/to/python \
|
|
109
|
+
pnpm --filter @nebutra/fonts subset:cjk -- --force
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Requires Python with `fontTools` and `brotli` (for `--flavor=woff2`).
|
|
113
|
+
The script downloads the OFL Noto Sans SC variable face, instances 400 / 500 /
|
|
114
|
+
600 / 700, then subsets. Outputs land in `generated/`. The committed woff2
|
|
115
|
+
files are the SIL OFL Noto Sans SC chinese-simplified faces used by
|
|
116
|
+
`next/font/local` so a clean clone does not need fontTools to render text.
|
|
117
|
+
|
|
118
|
+
### Why three weights
|
|
119
|
+
|
|
120
|
+
The design system's numeric slots are `--font-weight-medium: 500` and
|
|
121
|
+
`--font-weight-heading: 600` (`packages/design/tokens/recipe.css`), and the token
|
|
122
|
+
CSS writes literal `font-weight` in only four values: 500 (43×), 600 (35×),
|
|
123
|
+
400 (27×), 700 (10×). So 400 / 500 / 600 ship. 700 resolves to the 600 face by
|
|
124
|
+
normal CSS font matching, and because that matched face is itself ≥ 600 no
|
|
125
|
+
browser applies synthetic bold — which is precisely why the third face is
|
|
126
|
+
DemiBold 600 rather than Bold 700. Choosing 700 instead would leave the *default*
|
|
127
|
+
heading weight, the most common heading value in the system, a step out of place.
|
|
128
|
+
Skin-declared fractional weights (300 / 450 / 510) resolve into the same set.
|
|
129
|
+
Each weight costs ~490 KB, so shipping all nine static faces would be ~4.4 MB.
|
|
130
|
+
|
|
131
|
+
### Why the static faces, not the variable one
|
|
132
|
+
|
|
133
|
+
A variable CJK font carries per-weight deltas for every glyph it keeps, so one
|
|
134
|
+
variable file costs more than the static weights a page actually uses. The
|
|
135
|
+
pipeline instances 400 / 500 / 600 / 700 and subsets each one.
|
|
136
|
+
|
|
137
|
+
### Character set
|
|
138
|
+
|
|
139
|
+
Three inputs, unioned — 4,282 characters in the current build:
|
|
140
|
+
|
|
141
|
+
1. **The zh catalogs, by glob** (1,586 chars) — every `zh*.json` under any
|
|
142
|
+
`messages/` or `locales/` directory, walked at build time, so new Chinese copy
|
|
143
|
+
is covered on the next run instead of drifting away from a hardcoded list.
|
|
144
|
+
2. **CJK punctuation and fullwidth forms, wholesale** (197 chars) — U+3000–303F,
|
|
145
|
+
U+FE30–FE4F, U+FF01–FF5E, U+FFE0–FFE6. Chinese text whose `、。!?()` are set
|
|
146
|
+
in a different face than its characters looks broken immediately: different
|
|
147
|
+
baseline, different advance width. 35 of these are absent from the source face
|
|
148
|
+
(`〄〰〸₩` and friends); the build reports them and they fall through the stack.
|
|
149
|
+
3. **A floor of common characters: GB2312 level-1** (3,755 chars, 一级汉字). The
|
|
150
|
+
catalogs only cover *our* copy — product surfaces render *user* data, and one
|
|
151
|
+
character of a name or a city falling back to PingFang mid-sentence is worse
|
|
152
|
+
than not using the font at all. The conventional floor is the 通用规范汉字表
|
|
153
|
+
一级字表 (3,500 常用字); GB2312 level-1 is a superset of essentially that set
|
|
154
|
+
and, the deciding factor, is derivable in-process from the platform's own
|
|
155
|
+
GB2312 decoder — no 3,500-entry list to vendor, review or let rot. It covers
|
|
156
|
+
>99.5% of running modern text. Level 2 (~3,000 further rare surname and
|
|
157
|
+
place-name glyphs) is excluded: it would roughly double every file for
|
|
158
|
+
characters that appear in a fraction of a percent of text, which is exactly
|
|
159
|
+
what OS fallback is for.
|
|
160
|
+
|
|
161
|
+
The floor is what costs the bytes: the catalog set alone subsets to 191,108 B per
|
|
162
|
+
weight, the full set to ~498,000 B.
|
|
163
|
+
|
|
164
|
+
> Possible follow-up, measured but **not** implemented here: splitting each
|
|
165
|
+
> weight into a hot tier (catalogs + punctuation, 1,777 chars, 197,028 B) and an
|
|
166
|
+
> extended tier (the GB2312 remainder, 2,509 chars, 312,644 B) with complementary
|
|
167
|
+
> `unicode-range`s would drop the common path from ~1.47 MB to ~591 KB across
|
|
168
|
+
> three weights, and only fetch the extended tier when user data actually renders
|
|
169
|
+
> a character outside our own copy. It costs two files per weight and (on the
|
|
170
|
+
> `next/font/local` path) a per-tier `declarations` entry to carry the range.
|
|
171
|
+
|
|
172
|
+
### Vendored sources
|
|
173
|
+
|
|
174
|
+
`vendor/noto-sans-sc/OFL.txt` is committed. Full source TTFs are downloaded by
|
|
175
|
+
`subset:cjk` and gitignored. Do not commit unmodified CJK source faces.
|
|
176
|
+
|
|
177
|
+
## Third-party font attribution
|
|
178
|
+
|
|
179
|
+
本软件使用了 **Noto Sans SC** 字体。
|
|
180
|
+
This software uses the **Noto Sans SC** typeface.
|
|
181
|
+
|
|
182
|
+
Noto Sans SC is licensed under the SIL Open Font License 1.1
|
|
183
|
+
(`vendor/noto-sans-sc/OFL.txt`). That licence is separate from this package's
|
|
184
|
+
MIT licence, which applies to first-party code only. Generated `.woff2` files
|
|
185
|
+
stay in the workspace for first-party apps and are excluded from the npm
|
|
186
|
+
tarball. See `NOTICE-FONTS.md`.
|
|
187
|
+
|
|
46
188
|
## Registered Families
|
|
47
189
|
|
|
48
190
|
The registry includes Geist, Inter, Space Grotesk, Playfair Display, JetBrains
|
|
@@ -57,4 +199,5 @@ uses the corresponding CSS variable.
|
|
|
57
199
|
|
|
58
200
|
## License
|
|
59
201
|
|
|
60
|
-
MIT
|
|
202
|
+
MIT for first-party code. Noto Sans SC binaries are SIL OFL 1.1 and are not
|
|
203
|
+
published to npm.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/next-cjk.ts
|
|
2
|
+
import localFont from "next/font/local";
|
|
3
|
+
var notoSansSc = localFont({
|
|
4
|
+
src: [
|
|
5
|
+
{ path: "../generated/noto-sans-sc-400.woff2", weight: "400", style: "normal" },
|
|
6
|
+
{ path: "../generated/noto-sans-sc-500.woff2", weight: "500", style: "normal" },
|
|
7
|
+
{ path: "../generated/noto-sans-sc-600.woff2", weight: "600", style: "normal" },
|
|
8
|
+
{ path: "../generated/noto-sans-sc-700.woff2", weight: "700", style: "normal" }
|
|
9
|
+
],
|
|
10
|
+
declarations: [
|
|
11
|
+
{
|
|
12
|
+
prop: "unicode-range",
|
|
13
|
+
value: "U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
display: "swap",
|
|
17
|
+
preload: false,
|
|
18
|
+
adjustFontFallback: false,
|
|
19
|
+
variable: "--font-noto-sans-sc"
|
|
20
|
+
});
|
|
21
|
+
var cjkFontClassName = notoSansSc.variable;
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
notoSansSc,
|
|
25
|
+
cjkFontClassName
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-GRE7GS6W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next-cjk.ts"],"sourcesContent":["/**\n * @nebutra/fonts/next/cjk — the self-hosted Simplified-Chinese face (server-only).\n *\n * WHY A SEPARATE ENTRY FROM `./next`: that module declares ~16 `next/font/google`\n * faces for the theme / DESIGN.md registry. Importing it just to get the CJK face\n * would drag those build-time Google downloads into every app — and this repo has\n * a known trap where `next/font/google` fails outright in a network-sandboxed dev\n * server. This file imports `next/font/local` ONLY: the woff2 files live in the\n * workspace `generated/` directory (not the npm tarball), so first-party apps\n * work offline, in CI, and in the sandbox. `./next` re-exports it,\n * so an app already applying `fontRegistryClassName` still only needs one import.\n *\n * WHY SELF-HOSTED AT ALL: Geist has no CJK coverage, so without this every Chinese\n * character falls back to whatever the OS supplies — PingFang on macOS, Microsoft\n * YaHei on Windows, something else on Android. Chinese copy is a first-class\n * surface here (see docs/microcopy/), so the face is pinned rather than left to\n * the OS.\n *\n * The files are built by `pnpm --filter @nebutra/fonts subset:cjk` from\n * Noto Sans SC (SIL OFL) and live in the workspace `generated/` directory.\n * The literal `src` list below mirrors NOTO_SANS_SC_SOURCES in\n * ../generated/index.ts (a drift test in ./next-cjk.test.ts asserts they agree).\n * It is spelled out rather than spread because next/font is a compile-time\n * transform — SWC statically analyses this call, so the options object cannot\n * be computed.\n */\n\nimport localFont from \"next/font/local\";\n\n/**\n * Noto Sans SC — 400 / 500 / 600 / 700 static subsets.\n *\n * - `preload: false` on purpose. Each weight is hundreds of KB; preloading them\n * on every route would tax Latin-only pages for nothing. The browser fetches\n * a weight only when a glyph in the `unicode-range` below actually renders.\n * - `declarations` carries that `unicode-range` (CJK blocks only — no ASCII, no\n * Latin, no general punctuation), so a Latin-only page can never trigger a CJK\n * download even if a font stack somewhere is written the wrong way round.\n * - `adjustFontFallback: false` — next/font's metric-matched fallback is derived\n * from Arial, which is meaningless for a Han face.\n */\nexport const notoSansSc = localFont({\n src: [\n { path: \"../generated/noto-sans-sc-400.woff2\", weight: \"400\", style: \"normal\" },\n { path: \"../generated/noto-sans-sc-500.woff2\", weight: \"500\", style: \"normal\" },\n { path: \"../generated/noto-sans-sc-600.woff2\", weight: \"600\", style: \"normal\" },\n { path: \"../generated/noto-sans-sc-700.woff2\", weight: \"700\", style: \"normal\" },\n ],\n declarations: [\n {\n prop: \"unicode-range\",\n value: \"U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF\",\n },\n ],\n display: \"swap\",\n preload: false,\n adjustFontFallback: false,\n variable: \"--font-noto-sans-sc\",\n});\n\n/**\n * Apply to <html> next to the Geist loaders so `--font-noto-sans-sc` exists:\n *\n * className={`${GeistSans.variable} ${GeistMono.variable} ${cjkFontClassName}`}\n *\n * The token stacks (`--font-sans` / `--font-cn` / `--font-display` in\n * @nebutra/tokens) reference the variable AFTER Geist, so Geist keeps Latin and\n * the numerals and only CJK falls through to this face.\n */\nexport const cjkFontClassName = notoSansSc.variable;\n"],"mappings":";AA2BA,OAAO,eAAe;AAcf,IAAM,aAAa,UAAU;AAAA,EAClC,KAAK;AAAA,IACH,EAAE,MAAM,uCAAuC,QAAQ,OAAO,OAAO,SAAS;AAAA,IAC9E,EAAE,MAAM,uCAAuC,QAAQ,OAAO,OAAO,SAAS;AAAA,IAC9E,EAAE,MAAM,uCAAuC,QAAQ,OAAO,OAAO,SAAS;AAAA,IAC9E,EAAE,MAAM,uCAAuC,QAAQ,OAAO,OAAO,SAAS;AAAA,EAChF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,SAAS;AAAA,EACT,SAAS;AAAA,EACT,oBAAoB;AAAA,EACpB,UAAU;AACZ,CAAC;AAWM,IAAM,mBAAmB,WAAW;","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — face metadata for `@nebutra/fonts/next/cjk`.
|
|
3
|
+
* Current binaries are SIL OFL Noto Sans SC (chinese-simplified) woff2 files.
|
|
4
|
+
* Rebuild with `pnpm --filter @nebutra/fonts subset:cjk` when FONTTOOLS_PYTHON
|
|
5
|
+
* is available, or replace the woff2 files from an OFL source.
|
|
6
|
+
*/
|
|
7
|
+
declare const NOTO_SANS_SC_VARIABLE: "--font-noto-sans-sc";
|
|
8
|
+
declare const NOTO_SANS_SC_FAMILY: "Noto Sans SC";
|
|
9
|
+
/** Characters covered per face (catalogs ∪ CJK punctuation ∪ GB2312 level-1). */
|
|
10
|
+
declare const NOTO_SANS_SC_CHAR_COUNT: 4282;
|
|
11
|
+
/** `unicode-range` of every generated @font-face — CJK only, no Latin. */
|
|
12
|
+
declare const NOTO_SANS_SC_UNICODE_RANGE: "U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF";
|
|
13
|
+
/** Sources for `next/font/local({ src: [...] })`, paths relative to this file. */
|
|
14
|
+
declare const NOTO_SANS_SC_SOURCES: readonly [{
|
|
15
|
+
readonly path: "./noto-sans-sc-400.woff2";
|
|
16
|
+
readonly weight: "400";
|
|
17
|
+
readonly style: "normal";
|
|
18
|
+
readonly bytes: 1142552;
|
|
19
|
+
}, {
|
|
20
|
+
readonly path: "./noto-sans-sc-500.woff2";
|
|
21
|
+
readonly weight: "500";
|
|
22
|
+
readonly style: "normal";
|
|
23
|
+
readonly bytes: 1159128;
|
|
24
|
+
}, {
|
|
25
|
+
readonly path: "./noto-sans-sc-600.woff2";
|
|
26
|
+
readonly weight: "600";
|
|
27
|
+
readonly style: "normal";
|
|
28
|
+
readonly bytes: 1162352;
|
|
29
|
+
}, {
|
|
30
|
+
readonly path: "./noto-sans-sc-700.woff2";
|
|
31
|
+
readonly weight: "700";
|
|
32
|
+
readonly style: "normal";
|
|
33
|
+
readonly bytes: 1172244;
|
|
34
|
+
}];
|
|
35
|
+
|
|
36
|
+
export { NOTO_SANS_SC_CHAR_COUNT, NOTO_SANS_SC_FAMILY, NOTO_SANS_SC_SOURCES, NOTO_SANS_SC_UNICODE_RANGE, NOTO_SANS_SC_VARIABLE };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// generated/index.ts
|
|
2
|
+
var NOTO_SANS_SC_VARIABLE = "--font-noto-sans-sc";
|
|
3
|
+
var NOTO_SANS_SC_FAMILY = "Noto Sans SC";
|
|
4
|
+
var NOTO_SANS_SC_CHAR_COUNT = 4282;
|
|
5
|
+
var NOTO_SANS_SC_UNICODE_RANGE = "U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF";
|
|
6
|
+
var NOTO_SANS_SC_SOURCES = [
|
|
7
|
+
{ path: "./noto-sans-sc-400.woff2", weight: "400", style: "normal", bytes: 1142552 },
|
|
8
|
+
{ path: "./noto-sans-sc-500.woff2", weight: "500", style: "normal", bytes: 1159128 },
|
|
9
|
+
{ path: "./noto-sans-sc-600.woff2", weight: "600", style: "normal", bytes: 1162352 },
|
|
10
|
+
{ path: "./noto-sans-sc-700.woff2", weight: "700", style: "normal", bytes: 1172244 }
|
|
11
|
+
];
|
|
12
|
+
export {
|
|
13
|
+
NOTO_SANS_SC_CHAR_COUNT,
|
|
14
|
+
NOTO_SANS_SC_FAMILY,
|
|
15
|
+
NOTO_SANS_SC_SOURCES,
|
|
16
|
+
NOTO_SANS_SC_UNICODE_RANGE,
|
|
17
|
+
NOTO_SANS_SC_VARIABLE
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../generated/index.ts"],"sourcesContent":["/**\n * GENERATED FILE — face metadata for `@nebutra/fonts/next/cjk`.\n * Current binaries are SIL OFL Noto Sans SC (chinese-simplified) woff2 files.\n * Rebuild with `pnpm --filter @nebutra/fonts subset:cjk` when FONTTOOLS_PYTHON\n * is available, or replace the woff2 files from an OFL source.\n */\n\nexport const NOTO_SANS_SC_VARIABLE = \"--font-noto-sans-sc\" as const;\n\nexport const NOTO_SANS_SC_FAMILY = \"Noto Sans SC\" as const;\n\n/** Characters covered per face (catalogs ∪ CJK punctuation ∪ GB2312 level-1). */\nexport const NOTO_SANS_SC_CHAR_COUNT = 4282 as const;\n\n/** `unicode-range` of every generated @font-face — CJK only, no Latin. */\nexport const NOTO_SANS_SC_UNICODE_RANGE =\n \"U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF\" as const;\n\n/** Sources for `next/font/local({ src: [...] })`, paths relative to this file. */\nexport const NOTO_SANS_SC_SOURCES = [\n { path: \"./noto-sans-sc-400.woff2\", weight: \"400\", style: \"normal\", bytes: 1142552 },\n { path: \"./noto-sans-sc-500.woff2\", weight: \"500\", style: \"normal\", bytes: 1159128 },\n { path: \"./noto-sans-sc-600.woff2\", weight: \"600\", style: \"normal\", bytes: 1162352 },\n { path: \"./noto-sans-sc-700.woff2\", weight: \"700\", style: \"normal\", bytes: 1172244 },\n] as const;\n"],"mappings":";AAOO,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;AAG5B,IAAM,0BAA0B;AAGhC,IAAM,6BACX;AAGK,IAAM,uBAAuB;AAAA,EAClC,EAAE,MAAM,4BAA4B,QAAQ,OAAO,OAAO,UAAU,OAAO,QAAQ;AAAA,EACnF,EAAE,MAAM,4BAA4B,QAAQ,OAAO,OAAO,UAAU,OAAO,QAAQ;AAAA,EACnF,EAAE,MAAM,4BAA4B,QAAQ,OAAO,OAAO,UAAU,OAAO,QAAQ;AAAA,EACnF,EAAE,MAAM,4BAA4B,QAAQ,OAAO,OAAO,UAAU,OAAO,QAAQ;AACrF;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nebutra/fonts — self-hosted OSS font registry (client-safe entry).
|
|
3
|
+
*
|
|
4
|
+
* Maps a normalized theme / DESIGN.md font-family name to the CSS variable that
|
|
5
|
+
* the build-time self-hosted face defines (declared with next/font in
|
|
6
|
+
* `@nebutra/fonts/next`, applied to <html> via `fontRegistryClassName`).
|
|
7
|
+
*
|
|
8
|
+
* WHY: next/font registers each face under a HASHED family name reachable ONLY
|
|
9
|
+
* via its CSS variable — `font-family: 'Inter'` does NOT use the self-hosted
|
|
10
|
+
* Inter. So when a theme / imported DESIGN.md font's primary family matches an
|
|
11
|
+
* entry here, callers prepend `var(--font-…)` to the stack, making the
|
|
12
|
+
* self-hosted font actually render — with ZERO runtime external requests
|
|
13
|
+
* (next/font self-hosts at build time) and next/font's automatic metric-matched
|
|
14
|
+
* fallback (no layout shift). Unmatched families keep their declared stack.
|
|
15
|
+
*
|
|
16
|
+
* This entry is FREE of `next/font` imports so client modules can use it.
|
|
17
|
+
* The `./next` subpath holds the (server-only) next/font declarations and MUST
|
|
18
|
+
* keep its CSS-variable names in sync with FONT_REGISTRY below.
|
|
19
|
+
*/
|
|
20
|
+
declare const FONT_REGISTRY: Record<string, string>;
|
|
21
|
+
/** The first (primary) family in a CSS font-family list, normalized. */
|
|
22
|
+
declare function primaryFamily(stack: string): string;
|
|
23
|
+
/** Registry CSS variable for a stack's primary family, or undefined. */
|
|
24
|
+
declare function resolveRegistryVar(stack: string): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Return `stack` with the self-hosted registry font prepended when its primary
|
|
27
|
+
* family is registered; otherwise return it unchanged.
|
|
28
|
+
* e.g. "Space Grotesk, sans-serif" → "var(--font-space-grotesk), Space Grotesk, sans-serif"
|
|
29
|
+
*/
|
|
30
|
+
declare function withRegistryFont(stack: string | undefined): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Like `withRegistryFont`, but matches the first registered family ANYWHERE in
|
|
33
|
+
* the stack rather than only in first position.
|
|
34
|
+
*
|
|
35
|
+
* A brand package names the typeface the design language actually uses, and
|
|
36
|
+
* those are frequently licensed faces we have no right to serve — Söhne, Mori,
|
|
37
|
+
* Lyon Text, Reckless. The declared stack already says what to do when they are
|
|
38
|
+
* absent: fall to the next family. But "the next family" is usually a bare name
|
|
39
|
+
* like `Inter`, which does NOT reach next/font's hashed face, so the stack
|
|
40
|
+
* skidded past every self-hosted option and landed on `ui-sans-serif`. All
|
|
41
|
+
* seven built-in design languages rendered in the system font until 2026-08-18.
|
|
42
|
+
*
|
|
43
|
+
* Prepending the nearest registered family produces exactly the outcome the
|
|
44
|
+
* declared chain intended, and leaves the licensed names in place so a customer
|
|
45
|
+
* who does own the font still gets it by shipping the face themselves.
|
|
46
|
+
*/
|
|
47
|
+
declare function withNearestRegistryFont(stack: string | undefined): string | undefined;
|
|
48
|
+
|
|
49
|
+
export { FONT_REGISTRY, primaryFamily, resolveRegistryVar, withNearestRegistryFont, withRegistryFont };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var FONT_REGISTRY = {
|
|
3
|
+
// Self-hosted via geist/font (default brand faces, loaded by the app shell)
|
|
4
|
+
geist: "--font-geist-sans",
|
|
5
|
+
"geist sans": "--font-geist-sans",
|
|
6
|
+
"geist mono": "--font-geist-mono",
|
|
7
|
+
// Self-hosted via next/font/local from the subset built in ./generated (see
|
|
8
|
+
// ./next-cjk). The Simplified-Chinese face — CJK only via unicode-range, so it
|
|
9
|
+
// cannot take Latin away from Geist.
|
|
10
|
+
"noto sans sc": "--font-noto-sans-sc",
|
|
11
|
+
// Self-hosted via next/font/google (see ./next)
|
|
12
|
+
inter: "--font-inter",
|
|
13
|
+
"inter tight": "--font-reg-inter-tight",
|
|
14
|
+
"space grotesk": "--font-space-grotesk",
|
|
15
|
+
"playfair display": "--font-playfair-display",
|
|
16
|
+
fraunces: "--font-reg-fraunces",
|
|
17
|
+
"source serif 4": "--font-reg-source-serif-4",
|
|
18
|
+
"jetbrains mono": "--font-jetbrains-mono",
|
|
19
|
+
manrope: "--font-reg-manrope",
|
|
20
|
+
sora: "--font-reg-sora",
|
|
21
|
+
"work sans": "--font-reg-work-sans",
|
|
22
|
+
"dm sans": "--font-reg-dm-sans",
|
|
23
|
+
"plus jakarta sans": "--font-reg-plus-jakarta-sans",
|
|
24
|
+
outfit: "--font-reg-outfit",
|
|
25
|
+
figtree: "--font-reg-figtree",
|
|
26
|
+
montserrat: "--font-reg-montserrat",
|
|
27
|
+
lexend: "--font-reg-lexend",
|
|
28
|
+
"fira code": "--font-reg-fira-code",
|
|
29
|
+
"roboto mono": "--font-reg-roboto-mono",
|
|
30
|
+
"source code pro": "--font-reg-source-code-pro"
|
|
31
|
+
};
|
|
32
|
+
function normalizeFamily(name) {
|
|
33
|
+
return name.replace(/['"]/g, "").trim().toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
function primaryFamily(stack) {
|
|
36
|
+
return normalizeFamily(stack.split(",")[0] ?? "");
|
|
37
|
+
}
|
|
38
|
+
function resolveRegistryVar(stack) {
|
|
39
|
+
return FONT_REGISTRY[primaryFamily(stack)];
|
|
40
|
+
}
|
|
41
|
+
function withRegistryFont(stack) {
|
|
42
|
+
if (!stack) return stack;
|
|
43
|
+
const variable = resolveRegistryVar(stack);
|
|
44
|
+
return variable ? `var(${variable}), ${stack}` : stack;
|
|
45
|
+
}
|
|
46
|
+
function withNearestRegistryFont(stack) {
|
|
47
|
+
if (!stack) return stack;
|
|
48
|
+
for (const token of stack.split(",")) {
|
|
49
|
+
const variable = FONT_REGISTRY[normalizeFamily(token)];
|
|
50
|
+
if (variable) return `var(${variable}), ${stack}`;
|
|
51
|
+
}
|
|
52
|
+
return stack;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
FONT_REGISTRY,
|
|
56
|
+
primaryFamily,
|
|
57
|
+
resolveRegistryVar,
|
|
58
|
+
withNearestRegistryFont,
|
|
59
|
+
withRegistryFont
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @nebutra/fonts — self-hosted OSS font registry (client-safe entry).\n *\n * Maps a normalized theme / DESIGN.md font-family name to the CSS variable that\n * the build-time self-hosted face defines (declared with next/font in\n * `@nebutra/fonts/next`, applied to <html> via `fontRegistryClassName`).\n *\n * WHY: next/font registers each face under a HASHED family name reachable ONLY\n * via its CSS variable — `font-family: 'Inter'` does NOT use the self-hosted\n * Inter. So when a theme / imported DESIGN.md font's primary family matches an\n * entry here, callers prepend `var(--font-…)` to the stack, making the\n * self-hosted font actually render — with ZERO runtime external requests\n * (next/font self-hosts at build time) and next/font's automatic metric-matched\n * fallback (no layout shift). Unmatched families keep their declared stack.\n *\n * This entry is FREE of `next/font` imports so client modules can use it.\n * The `./next` subpath holds the (server-only) next/font declarations and MUST\n * keep its CSS-variable names in sync with FONT_REGISTRY below.\n */\n\nexport const FONT_REGISTRY: Record<string, string> = {\n // Self-hosted via geist/font (default brand faces, loaded by the app shell)\n geist: \"--font-geist-sans\",\n \"geist sans\": \"--font-geist-sans\",\n \"geist mono\": \"--font-geist-mono\",\n // Self-hosted via next/font/local from the subset built in ./generated (see\n // ./next-cjk). The Simplified-Chinese face — CJK only via unicode-range, so it\n // cannot take Latin away from Geist.\n \"noto sans sc\": \"--font-noto-sans-sc\",\n // Self-hosted via next/font/google (see ./next)\n inter: \"--font-inter\",\n \"inter tight\": \"--font-reg-inter-tight\",\n \"space grotesk\": \"--font-space-grotesk\",\n \"playfair display\": \"--font-playfair-display\",\n fraunces: \"--font-reg-fraunces\",\n \"source serif 4\": \"--font-reg-source-serif-4\",\n \"jetbrains mono\": \"--font-jetbrains-mono\",\n manrope: \"--font-reg-manrope\",\n sora: \"--font-reg-sora\",\n \"work sans\": \"--font-reg-work-sans\",\n \"dm sans\": \"--font-reg-dm-sans\",\n \"plus jakarta sans\": \"--font-reg-plus-jakarta-sans\",\n outfit: \"--font-reg-outfit\",\n figtree: \"--font-reg-figtree\",\n montserrat: \"--font-reg-montserrat\",\n lexend: \"--font-reg-lexend\",\n \"fira code\": \"--font-reg-fira-code\",\n \"roboto mono\": \"--font-reg-roboto-mono\",\n \"source code pro\": \"--font-reg-source-code-pro\",\n};\n\n/** Normalize a single font-family token: strip quotes/whitespace, lowercase. */\nfunction normalizeFamily(name: string): string {\n // Strip ALL quotes with a quantifier-free global replace (quotes only appear\n // at token boundaries in a font-family value). Avoids the end-anchored /['\"]+$/\n // form, which CodeQL flags as polynomial ReDoS (scanned from every position).\n return name.replace(/['\"]/g, \"\").trim().toLowerCase();\n}\n\n/** The first (primary) family in a CSS font-family list, normalized. */\nexport function primaryFamily(stack: string): string {\n return normalizeFamily(stack.split(\",\")[0] ?? \"\");\n}\n\n/** Registry CSS variable for a stack's primary family, or undefined. */\nexport function resolveRegistryVar(stack: string): string | undefined {\n return FONT_REGISTRY[primaryFamily(stack)];\n}\n\n/**\n * Return `stack` with the self-hosted registry font prepended when its primary\n * family is registered; otherwise return it unchanged.\n * e.g. \"Space Grotesk, sans-serif\" → \"var(--font-space-grotesk), Space Grotesk, sans-serif\"\n */\nexport function withRegistryFont(stack: string | undefined): string | undefined {\n if (!stack) return stack;\n const variable = resolveRegistryVar(stack);\n return variable ? `var(${variable}), ${stack}` : stack;\n}\n\n/**\n * Like `withRegistryFont`, but matches the first registered family ANYWHERE in\n * the stack rather than only in first position.\n *\n * A brand package names the typeface the design language actually uses, and\n * those are frequently licensed faces we have no right to serve — Söhne, Mori,\n * Lyon Text, Reckless. The declared stack already says what to do when they are\n * absent: fall to the next family. But \"the next family\" is usually a bare name\n * like `Inter`, which does NOT reach next/font's hashed face, so the stack\n * skidded past every self-hosted option and landed on `ui-sans-serif`. All\n * seven built-in design languages rendered in the system font until 2026-08-18.\n *\n * Prepending the nearest registered family produces exactly the outcome the\n * declared chain intended, and leaves the licensed names in place so a customer\n * who does own the font still gets it by shipping the face themselves.\n */\nexport function withNearestRegistryFont(stack: string | undefined): string | undefined {\n if (!stack) return stack;\n for (const token of stack.split(\",\")) {\n const variable = FONT_REGISTRY[normalizeFamily(token)];\n if (variable) return `var(${variable}), ${stack}`;\n }\n return stack;\n}\n"],"mappings":";AAoBO,IAAM,gBAAwC;AAAA;AAAA,EAEnD,OAAO;AAAA,EACP,cAAc;AAAA,EACd,cAAc;AAAA;AAAA;AAAA;AAAA,EAId,gBAAgB;AAAA;AAAA,EAEhB,OAAO;AAAA,EACP,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,SAAS;AAAA,EACT,MAAM;AAAA,EACN,aAAa;AAAA,EACb,WAAW;AAAA,EACX,qBAAqB;AAAA,EACrB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,mBAAmB;AACrB;AAGA,SAAS,gBAAgB,MAAsB;AAI7C,SAAO,KAAK,QAAQ,SAAS,EAAE,EAAE,KAAK,EAAE,YAAY;AACtD;AAGO,SAAS,cAAc,OAAuB;AACnD,SAAO,gBAAgB,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE;AAClD;AAGO,SAAS,mBAAmB,OAAmC;AACpE,SAAO,cAAc,cAAc,KAAK,CAAC;AAC3C;AAOO,SAAS,iBAAiB,OAA+C;AAC9E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,WAAW,mBAAmB,KAAK;AACzC,SAAO,WAAW,OAAO,QAAQ,MAAM,KAAK,KAAK;AACnD;AAkBO,SAAS,wBAAwB,OAA+C;AACrF,MAAI,CAAC,MAAO,QAAO;AACnB,aAAW,SAAS,MAAM,MAAM,GAAG,GAAG;AACpC,UAAM,WAAW,cAAc,gBAAgB,KAAK,CAAC;AACrD,QAAI,SAAU,QAAO,OAAO,QAAQ,MAAM,KAAK;AAAA,EACjD;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as next_dist_compiled__next_font from 'next/dist/compiled/@next/font';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @nebutra/fonts/next/cjk — the self-hosted Simplified-Chinese face (server-only).
|
|
5
|
+
*
|
|
6
|
+
* WHY A SEPARATE ENTRY FROM `./next`: that module declares ~16 `next/font/google`
|
|
7
|
+
* faces for the theme / DESIGN.md registry. Importing it just to get the CJK face
|
|
8
|
+
* would drag those build-time Google downloads into every app — and this repo has
|
|
9
|
+
* a known trap where `next/font/google` fails outright in a network-sandboxed dev
|
|
10
|
+
* server. This file imports `next/font/local` ONLY: the woff2 files live in the
|
|
11
|
+
* workspace `generated/` directory (not the npm tarball), so first-party apps
|
|
12
|
+
* work offline, in CI, and in the sandbox. `./next` re-exports it,
|
|
13
|
+
* so an app already applying `fontRegistryClassName` still only needs one import.
|
|
14
|
+
*
|
|
15
|
+
* WHY SELF-HOSTED AT ALL: Geist has no CJK coverage, so without this every Chinese
|
|
16
|
+
* character falls back to whatever the OS supplies — PingFang on macOS, Microsoft
|
|
17
|
+
* YaHei on Windows, something else on Android. Chinese copy is a first-class
|
|
18
|
+
* surface here (see docs/microcopy/), so the face is pinned rather than left to
|
|
19
|
+
* the OS.
|
|
20
|
+
*
|
|
21
|
+
* The files are built by `pnpm --filter @nebutra/fonts subset:cjk` from
|
|
22
|
+
* Noto Sans SC (SIL OFL) and live in the workspace `generated/` directory.
|
|
23
|
+
* The literal `src` list below mirrors NOTO_SANS_SC_SOURCES in
|
|
24
|
+
* ../generated/index.ts (a drift test in ./next-cjk.test.ts asserts they agree).
|
|
25
|
+
* It is spelled out rather than spread because next/font is a compile-time
|
|
26
|
+
* transform — SWC statically analyses this call, so the options object cannot
|
|
27
|
+
* be computed.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Noto Sans SC — 400 / 500 / 600 / 700 static subsets.
|
|
31
|
+
*
|
|
32
|
+
* - `preload: false` on purpose. Each weight is hundreds of KB; preloading them
|
|
33
|
+
* on every route would tax Latin-only pages for nothing. The browser fetches
|
|
34
|
+
* a weight only when a glyph in the `unicode-range` below actually renders.
|
|
35
|
+
* - `declarations` carries that `unicode-range` (CJK blocks only — no ASCII, no
|
|
36
|
+
* Latin, no general punctuation), so a Latin-only page can never trigger a CJK
|
|
37
|
+
* download even if a font stack somewhere is written the wrong way round.
|
|
38
|
+
* - `adjustFontFallback: false` — next/font's metric-matched fallback is derived
|
|
39
|
+
* from Arial, which is meaningless for a Han face.
|
|
40
|
+
*/
|
|
41
|
+
declare const notoSansSc: next_dist_compiled__next_font.NextFontWithVariable;
|
|
42
|
+
/**
|
|
43
|
+
* Apply to <html> next to the Geist loaders so `--font-noto-sans-sc` exists:
|
|
44
|
+
*
|
|
45
|
+
* className={`${GeistSans.variable} ${GeistMono.variable} ${cjkFontClassName}`}
|
|
46
|
+
*
|
|
47
|
+
* The token stacks (`--font-sans` / `--font-cn` / `--font-display` in
|
|
48
|
+
* @nebutra/tokens) reference the variable AFTER Geist, so Geist keeps Latin and
|
|
49
|
+
* the numerals and only CJK falls through to this face.
|
|
50
|
+
*/
|
|
51
|
+
declare const cjkFontClassName: string;
|
|
52
|
+
|
|
53
|
+
export { cjkFontClassName, notoSansSc };
|
package/dist/next-cjk.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as next_dist_compiled__next_font from 'next/dist/compiled/@next/font';
|
|
2
|
+
export { cjkFontClassName, notoSansSc } from './next-cjk.js';
|
|
3
|
+
|
|
4
|
+
/** All registry faces, in declaration order. */
|
|
5
|
+
declare const FONT_REGISTRY_FACES: readonly [next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable, next_dist_compiled__next_font.NextFontWithVariable];
|
|
6
|
+
/**
|
|
7
|
+
* Space-joined `.variable` classNames for every registry face. Apply to <html>
|
|
8
|
+
* so all `--font-*` registry variables are defined (font files lazy-load on
|
|
9
|
+
* first use). Combine with the app's own Geist faces.
|
|
10
|
+
*/
|
|
11
|
+
declare const fontRegistryClassName: string;
|
|
12
|
+
|
|
13
|
+
export { FONT_REGISTRY_FACES, fontRegistryClassName };
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cjkFontClassName,
|
|
3
|
+
notoSansSc
|
|
4
|
+
} from "./chunk-GRE7GS6W.js";
|
|
5
|
+
|
|
6
|
+
// src/next.ts
|
|
7
|
+
import {
|
|
8
|
+
DM_Sans,
|
|
9
|
+
Figtree,
|
|
10
|
+
Fira_Code,
|
|
11
|
+
Fraunces,
|
|
12
|
+
Inter,
|
|
13
|
+
Inter_Tight,
|
|
14
|
+
JetBrains_Mono,
|
|
15
|
+
Lexend,
|
|
16
|
+
Manrope,
|
|
17
|
+
Montserrat,
|
|
18
|
+
Outfit,
|
|
19
|
+
Playfair_Display,
|
|
20
|
+
Plus_Jakarta_Sans,
|
|
21
|
+
Roboto_Mono,
|
|
22
|
+
Sora,
|
|
23
|
+
Source_Code_Pro,
|
|
24
|
+
Source_Serif_4,
|
|
25
|
+
Space_Grotesk,
|
|
26
|
+
Work_Sans
|
|
27
|
+
} from "next/font/google";
|
|
28
|
+
var inter = Inter({ subsets: ["latin"], display: "swap", variable: "--font-inter" });
|
|
29
|
+
var interTight = Inter_Tight({
|
|
30
|
+
subsets: ["latin"],
|
|
31
|
+
display: "swap",
|
|
32
|
+
variable: "--font-reg-inter-tight"
|
|
33
|
+
});
|
|
34
|
+
var spaceGrotesk = Space_Grotesk({
|
|
35
|
+
subsets: ["latin"],
|
|
36
|
+
display: "swap",
|
|
37
|
+
variable: "--font-space-grotesk"
|
|
38
|
+
});
|
|
39
|
+
var playfairDisplay = Playfair_Display({
|
|
40
|
+
subsets: ["latin"],
|
|
41
|
+
display: "swap",
|
|
42
|
+
variable: "--font-playfair-display"
|
|
43
|
+
});
|
|
44
|
+
var fraunces = Fraunces({
|
|
45
|
+
subsets: ["latin"],
|
|
46
|
+
display: "swap",
|
|
47
|
+
variable: "--font-reg-fraunces"
|
|
48
|
+
});
|
|
49
|
+
var jetbrainsMono = JetBrains_Mono({
|
|
50
|
+
subsets: ["latin"],
|
|
51
|
+
display: "swap",
|
|
52
|
+
variable: "--font-jetbrains-mono"
|
|
53
|
+
});
|
|
54
|
+
var manrope = Manrope({ subsets: ["latin"], display: "swap", variable: "--font-reg-manrope" });
|
|
55
|
+
var sora = Sora({ subsets: ["latin"], display: "swap", variable: "--font-reg-sora" });
|
|
56
|
+
var workSans = Work_Sans({
|
|
57
|
+
subsets: ["latin"],
|
|
58
|
+
display: "swap",
|
|
59
|
+
variable: "--font-reg-work-sans"
|
|
60
|
+
});
|
|
61
|
+
var dmSans = DM_Sans({ subsets: ["latin"], display: "swap", variable: "--font-reg-dm-sans" });
|
|
62
|
+
var plusJakartaSans = Plus_Jakarta_Sans({
|
|
63
|
+
subsets: ["latin"],
|
|
64
|
+
display: "swap",
|
|
65
|
+
variable: "--font-reg-plus-jakarta-sans"
|
|
66
|
+
});
|
|
67
|
+
var outfit = Outfit({ subsets: ["latin"], display: "swap", variable: "--font-reg-outfit" });
|
|
68
|
+
var figtree = Figtree({ subsets: ["latin"], display: "swap", variable: "--font-reg-figtree" });
|
|
69
|
+
var montserrat = Montserrat({
|
|
70
|
+
subsets: ["latin"],
|
|
71
|
+
display: "swap",
|
|
72
|
+
variable: "--font-reg-montserrat"
|
|
73
|
+
});
|
|
74
|
+
var lexend = Lexend({ subsets: ["latin"], display: "swap", variable: "--font-reg-lexend" });
|
|
75
|
+
var firaCode = Fira_Code({
|
|
76
|
+
subsets: ["latin"],
|
|
77
|
+
display: "swap",
|
|
78
|
+
variable: "--font-reg-fira-code"
|
|
79
|
+
});
|
|
80
|
+
var robotoMono = Roboto_Mono({
|
|
81
|
+
subsets: ["latin"],
|
|
82
|
+
display: "swap",
|
|
83
|
+
variable: "--font-reg-roboto-mono"
|
|
84
|
+
});
|
|
85
|
+
var sourceSerif4 = Source_Serif_4({
|
|
86
|
+
subsets: ["latin"],
|
|
87
|
+
display: "swap",
|
|
88
|
+
variable: "--font-reg-source-serif-4"
|
|
89
|
+
});
|
|
90
|
+
var sourceCodePro = Source_Code_Pro({
|
|
91
|
+
subsets: ["latin"],
|
|
92
|
+
display: "swap",
|
|
93
|
+
variable: "--font-reg-source-code-pro"
|
|
94
|
+
});
|
|
95
|
+
var FONT_REGISTRY_FACES = [
|
|
96
|
+
inter,
|
|
97
|
+
interTight,
|
|
98
|
+
spaceGrotesk,
|
|
99
|
+
playfairDisplay,
|
|
100
|
+
sourceSerif4,
|
|
101
|
+
fraunces,
|
|
102
|
+
jetbrainsMono,
|
|
103
|
+
manrope,
|
|
104
|
+
sora,
|
|
105
|
+
workSans,
|
|
106
|
+
dmSans,
|
|
107
|
+
plusJakartaSans,
|
|
108
|
+
outfit,
|
|
109
|
+
figtree,
|
|
110
|
+
montserrat,
|
|
111
|
+
lexend,
|
|
112
|
+
firaCode,
|
|
113
|
+
robotoMono,
|
|
114
|
+
sourceCodePro
|
|
115
|
+
];
|
|
116
|
+
var fontRegistryClassName = FONT_REGISTRY_FACES.map((face) => face.variable).join(" ");
|
|
117
|
+
export {
|
|
118
|
+
FONT_REGISTRY_FACES,
|
|
119
|
+
cjkFontClassName,
|
|
120
|
+
fontRegistryClassName,
|
|
121
|
+
notoSansSc
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.ts"],"sourcesContent":["/**\n * @nebutra/fonts/next — build-time self-hosted OSS font faces (server-only).\n *\n * Each face is loaded via next/font/google, which downloads the font AT BUILD\n * TIME and self-hosts it from the app's own origin — there are ZERO runtime\n * requests to Google (no IP leak / GDPR concern, no third-party runtime\n * dependency). Each face exposes a CSS variable; the browser only fetches a\n * given font file when an element actually uses that variable, so declaring the\n * whole registry is cheap.\n *\n * Apply `fontRegistryClassName` to <html> so the `--font-*` variables exist;\n * the appearance layer then prepends the matching `var(--font-*)` (see the\n * client-safe map in `@nebutra/fonts`) when a theme / DESIGN.md font matches.\n *\n * All declarations use a literal options object — next/font statically analyses\n * the call, so the config must NOT be computed. Variable fonts omit `weight`.\n * Keep the `variable` names in sync with FONT_REGISTRY in `../index.ts`.\n *\n * The self-hosted Simplified-Chinese face lives in `./next-cjk` (next/font/local,\n * no Google fetch) and is re-exported here for discoverability. It is NOT folded\n * into `fontRegistryClassName`: the CJK face is core typography, not an optional\n * theme face, and every app wires it the same way — `${cjkFontClassName}` on\n * <html> beside the Geist loaders. Apps that only need the CJK face should import\n * `@nebutra/fonts/next/cjk` directly so they don't pull in the Google faces below.\n */\n\nimport {\n DM_Sans,\n Figtree,\n Fira_Code,\n Fraunces,\n Inter,\n Inter_Tight,\n JetBrains_Mono,\n Lexend,\n Manrope,\n Montserrat,\n Outfit,\n Playfair_Display,\n Plus_Jakarta_Sans,\n Roboto_Mono,\n Sora,\n Source_Code_Pro,\n Source_Serif_4,\n Space_Grotesk,\n Work_Sans,\n} from \"next/font/google\";\n\nexport { cjkFontClassName, notoSansSc } from \"./next-cjk\";\n\nconst inter = Inter({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-inter\" });\nconst interTight = Inter_Tight({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-inter-tight\",\n});\nconst spaceGrotesk = Space_Grotesk({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-space-grotesk\",\n});\nconst playfairDisplay = Playfair_Display({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-playfair-display\",\n});\nconst fraunces = Fraunces({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-fraunces\",\n});\nconst jetbrainsMono = JetBrains_Mono({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-jetbrains-mono\",\n});\nconst manrope = Manrope({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-reg-manrope\" });\nconst sora = Sora({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-reg-sora\" });\nconst workSans = Work_Sans({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-work-sans\",\n});\nconst dmSans = DM_Sans({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-reg-dm-sans\" });\nconst plusJakartaSans = Plus_Jakarta_Sans({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-plus-jakarta-sans\",\n});\nconst outfit = Outfit({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-reg-outfit\" });\nconst figtree = Figtree({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-reg-figtree\" });\nconst montserrat = Montserrat({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-montserrat\",\n});\nconst lexend = Lexend({ subsets: [\"latin\"], display: \"swap\", variable: \"--font-reg-lexend\" });\nconst firaCode = Fira_Code({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-fira-code\",\n});\nconst robotoMono = Roboto_Mono({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-roboto-mono\",\n});\nconst sourceSerif4 = Source_Serif_4({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-source-serif-4\",\n});\nconst sourceCodePro = Source_Code_Pro({\n subsets: [\"latin\"],\n display: \"swap\",\n variable: \"--font-reg-source-code-pro\",\n});\n\n/** All registry faces, in declaration order. */\nexport const FONT_REGISTRY_FACES = [\n inter,\n interTight,\n spaceGrotesk,\n playfairDisplay,\n sourceSerif4,\n fraunces,\n jetbrainsMono,\n manrope,\n sora,\n workSans,\n dmSans,\n plusJakartaSans,\n outfit,\n figtree,\n montserrat,\n lexend,\n firaCode,\n robotoMono,\n sourceCodePro,\n] as const;\n\n/**\n * Space-joined `.variable` classNames for every registry face. Apply to <html>\n * so all `--font-*` registry variables are defined (font files lazy-load on\n * first use). Combine with the app's own Geist faces.\n */\nexport const fontRegistryClassName = FONT_REGISTRY_FACES.map((face) => face.variable).join(\" \");\n"],"mappings":";;;;;;AA0BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,IAAM,QAAQ,MAAM,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,eAAe,CAAC;AACrF,IAAM,aAAa,YAAY;AAAA,EAC7B,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,eAAe,cAAc;AAAA,EACjC,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,kBAAkB,iBAAiB;AAAA,EACvC,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,WAAW,SAAS;AAAA,EACxB,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,gBAAgB,eAAe;AAAA,EACnC,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,UAAU,QAAQ,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,qBAAqB,CAAC;AAC/F,IAAM,OAAO,KAAK,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,kBAAkB,CAAC;AACtF,IAAM,WAAW,UAAU;AAAA,EACzB,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,SAAS,QAAQ,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,qBAAqB,CAAC;AAC9F,IAAM,kBAAkB,kBAAkB;AAAA,EACxC,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,SAAS,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,oBAAoB,CAAC;AAC5F,IAAM,UAAU,QAAQ,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,qBAAqB,CAAC;AAC/F,IAAM,aAAa,WAAW;AAAA,EAC5B,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,SAAS,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,QAAQ,UAAU,oBAAoB,CAAC;AAC5F,IAAM,WAAW,UAAU;AAAA,EACzB,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,aAAa,YAAY;AAAA,EAC7B,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,eAAe,eAAe;AAAA,EAClC,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AACD,IAAM,gBAAgB,gBAAgB;AAAA,EACpC,SAAS,CAAC,OAAO;AAAA,EACjB,SAAS;AAAA,EACT,UAAU;AACZ,CAAC;AAGM,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,wBAAwB,oBAAoB,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,KAAK,GAAG;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — face metadata for `@nebutra/fonts/next/cjk`.
|
|
3
|
+
* Current binaries are SIL OFL Noto Sans SC (chinese-simplified) woff2 files.
|
|
4
|
+
* Rebuild with `pnpm --filter @nebutra/fonts subset:cjk` when FONTTOOLS_PYTHON
|
|
5
|
+
* is available, or replace the woff2 files from an OFL source.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const NOTO_SANS_SC_VARIABLE = "--font-noto-sans-sc" as const;
|
|
9
|
+
|
|
10
|
+
export const NOTO_SANS_SC_FAMILY = "Noto Sans SC" as const;
|
|
11
|
+
|
|
12
|
+
/** Characters covered per face (catalogs ∪ CJK punctuation ∪ GB2312 level-1). */
|
|
13
|
+
export const NOTO_SANS_SC_CHAR_COUNT = 4282 as const;
|
|
14
|
+
|
|
15
|
+
/** `unicode-range` of every generated @font-face — CJK only, no Latin. */
|
|
16
|
+
export const NOTO_SANS_SC_UNICODE_RANGE =
|
|
17
|
+
"U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF" as const;
|
|
18
|
+
|
|
19
|
+
/** Sources for `next/font/local({ src: [...] })`, paths relative to this file. */
|
|
20
|
+
export const NOTO_SANS_SC_SOURCES = [
|
|
21
|
+
{ path: "./noto-sans-sc-400.woff2", weight: "400", style: "normal", bytes: 1142552 },
|
|
22
|
+
{ path: "./noto-sans-sc-500.woff2", weight: "500", style: "normal", bytes: 1159128 },
|
|
23
|
+
{ path: "./noto-sans-sc-600.woff2", weight: "600", style: "normal", bytes: 1162352 },
|
|
24
|
+
{ path: "./noto-sans-sc-700.woff2", weight: "700", style: "normal", bytes: 1172244 },
|
|
25
|
+
] as const;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* noto-sans-sc.css — self-hosted Noto Sans SC (SIL Open Font License 1.1).
|
|
3
|
+
* See vendor/noto-sans-sc/OFL.txt.
|
|
4
|
+
*
|
|
5
|
+
* STACK ORDER: use "Geist, Noto Sans SC, …" — Geist first so it keeps Latin
|
|
6
|
+
* and the numerals. The unicode-range below contains no Latin, ASCII or
|
|
7
|
+
* general punctuation, so Latin-only text never downloads a CJK file.
|
|
8
|
+
*/
|
|
9
|
+
@font-face {
|
|
10
|
+
font-family: "Noto Sans SC";
|
|
11
|
+
font-style: normal;
|
|
12
|
+
font-weight: 400;
|
|
13
|
+
font-display: swap;
|
|
14
|
+
src: url("./noto-sans-sc-400.woff2") format("woff2");
|
|
15
|
+
unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@font-face {
|
|
19
|
+
font-family: "Noto Sans SC";
|
|
20
|
+
font-style: normal;
|
|
21
|
+
font-weight: 500;
|
|
22
|
+
font-display: swap;
|
|
23
|
+
src: url("./noto-sans-sc-500.woff2") format("woff2");
|
|
24
|
+
unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@font-face {
|
|
28
|
+
font-family: "Noto Sans SC";
|
|
29
|
+
font-style: normal;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
font-display: swap;
|
|
32
|
+
src: url("./noto-sans-sc-600.woff2") format("woff2");
|
|
33
|
+
unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@font-face {
|
|
37
|
+
font-family: "Noto Sans SC";
|
|
38
|
+
font-style: normal;
|
|
39
|
+
font-weight: 700;
|
|
40
|
+
font-display: swap;
|
|
41
|
+
src: url("./noto-sans-sc-700.woff2") format("woff2");
|
|
42
|
+
unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF;
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generatedBy": "packages/design/fonts/scripts/subset-cjk.mjs",
|
|
3
|
+
"source": "Noto Sans SC (SIL Open Font License 1.1)",
|
|
4
|
+
"faces": [
|
|
5
|
+
{ "weight": 400, "output": "noto-sans-sc-400.woff2", "bytes": 1142552 },
|
|
6
|
+
{ "weight": 500, "output": "noto-sans-sc-500.woff2", "bytes": 1159128 },
|
|
7
|
+
{ "weight": 600, "output": "noto-sans-sc-600.woff2", "bytes": 1162352 },
|
|
8
|
+
{ "weight": 700, "output": "noto-sans-sc-700.woff2", "bytes": 1172244 }
|
|
9
|
+
]
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/fonts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted OSS font registry — build-time next/font faces + a name→CSS-var resolver so theme / DESIGN.md fonts render with zero runtime external requests",
|
|
6
6
|
"private": false,
|
|
7
7
|
"nebutra": {
|
|
8
|
+
"graph": "core",
|
|
8
9
|
"status": "wip",
|
|
9
10
|
"productionReady": false,
|
|
10
11
|
"surface": "support-contract",
|
|
@@ -22,19 +23,52 @@
|
|
|
22
23
|
"directory": "packages/design/fonts"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
|
-
"
|
|
26
|
-
"src
|
|
26
|
+
"dist",
|
|
27
|
+
"src",
|
|
28
|
+
"generated/noto-sans-sc.css",
|
|
29
|
+
"generated/subset-manifest.json",
|
|
30
|
+
"generated/index.ts",
|
|
31
|
+
"vendor/noto-sans-sc/OFL.txt",
|
|
32
|
+
"NOTICE-FONTS.md",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"CHANGELOG.md"
|
|
27
36
|
],
|
|
28
|
-
"main": "./
|
|
29
|
-
"types": "./
|
|
37
|
+
"main": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
30
39
|
"exports": {
|
|
31
|
-
".":
|
|
32
|
-
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"import": "./dist/index.js",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./registry": {
|
|
46
|
+
"types": "./src/index.ts",
|
|
47
|
+
"import": "./src/index.ts",
|
|
48
|
+
"default": "./src/index.ts"
|
|
49
|
+
},
|
|
50
|
+
"./next": {
|
|
51
|
+
"types": "./src/next.ts",
|
|
52
|
+
"import": "./src/next.ts",
|
|
53
|
+
"default": "./src/next.ts"
|
|
54
|
+
},
|
|
55
|
+
"./next/cjk": {
|
|
56
|
+
"types": "./src/next-cjk.ts",
|
|
57
|
+
"import": "./src/next-cjk.ts",
|
|
58
|
+
"default": "./src/next-cjk.ts"
|
|
59
|
+
},
|
|
60
|
+
"./cjk": {
|
|
61
|
+
"types": "./dist/generated/index.d.ts",
|
|
62
|
+
"import": "./dist/generated/index.js",
|
|
63
|
+
"default": "./dist/generated/index.js"
|
|
64
|
+
},
|
|
65
|
+
"./cjk.css": "./generated/noto-sans-sc.css"
|
|
33
66
|
},
|
|
34
67
|
"devDependencies": {
|
|
35
68
|
"@types/react": "^19.2.14",
|
|
36
69
|
"typescript": "^5.9.3",
|
|
37
|
-
"vitest": "^4.1.4"
|
|
70
|
+
"vitest": "^4.1.4",
|
|
71
|
+
"tsup": "^8.5.1"
|
|
38
72
|
},
|
|
39
73
|
"peerDependencies": {
|
|
40
74
|
"next": ">=15",
|
|
@@ -44,7 +78,9 @@
|
|
|
44
78
|
"access": "public"
|
|
45
79
|
},
|
|
46
80
|
"scripts": {
|
|
81
|
+
"subset:cjk": "node scripts/subset-cjk.mjs",
|
|
47
82
|
"test": "vitest run",
|
|
48
|
-
"typecheck": "tsc --noEmit"
|
|
83
|
+
"typecheck": "tsc --noEmit",
|
|
84
|
+
"build": "tsup"
|
|
49
85
|
}
|
|
50
86
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { primaryFamily, resolveRegistryVar, withRegistryFont } from "./index";
|
|
3
|
+
|
|
4
|
+
describe("@nebutra/fonts registry", () => {
|
|
5
|
+
it("normalizes the primary family (strips quotes, lowercases)", () => {
|
|
6
|
+
expect(primaryFamily("'Space Grotesk', sans-serif")).toBe("space grotesk");
|
|
7
|
+
expect(primaryFamily("Inter")).toBe("inter");
|
|
8
|
+
expect(primaryFamily('"JetBrains Mono", ui-monospace, monospace')).toBe("jetbrains mono");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("resolves a registered family to its CSS variable", () => {
|
|
12
|
+
expect(resolveRegistryVar("Space Grotesk, sans-serif")).toBe("--font-space-grotesk");
|
|
13
|
+
expect(resolveRegistryVar("'JetBrains Mono', monospace")).toBe("--font-jetbrains-mono");
|
|
14
|
+
expect(resolveRegistryVar("Geist, sans-serif")).toBe("--font-geist-sans");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns undefined for an unregistered (e.g. proprietary) family", () => {
|
|
18
|
+
expect(resolveRegistryVar("Airbnb Cereal VF, sans-serif")).toBeUndefined();
|
|
19
|
+
expect(resolveRegistryVar("BMWTypeNext, sans-serif")).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("prepends the self-hosted var for a registered family, keeping the fallback", () => {
|
|
23
|
+
expect(withRegistryFont("Space Grotesk, sans-serif")).toBe(
|
|
24
|
+
"var(--font-space-grotesk), Space Grotesk, sans-serif",
|
|
25
|
+
);
|
|
26
|
+
expect(withRegistryFont("'JetBrains Mono', ui-monospace, monospace")).toBe(
|
|
27
|
+
"var(--font-jetbrains-mono), 'JetBrains Mono', ui-monospace, monospace",
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("leaves an unregistered stack untouched (graceful fallback)", () => {
|
|
32
|
+
expect(withRegistryFont("Airbnb Cereal, sans-serif")).toBe("Airbnb Cereal, sans-serif");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("handles undefined/empty input safely", () => {
|
|
36
|
+
expect(withRegistryFont(undefined)).toBeUndefined();
|
|
37
|
+
expect(withRegistryFont("")).toBe("");
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -23,10 +23,17 @@ export const FONT_REGISTRY: Record<string, string> = {
|
|
|
23
23
|
geist: "--font-geist-sans",
|
|
24
24
|
"geist sans": "--font-geist-sans",
|
|
25
25
|
"geist mono": "--font-geist-mono",
|
|
26
|
+
// Self-hosted via next/font/local from the subset built in ./generated (see
|
|
27
|
+
// ./next-cjk). The Simplified-Chinese face — CJK only via unicode-range, so it
|
|
28
|
+
// cannot take Latin away from Geist.
|
|
29
|
+
"noto sans sc": "--font-noto-sans-sc",
|
|
26
30
|
// Self-hosted via next/font/google (see ./next)
|
|
27
31
|
inter: "--font-inter",
|
|
32
|
+
"inter tight": "--font-reg-inter-tight",
|
|
28
33
|
"space grotesk": "--font-space-grotesk",
|
|
29
34
|
"playfair display": "--font-playfair-display",
|
|
35
|
+
fraunces: "--font-reg-fraunces",
|
|
36
|
+
"source serif 4": "--font-reg-source-serif-4",
|
|
30
37
|
"jetbrains mono": "--font-jetbrains-mono",
|
|
31
38
|
manrope: "--font-reg-manrope",
|
|
32
39
|
sora: "--font-reg-sora",
|
|
@@ -70,3 +77,28 @@ export function withRegistryFont(stack: string | undefined): string | undefined
|
|
|
70
77
|
const variable = resolveRegistryVar(stack);
|
|
71
78
|
return variable ? `var(${variable}), ${stack}` : stack;
|
|
72
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Like `withRegistryFont`, but matches the first registered family ANYWHERE in
|
|
83
|
+
* the stack rather than only in first position.
|
|
84
|
+
*
|
|
85
|
+
* A brand package names the typeface the design language actually uses, and
|
|
86
|
+
* those are frequently licensed faces we have no right to serve — Söhne, Mori,
|
|
87
|
+
* Lyon Text, Reckless. The declared stack already says what to do when they are
|
|
88
|
+
* absent: fall to the next family. But "the next family" is usually a bare name
|
|
89
|
+
* like `Inter`, which does NOT reach next/font's hashed face, so the stack
|
|
90
|
+
* skidded past every self-hosted option and landed on `ui-sans-serif`. All
|
|
91
|
+
* seven built-in design languages rendered in the system font until 2026-08-18.
|
|
92
|
+
*
|
|
93
|
+
* Prepending the nearest registered family produces exactly the outcome the
|
|
94
|
+
* declared chain intended, and leaves the licensed names in place so a customer
|
|
95
|
+
* who does own the font still gets it by shipping the face themselves.
|
|
96
|
+
*/
|
|
97
|
+
export function withNearestRegistryFont(stack: string | undefined): string | undefined {
|
|
98
|
+
if (!stack) return stack;
|
|
99
|
+
for (const token of stack.split(",")) {
|
|
100
|
+
const variable = FONT_REGISTRY[normalizeFamily(token)];
|
|
101
|
+
if (variable) return `var(${variable}), ${stack}`;
|
|
102
|
+
}
|
|
103
|
+
return stack;
|
|
104
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
NOTO_SANS_SC_SOURCES,
|
|
6
|
+
NOTO_SANS_SC_UNICODE_RANGE,
|
|
7
|
+
NOTO_SANS_SC_VARIABLE,
|
|
8
|
+
} from "../generated/index";
|
|
9
|
+
import { FONT_REGISTRY } from "./index";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* next/font is a compile-time transform: the options object passed to
|
|
13
|
+
* localFont() must be a literal, so ./next-cjk.ts cannot spread the generated
|
|
14
|
+
* metadata. These tests read that file as TEXT (importing it would pull in
|
|
15
|
+
* next/font/local, which only exists inside a Next build) and assert the
|
|
16
|
+
* literals still agree with what the subsetter actually emitted.
|
|
17
|
+
*/
|
|
18
|
+
const NEXT_CJK_SOURCE = readFileSync(
|
|
19
|
+
fileURLToPath(new URL("./next-cjk.ts", import.meta.url)),
|
|
20
|
+
"utf8",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe("Noto Sans SC wiring", () => {
|
|
24
|
+
it("declares every generated weight, with the file the subsetter wrote", () => {
|
|
25
|
+
for (const { path, weight } of NOTO_SANS_SC_SOURCES) {
|
|
26
|
+
const file = path.replace(/^\.\//, "");
|
|
27
|
+
expect(NEXT_CJK_SOURCE).toContain(`"../generated/${file}", weight: "${weight}"`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("declares no weight that has no file (nothing can be synthesised)", () => {
|
|
32
|
+
const declared = [...NEXT_CJK_SOURCE.matchAll(/weight: "(\d+)"/g)].map((m) => m[1]);
|
|
33
|
+
expect(declared.sort()).toEqual(NOTO_SANS_SC_SOURCES.map((s) => s.weight).sort());
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("carries the generated unicode-range verbatim (CJK only, no Latin)", () => {
|
|
37
|
+
expect(NEXT_CJK_SOURCE).toContain(NOTO_SANS_SC_UNICODE_RANGE);
|
|
38
|
+
expect(NOTO_SANS_SC_UNICODE_RANGE).not.toMatch(/U\+00[0-7]/i);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("uses the CSS variable the registry and the token stacks reference", () => {
|
|
42
|
+
expect(NEXT_CJK_SOURCE).toContain(`variable: "${NOTO_SANS_SC_VARIABLE}"`);
|
|
43
|
+
expect(FONT_REGISTRY["noto sans sc"]).toBe(NOTO_SANS_SC_VARIABLE);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("never preloads (a CJK weight must be demand-loaded)", () => {
|
|
47
|
+
expect(NEXT_CJK_SOURCE).toContain("preload: false");
|
|
48
|
+
});
|
|
49
|
+
});
|
package/src/next-cjk.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nebutra/fonts/next/cjk — the self-hosted Simplified-Chinese face (server-only).
|
|
3
|
+
*
|
|
4
|
+
* WHY A SEPARATE ENTRY FROM `./next`: that module declares ~16 `next/font/google`
|
|
5
|
+
* faces for the theme / DESIGN.md registry. Importing it just to get the CJK face
|
|
6
|
+
* would drag those build-time Google downloads into every app — and this repo has
|
|
7
|
+
* a known trap where `next/font/google` fails outright in a network-sandboxed dev
|
|
8
|
+
* server. This file imports `next/font/local` ONLY: the woff2 files live in the
|
|
9
|
+
* workspace `generated/` directory (not the npm tarball), so first-party apps
|
|
10
|
+
* work offline, in CI, and in the sandbox. `./next` re-exports it,
|
|
11
|
+
* so an app already applying `fontRegistryClassName` still only needs one import.
|
|
12
|
+
*
|
|
13
|
+
* WHY SELF-HOSTED AT ALL: Geist has no CJK coverage, so without this every Chinese
|
|
14
|
+
* character falls back to whatever the OS supplies — PingFang on macOS, Microsoft
|
|
15
|
+
* YaHei on Windows, something else on Android. Chinese copy is a first-class
|
|
16
|
+
* surface here (see docs/microcopy/), so the face is pinned rather than left to
|
|
17
|
+
* the OS.
|
|
18
|
+
*
|
|
19
|
+
* The files are built by `pnpm --filter @nebutra/fonts subset:cjk` from
|
|
20
|
+
* Noto Sans SC (SIL OFL) and live in the workspace `generated/` directory.
|
|
21
|
+
* The literal `src` list below mirrors NOTO_SANS_SC_SOURCES in
|
|
22
|
+
* ../generated/index.ts (a drift test in ./next-cjk.test.ts asserts they agree).
|
|
23
|
+
* It is spelled out rather than spread because next/font is a compile-time
|
|
24
|
+
* transform — SWC statically analyses this call, so the options object cannot
|
|
25
|
+
* be computed.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import localFont from "next/font/local";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Noto Sans SC — 400 / 500 / 600 / 700 static subsets.
|
|
32
|
+
*
|
|
33
|
+
* - `preload: false` on purpose. Each weight is hundreds of KB; preloading them
|
|
34
|
+
* on every route would tax Latin-only pages for nothing. The browser fetches
|
|
35
|
+
* a weight only when a glyph in the `unicode-range` below actually renders.
|
|
36
|
+
* - `declarations` carries that `unicode-range` (CJK blocks only — no ASCII, no
|
|
37
|
+
* Latin, no general punctuation), so a Latin-only page can never trigger a CJK
|
|
38
|
+
* download even if a font stack somewhere is written the wrong way round.
|
|
39
|
+
* - `adjustFontFallback: false` — next/font's metric-matched fallback is derived
|
|
40
|
+
* from Arial, which is meaningless for a Han face.
|
|
41
|
+
*/
|
|
42
|
+
export const notoSansSc = localFont({
|
|
43
|
+
src: [
|
|
44
|
+
{ path: "../generated/noto-sans-sc-400.woff2", weight: "400", style: "normal" },
|
|
45
|
+
{ path: "../generated/noto-sans-sc-500.woff2", weight: "500", style: "normal" },
|
|
46
|
+
{ path: "../generated/noto-sans-sc-600.woff2", weight: "600", style: "normal" },
|
|
47
|
+
{ path: "../generated/noto-sans-sc-700.woff2", weight: "700", style: "normal" },
|
|
48
|
+
],
|
|
49
|
+
declarations: [
|
|
50
|
+
{
|
|
51
|
+
prop: "unicode-range",
|
|
52
|
+
value: "U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FE30-FE4F, U+FF00-FFEF",
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
display: "swap",
|
|
56
|
+
preload: false,
|
|
57
|
+
adjustFontFallback: false,
|
|
58
|
+
variable: "--font-noto-sans-sc",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Apply to <html> next to the Geist loaders so `--font-noto-sans-sc` exists:
|
|
63
|
+
*
|
|
64
|
+
* className={`${GeistSans.variable} ${GeistMono.variable} ${cjkFontClassName}`}
|
|
65
|
+
*
|
|
66
|
+
* The token stacks (`--font-sans` / `--font-cn` / `--font-display` in
|
|
67
|
+
* @nebutra/tokens) reference the variable AFTER Geist, so Geist keeps Latin and
|
|
68
|
+
* the numerals and only CJK falls through to this face.
|
|
69
|
+
*/
|
|
70
|
+
export const cjkFontClassName = notoSansSc.variable;
|
package/src/next.ts
CHANGED
|
@@ -15,13 +15,22 @@
|
|
|
15
15
|
* All declarations use a literal options object — next/font statically analyses
|
|
16
16
|
* the call, so the config must NOT be computed. Variable fonts omit `weight`.
|
|
17
17
|
* Keep the `variable` names in sync with FONT_REGISTRY in `../index.ts`.
|
|
18
|
+
*
|
|
19
|
+
* The self-hosted Simplified-Chinese face lives in `./next-cjk` (next/font/local,
|
|
20
|
+
* no Google fetch) and is re-exported here for discoverability. It is NOT folded
|
|
21
|
+
* into `fontRegistryClassName`: the CJK face is core typography, not an optional
|
|
22
|
+
* theme face, and every app wires it the same way — `${cjkFontClassName}` on
|
|
23
|
+
* <html> beside the Geist loaders. Apps that only need the CJK face should import
|
|
24
|
+
* `@nebutra/fonts/next/cjk` directly so they don't pull in the Google faces below.
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
27
|
import {
|
|
21
28
|
DM_Sans,
|
|
22
29
|
Figtree,
|
|
23
30
|
Fira_Code,
|
|
31
|
+
Fraunces,
|
|
24
32
|
Inter,
|
|
33
|
+
Inter_Tight,
|
|
25
34
|
JetBrains_Mono,
|
|
26
35
|
Lexend,
|
|
27
36
|
Manrope,
|
|
@@ -32,11 +41,19 @@ import {
|
|
|
32
41
|
Roboto_Mono,
|
|
33
42
|
Sora,
|
|
34
43
|
Source_Code_Pro,
|
|
44
|
+
Source_Serif_4,
|
|
35
45
|
Space_Grotesk,
|
|
36
46
|
Work_Sans,
|
|
37
47
|
} from "next/font/google";
|
|
38
48
|
|
|
49
|
+
export { cjkFontClassName, notoSansSc } from "./next-cjk";
|
|
50
|
+
|
|
39
51
|
const inter = Inter({ subsets: ["latin"], display: "swap", variable: "--font-inter" });
|
|
52
|
+
const interTight = Inter_Tight({
|
|
53
|
+
subsets: ["latin"],
|
|
54
|
+
display: "swap",
|
|
55
|
+
variable: "--font-reg-inter-tight",
|
|
56
|
+
});
|
|
40
57
|
const spaceGrotesk = Space_Grotesk({
|
|
41
58
|
subsets: ["latin"],
|
|
42
59
|
display: "swap",
|
|
@@ -47,6 +64,11 @@ const playfairDisplay = Playfair_Display({
|
|
|
47
64
|
display: "swap",
|
|
48
65
|
variable: "--font-playfair-display",
|
|
49
66
|
});
|
|
67
|
+
const fraunces = Fraunces({
|
|
68
|
+
subsets: ["latin"],
|
|
69
|
+
display: "swap",
|
|
70
|
+
variable: "--font-reg-fraunces",
|
|
71
|
+
});
|
|
50
72
|
const jetbrainsMono = JetBrains_Mono({
|
|
51
73
|
subsets: ["latin"],
|
|
52
74
|
display: "swap",
|
|
@@ -83,6 +105,11 @@ const robotoMono = Roboto_Mono({
|
|
|
83
105
|
display: "swap",
|
|
84
106
|
variable: "--font-reg-roboto-mono",
|
|
85
107
|
});
|
|
108
|
+
const sourceSerif4 = Source_Serif_4({
|
|
109
|
+
subsets: ["latin"],
|
|
110
|
+
display: "swap",
|
|
111
|
+
variable: "--font-reg-source-serif-4",
|
|
112
|
+
});
|
|
86
113
|
const sourceCodePro = Source_Code_Pro({
|
|
87
114
|
subsets: ["latin"],
|
|
88
115
|
display: "swap",
|
|
@@ -92,8 +119,11 @@ const sourceCodePro = Source_Code_Pro({
|
|
|
92
119
|
/** All registry faces, in declaration order. */
|
|
93
120
|
export const FONT_REGISTRY_FACES = [
|
|
94
121
|
inter,
|
|
122
|
+
interTight,
|
|
95
123
|
spaceGrotesk,
|
|
96
124
|
playfairDisplay,
|
|
125
|
+
sourceSerif4,
|
|
126
|
+
fraunces,
|
|
97
127
|
jetbrainsMono,
|
|
98
128
|
manrope,
|
|
99
129
|
sora,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
This Font Software is licensed under the SIL Open Font License,
|
|
2
|
+
Version 1.1.
|
|
3
|
+
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
-----------------------------------------------------------
|
|
8
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
PREAMBLE
|
|
12
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
13
|
+
development of collaborative font projects, to support the font
|
|
14
|
+
creation efforts of academic and linguistic communities, and to
|
|
15
|
+
provide a free and open framework in which fonts may be shared and
|
|
16
|
+
improved in partnership with others.
|
|
17
|
+
|
|
18
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
19
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
20
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
21
|
+
redistributed and/or sold with any software provided that any reserved
|
|
22
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
23
|
+
however, cannot be released under any other type of license. The
|
|
24
|
+
requirement for fonts to remain under this license does not apply to
|
|
25
|
+
any document created using the fonts or their derivatives.
|
|
26
|
+
|
|
27
|
+
DEFINITIONS
|
|
28
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
29
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
30
|
+
include source files, build scripts and documentation.
|
|
31
|
+
|
|
32
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
33
|
+
copyright statement(s).
|
|
34
|
+
|
|
35
|
+
"Original Version" refers to the collection of Font Software
|
|
36
|
+
components as distributed by the Copyright Holder(s).
|
|
37
|
+
|
|
38
|
+
"Modified Version" refers to any derivative made by adding to,
|
|
39
|
+
deleting, or substituting -- in part or in whole -- any of the
|
|
40
|
+
components of the Original Version, by changing formats or by porting
|
|
41
|
+
the Font Software to a new environment.
|
|
42
|
+
|
|
43
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
44
|
+
writer or other person who contributed to the Font Software.
|
|
45
|
+
|
|
46
|
+
PERMISSION & CONDITIONS
|
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
48
|
+
a copy of the Font Software, to use, study, copy, merge, embed,
|
|
49
|
+
modify, redistribute, and sell modified and unmodified copies of the
|
|
50
|
+
Font Software, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
1) Neither the Font Software nor any of its individual components, in
|
|
53
|
+
Original or Modified Versions, may be sold by itself.
|
|
54
|
+
|
|
55
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
56
|
+
redistributed and/or sold with any software, provided that each copy
|
|
57
|
+
contains the above copyright notice and this license. These can be
|
|
58
|
+
included either as stand-alone text files, human-readable headers or
|
|
59
|
+
in the appropriate machine-readable metadata fields within text or
|
|
60
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
61
|
+
|
|
62
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
63
|
+
Name(s) unless explicit written permission is granted by the
|
|
64
|
+
corresponding Copyright Holder. This restriction only applies to the
|
|
65
|
+
primary font name as presented to the users.
|
|
66
|
+
|
|
67
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
68
|
+
Software shall not be used to promote, endorse or advertise any
|
|
69
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
70
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
71
|
+
permission.
|
|
72
|
+
|
|
73
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
74
|
+
must be distributed entirely under this license, and must not be
|
|
75
|
+
distributed under any other license. The requirement for fonts to
|
|
76
|
+
remain under this license does not apply to any document created using
|
|
77
|
+
the Font Software.
|
|
78
|
+
|
|
79
|
+
TERMINATION
|
|
80
|
+
This license becomes null and void if any of the above conditions are
|
|
81
|
+
not met.
|
|
82
|
+
|
|
83
|
+
DISCLAIMER
|
|
84
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
87
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
88
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
89
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
90
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
91
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
92
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|