Initial commit

This commit is contained in:
Alejandro Martinez
2026-02-12 02:04:10 +01:00
commit f09af719cf
13433 changed files with 2193445 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import type { FontFileContentResolver, FontFileIdGenerator, Hasher } from '../definitions.js';
import type { FontType } from '../types.js';
export declare class BuildFontFileIdGenerator implements FontFileIdGenerator {
#private;
constructor({ hasher, contentResolver, }: {
hasher: Hasher;
contentResolver: FontFileContentResolver;
});
generate({ originalUrl, type }: {
originalUrl: string;
type: FontType;
}): string;
}

View File

@@ -0,0 +1,17 @@
class BuildFontFileIdGenerator {
#hasher;
#contentResolver;
constructor({
hasher,
contentResolver
}) {
this.#hasher = hasher;
this.#contentResolver = contentResolver;
}
generate({ originalUrl, type }) {
return `${this.#hasher.hashString(this.#contentResolver.resolve(originalUrl))}.${type}`;
}
}
export {
BuildFontFileIdGenerator
};

View File

@@ -0,0 +1,12 @@
import type { AssetsPrefix } from '../../../types/public/index.js';
import type { UrlResolver } from '../definitions.js';
export declare class BuildUrlResolver implements UrlResolver {
#private;
constructor({ base, assetsPrefix, searchParams, }: {
base: string;
assetsPrefix: AssetsPrefix;
searchParams: URLSearchParams;
});
resolve(id: string): string;
get cspResources(): Array<string>;
}

View File

@@ -0,0 +1,40 @@
import { fileExtension, joinPaths, prependForwardSlash } from "../../../core/path.js";
import { getAssetsPrefix } from "../../utils/getAssetsPrefix.js";
import { createPlaceholderURL, stringifyPlaceholderURL } from "../../utils/url.js";
class BuildUrlResolver {
#resources = /* @__PURE__ */ new Set();
#base;
#assetsPrefix;
#searchParams;
constructor({
base,
assetsPrefix,
searchParams
}) {
this.#base = base;
this.#assetsPrefix = assetsPrefix;
this.#searchParams = searchParams;
}
resolve(id) {
const prefix = this.#assetsPrefix ? getAssetsPrefix(fileExtension(id), this.#assetsPrefix) : void 0;
let urlPath;
if (prefix) {
this.#resources.add(prefix);
urlPath = joinPaths(prefix, this.#base, id);
} else {
this.#resources.add("'self'");
urlPath = prependForwardSlash(joinPaths(this.#base, id));
}
const url = createPlaceholderURL(urlPath);
this.#searchParams.forEach((value, key) => {
url.searchParams.set(key, value);
});
return stringifyPlaceholderURL(url);
}
get cspResources() {
return Array.from(this.#resources);
}
}
export {
BuildUrlResolver
};

View File

@@ -0,0 +1,11 @@
import type { FontFetcher, Storage } from '../definitions.js';
import type { FontFileData } from '../types.js';
export declare class CachedFontFetcher implements FontFetcher {
#private;
constructor({ storage, fetch, readFile, }: {
storage: Storage;
fetch: (url: string, init?: RequestInit) => Promise<Response>;
readFile: (url: string) => Promise<Buffer>;
});
fetch({ id, url, init }: FontFileData): Promise<Buffer>;
}

View File

@@ -0,0 +1,50 @@
import { isAbsolute } from "node:path";
import { AstroError, AstroErrorData } from "../../../core/errors/index.js";
class CachedFontFetcher {
#storage;
#fetch;
#readFile;
constructor({
storage,
fetch,
readFile
}) {
this.#storage = storage;
this.#fetch = fetch;
this.#readFile = readFile;
}
async #cache(storage, key, cb) {
const existing = await storage.getItemRaw(key);
if (existing) {
return existing;
}
const data = await cb();
await storage.setItemRaw(key, data);
return data;
}
async fetch({ id, url, init }) {
return await this.#cache(this.#storage, id, async () => {
try {
if (isAbsolute(url)) {
return await this.#readFile(url);
}
const response = await this.#fetch(url, init ?? void 0);
if (!response.ok) {
throw new Error(`Response was not successful, received status code ${response.status}`);
}
return Buffer.from(await response.arrayBuffer());
} catch (cause) {
throw new AstroError(
{
...AstroErrorData.CannotFetchFontFile,
message: AstroErrorData.CannotFetchFontFile.message(url)
},
{ cause }
);
}
});
}
}
export {
CachedFontFetcher
};

