Skip to content

Commit

Permalink
Generate proper font fallbacks which match the metrics of the font we…
Browse files Browse the repository at this point in the history
… use
  • Loading branch information
sandhose committed Oct 22, 2024
1 parent 407e0a3 commit 3717ddd
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"@babel/plugin-transform-react-jsx": "^7.24.7",
"@babel/types": "^7.24.7",
"@biomejs/biome": "^1.8.3",
"@capsizecss/core": "^4.1.2",
"@capsizecss/metrics": "^3.3.0",
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@tokens-studio/sd-transforms": "^1.0.0",
Expand Down
21 changes: 21 additions & 0 deletions src/configs/getWebConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import { camelCase } from "lodash-es";
import StyleDictionary from "style-dictionary";
import type { File, PlatformConfig } from "style-dictionary/types";
import type { Theme } from "../@types";
import { isCoreColor } from "../filters/isCoreColor";
Expand All @@ -25,9 +26,22 @@ import {
type Tier,
cssFileName,
} from "../utils/cssFileName";
import { fontFaces, fontFamilyOverrides } from "../utils/fontFallbacks";

const basePxFontSize = 16;

StyleDictionary.registerFormat({
name: "css/fontFallbacks",
format: () => fontFaces,
});

StyleDictionary.registerTransform({
name: "css/fontFallbacks",
type: "value",
filter: (token) => token.type === "fontFamilies",
transform: (token) => fontFamilyOverrides[token.value] ?? token.value,
});

export default function (
target: "js" | "css" | "ts",
theme: Theme,
Expand All @@ -42,6 +56,7 @@ export default function (
"attribute/cti",
"css/pxToRem",
"css/percentageToUnitless",
"css/fontFallbacks",
target === "css" ? "name/kebab" : "camelCaseDecimal",
];

Expand Down Expand Up @@ -115,6 +130,11 @@ function getFilesFormat(theme: Theme, target: "css" | "js" | "ts"): File[] {
},
});

const fontFaces = {
destination: `${COMPOUND_TOKENS_NAMESPACE}-font-fallbacks.css`,
format: "css/fontFallbacks",
};

return [
common("base"),
common("semantic"),
Expand All @@ -126,5 +146,6 @@ function getFilesFormat(theme: Theme, target: "css" | "js" | "ts"): File[] {
// This file is to be imported with a media query import
themed("base", true),
themed("semantic", true),
fontFaces,
];
}
221 changes: 221 additions & 0 deletions src/utils/fontFallbacks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { createFontStack } from "@capsizecss/core";

// The goal of this is to generate @font-face rules to fallback to local fonts,
// but with font metrics adjusted to match the ones of the font we actually use.
// This way, we can display temporarily a local sans-serif font, laid out very
// close to what Inter will look like once we actually loaded it.
//
// Out of this file, we get two exports:
// - fontFamilyOverrides: a mapping from font we have in tokens to a font-family
// which has all the overrides
// - fontFaces: the @font-face rules to inject

// Inter: the font we actually use
import inter500 from "@capsizecss/metrics/inter/500";
import inter500Italic from "@capsizecss/metrics/inter/500italic";
import inter600 from "@capsizecss/metrics/inter/600";
import inter600Italic from "@capsizecss/metrics/inter/600italic";
import inter400Italic from "@capsizecss/metrics/inter/italic";
import inter400 from "@capsizecss/metrics/inter/regular";

// Roboto
import roboto500 from "@capsizecss/metrics/roboto/500";
import roboto500Italic from "@capsizecss/metrics/roboto/500italic";
import roboto700 from "@capsizecss/metrics/roboto/700";
import roboto700Italic from "@capsizecss/metrics/roboto/700italic";
import roboto400Italic from "@capsizecss/metrics/roboto/italic";
import roboto400 from "@capsizecss/metrics/roboto/regular";

