css-doodle
Version:
A web component for visual art & creative coding
121 lines (107 loc) • 3.28 kB
JavaScript
import { unique } from './index.js';
let embedFonts = new Map();
let linkFonts = new Set();
function getGoogleFontLink(names) {
if (!Array.isArray(names)) {
names = [names];
}
let params = names.map(n => `family=${encodeURIComponent(n)}`).join('&');
return `https://fonts.googleapis.com/css?display=swap&${params}`;
}
export function loadGoogleFontLink(fonts) {
let names = [];
if (!Array.isArray(fonts)) {
return Promise.resolve();
}
for (let name of fonts) {
if (linkFonts.has(name)) {
continue;
}
linkFonts.add(name);
names.push(name);
}
if (!names.length) {
return Promise.resolve();
}
if (typeof document !== 'undefined') {
return new Promise(resolve => {
let link = document.createElement("link");
link.rel = "stylesheet";
link.href = getGoogleFontLink(names);
link.onload = resolve;
link.onerror = resolve;
document.head.appendChild(link);
});
}
return Promise.resolve();
}
async function fetchCSS(names) {
let res = await fetch(getGoogleFontLink(names));
if (!res.ok) throw new Error(`Failed to fetch fonts: ${res.status}`);
return res.text();
}
function extractFonts(css) {
let blockRegex = /@font-face\s*{([^}]+)}/gi;
let fonts = [];
let seenUrls = new Set();
let match;
while ((match = blockRegex.exec(css)) !== null) {
let content = match[1];
let getProp = (prop) => {
let re = new RegExp(`${prop}:\\s*['"]?([^'";\\)]+)['"]?`, 'i');
let res = content.match(re);
return res ? res[1].trim() : null;
};
let urlMatch = content.match(/url\(([^)]+)\)/i);
let url = urlMatch ? urlMatch[1].replace(/['"]/g, '') : null;
let family = getProp('font-family');
if (family && url && !seenUrls.has(url)) {
seenUrls.add(url);
fonts.push({
family,
url,
weight: getProp('font-weight') || '400',
style: getProp('font-style') || 'normal'
});
}
}
if (!fonts.length) throw new Error('No fonts found in CSS');
return fonts;
}
async function toBase64(url) {
let cached = embedFonts.get(url);
if (cached) return cached;
let res = await fetch(url);
if (!res.ok) throw new Error(`Failed to fetch font (${res.status}): ${url}`);
let blob = await res.blob();
let base64 = await new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onloadend = () => {
let data = reader.result.split(',')[1];
resolve(data);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
embedFonts.set(url, base64);
return base64;
}
export async function loadGoogleFontEmbed(names = Array.from(linkFonts)) {
if (!Array.isArray(names) || !names.length) return '';
let filtered = unique(names);
if (!filtered.length) return '';
try {
let css = await fetchCSS(filtered);
let fonts = extractFonts(css);
let embedded = await Promise.all(
fonts.map(async ({ family, url }) => {
let base64 = await toBase64(url);
return `@font-face {\n font-family: "${family}";\n src: url("data:font/woff2;base64,${base64}") format("woff2");\n}`;
})
);
return embedded.join('\n');
} catch (error) {
console.warn('Error loading fonts:', error);
return '';
}
}