import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
import { hash } from "ohash";
import { findAll, generate, parse } from "css-tree";
import { ofetch } from "ofetch";

//#region src/css/parse.ts
const extractableKeyMap = {
	"src": "src",
	"font-display": "display",
	"font-weight": "weight",
	"font-style": "style",
	"font-feature-settings": "featureSettings",
	"font-variation-settings": "variationSettings",
	"unicode-range": "unicodeRange"
};
const formatPriorityList = Object.values({
	woff2: "woff2",
	woff: "woff",
	otf: "opentype",
	ttf: "truetype",
	eot: "embedded-opentype",
	svg: "svg"
});
function extractFontFaceData(css, family) {
	const fontFaces = [];
	for (const node of findAll(parse(css), (node) => node.type === "Atrule" && node.name === "font-face")) {
		/* v8 ignore next 3 */
		if (node.type !== "Atrule" || node.name !== "font-face") continue;
		if (family) {
			if (!node.block?.children.some((child) => {
				if (child.type !== "Declaration" || child.property !== "font-family") return false;
				const value = extractCSSValue(child);
				const slug = family.toLowerCase();
				if (typeof value === "string" && value.toLowerCase() === slug) return true;
				if (Array.isArray(value) && value.length > 0 && value.some((v) => v.toLowerCase() === slug)) return true;
				return false;
			})) continue;
		}
		const data = {};
		for (const child of node.block?.children || []) if (child.type === "Declaration" && child.property in extractableKeyMap) {
			const value = extractCSSValue(child);
			data[extractableKeyMap[child.property]] = ["src", "unicode-range"].includes(child.property) && !Array.isArray(value) ? [value] : value;
		}
		if (!data.src) continue;
		fontFaces.push(data);
	}
	return mergeFontSources(fontFaces);
}
function processRawValue(value) {
	return value.split(",").map((v) => v.trim().replace(/^(?<quote>['"])(.*)\k<quote>$/, "$2"));
}
function extractCSSValue(node) {
	if (node.value.type === "Raw") return processRawValue(node.value.value);
	const values = [];
	let buffer = "";
	for (const child of node.value.children) {
		if (child.type === "Function") {
			if (child.name === "local" && child.children.first?.type === "String") values.push({ name: child.children.first.value });
			if (child.name === "format") {
				if (child.children.first?.type === "String") values.at(-1).format = child.children.first.value;
				else if (child.children.first?.type === "Identifier") values.at(-1).format = child.children.first.name;
			}
			if (child.name === "tech") {
				if (child.children.first?.type === "String") values.at(-1).tech = child.children.first.value;
				else if (child.children.first?.type === "Identifier") values.at(-1).tech = child.children.first.name;
			}
		}
		if (child.type === "Url") values.push({ url: child.value });
		if (child.type === "Identifier") buffer = buffer ? `${buffer} ${child.name}` : child.name;
		if (child.type === "String") values.push(child.value);
		if (child.type === "Dimension") {
			const dimensionValue = child.value + child.unit;
			buffer = buffer ? `${buffer} ${dimensionValue}` : dimensionValue;
		}
		if (child.type === "Operator" && child.value === "," && buffer) {
			values.push(buffer);
			buffer = "";
		}
		if (child.type === "UnicodeRange") values.push(child.value);
		if (child.type === "Number") values.push(Number(child.value));
	}
	if (buffer) values.push(buffer);
	if (values.length === 1) return values[0];
	return values;
}
function mergeFontSources(data) {
	const mergedData = [];
	for (const face of data) {
		const keys = Object.keys(face).filter((k) => k !== "src");
		const existing = mergedData.find((f) => Object.keys(f).length === keys.length + 1 && keys.every((key) => f[key]?.toString() === face[key]?.toString()));
		if (existing) {
			for (const s of face.src) if (existing.src.every((src) => "url" in src ? !("url" in s) || s.url !== src.url : !("name" in s) || s.name !== src.name)) existing.src.push(s);
		} else mergedData.push(face);
	}
	for (const face of mergedData) face.src.sort((a, b) => {
		return ("format" in a ? formatPriorityList.indexOf(a.format || "woff2") : -2) - ("format" in b ? formatPriorityList.indexOf(b.format || "woff2") : -2);
	});
	return mergedData;
}

//#endregion
//#region src/fetch.ts
function mini$fetch(url, options) {
	const retries = options?.retries ?? 3;
	const retryDelay = options?.retryDelay ?? 1e3;
	return ofetch(url, {
		baseURL: options?.baseURL,
		query: options?.query,
		responseType: options?.responseType ?? "text",
		headers: options?.headers,
		retry: false
	}).catch((err) => {
		if (retries <= 0) throw err;
		console.warn(`Could not fetch from \`${(options?.baseURL ?? "") + url}\`. Will retry in \`${retryDelay}ms\`. \`${retries}\` retries left.`);
		return new Promise((resolve) => setTimeout(resolve, retryDelay)).then(() => mini$fetch(url, {
			...options,
			retries: retries - 1
		}));
	});
}
const $fetch = Object.assign(mini$fetch, { create: (defaults) => (url, options) => mini$fetch(url, {
	...defaults,
	...options
}) });

//#endregion
//#region src/utils.ts
function defineFontProvider(name, provider) {
	return ((options) => Object.assign(provider.bind(null, options || {}), {
		_name: name,
		_options: options
	}));
}
function prepareWeights({ inputWeights, weights, hasVariableWeights }) {
	const collectedWeights = [];
	for (const weight of inputWeights) {
		if (weight.includes(" ")) {
			if (hasVariableWeights) {
				collectedWeights.push(weight);
				continue;
			}
			const [min, max] = weight.split(" ");
			collectedWeights.push(...weights.filter((_w) => {
				const w = Number(_w);
				return w >= Number(min) && w <= Number(max);
			}).map((w) => String(w)));
			continue;
		}
		if (weights.includes(weight)) collectedWeights.push(weight);
	}
	return [...new Set(collectedWeights)].map((weight) => ({
		weight,
		variable: weight.includes(" ")
	}));
}
function splitCssIntoSubsets(input) {
	const data = [];
	const comments = [];
	const nodes = findAll(parse(input, {
		positions: true,
		onComment(value, loc) {
			comments.push({
				value: value.trim(),
				endLine: loc.end.line
			});
		}
	}), (node) => node.type === "Atrule" && node.name === "font-face");
	if (comments.length === 0) return [{
		subset: null,
		css: input
	}];
	for (const node of nodes) {
		const comment = comments.filter((comment) => comment.endLine < node.loc.start.line).at(-1);
		data.push({
			subset: comment?.value ?? null,
			css: generate(node)
		});
	}
	return data;
}
const formatMap = {
	woff2: "woff2",
	woff: "woff",
	otf: "opentype",
	ttf: "truetype",
	eot: "embedded-opentype"
};
/** Maps variation format strings (e.g. 'woff2-variations') to their base CSS format name (e.g. 'woff2') */
const variationFormatMap = {
	"woff2-variations": "woff2",
	"woff-variations": "woff",
	"opentype-variations": "opentype",
	"truetype-variations": "truetype"
};
function computeIdFromSource(source) {
	return "name" in source ? source.name : source.url;
}
function cleanFontFaces(fonts, _formats) {
	const formats = _formats.map((format) => formatMap[format]);
	const result = [];
	const hashToIndex = /* @__PURE__ */ new Map();
	for (const { src: _src, meta, ...font } of fonts) {
		const key = hash(font);
		const index = hashToIndex.get(key);
		const src = _src.map((source) => "name" in source ? source : {
			...source,
			...source.format ? { format: formatMap[source.format] ?? source.format } : {}
		}).filter((source) => {
			if ("name" in source) return true;
			if (!source.format) return true;
			if (formats.includes(source.format)) return true;
			const baseFormat = variationFormatMap[source.format];
			return !!baseFormat && formats.includes(baseFormat);
		});
		if (src.length === 0) continue;
		if (index === void 0) {
			hashToIndex.set(key, result.push({
				...font,
				...meta ? { meta } : {},
				src
			}) - 1);
			continue;
		}
		const existing = result[index];
		const ids = new Set(existing.src.map((source) => computeIdFromSource(source)));
		existing.src.push(...src.filter((source) => {
			const id = computeIdFromSource(source);
			return !ids.has(id) && ids.add(id);
		}));
	}
	return result;
}

//#endregion
//#region src/providers/adobe.ts
const fontCSSAPI = $fetch.create({ baseURL: "https://use.typekit.net" });
async function getAdobeFontMeta(id) {
	const { kit } = await $fetch(`https://typekit.com/api/v1/json/kits/${id}/published`, { responseType: "json" });
	return kit;
}
const KIT_REFRESH_TIMEOUT = 300 * 1e3;
var adobe_default = defineFontProvider("adobe", async (options, ctx) => {
	if (!options.id) return;
	const familyMap = /* @__PURE__ */ new Map();
	const notFoundFamilies = /* @__PURE__ */ new Set();
	const fonts = { kits: [] };
	let lastRefreshKitTime;
	let fetchKitsPromise;
	const kits = typeof options.id === "string" ? [options.id] : options.id;
	await fetchKits();
	async function fetchKits(bypassCache = false) {
		familyMap.clear();
		notFoundFamilies.clear();
		fonts.kits = [];
		await Promise.all(kits.map(async (id) => {
			let meta;
			const key = `adobe:meta-${id}.json`;
			if (bypassCache) {
				meta = await getAdobeFontMeta(id);
				await ctx.storage.setItem(key, meta);
			} else meta = await ctx.storage.getItem(key, () => getAdobeFontMeta(id));
			if (!meta) throw new TypeError("No font metadata found in adobe response.");
			fonts.kits.push(meta);
			for (const family of meta.families) familyMap.set(family.name, family.id);
		}));
	}
	async function getFontDetails(family, options) {
		options.weights = options.weights.map(String);
		for (const kit of fonts.kits) {
			const font = kit.families.find((f) => f.name === family);
			if (!font) continue;
			const weights = prepareWeights({
				inputWeights: options.weights,
				hasVariableWeights: false,
				weights: font.variations.map((v) => `${v.slice(-1)}00`)
			}).map((w) => w.weight);
			const styles = [];
			for (const style of font.variations) {
				if (style.includes("i") && !options.styles.includes("italic")) continue;
				if (!weights.includes(String(`${style.slice(-1)}00`))) continue;
				styles.push(style);
			}
			if (styles.length === 0) continue;
			return extractFontFaceData(await fontCSSAPI(`/${kit.id}.css`), font.css_names[0] ?? family.toLowerCase().split(" ").join("-")).filter((font) => {
				const [lowerWeight, upperWeight] = Array.isArray(font.weight) ? font.weight : [0, 0];
				return (!options.styles || !font.style || options.styles.includes(font.style)) && (!weights || !font.weight || Array.isArray(font.weight) ? weights.some((weight) => Number(weight) <= upperWeight || Number(weight) >= lowerWeight) : weights.includes(String(font.weight)));
			});
		}
		return [];
	}
	return {
		listFonts() {
			return [...familyMap.keys()];
		},
		async resolveFont(family, options) {
			if (notFoundFamilies.has(family)) return;
			if (!familyMap.has(family)) if (fetchKitsPromise) await fetchKitsPromise;
			else {
				const lastRefetch = lastRefreshKitTime || 0;
				const now = Date.now();
				if (now - lastRefetch > KIT_REFRESH_TIMEOUT) {
					lastRefreshKitTime = now;
					fetchKitsPromise = fetchKits(true).finally(() => {
						fetchKitsPromise = void 0;
					});
					await fetchKitsPromise;
				}
			}
			if (!familyMap.has(family)) {
				notFoundFamilies.add(family);
				return;
			}
			return { fonts: await ctx.storage.getItem(`adobe:${family}-${hash(options)}-data.json`, () => getFontDetails(family, options)) };
		}
	};
});

//#endregion
//#region src/providers/bunny.ts
const fontAPI$2 = $fetch.create({ baseURL: "https://fonts.bunny.net" });
var bunny_default = defineFontProvider("bunny", async (_options, ctx) => {
	const familyMap = /* @__PURE__ */ new Map();
	const fonts = await ctx.storage.getItem("bunny:meta.json", () => fontAPI$2("/list", { responseType: "json" }));
	for (const [id, family] of Object.entries(fonts)) familyMap.set(family.familyName, id);
	async function getFontDetails(family, options) {
		const id = familyMap.get(family);
		const font = fonts[id];
		const weights = prepareWeights({
			inputWeights: options.weights,
			hasVariableWeights: false,
			weights: font.weights.map(String)
		});
		const styleMap = {
			italic: "i",
			oblique: "i",
			normal: ""
		};
		const styles = new Set(options.styles.map((i) => styleMap[i]));
		if (weights.length === 0 || styles.size === 0) return [];
		const css = await fontAPI$2("/css", { query: { family: `${id}:${weights.flatMap((w) => [...styles].map((s) => `${w.weight}${s}`)).join(",")}` } });
		const resolvedFontFaceData = [];
		const groups = splitCssIntoSubsets(css).filter((group) => group.subset ? options.subsets.includes(group.subset) : true);
		for (const group of groups) {
			const data = extractFontFaceData(group.css);
			data.map((f) => {
				f.meta ??= {};
				if (group.subset) f.meta.subset = group.subset;
				return f;
			});
			resolvedFontFaceData.push(...data);
		}
		return cleanFontFaces(resolvedFontFaceData, options.formats);
	}
	return {
		listFonts() {
			return [...familyMap.keys()];
		},
		async resolveFont(fontFamily, defaults) {
			if (!familyMap.has(fontFamily)) return;
			return { fonts: await ctx.storage.getItem(`bunny:${fontFamily}-${hash(defaults)}-data.json`, () => getFontDetails(fontFamily, defaults)) };
		}
	};
});

//#endregion
//#region src/providers/fontshare.ts
const fontAPI$1 = $fetch.create({ baseURL: "https://api.fontshare.com/v2" });
var fontshare_default = defineFontProvider("fontshare", async (_options, ctx) => {
	const fontshareFamilies = /* @__PURE__ */ new Set();
	const fonts = await ctx.storage.getItem("fontshare:meta.json", async () => {
		const fonts = [];
		let offset = 0;
		let chunk;
		do {
			chunk = await fontAPI$1("/fonts", {
				responseType: "json",
				query: {
					offset,
					limit: 100
				}
			});
			fonts.push(...chunk.fonts);
			offset++;
		} while (chunk.has_more);
		return fonts;
	});
	for (const font of fonts) fontshareFamilies.add(font.name);
	async function getFontDetails(family, options) {
		const font = fonts.find((f) => f.name === family);
		const numbers = [];
		const weights = prepareWeights({
			inputWeights: options.weights,
			hasVariableWeights: false,
			weights: font.styles.map((s) => String(s.weight.weight))
		}).map((w) => w.weight);
		for (const style of font.styles) {
			if (style.is_italic && !options.styles.includes("italic")) continue;
			if (!style.is_italic && !options.styles.includes("normal")) continue;
			if (!weights.includes(String(style.weight.weight))) continue;
			numbers.push(style.weight.number);
		}
		if (numbers.length === 0) return [];
		return cleanFontFaces(extractFontFaceData(await fontAPI$1(`/css?f[]=${`${font.slug}@${numbers.join(",")}`}`)), options.formats);
	}
	return {
		listFonts() {
			return [...fontshareFamilies];
		},
		async resolveFont(fontFamily, defaults) {
			if (!fontshareFamilies.has(fontFamily)) return;
			return { fonts: await ctx.storage.getItem(`fontshare:${fontFamily}-${hash(defaults)}-data.json`, () => getFontDetails(fontFamily, defaults)) };
		}
	};
});

//#endregion
//#region src/providers/fontsource.ts
const fontAPI = $fetch.create({ baseURL: "https://api.fontsource.org/v1" });
var fontsource_default = defineFontProvider("fontsource", async (_options, ctx) => {
	const fonts = await ctx.storage.getItem("fontsource:meta.json", () => fontAPI("/fonts", { responseType: "json" }));
	const familyMap = /* @__PURE__ */ new Map();
	for (const meta of fonts) familyMap.set(meta.family, meta);
	async function getFontDetails(family, options) {
		const font = familyMap.get(family);
		const weights = prepareWeights({
			inputWeights: options.weights,
			hasVariableWeights: font.variable,
			weights: font.weights.map(String)
		});
		const styles = options.styles.filter((style) => font.styles.includes(style));
		const subsets = options.subsets ? options.subsets.filter((subset) => font.subsets.includes(subset)) : [font.defSubset];
		if (weights.length === 0 || styles.length === 0) return [];
		const fontDetail = await fontAPI(`/fonts/${font.id}`, { responseType: "json" });
		const fontFaceData = [];
		for (const subset of subsets) for (const style of styles) for (const { weight, variable } of weights) {
			if (variable) {
				try {
					const variableAxes = await ctx.storage.getItem(`fontsource:${font.family}-axes.json`, () => fontAPI(`/variable/${font.id}`, { responseType: "json" }));
					if (variableAxes && variableAxes.axes.wght) fontFaceData.push({
						style,
						weight: [Number(variableAxes.axes.wght.min), Number(variableAxes.axes.wght.max)],
						src: [{
							url: `https://cdn.jsdelivr.net/fontsource/fonts/${font.id}:vf@latest/${subset}-wght-${style}.woff2`,
							format: "woff2"
						}],
						unicodeRange: fontDetail.unicodeRange[subset]?.split(","),
						meta: { subset }
					});
				} catch {
					console.error(`Could not download variable axes metadata for \`${font.family}\` from \`fontsource\`. \`unifont\` will not be able to inject variable axes for ${font.family}.`);
				}
				continue;
			}
			const variantUrl = fontDetail.variants[weight][style][subset].url;
			fontFaceData.push({
				style,
				weight,
				src: Object.entries(variantUrl).map(([format, url]) => ({
					url,
					format
				})),
				unicodeRange: fontDetail.unicodeRange[subset]?.split(","),
				meta: { subset }
			});
		}
		return cleanFontFaces(fontFaceData, options.formats);
	}
	return {
		listFonts() {
			return [...familyMap.keys()];
		},
		async resolveFont(fontFamily, options) {
			if (!familyMap.has(fontFamily)) return;
			return { fonts: await ctx.storage.getItem(`fontsource:${fontFamily}-${hash(options)}-data.json`, () => getFontDetails(fontFamily, options)) };
		}
	};
});

//#endregion
//#region src/providers/google.ts
const userAgents = {
	eot: "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)",
	ttf: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1",
	woff: "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0",
	woff2: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
};
var google_default = defineFontProvider("google", async (providerOptions, ctx) => {
	const googleFonts = await ctx.storage.getItem("google:meta.json", () => $fetch("https://fonts.google.com/metadata/fonts", { responseType: "json" }).then((r) => r.familyMetadataList));
	const styleMap = {
		italic: "1",
		oblique: "1",
		normal: "0"
	};
	async function getFontDetails(family, options) {
		const font = googleFonts.find((font) => font.family === family);
		const styles = [...new Set(options.styles.map((i) => styleMap[i]))].sort();
		const glyphs = (options.options?.experimental?.glyphs ?? providerOptions.experimental?.glyphs?.[family])?.join("");
		const weights = prepareWeights({
			inputWeights: options.weights,
			hasVariableWeights: font.axes.some((a) => a.tag === "wght"),
			weights: Object.keys(font.fonts)
		}).map((v) => v.variable ? {
			weight: v.weight.replace(" ", ".."),
			variable: v.variable
		} : v);
		if (weights.length === 0 || styles.length === 0) return [];
		const resolvedAxes = [];
		let resolvedVariants = [];
		const variableAxis = options.options?.experimental?.variableAxis ?? providerOptions.experimental?.variableAxis?.[family];
		const candidateAxes = [
			"wght",
			"ital",
			...Object.keys(variableAxis ?? {})
		].sort(googleFlavoredSorting);
		for (const axis of candidateAxes) {
			const axisValue = {
				wght: weights.map((v) => v.weight),
				ital: styles
			}[axis] ?? variableAxis[axis].map((v) => Array.isArray(v) ? `${v[0]}..${v[1]}` : v);
			if (resolvedVariants.length === 0) resolvedVariants = axisValue;
			else resolvedVariants = resolvedVariants.flatMap((v) => [...axisValue].map((o) => [v, o].join(","))).sort();
			resolvedAxes.push(axis);
		}
		let priority = 0;
		const resolvedFontFaceData = [];
		for (const format of options.formats) {
			const userAgent = userAgents[format];
			if (!userAgent) continue;
			const groups = splitCssIntoSubsets(await $fetch("/css2", {
				baseURL: "https://fonts.googleapis.com",
				headers: { "user-agent": userAgent },
				query: {
					family: `${family}:${resolvedAxes.join(",")}@${resolvedVariants.join(";")}`,
					...glyphs && { text: glyphs }
				}
			})).filter((group) => group.subset ? options.subsets.includes(group.subset) : true);
			for (const group of groups) {
				const data = extractFontFaceData(group.css);
				data.map((f) => {
					f.meta ??= {};
					f.meta.priority = priority;
					if (group.subset) f.meta.subset = group.subset;
					return f;
				});
				resolvedFontFaceData.push(...data);
			}
			priority++;
		}
		return cleanFontFaces(resolvedFontFaceData, options.formats);
	}
	return {
		listFonts() {
			return googleFonts.map((font) => font.family);
		},
		async resolveFont(fontFamily, options) {
			if (!googleFonts.some((font) => font.family === fontFamily)) return;
			return { fonts: await ctx.storage.getItem(`google:${fontFamily}-${hash(options)}-data.json`, () => getFontDetails(fontFamily, options)) };
		}
	};
});
function googleFlavoredSorting(a, b) {
	const isALowercase = a.charAt(0) === a.charAt(0).toLowerCase();
	const isBLowercase = b.charAt(0) === b.charAt(0).toLowerCase();
	if (isALowercase !== isBLowercase) return Number(isBLowercase) - Number(isALowercase);
	else return a.localeCompare(b);
}

//#endregion
//#region src/providers/googleicons.ts
var googleicons_default = defineFontProvider("googleicons", async (providerOptions, ctx) => {
	const googleIcons = await ctx.storage.getItem("googleicons:meta.json", async () => {
		const data = await $fetch("https://fonts.google.com/metadata/icons?key=material_symbols&incomplete=true");
		return JSON.parse(data.substring(data.indexOf("\n") + 1)).families;
	});
	async function getFontDetails(family, options) {
		const iconNames = (options.options?.experimental?.glyphs ?? providerOptions.experimental?.glyphs?.[family])?.join("");
		let css = "";
		for (const format of options.formats) {
			const userAgent = userAgents[format];
			if (!userAgent) continue;
			if (family.includes("Icons")) css += await $fetch("/icon", {
				baseURL: "https://fonts.googleapis.com",
				headers: { "user-agent": userAgent },
				query: { family }
			});
			else css += await $fetch("/css2", {
				baseURL: "https://fonts.googleapis.com",
				headers: { "user-agent": userAgent },
				query: {
					family: `${family}:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200`,
					...iconNames && { icon_names: iconNames }
				}
			});
		}
		return cleanFontFaces(extractFontFaceData(css), options.formats);
	}
	return {
		listFonts() {
			return googleIcons;
		},
		async resolveFont(fontFamily, options) {
			if (!googleIcons.includes(fontFamily)) return;
			return { fonts: await ctx.storage.getItem(`googleicons:${fontFamily}-${hash(options)}-data.json`, () => getFontDetails(fontFamily, options)) };
		}
	};
});

//#endregion
//#region src/providers/npm.ts
const DEFAULT_CDN = "https://cdn.jsdelivr.net/npm";
const KNOWN_FONT_PACKAGES = [
	{
		match: /^@fontsource-variable\//,
		family: (pkg) => {
			return `${slugToFamily(pkg.replace("@fontsource-variable/", ""))} Variable`;
		}
	},
	{
		match: /^@fontsource\//,
		family: (pkg) => {
			return slugToFamily(pkg.replace("@fontsource/", ""));
		}
	},
	{
		match: /^cal-sans$/,
		family: () => "Cal Sans"
	}
];
/** Convert a slug like "geist-sans" to "Geist Sans" */
function slugToFamily(slug) {
	return slug.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
}
/** Convert a family name like "Geist Sans" to a fontsource slug "geist-sans" */
function familyToSlug(family) {
	return family.toLowerCase().replace(/\s+/g, "-");
}
/**
* Guess the npm package name and CSS file for a font family that wasn't
* found in the auto-detected packages. Uses fontsource conventions as fallback.
*/
function guessPackageForFamily(family) {
	if (family.endsWith(" Variable")) return {
		pkgName: `@fontsource-variable/${familyToSlug(family.replace(/ Variable$/, ""))}`,
		file: "index.css"
	};
	return {
		pkgName: `@fontsource/${familyToSlug(family)}`,
		file: "index.css"
	};
}
var npm_default = defineFontProvider("npm", (providerOptions, ctx) => {
	const cdn = providerOptions.cdn || DEFAULT_CDN;
	const remote = providerOptions.remote ?? true;
	const npmFetch = $fetch.create({ baseURL: cdn });
	const readFile = providerOptions.readFile;
	const root = providerOptions.root || ".";
	let detectedFonts;
	let detectedFontsHash;
	async function getDetectedFonts() {
		if (!readFile) return detectedFonts ??= /* @__PURE__ */ new Map();
		let pkgJsonContent;
		try {
			pkgJsonContent = await readFile(`${root}/package.json`);
		} catch {
			return detectedFonts ??= /* @__PURE__ */ new Map();
		}
		if (!pkgJsonContent) return detectedFonts ??= /* @__PURE__ */ new Map();
		const contentHash = hash(pkgJsonContent);
		if (detectedFonts && detectedFontsHash === contentHash) return detectedFonts;
		detectedFontsHash = contentHash;
		detectedFonts = /* @__PURE__ */ new Map();
		try {
			const pkgJson = JSON.parse(pkgJsonContent);
			const allDeps = {
				...pkgJson.dependencies,
				...pkgJson.devDependencies
			};
			for (const depName of Object.keys(allDeps)) for (const pattern of KNOWN_FONT_PACKAGES) if (pattern.match.test(depName)) {
				const family = pattern.family(depName);
				detectedFonts.set(family.toLowerCase(), {
					family,
					pkgName: depName,
					file: pattern.file || "index.css"
				});
				break;
			}
		} catch {}
		return detectedFonts;
	}
	function resolveUrlsToAbsolute(fontFaces, baseUrl) {
		for (const face of fontFaces) if (Array.isArray(face.src)) face.src = face.src.map((src) => {
			if ("url" in src) {
				const url = src.url;
				if (url.startsWith("http") || url.startsWith("data:") || url.startsWith("//")) return src;
				return {
					...src,
					url: new URL(url, baseUrl).href
				};
			}
			return src;
		});
	}
	async function resolveFromLocal(pkgName, cssFile, family, formats) {
		if (!readFile) return null;
		const css = await readFile(`${root}/node_modules/${pkgName}/${cssFile}`).catch(() => null);
		if (!css) return null;
		const fontFaces = extractFontFaceData(css, family);
		if (fontFaces.length === 0) return null;
		let version = "latest";
		try {
			const localPkgJson = await readFile(`${root}/node_modules/${pkgName}/package.json`);
			if (localPkgJson) {
				const parsed = JSON.parse(localPkgJson);
				if (parsed.version) version = parsed.version;
			}
		} catch {}
		resolveUrlsToAbsolute(fontFaces, `${cdn}/${pkgName}@${version}/`);
		return cleanFontFaces(fontFaces, formats);
	}
	async function resolveFromCdn(pkgName, pkgVersion, cssFile, family, formats) {
		let css;
		try {
			css = await npmFetch(`${pkgName}@${pkgVersion}/${cssFile}`);
		} catch {
			return null;
		}
		if (!css) return null;
		const fontFaces = extractFontFaceData(css, family);
		resolveUrlsToAbsolute(fontFaces, `${cdn}/${pkgName}@${pkgVersion}/`);
		return cleanFontFaces(fontFaces, formats);
	}
	return {
		async listFonts() {
			const fonts = await getDetectedFonts();
			if (fonts.size === 0) return;
			return [...fonts.values()].map((f) => f.family);
		},
		async resolveFont(family, options) {
			const familyOptions = options.options || {};
			let pkgName;
			let cssFile;
			let pkgVersion;
			if (familyOptions.package) {
				pkgName = familyOptions.package;
				cssFile = familyOptions.file || "index.css";
				pkgVersion = familyOptions.version || "latest";
			} else {
				const detected = (await getDetectedFonts()).get(family.toLowerCase());
				if (detected) {
					pkgName = detected.pkgName;
					cssFile = familyOptions.file || detected.file;
					pkgVersion = familyOptions.version || "latest";
				} else {
					const guessed = guessPackageForFamily(family);
					pkgName = guessed.pkgName;
					cssFile = familyOptions.file || guessed.file;
					pkgVersion = familyOptions.version || "latest";
				}
			}
			const key = `npm:${pkgName}/${cssFile}-${hash(options)}`;
			const fonts = await ctx.storage.getItem(key, async () => {
				const localResult = await resolveFromLocal(pkgName, cssFile, family, options.formats);
				if (localResult) return localResult;
				if (!remote) return null;
				return await resolveFromCdn(pkgName, pkgVersion, cssFile, family, options.formats);
			});
			if (!fonts) return;
			return { fonts };
		}
	};
});

//#endregion
//#region src/providers.ts
var providers_exports = /* @__PURE__ */ __exportAll({
	adobe: () => adobe_default,
	bunny: () => bunny_default,
	fontshare: () => fontshare_default,
	fontsource: () => fontsource_default,
	google: () => google_default,
	googleicons: () => googleicons_default,
	npm: () => npm_default
});

//#endregion
//#region package.json
var version = "0.7.4";

//#endregion
//#region src/cache.ts
function memoryStorage() {
	const cache = /* @__PURE__ */ new Map();
	return {
		getItem(key) {
			return cache.get(key);
		},
		setItem(key, value) {
			cache.set(key, value);
		}
	};
}
const ONE_WEEK = 1e3 * 60 * 60 * 24 * 7;
function createAsyncStorage(storage, options = {}) {
	const prefix = options?.cachedBy?.length ? `${createCacheKey(...options.cachedBy)}:` : "";
	const resolveKey = (key) => `${prefix}${key}`;
	return {
		async getItem(key, init) {
			const resolvedKey = resolveKey(key);
			const now = Date.now();
			const res = await storage.getItem(resolvedKey);
			if (res && res.expires > now && res.version === version) return res.data;
			if (!init) return null;
			const data = await init();
			await storage.setItem(resolvedKey, {
				expires: now + ONE_WEEK,
				version,
				data
			});
			return data;
		},
		async setItem(key, data) {
			await storage.setItem(resolveKey(key), {
				expires: Date.now() + ONE_WEEK,
				version,
				data
			});
		}
	};
}
function createCacheKey(...fragments) {
	return fragments.map((f) => {
		return sanitize(typeof f === "string" ? f : hash(f));
	}).join(":");
}
function sanitize(input) {
	if (!input) return "";
	return input.replace(/[^\w.-]/g, "_");
}

//#endregion
//#region src/unifont.ts
const defaultResolveOptions = {
	weights: ["400"],
	styles: ["normal", "italic"],
	subsets: [
		"cyrillic-ext",
		"cyrillic",
		"greek-ext",
		"greek",
		"vietnamese",
		"latin-ext",
		"latin"
	],
	formats: ["woff2"]
};
async function createUnifont(providers, unifontOptions) {
	const stack = {};
	const storage = unifontOptions?.storage ?? memoryStorage();
	for (const provider of providers) stack[provider._name] = void 0;
	await Promise.all(providers.map(async (provider) => {
		const context = { storage: createAsyncStorage(storage, { cachedBy: [provider._name, provider._options] }) };
		try {
			const initializedProvider = await provider(context);
			if (initializedProvider) stack[provider._name] = initializedProvider;
		} catch (cause) {
			const message = `Could not initialize provider \`${provider._name}\`. \`unifont\` will not be able to process fonts provided by this provider.`;
			if (unifontOptions?.throwOnError) throw new Error(message, { cause });
			console.error(message, cause);
		}
		if (!stack[provider._name]?.resolveFont) delete stack[provider._name];
	}));
	const allProviders = Object.keys(stack);
	async function resolveFont(fontFamily, options = {}, providers = allProviders) {
		const mergedOptions = {
			...defaultResolveOptions,
			...options
		};
		for (const id of providers) {
			const provider = stack[id];
			try {
				const result = await provider?.resolveFont(fontFamily, {
					...mergedOptions,
					options: mergedOptions.options?.[id]
				});
				if (result) return {
					provider: id,
					...result
				};
			} catch (cause) {
				const message = `Could not resolve font face for \`${fontFamily}\` from \`${id}\` provider.`;
				if (unifontOptions?.throwOnError) throw new Error(message, { cause });
				console.error(message, cause);
			}
		}
		return { fonts: [] };
	}
	async function listFonts(providers = allProviders) {
		let names;
		for (const id of providers) {
			const provider = stack[id];
			try {
				const result = await provider?.listFonts?.();
				if (result) {
					names ??= [];
					names.push(...result);
				}
			} catch (cause) {
				const message = `Could not list names from \`${id}\` provider.`;
				if (unifontOptions?.throwOnError) throw new Error(message, { cause });
				console.error(message, cause);
			}
		}
		return names;
	}
	return {
		resolveFont,
		listFonts
	};
}

//#endregion
export { createUnifont, defaultResolveOptions, defineFontProvider, providers_exports as providers };