// Segoe UI
import segoeUI600 from "@capsizecss/metrics/segoeUI/600";
import segoeUI600Italic from "@capsizecss/metrics/segoeUI/600italic";
import segoeUI700 from "@capsizecss/metrics/segoeUI/700";
import segoeUI700Italic from "@capsizecss/metrics/segoeUI/700italic";
import segoeUI400Italic from "@capsizecss/metrics/segoeUI/italic";
import segoeUI400 from "@capsizecss/metrics/segoeUI/regular";

// Helvetica Neue
import helveticaNeue500 from "@capsizecss/metrics/helveticaNeue/500";
import helveticaNeue500Italic from "@capsizecss/metrics/helveticaNeue/500italic";
import helveticaNeue700 from "@capsizecss/metrics/helveticaNeue/700";
import helveticaNeue700Italic from "@capsizecss/metrics/helveticaNeue/700italic";
import helveticaNeue400Italic from "@capsizecss/metrics/helveticaNeue/italic";
import helveticaNeue400 from "@capsizecss/metrics/helveticaNeue/regular";

// Ubuntu
import ubuntu500 from "@capsizecss/metrics/ubuntu/500";
import ubuntu500Italic from "@capsizecss/metrics/ubuntu/500italic";
import ubuntu700 from "@capsizecss/metrics/ubuntu/700";
import ubuntu700Italic from "@capsizecss/metrics/ubuntu/700italic";
import ubuntu400Italic from "@capsizecss/metrics/ubuntu/italic";
import ubuntu400 from "@capsizecss/metrics/ubuntu/regular";

// Fira Sans
import firaSans500 from "@capsizecss/metrics/firaSans/500";
import firaSans500Italic from "@capsizecss/metrics/firaSans/500italic";
import firaSans600 from "@capsizecss/metrics/firaSans/600";
import firaSans600Italic from "@capsizecss/metrics/firaSans/600italic";
import firaSans400Italic from "@capsizecss/metrics/firaSans/italic";
import firaSans400 from "@capsizecss/metrics/firaSans/regular";

// Noto Sans
import notoSans500 from "@capsizecss/metrics/notoSans/500";
import notoSans500Italic from "@capsizecss/metrics/notoSans/500italic";
import notoSans600 from "@capsizecss/metrics/notoSans/600";
import notoSans600Italic from "@capsizecss/metrics/notoSans/600italic";
import notoSans400Italic from "@capsizecss/metrics/notoSans/italic";
import notoSans400 from "@capsizecss/metrics/notoSans/regular";

// The ultimate sans-serif fallback: Arial
import arial700 from "@capsizecss/metrics/arial/700";
import arial700Italic from "@capsizecss/metrics/arial/700italic";
import arial400Italic from "@capsizecss/metrics/arial/italic";
import arial400 from "@capsizecss/metrics/arial/regular";

const { fontFamily: interFontFamily, fontFaces: inter400Stack } =
createFontStack(
[
inter400,
helveticaNeue400,
segoeUI400,
roboto400,
ubuntu400,
firaSans400,
notoSans400,
arial400,
],
{
fontFaceProperties: {
fontStyle: "normal",
fontWeight: 400,
fontDisplay: "swap",
},
},
);

const { fontFaces: inter400ItalicStack } = createFontStack(
[
inter400Italic,
helveticaNeue400Italic,
segoeUI400Italic,
roboto400Italic,
ubuntu400Italic,
firaSans400Italic,
notoSans400Italic,
arial400Italic,
],
{
fontFaceProperties: {
fontStyle: "italic",
fontWeight: 400,
fontDisplay: "swap",
},
},
);

const { fontFaces: inter500Stack } = createFontStack(
[
inter500,
helveticaNeue500,
segoeUI600,
roboto500,
ubuntu500,
firaSans500,
notoSans500,
],
{
fontFaceProperties: {
fontStyle: "normal",
fontWeight: 500,
fontDisplay: "swap",
},
},
);