View File

@@ -0,0 +1,18 @@
import type { CollectedFontForMetrics } from '../core/optimize-fallbacks.js';
import type { CssRenderer, FontFetcher, FontMetricsResolver } from '../definitions.js';
import type { CssProperties, FontFaceMetrics } from '../types.js';
export declare class CapsizeFontMetricsResolver implements FontMetricsResolver {
#private;
constructor({ fontFetcher, cssRenderer, }: {
fontFetcher: FontFetcher;
cssRenderer: CssRenderer;
});
getMetrics(name: string, font: CollectedFontForMetrics): Promise<FontFaceMetrics>;
generateFontFace({ metrics, fallbackMetrics, name: fallbackName, font: fallbackFontName, properties, }: {
metrics: FontFaceMetrics;
fallbackMetrics: FontFaceMetrics;
name: string;
font: string;
properties: CssProperties;
}): string;
}

View File

@@ -0,0 +1,71 @@
import { fromBuffer } from "@capsizecss/unpack";
import { renderFontSrc } from "../utils.js";
function filterRequiredMetrics({
ascent,
descent,
lineGap,
unitsPerEm,
xWidthAvg
}) {
return {
ascent,
descent,
lineGap,
unitsPerEm,
xWidthAvg
};
}
function round(value) {
return parseFloat(value.toFixed(4));
}
function toPercentString(value) {
return `${round(value * 100)}%`;
}
class CapsizeFontMetricsResolver {
#cache = {};
#fontFetcher;
#cssRenderer;
constructor({
fontFetcher,
cssRenderer
}) {
this.#fontFetcher = fontFetcher;
this.#cssRenderer = cssRenderer;
}
async getMetrics(name, font) {
return this.#cache[name] ??= filterRequiredMetrics(
await fromBuffer(await this.#fontFetcher.fetch(font))
);
}
// Adapted from Capsize
// Source: https://github.com/seek-oss/capsize/blob/b752693428b45994442433f7e3476ca4e3e3c507/packages/core/src/createFontStack.ts
generateFontFace({
metrics,
fallbackMetrics,
name: fallbackName,
font: fallbackFontName,
properties
}) {
const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
const fallbackFontXAvgRatio = fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm;
const sizeAdjust = preferredFontXAvgRatio && fallbackFontXAvgRatio ? preferredFontXAvgRatio / fallbackFontXAvgRatio : 1;
const adjustedEmSquare = metrics.unitsPerEm * sizeAdjust;
const ascentOverride = metrics.ascent / adjustedEmSquare;
const descentOverride = Math.abs(metrics.descent) / adjustedEmSquare;
const lineGapOverride = metrics.lineGap / adjustedEmSquare;
const fallbackAscentOverride = fallbackMetrics.ascent / adjustedEmSquare;
const fallbackDescentOverride = Math.abs(fallbackMetrics.descent) / adjustedEmSquare;
const fallbackLineGapOverride = fallbackMetrics.lineGap / adjustedEmSquare;
return this.#cssRenderer.generateFontFace(fallbackName, {
...properties,
src: renderFontSrc([{ name: fallbackFontName }]),
"size-adjust": sizeAdjust && sizeAdjust !== 1 ? toPercentString(sizeAdjust) : void 0,
"ascent-override": ascentOverride && ascentOverride !== fallbackAscentOverride ? toPercentString(ascentOverride) : void 0,
"descent-override": descentOverride && descentOverride !== fallbackDescentOverride ? toPercentString(descentOverride) : void 0,
"line-gap-override": lineGapOverride !== fallbackLineGapOverride ? toPercentString(lineGapOverride) : void 0
});
}
}
export {
CapsizeFontMetricsResolver
};

