fontaine 1.0.0 → 1.0.2
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/README.md +21 -10
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{metrics-CS5CISMK.cjs → metrics-CioeRv3e.cjs} +36 -13
- package/dist/{metrics-BfrxJdKT.mjs → metrics-Dru3RLLL.mjs} +36 -13
- package/dist/postcss.cjs +1 -1
- package/dist/postcss.mjs +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ import { defineConfig } from 'astro/config'
|
|
|
53
53
|
|
|
54
54
|
const options = {
|
|
55
55
|
// You can specify fallbacks as an array (applies to all fonts)
|
|
56
|
-
fallbacks: ['
|
|
56
|
+
fallbacks: ['Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
|
|
57
57
|
|
|
58
58
|
// Or as an object to configure specific fallbacks per font family
|
|
59
59
|
// fallbacks: {
|
|
@@ -146,7 +146,7 @@ For these setups, use the PostCSS plugin instead. It runs over fully-compiled CS
|
|
|
146
146
|
module.exports = {
|
|
147
147
|
plugins: [
|
|
148
148
|
require('fontaine/postcss')({
|
|
149
|
-
fallbacks: ['
|
|
149
|
+
fallbacks: ['Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
|
|
150
150
|
}),
|
|
151
151
|
// ...any other plugins, such as autoprefixer or cssnano
|
|
152
152
|
],
|
|
@@ -183,9 +183,9 @@ const options = {
|
|
|
183
183
|
|
|
184
184
|
### Default Category Fallbacks
|
|
185
185
|
|
|
186
|
-
- **sans-serif**: `
|
|
187
|
-
- **serif**: `Times New Roman`, `Georgia`, `Noto Serif`
|
|
188
|
-
- **monospace**: `Courier New`, `Roboto Mono`, `Noto Sans Mono`
|
|
186
|
+
- **sans-serif**: `Segoe UI`, `Helvetica Neue`, `Arial`, `Noto Sans`, `Liberation Sans`
|
|
187
|
+
- **serif**: `Times New Roman`, `Georgia`, `Noto Serif`, `Liberation Serif`
|
|
188
|
+
- **monospace**: `Courier New`, `Roboto Mono`, `Noto Sans Mono`, `Liberation Mono`
|
|
189
189
|
- **display** & **handwriting**: Same as sans-serif
|
|
190
190
|
|
|
191
191
|
> **Note:** These presets are available programmatically via `DEFAULT_CATEGORY_FALLBACKS` and can be used with the `resolveCategoryFallbacks` helper function for advanced use cases. Both are exported from the `fontaine` package and shared across related packages (e.g., `fontless`) to ensure consistent fallback behavior.
|
|
@@ -226,15 +226,26 @@ Example:
|
|
|
226
226
|
format('woff');
|
|
227
227
|
font-weight: 700;
|
|
228
228
|
}
|
|
229
|
-
/*
|
|
229
|
+
/* One additional font-face declaration per fallback font will be added to your CSS. */
|
|
230
230
|
@font-face {
|
|
231
231
|
font-family: 'Roboto fallback';
|
|
232
|
-
src: local('
|
|
233
|
-
local('
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
src: local('Segoe UI'), local('Segoe UI Regular'), local('SegoeUI-Regular'),
|
|
233
|
+
local('SegoeUI');
|
|
234
|
+
size-adjust: 100.3304%;
|
|
235
|
+
ascent-override: 92.4679%;
|
|
236
|
+
descent-override: 24.3337%;
|
|
236
237
|
line-gap-override: 0%;
|
|
237
238
|
}
|
|
239
|
+
@font-face {
|
|
240
|
+
font-family: 'Roboto fallback';
|
|
241
|
+
src: local('Arial'), local('Arial Regular'), local('Arial-Regular'),
|
|
242
|
+
local('ArialMT');
|
|
243
|
+
size-adjust: 99.7809%;
|
|
244
|
+
ascent-override: 92.9771%;
|
|
245
|
+
descent-override: 24.4677%;
|
|
246
|
+
line-gap-override: 0%;
|
|
247
|
+
}
|
|
248
|
+
/* ... */
|
|
238
249
|
```
|
|
239
250
|
|
|
240
251
|
Then, whenever you use `font-family: 'Roboto'`, `fontaine` will add the fallback to the font-family:
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
const require_metrics = require("./metrics-
|
|
24
|
+
const require_metrics = require("./metrics-CioeRv3e.cjs");
|
|
25
25
|
let css_tree = require("css-tree");
|
|
26
26
|
let node_path = require("node:path");
|
|
27
27
|
let node_url = require("node:url");
|
package/dist/index.d.cts
CHANGED
|
@@ -28,7 +28,7 @@ interface FallbackOptions {
|
|
|
28
28
|
*/
|
|
29
29
|
[key: string]: any;
|
|
30
30
|
}
|
|
31
|
-
type FontFaceMetrics = Pick<Font, "ascent" | "descent" | "lineGap" | "unitsPerEm" | "xWidthAvg"> & {
|
|
31
|
+
type FontFaceMetrics = Pick<Font, "ascent" | "descent" | "lineGap" | "unitsPerEm" | "xWidthAvg"> & Partial<Pick<Font, "fullName" | "postscriptName">> & {
|
|
32
32
|
category?: string;
|
|
33
33
|
};
|
|
34
34
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -28,7 +28,7 @@ interface FallbackOptions {
|
|
|
28
28
|
*/
|
|
29
29
|
[key: string]: any;
|
|
30
30
|
}
|
|
31
|
-
type FontFaceMetrics = Pick<Font, "ascent" | "descent" | "lineGap" | "unitsPerEm" | "xWidthAvg"> & {
|
|
31
|
+
type FontFaceMetrics = Pick<Font, "ascent" | "descent" | "lineGap" | "unitsPerEm" | "xWidthAvg"> & Partial<Pick<Font, "fullName" | "postscriptName">> & {
|
|
32
32
|
category?: string;
|
|
33
33
|
};
|
|
34
34
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resolveCategoryFallbacks, c as generateFontFace, d as withoutQueryOrFragment, f as withoutQuotes, i as DEFAULT_CATEGORY_FALLBACKS, l as genericCSSFamilies, n as readMetrics, o as cssWideKeywords, r as readMetricsForSource, s as generateFallbackName, t as getMetricsForFamily, u as parseFontFace } from "./metrics-
|
|
1
|
+
import { a as resolveCategoryFallbacks, c as generateFontFace, d as withoutQueryOrFragment, f as withoutQuotes, i as DEFAULT_CATEGORY_FALLBACKS, l as genericCSSFamilies, n as readMetrics, o as cssWideKeywords, r as readMetricsForSource, s as generateFallbackName, t as getMetricsForFamily, u as parseFontFace } from "./metrics-Dru3RLLL.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { parse, walk } from "css-tree";
|
|
4
4
|
import { isAbsolute } from "node:path";
|
|
@@ -188,7 +188,7 @@ function generateFontFace(metrics, fallback) {
|
|
|
188
188
|
const lineGapOverride = metrics.lineGap / adjustedEmSquare;
|
|
189
189
|
return `@font-face {\n${toCSS({
|
|
190
190
|
"font-family": JSON.stringify(fallbackName),
|
|
191
|
-
"src": renderLocalSrc(fallbackFontName),
|
|
191
|
+
"src": renderLocalSrc(fallbackFontName, fallbackMetrics),
|
|
192
192
|
"size-adjust": toPercentage(sizeAdjust),
|
|
193
193
|
"ascent-override": toPercentage(ascentOverride),
|
|
194
194
|
"descent-override": toPercentage(descentOverride),
|
|
@@ -196,8 +196,16 @@ function generateFontFace(metrics, fallback) {
|
|
|
196
196
|
...properties
|
|
197
197
|
})}\n}\n`;
|
|
198
198
|
}
|
|
199
|
-
function renderLocalSrc(font) {
|
|
200
|
-
|
|
199
|
+
function renderLocalSrc(font, metrics) {
|
|
200
|
+
const family = withoutQuotes(font);
|
|
201
|
+
const families = genericFamilyLocals[family.toLowerCase()] || [family];
|
|
202
|
+
const names = new Set(families.flatMap((family) => [
|
|
203
|
+
family,
|
|
204
|
+
`${family} Regular`,
|
|
205
|
+
`${family.replace(/\s+/g, "")}-Regular`
|
|
206
|
+
]));
|
|
207
|
+
for (const name of [metrics?.fullName, metrics?.postscriptName]) if (name) names.add(name);
|
|
208
|
+
return [...names].map((local) => `local(${JSON.stringify(local)})`).join(", ");
|
|
201
209
|
}
|
|
202
210
|
//#endregion
|
|
203
211
|
//#region src/fallbacks.ts
|
|
@@ -207,35 +215,37 @@ function renderLocalSrc(font) {
|
|
|
207
215
|
*/
|
|
208
216
|
const DEFAULT_CATEGORY_FALLBACKS = {
|
|
209
217
|
"sans-serif": [
|
|
210
|
-
"BlinkMacSystemFont",
|
|
211
218
|
"Segoe UI",
|
|
212
219
|
"Helvetica Neue",
|
|
213
220
|
"Arial",
|
|
214
|
-
"Noto Sans"
|
|
221
|
+
"Noto Sans",
|
|
222
|
+
"Liberation Sans"
|
|
215
223
|
],
|
|
216
224
|
"serif": [
|
|
217
225
|
"Times New Roman",
|
|
218
226
|
"Georgia",
|
|
219
|
-
"Noto Serif"
|
|
227
|
+
"Noto Serif",
|
|
228
|
+
"Liberation Serif"
|
|
220
229
|
],
|
|
221
230
|
"monospace": [
|
|
222
231
|
"Courier New",
|
|
223
232
|
"Roboto Mono",
|
|
224
|
-
"Noto Sans Mono"
|
|
233
|
+
"Noto Sans Mono",
|
|
234
|
+
"Liberation Mono"
|
|
225
235
|
],
|
|
226
236
|
"display": [
|
|
227
|
-
"BlinkMacSystemFont",
|
|
228
237
|
"Segoe UI",
|
|
229
238
|
"Helvetica Neue",
|
|
230
239
|
"Arial",
|
|
231
|
-
"Noto Sans"
|
|
240
|
+
"Noto Sans",
|
|
241
|
+
"Liberation Sans"
|
|
232
242
|
],
|
|
233
243
|
"handwriting": [
|
|
234
|
-
"BlinkMacSystemFont",
|
|
235
244
|
"Segoe UI",
|
|
236
245
|
"Helvetica Neue",
|
|
237
246
|
"Arial",
|
|
238
|
-
"Noto Sans"
|
|
247
|
+
"Noto Sans",
|
|
248
|
+
"Liberation Sans"
|
|
239
249
|
]
|
|
240
250
|
};
|
|
241
251
|
/**
|
|
@@ -270,6 +280,11 @@ function resolveCategoryFallbacks(options) {
|
|
|
270
280
|
//#endregion
|
|
271
281
|
//#region src/metrics.ts
|
|
272
282
|
const metricCache = {};
|
|
283
|
+
const metricAliases = {
|
|
284
|
+
"liberation sans": "Arimo",
|
|
285
|
+
"liberation serif": "Tinos",
|
|
286
|
+
"liberation mono": "Cousine"
|
|
287
|
+
};
|
|
273
288
|
function filterRequiredMetrics(font) {
|
|
274
289
|
return {
|
|
275
290
|
ascent: font.ascent,
|
|
@@ -290,7 +305,8 @@ async function getMetricsForFamily(family) {
|
|
|
290
305
|
family = withoutQuotes(family);
|
|
291
306
|
if (family in metricCache) return metricCache[family] ?? null;
|
|
292
307
|
try {
|
|
293
|
-
const
|
|
308
|
+
const alias = metricAliases[family.toLowerCase()];
|
|
309
|
+
const name = fontFamilyToCamelCase(alias ?? family);
|
|
294
310
|
const { entireMetricsCollection } = await import("#capsize-font-metrics");
|
|
295
311
|
const metrics = entireMetricsCollection[name];
|
|
296
312
|
/* v8 ignore next 4 */
|
|
@@ -299,6 +315,10 @@ async function getMetricsForFamily(family) {
|
|
|
299
315
|
return null;
|
|
300
316
|
}
|
|
301
317
|
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
318
|
+
if (!alias) {
|
|
319
|
+
filteredMetrics.fullName = metrics.fullName;
|
|
320
|
+
filteredMetrics.postscriptName = metrics.postscriptName;
|
|
321
|
+
}
|
|
302
322
|
metricCache[family] = filteredMetrics;
|
|
303
323
|
return filteredMetrics;
|
|
304
324
|
} catch {
|
|
@@ -324,7 +344,10 @@ async function readMetrics(_source) {
|
|
|
324
344
|
if (url.protocol === "file:") metrics = await (0, _capsizecss_unpack_fs.fromFile)((0, node_url.fileURLToPath)(source));
|
|
325
345
|
else if (urlRequestCache.has(source)) metrics = await urlRequestCache.get(source);
|
|
326
346
|
else {
|
|
327
|
-
const requestPromise = (0, _capsizecss_unpack.fromUrl)(source)
|
|
347
|
+
const requestPromise = (0, _capsizecss_unpack.fromUrl)(source).catch((error) => {
|
|
348
|
+
urlRequestCache.delete(source);
|
|
349
|
+
throw error;
|
|
350
|
+
});
|
|
328
351
|
urlRequestCache.set(source, requestPromise);
|
|
329
352
|
metrics = await requestPromise;
|
|
330
353
|
}
|
|
@@ -188,7 +188,7 @@ function generateFontFace(metrics, fallback) {
|
|
|
188
188
|
const lineGapOverride = metrics.lineGap / adjustedEmSquare;
|
|
189
189
|
return `@font-face {\n${toCSS({
|
|
190
190
|
"font-family": JSON.stringify(fallbackName),
|
|
191
|
-
"src": renderLocalSrc(fallbackFontName),
|
|
191
|
+
"src": renderLocalSrc(fallbackFontName, fallbackMetrics),
|
|
192
192
|
"size-adjust": toPercentage(sizeAdjust),
|
|
193
193
|
"ascent-override": toPercentage(ascentOverride),
|
|
194
194
|
"descent-override": toPercentage(descentOverride),
|
|
@@ -196,8 +196,16 @@ function generateFontFace(metrics, fallback) {
|
|
|
196
196
|
...properties
|
|
197
197
|
})}\n}\n`;
|
|
198
198
|
}
|
|
199
|
-
function renderLocalSrc(font) {
|
|
200
|
-
|
|
199
|
+
function renderLocalSrc(font, metrics) {
|
|
200
|
+
const family = withoutQuotes(font);
|
|
201
|
+
const families = genericFamilyLocals[family.toLowerCase()] || [family];
|
|
202
|
+
const names = new Set(families.flatMap((family) => [
|
|
203
|
+
family,
|
|
204
|
+
`${family} Regular`,
|
|
205
|
+
`${family.replace(/\s+/g, "")}-Regular`
|
|
206
|
+
]));
|
|
207
|
+
for (const name of [metrics?.fullName, metrics?.postscriptName]) if (name) names.add(name);
|
|
208
|
+
return [...names].map((local) => `local(${JSON.stringify(local)})`).join(", ");
|
|
201
209
|
}
|
|
202
210
|
//#endregion
|
|
203
211
|
//#region src/fallbacks.ts
|
|
@@ -207,35 +215,37 @@ function renderLocalSrc(font) {
|
|
|
207
215
|
*/
|
|
208
216
|
const DEFAULT_CATEGORY_FALLBACKS = {
|
|
209
217
|
"sans-serif": [
|
|
210
|
-
"BlinkMacSystemFont",
|
|
211
218
|
"Segoe UI",
|
|
212
219
|
"Helvetica Neue",
|
|
213
220
|
"Arial",
|
|
214
|
-
"Noto Sans"
|
|
221
|
+
"Noto Sans",
|
|
222
|
+
"Liberation Sans"
|
|
215
223
|
],
|
|
216
224
|
"serif": [
|
|
217
225
|
"Times New Roman",
|
|
218
226
|
"Georgia",
|
|
219
|
-
"Noto Serif"
|
|
227
|
+
"Noto Serif",
|
|
228
|
+
"Liberation Serif"
|
|
220
229
|
],
|
|
221
230
|
"monospace": [
|
|
222
231
|
"Courier New",
|
|
223
232
|
"Roboto Mono",
|
|
224
|
-
"Noto Sans Mono"
|
|
233
|
+
"Noto Sans Mono",
|
|
234
|
+
"Liberation Mono"
|
|
225
235
|
],
|
|
226
236
|
"display": [
|
|
227
|
-
"BlinkMacSystemFont",
|
|
228
237
|
"Segoe UI",
|
|
229
238
|
"Helvetica Neue",
|
|
230
239
|
"Arial",
|
|
231
|
-
"Noto Sans"
|
|
240
|
+
"Noto Sans",
|
|
241
|
+
"Liberation Sans"
|
|
232
242
|
],
|
|
233
243
|
"handwriting": [
|
|
234
|
-
"BlinkMacSystemFont",
|
|
235
244
|
"Segoe UI",
|
|
236
245
|
"Helvetica Neue",
|
|
237
246
|
"Arial",
|
|
238
|
-
"Noto Sans"
|
|
247
|
+
"Noto Sans",
|
|
248
|
+
"Liberation Sans"
|
|
239
249
|
]
|
|
240
250
|
};
|
|
241
251
|
/**
|
|
@@ -270,6 +280,11 @@ function resolveCategoryFallbacks(options) {
|
|
|
270
280
|
//#endregion
|
|
271
281
|
//#region src/metrics.ts
|
|
272
282
|
const metricCache = {};
|
|
283
|
+
const metricAliases = {
|
|
284
|
+
"liberation sans": "Arimo",
|
|
285
|
+
"liberation serif": "Tinos",
|
|
286
|
+
"liberation mono": "Cousine"
|
|
287
|
+
};
|
|
273
288
|
function filterRequiredMetrics(font) {
|
|
274
289
|
return {
|
|
275
290
|
ascent: font.ascent,
|
|
@@ -290,7 +305,8 @@ async function getMetricsForFamily(family) {
|
|
|
290
305
|
family = withoutQuotes(family);
|
|
291
306
|
if (family in metricCache) return metricCache[family] ?? null;
|
|
292
307
|
try {
|
|
293
|
-
const
|
|
308
|
+
const alias = metricAliases[family.toLowerCase()];
|
|
309
|
+
const name = fontFamilyToCamelCase(alias ?? family);
|
|
294
310
|
const { entireMetricsCollection } = await import("#capsize-font-metrics");
|
|
295
311
|
const metrics = entireMetricsCollection[name];
|
|
296
312
|
/* v8 ignore next 4 */
|
|
@@ -299,6 +315,10 @@ async function getMetricsForFamily(family) {
|
|
|
299
315
|
return null;
|
|
300
316
|
}
|
|
301
317
|
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
318
|
+
if (!alias) {
|
|
319
|
+
filteredMetrics.fullName = metrics.fullName;
|
|
320
|
+
filteredMetrics.postscriptName = metrics.postscriptName;
|
|
321
|
+
}
|
|
302
322
|
metricCache[family] = filteredMetrics;
|
|
303
323
|
return filteredMetrics;
|
|
304
324
|
} catch {
|
|
@@ -324,7 +344,10 @@ async function readMetrics(_source) {
|
|
|
324
344
|
if (url.protocol === "file:") metrics = await fromFile(fileURLToPath(source));
|
|
325
345
|
else if (urlRequestCache.has(source)) metrics = await urlRequestCache.get(source);
|
|
326
346
|
else {
|
|
327
|
-
const requestPromise = fromUrl(source)
|
|
347
|
+
const requestPromise = fromUrl(source).catch((error) => {
|
|
348
|
+
urlRequestCache.delete(source);
|
|
349
|
+
throw error;
|
|
350
|
+
});
|
|
328
351
|
urlRequestCache.set(source, requestPromise);
|
|
329
352
|
metrics = await requestPromise;
|
|
330
353
|
}
|
package/dist/postcss.cjs
CHANGED
package/dist/postcss.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resolveCategoryFallbacks, c as generateFontFace, d as withoutQueryOrFragment, f as withoutQuotes, l as genericCSSFamilies, o as cssWideKeywords, r as readMetricsForSource, s as generateFallbackName, t as getMetricsForFamily, u as parseFontFace } from "./metrics-
|
|
1
|
+
import { a as resolveCategoryFallbacks, c as generateFontFace, d as withoutQueryOrFragment, f as withoutQuotes, l as genericCSSFamilies, o as cssWideKeywords, r as readMetricsForSource, s as generateFallbackName, t as getMetricsForFamily, u as parseFontFace } from "./metrics-Dru3RLLL.mjs";
|
|
2
2
|
import { parse } from "css-tree";
|
|
3
3
|
//#region src/postcss.ts
|
|
4
4
|
const supportedExtensions = [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontaine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"description": "Automatic font fallback based on font metrics",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Daniel Roe",
|
|
@@ -66,18 +66,18 @@
|
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@capsizecss/metrics": "4.3.0",
|
|
68
68
|
"@types/css-tree": "3.2.0",
|
|
69
|
-
"@types/node": "24.
|
|
69
|
+
"@types/node": "24.19.1",
|
|
70
70
|
"@types/serve-handler": "6.1.4",
|
|
71
|
-
"@vitest/coverage-v8": "5.0.
|
|
72
|
-
"eslint": "10.
|
|
71
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
72
|
+
"eslint": "10.13.0",
|
|
73
73
|
"get-port-please": "3.2.0",
|
|
74
|
-
"magic-regexp": "0.11.
|
|
75
|
-
"postcss": "8.5.
|
|
74
|
+
"magic-regexp": "0.11.2",
|
|
75
|
+
"postcss": "8.5.29",
|
|
76
76
|
"serve-handler": "6.1.7",
|
|
77
77
|
"tsdown": "0.23.0",
|
|
78
78
|
"typescript": "6.0.3",
|
|
79
|
-
"vite": "8.3.
|
|
80
|
-
"vitest": "5.0.
|
|
79
|
+
"vite": "8.3.4",
|
|
80
|
+
"vitest": "5.0.5"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
83
|
"build": "tsdown",
|