const { fontFaces: inter500ItalicStack } = createFontStack(
[
inter500Italic,
helveticaNeue500Italic,
segoeUI600Italic,
roboto500Italic,
ubuntu500Italic,
firaSans500Italic,
notoSans500Italic,
],
{
fontFaceProperties: {
fontStyle: "italic",
fontWeight: 500,
fontDisplay: "swap",
},
},
);

const { fontFaces: inter600Stack } = createFontStack(
[
inter600,
helveticaNeue700,
segoeUI700,
roboto700,
ubuntu700,
firaSans600,
notoSans600,
arial700,
],
{
fontFaceProperties: {
fontStyle: "normal",
fontWeight: 600,
fontDisplay: "swap",
},
},
);

const { fontFaces: inter600ItalicStack } = createFontStack(
[
inter600Italic,
helveticaNeue700Italic,
segoeUI700Italic,
roboto700Italic,
ubuntu700Italic,
firaSans600Italic,
notoSans600Italic,
arial700Italic,
],
{
fontFaceProperties: {
fontStyle: "italic",
fontWeight: 600,
fontDisplay: "swap",
},
},
);

// The font token -> font-family mapping used to override
export const fontFamilyOverrides: Record<string, string> = {
Inter: `${interFontFamily}, sans-serif`,

// We don't bother to compute accurate fallbacks for the monospace font for
// now, as we don't use it as much, but we make sure we have at least a
// cross-browser fallback for it
Inconsolata: "Inconsolata, ui-monospace, monospace",
};

// The CSS @font-face rules to inject
export const fontFaces = `/* Fallback for Inter regular */
${inter400Stack}
${inter400ItalicStack}
/* Fallback for Inter medium */
${inter500Stack}
${inter500ItalicStack}
/* Fallback for Inter semibold */
${inter600Stack}
${inter600ItalicStack}
`;
7 changes: 6 additions & 1 deletion src/utils/generateCssIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ limitations under the License.
import path from "node:path";
import fs from "fs-extra";
import type { Theme } from "../@types";
import { type Tier, cssFileName } from "./cssFileName";
import {
COMPOUND_TOKENS_NAMESPACE,
type Tier,
cssFileName,
} from "./cssFileName";

const header = `/* Establish a layer order that allows semantic tokens to be customized, but not base tokens.
* The layers are prefixed by 'cpd-' because Tailwind will interpret '@layer base' directives.
Expand All @@ -29,6 +33,7 @@ const tiers: Tier[] = ["base", "semantic"];

export function generateCssIndex(): void {
const imports = [
`@import url("./${COMPOUND_TOKENS_NAMESPACE}-font-fallbacks.css");`,
...(function* () {
for (const theme of themes) {
for (const tier of tiers) {
Expand Down
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,18 @@
dependencies:
postcss-calc-ast-parser "^0.1.4"

"@capsizecss/core@^4.1.2":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@capsizecss/core/-/core-4.1.2.tgz#a278e19953ed4922653d2c2a7614915fd8fc5f24"
integrity sha512-5tMjLsVsaEEwJ816y3eTfhhTIyUWNFt58x6YcHni0eV5tta8MGDOAIe+CV5ICb5pguXgDpNGLprqhPqBWtkFSg==
dependencies:
csstype "^3.1.1"

"@capsizecss/metrics@^3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@capsizecss/metrics/-/metrics-3.3.0.tgz#bbbe3df4cd5acedbb605757ae0ba440cf7ea9d17"
integrity sha512-WAQtKgyz7fZDEMuERSLPmWXuV53O/HDJZLof8BMWEX1GTWYiiNdqGA6j56+GCSSeVyzYDxkBnm5taIh0YyW6fQ==

"@esbuild/[email protected]":
version "0.23.1"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353"
Expand Down Expand Up @@ -1031,6 +1043,11 @@ csso@^4.2.0:
dependencies:
css-tree "^1.1.2"

csstype@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==

debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
Expand Down

0 comments on commit 3717ddd

Please sign in to comment.