mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Self-host default fonts and inline only the ones a site uses (RND-12524) (#4522)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Self-host default Google fonts and inline only the @font-face rules of the fonts a site uses, instead of shipping render-blocking stylesheets covering all 23 families on every page.
|
||||
@@ -118,8 +118,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "./scripts/generate.sh",
|
||||
"generate:assets": "bun ./scripts/generate-mermaid-runtime.ts && bun ./scripts/generate-scalar-runtime.ts",
|
||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math && rm -rf ./public/~gitbook/static/mermaid && rm -rf ./public/~gitbook/static/scalar",
|
||||
"generate:assets": "bun ./scripts/generate-mermaid-runtime.ts && bun ./scripts/generate-scalar-runtime.ts && bun ./scripts/download-fonts.ts",
|
||||
"generate:fonts": "bun ./scripts/generate-font-faces.ts",
|
||||
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math && rm -rf ./public/~gitbook/static/mermaid && rm -rf ./public/~gitbook/static/scalar && rm -rf ./public/~gitbook/static/fonts",
|
||||
"dev": "bun run generate:assets && env-cmd --silent -f ../../.env.local next --webpack",
|
||||
"build": "bun run generate:assets && next build --webpack",
|
||||
"build:local": "bun run generate:assets && GITBOOK_URL=http://localhost:3000 next build --webpack",
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { copyFile, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import type { FontSourcesData } from '../src/fonts/types';
|
||||
import { getFontDefinitionsHash } from './font-definitions-hash';
|
||||
|
||||
const CONCURRENCY = 8;
|
||||
const ATTEMPTS = 6;
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const fontsDir = join(scriptDir, '../src/fonts');
|
||||
const outputDir = join(scriptDir, '../public/~gitbook/static/fonts');
|
||||
const sourcesPath = join(fontsDir, 'generated/sources.json');
|
||||
|
||||
const readSources = async () => JSON.parse(await readFile(sourcesPath, 'utf8')) as FontSourcesData;
|
||||
|
||||
let sourcesData = await readSources();
|
||||
if (sourcesData.definitionsHash !== (await getFontDefinitionsHash())) {
|
||||
console.warn(
|
||||
'definitions.ts changed since the font manifest was generated — regenerating. Commit the changes in src/fonts/generated.'
|
||||
);
|
||||
await import('./generate-font-faces');
|
||||
sourcesData = await readSources();
|
||||
}
|
||||
|
||||
const { google, local } = sourcesData;
|
||||
const sources = new Map<string, string>(Object.entries(local));
|
||||
for (const [googleId, { prefix, files }] of Object.entries(google)) {
|
||||
for (const file of files) {
|
||||
sources.set(`${googleId}/${file}`, `${prefix}/${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Faces move between releases; stale files would otherwise pile up in the deployed assets.
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
for (const entry of await readdir(outputDir, { recursive: true, withFileTypes: true })) {
|
||||
if (entry.isFile()) {
|
||||
const file = relative(outputDir, join(entry.parentPath, entry.name));
|
||||
if (!sources.has(file)) {
|
||||
await rm(join(outputDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pending = [...sources].filter(([file]) => !Bun.file(join(outputDir, file)).size);
|
||||
if (pending.length > 0) {
|
||||
console.log(`Downloading ${pending.length} font files…`);
|
||||
}
|
||||
|
||||
const queue = pending.values();
|
||||
await Promise.all(Array.from({ length: CONCURRENCY }, () => worker()));
|
||||
|
||||
async function worker() {
|
||||
for (const [file, source] of queue) {
|
||||
const target = join(outputDir, file);
|
||||
await mkdir(dirname(target), { recursive: true });
|
||||
|
||||
if (source.startsWith('./')) {
|
||||
await copyFile(join(fontsDir, source), target);
|
||||
continue;
|
||||
}
|
||||
|
||||
await writeFile(target, await fetchWithRetries(source));
|
||||
}
|
||||
}
|
||||
|
||||
// Google Fonts intermittently refuses connections when a build asks for hundreds of files at once,
|
||||
// and a single miss fails the whole build.
|
||||
async function fetchWithRetries(url: string): Promise<Buffer> {
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
return Buffer.from(await response.arrayBuffer());
|
||||
} catch (error) {
|
||||
if (attempt >= ATTEMPTS) {
|
||||
throw new Error(`Unable to download ${url}: ${error}`);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export async function getFontDefinitionsHash(): Promise<string> {
|
||||
const path = join(dirname(fileURLToPath(import.meta.url)), '../src/fonts/definitions.ts');
|
||||
return createHash('sha256')
|
||||
.update(await readFile(path))
|
||||
.digest('hex');
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// Regenerates the committed font manifest from the Google Fonts CSS API. download-fonts.ts runs it
|
||||
// automatically when definitions.ts changed since the last generation; `bun run generate:fonts`
|
||||
// forces it (e.g. to pick up new Google Fonts releases).
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { basename, dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { ABC_FAVORIT, FONT_DEFINITIONS, type FontDefinition } from '../src/fonts/definitions';
|
||||
import type {
|
||||
FontFacesData,
|
||||
FontFallbackFaceData,
|
||||
FontSourcesData,
|
||||
FontVariantData,
|
||||
} from '../src/fonts/types';
|
||||
import { getFontDefinitionsHash } from './font-definitions-hash';
|
||||
|
||||
// Google Fonts picks the file format from the user agent — the same modern Chrome `next/font` sends,
|
||||
// so we keep getting compact woff2 (and vector rather than bitmap emoji).
|
||||
const USER_AGENT =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36';
|
||||
|
||||
// The precalculated metrics `next/font/google` uses, so the fallback faces stay identical.
|
||||
const { calculateSizeAdjustValues } = createRequire(import.meta.url)(
|
||||
'next/dist/server/font-utils'
|
||||
) as {
|
||||
calculateSizeAdjustValues: (family: string) => {
|
||||
ascent: string;
|
||||
descent: string;
|
||||
lineGap: string;
|
||||
fallbackFont: string;
|
||||
sizeAdjust: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ResolvedFace = {
|
||||
weight: string;
|
||||
style: string;
|
||||
file: string;
|
||||
source: string;
|
||||
unicodeRange: string;
|
||||
};
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const faces: FontFacesData = {};
|
||||
const sources: FontSourcesData = {
|
||||
definitionsHash: await getFontDefinitionsHash(),
|
||||
google: {},
|
||||
local: {},
|
||||
};
|
||||
|
||||
for (const [name, definition] of Object.entries(FONT_DEFINITIONS)) {
|
||||
const resolved = definition.googleId
|
||||
? await getGoogleFaces(definition)
|
||||
: await getABCFavoritFaces();
|
||||
|
||||
if (resolved.length === 0) {
|
||||
throw new Error(`No font faces resolved for ${name}`);
|
||||
}
|
||||
|
||||
recordSources(definition, resolved);
|
||||
|
||||
const subsets = [...new Set(resolved.map((face) => face.unicodeRange))];
|
||||
const variants = new Map<string, FontVariantData>();
|
||||
|
||||
for (const face of resolved) {
|
||||
const key = `${face.weight}|${face.style}`;
|
||||
let variant = variants.get(key);
|
||||
if (!variant) {
|
||||
variant = { weight: face.weight, style: face.style, files: [] };
|
||||
variants.set(key, variant);
|
||||
}
|
||||
variant.files[subsets.indexOf(face.unicodeRange)] = face.file;
|
||||
}
|
||||
|
||||
faces[name] = {
|
||||
family: definition.family,
|
||||
variable: definition.variable,
|
||||
fontFamilyValue: [
|
||||
`"${definition.family}"`,
|
||||
...(definition.adjustFallback ? [`"${definition.family} Fallback"`] : []),
|
||||
...definition.fallback,
|
||||
].join(','),
|
||||
subsets,
|
||||
variants: [...variants.values()],
|
||||
fallbackFace: definition.adjustFallback ? getFallbackFace(name, definition.family) : null,
|
||||
...(definition.googleId ? {} : { ascentOverride: ABC_FAVORIT.ascentOverride }),
|
||||
};
|
||||
}
|
||||
|
||||
const generatedDir = join(scriptDir, '../src/fonts/generated');
|
||||
await writeFile(join(generatedDir, 'faces.json'), `${JSON.stringify(faces, null, 4)}\n`);
|
||||
await writeFile(join(generatedDir, 'sources.json'), `${JSON.stringify(sources, null, 4)}\n`);
|
||||
|
||||
/** Google serves every file of a family from one versioned directory, so only the names differ. */
|
||||
function recordSources(definition: FontDefinition, resolved: ResolvedFace[]) {
|
||||
if (!definition.googleId) {
|
||||
for (const face of resolved) {
|
||||
sources.local[face.file] = face.source;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const prefixes = new Set(
|
||||
resolved.map((face) => face.source.slice(0, face.source.lastIndexOf('/')))
|
||||
);
|
||||
if (prefixes.size !== 1) {
|
||||
throw new Error(`${definition.family} spans several Google Fonts directories`);
|
||||
}
|
||||
|
||||
sources.google[definition.googleId] = {
|
||||
prefix: [...prefixes][0] as string,
|
||||
files: [...new Set(resolved.map((face) => basename(face.file)))],
|
||||
};
|
||||
}
|
||||
|
||||
async function getGoogleFaces(definition: FontDefinition): Promise<ResolvedFace[]> {
|
||||
const { family, googleId, weights } = definition;
|
||||
const url = `https://fonts.googleapis.com/css2?family=${family.replaceAll(' ', '+')}:wght@${weights.join(';')}&display=swap`;
|
||||
|
||||
const response = await fetch(url, { headers: { 'User-Agent': USER_AGENT } });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unable to fetch ${family} from Google Fonts: ${response.status} (${url})`);
|
||||
}
|
||||
|
||||
const resolved = [...(await response.text()).matchAll(/@font-face\s*\{([^}]*)\}/g)].map(
|
||||
(match) => {
|
||||
const block = match[1] ?? '';
|
||||
const source = read(block, 'src')?.match(/url\((https:[^)]+\.woff2)\)/)?.[1];
|
||||
const weight = read(block, 'font-weight');
|
||||
const unicodeRange = read(block, 'unicode-range');
|
||||
|
||||
if (!source || !weight || !unicodeRange) {
|
||||
throw new Error(`Unexpected @font-face for ${family}: ${block}`);
|
||||
}
|
||||
|
||||
return {
|
||||
weight,
|
||||
style: read(block, 'font-style') ?? 'normal',
|
||||
// Google's filenames are content-addressed, so the asset can stay immutable.
|
||||
file: `${googleId}/${basename(new URL(source).pathname)}`,
|
||||
source,
|
||||
unicodeRange: unicodeRange.toLowerCase(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const missing = weights.filter((weight) => !resolved.some((face) => face.weight === weight));
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Google Fonts returned no ${missing.join('/')} weight for ${family}`);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
async function getABCFavoritFaces(): Promise<ResolvedFace[]> {
|
||||
return Promise.all(
|
||||
ABC_FAVORIT.sources.map(async (source) => {
|
||||
const path = join(scriptDir, '../src/fonts/ABCFavorit', source.file);
|
||||
const digest = createHash('sha256')
|
||||
.update(await readFile(path))
|
||||
.digest('hex');
|
||||
|
||||
return {
|
||||
weight: source.weight,
|
||||
style: source.style,
|
||||
file: `abcfavorit/${digest.slice(0, 16)}.woff2`,
|
||||
source: `./ABCFavorit/${source.file}`,
|
||||
unicodeRange: '',
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getFallbackFace(name: string, family: string): FontFallbackFaceData {
|
||||
if (name === 'ABCFavorit') {
|
||||
return { family: `${family} Fallback`, local: 'Arial', ...ABC_FAVORIT.fallbackMetrics };
|
||||
}
|
||||
|
||||
const metrics = calculateSizeAdjustValues(family);
|
||||
return {
|
||||
family: `${family} Fallback`,
|
||||
local: metrics.fallbackFont,
|
||||
ascentOverride: `${metrics.ascent}%`,
|
||||
descentOverride: `${metrics.descent}%`,
|
||||
lineGapOverride: `${metrics.lineGap}%`,
|
||||
sizeAdjust: `${metrics.sizeAdjust}%`,
|
||||
};
|
||||
}
|
||||
|
||||
function read(block: string, property: string): string | undefined {
|
||||
return block.match(new RegExp(`${property}\\s*:\\s*([^;]+);`))?.[1]?.trim();
|
||||
}
|
||||
@@ -26,8 +26,12 @@ import { AnnouncementDismissedScript } from '../Announcement';
|
||||
import { SelectStateScript } from '../Select';
|
||||
import { OperatingSystemClassScript } from './OperatingSystemClassScript';
|
||||
import { RootLayoutClientContexts } from './RootLayoutClientContexts';
|
||||
import { type FontData, getFontData } from '@/fonts';
|
||||
import { fontNotoColorEmoji, fonts } from '@/fonts/default';
|
||||
import {
|
||||
DEFAULT_MONOSPACE_FONT,
|
||||
type FontData,
|
||||
generateEmojiFontFacesCSS,
|
||||
getFontData,
|
||||
} from '@/fonts';
|
||||
import './globals.css';
|
||||
import { getContentLocale, getSpaceLanguage } from '@/intl/server';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
@@ -91,12 +95,10 @@ export async function CustomizationRootLayout(props: {
|
||||
const fontData = getFontData(customization.styling.font, 'content');
|
||||
// Temporarily add a if here while the cache is being warmed up.
|
||||
// We can remove the condition after 14-07-2025.
|
||||
const monospaceFontData = customization.styling.monospaceFont
|
||||
? getFontData(customization.styling.monospaceFont, 'mono')
|
||||
: {
|
||||
type: 'default' as const,
|
||||
variable: fonts.IBMPlexMono.variable,
|
||||
};
|
||||
const monospaceFontData = getFontData(
|
||||
customization.styling.monospaceFont ?? DEFAULT_MONOSPACE_FONT,
|
||||
'mono'
|
||||
);
|
||||
|
||||
// Preconnect and preload custom fonts if needed
|
||||
preloadFont(fontData);
|
||||
@@ -127,10 +129,8 @@ export async function CustomizationRootLayout(props: {
|
||||
sidebarStyles.list && `sidebar-list-${sidebarStyles.list}`,
|
||||
'links' in customization.styling && `links-${customization.styling.links}`,
|
||||
'depth' in customization.styling && `depth-${customization.styling.depth}`,
|
||||
fontNotoColorEmoji.variable,
|
||||
monospaceFontData.type === 'default' ? monospaceFontData.variable : null,
|
||||
fontData.type === 'default'
|
||||
? [fontData.variable, `font-${customization.styling.font}`]
|
||||
typeof customization.styling.font === 'string'
|
||||
? `font-${customization.styling.font}`
|
||||
: null,
|
||||
|
||||
// Set the dark/light class statically to avoid flashing and make it work when JS is disabled
|
||||
@@ -150,11 +150,10 @@ export async function CustomizationRootLayout(props: {
|
||||
{/* Apply the visitor's content selection to <html> before first paint (no flash) */}
|
||||
<SelectStateScript />
|
||||
|
||||
{/* Inject custom font @font-face rules */}
|
||||
{fontData.type === 'custom' ? <style>{fontData.fontFaceRules}</style> : null}
|
||||
{monospaceFontData.type === 'custom' ? (
|
||||
<style>{monospaceFontData.fontFaceRules}</style>
|
||||
) : null}
|
||||
{/* Only the picked families, so the head never carries every font we support */}
|
||||
<style>{generateEmojiFontFacesCSS()}</style>
|
||||
<style>{fontData.fontFaceRules}</style>
|
||||
<style>{monospaceFontData.fontFaceRules}</style>
|
||||
|
||||
{/* Inject a script to detect if the announcmeent banner has been dismissed */}
|
||||
{'announcement' in customization && customization.announcement?.enabled ? (
|
||||
|
||||
@@ -1,297 +1,83 @@
|
||||
import {
|
||||
DM_Mono,
|
||||
Fira_Code,
|
||||
Fira_Sans_Extra_Condensed,
|
||||
IBM_Plex_Mono,
|
||||
IBM_Plex_Serif,
|
||||
Inconsolata,
|
||||
Inter,
|
||||
JetBrains_Mono,
|
||||
Lato,
|
||||
Merriweather,
|
||||
Noto_Color_Emoji,
|
||||
Noto_Sans,
|
||||
Open_Sans,
|
||||
Overpass,
|
||||
Poppins,
|
||||
Raleway,
|
||||
Roboto,
|
||||
Roboto_Mono,
|
||||
Roboto_Slab,
|
||||
Source_Code_Pro,
|
||||
Source_Sans_3,
|
||||
Space_Mono,
|
||||
Ubuntu,
|
||||
} from 'next/font/google';
|
||||
import localFont from 'next/font/local';
|
||||
import { CustomizationDefaultMonospaceFont } from '@gitbook/api';
|
||||
|
||||
import { CustomizationDefaultFont, CustomizationDefaultMonospaceFont } from '@gitbook/api';
|
||||
import { EMOJI_FONT, type FontName } from './definitions';
|
||||
import faces from './generated/faces.json';
|
||||
import type { FontFacesData, FontFamilyData } from './types';
|
||||
import { getAssetURL } from '@/lib/assets';
|
||||
|
||||
export const fontNotoColorEmoji = Noto_Color_Emoji({
|
||||
variable: '--font-noto-color-emoji',
|
||||
weight: ['400'],
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
});
|
||||
const fontFaces = faces as FontFacesData;
|
||||
|
||||
/*
|
||||
Fonts are downloaded and loaded by next/font.
|
||||
// The rules only vary by font, and every page renders a few dozen of them.
|
||||
const cache = new Map<FontName, string>();
|
||||
|
||||
We can't use "preload: true" as otherwise Next will preload all the fonts on the page
|
||||
while spaces only use one font at a time.
|
||||
*/
|
||||
/** Used until the cache has warmed up for sites saved before the setting existed. */
|
||||
export const DEFAULT_MONOSPACE_FONT = CustomizationDefaultMonospaceFont.IBMPlexMono;
|
||||
|
||||
const inter = Inter({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
// Emitted per picked family and inlined in the head, so a page never carries the other 20 families
|
||||
// the way a shared `next/font` stylesheet did.
|
||||
export function generateDefaultFontFacesCSS(font: FontName): string {
|
||||
const cached = cache.get(font);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const firaSans = Fira_Sans_Extra_Condensed({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
const family = fontFaces[font];
|
||||
if (!family) {
|
||||
throw new Error(`Missing generated font faces for ${font}`);
|
||||
}
|
||||
|
||||
const ibmPlexSerif = IBM_Plex_Serif({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['serif'],
|
||||
});
|
||||
const css = [
|
||||
...family.variants.flatMap((variant) =>
|
||||
variant.files.map((file, subset) =>
|
||||
generateFace(family, variant.weight, variant.style, file, family.subsets[subset])
|
||||
)
|
||||
),
|
||||
generateFallbackFace(family),
|
||||
`:root { ${family.variable}: ${family.fontFamilyValue}; }`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
const lato = Lato({
|
||||
weight: ['400', '700', '900'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
cache.set(font, css);
|
||||
return css;
|
||||
}
|
||||
|
||||
const merriweather = Merriweather({
|
||||
weight: ['400', '700', '900'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['serif'],
|
||||
});
|
||||
export function generateEmojiFontFacesCSS(): string {
|
||||
return generateDefaultFontFacesCSS(EMOJI_FONT);
|
||||
}
|
||||
|
||||
const notoSans = Noto_Sans({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
function generateFace(
|
||||
family: FontFamilyData,
|
||||
weight: string,
|
||||
style: string,
|
||||
file: string,
|
||||
unicodeRange: string | undefined
|
||||
): string {
|
||||
return `@font-face {
|
||||
font-family: "${family.family}";
|
||||
font-style: ${style};
|
||||
font-weight: ${weight};
|
||||
font-display: swap;${family.ascentOverride ? `\n ascent-override: ${family.ascentOverride};` : ''}
|
||||
src: url(${getAssetURL(`fonts/${file}`)}) format("woff2");${
|
||||
unicodeRange ? `\n unicode-range: ${unicodeRange};` : ''
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
const openSans = Open_Sans({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
// A `local()` face with the real font's metrics: the page keeps the system font's glyphs but the
|
||||
// picked font's proportions, so swapping it in barely shifts the layout.
|
||||
function generateFallbackFace(family: FontFamilyData): string {
|
||||
const fallback = family.fallbackFace;
|
||||
if (!fallback) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const overpass = Overpass({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const poppins = Poppins({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const raleway = Raleway({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const robotoSlab = Roboto_Slab({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const sourceSansPro = Source_Sans_3({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const ubuntu = Ubuntu({
|
||||
weight: ['400', '500', '700'],
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
});
|
||||
|
||||
const abcFavorit = localFont({
|
||||
variable: '--font-content',
|
||||
preload: false,
|
||||
display: 'swap',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
src: [
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-Variable.woff2',
|
||||
weight: '400 700',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-BoldItalic.woff2',
|
||||
weight: '700',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-MediumItalic.woff2',
|
||||
weight: '500',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: './ABCFavorit/ABCFavorit-RegularItalic.woff2',
|
||||
weight: '400',
|
||||
style: 'italic',
|
||||
},
|
||||
],
|
||||
declarations: [{ prop: 'ascent-override', value: '100%' }],
|
||||
});
|
||||
|
||||
const ibmPlexMono = IBM_Plex_Mono({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const dmMono = DM_Mono({
|
||||
weight: ['400', '500'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const firaCode = Fira_Code({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const inconsolata = Inconsolata({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const jetBrainsMono = JetBrains_Mono({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const robotoMono = Roboto_Mono({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const sourceCodePro = Source_Code_Pro({
|
||||
weight: ['400', '500', '600', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
const spaceMono = Space_Mono({
|
||||
weight: ['400', '700'],
|
||||
variable: '--font-mono',
|
||||
style: 'normal',
|
||||
display: 'swap',
|
||||
preload: false,
|
||||
fallback: ['monospace'],
|
||||
adjustFontFallback: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Font definitions.
|
||||
*/
|
||||
export const fonts: {
|
||||
[fontName in CustomizationDefaultFont | CustomizationDefaultMonospaceFont]: {
|
||||
variable: string;
|
||||
};
|
||||
} = {
|
||||
[CustomizationDefaultFont.Inter]: inter,
|
||||
[CustomizationDefaultFont.FiraSans]: firaSans,
|
||||
[CustomizationDefaultFont.IBMPlexSerif]: ibmPlexSerif,
|
||||
[CustomizationDefaultFont.Lato]: lato,
|
||||
[CustomizationDefaultFont.Merriweather]: merriweather,
|
||||
[CustomizationDefaultFont.NotoSans]: notoSans,
|
||||
[CustomizationDefaultFont.OpenSans]: openSans,
|
||||
[CustomizationDefaultFont.Overpass]: overpass,
|
||||
[CustomizationDefaultFont.Poppins]: poppins,
|
||||
[CustomizationDefaultFont.Raleway]: raleway,
|
||||
[CustomizationDefaultFont.Roboto]: roboto,
|
||||
[CustomizationDefaultFont.RobotoSlab]: robotoSlab,
|
||||
[CustomizationDefaultFont.SourceSansPro]: sourceSansPro,
|
||||
[CustomizationDefaultFont.Ubuntu]: ubuntu,
|
||||
[CustomizationDefaultFont.ABCFavorit]: abcFavorit,
|
||||
[CustomizationDefaultMonospaceFont.IBMPlexMono]: ibmPlexMono,
|
||||
[CustomizationDefaultMonospaceFont.DMMono]: dmMono,
|
||||
[CustomizationDefaultMonospaceFont.FiraCode]: firaCode,
|
||||
[CustomizationDefaultMonospaceFont.Inconsolata]: inconsolata,
|
||||
[CustomizationDefaultMonospaceFont.JetBrainsMono]: jetBrainsMono,
|
||||
[CustomizationDefaultMonospaceFont.RobotoMono]: robotoMono,
|
||||
[CustomizationDefaultMonospaceFont.SourceCodePro]: sourceCodePro,
|
||||
[CustomizationDefaultMonospaceFont.SpaceMono]: spaceMono,
|
||||
};
|
||||
return `@font-face {
|
||||
font-family: "${fallback.family}";
|
||||
src: local("${fallback.local}");
|
||||
ascent-override: ${fallback.ascentOverride};
|
||||
descent-override: ${fallback.descentOverride};
|
||||
line-gap-override: ${fallback.lineGapOverride};
|
||||
size-adjust: ${fallback.sizeAdjust};
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { CustomizationDefaultFont, CustomizationDefaultMonospaceFont } from '@gitbook/api';
|
||||
|
||||
/** The emoji font is always loaded, alongside whichever content and monospace fonts a site picked. */
|
||||
export const EMOJI_FONT = 'NotoColorEmoji';
|
||||
|
||||
export type FontName =
|
||||
| CustomizationDefaultFont
|
||||
| CustomizationDefaultMonospaceFont
|
||||
| typeof EMOJI_FONT;
|
||||
|
||||
export interface FontDefinition {
|
||||
/** `font-family` to declare the faces under. */
|
||||
family: string;
|
||||
/** Family id on Google Fonts, or `null` for the fonts we ship ourselves. */
|
||||
googleId: string | null;
|
||||
weights: string[];
|
||||
/** CSS variable the family is exposed as. */
|
||||
variable: '--font-content' | '--font-mono' | '--font-noto-color-emoji';
|
||||
/** Families appended after the (metric-adjusted) fallback. */
|
||||
fallback: string[];
|
||||
/** Declare a `<family> Fallback` face with metric overrides, so the swap shifts layout as
|
||||
* little as possible. Monospace fonts opt out, as they did under `next/font`. */
|
||||
adjustFallback: boolean;
|
||||
}
|
||||
|
||||
const content = (
|
||||
family: string,
|
||||
googleId: string,
|
||||
weights: string[],
|
||||
fallback: string[] = ['system-ui', 'arial']
|
||||
): FontDefinition => ({
|
||||
family,
|
||||
googleId,
|
||||
weights,
|
||||
variable: '--font-content',
|
||||
fallback,
|
||||
adjustFallback: true,
|
||||
});
|
||||
|
||||
const mono = (family: string, googleId: string, weights: string[]): FontDefinition => ({
|
||||
family,
|
||||
googleId,
|
||||
weights,
|
||||
variable: '--font-mono',
|
||||
fallback: ['monospace'],
|
||||
adjustFallback: false,
|
||||
});
|
||||
|
||||
export const FONT_DEFINITIONS: Record<FontName, FontDefinition> = {
|
||||
[CustomizationDefaultFont.Inter]: content('Inter', 'inter', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.FiraSans]: content(
|
||||
'Fira Sans Extra Condensed',
|
||||
'fira-sans-extra-condensed',
|
||||
['400', '500', '600', '700']
|
||||
),
|
||||
[CustomizationDefaultFont.IBMPlexSerif]: content(
|
||||
'IBM Plex Serif',
|
||||
'ibm-plex-serif',
|
||||
['400', '500', '600', '700'],
|
||||
['serif']
|
||||
),
|
||||
[CustomizationDefaultFont.Lato]: content('Lato', 'lato', ['400', '700', '900']),
|
||||
[CustomizationDefaultFont.Merriweather]: content(
|
||||
'Merriweather',
|
||||
'merriweather',
|
||||
['400', '700', '900'],
|
||||
['serif']
|
||||
),
|
||||
[CustomizationDefaultFont.NotoSans]: content('Noto Sans', 'noto-sans', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.OpenSans]: content('Open Sans', 'open-sans', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.Overpass]: content('Overpass', 'overpass', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.Poppins]: content('Poppins', 'poppins', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.Raleway]: content('Raleway', 'raleway', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.Roboto]: content('Roboto', 'roboto', ['400', '500', '600', '700']),
|
||||
[CustomizationDefaultFont.RobotoSlab]: content('Roboto Slab', 'roboto-slab', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.SourceSansPro]: content('Source Sans 3', 'source-sans-3', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultFont.Ubuntu]: content('Ubuntu', 'ubuntu', ['400', '500', '700']),
|
||||
[CustomizationDefaultFont.ABCFavorit]: {
|
||||
family: 'abcFavorit',
|
||||
googleId: null,
|
||||
weights: [],
|
||||
variable: '--font-content',
|
||||
fallback: ['system-ui', 'arial'],
|
||||
adjustFallback: true,
|
||||
},
|
||||
|
||||
[CustomizationDefaultMonospaceFont.IBMPlexMono]: mono('IBM Plex Mono', 'ibm-plex-mono', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.DMMono]: mono('DM Mono', 'dm-mono', ['400', '500']),
|
||||
[CustomizationDefaultMonospaceFont.FiraCode]: mono('Fira Code', 'fira-code', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.Inconsolata]: mono('Inconsolata', 'inconsolata', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.JetBrainsMono]: mono('JetBrains Mono', 'jetbrains-mono', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.RobotoMono]: mono('Roboto Mono', 'roboto-mono', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.SourceCodePro]: mono('Source Code Pro', 'source-code-pro', [
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
]),
|
||||
[CustomizationDefaultMonospaceFont.SpaceMono]: mono('Space Mono', 'space-mono', ['400', '700']),
|
||||
|
||||
[EMOJI_FONT]: {
|
||||
family: 'Noto Color Emoji',
|
||||
googleId: 'noto-color-emoji',
|
||||
weights: ['400'],
|
||||
variable: '--font-noto-color-emoji',
|
||||
fallback: [],
|
||||
adjustFallback: true,
|
||||
},
|
||||
};
|
||||
|
||||
// `abcFavorit` is licensed, so it ships in the repo instead of coming from Google Fonts. The
|
||||
// fallback metrics are the ones `next/font/local` measured from these exact files with fontkit.
|
||||
export const ABC_FAVORIT = {
|
||||
sources: [
|
||||
{ file: 'ABCFavorit-Variable.woff2', weight: '400 700', style: 'normal' },
|
||||
{ file: 'ABCFavorit-BoldItalic.woff2', weight: '700', style: 'italic' },
|
||||
{ file: 'ABCFavorit-MediumItalic.woff2', weight: '500', style: 'italic' },
|
||||
{ file: 'ABCFavorit-RegularItalic.woff2', weight: '400', style: 'italic' },
|
||||
],
|
||||
/** The design sits low in its em box; without this the fallback swap jumps vertically. */
|
||||
ascentOverride: '100%',
|
||||
fallbackMetrics: {
|
||||
ascentOverride: '90.97%',
|
||||
descentOverride: '37.34%',
|
||||
lineGapOverride: '0.00%',
|
||||
sizeAdjust: '104.43%',
|
||||
},
|
||||
} as const;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,342 @@
|
||||
{
|
||||
"definitionsHash": "d48c6da97e3309997e0ac0e87ad29a94064054fde8bd7ab33754f1d934804323",
|
||||
"google": {
|
||||
"inter": {
|
||||
"prefix": "https://fonts.gstatic.com/s/inter/v20",
|
||||
"files": [
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7W0Q5n-wU.woff2",
|
||||
"UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7W0Q5nw.woff2"
|
||||
]
|
||||
},
|
||||
"fira-sans-extra-condensed": {
|
||||
"prefix": "https://fonts.gstatic.com/s/firasansextracondensed/v11",
|
||||
"files": [
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fKuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fuuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fOuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fyuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fCuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1fGuulWcrE5Hcg.woff2",
|
||||
"NaPKcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda1f-uulWcrE4.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3W-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3y-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3S-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3u-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3e-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3a-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3QyNr3i-oWR9e2U.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3W-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3y-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3S-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3u-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3e-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3a-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3SCKr3i-oWR9e2U.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3W-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3y-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3S-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3u-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3e-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3a-oWR9e2WPJQ.woff2",
|
||||
"NaPPcYDaAO5dirw6IaFn7lPJFqXmS-M9Atn3wgda3USLr3i-oWR9e2U.woff2"
|
||||
]
|
||||
},
|
||||
"ibm-plex-serif": {
|
||||
"prefix": "https://fonts.gstatic.com/s/ibmplexserif/v20",
|
||||
"files": [
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUS2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUb2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUQ2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUR2zcZiVbJsNo.woff2",
|
||||
"jizDREVNn1dOx-zrZ2X3pZvkTiUf2zcZiVbJ.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI5q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CIwq1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI7q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI6q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3s-CI0q1vjitOh.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI5q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yIwq1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI7q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI6q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi3A_yI0q1vjitOh.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI5q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iIwq1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI7q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI6q1vjitOh3oc.woff2",
|
||||
"jizAREVNn1dOx-zrZ2X3pZvkTi2k_iI0q1vjitOh.woff2"
|
||||
]
|
||||
},
|
||||
"lato": {
|
||||
"prefix": "https://fonts.gstatic.com/s/lato/v25",
|
||||
"files": [
|
||||
"S6uyw4BMUTPHjxAwXiWtFCfQ7A.woff2",
|
||||
"S6uyw4BMUTPHjx4wXiWtFCc.woff2",
|
||||
"S6u9w4BMUTPHh6UVSwaPGQ3q5d0N7w.woff2",
|
||||
"S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2",
|
||||
"S6u9w4BMUTPHh50XSwaPGQ3q5d0N7w.woff2",
|
||||
"S6u9w4BMUTPHh50XSwiPGQ3q5d0.woff2"
|
||||
]
|
||||
},
|
||||
"merriweather": {
|
||||
"prefix": "https://fonts.gstatic.com/s/merriweather/v33",
|
||||
"files": [
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqnJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSequJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqlJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqkJ-mXq1Gi3iE.woff2",
|
||||
"u-4e0qyriQwlOrhSvowK_l5UcA6zuSYEqOzpPe3HOZJ5eX1WtLaQwmYiSeqqJ-mXq1Gi.woff2"
|
||||
]
|
||||
},
|
||||
"noto-sans": {
|
||||
"prefix": "https://fonts.gstatic.com/s/notosans/v42",
|
||||
"files": [
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aPdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5ardu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5a_du3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aLdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5a3du3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aHdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5aDdu3mhPy1Fig.woff2",
|
||||
"o-0bIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjc5a7du3mhPy0.woff2"
|
||||
]
|
||||
},
|
||||
"open-sans": {
|
||||
"prefix": "https://fonts.gstatic.com/s/opensans/v44",
|
||||
"files": [
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu0SC55K5gw.woff2",
|
||||
"memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-mu0SC55I.woff2"
|
||||
]
|
||||
},
|
||||
"overpass": {
|
||||
"prefix": "https://fonts.gstatic.com/s/overpass/v19",
|
||||
"files": [
|
||||
"qFdH35WCmI96Ajtm81GoU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GhU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GqU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GrU9vgwBcIs1s.woff2",
|
||||
"qFdH35WCmI96Ajtm81GlU9vgwBcI.woff2"
|
||||
]
|
||||
},
|
||||
"poppins": {
|
||||
"prefix": "https://fonts.gstatic.com/s/poppins/v24",
|
||||
"files": [
|
||||
"pxiEyp8kv8JHgFVrJJbecnFHGPezSQ.woff2",
|
||||
"pxiEyp8kv8JHgFVrJJnecnFHGPezSQ.woff2",
|
||||
"pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2",
|
||||
"pxiByp8kv8JHgFVrLGT9Z11lFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLGT9Z1JlFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLGT9Z1xlFd2JQEk.woff2",
|
||||
"pxiByp8kv8JHgFVrLEj6Z11lFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLEj6Z1JlFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLEj6Z1xlFd2JQEk.woff2",
|
||||
"pxiByp8kv8JHgFVrLCz7Z11lFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLCz7Z1JlFd2JQEl8qw.woff2",
|
||||
"pxiByp8kv8JHgFVrLCz7Z1xlFd2JQEk.woff2"
|
||||
]
|
||||
},
|
||||
"raleway": {
|
||||
"prefix": "https://fonts.gstatic.com/s/raleway/v37",
|
||||
"files": [
|
||||
"1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.woff2",
|
||||
"1Ptug8zYS_SKggPNyC0IT4ttDfA.woff2"
|
||||
]
|
||||
},
|
||||
"roboto": {
|
||||
"prefix": "https://fonts.gstatic.com/s/roboto/v51",
|
||||
"files": [
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBHMdazTgWw.woff2",
|
||||
"KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBHMdazQ.woff2"
|
||||
]
|
||||
},
|
||||
"roboto-slab": {
|
||||
"prefix": "https://fonts.gstatic.com/s/robotoslab/v36",
|
||||
"files": [
|
||||
"BngMUXZYTXPIvIBgJJSb6ufA5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufJ5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufB5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufO5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufC5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufD5qWr4xCCQ_k.woff2",
|
||||
"BngMUXZYTXPIvIBgJJSb6ufN5qWr4xCC.woff2"
|
||||
]
|
||||
},
|
||||
"source-sans-3": {
|
||||
"prefix": "https://fonts.gstatic.com/s/sourcesans3/v19",
|
||||
"files": [
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wIaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wsaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wMaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wwaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wAaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3wEaZejf5HdF8Q.woff2",
|
||||
"nwpStKy2OAdR1K-IwhWudF-R3w8aZejf5Hc.woff2"
|
||||
]
|
||||
},
|
||||
"ubuntu": {
|
||||
"prefix": "https://fonts.gstatic.com/s/ubuntu/v21",
|
||||
"files": [
|
||||
"4iCs6KVjbNBYlgoKcg72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKew72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKcw72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKfA72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKcQ72nU6AF7xm.woff2",
|
||||
"4iCs6KVjbNBYlgoKfw72nU6AFw.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jvWyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jtGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jvGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3js2yNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jvmyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCjC3jsGyNPYZvgw.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjvWyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjtGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjvGyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjs2yNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjvmyNPYZvg7UI.woff2",
|
||||
"4iCv6KVjbNBYlgoCxCvjsGyNPYZvgw.woff2"
|
||||
]
|
||||
},
|
||||
"ibm-plex-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/ibmplexmono/v20",
|
||||
"files": [
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1iIq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1isq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1iAq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1iEq131nj-otFQ.woff2",
|
||||
"-F63fjptAgt5VM-kVkqdyU8n1i8q131nj-o.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwl1FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwlRFgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwl9FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwl5FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3twJwlBFgsAXHNk.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwl1FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwlRFgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwl9FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwl5FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3vAOwlBFgsAXHNk.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwl1FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwlRFgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwl9FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwl5FgsAXHNlYzg.woff2",
|
||||
"-F6qfjptAgt5VM-kVkqdyU8n3pQPwlBFgsAXHNk.woff2"
|
||||
]
|
||||
},
|
||||
"dm-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/dmmono/v16",
|
||||
"files": [
|
||||
"aFTU7PB1QTsUX8KYthSQBK6PYK3EXw.woff2",
|
||||
"aFTU7PB1QTsUX8KYthqQBK6PYK0.woff2",
|
||||
"aFTR7PB1QTsUX8KYvumzEY2tbYf-Vlh3uA.woff2",
|
||||
"aFTR7PB1QTsUX8KYvumzEYOtbYf-Vlg.woff2"
|
||||
]
|
||||
},
|
||||
"fira-code": {
|
||||
"prefix": "https://fonts.gstatic.com/s/firacode/v27",
|
||||
"files": [
|
||||
"uU9NCBsR6Z2vfE9aq3bh0NSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh2dSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh0dSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh3tSDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bhZ_Wmh3mUfBsu_Q.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh09SDqFGedCMX.woff2",
|
||||
"uU9NCBsR6Z2vfE9aq3bh3dSDqFGedA.woff2"
|
||||
]
|
||||
},
|
||||
"inconsolata": {
|
||||
"prefix": "https://fonts.gstatic.com/s/inconsolata/v37",
|
||||
"files": [
|
||||
"QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyxq15IDhunJ_o.woff2",
|
||||
"QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyx615IDhunJ_o.woff2",
|
||||
"QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyya15IDhunA.woff2"
|
||||
]
|
||||
},
|
||||
"jetbrains-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/jetbrainsmono/v24",
|
||||
"files": [
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwgknk-6nFg.woff2",
|
||||
"tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwgknk-4.woff2"
|
||||
]
|
||||
},
|
||||
"roboto-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/robotomono/v31",
|
||||
"files": [
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2",
|
||||
"L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2"
|
||||
]
|
||||
},
|
||||
"source-code-pro": {
|
||||
"prefix": "https://fonts.gstatic.com/s/sourcecodepro/v31",
|
||||
"files": [
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlMOvWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlOevWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlMevWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlPuvWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlMuvWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlM-vWnsUnxlC9.woff2",
|
||||
"HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevWnsUnxg.woff2"
|
||||
]
|
||||
},
|
||||
"space-mono": {
|
||||
"prefix": "https://fonts.gstatic.com/s/spacemono/v17",
|
||||
"files": [
|
||||
"i7dPIFZifjKcF5UAWdDRYE58RXi4EwSsbg.woff2",
|
||||
"i7dPIFZifjKcF5UAWdDRYE98RXi4EwSsbg.woff2",
|
||||
"i7dPIFZifjKcF5UAWdDRYEF8RXi4EwQ.woff2",
|
||||
"i7dMIFZifjKcF5UAWdDRaPpZUFqaHi6WZ3S_Yg.woff2",
|
||||
"i7dMIFZifjKcF5UAWdDRaPpZUFuaHi6WZ3S_Yg.woff2",
|
||||
"i7dMIFZifjKcF5UAWdDRaPpZUFWaHi6WZ3Q.woff2"
|
||||
]
|
||||
},
|
||||
"noto-color-emoji": {
|
||||
"prefix": "https://fonts.gstatic.com/s/notocoloremoji/v40",
|
||||
"files": [
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.0.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.1.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.2.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.3.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.4.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.5.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.6.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.7.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.8.woff2",
|
||||
"Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4gw.9.woff2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"local": {
|
||||
"abcfavorit/7bb31c73cb8d820e.woff2": "./ABCFavorit/ABCFavorit-Variable.woff2",
|
||||
"abcfavorit/aed849de6e333605.woff2": "./ABCFavorit/ABCFavorit-BoldItalic.woff2",
|
||||
"abcfavorit/b435fb7330f5e171.woff2": "./ABCFavorit/ABCFavorit-MediumItalic.woff2",
|
||||
"abcfavorit/aee27c224ccd5167.woff2": "./ABCFavorit/ABCFavorit-RegularItalic.woff2"
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,32 @@ import type {
|
||||
} from '@gitbook/api';
|
||||
|
||||
import { generateFontFacesCSS, getFontSourcesToPreload } from './custom';
|
||||
import { fonts } from './default';
|
||||
import { generateDefaultFontFacesCSS } from './default';
|
||||
|
||||
export { DEFAULT_MONOSPACE_FONT, generateEmojiFontFacesCSS } from './default';
|
||||
|
||||
/**
|
||||
* Represents font data for either a default font or a custom font
|
||||
*/
|
||||
export type FontData = DefaultFontData | CustomFontData;
|
||||
|
||||
interface BaseFontData {
|
||||
/** `@font-face` rules and the CSS variable, to inline in the document head. */
|
||||
fontFaceRules: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font data for a default font, currently handle with next/font
|
||||
* Font data for a default font, self-hosted from our own assets
|
||||
*/
|
||||
interface DefaultFontData {
|
||||
interface DefaultFontData extends BaseFontData {
|
||||
type: 'default';
|
||||
variable: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font data for a custom font with @font-face rules
|
||||
*/
|
||||
interface CustomFontData {
|
||||
interface CustomFontData extends BaseFontData {
|
||||
type: 'custom';
|
||||
fontFaceRules: string;
|
||||
preloadSources: CustomizationFontDefinition['fontFaces'];
|
||||
}
|
||||
|
||||
@@ -39,7 +44,7 @@ export function getFontData(
|
||||
if (typeof font === 'string') {
|
||||
return {
|
||||
type: 'default',
|
||||
variable: fonts[font].variable,
|
||||
fontFaceRules: generateDefaultFontFacesCSS(font),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export interface FontVariantData {
|
||||
weight: string;
|
||||
style: string;
|
||||
/** Path under `~gitbook/static/fonts`, index-aligned with the family's `subsets`. */
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface FontFallbackFaceData {
|
||||
family: string;
|
||||
local: string;
|
||||
ascentOverride: string;
|
||||
descentOverride: string;
|
||||
lineGapOverride: string;
|
||||
sizeAdjust: string;
|
||||
}
|
||||
|
||||
export interface FontFamilyData {
|
||||
family: string;
|
||||
variable: string;
|
||||
/** Full value for the CSS variable, fallbacks included. */
|
||||
fontFamilyValue: string;
|
||||
/** `unicode-range` per subset. Held once per family rather than repeated on every variant. */
|
||||
subsets: string[];
|
||||
variants: FontVariantData[];
|
||||
fallbackFace: FontFallbackFaceData | null;
|
||||
/** Applied to every face of the family. */
|
||||
ascentOverride?: string;
|
||||
}
|
||||
|
||||
export type FontFacesData = Record<string, FontFamilyData>;
|
||||
|
||||
/** Where `scripts/download-fonts.ts` fetches (or copies) each file from. */
|
||||
export interface FontSourcesData {
|
||||
/** Hash of definitions.ts at generation time, used to detect a stale manifest. */
|
||||
definitionsHash: string;
|
||||
google: Record<string, { prefix: string; files: string[] }>;
|
||||
local: Record<string, string>;
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
"outputs": [
|
||||
"public/~gitbook/static/icons/**/*",
|
||||
"public/~gitbook/static/math/**/*",
|
||||
"public/~gitbook/static/embed/**/*"
|
||||
"public/~gitbook/static/embed/**/*",
|
||||
"public/~gitbook/static/fonts/**/*"
|
||||
],
|
||||
"dependsOn": ["^generate", "@gitbook/embed#build"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user