View File

@@ -0,0 +1,16 @@
import type * as unifont from 'unifont';
import type { FontFileContentResolver, FontFileIdGenerator, Hasher } from '../definitions.js';
import type { FontType } from '../types.js';
export declare class DevFontFileIdGenerator implements FontFileIdGenerator {
#private;
constructor({ hasher, contentResolver, }: {
hasher: Hasher;
contentResolver: FontFileContentResolver;
});
generate({ cssVariable, originalUrl, type, font, }: {
originalUrl: string;
type: FontType;
cssVariable: string;
font: unifont.FontFaceData;
}): string;
}

View File

@@ -0,0 +1,37 @@
class DevFontFileIdGenerator {
#hasher;
#contentResolver;
constructor({
hasher,
contentResolver
}) {
this.#hasher = hasher;
this.#contentResolver = contentResolver;
}
#formatWeight(weight) {
if (Array.isArray(weight)) {
return weight.join("-");
}
if (typeof weight === "number") {
return weight.toString();
}
return weight?.replace(/\s+/g, "-");
}
generate({
cssVariable,
originalUrl,
type,
font
}) {
return [
cssVariable.slice(2),
this.#formatWeight(font.weight),
font.style,
font.meta?.subset,
`${this.#hasher.hashString(this.#contentResolver.resolve(originalUrl))}.${type}`
].filter(Boolean).join("-");
}
}
export {
DevFontFileIdGenerator
};

View File

@@ -0,0 +1,10 @@
import type { UrlResolver } from '../definitions.js';
export declare class DevUrlResolver implements UrlResolver {
#private;
constructor({ base, searchParams, }: {
base: string;
searchParams: URLSearchParams;
});
resolve(id: string): string;
get cspResources(): Array<string>;
}

View File

@@ -0,0 +1,29 @@
import { joinPaths, prependForwardSlash } from "../../../core/path.js";
import { createPlaceholderURL, stringifyPlaceholderURL } from "../../utils/url.js";
class DevUrlResolver {
#resolved = false;
#base;
#searchParams;
constructor({
base,
searchParams
}) {
this.#base = base;
this.#searchParams = searchParams;
}
resolve(id) {
this.#resolved ||= true;
const urlPath = prependForwardSlash(joinPaths(this.#base, id));
const url = createPlaceholderURL(urlPath);
this.#searchParams.forEach((value, key) => {
url.searchParams.set(key, value);
});
return stringifyPlaceholderURL(url);
}
get cspResources() {
return this.#resolved ? ["'self'"] : [];
}
}
export {
DevUrlResolver
};

View File

@@ -0,0 +1,11 @@
import type { FontFileReader } from '../definitions.js';
import type { Style } from '../types.js';
export declare class FontaceFontFileReader implements FontFileReader {
extract({ family, url }: {
family: string;
url: string;
}): {
weight: string;
style: Style;
};
}

View File

@@ -0,0 +1,25 @@
import { readFileSync } from "node:fs";
import { fontace } from "fontace";
import { AstroError, AstroErrorData } from "../../../core/errors/index.js";
class FontaceFontFileReader {
extract({ family, url }) {
try {
const data = fontace(readFileSync(url));
return {
weight: data.weight,
style: data.style
};
} catch (cause) {
throw new AstroError(
{
...AstroErrorData.CannotDetermineWeightAndStyleFromFontFile,
message: AstroErrorData.CannotDetermineWeightAndStyleFromFontFile.message(family, url)
},
{ cause }
);
}
}
}
export {
FontaceFontFileReader
};

View File

