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 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: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
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: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
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**: `BlinkMacSystemFont`, `Segoe UI`, `Helvetica Neue`, `Arial`, `Noto Sans`
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
- /* This additional font-face declaration will be added to your CSS. */
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('BlinkMacSystemFont'), local('Segoe UI'), local('Helvetica Neue'),
233
- local('Arial'), local('Noto Sans');
234
- ascent-override: 92.7734375%;
235
- descent-override: 24.4140625%;
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-CS5CISMK.cjs");
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-BfrxJdKT.mjs";
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
- return (genericFamilyLocals[withoutQuotes(font).toLowerCase()] || [font]).map((local) => `local(${JSON.stringify(local)})`).join(", ");
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 name = fontFamilyToCamelCase(family);
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
- return (genericFamilyLocals[withoutQuotes(font).toLowerCase()] || [font]).map((local) => `local(${JSON.stringify(local)})`).join(", ");
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 name = fontFamilyToCamelCase(family);
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
@@ -1,4 +1,4 @@
1
- const require_metrics = require("./metrics-CS5CISMK.cjs");
1
+ const require_metrics = require("./metrics-CioeRv3e.cjs");
2
2
  let css_tree = require("css-tree");
3
3
  //#region src/postcss.ts
4
4
  const supportedExtensions = [
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-BfrxJdKT.mjs";
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.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.13.6",
69
+ "@types/node": "24.19.1",
70
70
  "@types/serve-handler": "6.1.4",
71
- "@vitest/coverage-v8": "5.0.1",
72
- "eslint": "10.11.0",
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.0",
75
- "postcss": "8.5.28",
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.0",
80
- "vitest": "5.0.1"
79
+ "vite": "8.3.4",
80
+ "vitest": "5.0.5"
81
81
  },
82
82
  "scripts": {
83
83
  "build": "tsdown",