fontaine 1.0.1 → 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
@@ -183,9 +183,9 @@ const options = {
183
183
 
184
184
  ### Default Category Fallbacks
185
185
 
186
- - **sans-serif**: `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('Segoe UI'), local('Helvetica Neue'), local('Arial'),
233
- 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-DjL28VHB.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-R5FQ6W30.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
@@ -210,29 +218,34 @@ const DEFAULT_CATEGORY_FALLBACKS = {
210
218
  "Segoe UI",
211
219
  "Helvetica Neue",
212
220
  "Arial",
213
- "Noto Sans"
221
+ "Noto Sans",
222
+ "Liberation Sans"
214
223
  ],
215
224
  "serif": [
216
225
  "Times New Roman",
217
226
  "Georgia",
218
- "Noto Serif"
227
+ "Noto Serif",
228
+ "Liberation Serif"
219
229
  ],
220
230
  "monospace": [
221
231
  "Courier New",
222
232
  "Roboto Mono",
223
- "Noto Sans Mono"
233
+ "Noto Sans Mono",
234
+ "Liberation Mono"
224
235
  ],
225
236
  "display": [
226
237
  "Segoe UI",
227
238
  "Helvetica Neue",
228
239
  "Arial",
229
- "Noto Sans"
240
+ "Noto Sans",
241
+ "Liberation Sans"
230
242
  ],
231
243
  "handwriting": [
232
244
  "Segoe UI",
233
245
  "Helvetica Neue",
234
246
  "Arial",
235
- "Noto Sans"
247
+ "Noto Sans",
248
+ "Liberation Sans"
236
249
  ]
237
250
  };
238
251
  /**
@@ -267,6 +280,11 @@ function resolveCategoryFallbacks(options) {
267
280
  //#endregion
268
281
  //#region src/metrics.ts
269
282
  const metricCache = {};
283
+ const metricAliases = {
284
+ "liberation sans": "Arimo",
285
+ "liberation serif": "Tinos",
286
+ "liberation mono": "Cousine"
287
+ };
270
288
  function filterRequiredMetrics(font) {
271
289
  return {
272
290
  ascent: font.ascent,
@@ -287,7 +305,8 @@ async function getMetricsForFamily(family) {
287
305
  family = withoutQuotes(family);
288
306
  if (family in metricCache) return metricCache[family] ?? null;
289
307
  try {
290
- const name = fontFamilyToCamelCase(family);
308
+ const alias = metricAliases[family.toLowerCase()];
309
+ const name = fontFamilyToCamelCase(alias ?? family);
291
310
  const { entireMetricsCollection } = await import("#capsize-font-metrics");
292
311
  const metrics = entireMetricsCollection[name];
293
312
  /* v8 ignore next 4 */
@@ -296,6 +315,10 @@ async function getMetricsForFamily(family) {
296
315
  return null;
297
316
  }
298
317
  const filteredMetrics = filterRequiredMetrics(metrics);
318
+ if (!alias) {
319
+ filteredMetrics.fullName = metrics.fullName;
320
+ filteredMetrics.postscriptName = metrics.postscriptName;
321
+ }
299
322
  metricCache[family] = filteredMetrics;
300
323
  return filteredMetrics;
301
324
  } catch {
@@ -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
@@ -210,29 +218,34 @@ const DEFAULT_CATEGORY_FALLBACKS = {
210
218
  "Segoe UI",
211
219
  "Helvetica Neue",
212
220
  "Arial",
213
- "Noto Sans"
221
+ "Noto Sans",
222
+ "Liberation Sans"
214
223
  ],
215
224
  "serif": [
216
225
  "Times New Roman",
217
226
  "Georgia",
218
- "Noto Serif"
227
+ "Noto Serif",
228
+ "Liberation Serif"
219
229
  ],
220
230
  "monospace": [
221
231
  "Courier New",
222
232
  "Roboto Mono",
223
- "Noto Sans Mono"
233
+ "Noto Sans Mono",
234
+ "Liberation Mono"
224
235
  ],
225
236
  "display": [
226
237
  "Segoe UI",
227
238
  "Helvetica Neue",
228
239
  "Arial",
229
- "Noto Sans"
240
+ "Noto Sans",
241
+ "Liberation Sans"
230
242
  ],
231
243
  "handwriting": [
232
244
  "Segoe UI",
233
245
  "Helvetica Neue",
234
246
  "Arial",
235
- "Noto Sans"
247
+ "Noto Sans",
248
+ "Liberation Sans"
236
249
  ]
237
250
  };
238
251
  /**
@@ -267,6 +280,11 @@ function resolveCategoryFallbacks(options) {
267
280
  //#endregion
268
281
  //#region src/metrics.ts
269
282
  const metricCache = {};
283
+ const metricAliases = {
284
+ "liberation sans": "Arimo",
285
+ "liberation serif": "Tinos",
286
+ "liberation mono": "Cousine"
287
+ };
270
288
  function filterRequiredMetrics(font) {
271
289
  return {
272
290
  ascent: font.ascent,
@@ -287,7 +305,8 @@ async function getMetricsForFamily(family) {
287
305
  family = withoutQuotes(family);
288
306
  if (family in metricCache) return metricCache[family] ?? null;
289
307
  try {
290
- const name = fontFamilyToCamelCase(family);
308
+ const alias = metricAliases[family.toLowerCase()];
309
+ const name = fontFamilyToCamelCase(alias ?? family);
291
310
  const { entireMetricsCollection } = await import("#capsize-font-metrics");
292
311
  const metrics = entireMetricsCollection[name];
293
312
  /* v8 ignore next 4 */
@@ -296,6 +315,10 @@ async function getMetricsForFamily(family) {
296
315
  return null;
297
316
  }
298
317
  const filteredMetrics = filterRequiredMetrics(metrics);
318
+ if (!alias) {
319
+ filteredMetrics.fullName = metrics.fullName;
320
+ filteredMetrics.postscriptName = metrics.postscriptName;
321
+ }
299
322
  metricCache[family] = filteredMetrics;
300
323
  return filteredMetrics;
301
324
  } catch {
package/dist/postcss.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_metrics = require("./metrics-DjL28VHB.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-R5FQ6W30.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.1",
4
+ "version": "1.0.2",
5
5
  "description": "Automatic font fallback based on font metrics",
6
6
  "author": {
7
7
  "name": "Daniel Roe",
@@ -68,16 +68,16 @@
68
68
  "@types/css-tree": "3.2.0",
69
69
  "@types/node": "24.19.1",
70
70
  "@types/serve-handler": "6.1.4",
71
- "@vitest/coverage-v8": "5.0.3",
72
- "eslint": "10.12.0",
71
+ "@vitest/coverage-v8": "5.0.5",
72
+ "eslint": "10.13.0",
73
73
  "get-port-please": "3.2.0",
74
74
  "magic-regexp": "0.11.2",
75
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.2",
80
- "vitest": "5.0.3"
79
+ "vite": "8.3.4",
80
+ "vitest": "5.0.5"
81
81
  },
82
82
  "scripts": {
83
83
  "build": "tsdown",