@@ -0,0 +1,10 @@
import type { FontFileContentResolver } from '../definitions.js';
type ReadFileSync = (path: string) => string;
export declare class FsFontFileContentResolver implements FontFileContentResolver {
#private;
constructor({ readFileSync }: {
readFileSync: ReadFileSync;
});
resolve(url: string): string;
}
export {};

View File

@@ -0,0 +1,21 @@
import { isAbsolute } from "node:path";
import { AstroError, AstroErrorData } from "../../../core/errors/index.js";
class FsFontFileContentResolver {
#readFileSync;
constructor({ readFileSync }) {
this.#readFileSync = readFileSync;
}
resolve(url) {
if (!isAbsolute(url)) {
return url;
}
try {
return url + this.#readFileSync(url);
} catch (cause) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause });
}
}
}
export {
FsFontFileContentResolver
};

View File

@@ -0,0 +1,5 @@
import type { StringMatcher } from '../definitions.js';
export declare class LevenshteinStringMatcher implements StringMatcher {
#private;
getClosestMatch(target: string, candidates: Array<string>): string;
}

View File

@@ -0,0 +1,145 @@
class LevenshteinStringMatcher {
#peq = new Uint32Array(65536);
#myers_32(a, b) {
const n = a.length;
const m = b.length;
const lst = 1 << n - 1;
let pv = -1;
let mv = 0;
let sc = n;
let i = n;
while (i--) {
this.#peq[a.charCodeAt(i)] |= 1 << i;
}
for (i = 0; i < m; i++) {
let eq = this.#peq[b.charCodeAt(i)];
const xv = eq | mv;
eq |= (eq & pv) + pv ^ pv;
mv |= ~(eq | pv);
pv &= eq;
if (mv & lst) {
sc++;
}
if (pv & lst) {
sc--;
}
mv = mv << 1 | 1;
pv = pv << 1 | ~(xv | mv);
mv &= xv;
}
i = n;
while (i--) {
this.#peq[a.charCodeAt(i)] = 0;
}
return sc;
}
#myers_x(b, a) {
const n = a.length;
const m = b.length;
const mhc = [];
const phc = [];
const hsize = Math.ceil(n / 32);
const vsize = Math.ceil(m / 32);
for (let i = 0; i < hsize; i++) {
phc[i] = -1;
mhc[i] = 0;
}
let j = 0;
for (; j < vsize - 1; j++) {
let mv2 = 0;
let pv2 = -1;
const start2 = j * 32;
const vlen2 = Math.min(32, m) + start2;
for (let k = start2; k < vlen2; k++) {
this.#peq[b.charCodeAt(k)] |= 1 << k;
}
for (let i = 0; i < n; i++) {
const eq = this.#peq[a.charCodeAt(i)];
const pb = phc[i / 32 | 0] >>> i & 1;
const mb = mhc[i / 32 | 0] >>> i & 1;
const xv = eq | mv2;
const xh = ((eq | mb) & pv2) + pv2 ^ pv2 | eq | mb;
let ph = mv2 | ~(xh | pv2);
let mh = pv2 & xh;
if (ph >>> 31 ^ pb) {
phc[i / 32 | 0] ^= 1 << i;
}
if (mh >>> 31 ^ mb) {
mhc[i / 32 | 0] ^= 1 << i;
}
ph = ph << 1 | pb;
mh = mh << 1 | mb;
pv2 = mh | ~(xv | ph);
mv2 = ph & xv;
}
for (let k = start2; k < vlen2; k++) {
this.#peq[b.charCodeAt(k)] = 0;
}
}
let mv = 0;
let pv = -1;
const start = j * 32;
const vlen = Math.min(32, m - start) + start;
for (let k = start; k < vlen; k++) {
this.#peq[b.charCodeAt(k)] |= 1 << k;
}
let score = m;
for (let i = 0; i < n; i++) {
const eq = this.#peq[a.charCodeAt(i)];
const pb = phc[i / 32 | 0] >>> i & 1;
const mb = mhc[i / 32 | 0] >>> i & 1;
const xv = eq | mv;
const xh = ((eq | mb) & pv) + pv ^ pv | eq | mb;
let ph = mv | ~(xh | pv);
let mh = pv & xh;
score += ph >>> m - 1 & 1;
score -= mh >>> m - 1 & 1;
if (ph >>> 31 ^ pb) {
phc[i / 32 | 0] ^= 1 << i;
}
if (mh >>> 31 ^ mb) {
mhc[i / 32 | 0] ^= 1 << i;
}
ph = ph << 1 | pb;
mh = mh << 1 | mb;
pv = mh | ~(xv | ph);
mv = ph & xv;
}
for (let k = start; k < vlen; k++) {
this.#peq[b.charCodeAt(k)] = 0;
}
return score;
}
#distance(a, b) {
if (a.length < b.length) {
const tmp = b;
b = a;
a = tmp;
}
if (b.length === 0) {
return a.length;
}
if (a.length <= 32) {
return this.#myers_32(a, b);
}
return this.#myers_x(a, b);
}
#closest(str, arr) {
let min_distance = Infinity;
let min_index = 0;
for (let i = 0; i < arr.length; i++) {
const dist = this.#distance(str, arr[i]);
if (dist < min_distance) {
min_distance = dist;
min_index = i;
}
}
return arr[min_index];
}
getClosestMatch(target, candidates) {
return this.#closest(target, candidates);
}
}
export {
LevenshteinStringMatcher
};

