full site update

This commit is contained in:
2025-07-24 18:46:24 +02:00
parent bfe2b90d8d
commit 37a6e0ab31
6912 changed files with 540482 additions and 361712 deletions

21
node_modules/unifont/LICENCE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Daniel Roe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

80
node_modules/unifont/README.md generated vendored Normal file
View File

@@ -0,0 +1,80 @@
# unifont
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![Github Actions][github-actions-src]][github-actions-href]
[![Codecov][codecov-src]][codecov-href]
> Framework agnostic tools for accessing data from font CDNs and providers
## Usage
Install package:
```sh
# npm
npm install unifont
```
```js
import { createUnifont, providers } from 'unifont'
const unifont = await createUnifont([
providers.google(),
])
const fonts = await unifont.resolveFont('Poppins')
console.log(fonts)
const availableFonts = await unifont.listFonts()
console.log(availableFonts)
```
In most environments, you will want to cache the results of font APIs to avoid unnecessary hits to them. By default `unifont` caches font data in memory.
For full control, `unifont` exposes a storage API which is compatible with `unstorage`. It simply needs to expose a `getItem` and `setItem` method.
```ts
import { createUnifont, providers } from 'unifont'
import { createStorage } from 'unstorage'
import fsDriver from 'unstorage/drivers/fs-lite'
const storage = createStorage({
driver: fsDriver({ base: 'node_modules/.cache/unifont' }),
})
const cachedUnifont = await createUnifont([providers.google()], { storage })
console.log(await cachedUnifont.resolveFont('Poppins'))
// cached data is stored in `node_modules/.cache/unifont`
```
For more about the storage drivers exposed from `unstorage`, check out https://unstorage.unjs.io.
## 💻 Development
- Clone this repository
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
- Install dependencies using `pnpm install`
- Run interactive tests using `pnpm dev`
## License
Made with ❤️
Published under [MIT License](./LICENCE).
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/unifont?style=flat-square
[npm-version-href]: https://npmjs.com/package/unifont
[npm-downloads-src]: https://img.shields.io/npm/dm/unifont?style=flat-square
[npm-downloads-href]: https://npm.chart.dev/unifont
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/unjs/unifont/ci.yml?branch=main&style=flat-square
[github-actions-href]: https://github.com/unjs/unifont/actions?query=workflow%3Aci
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/unifont/main?style=flat-square
[codecov-href]: https://codecov.io/gh/unjs/unifont

