Skip to content

Commit

Permalink
refactor(cli): expose css generation for each package (#836)
Browse files Browse the repository at this point in the history
* refactor: expose css generation for each package

* Create weak-queens-eat.md

* fix: invalid weight file name
  • Loading branch information
ayuhito committed Sep 20, 2023
1 parent aec97a1 commit 398284b
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 100 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-queens-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fontsource-utils/cli": patch
---

refactor(cli): expose css generation for each package
159 changes: 121 additions & 38 deletions packages/cli/src/google/packager-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@ import {
type APIIconResponse,
APIIconStatic,
APIIconVariable,
type FontObjectV2,
type FontObjectVariable,
} from 'google-font-metadata';
import * as path from 'pathe';

import type { BuildOptions } from '../types';
import type { BuildOptions, CSSGenerate } from '../types';
import {
findClosest,
makeFontFilePath,
makeVariableFontFilePath,
} from '../utils';

const packagerIconsStatic = async (id: string, opts: BuildOptions) => {
const { family, styles, weights, subsets, variants } = APIIconStatic[id];
const generateIconStaticCSS = (
metadata: FontObjectV2['id'],
makeFontFilePath: (
id: string,
subset: string,
weight: string,
style: string,
extension: string,
) => string,
): CSSGenerate => {
const cssGenerate: CSSGenerate = [];
const { id, family, styles, weights, subsets, variants } = metadata;

// Find the weight for index.css in the case weight 400 does not exist.
const indexWeight = findClosest(weights, 400);
Expand All @@ -37,11 +49,23 @@ const packagerIconsStatic = async (id: string, opts: BuildOptions) => {
weight,
src: [
{
url: makeFontFilePath(id, subset, weight, style, 'woff2'),
url: makeFontFilePath(
id,
subset,
String(weight),
style,
'woff2',
),
format: 'woff2' as const,
},
{
url: makeFontFilePath(id, subset, weight, style, 'woff'),
url: makeFontFilePath(
id,
subset,
String(weight),
style,
'woff',
),
format: 'woff' as const,
},
],
Expand All @@ -51,70 +75,107 @@ const packagerIconsStatic = async (id: string, opts: BuildOptions) => {
const css = generateFontFace(fontObj);

if (style === 'normal') {
let cssPath = path.join(opts.dir, `${weight}.css`);
await fs.writeFile(cssPath, css);

cssPath = path.join(opts.dir, `${subset}-${weight}.css`);
await fs.writeFile(cssPath, css);
cssGenerate.push(
{
filename: `${weight}.css`,
css,
},
{
filename: `${subset}-${weight}.css`,
css,
},
);
} else {
let cssPath = path.join(opts.dir, `${weight}-italic.css`);
await fs.writeFile(cssPath, css);

cssPath = path.join(opts.dir, `${subset}-${weight}-italic.css`);
await fs.writeFile(cssPath, css);
cssGenerate.push(
{
filename: `${weight}-italic.css`,
css,
},
{
filename: `${subset}-${weight}-italic.css`,
css,
},
);
}
cssSubset.push(css);
}
}

// If the weight is index, generate index.css
if (weight === indexWeight) {
const cssIndexPath = path.join(opts.dir, 'index.css');
await fs.writeFile(cssIndexPath, cssSubset.join('\n\n'));
cssGenerate.push({
filename: 'index.css',
css: cssSubset.join('\n\n'),
});
}

const cssSubsetPath = path.join(opts.dir, `${subset}.css`);
await fs.writeFile(cssSubsetPath, cssSubset.join('\n\n'));
cssGenerate.push({
filename: `${subset}.css`,
css: cssSubset.join('\n\n'),
});
}
}

return cssGenerate;
};