View File

@@ -0,0 +1,15 @@
import type { CssRenderer } from '../definitions.js';
import type { CssProperties } from '../types.js';
export declare function renderFontFace(properties: CssProperties, minify: boolean): string;
export declare function renderCssVariable(key: string, values: Array<string>, minify: boolean): string;
export declare function withFamily(family: string, properties: CssProperties): CssProperties;
/** If the value contains spaces (which would be incorrectly interpreted), we wrap it in quotes. */
export declare function handleValueWithSpaces(value: string): string;
export declare class MinifiableCssRenderer implements CssRenderer {
#private;
constructor({ minify }: {
minify: boolean;
});
generateFontFace(family: string, properties: CssProperties): string;
generateCssVariable(key: string, values: Array<string>): string;
}

View File

@@ -0,0 +1,44 @@
function renderFontFace(properties, minify) {
const lf = minify ? "" : `
`;
const sp = minify ? "" : " ";
return `@font-face${sp}{${lf}${Object.entries(properties).filter(([, value]) => Boolean(value)).map(([key, value]) => `${sp}${sp}${key}:${sp}${value};`).join(lf)}${lf}}${lf}`;
}
function renderCssVariable(key, values, minify) {
const lf = minify ? "" : `
`;
const sp = minify ? "" : " ";
return `:root${sp}{${lf}${sp}${sp}${key}:${sp}${values.map((v) => handleValueWithSpaces(v)).join(`,${sp}`)};${lf}}${lf}`;
}
function withFamily(family, properties) {
return {
"font-family": handleValueWithSpaces(family),
...properties
};
}
const SPACE_RE = /\s/;
function handleValueWithSpaces(value) {
if (SPACE_RE.test(value)) {
return JSON.stringify(value);
}
return value;
}
class MinifiableCssRenderer {
#minify;
constructor({ minify }) {
this.#minify = minify;
}
generateFontFace(family, properties) {
return renderFontFace(withFamily(family, properties), this.#minify);
}
generateCssVariable(key, values) {
return renderCssVariable(key, values, this.#minify);
}
}
export {
MinifiableCssRenderer,
handleValueWithSpaces,
renderCssVariable,
renderFontFace,
withFamily
};

View File

@@ -0,0 +1,5 @@
import type { FontTypeExtractor } from '../definitions.js';
import type { FontType } from '../types.js';
export declare class NodeFontTypeExtractor implements FontTypeExtractor {
extract(url: string): FontType;
}

View File

@@ -0,0 +1,21 @@
import { extname } from "node:path";
import { AstroError, AstroErrorData } from "../../../core/errors/index.js";
import { isFontType } from "../utils.js";
class NodeFontTypeExtractor {
extract(url) {
const extension = extname(url).slice(1);
if (!isFontType(extension)) {
throw new AstroError(
{
...AstroErrorData.CannotExtractFontType,
message: AstroErrorData.CannotExtractFontType.message(url)
},
{ cause: `Unexpected extension, got "${extension}"` }
);
}
return extension;
}
}
export {
NodeFontTypeExtractor
};

View File

@@ -0,0 +1,6 @@
import type { SystemFallbacksProvider } from '../definitions.js';
import type { FontFaceMetrics, GenericFallbackName } from '../types.js';
export declare class RealSystemFallbacksProvider implements SystemFallbacksProvider {
getLocalFonts(fallback: GenericFallbackName): Array<string> | null;
getMetricsForLocalFont(family: string): FontFaceMetrics;
}

View File

@@ -0,0 +1,71 @@
const SYSTEM_METRICS = {
"Times New Roman": {
ascent: 1825,
descent: -443,
lineGap: 87,
unitsPerEm: 2048,
xWidthAvg: 832
},
Arial: {
ascent: 1854,
descent: -434,
lineGap: 67,
unitsPerEm: 2048,
xWidthAvg: 913
},
"Courier New": {
ascent: 1705,
descent: -615,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 1229
},
BlinkMacSystemFont: {
ascent: 1980,
descent: -432,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 853
},
"Segoe UI": {
ascent: 2210,
descent: -514,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 908
},
Roboto: {
ascent: 1900,
descent: -500,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 911
},
"Helvetica Neue": {
ascent: 952,
descent: -213,
lineGap: 28,
unitsPerEm: 1e3,
xWidthAvg: 450
}
};
const DEFAULT_FALLBACKS = {
serif: ["Times New Roman"],
"sans-serif": ["Arial"],
monospace: ["Courier New"],
"system-ui": ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
"ui-serif": ["Times New Roman"],
"ui-sans-serif": ["Arial"],
"ui-monospace": ["Courier New"]
};
class RealSystemFallbacksProvider {
getLocalFonts(fallback) {
return DEFAULT_FALLBACKS[fallback] ?? null;
}
getMetricsForLocalFont(family) {
return SYSTEM_METRICS[family];
}
}
export {
RealSystemFallbacksProvider
};

View File

@@ -0,0 +1,34 @@
import type { FontFaceData, Provider } from 'unifont';
import type { FontResolver, Hasher, Storage } from '../definitions.js';
import type { FontProvider, ResolvedFontFamily, ResolveFontOptions } from '../types.js';
type NonEmptyProviders = [
Provider<string, Record<string, any>>,
...Array<Provider<string, Record<string, any>>>
];
export declare class UnifontFontResolver implements FontResolver {
#private;
private constructor();
static idFromProvider({ hasher, provider }: {
hasher: Hasher;
provider: FontProvider;
}): string;
static astroToUnifontProvider(astroProvider: FontProvider, root: URL): Provider;
static extractUnifontProviders({ families, hasher, root, }: {
families: Array<ResolvedFontFamily>;
hasher: Hasher;
root: URL;
}): NonEmptyProviders;
static create({ families, hasher, storage, root, }: {
families: Array<ResolvedFontFamily>;
hasher: Hasher;
storage: Storage;
root: URL;
}): Promise<UnifontFontResolver>;
resolveFont({ familyName, provider, options, ...rest }: ResolveFontOptions<Record<string, any>> & {
provider: FontProvider;
}): Promise<Array<FontFaceData>>;
listFonts({ provider }: {
provider: FontProvider;
}): Promise<string[] | undefined>;
}
export {};

View File

@@ -0,0 +1,98 @@
import { createUnifont, defineFontProvider } from "unifont";
class UnifontFontResolver {
#unifont;
#hasher;
constructor({
unifont,
hasher
}) {
this.#unifont = unifont;
this.#hasher = hasher;
}
static idFromProvider({ hasher, provider }) {
const hash = hasher.hashObject({
name: provider.name,
...provider.config
});
return `${provider.name}-${hash}`;
}
static astroToUnifontProvider(astroProvider, root) {
return defineFontProvider(astroProvider.name, async (_options, ctx) => {
await astroProvider?.init?.({ ...ctx, root });
return {
async resolveFont(familyName, { options, ...rest }) {
return await astroProvider.resolveFont({ familyName, options, ...rest });
},
async listFonts() {
return astroProvider.listFonts?.();
}
};
})(astroProvider.config);
}
static extractUnifontProviders({
families,
hasher,
root
}) {
const providers = /* @__PURE__ */ new Map();
for (const { provider } of families) {
const id = this.idFromProvider({ hasher, provider });
if (!providers.has(id)) {
const unifontProvider = this.astroToUnifontProvider(provider, root);
unifontProvider._name = this.idFromProvider({ hasher, provider });
providers.set(id, unifontProvider);
}
}
return Array.from(providers.values());
}
static async create({
families,
hasher,
storage,
root
}) {
return new UnifontFontResolver({
unifont: await createUnifont(this.extractUnifontProviders({ families, hasher, root }), {
storage,
// TODO: consider enabling, would require new astro errors
throwOnError: false
}),
hasher
});
}
async resolveFont({
familyName,
provider,
options,
...rest
}) {
const id = UnifontFontResolver.idFromProvider({
hasher: this.#hasher,
provider
});
const { fonts } = await this.#unifont.resolveFont(
familyName,
{
// Options are currently namespaced by provider name, it may change in
// https://github.com/unjs/unifont/pull/287
options: {
[id]: options
},
...rest
},
[id]
);
return fonts;
}
async listFonts({ provider }) {
return await this.#unifont.listFonts([
UnifontFontResolver.idFromProvider({
hasher: this.#hasher,
provider
})
]);
}
}
export {
UnifontFontResolver
};

