Self-host default fonts and inline only the ones a site uses (RND-12524) (#4522)

This commit is contained in:
Nolann B.
2026-08-20 12:36:55 +02:00
committed by GitHub
parent 75bdff3ff3
commit 7bab574c63
13 changed files with 2445 additions and 312 deletions
+5
View File
@@ -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.
+3 -2
View File
@@ -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 ? (
+71 -285
View File
@@ -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};
}`;
}
+178
View File
@@ -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"
}
}
+12 -7
View File
@@ -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),
};
}
+38
View File
@@ -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>;
}
+2 -1
View File
@@ -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"]
},