nuxt-og-image 7.0.3 → 7.0.4
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/chunks/tw4.mjs +1 -1
- package/dist/chunks/uno.mjs +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/og-image/font-binary.d.ts +21 -0
- package/dist/runtime/server/og-image/font-binary.js +123 -0
- package/dist/runtime/server/og-image/fonts.d.ts +3 -3
- package/dist/runtime/server/og-image/fonts.js +40 -11
- package/dist/shared/{nuxt-og-image.B5ZhjIiR.mjs → nuxt-og-image.C4hc520M.mjs} +30 -18
- package/package.json +1 -1
- package/skills/nuxt-og-image/SKILL.md +1 -1
package/dist/chunks/tw4.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { resolveModulePath } from 'exsolve';
|
|
4
4
|
import { dirname, join } from 'pathe';
|
|
5
|
-
import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.
|
|
5
|
+
import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.C4hc520M.mjs';
|
|
6
6
|
import 'node:fs';
|
|
7
7
|
import 'node:module';
|
|
8
8
|
import '@nuxt/kit';
|
package/dist/chunks/uno.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
3
|
import { logger } from '../../dist/runtime/logger.js';
|
|
4
|
-
import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.
|
|
5
|
-
export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.
|
|
4
|
+
import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.C4hc520M.mjs';
|
|
5
|
+
export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.C4hc520M.mjs';
|
|
6
6
|
import 'node:fs';
|
|
7
7
|
import 'node:module';
|
|
8
8
|
import 'node:url';
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import 'defu';
|
|
|
7
7
|
import 'fnv1a-64';
|
|
8
8
|
import 'nuxtseo-shared/kit';
|
|
9
9
|
import 'pathe';
|
|
10
|
-
export { n as default } from './shared/nuxt-og-image.
|
|
10
|
+
export { n as default } from './shared/nuxt-og-image.C4hc520M.mjs';
|
|
11
11
|
import '../dist/runtime/logger.js';
|
|
12
12
|
import '../dist/runtime/shared/publish.js';
|
|
13
13
|
import 'node:crypto';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font binaries as renderers receive them.
|
|
3
|
+
*
|
|
4
|
+
* Takumi's WASM build rejects WOFF, and one bad font fails the whole render.
|
|
5
|
+
* The loader inflates WOFF to the TTF or OTF it wraps and rejects bytes that are no font.
|
|
6
|
+
* The decoder only needs `DecompressionStream`, so it runs on Node and edge runtimes.
|
|
7
|
+
*/
|
|
8
|
+
export type FontBinaryResult = {
|
|
9
|
+
_tag: 'Ok';
|
|
10
|
+
data: Uint8Array<ArrayBuffer>;
|
|
11
|
+
} | {
|
|
12
|
+
_tag: 'Err';
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
/** Whether the bytes are WOFF2, which Satori cannot read. WOFF2 needs Brotli, so the loader never inflates it. */
|
|
16
|
+
export declare function isWoff2Binary(source: BufferSource): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Parse loaded font bytes into a binary that every Takumi build accepts.
|
|
19
|
+
* TTF, OTF, font collections and WOFF2 pass through; WOFF inflates to TTF or OTF.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseFontBinary(source: BufferSource): Promise<FontBinaryResult>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const WOFF_HEADER_SIZE = 44;
|
|
2
|
+
const WOFF_TABLE_ENTRY_SIZE = 20;
|
|
3
|
+
const SFNT_TABLE_RECORD_SIZE = 16;
|
|
4
|
+
const MAX_SFNT_SIZE = 64 * 1024 * 1024;
|
|
5
|
+
const SIGNATURES = {
|
|
6
|
+
truetype: 65536,
|
|
7
|
+
appleTrueType: 1953658213,
|
|
8
|
+
// 'true'
|
|
9
|
+
openType: 1330926671,
|
|
10
|
+
// 'OTTO'
|
|
11
|
+
collection: 1953784678,
|
|
12
|
+
// 'ttcf'
|
|
13
|
+
woff: 2001684038,
|
|
14
|
+
// 'wOFF'
|
|
15
|
+
woff2: 2001684018
|
|
16
|
+
// 'wOF2'
|
|
17
|
+
};
|
|
18
|
+
function toBytes(source) {
|
|
19
|
+
if (ArrayBuffer.isView(source))
|
|
20
|
+
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
21
|
+
return new Uint8Array(source);
|
|
22
|
+
}
|
|
23
|
+
function readSignature(data) {
|
|
24
|
+
if (data.byteLength < 4)
|
|
25
|
+
return void 0;
|
|
26
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0);
|
|
27
|
+
}
|
|
28
|
+
async function inflate(data, size) {
|
|
29
|
+
const output = new Uint8Array(size);
|
|
30
|
+
const reader = new Blob([data]).stream().pipeThrough(new DecompressionStream("deflate")).getReader();
|
|
31
|
+
let written = 0;
|
|
32
|
+
while (true) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done)
|
|
35
|
+
break;
|
|
36
|
+
if (written + value.byteLength > size) {
|
|
37
|
+
await reader.cancel();
|
|
38
|
+
throw new Error("a table inflates past its declared size");
|
|
39
|
+
}
|
|
40
|
+
output.set(value, written);
|
|
41
|
+
written += value.byteLength;
|
|
42
|
+
}
|
|
43
|
+
if (written !== size)
|
|
44
|
+
throw new Error("a table inflates short of its declared size");
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
async function decodeWoff(woff) {
|
|
48
|
+
if (woff.byteLength < WOFF_HEADER_SIZE)
|
|
49
|
+
throw new Error("the WOFF header is truncated");
|
|
50
|
+
const view = new DataView(woff.buffer, woff.byteOffset, woff.byteLength);
|
|
51
|
+
const flavor = view.getUint32(4);
|
|
52
|
+
const numTables = view.getUint16(12);
|
|
53
|
+
if (numTables === 0)
|
|
54
|
+
throw new Error("the WOFF file has no tables");
|
|
55
|
+
if (WOFF_HEADER_SIZE + numTables * WOFF_TABLE_ENTRY_SIZE > woff.byteLength)
|
|
56
|
+
throw new Error("the WOFF table directory is truncated");
|
|
57
|
+
const tables = Array.from({ length: numTables }, (_, index) => {
|
|
58
|
+
const entry = WOFF_HEADER_SIZE + index * WOFF_TABLE_ENTRY_SIZE;
|
|
59
|
+
return {
|
|
60
|
+
tag: view.getUint32(entry),
|
|
61
|
+
offset: view.getUint32(entry + 4),
|
|
62
|
+
compLength: view.getUint32(entry + 8),
|
|
63
|
+
origLength: view.getUint32(entry + 12),
|
|
64
|
+
checksum: view.getUint32(entry + 16)
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
const headerSize = 12 + numTables * SFNT_TABLE_RECORD_SIZE;
|
|
68
|
+
let sfntSize = headerSize;
|
|
69
|
+
for (const table of tables) {
|
|
70
|
+
if (table.offset + table.compLength > woff.byteLength)
|
|
71
|
+
throw new Error("a WOFF table runs past the end of the file");
|
|
72
|
+
if (table.compLength > table.origLength)
|
|
73
|
+
throw new Error("a WOFF table is larger than its decoded size");
|
|
74
|
+
sfntSize += table.origLength + 3 & ~3;
|
|
75
|
+
}
|
|
76
|
+
if (sfntSize > MAX_SFNT_SIZE)
|
|
77
|
+
throw new Error(`the decoded font exceeds ${MAX_SFNT_SIZE} bytes`);
|
|
78
|
+
const tableData = await Promise.all(tables.map((table) => {
|
|
79
|
+
const stored = woff.subarray(table.offset, table.offset + table.compLength);
|
|
80
|
+
return table.compLength < table.origLength ? inflate(stored, table.origLength) : stored;
|
|
81
|
+
}));
|
|
82
|
+
const sfnt = new Uint8Array(sfntSize);
|
|
83
|
+
const out = new DataView(sfnt.buffer);
|
|
84
|
+
const entrySelector = Math.floor(Math.log2(numTables));
|
|
85
|
+
const searchRange = 2 ** entrySelector * SFNT_TABLE_RECORD_SIZE;
|
|
86
|
+
out.setUint32(0, flavor);
|
|
87
|
+
out.setUint16(4, numTables);
|
|
88
|
+
out.setUint16(6, searchRange);
|
|
89
|
+
out.setUint16(8, entrySelector);
|
|
90
|
+
out.setUint16(10, numTables * SFNT_TABLE_RECORD_SIZE - searchRange);
|
|
91
|
+
let offset = headerSize;
|
|
92
|
+
tables.forEach((table, index) => {
|
|
93
|
+
const record = 12 + index * SFNT_TABLE_RECORD_SIZE;
|
|
94
|
+
out.setUint32(record, table.tag);
|
|
95
|
+
out.setUint32(record + 4, table.checksum);
|
|
96
|
+
out.setUint32(record + 8, offset);
|
|
97
|
+
out.setUint32(record + 12, table.origLength);
|
|
98
|
+
sfnt.set(tableData[index], offset);
|
|
99
|
+
offset += table.origLength + 3 & ~3;
|
|
100
|
+
});
|
|
101
|
+
return sfnt;
|
|
102
|
+
}
|
|
103
|
+
export function isWoff2Binary(source) {
|
|
104
|
+
return readSignature(toBytes(source)) === SIGNATURES.woff2;
|
|
105
|
+
}
|
|
106
|
+
export async function parseFontBinary(source) {
|
|
107
|
+
const data = toBytes(source);
|
|
108
|
+
switch (readSignature(data)) {
|
|
109
|
+
case SIGNATURES.truetype:
|
|
110
|
+
case SIGNATURES.appleTrueType:
|
|
111
|
+
case SIGNATURES.openType:
|
|
112
|
+
case SIGNATURES.collection:
|
|
113
|
+
case SIGNATURES.woff2:
|
|
114
|
+
return { _tag: "Ok", data };
|
|
115
|
+
case SIGNATURES.woff:
|
|
116
|
+
return decodeWoff(data).then(
|
|
117
|
+
(sfnt) => ({ _tag: "Ok", data: sfnt }),
|
|
118
|
+
(error) => ({ _tag: "Err", reason: `it is a WOFF file that could not be decoded: ${error.message}` })
|
|
119
|
+
);
|
|
120
|
+
default:
|
|
121
|
+
return { _tag: "Err", reason: "it is not a TTF, OTF, WOFF or WOFF2 file" };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -5,9 +5,9 @@ export { buildSubsetFamilyChain, renameSubsetFonts, resolveSubsetChain } from '.
|
|
|
5
5
|
export { codepointsIntersectRanges, extractCodepoints, parseUnicodeRange } from './unicode-range.js';
|
|
6
6
|
export interface LoadFontsOptions {
|
|
7
7
|
/**
|
|
8
|
-
* Font formats the renderer can
|
|
8
|
+
* Font source formats the renderer can take.
|
|
9
9
|
* Satori: ttf, otf, woff
|
|
10
|
-
* Takumi: ttf, otf, woff, woff2
|
|
10
|
+
* Takumi: ttf, otf, woff, woff2. The loader inflates WOFF, which Takumi's WASM build rejects.
|
|
11
11
|
*/
|
|
12
12
|
supportedFormats: Set<FontFormat>;
|
|
13
13
|
/** Component pascalName — filters fonts to only what this component needs */
|
|
@@ -40,7 +40,7 @@ export declare function loadAllFonts(event: H3Event, options: LoadFontsOptions):
|
|
|
40
40
|
* parsed at this boundary (require string name/path, coerce weight) and the
|
|
41
41
|
* array is bounded before any font is fetched.
|
|
42
42
|
*/
|
|
43
|
-
export declare function loadDefinedFonts(event: OgImageRenderEventContext, fontDefs: any[]): Promise<RuntimeFontConfig[]>;
|
|
43
|
+
export declare function loadDefinedFonts(event: OgImageRenderEventContext, fontDefs: any[], supportedFormats: Set<FontFormat>): Promise<RuntimeFontConfig[]>;
|
|
44
44
|
export interface LoadFontsForRendererOptions extends LoadFontsOptions {
|
|
45
45
|
/** Custom font definitions from defineOgImage({ fonts: [...] }) — satori only */
|
|
46
46
|
fontDefs?: any[];
|
|
@@ -4,11 +4,21 @@ import resolvedFonts from "#og-image/fonts";
|
|
|
4
4
|
import availableFonts from "#og-image/fonts-available";
|
|
5
5
|
import { logger } from "../../logger.js";
|
|
6
6
|
import { fontArrayCache, fontCache } from "./cache/lru.js";
|
|
7
|
+
import { isWoff2Binary, parseFontBinary } from "./font-binary.js";
|
|
7
8
|
import { fontFormat, selectFontSource } from "./font-source.js";
|
|
8
9
|
import { renameSubsetFonts } from "./font-subsets.js";
|
|
9
10
|
import { codepointsIntersectRanges, parseUnicodeRange } from "./unicode-range.js";
|
|
10
11
|
export { buildSubsetFamilyChain, renameSubsetFonts, resolveSubsetChain } from "./font-subsets.js";
|
|
11
12
|
export { codepointsIntersectRanges, extractCodepoints, parseUnicodeRange } from "./unicode-range.js";
|
|
13
|
+
const _warnedFontKeys = /* @__PURE__ */ new Set();
|
|
14
|
+
function warnOnce(key, message) {
|
|
15
|
+
if (_warnedFontKeys.has(key))
|
|
16
|
+
return;
|
|
17
|
+
if (_warnedFontKeys.size >= 100)
|
|
18
|
+
_warnedFontKeys.clear();
|
|
19
|
+
_warnedFontKeys.add(key);
|
|
20
|
+
logger.warn(message);
|
|
21
|
+
}
|
|
12
22
|
async function loadFont(event, font, src) {
|
|
13
23
|
const cacheKey = `${font.family}-${font.weight}-${font.style}-${src}`;
|
|
14
24
|
const cached = fontCache.get(cacheKey);
|
|
@@ -20,8 +30,13 @@ async function loadFont(event, font, src) {
|
|
|
20
30
|
});
|
|
21
31
|
if (!data)
|
|
22
32
|
return null;
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
const binary = await parseFontBinary(data);
|
|
34
|
+
if (binary._tag === "Err") {
|
|
35
|
+
warnOnce(`binary-${src}`, `OG images skip font "${font.family}" (${src}) because ${binary.reason}. Serve it as TTF, OTF, WOFF or WOFF2.`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
fontCache.set(cacheKey, binary.data);
|
|
39
|
+
return binary.data;
|
|
25
40
|
}
|
|
26
41
|
function findClosestWeight(target, available) {
|
|
27
42
|
return available.reduce(
|
|
@@ -65,7 +80,6 @@ function selectFontsForRequirements(allFonts, requirements) {
|
|
|
65
80
|
selected.push(...fallbackFonts);
|
|
66
81
|
return selected;
|
|
67
82
|
}
|
|
68
|
-
const _warnedFontKeys = /* @__PURE__ */ new Set();
|
|
69
83
|
export function loadAllFontsDebug(component) {
|
|
70
84
|
const map = getComponentFontMap();
|
|
71
85
|
const componentReqs = component ? map[component] : null;
|
|
@@ -122,6 +136,10 @@ export async function loadAllFonts(event, options) {
|
|
|
122
136
|
}
|
|
123
137
|
if (!data)
|
|
124
138
|
return null;
|
|
139
|
+
if (!options.supportedFormats.has("woff2") && isWoff2Binary(data)) {
|
|
140
|
+
warnOnce(`woff2-${src}`, `OG images skip font "${f.family}" (${src}) for this renderer because it is a WOFF2 file. Satori reads TTF, OTF and WOFF.`);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
125
143
|
return {
|
|
126
144
|
...f,
|
|
127
145
|
src,
|
|
@@ -183,7 +201,7 @@ function normalizeFontWeight(weight) {
|
|
|
183
201
|
return 400;
|
|
184
202
|
return Math.min(Math.max(Math.round(n), 1), 1e3);
|
|
185
203
|
}
|
|
186
|
-
export async function loadDefinedFonts(event, fontDefs) {
|
|
204
|
+
export async function loadDefinedFonts(event, fontDefs, supportedFormats) {
|
|
187
205
|
const results = [];
|
|
188
206
|
const defs = Array.isArray(fontDefs) ? fontDefs : [];
|
|
189
207
|
if (import.meta.dev && defs.length > MAX_DEFINED_FONTS)
|
|
@@ -201,19 +219,30 @@ export async function loadDefinedFonts(event, fontDefs) {
|
|
|
201
219
|
const data = await resolve(event.e, fontConfig).catch(() => {
|
|
202
220
|
return null;
|
|
203
221
|
});
|
|
204
|
-
if (data)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
222
|
+
if (!data)
|
|
223
|
+
continue;
|
|
224
|
+
const binary = await parseFontBinary(data);
|
|
225
|
+
if (binary._tag === "Err") {
|
|
226
|
+
if (import.meta.dev)
|
|
227
|
+
warnOnce(`binary-${path}`, `defineOgImage() font "${name}" is skipped because ${binary.reason}. Use a TTF, OTF, WOFF or WOFF2 file.`);
|
|
228
|
+
continue;
|
|
210
229
|
}
|
|
230
|
+
if (!supportedFormats.has("woff2") && isWoff2Binary(binary.data)) {
|
|
231
|
+
if (import.meta.dev)
|
|
232
|
+
warnOnce(`woff2-${path}`, `defineOgImage() font "${name}" is skipped because Satori cannot read WOFF2. Use a TTF, OTF or WOFF file.`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
results.push({
|
|
236
|
+
...fontConfig,
|
|
237
|
+
cacheKey: `custom-${name}-${weight}-${style}-${path}`,
|
|
238
|
+
data: binary.data
|
|
239
|
+
});
|
|
211
240
|
}
|
|
212
241
|
return results;
|
|
213
242
|
}
|
|
214
243
|
export async function loadFontsForRenderer(event, options) {
|
|
215
244
|
const baseFonts = await loadAllFonts(event.e, options);
|
|
216
|
-
const customFonts = Array.isArray(options.fontDefs) && options.fontDefs.length > 0 ? await loadDefinedFonts(event, options.fontDefs) : [];
|
|
245
|
+
const customFonts = Array.isArray(options.fontDefs) && options.fontDefs.length > 0 ? await loadDefinedFonts(event, options.fontDefs, options.supportedFormats) : [];
|
|
217
246
|
const allFonts = customFonts.length > 0 ? [...baseFonts, ...customFonts] : baseFonts;
|
|
218
247
|
return renameSubsetFonts(allFonts);
|
|
219
248
|
}
|
|
@@ -2553,7 +2553,12 @@ async function resolveAndDownloadFamily(options) {
|
|
|
2553
2553
|
const resolution = await options.resolver(family, override);
|
|
2554
2554
|
if (!resolution?.fonts?.length)
|
|
2555
2555
|
return results;
|
|
2556
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
2556
2557
|
for (const font of resolution.fonts) {
|
|
2558
|
+
const style = font.style || "normal";
|
|
2559
|
+
if (!styles.includes(style))
|
|
2560
|
+
continue;
|
|
2561
|
+
const resolvedWeights = resolveWeightsFromFontEntry(font.weight, weights);
|
|
2557
2562
|
for (const src of font.src) {
|
|
2558
2563
|
if (!("url" in src))
|
|
2559
2564
|
continue;
|
|
@@ -2562,23 +2567,22 @@ async function resolveAndDownloadFamily(options) {
|
|
|
2562
2567
|
const isTtf = format === "truetype" || format === "ttf";
|
|
2563
2568
|
if (!isTtf && format !== "woff")
|
|
2564
2569
|
continue;
|
|
2565
|
-
const style = font.style || "normal";
|
|
2566
|
-
if (!styles.includes(style))
|
|
2567
|
-
continue;
|
|
2568
|
-
const resolvedWeights = resolveWeightsFromFontEntry(font.weight, weights);
|
|
2569
|
-
if (resolvedWeights.length === 0)
|
|
2570
|
-
continue;
|
|
2571
|
-
const ext = isTtf ? "ttf" : "woff";
|
|
2572
2570
|
for (const weight of resolvedWeights) {
|
|
2573
|
-
const
|
|
2574
|
-
|
|
2575
|
-
if (!await downloadFontFile(url, destPath))
|
|
2576
|
-
continue;
|
|
2577
|
-
results.push({ family, weight, style, format, filename, url });
|
|
2578
|
-
logger.debug(`Resolved static font: ${family} ${weight}`);
|
|
2571
|
+
const key = `${weight}-${style}`;
|
|
2572
|
+
candidates.set(key, [...candidates.get(key) || [], { weight, style, url, isTtf }]);
|
|
2579
2573
|
}
|
|
2580
2574
|
}
|
|
2581
2575
|
}
|
|
2576
|
+
for (const sources of candidates.values()) {
|
|
2577
|
+
for (const { weight, style, url, isTtf } of sources.toSorted((a, b) => Number(b.isTtf) - Number(a.isTtf))) {
|
|
2578
|
+
const filename = `${family.replace(RE_NON_ALPHANUMERIC, "_")}-${weight}-${style}.${isTtf ? "ttf" : "woff"}`;
|
|
2579
|
+
if (!await downloadFontFile(url, join(staticFontDir, filename)))
|
|
2580
|
+
continue;
|
|
2581
|
+
results.push({ family, weight, style, format: isTtf ? "truetype" : "woff", filename, url });
|
|
2582
|
+
logger.debug(`Resolved static font: ${family} ${weight}`);
|
|
2583
|
+
break;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2582
2586
|
} catch (err) {
|
|
2583
2587
|
options.logger.debug(`Failed to resolve fallback font for ${family}:`, err);
|
|
2584
2588
|
}
|
|
@@ -6121,7 +6125,7 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
6121
6125
|
});
|
|
6122
6126
|
});
|
|
6123
6127
|
}
|
|
6124
|
-
|
|
6128
|
+
async function resolveFontList() {
|
|
6125
6129
|
builtFontKey = currentFontKey();
|
|
6126
6130
|
await loadCssMetadata();
|
|
6127
6131
|
if (!fontProcessingDone && hasSatoriRenderer() && hasNuxtFonts) {
|
|
@@ -6138,7 +6142,7 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
6138
6142
|
});
|
|
6139
6143
|
fontProcessingDone = true;
|
|
6140
6144
|
}
|
|
6141
|
-
|
|
6145
|
+
return await resolveOgImageFonts({
|
|
6142
6146
|
nuxt,
|
|
6143
6147
|
hasNuxtFonts,
|
|
6144
6148
|
hasSatoriRenderer: hasSatoriRenderer(),
|
|
@@ -6150,8 +6154,15 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
6150
6154
|
logger,
|
|
6151
6155
|
ogFontsDir: resolve("./runtime/public/_og-fonts")
|
|
6152
6156
|
});
|
|
6153
|
-
|
|
6154
|
-
|
|
6157
|
+
}
|
|
6158
|
+
let builtFontList;
|
|
6159
|
+
function getFontList() {
|
|
6160
|
+
const key = currentFontKey();
|
|
6161
|
+
if (nuxt.options.dev || builtFontList?.key !== key)
|
|
6162
|
+
builtFontList = { key, fonts: resolveFontList() };
|
|
6163
|
+
return builtFontList.fonts;
|
|
6164
|
+
}
|
|
6165
|
+
nuxt.options.nitro.virtual["#og-image/fonts"] = async () => `export default ${JSON.stringify(await getFontList())}`;
|
|
6155
6166
|
nuxt.options.nitro.virtual["#og-image/fonts-available"] = async () => {
|
|
6156
6167
|
const fonts = hasNuxtFonts ? await getResolvedNuxtFonts(nuxt, { fontState, requiredWeights: fontRequirementsState.weights }) : [];
|
|
6157
6168
|
return `export default ${JSON.stringify(fonts)}`;
|
|
@@ -6231,7 +6242,8 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
|
|
|
6231
6242
|
});
|
|
6232
6243
|
fontProcessingDone = true;
|
|
6233
6244
|
});
|
|
6234
|
-
nuxt.hook("nitro:build:public-assets", (nitro) => {
|
|
6245
|
+
nuxt.hook("nitro:build:public-assets", async (nitro) => {
|
|
6246
|
+
await getFontList();
|
|
6235
6247
|
copyStaticFontsToOutput({
|
|
6236
6248
|
buildDir: nuxt.options.buildDir,
|
|
6237
6249
|
bundledFontsDir: resolve("./runtime/public/_og-fonts"),
|
package/package.json
CHANGED
|
@@ -88,7 +88,7 @@ To use an existing image, call `useSeoMeta({ ogImage: '/cover.png' })`. The v5 `
|
|
|
88
88
|
|
|
89
89
|
## Fonts
|
|
90
90
|
|
|
91
|
-
Custom fonts load only through `@nuxt/fonts`, and each family needs `global: true`. CSS `@font-face` rules and font CDN links do not reach the renderer. Local fonts: Takumi reads woff2 and
|
|
91
|
+
Custom fonts load only through `@nuxt/fonts`, and each family needs `global: true`. CSS `@font-face` rules and font CDN links do not reach the renderer. Local fonts: Takumi reads woff2, woff, ttf, and otf; Satori reads woff, ttf, and otf.
|
|
92
92
|
|
|
93
93
|
```ts
|
|
94
94
|
export default defineNuxtConfig({
|