const packagerIconsVariable = async (id: string, opts: BuildOptions) => {
const icon = APIIconVariable[id];
const packagerIconsStatic = async (id: string, opts: BuildOptions) => {
const cssGenerate = generateIconStaticCSS(
APIIconStatic[id],
makeFontFilePath,
);

for (const item of cssGenerate) {
const cssPath = path.join(opts.dir, item.filename);
await fs.writeFile(cssPath, item.css);
}
};

const generateIconVariableCSS = (
metadata: FontObjectVariable['id'],
makeFontFilePath: (
id: string,
subset: string,
axesLower: string,
style: string,
) => string,
): CSSGenerate => {
const cssGenerate: CSSGenerate = [];
const { id, family, variants, axes } = metadata;

// Generate CSS
let indexCSS = '';

for (const axes of Object.keys(icon.variants)) {
const variant = icon.variants[axes];
for (const axesKey of Object.keys(variants)) {
const variant = variants[axesKey];
const styles = Object.keys(variant);
const axesLower = axes.toLowerCase();
const axesLower = axesKey.toLowerCase();

// These are variable modifiers to change specific CSS selectors
// for variable fonts.
const variableOpts: APIIconResponse['axes'] = {
wght: icon.axes.wght,
wght: axes.wght,
};
if (axes === 'standard' || axes === 'full' || axes === 'wdth')
variableOpts.stretch = icon.axes.wdth;
if (axesKey === 'standard' || axesKey === 'full' || axesKey === 'wdth')
variableOpts.stretch = axes.wdth;

if (axes === 'standard' || axes === 'full' || axes === 'slnt')
variableOpts.slnt = icon.axes.slnt;
if (axesKey === 'standard' || axesKey === 'full' || axesKey === 'slnt')
variableOpts.slnt = axes.slnt;

// Generate variable CSS
for (const style of styles) {
const cssStyle: string[] = [];

for (const subset of Object.keys(variant[style])) {
const fontObj: FontObject = {
family: icon.family,
family,
style,
display: 'swap',
weight: Number(icon.axes.wght.default),
weight: Number(axes.wght.default),
variable: variableOpts,
src: [
{
url: makeVariableFontFilePath(id, subset, axesLower, style),
url: makeFontFilePath(id, subset, axesLower, style),
format: 'woff2-variations',
},
],
Expand All @@ -129,18 +190,40 @@ const packagerIconsVariable = async (id: string, opts: BuildOptions) => {
// Write down CSS
const filename =
style === 'normal' ? `${axesLower}.css` : `${axesLower}-${style}.css`;
const cssPath = path.join(opts.dir, filename);
const css = cssStyle.join('\n\n');
await fs.writeFile(cssPath, css);
cssGenerate.push({
filename,
css,
});

// Some fonts may not have a wght axis, but usually have an opsz axis to compensate
if (axes === 'wght') indexCSS = css;
if (!indexCSS && axes === 'opsz') indexCSS = css;
if (axesKey === 'wght') indexCSS = css;
if (!indexCSS && axesKey === 'opsz') indexCSS = css;
}
}

// Write down index.css for variable package
await fs.writeFile(path.join(opts.dir, 'index.css'), indexCSS);
cssGenerate.push({
filename: 'index.css',
css: indexCSS,
});

return cssGenerate;
};

export { packagerIconsStatic, packagerIconsVariable };
const packagerIconsVariable = async (id: string, opts: BuildOptions) => {
const icon = APIIconVariable[id];
const cssGenerate = generateIconVariableCSS(icon, makeVariableFontFilePath);

for (const item of cssGenerate) {
const cssPath = path.join(opts.dir, item.filename);
await fs.writeFile(cssPath, item.css);
}
};

export {
generateIconStaticCSS,
generateIconVariableCSS,
packagerIconsStatic,
packagerIconsVariable,
};
76 changes: 58 additions & 18 deletions packages/cli/src/google/packager-v1.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
/* eslint-disable no-await-in-loop */
import { generateFontFace } from '@fontsource-utils/generate';
import fs from 'fs-extra';
import { APIv1 } from 'google-font-metadata';
import { APIv1, type FontObjectV1 } from 'google-font-metadata';
import * as path from 'pathe';

import type { BuildOptions } from '../types';
import type { BuildOptions, CSSGenerate } from '../types';
import { makeFontFilePath } from '../utils';

const packagerV1 = async (id: string, opts: BuildOptions) => {
const { family, styles, weights, subsets, variants } = APIv1[id];
const generateV1CSS = (
metadata: FontObjectV1['id'],
makeFontFilePath: (
id: string,
subset: string,
weight: string,
style: string,
extension: string,
) => string,
): CSSGenerate => {
const cssGenerate: CSSGenerate = [];
const { id, family, styles, weights, subsets, variants } = metadata;

for (const subset of subsets) {
// Arrays of CSS blocks to be concatenated
Expand All @@ -26,11 +36,23 @@ const packagerV1 = async (id: string, opts: BuildOptions) => {
weight,
src: [
{
url: makeFontFilePath(id, subset, weight, style, 'woff2'),
url: makeFontFilePath(
id,
subset,
String(weight),
style,
'woff2',
),
format: 'woff2' as const,
},
{
url: makeFontFilePath(id, subset, weight, style, 'woff'),
url: makeFontFilePath(
id,
subset,
String(weight),
style,
'woff',
),
format: 'woff' as const,
},
],
Expand All @@ -41,31 +63,49 @@ const packagerV1 = async (id: string, opts: BuildOptions) => {

// Needed to differentiate filenames
if (style === 'normal') {
const cssPath = path.join(opts.dir, `${subset}-${weight}.css`);
await fs.writeFile(cssPath, css);
cssGenerate.push({
filename: `${subset}-${weight}.css`,
css,
});

cssSubset.push(css);
} else {
const cssStylePath = path.join(
opts.dir,
`${subset}-${weight}-${style}.css`
);
await fs.writeFile(cssStylePath, css);
cssGenerate.push({
filename: `${subset}-${weight}-${style}.css`,
css,
});

cssSubsetItalic.push(css);
}
}
}
}
const cssSubsetPath = path.join(opts.dir, `${subset}.css`);
await fs.writeFile(cssSubsetPath, cssSubset.join('\n\n'));

cssGenerate.push({
filename: `${subset}.css`,
css: cssSubset.join('\n\n'),
});

// If there are italic styles for a subset
if (cssSubsetItalic.length > 0) {
const cssSubsetItalicPath = path.join(opts.dir, `${subset}-italic.css`);
await fs.writeFile(cssSubsetItalicPath, cssSubsetItalic.join('\n\n'));
cssGenerate.push({
filename: `${subset}-italic.css`,
css: cssSubsetItalic.join('\n\n'),
});
}
}

return cssGenerate;
};

const packagerV1 = async (id: string, opts: BuildOptions) => {
const metadata = APIv1[id];
const cssGenerate = generateV1CSS(metadata, makeFontFilePath);

for (const item of cssGenerate) {
const cssPath = path.join(opts.dir, item.filename);
await fs.writeFile(cssPath, item.css);
}
};

export { packagerV1 };
export { generateV1CSS, packagerV1 };

0 comments on commit 398284b

Please sign in to comment.