const GOOGLE_FONTS_CSS_IMPORT_RE = /@import\s+url\(\s*['"]?(https:\/\/fonts\.googleapis\.com\/[^'")\s]+)['"]?\s*\)\s*;?/gi; const GOOGLE_FONTS_LINK_RE = /]*\bhref=['"](https:\/\/fonts\.googleapis\.com\/[^'"]+)['"][^>]*>/gi; async function fetchStylesheet(url) { const response = await fetch(url, { headers: { 'User-Agent': 'TKMind-MindSpace/1.0' }, signal: AbortSignal.timeout(15_000), }); if (!response.ok) { throw new Error(`无法下载样式表: ${response.status}`); } return response.text(); } function uniqueUrls(matches) { return [...new Set(matches.map((value) => value.replace(/&/g, '&')))]; } export async function localizeGoogleFontsCss(html) { const source = String(html); const importUrls = uniqueUrls([...source.matchAll(GOOGLE_FONTS_CSS_IMPORT_RE)].map((m) => m[1])); const linkUrls = uniqueUrls([...source.matchAll(GOOGLE_FONTS_LINK_RE)].map((m) => m[1])); const stylesheetUrls = uniqueUrls([...importUrls, ...linkUrls]); if (!stylesheetUrls.length) { return { html: source, localizedCount: 0, inlinedBytes: 0 }; } const inlinedBlocks = []; for (const url of stylesheetUrls) { try { inlinedBlocks.push(`/* localized from ${url} */\n${await fetchStylesheet(url)}`); } catch { inlinedBlocks.push(`/* failed to localize ${url} */`); } } let next = source; for (const url of importUrls) { const pattern = new RegExp( `@import\\s+url\\(\\s*['"]?${url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]?\\s*\\)\\s*;?`, 'gi', ); next = next.replace(pattern, ''); } next = next.replace(GOOGLE_FONTS_LINK_RE, ''); const styleBlock = ``; if (/]*>/i.test(next)) { next = next.replace(/]*)>/i, `${styleBlock}`); } else if (/]*>/i.test(next)) { next = next.replace(/]*)>/i, `${styleBlock}`); } else { next = `${styleBlock}\n${next}`; } const inlinedBytes = Buffer.byteLength(inlinedBlocks.join('\n')); return { html: next, localizedCount: stylesheetUrls.length, inlinedBytes }; }