fontaine 0.5.0 → 0.6.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/README.md +2 -2
- package/dist/index.cjs +174 -125
- package/dist/index.d.cts +88 -16
- package/dist/index.d.mts +88 -16
- package/dist/index.d.ts +88 -16
- package/dist/index.mjs +176 -127
- package/package.json +33 -30
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ Published under [MIT License](./LICENCE).
|
|
|
187
187
|
[npm-version-href]: https://npmjs.com/package/fontaine
|
|
188
188
|
[npm-downloads-src]: https://img.shields.io/npm/dm/fontaine?style=flat-square
|
|
189
189
|
[npm-downloads-href]: https://npmjs.com/package/fontaine
|
|
190
|
-
[github-actions-src]: https://img.shields.io/github/workflow/status/unjs/fontaine/ci
|
|
191
|
-
[github-actions-href]: https://github.com/unjs/fontaine/actions
|
|
190
|
+
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/unjs/fontaine/ci.yml?branch=main&style=flat-square
|
|
191
|
+
[github-actions-href]: https://github.com/unjs/fontaine/actions/workflows/ci.yml
|
|
192
192
|
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/fontaine/main?style=flat-square
|
|
193
193
|
[codecov-href]: https://codecov.io/gh/unjs/fontaine
|
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const cssTree = require('css-tree');
|
|
4
4
|
const magicRegexp = require('magic-regexp');
|
|
5
|
-
const MagicString = require('magic-string');
|
|
6
|
-
const ufo = require('ufo');
|
|
7
|
-
const pathe = require('pathe');
|
|
8
5
|
const node_url = require('node:url');
|
|
9
|
-
const unpack = require('@capsizecss/unpack');
|
|
10
6
|
const metrics = require('@capsizecss/metrics');
|
|
7
|
+
const unpack = require('@capsizecss/unpack');
|
|
8
|
+
const ufo = require('ufo');
|
|
9
|
+
const MagicString = require('magic-string');
|
|
10
|
+
const pathe = require('pathe');
|
|
11
|
+
const unplugin = require('unplugin');
|
|
11
12
|
|
|
12
13
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
13
14
|
|
|
@@ -24,44 +25,91 @@ const QUOTES_RE = magicRegexp.createRegExp(
|
|
|
24
25
|
magicRegexp.charIn(`"'`).at.lineStart().or(magicRegexp.charIn(`"'`).at.lineEnd()),
|
|
25
26
|
["g"]
|
|
26
27
|
);
|
|
27
|
-
const FAMILY_RE = magicRegexp.createRegExp(
|
|
28
|
-
magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("fontFamily"))
|
|
29
|
-
);
|
|
30
|
-
const SOURCE_RE = magicRegexp.createRegExp(
|
|
31
|
-
magicRegexp.exactly("src:").and(magicRegexp.whitespace.optionally()).and(magicRegexp.charNotIn(";}").times.any().as("src")),
|
|
32
|
-
["g"]
|
|
33
|
-
);
|
|
34
|
-
const URL_RE = magicRegexp.createRegExp(
|
|
35
|
-
magicRegexp.exactly("url(").and(magicRegexp.charNotIn(")").times.any().as("url")).and(")"),
|
|
36
|
-
["g"]
|
|
37
|
-
);
|
|
38
28
|
const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
const genericCSSFamilies = /* @__PURE__ */ new Set([
|
|
30
|
+
"serif",
|
|
31
|
+
"sans-serif",
|
|
32
|
+
"monospace",
|
|
33
|
+
"cursive",
|
|
34
|
+
"fantasy",
|
|
35
|
+
"system-ui",
|
|
36
|
+
"ui-serif",
|
|
37
|
+
"ui-sans-serif",
|
|
38
|
+
"ui-monospace",
|
|
39
|
+
"ui-rounded",
|
|
40
|
+
"emoji",
|
|
41
|
+
"math",
|
|
42
|
+
"fangsong"
|
|
43
|
+
]);
|
|
44
|
+
const fontProperties = /* @__PURE__ */ new Set(["font-weight", "font-style", "font-stretch"]);
|
|
45
|
+
function parseFontFace(css) {
|
|
46
|
+
const families = [];
|
|
47
|
+
const ast = typeof css === "string" ? cssTree.parse(css, { positions: true }) : css;
|
|
48
|
+
cssTree.walk(ast, {
|
|
49
|
+
visit: "Atrule",
|
|
50
|
+
enter(node) {
|
|
51
|
+
if (node.name !== "font-face")
|
|
52
|
+
return;
|
|
53
|
+
let family;
|
|
54
|
+
const sources = [];
|
|
55
|
+
const properties = {};
|
|
56
|
+
if (node.block) {
|
|
57
|
+
cssTree.walk(node.block, {
|
|
58
|
+
visit: "Declaration",
|
|
59
|
+
enter(declaration) {
|
|
60
|
+
if (declaration.property === "font-family" && declaration.value.type === "Value") {
|
|
61
|
+
for (const child of declaration.value.children) {
|
|
62
|
+
if (child.type === "String") {
|
|
63
|
+
family = withoutQuotes(child.value);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (child.type === "Identifier" && !genericCSSFamilies.has(child.name)) {
|
|
67
|
+
family = child.name;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (fontProperties.has(declaration.property)) {
|
|
73
|
+
if (declaration.value.type === "Value") {
|
|
74
|
+
for (const child of declaration.value.children) {
|
|
75
|
+
const hasValue = !!properties[declaration.property];
|
|
76
|
+
properties[declaration.property] ||= "";
|
|
77
|
+
properties[declaration.property] += (hasValue ? " " : "") + cssTree.generate(child);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (declaration.property === "src") {
|
|
82
|
+
cssTree.walk(declaration.value, {
|
|
83
|
+
visit: "Url",
|
|
84
|
+
enter(urlNode) {
|
|
85
|
+
const source = withoutQuotes(urlNode.value);
|
|
86
|
+
if (source) {
|
|
87
|
+
sources.push(source);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (family) {
|
|
96
|
+
for (const source of sources) {
|
|
97
|
+
families.push({ index: node.loc.start.offset, family, source, properties });
|
|
98
|
+
}
|
|
99
|
+
if (!sources.length) {
|
|
100
|
+
families.push({ index: node.loc.start.offset, family, properties });
|
|
101
|
+
}
|
|
49
102
|
}
|
|
50
103
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
104
|
+
});
|
|
105
|
+
return families;
|
|
53
106
|
}
|
|
54
107
|
function generateFallbackName(name) {
|
|
55
108
|
const firstFamily = withoutQuotes(name.split(",").shift());
|
|
56
109
|
return `${firstFamily} fallback`;
|
|
57
110
|
}
|
|
58
111
|
function generateFontFace(metrics, fallback) {
|
|
59
|
-
const {
|
|
60
|
-
name: fallbackName,
|
|
61
|
-
font: fallbackFontName,
|
|
62
|
-
metrics: fallbackMetrics,
|
|
63
|
-
...properties
|
|
64
|
-
} = fallback;
|
|
112
|
+
const { name: fallbackName, font: fallbackFontName, metrics: fallbackMetrics, ...properties } = fallback;
|
|
65
113
|
const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
|
|
66
114
|
const fallbackFontXAvgRatio = fallbackMetrics ? fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm : 1;
|
|
67
115
|
const sizeAdjust = fallbackMetrics && preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
|
|
@@ -85,13 +133,7 @@ ${toCSS(declaration)}
|
|
|
85
133
|
}
|
|
86
134
|
|
|
87
135
|
const metricCache = {};
|
|
88
|
-
function filterRequiredMetrics({
|
|
89
|
-
ascent,
|
|
90
|
-
descent,
|
|
91
|
-
lineGap,
|
|
92
|
-
unitsPerEm,
|
|
93
|
-
xWidthAvg
|
|
94
|
-
}) {
|
|
136
|
+
function filterRequiredMetrics({ ascent, descent, lineGap, unitsPerEm, xWidthAvg }) {
|
|
95
137
|
return {
|
|
96
138
|
ascent,
|
|
97
139
|
descent,
|
|
@@ -108,6 +150,10 @@ async function getMetricsForFamily(family) {
|
|
|
108
150
|
const name = metrics.fontFamilyToCamelCase(family);
|
|
109
151
|
const { entireMetricsCollection } = await import('@capsizecss/metrics/entireMetricsCollection');
|
|
110
152
|
const metrics$1 = entireMetricsCollection[name];
|
|
153
|
+
if (!("descent" in metrics$1)) {
|
|
154
|
+
metricCache[family] = null;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
111
157
|
const filteredMetrics = filterRequiredMetrics(metrics$1);
|
|
112
158
|
metricCache[family] = filteredMetrics;
|
|
113
159
|
return filteredMetrics;
|
|
@@ -116,111 +162,114 @@ async function getMetricsForFamily(family) {
|
|
|
116
162
|
return null;
|
|
117
163
|
}
|
|
118
164
|
}
|
|
165
|
+
const urlRequestCache = /* @__PURE__ */ new Map();
|
|
119
166
|
async function readMetrics(_source) {
|
|
120
167
|
const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
|
|
121
168
|
if (source in metricCache)
|
|
122
|
-
return
|
|
169
|
+
return metricCache[source];
|
|
123
170
|
const { protocol } = ufo.parseURL(source);
|
|
124
171
|
if (!protocol)
|
|
125
172
|
return null;
|
|
126
|
-
|
|
173
|
+
let metrics;
|
|
174
|
+
if (protocol === "file:") {
|
|
175
|
+
metrics = await unpack.fromFile(node_url.fileURLToPath(source));
|
|
176
|
+
} else {
|
|
177
|
+
if (urlRequestCache.has(source)) {
|
|
178
|
+
metrics = await urlRequestCache.get(source);
|
|
179
|
+
} else {
|
|
180
|
+
const requestPromise = unpack.fromUrl(source);
|
|
181
|
+
urlRequestCache.set(source, requestPromise);
|
|
182
|
+
metrics = await requestPromise;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
127
185
|
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
128
186
|
metricCache[source] = filteredMetrics;
|
|
129
187
|
return filteredMetrics;
|
|
130
188
|
}
|
|
131
189
|
|
|
132
190
|
const supportedExtensions = ["woff2", "woff", "ttf"];
|
|
133
|
-
const FONT_FACE_RE = magicRegexp.createRegExp(
|
|
134
|
-
magicRegexp.exactly("@font-face").and(magicRegexp.whitespace.times.any()).and("{").and(magicRegexp.charNotIn("}").times.any()).and("}"),
|
|
135
|
-
["g"]
|
|
136
|
-
);
|
|
137
|
-
const FONT_FAMILY_RE = magicRegexp.createRegExp(
|
|
138
|
-
magicRegexp.charNotIn(";}").times.any().as("family").after(magicRegexp.exactly("font-family:").and(magicRegexp.whitespace.times.any())).before(magicRegexp.charIn(";}").or(magicRegexp.exactly("").at.lineEnd())),
|
|
139
|
-
["g"]
|
|
140
|
-
);
|
|
141
191
|
const CSS_RE = magicRegexp.createRegExp(
|
|
142
192
|
magicRegexp.exactly(".").and(magicRegexp.anyOf("sass", "css", "scss")).at.lineEnd()
|
|
143
193
|
);
|
|
144
|
-
const
|
|
145
|
-
(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
194
|
+
const RELATIVE_RE = magicRegexp.createRegExp(
|
|
195
|
+
magicRegexp.exactly(".").or("..").and(magicRegexp.anyOf("/", "\\")).at.lineStart()
|
|
196
|
+
);
|
|
197
|
+
const FontaineTransform = unplugin.createUnplugin((options) => {
|
|
198
|
+
const cssContext = options.css = options.css || {};
|
|
199
|
+
cssContext.value = "";
|
|
200
|
+
const resolvePath = options.resolvePath || ((id) => id);
|
|
201
|
+
const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
|
|
202
|
+
const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
|
|
203
|
+
function readMetricsFromId(path, importer) {
|
|
204
|
+
const resolvedPath = pathe.isAbsolute(importer) && RELATIVE_RE.test(path) ? new URL(path, node_url.pathToFileURL(importer)) : resolvePath(path);
|
|
205
|
+
return readMetrics(resolvedPath);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
name: "fontaine-transform",
|
|
209
|
+
enforce: "pre",
|
|
210
|
+
transformInclude(id) {
|
|
211
|
+
const { pathname } = ufo.parseURL(id);
|
|
212
|
+
return CSS_RE.test(pathname) || CSS_RE.test(id);
|
|
213
|
+
},
|
|
214
|
+
async transform(code, id) {
|
|
215
|
+
const s = new MagicString__default(code);
|
|
216
|
+
const ast = cssTree.parse(code, { positions: true });
|
|
217
|
+
for (const { family, source, index, properties } of parseFontFace(ast)) {
|
|
218
|
+
if (!supportedExtensions.some((e) => source?.endsWith(e)))
|
|
219
|
+
continue;
|
|
220
|
+
if (skipFontFaceGeneration(fallbackName(family)))
|
|
221
|
+
continue;
|
|
222
|
+
const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
|
|
223
|
+
if (!metrics)
|
|
224
|
+
continue;
|
|
225
|
+
for (let i = options.fallbacks.length - 1; i >= 0; i--) {
|
|
226
|
+
const fallback = options.fallbacks[i];
|
|
227
|
+
const fallbackMetrics = await getMetricsForFamily(fallback);
|
|
228
|
+
if (!fallbackMetrics)
|
|
168
229
|
continue;
|
|
169
|
-
|
|
170
|
-
|
|
230
|
+
const fontFace = generateFontFace(metrics, {
|
|
231
|
+
name: fallbackName(family),
|
|
232
|
+
font: fallback,
|
|
233
|
+
metrics: fallbackMetrics,
|
|
234
|
+
...properties
|
|
235
|
+
});
|
|
236
|
+
cssContext.value += fontFace;
|
|
237
|
+
s.appendLeft(index, fontFace);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
cssTree.walk(ast, {
|
|
241
|
+
visit: "Declaration",
|
|
242
|
+
enter(node) {
|
|
243
|
+
if (node.property !== "font-family")
|
|
244
|
+
return;
|
|
245
|
+
if (this.atrule && this.atrule.name === "font-face")
|
|
246
|
+
return;
|
|
247
|
+
if (node.value.type !== "Value")
|
|
248
|
+
return;
|
|
249
|
+
for (const child of node.value.children) {
|
|
250
|
+
let family;
|
|
251
|
+
if (child.type === "String") {
|
|
252
|
+
family = withoutQuotes(child.value);
|
|
253
|
+
} else if (child.type === "Identifier" && child.name !== "inherit") {
|
|
254
|
+
family = child.name;
|
|
255
|
+
}
|
|
171
256
|
if (!family)
|
|
172
257
|
continue;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (skipFontFaceGeneration(fallbackName(family)))
|
|
176
|
-
continue;
|
|
177
|
-
const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
|
|
178
|
-
if (!metrics)
|
|
179
|
-
continue;
|
|
180
|
-
for (let i = options.fallbacks.length - 1; i >= 0; i--) {
|
|
181
|
-
const fallback = options.fallbacks[i];
|
|
182
|
-
const fallbackMetrics = await getMetricsForFamily(fallback);
|
|
183
|
-
if (!fallbackMetrics)
|
|
184
|
-
continue;
|
|
185
|
-
const fontFace = generateFontFace(metrics, {
|
|
186
|
-
name: fallbackName(family),
|
|
187
|
-
font: fallback,
|
|
188
|
-
metrics: fallbackMetrics
|
|
189
|
-
});
|
|
190
|
-
cssContext.value += fontFace;
|
|
191
|
-
s.appendLeft(match.index, fontFace);
|
|
192
|
-
}
|
|
258
|
+
s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
|
|
259
|
+
return;
|
|
193
260
|
}
|
|
194
261
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (!families.length || families[0] === "inherit")
|
|
203
|
-
continue;
|
|
204
|
-
s.overwrite(
|
|
205
|
-
index,
|
|
206
|
-
index + matchContent.length,
|
|
207
|
-
` ${[
|
|
208
|
-
families[0],
|
|
209
|
-
`"${generateFallbackName(families[0])}"`,
|
|
210
|
-
...families.slice(1)
|
|
211
|
-
].join(", ")}`
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
if (s.hasChanged()) {
|
|
215
|
-
return {
|
|
216
|
-
code: s.toString(),
|
|
217
|
-
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
218
|
-
};
|
|
219
|
-
}
|
|
262
|
+
});
|
|
263
|
+
if (s.hasChanged()) {
|
|
264
|
+
return {
|
|
265
|
+
code: s.toString(),
|
|
266
|
+
/* v8 ignore next 3 */
|
|
267
|
+
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
268
|
+
};
|
|
220
269
|
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
});
|
|
224
273
|
|
|
225
274
|
exports.FontaineTransform = FontaineTransform;
|
|
226
275
|
exports.generateFallbackName = generateFallbackName;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,32 +1,104 @@
|
|
|
1
|
-
import * as unplugin from 'unplugin';
|
|
2
1
|
import { Font } from '@capsizecss/unpack';
|
|
2
|
+
import * as unplugin from 'unplugin';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a fallback name based on the first font family specified in the input string.
|
|
6
|
+
* @param {string} name - The full font family string.
|
|
7
|
+
* @returns {string} - The fallback font name.
|
|
8
|
+
*/
|
|
9
|
+
declare function generateFallbackName(name: string): string;
|
|
10
|
+
interface FallbackOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The name of the fallback font.
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* The fallback font family name.
|
|
17
|
+
*/
|
|
18
|
+
font: string;
|
|
19
|
+
/**
|
|
20
|
+
* Metrics for fallback face calculations.
|
|
21
|
+
* @optional
|
|
22
|
+
*/
|
|
23
|
+
metrics?: FontFaceMetrics;
|
|
24
|
+
/**
|
|
25
|
+
* Additional properties that may be included dynamically
|
|
26
|
+
*/
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
|
|
30
|
+
/**
|
|
31
|
+
* Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
|
|
32
|
+
* @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
|
|
33
|
+
* @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
|
|
34
|
+
* @returns {string} - The full `@font-face` CSS declaration.
|
|
35
|
+
*/
|
|
36
|
+
declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
|
|
40
|
+
* @param {string} family - The name of the font family for which metrics are requested.
|
|
41
|
+
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
|
|
42
|
+
* @async
|
|
43
|
+
*/
|
|
44
|
+
declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
|
|
47
|
+
* It caches the results to optimise subsequent requests for the same source.
|
|
48
|
+
* @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
|
|
49
|
+
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
|
|
50
|
+
* @async
|
|
51
|
+
*/
|
|
52
|
+
declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
|
|
3
53
|
|
|
4
54
|
interface FontaineTransformOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Configuration options for the CSS transformation.
|
|
57
|
+
* @optional
|
|
58
|
+
*/
|
|
5
59
|
css?: {
|
|
60
|
+
/**
|
|
61
|
+
* Holds the current value of the CSS being transformed.
|
|
62
|
+
* @optional
|
|
63
|
+
*/
|
|
6
64
|
value?: string;
|
|
7
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* An array of fallback font family names to use.
|
|
68
|
+
*/
|
|
8
69
|
fallbacks: string[];
|
|
70
|
+
/**
|
|
71
|
+
* Function to resolve a given path to a valid URL or local path.
|
|
72
|
+
* This is typically used to resolve font file paths.
|
|
73
|
+
* @optional
|
|
74
|
+
*/
|
|
9
75
|
resolvePath?: (path: string) => string | URL;
|
|
76
|
+
/**
|
|
77
|
+
* A function to determine whether to skip font face generation for a given fallback name.
|
|
78
|
+
* @optional
|
|
79
|
+
*/
|
|
10
80
|
skipFontFaceGeneration?: (fallbackName: string) => boolean;
|
|
11
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Function to generate an unquoted font family name to use as a fallback.
|
|
83
|
+
* This should return a valid CSS font family name and should not include quotes.
|
|
84
|
+
* @optional
|
|
85
|
+
*/
|
|
12
86
|
fallbackName?: (name: string) => string;
|
|
13
87
|
/** @deprecated use fallbackName */
|
|
14
88
|
overrideName?: (name: string) => string;
|
|
89
|
+
/**
|
|
90
|
+
* Specifies whether to create a source map for the transformation.
|
|
91
|
+
* @optional
|
|
92
|
+
*/
|
|
15
93
|
sourcemap?: boolean;
|
|
16
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Transforms CSS files to include font fallbacks.
|
|
97
|
+
*
|
|
98
|
+
* @param options - The transformation options. See {@link FontaineTransformOptions}.
|
|
99
|
+
* @returns The unplugin instance.
|
|
100
|
+
*/
|
|
17
101
|
declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, boolean>;
|
|
18
102
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
name: string;
|
|
22
|
-
font: string;
|
|
23
|
-
metrics?: FontFaceMetrics;
|
|
24
|
-
[key: string]: any;
|
|
25
|
-
}
|
|
26
|
-
type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
|
|
27
|
-
declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
|
|
28
|
-
|
|
29
|
-
declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
|
|
30
|
-
declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
|
|
31
|
-
|
|
32
|
-
export { FontaineTransform, type FontaineTransformOptions, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
|
103
|
+
export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
|
104
|
+
export type { FontaineTransformOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,32 +1,104 @@
|
|
|
1
|
-
import * as unplugin from 'unplugin';
|
|
2
1
|
import { Font } from '@capsizecss/unpack';
|
|
2
|
+
import * as unplugin from 'unplugin';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a fallback name based on the first font family specified in the input string.
|
|
6
|
+
* @param {string} name - The full font family string.
|
|
7
|
+
* @returns {string} - The fallback font name.
|
|
8
|
+
*/
|
|
9
|
+
declare function generateFallbackName(name: string): string;
|
|
10
|
+
interface FallbackOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The name of the fallback font.
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* The fallback font family name.
|
|
17
|
+
*/
|
|
18
|
+
font: string;
|
|
19
|
+
/**
|
|
20
|
+
* Metrics for fallback face calculations.
|
|
21
|
+
* @optional
|
|
22
|
+
*/
|
|
23
|
+
metrics?: FontFaceMetrics;
|
|
24
|
+
/**
|
|
25
|
+
* Additional properties that may be included dynamically
|
|
26
|
+
*/
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
|
|
30
|
+
/**
|
|
31
|
+
* Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
|
|
32
|
+
* @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
|
|
33
|
+
* @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
|
|
34
|
+
* @returns {string} - The full `@font-face` CSS declaration.
|
|
35
|
+
*/
|
|
36
|
+
declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
|
|
40
|
+
* @param {string} family - The name of the font family for which metrics are requested.
|
|
41
|
+
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
|
|
42
|
+
* @async
|
|
43
|
+
*/
|
|
44
|
+
declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
|
|
47
|
+
* It caches the results to optimise subsequent requests for the same source.
|
|
48
|
+
* @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
|
|
49
|
+
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
|
|
50
|
+
* @async
|
|
51
|
+
*/
|
|
52
|
+
declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
|
|
3
53
|
|
|
4
54
|
interface FontaineTransformOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Configuration options for the CSS transformation.
|
|
57
|
+
* @optional
|
|
58
|
+
*/
|
|
5
59
|
css?: {
|
|
60
|
+
/**
|
|
61
|
+
* Holds the current value of the CSS being transformed.
|
|
62
|
+
* @optional
|
|
63
|
+
*/
|
|
6
64
|
value?: string;
|
|
7
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* An array of fallback font family names to use.
|
|
68
|
+
*/
|
|
8
69
|
fallbacks: string[];
|
|
70
|
+
/**
|
|
71
|
+
* Function to resolve a given path to a valid URL or local path.
|
|
72
|
+
* This is typically used to resolve font file paths.
|
|
73
|
+
* @optional
|
|
74
|
+
*/
|
|
9
75
|
resolvePath?: (path: string) => string | URL;
|
|
76
|
+
/**
|
|
77
|
+
* A function to determine whether to skip font face generation for a given fallback name.
|
|
78
|
+
* @optional
|
|
79
|
+
*/
|
|
10
80
|
skipFontFaceGeneration?: (fallbackName: string) => boolean;
|
|
11
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Function to generate an unquoted font family name to use as a fallback.
|
|
83
|
+
* This should return a valid CSS font family name and should not include quotes.
|
|
84
|
+
* @optional
|
|
85
|
+
*/
|
|
12
86
|
fallbackName?: (name: string) => string;
|
|
13
87
|
/** @deprecated use fallbackName */
|
|
14
88
|
overrideName?: (name: string) => string;
|
|
89
|
+
/**
|
|
90
|
+
* Specifies whether to create a source map for the transformation.
|
|
91
|
+
* @optional
|
|
92
|
+
*/
|
|
15
93
|
sourcemap?: boolean;
|
|
16
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Transforms CSS files to include font fallbacks.
|
|
97
|
+
*
|
|
98
|
+
* @param options - The transformation options. See {@link FontaineTransformOptions}.
|
|
99
|
+
* @returns The unplugin instance.
|
|
100
|
+
*/
|
|
17
101
|
declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, boolean>;
|
|
18
102
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
name: string;
|
|
22
|
-
font: string;
|
|
23
|
-
metrics?: FontFaceMetrics;
|
|
24
|
-
[key: string]: any;
|
|
25
|
-
}
|
|
26
|
-
type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
|
|
27
|
-
declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
|
|
28
|
-
|
|
29
|
-
declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
|
|
30
|
-
declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
|
|
31
|
-
|
|
32
|
-
export { FontaineTransform, type FontaineTransformOptions, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
|
103
|
+
export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
|
104
|
+
export type { FontaineTransformOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,104 @@
|
|
|
1
|
-
import * as unplugin from 'unplugin';
|
|
2
1
|
import { Font } from '@capsizecss/unpack';
|
|
2
|
+
import * as unplugin from 'unplugin';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a fallback name based on the first font family specified in the input string.
|
|
6
|
+
* @param {string} name - The full font family string.
|
|
7
|
+
* @returns {string} - The fallback font name.
|
|
8
|
+
*/
|
|
9
|
+
declare function generateFallbackName(name: string): string;
|
|
10
|
+
interface FallbackOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The name of the fallback font.
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* The fallback font family name.
|
|
17
|
+
*/
|
|
18
|
+
font: string;
|
|
19
|
+
/**
|
|
20
|
+
* Metrics for fallback face calculations.
|
|
21
|
+
* @optional
|
|
22
|
+
*/
|
|
23
|
+
metrics?: FontFaceMetrics;
|
|
24
|
+
/**
|
|
25
|
+
* Additional properties that may be included dynamically
|
|
26
|
+
*/
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
|
|
30
|
+
/**
|
|
31
|
+
* Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
|
|
32
|
+
* @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
|
|
33
|
+
* @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
|
|
34
|
+
* @returns {string} - The full `@font-face` CSS declaration.
|
|
35
|
+
*/
|
|
36
|
+
declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
|
|
40
|
+
* @param {string} family - The name of the font family for which metrics are requested.
|
|
41
|
+
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
|
|
42
|
+
* @async
|
|
43
|
+
*/
|
|
44
|
+
declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
|
|
47
|
+
* It caches the results to optimise subsequent requests for the same source.
|
|
48
|
+
* @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
|
|
49
|
+
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
|
|
50
|
+
* @async
|
|
51
|
+
*/
|
|
52
|
+
declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
|
|
3
53
|
|
|
4
54
|
interface FontaineTransformOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Configuration options for the CSS transformation.
|
|
57
|
+
* @optional
|
|
58
|
+
*/
|
|
5
59
|
css?: {
|
|
60
|
+
/**
|
|
61
|
+
* Holds the current value of the CSS being transformed.
|
|
62
|
+
* @optional
|
|
63
|
+
*/
|
|
6
64
|
value?: string;
|
|
7
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* An array of fallback font family names to use.
|
|
68
|
+
*/
|
|
8
69
|
fallbacks: string[];
|
|
70
|
+
/**
|
|
71
|
+
* Function to resolve a given path to a valid URL or local path.
|
|
72
|
+
* This is typically used to resolve font file paths.
|
|
73
|
+
* @optional
|
|
74
|
+
*/
|
|
9
75
|
resolvePath?: (path: string) => string | URL;
|
|
76
|
+
/**
|
|
77
|
+
* A function to determine whether to skip font face generation for a given fallback name.
|
|
78
|
+
* @optional
|
|
79
|
+
*/
|
|
10
80
|
skipFontFaceGeneration?: (fallbackName: string) => boolean;
|
|
11
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Function to generate an unquoted font family name to use as a fallback.
|
|
83
|
+
* This should return a valid CSS font family name and should not include quotes.
|
|
84
|
+
* @optional
|
|
85
|
+
*/
|
|
12
86
|
fallbackName?: (name: string) => string;
|
|
13
87
|
/** @deprecated use fallbackName */
|
|
14
88
|
overrideName?: (name: string) => string;
|
|
89
|
+
/**
|
|
90
|
+
* Specifies whether to create a source map for the transformation.
|
|
91
|
+
* @optional
|
|
92
|
+
*/
|
|
15
93
|
sourcemap?: boolean;
|
|
16
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Transforms CSS files to include font fallbacks.
|
|
97
|
+
*
|
|
98
|
+
* @param options - The transformation options. See {@link FontaineTransformOptions}.
|
|
99
|
+
* @returns The unplugin instance.
|
|
100
|
+
*/
|
|
17
101
|
declare const FontaineTransform: unplugin.UnpluginInstance<FontaineTransformOptions, boolean>;
|
|
18
102
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
name: string;
|
|
22
|
-
font: string;
|
|
23
|
-
metrics?: FontFaceMetrics;
|
|
24
|
-
[key: string]: any;
|
|
25
|
-
}
|
|
26
|
-
type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
|
|
27
|
-
declare function generateFontFace(metrics: FontFaceMetrics, fallback: FallbackOptions): string;
|
|
28
|
-
|
|
29
|
-
declare function getMetricsForFamily(family: string): Promise<FontFaceMetrics | null>;
|
|
30
|
-
declare function readMetrics(_source: URL | string): Promise<FontFaceMetrics | null>;
|
|
31
|
-
|
|
32
|
-
export { FontaineTransform, type FontaineTransformOptions, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
|
103
|
+
export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
|
104
|
+
export type { FontaineTransformOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createRegExp, charIn, exactly,
|
|
3
|
-
import
|
|
4
|
-
import { parseURL } from 'ufo';
|
|
5
|
-
import { isAbsolute, join } from 'pathe';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { fromFile, fromUrl } from '@capsizecss/unpack';
|
|
1
|
+
import { parse, walk, generate } from 'css-tree';
|
|
2
|
+
import { createRegExp, charIn, exactly, anyOf } from 'magic-regexp';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
4
|
import { fontFamilyToCamelCase } from '@capsizecss/metrics';
|
|
5
|
+
import { fromFile, fromUrl } from '@capsizecss/unpack';
|
|
6
|
+
import { parseURL } from 'ufo';
|
|
7
|
+
import MagicString from 'magic-string';
|
|
8
|
+
import { isAbsolute } from 'pathe';
|
|
9
|
+
import { createUnplugin } from 'unplugin';
|
|
9
10
|
|
|
10
11
|
function toPercentage(value, fractionDigits = 4) {
|
|
11
12
|
const percentage = value * 100;
|
|
@@ -18,44 +19,91 @@ const QUOTES_RE = createRegExp(
|
|
|
18
19
|
charIn(`"'`).at.lineStart().or(charIn(`"'`).at.lineEnd()),
|
|
19
20
|
["g"]
|
|
20
21
|
);
|
|
21
|
-
const FAMILY_RE = createRegExp(
|
|
22
|
-
exactly("font-family:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("fontFamily"))
|
|
23
|
-
);
|
|
24
|
-
const SOURCE_RE = createRegExp(
|
|
25
|
-
exactly("src:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("src")),
|
|
26
|
-
["g"]
|
|
27
|
-
);
|
|
28
|
-
const URL_RE = createRegExp(
|
|
29
|
-
exactly("url(").and(charNotIn(")").times.any().as("url")).and(")"),
|
|
30
|
-
["g"]
|
|
31
|
-
);
|
|
32
22
|
const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
const genericCSSFamilies = /* @__PURE__ */ new Set([
|
|
24
|
+
"serif",
|
|
25
|
+
"sans-serif",
|
|
26
|
+
"monospace",
|
|
27
|
+
"cursive",
|
|
28
|
+
"fantasy",
|
|
29
|
+
"system-ui",
|
|
30
|
+
"ui-serif",
|
|
31
|
+
"ui-sans-serif",
|
|
32
|
+
"ui-monospace",
|
|
33
|
+
"ui-rounded",
|
|
34
|
+
"emoji",
|
|
35
|
+
"math",
|
|
36
|
+
"fangsong"
|
|
37
|
+
]);
|
|
38
|
+
const fontProperties = /* @__PURE__ */ new Set(["font-weight", "font-style", "font-stretch"]);
|
|
39
|
+
function parseFontFace(css) {
|
|
40
|
+
const families = [];
|
|
41
|
+
const ast = typeof css === "string" ? parse(css, { positions: true }) : css;
|
|
42
|
+
walk(ast, {
|
|
43
|
+
visit: "Atrule",
|
|
44
|
+
enter(node) {
|
|
45
|
+
if (node.name !== "font-face")
|
|
46
|
+
return;
|
|
47
|
+
let family;
|
|
48
|
+
const sources = [];
|
|
49
|
+
const properties = {};
|
|
50
|
+
if (node.block) {
|
|
51
|
+
walk(node.block, {
|
|
52
|
+
visit: "Declaration",
|
|
53
|
+
enter(declaration) {
|
|
54
|
+
if (declaration.property === "font-family" && declaration.value.type === "Value") {
|
|
55
|
+
for (const child of declaration.value.children) {
|
|
56
|
+
if (child.type === "String") {
|
|
57
|
+
family = withoutQuotes(child.value);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if (child.type === "Identifier" && !genericCSSFamilies.has(child.name)) {
|
|
61
|
+
family = child.name;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (fontProperties.has(declaration.property)) {
|
|
67
|
+
if (declaration.value.type === "Value") {
|
|
68
|
+
for (const child of declaration.value.children) {
|
|
69
|
+
const hasValue = !!properties[declaration.property];
|
|
70
|
+
properties[declaration.property] ||= "";
|
|
71
|
+
properties[declaration.property] += (hasValue ? " " : "") + generate(child);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (declaration.property === "src") {
|
|
76
|
+
walk(declaration.value, {
|
|
77
|
+
visit: "Url",
|
|
78
|
+
enter(urlNode) {
|
|
79
|
+
const source = withoutQuotes(urlNode.value);
|
|
80
|
+
if (source) {
|
|
81
|
+
sources.push(source);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (family) {
|
|
90
|
+
for (const source of sources) {
|
|
91
|
+
families.push({ index: node.loc.start.offset, family, source, properties });
|
|
92
|
+
}
|
|
93
|
+
if (!sources.length) {
|
|
94
|
+
families.push({ index: node.loc.start.offset, family, properties });
|
|
95
|
+
}
|
|
43
96
|
}
|
|
44
97
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
98
|
+
});
|
|
99
|
+
return families;
|
|
47
100
|
}
|
|
48
101
|
function generateFallbackName(name) {
|
|
49
102
|
const firstFamily = withoutQuotes(name.split(",").shift());
|
|
50
103
|
return `${firstFamily} fallback`;
|
|
51
104
|
}
|
|
52
105
|
function generateFontFace(metrics, fallback) {
|
|
53
|
-
const {
|
|
54
|
-
name: fallbackName,
|
|
55
|
-
font: fallbackFontName,
|
|
56
|
-
metrics: fallbackMetrics,
|
|
57
|
-
...properties
|
|
58
|
-
} = fallback;
|
|
106
|
+
const { name: fallbackName, font: fallbackFontName, metrics: fallbackMetrics, ...properties } = fallback;
|
|
59
107
|
const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
|
|
60
108
|
const fallbackFontXAvgRatio = fallbackMetrics ? fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm : 1;
|
|
61
109
|
const sizeAdjust = fallbackMetrics && preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
|
|
@@ -79,13 +127,7 @@ ${toCSS(declaration)}
|
|
|
79
127
|
}
|
|
80
128
|
|
|
81
129
|
const metricCache = {};
|
|
82
|
-
function filterRequiredMetrics({
|
|
83
|
-
ascent,
|
|
84
|
-
descent,
|
|
85
|
-
lineGap,
|
|
86
|
-
unitsPerEm,
|
|
87
|
-
xWidthAvg
|
|
88
|
-
}) {
|
|
130
|
+
function filterRequiredMetrics({ ascent, descent, lineGap, unitsPerEm, xWidthAvg }) {
|
|
89
131
|
return {
|
|
90
132
|
ascent,
|
|
91
133
|
descent,
|
|
@@ -102,6 +144,10 @@ async function getMetricsForFamily(family) {
|
|
|
102
144
|
const name = fontFamilyToCamelCase(family);
|
|
103
145
|
const { entireMetricsCollection } = await import('@capsizecss/metrics/entireMetricsCollection');
|
|
104
146
|
const metrics = entireMetricsCollection[name];
|
|
147
|
+
if (!("descent" in metrics)) {
|
|
148
|
+
metricCache[family] = null;
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
105
151
|
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
106
152
|
metricCache[family] = filteredMetrics;
|
|
107
153
|
return filteredMetrics;
|
|
@@ -110,110 +156,113 @@ async function getMetricsForFamily(family) {
|
|
|
110
156
|
return null;
|
|
111
157
|
}
|
|
112
158
|
}
|
|
159
|
+
const urlRequestCache = /* @__PURE__ */ new Map();
|
|
113
160
|
async function readMetrics(_source) {
|
|
114
161
|
const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
|
|
115
162
|
if (source in metricCache)
|
|
116
|
-
return
|
|
163
|
+
return metricCache[source];
|
|
117
164
|
const { protocol } = parseURL(source);
|
|
118
165
|
if (!protocol)
|
|
119
166
|
return null;
|
|
120
|
-
|
|
167
|
+
let metrics;
|
|
168
|
+
if (protocol === "file:") {
|
|
169
|
+
metrics = await fromFile(fileURLToPath(source));
|
|
170
|
+
} else {
|
|
171
|
+
if (urlRequestCache.has(source)) {
|
|
172
|
+
metrics = await urlRequestCache.get(source);
|
|
173
|
+
} else {
|
|
174
|
+
const requestPromise = fromUrl(source);
|
|
175
|
+
urlRequestCache.set(source, requestPromise);
|
|
176
|
+
metrics = await requestPromise;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
121
179
|
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
122
180
|
metricCache[source] = filteredMetrics;
|
|
123
181
|
return filteredMetrics;
|
|
124
182
|
}
|
|
125
183
|
|
|
126
184
|
const supportedExtensions = ["woff2", "woff", "ttf"];
|
|
127
|
-
const FONT_FACE_RE = createRegExp(
|
|
128
|
-
exactly("@font-face").and(whitespace.times.any()).and("{").and(charNotIn("}").times.any()).and("}"),
|
|
129
|
-
["g"]
|
|
130
|
-
);
|
|
131
|
-
const FONT_FAMILY_RE = createRegExp(
|
|
132
|
-
charNotIn(";}").times.any().as("family").after(exactly("font-family:").and(whitespace.times.any())).before(charIn(";}").or(exactly("").at.lineEnd())),
|
|
133
|
-
["g"]
|
|
134
|
-
);
|
|
135
185
|
const CSS_RE = createRegExp(
|
|
136
186
|
exactly(".").and(anyOf("sass", "css", "scss")).at.lineEnd()
|
|
137
187
|
);
|
|
138
|
-
const
|
|
139
|
-
(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
188
|
+
const RELATIVE_RE = createRegExp(
|
|
189
|
+
exactly(".").or("..").and(anyOf("/", "\\")).at.lineStart()
|
|
190
|
+
);
|
|
191
|
+
const FontaineTransform = createUnplugin((options) => {
|
|
192
|
+
const cssContext = options.css = options.css || {};
|
|
193
|
+
cssContext.value = "";
|
|
194
|
+
const resolvePath = options.resolvePath || ((id) => id);
|
|
195
|
+
const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
|
|
196
|
+
const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
|
|
197
|
+
function readMetricsFromId(path, importer) {
|
|
198
|
+
const resolvedPath = isAbsolute(importer) && RELATIVE_RE.test(path) ? new URL(path, pathToFileURL(importer)) : resolvePath(path);
|
|
199
|
+
return readMetrics(resolvedPath);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
name: "fontaine-transform",
|
|
203
|
+
enforce: "pre",
|
|
204
|
+
transformInclude(id) {
|
|
205
|
+
const { pathname } = parseURL(id);
|
|
206
|
+
return CSS_RE.test(pathname) || CSS_RE.test(id);
|
|
207
|
+
},
|
|
208
|
+
async transform(code, id) {
|
|
209
|
+
const s = new MagicString(code);
|
|
210
|
+
const ast = parse(code, { positions: true });
|
|
211
|
+
for (const { family, source, index, properties } of parseFontFace(ast)) {
|
|
212
|
+
if (!supportedExtensions.some((e) => source?.endsWith(e)))
|
|
213
|
+
continue;
|
|
214
|
+
if (skipFontFaceGeneration(fallbackName(family)))
|
|
215
|
+
continue;
|
|
216
|
+
const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
|
|
217
|
+
if (!metrics)
|
|
218
|
+
continue;
|
|
219
|
+
for (let i = options.fallbacks.length - 1; i >= 0; i--) {
|
|
220
|
+
const fallback = options.fallbacks[i];
|
|
221
|
+
const fallbackMetrics = await getMetricsForFamily(fallback);
|
|
222
|
+
if (!fallbackMetrics)
|
|
162
223
|
continue;
|
|
163
|
-
|
|
164
|
-
|
|
224
|
+
const fontFace = generateFontFace(metrics, {
|
|
225
|
+
name: fallbackName(family),
|
|
226
|
+
font: fallback,
|
|
227
|
+
metrics: fallbackMetrics,
|
|
228
|
+
...properties
|
|
229
|
+
});
|
|
230
|
+
cssContext.value += fontFace;
|
|
231
|
+
s.appendLeft(index, fontFace);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
walk(ast, {
|
|
235
|
+
visit: "Declaration",
|
|
236
|
+
enter(node) {
|
|
237
|
+
if (node.property !== "font-family")
|
|
238
|
+
return;
|
|
239
|
+
if (this.atrule && this.atrule.name === "font-face")
|
|
240
|
+
return;
|
|
241
|
+
if (node.value.type !== "Value")
|
|
242
|
+
return;
|
|
243
|
+
for (const child of node.value.children) {
|
|
244
|
+
let family;
|
|
245
|
+
if (child.type === "String") {
|
|
246
|
+
family = withoutQuotes(child.value);
|
|
247
|
+
} else if (child.type === "Identifier" && child.name !== "inherit") {
|
|
248
|
+
family = child.name;
|
|
249
|
+
}
|
|
165
250
|
if (!family)
|
|
166
251
|
continue;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (skipFontFaceGeneration(fallbackName(family)))
|
|
170
|
-
continue;
|
|
171
|
-
const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
|
|
172
|
-
if (!metrics)
|
|
173
|
-
continue;
|
|
174
|
-
for (let i = options.fallbacks.length - 1; i >= 0; i--) {
|
|
175
|
-
const fallback = options.fallbacks[i];
|
|
176
|
-
const fallbackMetrics = await getMetricsForFamily(fallback);
|
|
177
|
-
if (!fallbackMetrics)
|
|
178
|
-
continue;
|
|
179
|
-
const fontFace = generateFontFace(metrics, {
|
|
180
|
-
name: fallbackName(family),
|
|
181
|
-
font: fallback,
|
|
182
|
-
metrics: fallbackMetrics
|
|
183
|
-
});
|
|
184
|
-
cssContext.value += fontFace;
|
|
185
|
-
s.appendLeft(match.index, fontFace);
|
|
186
|
-
}
|
|
252
|
+
s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
|
|
253
|
+
return;
|
|
187
254
|
}
|
|
188
255
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!families.length || families[0] === "inherit")
|
|
197
|
-
continue;
|
|
198
|
-
s.overwrite(
|
|
199
|
-
index,
|
|
200
|
-
index + matchContent.length,
|
|
201
|
-
` ${[
|
|
202
|
-
families[0],
|
|
203
|
-
`"${generateFallbackName(families[0])}"`,
|
|
204
|
-
...families.slice(1)
|
|
205
|
-
].join(", ")}`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
if (s.hasChanged()) {
|
|
209
|
-
return {
|
|
210
|
-
code: s.toString(),
|
|
211
|
-
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
212
|
-
};
|
|
213
|
-
}
|
|
256
|
+
});
|
|
257
|
+
if (s.hasChanged()) {
|
|
258
|
+
return {
|
|
259
|
+
code: s.toString(),
|
|
260
|
+
/* v8 ignore next 3 */
|
|
261
|
+
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
262
|
+
};
|
|
214
263
|
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
});
|
|
218
267
|
|
|
219
268
|
export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };
|
package/package.json
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontaine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"packageManager": "pnpm@
|
|
4
|
+
"version": "0.6.0",
|
|
5
|
+
"packageManager": "pnpm@10.9.0",
|
|
6
6
|
"description": "Automatic font fallback based on font metrics",
|
|
7
7
|
"author": {
|
|
8
|
-
"name": "Daniel Roe
|
|
8
|
+
"name": "Daniel Roe",
|
|
9
|
+
"email": "daniel@roe.dev",
|
|
9
10
|
"url": "https://github.com/danielroe"
|
|
10
11
|
},
|
|
11
12
|
"license": "MIT",
|
|
12
|
-
"repository":
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/unjs/fontaine.git"
|
|
16
|
+
},
|
|
13
17
|
"keywords": [
|
|
14
18
|
"fonts",
|
|
15
19
|
"cls",
|
|
@@ -34,43 +38,42 @@
|
|
|
34
38
|
"dev": "vitest",
|
|
35
39
|
"demo": "vite dev playground",
|
|
36
40
|
"demo:dev": "pnpm demo --config test/vite.config.mjs",
|
|
37
|
-
"lint": "eslint
|
|
41
|
+
"lint": "eslint .",
|
|
38
42
|
"prepare": "simple-git-hooks install && pnpm build",
|
|
39
43
|
"prepublishOnly": "pnpm lint && pnpm test",
|
|
40
44
|
"release": "pnpm test && bumpp && npm publish",
|
|
41
45
|
"test": "vitest run"
|
|
42
46
|
},
|
|
43
47
|
"dependencies": {
|
|
44
|
-
"@capsizecss/metrics": "^
|
|
45
|
-
"@capsizecss/unpack": "^2.0
|
|
46
|
-
"
|
|
47
|
-
"magic-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
48
|
+
"@capsizecss/metrics": "^3.5.0",
|
|
49
|
+
"@capsizecss/unpack": "^2.4.0",
|
|
50
|
+
"css-tree": "^3.1.0",
|
|
51
|
+
"magic-regexp": "^0.10.0",
|
|
52
|
+
"magic-string": "^0.30.17",
|
|
53
|
+
"pathe": "^2.0.3",
|
|
54
|
+
"ufo": "^1.6.1",
|
|
55
|
+
"unplugin": "^2.3.2"
|
|
51
56
|
},
|
|
52
57
|
"devDependencies": {
|
|
53
|
-
"@antfu/eslint-config": "
|
|
58
|
+
"@antfu/eslint-config": "4.12.0",
|
|
54
59
|
"@nuxtjs/eslint-config-typescript": "latest",
|
|
55
|
-
"@types/
|
|
60
|
+
"@types/css-tree": "^2.3.10",
|
|
61
|
+
"@types/node": "22.14.1",
|
|
56
62
|
"@types/serve-handler": "6.1.4",
|
|
57
|
-
"@typescript-eslint/eslint-plugin": "
|
|
58
|
-
"@typescript-eslint/parser": "
|
|
59
|
-
"@vitest/coverage-v8": "
|
|
60
|
-
"bumpp": "
|
|
61
|
-
"eslint": "
|
|
62
|
-
"
|
|
63
|
-
"eslint-plugin-prettier": "latest",
|
|
64
|
-
"execa": "8.0.1",
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "8.31.0",
|
|
64
|
+
"@typescript-eslint/parser": "8.31.0",
|
|
65
|
+
"@vitest/coverage-v8": "3.1.2",
|
|
66
|
+
"bumpp": "10.1.0",
|
|
67
|
+
"eslint": "9.25.1",
|
|
68
|
+
"execa": "9.5.2",
|
|
65
69
|
"get-port-please": "3.1.2",
|
|
66
|
-
"lint-staged": "15.
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"typescript": "5.4.2",
|
|
70
|
+
"lint-staged": "15.5.1",
|
|
71
|
+
"serve-handler": "6.1.6",
|
|
72
|
+
"simple-git-hooks": "2.12.1",
|
|
73
|
+
"typescript": "5.8.3",
|
|
71
74
|
"unbuild": "latest",
|
|
72
|
-
"vite": "
|
|
73
|
-
"vitest": "
|
|
75
|
+
"vite": "6.3.2",
|
|
76
|
+
"vitest": "3.1.2"
|
|
74
77
|
},
|
|
75
78
|
"resolutions": {
|
|
76
79
|
"fontaine": "link:."
|
|
@@ -80,7 +83,7 @@
|
|
|
80
83
|
},
|
|
81
84
|
"lint-staged": {
|
|
82
85
|
"*.{js,ts,mjs,cjs,json,.*rc}": [
|
|
83
|
-
"
|
|
86
|
+
"npx eslint --fix"
|
|
84
87
|
]
|
|
85
88
|
}
|
|
86
89
|
}
|