Skip to content

Commit

Permalink
fix(api): generate variable variants for css generator (#909)
Browse files Browse the repository at this point in the history
* refactor: move validation funcs to own file

* fix: generate variants for variable fonts

* Create wild-meals-watch.md
  • Loading branch information
ayuhito committed Dec 10, 2023
1 parent 6216b7d commit c9796f8
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 173 deletions.
6 changes: 6 additions & 0 deletions .changeset/wild-meals-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"cdn": patch
"common-api": patch
---

fix(api): generate variable variants for css generator
26 changes: 15 additions & 11 deletions api/cdn/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import {
generateV2CSS,
generateVariableCSS,
} from '@fontsource-utils/cli';
import {
type IDResponse,
type VariableMetadataWithVariants,
} from 'common-api/types';
import { type IDResponse, type VariableMetadata } from 'common-api/types';
import { StatusError } from 'itty-router';

import { generateVariableVariants } from './util';

const CSS_TTL = 60 * 60 * 24; // 1 day

const makeFontFilePath = (
export const makeFontFilePath = (
tag: string,
subset: string,
weight: string,
Expand All @@ -26,7 +25,7 @@ const makeFontFilePath = (
.replace(']', '');
};

const makeFontFileVariablePath = (
export const makeFontFileVariablePath = (
tag: string,
subset: string,
axes: string,
Expand Down Expand Up @@ -92,7 +91,7 @@ export const updateCss = async (
}

if (!css) {
throw new StatusError(404, 'Not Found. Invalid filename.');
throw new StatusError(404, 'Not Found. Unable to find filename.');
}

return css;
Expand All @@ -103,7 +102,7 @@ export const updateVariableCSS = async (
version: string,
file: string,
metadata: IDResponse,
variableMeta: VariableMetadataWithVariants,
variableMeta: VariableMetadata,
env: Env,
ctx: ExecutionContext,
): Promise<string> => {
Expand All @@ -112,10 +111,15 @@ export const updateVariableCSS = async (
const tag = `${id}@${version}`;
const vfTag = `${id}:vf@${version}`;

const generateMeta = {
id,
...generateVariableVariants(metadata, variableMeta),
};

// Icons are handled differently
if (category === 'icons' && type === 'google') {
const cssGenerate = generateIconVariableCSS(
variableMeta,
generateMeta,
makeFontFileVariablePath,
vfTag,
);
Expand All @@ -140,7 +144,7 @@ export const updateVariableCSS = async (
} else {
const cssGenerate = generateVariableCSS(
metadata,
variableMeta,
generateMeta,
makeFontFileVariablePath,
vfTag,
);
Expand All @@ -165,7 +169,7 @@ export const updateVariableCSS = async (
}

if (!css) {
throw new StatusError(404, 'Not Found. Invalid filename.');
throw new StatusError(440, 'Not Found. Unable to find filename.');
}

return css;
Expand Down
4 changes: 2 additions & 2 deletions api/cdn/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import {
import { updateCss, updateVariableCSS } from './css';
import type { CFRouterContext, TTLMetadata } from './types';
import { updateFile, updateVariableFile, updateZip } from './update';
import { splitTag } from './util';
import {
splitTag,
validateCSSFilename,
validateFontFilename,
validateVariableFontFileName,
validateVCSSFilename,
} from './util';
} from './validate';

interface CDNRequest extends IRequestStrict {
tag: string;
Expand Down
219 changes: 65 additions & 154 deletions api/cdn/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
type IDResponse,
type VariableMetadata,
type VariableMetadataWithVariants,
type VariableVariants,
} from 'common-api/types';
import { findVersion, getVersion } from 'common-api/util';
import { StatusError } from 'itty-router';

import { makeFontFileVariablePath } from './css';

const ACCEPTED_EXTENSIONS = ['woff2', 'woff', 'ttf', 'zip'] as const;
type AcceptedExtension = (typeof ACCEPTED_EXTENSIONS)[number];

const isNumeric = (num: any) =>
(typeof num === 'number' || (typeof num === 'string' && num.trim() !== '')) &&
!Number.isNaN(num as number);

interface Tag {
id: string;
version: string;
Expand Down Expand Up @@ -103,162 +103,73 @@ export const splitTag = async (
};
};

export const validateFontFilename = (file: string, metadata: IDResponse) => {
const [filename, extension] = file.split('.');
if (!extension || !isAcceptedExtension(extension)) {
throw new StatusError(400, 'Bad Request. Invalid file extension.');
}

if (file === 'download.zip') {
return;
}

const { weights, styles, subsets } = metadata;

const filenameArr = filename.split('-');
const style = filenameArr.pop();
const weight = filenameArr.pop();
const subset = filenameArr.join('-');

// Accept id-subset-weight-style
if (
style &&
// It could also be a numbered subset
(subsets.includes(subset) || isNumeric(subset)) &&
weights.includes(Number(weight)) &&
styles.includes(style)
) {
return;
}

throw new StatusError(404, 'Not Found. Invalid filename.');
};

export const validateVariableFontFileName = (
file: string,
export const generateVariableVariants = (
metadata: IDResponse,
variableMeta?: VariableMetadataWithVariants,
) => {
if (!variableMeta) {
throw new StatusError(400, 'Bad Request. Variable font not found.');
}

const [filename, extension] = file.split('.');
if (!extension || extension !== 'woff2') {
throw new StatusError(400, 'Bad Request. Invalid file extension.');
}

const { subsets, styles } = metadata;
const { axes } = variableMeta;

const filenameArr = filename.split('-');
const style = filenameArr.pop();
const axesKey = filenameArr.pop();
const subset = filenameArr.join('-');

const isValidAxesKey =
Boolean(axesKey && axes[axesKey]) ||
axesKey === 'standard' ||
axesKey === 'full';

// Accept id-subset-axes-style
if (
(subsets.includes(subset) || isNumeric(subset)) &&
isValidAxesKey &&
style &&
styles.includes(style)
) {
return;
}

throw new StatusError(404, 'Not Found. Invalid filename.');
};

export const validateCSSFilename = (file: string, metadata: IDResponse) => {
const [filename, extension] = file.split('.');
if (!extension || extension !== 'css') {
throw new StatusError(400, 'Bad Request. Invalid file extension.');
}

// Accept index.css
if (filename === 'index') {
return;
}

const { weights, styles, subsets } = metadata;

// Accept weight.css
if (weights.includes(Number(filename))) {
return;
}

// Accept weight-italic.css
let weight, style, subset;
[weight, style] = filename.split('-');
if (
weights.includes(Number(weight)) &&
style === 'italic' &&
styles.includes(style)
) {
return;
}

// Accept subset-weight.css
const subsetWeight = filename.split('-');
weight = subsetWeight.pop();
subset = subsetWeight.join('-');
if (subsets.includes(subset) && weights.includes(Number(weight))) {
return;
}

// Accept subset-weight-style.css
const subsetWeightStyle = filename.split('-');
style = subsetWeightStyle.pop();
weight = subsetWeightStyle.pop();
subset = subsetWeightStyle.join('-');
if (
style &&
subsets.includes(subset) &&
weights.includes(Number(weight)) &&
styles.includes(style)
) {
return;
}

throw new StatusError(404, 'Not Found. Invalid filename.');
};

export const validateVCSSFilename = (
file: string,
variableMeta: VariableMetadataWithVariants,
) => {
const [filename, extension] = file.split('.');
if (!extension || extension !== 'css') {
throw new StatusError(400, 'Bad Request. Invalid file extension.');
}

const { axes } = variableMeta;
// Accept index.css
if (filename === 'index') {
return;
variableMeta: VariableMetadata,
): VariableMetadataWithVariants => {
const variants: VariableVariants = {};
// Remove ital from axes keys if it exists
const keys = Object.keys(variableMeta.axes).filter((key) => key !== 'ital');

for (const axis of keys) {
variants[axis] = {};

for (const style of metadata.styles) {
variants[axis][style] = {};

for (const subset of metadata.subsets) {
const value = makeFontFileVariablePath(
metadata.family,
style,
subset,
axis,
);
variants[axis][style][subset] = value;
}
}
}

// Accept standard.css and full.css
if (filename === 'standard' || filename === 'full') {
return;
// Check if axes have any of the following standard keys
const standardKeys = new Set(['wght', 'wdth', 'slnt', 'opsz']);
const isStandard = keys.some((key) => standardKeys.has(key));
if (isStandard) {
for (const style of metadata.styles) {
variants.standard = {};
variants.standard[style] = {};

for (const subset of metadata.subsets) {
const value = makeFontFileVariablePath(
metadata.family,
style,
subset,
'standard',
);
variants.standard[style][subset] = value;
}
}
}

// Accept axes.css
const axesKeys = Object.keys(axes);
if (axesKeys.includes(filename)) {
return;
}
// Check if axes do not match the standard keys
const isFull = keys.some((key) => !standardKeys.has(key));
if (isFull) {
for (const style of metadata.styles) {
variants.full = {};
variants.full[style] = {};

// Accept axes-italic.css
const [axesKey, italic] = filename.split('-');
if (italic === 'italic' && axesKeys.includes(axesKey)) {
return;
for (const subset of metadata.subsets) {
const value = makeFontFileVariablePath(
metadata.family,
style,
subset,
'full',
);
variants.full[style][subset] = value;
}
}
}

throw new StatusError(404, 'Not Found. Invalid filename.');
return {
...variableMeta,
variants,
};
};

0 comments on commit c9796f8

Please sign in to comment.