163
node_modules/unifont/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,163 @@
type Awaitable$1<T> = T | Promise<T>;
type StorageValue = string | Record<string, unknown>;
interface Storage {
getItem: (key: string) => Awaitable$1<any | null>;
setItem: <T extends StorageValue = StorageValue>(key: string, value: T) => Awaitable$1<void>;
}
type Awaitable<T> = T | Promise<T>;
interface ProviderContext {
storage: {
getItem: {
<T = unknown>(key: string): Promise<T | null>;
<T = unknown>(key: string, init: () => Awaitable<T>): Promise<T>;
};
setItem: (key: string, value: unknown) => Awaitable<void>;
};
}
type FontStyles = 'normal' | 'italic' | 'oblique';
interface ResolveFontOptions {
weights: string[];
styles: FontStyles[];
subsets: string[];
fallbacks?: string[];
}
interface RemoteFontSource {
url: string;
originalURL?: string;
format?: string;
tech?: string;
}
interface LocalFontSource {
name: string;
}
interface FontFaceMeta {
/** The priority of the font face, usually used to indicate fallbacks. Smaller is more prioritized. */
priority?: number;
/**
* A `RequestInit` object that should be used when fetching this font. This can be useful for
* adding authorization headers and other metadata required for a font request.
* @see https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
*/
init?: RequestInit;
}
interface FontFaceData {
src: Array<LocalFontSource | RemoteFontSource>;
/**
* The font-display descriptor.
* @default 'swap'
*/
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
/** A font-weight value. */
weight?: string | number | [number, number];
/** A font-stretch value. */
stretch?: string;
/** A font-style value. */
style?: string;
/** The range of Unicode code points to be used from the font. */
unicodeRange?: string[];
/** Allows control over advanced typographic features in OpenType fonts. */
featureSettings?: string;
/** Allows low-level control over OpenType or TrueType font variations, by specifying the four letter axis names of the features to vary, along with their variation values. */
variationSettings?: string;
/** Metadata for the font face used by unifont */
meta?: FontFaceMeta;
}
interface ResolveFontResult {
/**
* Return data used to generate @font-face declarations.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
*/
fonts: FontFaceData[];
fallbacks?: string[];
}
interface InitializedProvider {
resolveFont: (family: string, options: ResolveFontOptions) => Awaitable<ResolveFontResult | undefined>;
listFonts?: (() => Awaitable<string[] | undefined>) | undefined;
}
interface ProviderDefinition<T = unknown> {
(options: T, ctx: ProviderContext): Awaitable<InitializedProvider | undefined>;
}
interface Provider {
_name: string;
(ctx: ProviderContext): Awaitable<InitializedProvider | undefined>;
}
type ProviderFactory<T = unknown> = unknown extends T ? () => Provider : Partial<T> extends T ? (options?: T) => Provider : (options: T) => Provider;
interface ProviderOption$2 {
id: string[] | string;
}
declare const _default$5: (options: ProviderOption$2) => Provider;
declare const _default$4: () => Provider;
declare const _default$3: () => Provider;
declare const _default$2: () => Provider;
type VariableAxis = 'opsz' | 'slnt' | 'wdth' | (string & {});
interface ProviderOption$1 {
experimental?: {
/**
* Experimental: Setting variable axis configuration on a per-font basis.
*/
variableAxis?: {
[key: string]: Partial<Record<VariableAxis, ([string, string] | string)[]>>;
};
/**
* Experimental: Specifying a list of glyphs to be included in the font for each font family.
* This can reduce the size of the font file.
*/
glyphs?: {
[fontFamily: string]: string[];
};
};
}
declare const _default$1: (options?: ProviderOption$1 | undefined) => Provider;
interface ProviderOption {
experimental?: {
/**
* Experimental: Specifying a list of icons to be included in the font for each font family.
* This can reduce the size of the font file.
*
* **Only available when resolving the new `Material Symbols` icons.**
*/
glyphs?: {
[fontFamily: string]: string[];
};
};
}
declare const _default: (options?: ProviderOption | undefined) => Provider;
declare namespace providers {
export {
_default$5 as adobe,
_default$4 as bunny,
_default$3 as fontshare,
_default$2 as fontsource,
_default$1 as google,
_default as googleicons,
};
}
declare function defineFontProvider<T = unknown>(name: string, provider: ProviderDefinition<T>): ProviderFactory<T>;
interface UnifontOptions {
storage?: Storage;
}
interface Unifont {
resolveFont: (fontFamily: string, options?: Partial<ResolveFontOptions>, providers?: string[]) => Promise<ResolveFontResult & {
provider?: string;
}>;
/** @deprecated use `resolveFont` */
resolveFontFace: (fontFamily: string, options?: Partial<ResolveFontOptions>, providers?: string[]) => Promise<ResolveFontResult & {
provider?: string;
}>;
listFonts: (providers?: string[]) => Promise<string[] | undefined>;
}
declare const defaultResolveOptions: ResolveFontOptions;
declare function createUnifont(providers: Provider[], options?: UnifontOptions): Promise<Unifont>;
export { createUnifont, defaultResolveOptions, defineFontProvider, providers };
export type { FontFaceData, FontFaceMeta, FontStyles, LocalFontSource, Provider, ProviderContext, ProviderDefinition, ProviderFactory, RemoteFontSource, ResolveFontOptions, Unifont, UnifontOptions };

