fontaine 0.4.1 → 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/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
- /** this should produce an unquoted font family name */
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
- declare const generateFallbackName: (name: string) => string;
20
- interface FallbackOptions {
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 const 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
- /** this should produce an unquoted font family name */
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
- declare const generateFallbackName: (name: string) => string;
20
- interface FallbackOptions {
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 const 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,40 +1,109 @@
1
- import { createUnplugin } from 'unplugin';
2
- import { createRegExp, charIn, exactly, whitespace, charNotIn, anyOf } from 'magic-regexp';
3
- import MagicString from 'magic-string';
4
- import { fileURLToPath } from 'node:url';
5
- 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';
6
4
  import { fontFamilyToCamelCase } from '@capsizecss/metrics';
5
+ import { fromFile, fromUrl } from '@capsizecss/unpack';
7
6
  import { parseURL } from 'ufo';
8
- import { isAbsolute, join } from 'pathe';
7
+ import MagicString from 'magic-string';
8
+ import { isAbsolute } from 'pathe';
9
+ import { createUnplugin } from 'unplugin';
9
10
 
10
- function* parseFontFace(css) {
11
- const fontFamily = css.match(FAMILY_RE)?.groups.fontFamily;
12
- const family = withoutQuotes(fontFamily?.split(",")[0] || "");
13
- for (const match of css.matchAll(SOURCE_RE)) {
14
- const sources = match.groups.src?.split(",");
15
- for (const entry of sources || []) {
16
- for (const url of entry.matchAll(URL_RE)) {
17
- const source = withoutQuotes(url.groups?.url || "");
18
- if (source) {
19
- yield { family, source };
11
+ function toPercentage(value, fractionDigits = 4) {
12
+ const percentage = value * 100;
13
+ return `${+percentage.toFixed(fractionDigits)}%`;
14
+ }
15
+ function toCSS(properties, indent = 2) {
16
+ return Object.entries(properties).map(([key, value]) => `${" ".repeat(indent)}${key}: ${value};`).join("\n");
17
+ }
18
+ const QUOTES_RE = createRegExp(
19
+ charIn(`"'`).at.lineStart().or(charIn(`"'`).at.lineEnd()),
20
+ ["g"]
21
+ );
22
+ const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
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 });
20
95
  }
21
96
  }
22
97
  }
23
- }
24
- yield { family: "", source: "" };
98
+ });
99
+ return families;
25
100
  }
