fontaine 0.8.0 → 0.8.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 +32 -4
- package/dist/capsize-font-metrics.mjs +1 -1
- package/dist/fallbacks-Cj4LUWhi.d.cts +34 -0
- package/dist/fallbacks-Cj4LUWhi.d.mts +34 -0
- package/dist/index.cjs +138 -299
- package/dist/index.d.cts +65 -98
- package/dist/index.d.mts +64 -98
- package/dist/index.mjs +120 -277
- package/dist/metrics-ChAPPk2j.cjs +425 -0
- package/dist/metrics-DgPzfV2U.mjs +354 -0
- package/dist/postcss.cjs +91 -0
- package/dist/postcss.d.cts +52 -0
- package/dist/postcss.d.mts +52 -0
- package/dist/postcss.mjs +91 -0
- package/package.json +34 -15
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/fallbacks.d.ts
|
|
2
|
+
type FontCategory = "sans-serif" | "serif" | "monospace" | "display" | "handwriting" | "cursive" | "fantasy" | "system-ui" | "ui-serif" | "ui-sans-serif" | "ui-monospace" | "ui-rounded" | "emoji" | "math" | "fangsong";
|
|
3
|
+
/**
|
|
4
|
+
* Default fallback font stacks for each font category.
|
|
5
|
+
* These are system fonts that work across different platforms.
|
|
6
|
+
*/
|
|
7
|
+
declare const DEFAULT_CATEGORY_FALLBACKS: Partial<Record<FontCategory, string[]>>;
|
|
8
|
+
interface ResolveCategoryFallbacksOptions {
|
|
9
|
+
/** Font family name to resolve fallbacks for */
|
|
10
|
+
fontFamily: string;
|
|
11
|
+
/** Global fallbacks (array) or per-family fallbacks (object). Array overrides all category-based resolution. */
|
|
12
|
+
fallbacks: string[] | Record<string, string[]>;
|
|
13
|
+
/** Font metrics containing category information */
|
|
14
|
+
metrics?: {
|
|
15
|
+
category?: string;
|
|
16
|
+
} | null;
|
|
17
|
+
/** User-provided category fallback overrides */
|
|
18
|
+
categoryFallbacks?: Partial<Record<FontCategory, string[]>>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the appropriate fallback fonts for a given font family.
|
|
22
|
+
*
|
|
23
|
+
* Resolution order:
|
|
24
|
+
* 1. If fallbacks is an array, use it as a global override
|
|
25
|
+
* 2. If fallbacks is an object with the font family key, use that override
|
|
26
|
+
* 3. If metrics contain a category, use the category-based fallbacks
|
|
27
|
+
* 4. Default to sans-serif category fallbacks
|
|
28
|
+
*
|
|
29
|
+
* @param options - Configuration for fallback resolution
|
|
30
|
+
* @returns Array of fallback font family names
|
|
31
|
+
*/
|
|
32
|
+
declare function resolveCategoryFallbacks(options: ResolveCategoryFallbacksOptions): string[];
|
|
33
|
+
//#endregion
|
|
34
|
+
export { resolveCategoryFallbacks as i, FontCategory as n, ResolveCategoryFallbacksOptions as r, DEFAULT_CATEGORY_FALLBACKS as t };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/fallbacks.d.ts
|
|
2
|
+
type FontCategory = "sans-serif" | "serif" | "monospace" | "display" | "handwriting" | "cursive" | "fantasy" | "system-ui" | "ui-serif" | "ui-sans-serif" | "ui-monospace" | "ui-rounded" | "emoji" | "math" | "fangsong";
|
|
3
|
+
/**
|
|
4
|
+
* Default fallback font stacks for each font category.
|
|
5
|
+
* These are system fonts that work across different platforms.
|
|
6
|
+
*/
|
|
7
|
+
declare const DEFAULT_CATEGORY_FALLBACKS: Partial<Record<FontCategory, string[]>>;
|
|
8
|
+
interface ResolveCategoryFallbacksOptions {
|
|
9
|
+
/** Font family name to resolve fallbacks for */
|
|
10
|
+
fontFamily: string;
|
|
11
|
+
/** Global fallbacks (array) or per-family fallbacks (object). Array overrides all category-based resolution. */
|
|
12
|
+
fallbacks: string[] | Record<string, string[]>;
|
|
13
|
+
/** Font metrics containing category information */
|
|
14
|
+
metrics?: {
|
|
15
|
+
category?: string;
|
|
16
|
+
} | null;
|
|
17
|
+
/** User-provided category fallback overrides */
|
|
18
|
+
categoryFallbacks?: Partial<Record<FontCategory, string[]>>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the appropriate fallback fonts for a given font family.
|
|
22
|
+
*
|
|
23
|
+
* Resolution order:
|
|
24
|
+
* 1. If fallbacks is an array, use it as a global override
|
|
25
|
+
* 2. If fallbacks is an object with the font family key, use that override
|
|
26
|
+
* 3. If metrics contain a category, use the category-based fallbacks
|
|
27
|
+
* 4. Default to sans-serif category fallbacks
|
|
28
|
+
*
|
|
29
|
+
* @param options - Configuration for fallback resolution
|
|
30
|
+
* @returns Array of fallback font family names
|
|
31
|
+
*/
|
|
32
|
+
declare function resolveCategoryFallbacks(options: ResolveCategoryFallbacksOptions): string[];
|
|
33
|
+
//#endregion
|
|
34
|
+
export { resolveCategoryFallbacks as i, FontCategory as n, ResolveCategoryFallbacksOptions as r, DEFAULT_CATEGORY_FALLBACKS as t };
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -6,306 +7,110 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
9
|
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
19
16
|
}
|
|
20
17
|
return to;
|
|
21
18
|
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
23
20
|
value: mod,
|
|
24
21
|
enumerable: true
|
|
25
22
|
}) : target, mod));
|
|
26
|
-
|
|
27
23
|
//#endregion
|
|
24
|
+
const require_metrics = require("./metrics-ChAPPk2j.cjs");
|
|
28
25
|
let css_tree = require("css-tree");
|
|
29
26
|
let magic_regexp = require("magic-regexp");
|
|
27
|
+
let pathe = require("pathe");
|
|
30
28
|
let node_url = require("node:url");
|
|
31
|
-
let
|
|
32
|
-
let
|
|
33
|
-
let ufo = require("ufo");
|
|
29
|
+
let node_fs_promises = require("node:fs/promises");
|
|
30
|
+
let node_module = require("node:module");
|
|
34
31
|
let magic_string = require("magic-string");
|
|
35
|
-
magic_string = __toESM(magic_string);
|
|
36
|
-
let pathe = require("pathe");
|
|
32
|
+
magic_string = __toESM(magic_string, 1);
|
|
37
33
|
let unplugin = require("unplugin");
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
|
|
48
|
-
const genericCSSFamilies = new Set([
|
|
49
|
-
"serif",
|
|
50
|
-
"sans-serif",
|
|
51
|
-
"monospace",
|
|
52
|
-
"cursive",
|
|
53
|
-
"fantasy",
|
|
54
|
-
"system-ui",
|
|
55
|
-
"ui-serif",
|
|
56
|
-
"ui-sans-serif",
|
|
57
|
-
"ui-monospace",
|
|
58
|
-
"ui-rounded",
|
|
59
|
-
"emoji",
|
|
60
|
-
"math",
|
|
61
|
-
"fangsong"
|
|
62
|
-
]);
|
|
63
|
-
const fontProperties = new Set([
|
|
64
|
-
"font-weight",
|
|
65
|
-
"font-style",
|
|
66
|
-
"font-stretch"
|
|
67
|
-
]);
|
|
34
|
+
//#region src/transform.ts
|
|
35
|
+
const supportedExtensions = [
|
|
36
|
+
"woff2",
|
|
37
|
+
"woff",
|
|
38
|
+
"ttf"
|
|
39
|
+
];
|
|
40
|
+
const CSS_RE = (0, magic_regexp.createRegExp)((0, magic_regexp.exactly)(".").and((0, magic_regexp.anyOf)("sass", "css", "scss")).and((0, magic_regexp.exactly)("?").and((0, magic_regexp.oneOrMore)(magic_regexp.char)).optionally()).at.lineEnd());
|
|
41
|
+
const RELATIVE_RE = (0, magic_regexp.createRegExp)((0, magic_regexp.exactly)(".").or("..").and((0, magic_regexp.anyOf)("/", "\\")).at.lineStart());
|
|
42
|
+
const SCSS_RE = (0, magic_regexp.createRegExp)((0, magic_regexp.exactly)(".").and((0, magic_regexp.anyOf)("sass", "scss")).and((0, magic_regexp.exactly)("?").and((0, magic_regexp.oneOrMore)(magic_regexp.char)).optionally()).at.lineEnd());
|
|
68
43
|
/**
|
|
69
|
-
*
|
|
44
|
+
* Replaces `//` line comments with spaces, preserving offsets.
|
|
70
45
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
46
|
+
* css-tree has no SCSS mode and bails on the first `//` comment, parsing the
|
|
47
|
+
* remainder of the file as a single `Raw` node, which hides every at-rule that
|
|
48
|
+
* follows it.
|
|
73
49
|
*/
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
50
|
+
function blankLineComments(code) {
|
|
51
|
+
let result = "";
|
|
52
|
+
let index = 0;
|
|
53
|
+
while (index < code.length) {
|
|
54
|
+
const char = code[index];
|
|
55
|
+
if (char === "\"" || char === "'") {
|
|
56
|
+
const end = code.indexOf(char, index + 1);
|
|
57
|
+
const stop = end === -1 ? code.length : end + 1;
|
|
58
|
+
result += code.slice(index, stop);
|
|
59
|
+
index = stop;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (char === "/" && code[index + 1] === "*") {
|
|
63
|
+
const end = code.indexOf("*/", index + 2);
|
|
64
|
+
const stop = end === -1 ? code.length : end + 2;
|
|
65
|
+
result += code.slice(index, stop);
|
|
66
|
+
index = stop;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if ((char === "u" || char === "U") && /^url\(/i.test(code.slice(index, index + 4))) {
|
|
70
|
+
const end = code.indexOf(")", index);
|
|
71
|
+
const stop = end === -1 ? code.length : end + 1;
|
|
72
|
+
result += code.slice(index, stop);
|
|
73
|
+
index = stop;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (char === "/" && code[index + 1] === "/") {
|
|
77
|
+
const newline = code.indexOf("\n", index);
|
|
78
|
+
const stop = newline === -1 ? code.length : newline;
|
|
79
|
+
result += " ".repeat(stop - index);
|
|
80
|
+
index = stop;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
result += char;
|
|
84
|
+
index++;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function extractCssImports(ast) {
|
|
89
|
+
const imports = [];
|
|
90
|
+
(0, css_tree.walk)(ast, {
|
|
77
91
|
visit: "Atrule",
|
|
78
92
|
enter(node) {
|
|
79
|
-
if (node.name !== "
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (child.type === "String") {
|
|
88
|
-
family = withoutQuotes(child.value);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
if (child.type === "Identifier" && !genericCSSFamilies.has(child.name)) {
|
|
92
|
-
family = child.name;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (fontProperties.has(declaration.property)) {
|
|
97
|
-
if (declaration.value.type === "Value") for (const child of declaration.value.children) {
|
|
98
|
-
const hasValue = !!properties[declaration.property];
|
|
99
|
-
properties[declaration.property] ||= "";
|
|
100
|
-
properties[declaration.property] += (hasValue ? " " : "") + (0, css_tree.generate)(child);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (declaration.property === "src") (0, css_tree.walk)(declaration.value, {
|
|
104
|
-
visit: "Url",
|
|
105
|
-
enter(urlNode) {
|
|
106
|
-
const source = withoutQuotes(urlNode.value);
|
|
107
|
-
if (source) sources.push(source);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
}
|
|
93
|
+
if (node.name !== "import" || node.prelude?.type !== "AtrulePrelude") return;
|
|
94
|
+
let specifier;
|
|
95
|
+
let conditional = false;
|
|
96
|
+
for (const child of node.prelude.children) if (child.type === "String" || child.type === "Url") specifier ||= require_metrics.withoutQuotes(child.value);
|
|
97
|
+
else if (child.type === "MediaQueryList" || child.type === "Function" && child.name.toLowerCase() === "supports") conditional = true;
|
|
98
|
+
if (specifier && !conditional && !specifier.startsWith("http:") && !specifier.startsWith("https:") && !specifier.startsWith("data:")) imports.push({
|
|
99
|
+
specifier: specifier.replace(/[?#].*$/, ""),
|
|
100
|
+
end: node.loc.end.offset
|
|
111
101
|
});
|
|
112
|
-
if (family) {
|
|
113
|
-
for (const source of sources) families.push({
|
|
114
|
-
index: node.loc.start.offset,
|
|
115
|
-
family,
|
|
116
|
-
source,
|
|
117
|
-
properties
|
|
118
|
-
});
|
|
119
|
-
if (!sources.length) families.push({
|
|
120
|
-
index: node.loc.start.offset,
|
|
121
|
-
family,
|
|
122
|
-
properties
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
102
|
}
|
|
126
103
|
});
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Generates a fallback name based on the first font family specified in the input string.
|
|
131
|
-
* @param {string} name - The full font family string.
|
|
132
|
-
* @returns {string} - The fallback font name.
|
|
133
|
-
*/
|
|
134
|
-
function generateFallbackName(name) {
|
|
135
|
-
return `${withoutQuotes(name.split(",").shift())} fallback`;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Generates a CSS `@font-face' declaration for a font, taking fallback and resizing into account.
|
|
139
|
-
* @param {FontFaceMetrics} metrics - The metrics of the preferred font. See {@link FontFaceMetrics}.
|
|
140
|
-
* @param {FallbackOptions} fallback - The fallback options, including name, font and optional metrics. See {@link FallbackOptions}.
|
|
141
|
-
* @returns {string} - The full `@font-face` CSS declaration.
|
|
142
|
-
*/
|
|
143
|
-
function generateFontFace(metrics, fallback) {
|
|
144
|
-
const { name: fallbackName, font: fallbackFontName, metrics: fallbackMetrics, ...properties } = fallback;
|
|
145
|
-
const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
|
|
146
|
-
const fallbackFontXAvgRatio = fallbackMetrics ? fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm : 1;
|
|
147
|
-
const sizeAdjust = fallbackMetrics && preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
|
|
148
|
-
const adjustedEmSquare = metrics.unitsPerEm * sizeAdjust;
|
|
149
|
-
const ascentOverride = metrics.ascent / adjustedEmSquare;
|
|
150
|
-
const descentOverride = Math.abs(metrics.descent) / adjustedEmSquare;
|
|
151
|
-
const lineGapOverride = metrics.lineGap / adjustedEmSquare;
|
|
152
|
-
return `@font-face {\n${toCSS({
|
|
153
|
-
"font-family": JSON.stringify(fallbackName),
|
|
154
|
-
"src": `local(${JSON.stringify(fallbackFontName)})`,
|
|
155
|
-
"size-adjust": toPercentage(sizeAdjust),
|
|
156
|
-
"ascent-override": toPercentage(ascentOverride),
|
|
157
|
-
"descent-override": toPercentage(descentOverride),
|
|
158
|
-
"line-gap-override": toPercentage(lineGapOverride),
|
|
159
|
-
...properties
|
|
160
|
-
})}\n}\n`;
|
|
104
|
+
return imports;
|
|
161
105
|
}
|
|
162
|
-
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/fallbacks.ts
|
|
165
|
-
/**
|
|
166
|
-
* Default fallback font stacks for each font category.
|
|
167
|
-
* These are system fonts that work across different platforms.
|
|
168
|
-
*/
|
|
169
|
-
const DEFAULT_CATEGORY_FALLBACKS = {
|
|
170
|
-
"sans-serif": [
|
|
171
|
-
"BlinkMacSystemFont",
|
|
172
|
-
"Segoe UI",
|
|
173
|
-
"Helvetica Neue",
|
|
174
|
-
"Arial",
|
|
175
|
-
"Noto Sans"
|
|
176
|
-
],
|
|
177
|
-
"serif": [
|
|
178
|
-
"Times New Roman",
|
|
179
|
-
"Georgia",
|
|
180
|
-
"Noto Serif"
|
|
181
|
-
],
|
|
182
|
-
"monospace": [
|
|
183
|
-
"Courier New",
|
|
184
|
-
"Roboto Mono",
|
|
185
|
-
"Noto Sans Mono"
|
|
186
|
-
],
|
|
187
|
-
"display": [
|
|
188
|
-
"BlinkMacSystemFont",
|
|
189
|
-
"Segoe UI",
|
|
190
|
-
"Helvetica Neue",
|
|
191
|
-
"Arial",
|
|
192
|
-
"Noto Sans"
|
|
193
|
-
],
|
|
194
|
-
"handwriting": [
|
|
195
|
-
"BlinkMacSystemFont",
|
|
196
|
-
"Segoe UI",
|
|
197
|
-
"Helvetica Neue",
|
|
198
|
-
"Arial",
|
|
199
|
-
"Noto Sans"
|
|
200
|
-
]
|
|
201
|
-
};
|
|
202
|
-
/**
|
|
203
|
-
* Resolves the appropriate fallback fonts for a given font family.
|
|
204
|
-
*
|
|
205
|
-
* Resolution order:
|
|
206
|
-
* 1. If fallbacks is an array, use it as a global override
|
|
207
|
-
* 2. If fallbacks is an object with the font family key, use that override
|
|
208
|
-
* 3. If metrics contain a category, use the category-based fallbacks
|
|
209
|
-
* 4. Default to sans-serif category fallbacks
|
|
210
|
-
*
|
|
211
|
-
* @param options - Configuration for fallback resolution
|
|
212
|
-
* @returns Array of fallback font family names
|
|
213
|
-
*/
|
|
214
|
-
function resolveCategoryFallbacks(options) {
|
|
215
|
-
const { fontFamily, fallbacks, metrics, categoryFallbacks } = options;
|
|
216
|
-
const mergedCategoryFallbacks = { ...DEFAULT_CATEGORY_FALLBACKS };
|
|
217
|
-
if (categoryFallbacks) for (const category in categoryFallbacks) {
|
|
218
|
-
const categoryKey = category;
|
|
219
|
-
const categoryFallbacksList = categoryFallbacks[categoryKey];
|
|
220
|
-
if (categoryFallbacksList) mergedCategoryFallbacks[categoryKey] = categoryFallbacksList;
|
|
221
|
-
}
|
|
222
|
-
if (Array.isArray(fallbacks)) return fallbacks;
|
|
223
|
-
const familyFallback = fallbacks[fontFamily];
|
|
224
|
-
if (familyFallback) return familyFallback;
|
|
225
|
-
if (metrics?.category) {
|
|
226
|
-
const categoryFallback = mergedCategoryFallbacks[metrics.category];
|
|
227
|
-
if (categoryFallback) return categoryFallback;
|
|
228
|
-
}
|
|
229
|
-
return mergedCategoryFallbacks["sans-serif"];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region src/metrics.ts
|
|
234
|
-
const metricCache = {};
|
|
235
|
-
function filterRequiredMetrics(font) {
|
|
236
|
-
return {
|
|
237
|
-
ascent: font.ascent,
|
|
238
|
-
descent: font.descent,
|
|
239
|
-
lineGap: font.lineGap,
|
|
240
|
-
unitsPerEm: font.unitsPerEm,
|
|
241
|
-
xWidthAvg: font.xWidthAvg,
|
|
242
|
-
category: font.category
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Retrieves the font metrics for a given font family from the metrics collection. Uses caching to avoid redundant calculations.
|
|
247
|
-
* @param {string} family - The name of the font family for which metrics are requested.
|
|
248
|
-
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves with the filtered font metrics or null if not found. See {@link FontFaceMetrics}.
|
|
249
|
-
* @async
|
|
250
|
-
*/
|
|
251
|
-
async function getMetricsForFamily(family) {
|
|
252
|
-
family = withoutQuotes(family);
|
|
253
|
-
if (family in metricCache) return metricCache[family] ?? null;
|
|
106
|
+
function resolveCssImport(specifier, importer) {
|
|
254
107
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const metrics = entireMetricsCollection[name];
|
|
258
|
-
/* v8 ignore next 4 */
|
|
259
|
-
if (!("descent" in metrics)) {
|
|
260
|
-
metricCache[family] = null;
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
264
|
-
metricCache[family] = filteredMetrics;
|
|
265
|
-
return filteredMetrics;
|
|
108
|
+
if (RELATIVE_RE.test(specifier) || (0, pathe.isAbsolute)(specifier)) return (0, node_url.fileURLToPath)(new URL(specifier, (0, node_url.pathToFileURL)(importer)));
|
|
109
|
+
return (0, node_module.createRequire)(importer).resolve(specifier.replace(/^~/, ""));
|
|
266
110
|
} catch {
|
|
267
|
-
|
|
268
|
-
return null;
|
|
111
|
+
return;
|
|
269
112
|
}
|
|
270
113
|
}
|
|
271
|
-
const urlRequestCache = /* @__PURE__ */ new Map();
|
|
272
|
-
/**
|
|
273
|
-
* Reads font metrics from a specified source URL or file path. This function supports both local files and remote URLs.
|
|
274
|
-
* It caches the results to optimise subsequent requests for the same source.
|
|
275
|
-
* @param {URL | string} _source - The source URL or local file path from which to read the font metrics.
|
|
276
|
-
* @returns {Promise<FontFaceMetrics | null>} - A promise that resolves to the filtered font metrics or null if the source cannot be processed.
|
|
277
|
-
* @async
|
|
278
|
-
*/
|
|
279
|
-
async function readMetrics(_source) {
|
|
280
|
-
const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
|
|
281
|
-
if (source in metricCache) return metricCache[source] ?? null;
|
|
282
|
-
const { protocol } = (0, ufo.parseURL)(source);
|
|
283
|
-
if (!protocol) return null;
|
|
284
|
-
let metrics;
|
|
285
|
-
if (protocol === "file:") metrics = await (0, _capsizecss_unpack_fs.fromFile)((0, node_url.fileURLToPath)(source));
|
|
286
|
-
else if (urlRequestCache.has(source)) metrics = await urlRequestCache.get(source);
|
|
287
|
-
else {
|
|
288
|
-
const requestPromise = (0, _capsizecss_unpack.fromUrl)(source);
|
|
289
|
-
urlRequestCache.set(source, requestPromise);
|
|
290
|
-
metrics = await requestPromise;
|
|
291
|
-
}
|
|
292
|
-
const filteredMetrics = filterRequiredMetrics(metrics);
|
|
293
|
-
metricCache[source] = filteredMetrics;
|
|
294
|
-
return filteredMetrics;
|
|
295
|
-
}
|
|
296
|
-
function fontFamilyToCamelCase(str) {
|
|
297
|
-
return str.split(/[\s|-]/).filter(Boolean).map((s, i) => `${s.charAt(0)[i > 0 ? "toUpperCase" : "toLowerCase"]()}${s.slice(1)}`).join("");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
//#endregion
|
|
301
|
-
//#region src/transform.ts
|
|
302
|
-
const supportedExtensions = [
|
|
303
|
-
"woff2",
|
|
304
|
-
"woff",
|
|
305
|
-
"ttf"
|
|
306
|
-
];
|
|
307
|
-
const CSS_RE = (0, magic_regexp.createRegExp)((0, magic_regexp.exactly)(".").and((0, magic_regexp.anyOf)("sass", "css", "scss")).and((0, magic_regexp.exactly)("?").and((0, magic_regexp.oneOrMore)(magic_regexp.char)).optionally()).at.lineEnd());
|
|
308
|
-
const RELATIVE_RE = (0, magic_regexp.createRegExp)((0, magic_regexp.exactly)(".").or("..").and((0, magic_regexp.anyOf)("/", "\\")).at.lineStart());
|
|
309
114
|
/**
|
|
310
115
|
* Transforms CSS files to include font fallbacks.
|
|
311
116
|
*
|
|
@@ -316,11 +121,8 @@ const FontaineTransform = (0, unplugin.createUnplugin)((options) => {
|
|
|
316
121
|
const cssContext = options.css = options.css || {};
|
|
317
122
|
cssContext.value = "";
|
|
318
123
|
const resolvePath = options.resolvePath || ((id) => id);
|
|
319
|
-
const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
|
|
124
|
+
const fallbackName = options.fallbackName || options.overrideName || require_metrics.generateFallbackName;
|
|
320
125
|
const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
|
|
321
|
-
function readMetricsFromId(path, importer) {
|
|
322
|
-
return readMetrics((0, pathe.isAbsolute)(importer) && RELATIVE_RE.test(path) ? new URL(path, (0, node_url.pathToFileURL)(importer)) : resolvePath(path));
|
|
323
|
-
}
|
|
324
126
|
return {
|
|
325
127
|
name: "fontaine-transform",
|
|
326
128
|
enforce: "pre",
|
|
@@ -328,14 +130,49 @@ const FontaineTransform = (0, unplugin.createUnplugin)((options) => {
|
|
|
328
130
|
filter: { id: [CSS_RE] },
|
|
329
131
|
async handler(code, id) {
|
|
330
132
|
const s = new magic_string.default(code);
|
|
331
|
-
const
|
|
332
|
-
|
|
133
|
+
const inserted = /* @__PURE__ */ new Set();
|
|
134
|
+
const ast = (0, css_tree.parse)(SCSS_RE.test(id) ? blankLineComments(code) : code, { positions: true });
|
|
135
|
+
const fontFaces = require_metrics.parseFontFace(ast).map((face) => ({
|
|
136
|
+
...face,
|
|
137
|
+
importer: id,
|
|
138
|
+
prefix: ""
|
|
139
|
+
}));
|
|
140
|
+
if ((0, pathe.isAbsolute)(id)) {
|
|
141
|
+
const imports = extractCssImports(ast);
|
|
142
|
+
const insertionIndex = imports.length ? Math.max(...imports.map((i) => i.end)) : 0;
|
|
143
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
144
|
+
const queue = imports.map((i) => ({
|
|
145
|
+
specifier: i.specifier,
|
|
146
|
+
importer: id
|
|
147
|
+
}));
|
|
148
|
+
while (queue.length) {
|
|
149
|
+
const { specifier, importer } = queue.shift();
|
|
150
|
+
const resolved = resolveCssImport(specifier, importer);
|
|
151
|
+
if (!resolved || seen.has(resolved) || !CSS_RE.test(resolved)) continue;
|
|
152
|
+
seen.add(resolved);
|
|
153
|
+
const importedCss = await (0, node_fs_promises.readFile)(resolved, "utf-8").catch(() => null);
|
|
154
|
+
if (importedCss === null) continue;
|
|
155
|
+
const importedAst = (0, css_tree.parse)(SCSS_RE.test(resolved) ? blankLineComments(importedCss) : importedCss, { positions: true });
|
|
156
|
+
for (const face of require_metrics.parseFontFace(importedAst)) fontFaces.push({
|
|
157
|
+
...face,
|
|
158
|
+
index: insertionIndex,
|
|
159
|
+
importer: resolved,
|
|
160
|
+
prefix: "\n"
|
|
161
|
+
});
|
|
162
|
+
for (const nested of extractCssImports(importedAst)) queue.push({
|
|
163
|
+
specifier: nested.specifier,
|
|
164
|
+
importer: resolved
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const { family, source: rawSource, index, properties, importer, prefix } of fontFaces) {
|
|
169
|
+
const source = rawSource && require_metrics.withoutQueryOrFragment(rawSource);
|
|
333
170
|
if (!supportedExtensions.some((e) => source?.endsWith(e))) continue;
|
|
334
171
|
if (skipFontFaceGeneration(fallbackName(family))) continue;
|
|
335
|
-
const metrics = await getMetricsForFamily(family) || source && await
|
|
172
|
+
const metrics = await require_metrics.getMetricsForFamily(family) || source && await require_metrics.readMetricsForSource(source, importer, resolvePath).catch(() => null);
|
|
336
173
|
/* v8 ignore next 2 */
|
|
337
174
|
if (!metrics) continue;
|
|
338
|
-
const familyFallbacks = resolveCategoryFallbacks({
|
|
175
|
+
const familyFallbacks = require_metrics.resolveCategoryFallbacks({
|
|
339
176
|
fontFamily: family,
|
|
340
177
|
fallbacks: options.fallbacks,
|
|
341
178
|
metrics,
|
|
@@ -343,16 +180,19 @@ const FontaineTransform = (0, unplugin.createUnplugin)((options) => {
|
|
|
343
180
|
});
|
|
344
181
|
for (let i = familyFallbacks.length - 1; i >= 0; i--) {
|
|
345
182
|
const fallback = familyFallbacks[i];
|
|
346
|
-
const fallbackMetrics = await getMetricsForFamily(fallback);
|
|
183
|
+
const fallbackMetrics = await require_metrics.getMetricsForFamily(fallback);
|
|
347
184
|
if (!fallbackMetrics) continue;
|
|
348
|
-
const fontFace = generateFontFace(metrics, {
|
|
185
|
+
const fontFace = require_metrics.generateFontFace(metrics, {
|
|
349
186
|
name: fallbackName(family),
|
|
350
187
|
font: fallback,
|
|
351
188
|
metrics: fallbackMetrics,
|
|
352
189
|
...properties
|
|
353
190
|
});
|
|
191
|
+
const key = `${index}:${fontFace}`;
|
|
192
|
+
if (inserted.has(key)) continue;
|
|
193
|
+
inserted.add(key);
|
|
354
194
|
cssContext.value += fontFace;
|
|
355
|
-
s.appendLeft(index, fontFace);
|
|
195
|
+
s.appendLeft(index, prefix + fontFace);
|
|
356
196
|
}
|
|
357
197
|
}
|
|
358
198
|
(0, css_tree.walk)(ast, {
|
|
@@ -360,20 +200,20 @@ const FontaineTransform = (0, unplugin.createUnplugin)((options) => {
|
|
|
360
200
|
enter(node) {
|
|
361
201
|
if (node.property !== "font-family") return;
|
|
362
202
|
if (this.atrule && this.atrule.name === "font-face") return;
|
|
363
|
-
if (node.value.type !== "Value")
|
|
364
|
-
/* v8 ignore next */ return;
|
|
203
|
+
if (node.value.type !== "Value") return;
|
|
365
204
|
for (const child of node.value.children) {
|
|
366
205
|
let family;
|
|
367
|
-
if (child.type === "String") family = withoutQuotes(child.value);
|
|
368
|
-
else if (child.type === "Identifier" && child.name
|
|
206
|
+
if (child.type === "String") family = require_metrics.withoutQuotes(child.value);
|
|
207
|
+
else if (child.type === "Identifier" && !require_metrics.cssWideKeywords.has(child.name.toLowerCase())) family = child.name;
|
|
369
208
|
if (!family) continue;
|
|
370
|
-
s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
|
|
209
|
+
if (!require_metrics.genericCSSFamilies.has(family.toLowerCase())) s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
|
|
371
210
|
return;
|
|
372
211
|
}
|
|
373
212
|
}
|
|
374
213
|
});
|
|
375
214
|
if (s.hasChanged()) return {
|
|
376
215
|
code: s.toString(),
|
|
216
|
+
/* v8 ignore next 3 */
|
|
377
217
|
map: options.sourcemap ? s.generateMap({
|
|
378
218
|
source: id,
|
|
379
219
|
includeContent: true
|
|
@@ -383,12 +223,11 @@ const FontaineTransform = (0, unplugin.createUnplugin)((options) => {
|
|
|
383
223
|
}
|
|
384
224
|
};
|
|
385
225
|
});
|
|
386
|
-
|
|
387
226
|
//#endregion
|
|
388
|
-
exports.DEFAULT_CATEGORY_FALLBACKS = DEFAULT_CATEGORY_FALLBACKS;
|
|
227
|
+
exports.DEFAULT_CATEGORY_FALLBACKS = require_metrics.DEFAULT_CATEGORY_FALLBACKS;
|
|
389
228
|
exports.FontaineTransform = FontaineTransform;
|
|
390
|
-
exports.generateFallbackName = generateFallbackName;
|
|
391
|
-
exports.generateFontFace = generateFontFace;
|
|
392
|
-
exports.getMetricsForFamily = getMetricsForFamily;
|
|
393
|
-
exports.readMetrics = readMetrics;
|
|
394
|
-
exports.resolveCategoryFallbacks = resolveCategoryFallbacks;
|
|
229
|
+
exports.generateFallbackName = require_metrics.generateFallbackName;
|
|
230
|
+
exports.generateFontFace = require_metrics.generateFontFace;
|
|
231
|
+
exports.getMetricsForFamily = require_metrics.getMetricsForFamily;
|
|
232
|
+
exports.readMetrics = require_metrics.readMetrics;
|
|
233
|
+
exports.resolveCategoryFallbacks = require_metrics.resolveCategoryFallbacks;
|