772
node_modules/unifont/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,772 @@
import { hash } from 'ohash';
import { findAll, parse, generate } from 'css-tree';
import { ofetch } from 'ofetch';
const version = "0.5.2";
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) {
return {
async getItem(key, init) {
const now = Date.now();
const res = await storage.getItem(key);
if (res && res.expires > now && res.version === version) {
return res.data;
}
if (!init) {
return null;
}
const data = await init();
await storage.setItem(key, { expires: now + ONE_WEEK, version, data });
return data;
},
async setItem(key, data) {
await storage.setItem(key, { expires: Date.now() + ONE_WEEK, version, data });
}
};
}
const extractableKeyMap = {
"src": "src",
"font-display": "display",
"font-weight": "weight",
"font-style": "style",
"font-feature-settings": "featureSettings",
"font-variations-settings": "variationSettings",
"unicode-range": "unicodeRange"
};
const formatMap = {
woff2: "woff2",
woff: "woff",
otf: "opentype",
ttf: "truetype",
eot: "embedded-opentype",
svg: "svg"
};
const formatPriorityList = Object.values(formatMap);
function extractFontFaceData(css, family) {
const fontFaces = [];
for (const node of findAll(parse(css), (node2) => node2.type === "Atrule" && node2.name === "font-face")) {
if (node.type !== "Atrule" || node.name !== "font-face") {
continue;
}
if (family) {
const isCorrectFontFace = 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;
});
if (!isCorrectFontFace) {
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) => {
const aIndex = "format" in a ? formatPriorityList.indexOf(a.format || "woff2") : -2;
const bIndex = "format" in b ? formatPriorityList.indexOf(b.format || "woff2") : -2;
return aIndex - bIndex;
});
}
return mergedData;
}
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
})
});
function defineFontProvider(name, provider) {
return (options) => Object.assign(provider.bind(null, options || {}), { _name: name });
}
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(" ")
}));
}
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 = 5 * 60 * 1e3;
const adobe = defineFontProvider("adobe", async (options, ctx) => {
if (!options.id) {
return;
}
const familyMap = /* @__PURE__ */ new Map();
const notFoundFamilies = /* @__PURE__ */ new Set();
const fonts = {
kits: []
};
let lastRefreshKitTime;
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, options2) {
options2.weights = options2.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: options2.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") && !options2.styles.includes("italic")) {
continue;
}
if (!weights.includes(String(`${style.slice(-1)}00`))) {
continue;
}
styles.push(style);
}
if (styles.length === 0) {
continue;
}
const css = await fontCSSAPI(`/${kit.id}.css`);
const cssName = font.css_names[0] ?? family.toLowerCase().split(" ").join("-");
return extractFontFaceData(css, cssName).filter((font2) => {
const [lowerWeight, upperWeight] = Array.isArray(font2.weight) ? font2.weight : [0, 0];
return (!options2.styles || !font2.style || options2.styles.includes(font2.style)) && (!weights || !font2.weight || Array.isArray(font2.weight) ? weights.some((weight) => Number(weight) <= upperWeight || Number(weight) >= lowerWeight) : weights.includes(String(font2.weight)));
});
}
return [];
}
return {
listFonts() {
return [...familyMap.keys()];
},
async resolveFont(family, options2) {
if (notFoundFamilies.has(family)) {
return;
}
if (!familyMap.has(family)) {
const lastRefetch = lastRefreshKitTime || 0;
const now = Date.now();
if (now - lastRefetch > KIT_REFRESH_TIMEOUT) {
lastRefreshKitTime = Date.now();
await fetchKits(true);
}
}
if (!familyMap.has(family)) {
notFoundFamilies.add(family);
return;
}
const fonts2 = await ctx.storage.getItem(`adobe:${family}-${hash(options2)}-data.json`, () => getFontDetails(family, options2));
return { fonts: fonts2 };
}
};
});
const fontAPI$2 = $fetch.create({ baseURL: "https://fonts.bunny.net" });
const bunny = 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 resolvedVariants = weights.flatMap((w) => [...styles].map((s) => `${w.weight}${s}`));
const css = await fontAPI$2("/css", {
query: {
family: `${id}:${resolvedVariants.join(",")}`
}
});
return extractFontFaceData(css);
}
return {
listFonts() {
return [...familyMap.keys()];
},
async resolveFont(fontFamily, defaults) {
if (!familyMap.has(fontFamily)) {
return;
}
const fonts2 = await ctx.storage.getItem(`bunny:${fontFamily}-${hash(defaults)}-data.json`, () => getFontDetails(fontFamily, defaults));
return { fonts: fonts2 };
}
};
});
const fontAPI$1 = $fetch.create({ baseURL: "https://api.fontshare.com/v2" });
const fontshare = defineFontProvider("fontshare", async (_options, ctx) => {
const fontshareFamilies = /* @__PURE__ */ new Set();
const fonts = await ctx.storage.getItem("fontshare:meta.json", async () => {
const fonts2 = [];
let offset = 0;
let chunk;
do {
chunk = await fontAPI$1("/fonts", {
responseType: "json",
query: {
offset,
limit: 100
}
});
fonts2.push(...chunk.fonts);
offset++;
} while (chunk.has_more);
return fonts2;
});
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 [];
const css = await fontAPI$1(`/css?f[]=${`${font.slug}@${numbers.join(",")}`}`);
return extractFontFaceData(css);
}
return {
listFonts() {
return [...fontshareFamilies];
},
async resolveFont(fontFamily, defaults) {
if (!fontshareFamilies.has(fontFamily)) {
return;
}
const fonts2 = await ctx.storage.getItem(`fontshare:${fontFamily}-${hash(defaults)}-data.json`, () => getFontDetails(fontFamily, defaults));
return { fonts: fonts2 };
}
};
});
const fontAPI = $fetch.create({ baseURL: "https://api.fontsource.org/v1" });
const fontsource = 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(",")
});
}
} 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(",")
});
}
}
}
return fontFaceData;
}
return {
listFonts() {
return [...familyMap.keys()];
},
async resolveFont(fontFamily, options) {
if (!familyMap.has(fontFamily)) {
return;
}
const fonts2 = await ctx.storage.getItem(`fontsource:${fontFamily}-${hash(options)}-data.json`, () => getFontDetails(fontFamily, options));
return { fonts: fonts2 };
}
};
});
function splitCssIntoSubsets(input) {
const data = [];
const comments = [];
const nodes = findAll(
parse(input, {
positions: true,
// Comments are not part of the tree. We rely on the positions to infer the subset
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((comment2) => comment2.endLine < node.loc.start.line).at(-1);
if (!comment)
continue;
data.push({ subset: comment.value, css: generate(node) });
}
return data;
}
const google = defineFontProvider("google", async (_options = {}, 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"
};
const userAgents = {
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",
ttf: "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.54.16 (KHTML, like Gecko) Version/5.1.4 Safari/534.54.16"
// eot: 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)',
// woff: 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0',
// svg: 'Mozilla/4.0 (iPad; CPU OS 4_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/4.1 Mobile/9A405 Safari/7534.48.3',
};
async function getFontDetails(family, options) {
const font = googleFonts.find((font2) => font2.family === family);
const styles = [...new Set(options.styles.map((i) => styleMap[i]))].sort();
const glyphs = _options.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 = [];
for (const axis of ["wght", "ital", ...Object.keys(_options?.experimental?.variableAxis?.[family] ?? {})].sort(googleFlavoredSorting)) {
const axisValue = {
wght: weights.map((v) => v.weight),
ital: styles
}[axis] ?? _options.experimental.variableAxis[family][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 extension in userAgents) {
const rawCss = await $fetch("/css2", {
baseURL: "https://fonts.googleapis.com",
headers: {
"user-agent": userAgents[extension]
},
query: {
family: `${family}:${resolvedAxes.join(",")}@${resolvedVariants.join(
";"
)}`,
...glyphs && { text: glyphs }
}
});
const groups = splitCssIntoSubsets(rawCss).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;
return f;
});
resolvedFontFaceData.push(...data);
}
priority++;
}
return resolvedFontFaceData;
}
return {
listFonts() {
return googleFonts.map((font) => font.family);
},
async resolveFont(fontFamily, options) {
if (!googleFonts.some((font) => font.family === fontFamily)) {
return;
}
const fonts = await ctx.storage.getItem(`google:${fontFamily}-${hash(options)}-data.json`, () => getFontDetails(fontFamily, options));
return { fonts };
}
};
});
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);
}
}
const googleicons = defineFontProvider("googleicons", async (_options, ctx) => {
const googleIcons = await ctx.storage.getItem("googleicons:meta.json", async () => {
const response = JSON.parse((await $fetch(
"https://fonts.google.com/metadata/icons?key=material_symbols&incomplete=true"
)).split("\n").slice(1).join("\n"));
return response.families;
});
const userAgents = {
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",
ttf: "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.54.16 (KHTML, like Gecko) Version/5.1.4 Safari/534.54.16"
// eot: 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)',
// woff: 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0',
// svg: 'Mozilla/4.0 (iPad; CPU OS 4_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/4.1 Mobile/9A405 Safari/7534.48.3',
};
async function getFontDetails(family) {
const iconNames = _options.experimental?.glyphs?.[family]?.sort().join(",");
let css = "";
for (const extension in userAgents) {
if (family.includes("Icons")) {
css += await $fetch("/icon", {
baseURL: "https://fonts.googleapis.com",
headers: { "user-agent": userAgents[extension] },
query: {
family
}
});
} else {
css += await $fetch("/css2", {
baseURL: "https://fonts.googleapis.com",
headers: { "user-agent": userAgents[extension] },
query: {
family: `${family}:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200`,
...iconNames && { icon_names: iconNames }
}
});
}
}
return extractFontFaceData(css);
}
return {
listFonts() {
return googleIcons;
},
async resolveFont(fontFamily, options) {
if (!googleIcons.includes(fontFamily)) {
return;
}
const fonts = await ctx.storage.getItem(`googleicons:${fontFamily}-${hash(options)}-data.json`, () => getFontDetails(fontFamily));
return { fonts };
}
};
});
const providers = {
__proto__: null,
adobe: adobe,
bunny: bunny,
fontshare: fontshare,
fontsource: fontsource,
google: google,
googleicons: googleicons
};
const defaultResolveOptions = {
weights: ["400"],
styles: ["normal", "italic"],
subsets: [
"cyrillic-ext",
"cyrillic",
"greek-ext",
"greek",
"vietnamese",
"latin-ext",
"latin"
]
};
async function createUnifont(providers2, options) {
const stack = {};
const unifontContext = {
storage: createAsyncStorage(options?.storage ?? memoryStorage())
};
for (const provider of providers2) {
stack[provider._name] = void 0;
}
await Promise.all(providers2.map(async (provider) => {
try {
const initializedProvider = await provider(unifontContext);
if (initializedProvider)
stack[provider._name] = initializedProvider;
} catch (err) {
console.error(`Could not initialize provider \`${provider._name}\`. \`unifont\` will not be able to process fonts provided by this provider.`, err);
}
if (!stack[provider._name]?.resolveFont) {
delete stack[provider._name];
}
}));
const allProviders = Object.keys(stack);
async function resolveFont(fontFamily, options2, providers3 = allProviders) {
const mergedOptions = { ...defaultResolveOptions, ...options2 };
for (const id of providers3) {
const provider = stack[id];
try {
const result = await provider?.resolveFont(fontFamily, mergedOptions);
if (result) {
return {
provider: id,
...result
};
}
} catch (err) {
console.error(`Could not resolve font face for \`${fontFamily}\` from \`${id}\` provider.`, err);
}
}
return { fonts: [] };
}
async function listFonts(providers3 = allProviders) {
let names;
for (const id of providers3) {
const provider = stack[id];
try {
const result = await provider?.listFonts?.();
if (result) {
names ??= [];
names.push(...result);
}
} catch (err) {
console.error(`Could not list names from \`${id}\` provider.`, err);
}
}
return names;
}
return {
resolveFont,
// TODO: remove before v1
resolveFontFace: resolveFont,
listFonts
};
}
export { createUnifont, defaultResolveOptions, defineFontProvider, providers };