26
- const generateFallbackName = (name) => {
101
+ function generateFallbackName(name) {
27
102
  const firstFamily = withoutQuotes(name.split(",").shift());
28
103
  return `${firstFamily} fallback`;
29
- };
30
- const withoutQuotes = (str) => str.trim().replace(QUOTES_RE, "");
31
- const generateFontFace = (metrics, fallback) => {
32
- const {
33
- name: fallbackName,
34
- font: fallbackFontName,
35
- metrics: fallbackMetrics,
36
- ...properties
37
- } = fallback;
104
+ }
105
+ function generateFontFace(metrics, fallback) {
106
+ const { name: fallbackName, font: fallbackFontName, metrics: fallbackMetrics, ...properties } = fallback;
38
107
  const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
39
108
  const fallbackFontXAvgRatio = fallbackMetrics ? fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm : 1;
40
109
  const sizeAdjust = fallbackMetrics && preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
@@ -44,7 +113,7 @@ const generateFontFace = (metrics, fallback) => {
44
113
  const lineGapOverride = metrics.lineGap / adjustedEmSquare;
45
114
  const declaration = {
46
115
  "font-family": JSON.stringify(fallbackName),
47
- src: `local(${JSON.stringify(fallbackFontName)})`,
116
+ "src": `local(${JSON.stringify(fallbackFontName)})`,
48
117
  "size-adjust": toPercentage(sizeAdjust),
49
118
  "ascent-override": toPercentage(ascentOverride),
50
119
  "descent-override": toPercentage(descentOverride),
@@ -55,50 +124,30 @@ const generateFontFace = (metrics, fallback) => {
55
124
  ${toCSS(declaration)}
56
125
  }
57
126
  `;
58
- };
59
- const toPercentage = (value, fractionDigits = 4) => {
60
- const percentage = value * 100;
61
- return +percentage.toFixed(fractionDigits) + "%";
62
- };
63
- const toCSS = (properties, indent = 2) => Object.entries(properties).map(([key, value]) => " ".repeat(indent) + `${key}: ${value};`).join("\n");
64
- const QUOTES_RE = createRegExp(
65
- charIn(`"'`).at.lineStart().or(charIn(`"'`).at.lineEnd()),
66
- ["g"]
67
- );
68
- const FAMILY_RE = createRegExp(
69
- exactly("font-family:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("fontFamily"))
70
- );
71
- const SOURCE_RE = createRegExp(
72
- exactly("src:").and(whitespace.optionally()).and(charNotIn(";}").times.any().as("src")),
73
- ["g"]
74
- );
75
- const URL_RE = createRegExp(
76
- exactly("url(").and(charNotIn(")").times.any().as("url")).and(")"),
77
- ["g"]
78
- );
127
+ }
79
128
 
80
129
  const metricCache = {};
81
- const filterRequiredMetrics = ({
82
- ascent,
83
- descent,
84
- lineGap,
85
- unitsPerEm,
86
- xWidthAvg
87
- }) => ({
88
- ascent,
89
- descent,
90
- lineGap,
91
- unitsPerEm,
92
- xWidthAvg
93
- });
130
+ function filterRequiredMetrics({ ascent, descent, lineGap, unitsPerEm, xWidthAvg }) {
131
+ return {
132
+ ascent,
133
+ descent,
134
+ lineGap,
135
+ unitsPerEm,
136
+ xWidthAvg
137
+ };
138
+ }
94
139
  async function getMetricsForFamily(family) {
95
140
  family = withoutQuotes(family);
96
141
  if (family in metricCache)
97
142
  return metricCache[family];
98
143
  try {
99
144
  const name = fontFamilyToCamelCase(family);
100
- const { entireMetricsCollection } = await import('@capsizecss/metrics/entireMetricsCollection/dist/capsizecss-metrics-entireMetricsCollection.cjs.js');
145
+ const { entireMetricsCollection } = await import('@capsizecss/metrics/entireMetricsCollection');
101
146
  const metrics = entireMetricsCollection[name];
147
+ if (!("descent" in metrics)) {
148
+ metricCache[family] = null;
149
+ return null;
150
+ }
102
151
  const filteredMetrics = filterRequiredMetrics(metrics);
103
152
  metricCache[family] = filteredMetrics;
104
153
  return filteredMetrics;
@@ -107,113 +156,113 @@ async function getMetricsForFamily(family) {
107
156
  return null;
108
157
  }
109
158
  }
159
+ const urlRequestCache = /* @__PURE__ */ new Map();
110
160
  async function readMetrics(_source) {
111
161
  const source = typeof _source !== "string" && "href" in _source ? _source.href : _source;
112
- if (source in metricCache) {
113
- return Promise.resolve(metricCache[source]);
114
- }
162
+ if (source in metricCache)
163
+ return metricCache[source];
115
164
  const { protocol } = parseURL(source);
116
165
  if (!protocol)
117
166
  return null;
118
- const metrics = protocol === "file:" ? await fromFile(fileURLToPath(source)) : await fromUrl(source);
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
+ }
119
179
  const filteredMetrics = filterRequiredMetrics(metrics);
120
180
  metricCache[source] = filteredMetrics;
121
181
  return filteredMetrics;
122
182
  }
123
183
 
124
184
  const supportedExtensions = ["woff2", "woff", "ttf"];
125
- const FontaineTransform = createUnplugin(
126
- (options) => {
127
- const cssContext = options.css = options.css || {};
128
- cssContext.value = "";
129
- const resolvePath = options.resolvePath || ((id) => id);
130
- const fallbackName = options.fallbackName || options.overrideName || generateFallbackName;
131
- const skipFontFaceGeneration = options.skipFontFaceGeneration || (() => false);
132
- function readMetricsFromId(path, importer) {
133
- const resolvedPath = isAbsolute(importer) && path.startsWith(".") ? join(importer, path) : resolvePath(path);
134
- return readMetrics(resolvedPath);
135
- }
136
- return {
137
- name: "fontaine-transform",
138
- enforce: "pre",
139
- transformInclude(id) {
140
- const { pathname } = parseURL(id);
141
- return CSS_RE.test(pathname) || CSS_RE.test(id);
142
- },
143
- async transform(code, id) {
144
- const s = new MagicString(code);
145
- const faceRanges = [];
146
- for (const match of code.matchAll(FONT_FACE_RE)) {
147
- const matchContent = match[0];
148
- if (match.index === void 0 || !matchContent)
185
+ const CSS_RE = createRegExp(
186
+ exactly(".").and(anyOf("sass", "css", "scss")).at.lineEnd()
187
+ );
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)
149
223
  continue;
150
- faceRanges.push([match.index, match.index + matchContent.length]);
151
- for (const { family, source } of parseFontFace(matchContent)) {
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
+ }
152
250
  if (!family)
153
251
  continue;
154
- if (!supportedExtensions.some((e) => source?.endsWith(e)))
155
- continue;
156
- if (skipFontFaceGeneration(fallbackName(family)))
157
- continue;
158
- const metrics = await getMetricsForFamily(family) || source && await readMetricsFromId(source, id).catch(() => null);
159
- if (!metrics) {
160
- continue;
161
- }
162
- for (let i = options.fallbacks.length - 1; i >= 0; i--) {
163
- const fallback = options.fallbacks[i];
164
- const fallbackMetrics = await getMetricsForFamily(fallback);
165
- if (!fallbackMetrics) {
166
- continue;
167
- }
168
- const fontFace = generateFontFace(metrics, {
169
- name: fallbackName(family),
170
- font: fallback,
171
- metrics: fallbackMetrics
172
- });
173
- cssContext.value += fontFace;
174
- s.appendLeft(match.index, fontFace);
175
- }
252
+ s.appendRight(child.loc.end.offset, `, "${fallbackName(family)}"`);
253
+ return;
176
254
  }
177
255
  }
178
- for (const match of code.matchAll(FONT_FAMILY_RE)) {
179
- const { index, 0: matchContent } = match;
180
- if (index === void 0 || !matchContent)
181
- continue;
182
- if (faceRanges.some(([start, end]) => index > start && index < end))
183
- continue;
184
- const families = matchContent.split(",").map((f) => f.trim()).filter((f) => !f.startsWith("var("));
185
- if (!families.length || families[0] === "inherit")
186
- continue;
187
- s.overwrite(
188
- index,
189
- index + matchContent.length,
190
- " " + [
191
- families[0],
192
- `"${generateFallbackName(families[0])}"`,
193
- ...families.slice(1)
194
- ].join(", ")
195
- );
196
- }
197
- if (s.hasChanged()) {
198
- return {
199
- code: s.toString(),
200
- map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
201
- };
202
- }
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
+ };
203
263
  }
204
- };
205
- }
206
- );
207
- const FONT_FACE_RE = createRegExp(
208
- exactly("@font-face").and(whitespace.times.any()).and("{").and(charNotIn("}").times.any()).and("}"),
209
- ["g"]
210
- );
211
- const FONT_FAMILY_RE = createRegExp(
212
- charNotIn(";}").times.any().as("family").after(exactly("font-family:").and(whitespace.times.any())).before(charIn(";}").or(exactly("").at.lineEnd())),
213
- ["g"]
214
- );
215
- const CSS_RE = createRegExp(
216
- exactly(".").and(anyOf("sass", "css", "scss")).at.lineEnd()
217
- );
264
+ }
265
+ };
266
+ });
218
267
 
219
268
  export { FontaineTransform, generateFallbackName, generateFontFace, getMetricsForFamily, readMetrics };