View File

@@ -0,0 +1,11 @@
import type { Storage } from '../definitions.js';
export declare class UnstorageFsStorage implements Storage {
#private;
constructor({ base }: {
base: URL;
});
getItem(key: string): Promise<any | null>;
getItemRaw(key: string): Promise<Buffer | null>;
setItem(key: string, value: any): Promise<void>;
setItemRaw(key: string, value: any): Promise<void>;
}

View File

@@ -0,0 +1,26 @@
import { fileURLToPath } from "node:url";
import * as unstorage from "unstorage";
import fsLiteDriver from "unstorage/drivers/fs-lite";
class UnstorageFsStorage {
#unstorage;
constructor({ base }) {
this.#unstorage = unstorage.createStorage({
driver: fsLiteDriver({ base: fileURLToPath(base) })
});
}
async getItem(key) {
return await this.#unstorage.getItem(key);
}
async getItemRaw(key) {
return await this.#unstorage.getItemRaw(key);
}
async setItem(key, value) {
return await this.#unstorage.setItem(key, value);
}
async setItemRaw(key, value) {
return await this.#unstorage.setItemRaw(key, value);
}
}
export {
UnstorageFsStorage
};

View File

@@ -0,0 +1,7 @@
import type { Hasher } from '../definitions.js';
export declare class XxhashHasher implements Hasher {
hashString: (input: string) => string;
private constructor();
static create(): Promise<XxhashHasher>;
hashObject(input: Record<string, any>): string;
}

View File

@@ -0,0 +1,18 @@
import xxhash from "xxhash-wasm";
import { sortObjectByKey } from "../utils.js";
class XxhashHasher {
hashString;
constructor(hashString) {
this.hashString = hashString;
}
static async create() {
const { h64ToString } = await xxhash();
return new XxhashHasher(h64ToString);
}
hashObject(input) {
return this.hashString(JSON.stringify(sortObjectByKey(input)));
}
}
export {
XxhashHasher
};