65
node_modules/unifont/package.json generated vendored Normal file
View File

@@ -0,0 +1,65 @@
{
"name": "unifont",
"type": "module",
"version": "0.5.2",
"packageManager": "pnpm@10.12.2",
"description": "Framework agnostic tools for accessing data from font CDNs and providers",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/unjs/unifont.git"
},
"sideEffects": false,
"exports": {
".": "./dist/index.js"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "unbuild",
"dev": "vitest dev",
"lint": "eslint .",
"prepare": "simple-git-hooks",
"prepack": "pnpm build",
"prepublishOnly": "pnpm lint && pnpm test",
"release": "bumpp && npm publish",
"test": "pnpm test:unit --coverage && pnpm test:types",
"test:unit": "vitest",
"test:types": "tsc --noEmit"
},
"dependencies": {
"css-tree": "^3.0.0",
"ofetch": "^1.4.1",
"ohash": "^2.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "4.16.1",
"@types/css-tree": "2.3.10",
"@types/node": "22.15.32",
"@vitest/coverage-v8": "3.2.4",
"bumpp": "10.2.0",
"eslint": "9.29.0",
"lint-staged": "16.1.2",
"simple-git-hooks": "2.13.0",
"typescript": "5.8.3",
"unbuild": "3.5.0",
"unstorage": "1.16.0",
"vite": "6.3.5",
"vitest": "3.2.4"
},
"resolutions": {
"unifont": "link:."
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*.{js,ts,mjs,cjs,json,.*rc}": [
"npx eslint --fix"
]
}
}