added fonts
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
import type { Font } from 'fontkit';
|
||||
import type { AdjustFontFallback } from 'next/font';
|
||||
/**
|
||||
* Given a font file and category, calculate the fallback font override values.
|
||||
* The returned values can be used to generate a CSS @font-face declaration.
|
||||
*
|
||||
* For example:
|
||||
* @font-face {
|
||||
* font-family: local-font;
|
||||
* src: local(Arial);
|
||||
* size-adjust: 90%;
|
||||
* }
|
||||
*
|
||||
* Read more about this technique in these texts by the Google Aurora team:
|
||||
* https://developer.chrome.com/blog/font-fallbacks/
|
||||
* https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub
|
||||
*/
|
||||
export declare function getFallbackMetricsFromFontFile(font: Font, category?: string): AdjustFontFallback;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getFallbackMetricsFromFontFile = void 0;
|
||||
// The font metadata of the fallback fonts, retrieved with fontkit on system font files
|
||||
// The average width is calculated with the calcAverageWidth function below
|
||||
const DEFAULT_SANS_SERIF_FONT = {
|
||||
name: 'Arial',
|
||||
azAvgWidth: 934.5116279069767,
|
||||
unitsPerEm: 2048,
|
||||
};
|
||||
const DEFAULT_SERIF_FONT = {
|
||||
name: 'Times New Roman',
|
||||
azAvgWidth: 854.3953488372093,
|
||||
unitsPerEm: 2048,
|
||||
};
|
||||
/**
|
||||
* Calculate the average character width of a font file.
|
||||
* Used to calculate the size-adjust property by comparing the fallback average with the loaded font average.
|
||||
*/
|
||||
function calcAverageWidth(font) {
|
||||
try {
|
||||
/**
|
||||
* Finding the right characters to use when calculating the average width is tricky.
|
||||
* We can't just use the average width of all characters, because we have to take letter frequency into account.
|
||||
* We also have to take word length into account, because the font's space width usually differ a lot from other characters.
|
||||
* The goal is to find a string that'll give you a good average width, given most texts in most languages.
|
||||
*
|
||||
* TODO: Currently only works for the latin alphabet. Support more languages by finding the right characters for additional languages.
|
||||
*
|
||||
* The used characters were decided through trial and error with letter frequency and word length tables as a guideline.
|
||||
* E.g. https://en.wikipedia.org/wiki/Letter_frequency
|
||||
*/
|
||||
const avgCharacters = 'aaabcdeeeefghiijklmnnoopqrrssttuvwxyz ';
|
||||
// Check if the font file has all the characters we need to calculate the average width
|
||||
const hasAllChars = font
|
||||
.glyphsForString(avgCharacters)
|
||||
.flatMap((glyph) => glyph.codePoints)
|
||||
.every((codePoint) => font.hasGlyphForCodePoint(codePoint));
|
||||
if (!hasAllChars)
|
||||
return undefined;
|
||||
const widths = font
|
||||
.glyphsForString(avgCharacters)
|
||||
.map((glyph) => glyph.advanceWidth);
|
||||
const totalWidth = widths.reduce((sum, width) => sum + width, 0);
|
||||
return totalWidth / widths.length;
|
||||
}
|
||||
catch {
|
||||
// Could not calculate average width from the font file, skip size-adjust
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
function formatOverrideValue(val) {
|
||||
return Math.abs(val * 100).toFixed(2) + '%';
|
||||
}
|
||||
/**
|
||||
* Given a font file and category, calculate the fallback font override values.
|
||||
* The returned values can be used to generate a CSS @font-face declaration.
|
||||
*
|
||||
* For example:
|
||||
* @font-face {
|
||||
* font-family: local-font;
|
||||
* src: local(Arial);
|
||||
* size-adjust: 90%;
|
||||
* }
|
||||
*
|
||||
* Read more about this technique in these texts by the Google Aurora team:
|
||||
* https://developer.chrome.com/blog/font-fallbacks/
|
||||
* https://docs.google.com/document/d/e/2PACX-1vRsazeNirATC7lIj2aErSHpK26hZ6dA9GsQ069GEbq5fyzXEhXbvByoftSfhG82aJXmrQ_sJCPBqcx_/pub
|
||||
*/
|
||||
function getFallbackMetricsFromFontFile(font, category = 'serif') {
|
||||
const fallbackFont = category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT;
|
||||
const azAvgWidth = calcAverageWidth(font);
|
||||
const { ascent, descent, lineGap, unitsPerEm } = font;
|
||||
const fallbackFontAvgWidth = fallbackFont.azAvgWidth / fallbackFont.unitsPerEm;
|
||||
let sizeAdjust = azAvgWidth
|
||||
? azAvgWidth / unitsPerEm / fallbackFontAvgWidth
|
||||
: 1;
|
||||
return {
|
||||
ascentOverride: formatOverrideValue(ascent / (unitsPerEm * sizeAdjust)),
|
||||
descentOverride: formatOverrideValue(descent / (unitsPerEm * sizeAdjust)),
|
||||
lineGapOverride: formatOverrideValue(lineGap / (unitsPerEm * sizeAdjust)),
|
||||
fallbackFont: fallbackFont.name,
|
||||
sizeAdjust: formatOverrideValue(sizeAdjust),
|
||||
};
|
||||
}
|
||||
exports.getFallbackMetricsFromFontFile = getFallbackMetricsFromFontFile;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import type { CssVariable, Display, NextFont, NextFontWithVariable } from '../types';
|
||||
type LocalFont<T extends CssVariable | undefined = undefined> = {
|
||||
src: string | Array<{
|
||||
path: string;
|
||||
weight?: string;
|
||||
style?: string;
|
||||
}>;
|
||||
display?: Display;
|
||||
weight?: string;
|
||||
style?: string;
|
||||
adjustFontFallback?: 'Arial' | 'Times New Roman' | false;
|
||||
fallback?: string[];
|
||||
preload?: boolean;
|
||||
variable?: T;
|
||||
declarations?: Array<{
|
||||
prop: string;
|
||||
value: string;
|
||||
}>;
|
||||
};
|
||||
export default function localFont<T extends CssVariable | undefined = undefined>(options: LocalFont<T>): T extends undefined ? NextFont : NextFontWithVariable;
|
||||
export {};
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function localFont(options) {
|
||||
throw new Error();
|
||||
}
|
||||
exports.default = localFont;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { FontLoader } from 'next/font';
|
||||
declare const nextFontLocalFontLoader: FontLoader;
|
||||
export default nextFontLocalFontLoader;
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
let fontFromBuffer;
|
||||
try {
|
||||
const mod = require('../fontkit').default;
|
||||
fontFromBuffer = mod.default || mod;
|
||||
}
|
||||
catch { }
|
||||
const util_1 = require("util");
|
||||
const pick_font_file_for_fallback_generation_1 = require("./pick-font-file-for-fallback-generation");
|
||||
const get_fallback_metrics_from_font_file_1 = require("./get-fallback-metrics-from-font-file");
|
||||
const validate_local_font_function_call_1 = require("./validate-local-font-function-call");
|
||||
const nextFontLocalFontLoader = async ({ functionName, variableName, data, emitFontFile, resolve, loaderContext, }) => {
|
||||
const { src, display, fallback, preload, variable, adjustFontFallback, declarations, weight: defaultWeight, style: defaultStyle, } = (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)(functionName, data[0]);
|
||||
// Load all font files and emit them to the .next output directory
|
||||
// Also generate a @font-face CSS for each font file
|
||||
const fontFiles = await Promise.all(src.map(async ({ path, style, weight, ext, format }) => {
|
||||
const resolved = await resolve(path);
|
||||
const fileBuffer = await (0, util_1.promisify)(loaderContext.fs.readFile)(resolved);
|
||||
const fontUrl = emitFontFile(fileBuffer, ext, preload, typeof adjustFontFallback === 'undefined' || !!adjustFontFallback);
|
||||
// Try to load font metadata from the font file using fontkit.
|
||||
// The data is used to calculate the fallback font override values.
|
||||
let fontMetadata;
|
||||
try {
|
||||
fontMetadata = fontFromBuffer === null || fontFromBuffer === void 0 ? void 0 : fontFromBuffer(fileBuffer);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Failed to load font file: ${resolved}\n${e}`);
|
||||
}
|
||||
// Get all values that should be added to the @font-face declaration
|
||||
const fontFaceProperties = [
|
||||
...(declarations
|
||||
? declarations.map(({ prop, value }) => [prop, value])
|
||||
: []),
|
||||
['font-family', variableName],
|
||||
['src', `url(${fontUrl}) format('${format}')`],
|
||||
['font-display', display],
|
||||
...((weight !== null && weight !== void 0 ? weight : defaultWeight)
|
||||
? [['font-weight', weight !== null && weight !== void 0 ? weight : defaultWeight]]
|
||||
: []),
|
||||
...((style !== null && style !== void 0 ? style : defaultStyle)
|
||||
? [['font-style', style !== null && style !== void 0 ? style : defaultStyle]]
|
||||
: []),
|
||||
];
|
||||
// Generate the @font-face CSS from the font-face properties
|
||||
const css = `@font-face {\n${fontFaceProperties
|
||||
.map(([property, value]) => `${property}: ${value};`)
|
||||
.join('\n')}\n}\n`;
|
||||
return {
|
||||
css,
|
||||
fontMetadata,
|
||||
weight,
|
||||
style,
|
||||
};
|
||||
}));
|
||||
// Calculate the fallback font override values using the font file metadata
|
||||
let adjustFontFallbackMetrics;
|
||||
if (adjustFontFallback !== false) {
|
||||
const fallbackFontFile = (0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)(fontFiles);
|
||||
if (fallbackFontFile.fontMetadata) {
|
||||
adjustFontFallbackMetrics = (0, get_fallback_metrics_from_font_file_1.getFallbackMetricsFromFontFile)(fallbackFontFile.fontMetadata, adjustFontFallback === 'Times New Roman' ? 'serif' : 'sans-serif');
|
||||
}
|
||||
}
|
||||
return {
|
||||
css: fontFiles.map(({ css }) => css).join('\n'),
|
||||
fallbackFonts: fallback,
|
||||
weight: src.length === 1 ? src[0].weight : undefined,
|
||||
style: src.length === 1 ? src[0].style : undefined,
|
||||
variable,
|
||||
adjustFontFallback: adjustFontFallbackMetrics,
|
||||
};
|
||||
};
|
||||
exports.default = nextFontLocalFontLoader;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const loader_1 = __importDefault(require("./loader"));
|
||||
describe('next/font/local loader', () => {
|
||||
describe('generated CSS', () => {
|
||||
test('Default CSS', async () => {
|
||||
const { css } = await (0, loader_1.default)({
|
||||
functionName: '',
|
||||
data: [{ src: './my-font.woff2' }],
|
||||
emitFontFile: () => '/_next/static/media/my-font.woff2',
|
||||
resolve: jest.fn(),
|
||||
isDev: false,
|
||||
isServer: true,
|
||||
variableName: 'myFont',
|
||||
loaderContext: {
|
||||
fs: {
|
||||
readFile: (_, cb) => cb(null, 'fontdata'),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
test('Weight and style', async () => {
|
||||
const { css } = await (0, loader_1.default)({
|
||||
functionName: '',
|
||||
data: [{ src: './my-font.woff2', weight: '100 900', style: 'italic' }],
|
||||
emitFontFile: () => '/_next/static/media/my-font.woff2',
|
||||
resolve: jest.fn(),
|
||||
isDev: false,
|
||||
isServer: true,
|
||||
variableName: 'myFont',
|
||||
loaderContext: {
|
||||
fs: {
|
||||
readFile: (_, cb) => cb(null, 'fontdata'),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 100 900;
|
||||
font-style: italic;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
test('Other properties', async () => {
|
||||
const { css } = await (0, loader_1.default)({
|
||||
functionName: '',
|
||||
data: [
|
||||
{
|
||||
src: './my-font.woff2',
|
||||
declarations: [
|
||||
{ prop: 'font-feature-settings', value: '"smcp" on' },
|
||||
{ prop: 'ascent-override', value: '90%' },
|
||||
],
|
||||
},
|
||||
],
|
||||
emitFontFile: () => '/_next/static/media/my-font.woff2',
|
||||
resolve: jest.fn(),
|
||||
isDev: false,
|
||||
isServer: true,
|
||||
variableName: 'myFont',
|
||||
loaderContext: {
|
||||
fs: {
|
||||
readFile: (_, cb) => cb(null, 'fontdata'),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"@font-face {
|
||||
font-feature-settings: "smcp" on;
|
||||
ascent-override: 90%;
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
test('Multiple weights default style', async () => {
|
||||
const { css } = await (0, loader_1.default)({
|
||||
functionName: '',
|
||||
data: [
|
||||
{
|
||||
style: 'italic',
|
||||
src: [
|
||||
{
|
||||
path: './fonts/font1.woff2',
|
||||
weight: '100',
|
||||
},
|
||||
{
|
||||
path: './fonts/font2.woff2',
|
||||
weight: '400',
|
||||
},
|
||||
{
|
||||
path: './fonts/font3.woff2',
|
||||
weight: '700',
|
||||
},
|
||||
{
|
||||
path: './fonts/font2.woff2',
|
||||
weight: '400',
|
||||
style: 'normal',
|
||||
},
|
||||
],
|
||||
adjustFontFallback: false,
|
||||
},
|
||||
],
|
||||
emitFontFile: () => `/_next/static/media/my-font.woff2`,
|
||||
resolve: jest.fn(),
|
||||
isDev: false,
|
||||
isServer: true,
|
||||
variableName: 'myFont',
|
||||
loaderContext: {
|
||||
fs: {
|
||||
readFile: (path, cb) => cb(null, path),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 100;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
test('Multiple styles default weight', async () => {
|
||||
const { css } = await (0, loader_1.default)({
|
||||
functionName: '',
|
||||
data: [
|
||||
{
|
||||
weight: '400',
|
||||
src: [
|
||||
{
|
||||
path: './fonts/font1.woff2',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: './fonts/font3.woff2',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: './fonts/font2.woff2',
|
||||
weight: '700',
|
||||
},
|
||||
],
|
||||
adjustFontFallback: false,
|
||||
},
|
||||
],
|
||||
emitFontFile: () => `/_next/static/media/my-font.woff2`,
|
||||
resolve: jest.fn(),
|
||||
isDev: false,
|
||||
isServer: true,
|
||||
variableName: 'myFont',
|
||||
loaderContext: {
|
||||
fs: {
|
||||
readFile: (path, cb) => cb(null, path),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: myFont;
|
||||
src: url(/_next/static/media/my-font.woff2) format('woff2');
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* If multiple font files are provided for a font family, we need to pick one to use for the automatic fallback generation.
|
||||
* This function returns the font file that is most likely to be used for the bulk of the text on a page.
|
||||
*
|
||||
* There are some assumptions here about the text on a page when picking the font file:
|
||||
* - Most of the text will have normal weight, use the one closest to 400
|
||||
* - Most of the text will have normal style, prefer normal over italic
|
||||
* - If two font files have the same distance from normal weight, the thinner one will most likely be the bulk of the text
|
||||
*/
|
||||
export declare function pickFontFileForFallbackGeneration<T extends {
|
||||
style?: string;
|
||||
weight?: string;
|
||||
}>(fontFiles: T[]): T;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.pickFontFileForFallbackGeneration = void 0;
|
||||
const next_font_error_1 = require("../next-font-error");
|
||||
const NORMAL_WEIGHT = 400;
|
||||
const BOLD_WEIGHT = 700;
|
||||
/**
|
||||
* Convert the weight string to a number so it can be used for comparison.
|
||||
* Weights can be defined as a number, 'normal' or 'bold'. https://developer.mozilla.org/docs/Web/CSS/@font-face/font-weight
|
||||
*/
|
||||
function getWeightNumber(weight) {
|
||||
return weight === 'normal'
|
||||
? NORMAL_WEIGHT
|
||||
: weight === 'bold'
|
||||
? BOLD_WEIGHT
|
||||
: Number(weight);
|
||||
}
|
||||
/**
|
||||
* Get the distance from normal (400) weight for the provided weight.
|
||||
* If it's not a variable font we can just return the distance.
|
||||
* If it's a variable font we need to compare its weight range to 400.
|
||||
*/
|
||||
function getDistanceFromNormalWeight(weight) {
|
||||
if (!weight)
|
||||
return 0;
|
||||
// If it's a variable font the weight is defined with two numbers "100 900", rather than just one "400"
|
||||
const [firstWeight, secondWeight] = weight
|
||||
.trim()
|
||||
.split(/ +/)
|
||||
.map(getWeightNumber);
|
||||
if (Number.isNaN(firstWeight) || Number.isNaN(secondWeight)) {
|
||||
(0, next_font_error_1.nextFontError)(`Invalid weight value in src array: \`${weight}\`.\nExpected \`normal\`, \`bold\` or a number.`);
|
||||
}
|
||||
// If the weight doesn't have have a second value, it's not a variable font
|
||||
// If that's the case, just return the distance from normal weight
|
||||
if (!secondWeight) {
|
||||
return firstWeight - NORMAL_WEIGHT;
|
||||
}
|
||||
// Normal weight is within variable font range
|
||||
if (firstWeight <= NORMAL_WEIGHT && secondWeight >= NORMAL_WEIGHT) {
|
||||
return 0;
|
||||
}
|
||||
// Normal weight is outside variable font range
|
||||
// Return the distance of normal weight to the variable font range
|
||||
const firstWeightDistance = firstWeight - NORMAL_WEIGHT;
|
||||
const secondWeightDistance = secondWeight - NORMAL_WEIGHT;
|
||||
if (Math.abs(firstWeightDistance) < Math.abs(secondWeightDistance)) {
|
||||
return firstWeightDistance;
|
||||
}
|
||||
return secondWeightDistance;
|
||||
}
|
||||
/**
|
||||
* If multiple font files are provided for a font family, we need to pick one to use for the automatic fallback generation.
|
||||
* This function returns the font file that is most likely to be used for the bulk of the text on a page.
|
||||
*
|
||||
* There are some assumptions here about the text on a page when picking the font file:
|
||||
* - Most of the text will have normal weight, use the one closest to 400
|
||||
* - Most of the text will have normal style, prefer normal over italic
|
||||
* - If two font files have the same distance from normal weight, the thinner one will most likely be the bulk of the text
|
||||
*/
|
||||
function pickFontFileForFallbackGeneration(fontFiles) {
|
||||
return fontFiles.reduce((usedFontFile, currentFontFile) => {
|
||||
if (!usedFontFile)
|
||||
return currentFontFile;
|
||||
const usedFontDistance = getDistanceFromNormalWeight(usedFontFile.weight);
|
||||
const currentFontDistance = getDistanceFromNormalWeight(currentFontFile.weight);
|
||||
// Prefer normal style if they have the same weight
|
||||
if (usedFontDistance === currentFontDistance &&
|
||||
(typeof currentFontFile.style === 'undefined' ||
|
||||
currentFontFile.style === 'normal')) {
|
||||
return currentFontFile;
|
||||
}
|
||||
const absUsedDistance = Math.abs(usedFontDistance);
|
||||
const absCurrentDistance = Math.abs(currentFontDistance);
|
||||
// Use closest absolute distance to normal weight
|
||||
if (absCurrentDistance < absUsedDistance)
|
||||
return currentFontFile;
|
||||
// Prefer the thinner font if both have the same absolute distance from normal weight
|
||||
if (absUsedDistance === absCurrentDistance &&
|
||||
currentFontDistance < usedFontDistance) {
|
||||
return currentFontFile;
|
||||
}
|
||||
return usedFontFile;
|
||||
});
|
||||
}
|
||||
exports.pickFontFileForFallbackGeneration = pickFontFileForFallbackGeneration;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Generated
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const pick_font_file_for_fallback_generation_1 = require("./pick-font-file-for-fallback-generation");
|
||||
describe('pickFontFileForFallbackGeneration', () => {
|
||||
it('should pick the weight closest to 400', () => {
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{
|
||||
weight: '300',
|
||||
},
|
||||
{
|
||||
weight: '600',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: '300',
|
||||
});
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{ weight: '200' },
|
||||
{
|
||||
weight: '500',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: '500',
|
||||
});
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{
|
||||
weight: 'normal',
|
||||
},
|
||||
{
|
||||
weight: '700',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: 'normal',
|
||||
});
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{
|
||||
weight: 'bold',
|
||||
},
|
||||
{
|
||||
weight: '900',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: 'bold',
|
||||
});
|
||||
});
|
||||
it('should pick the thinner weight if both have the same distance to 400', () => {
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{
|
||||
weight: '300',
|
||||
},
|
||||
{
|
||||
weight: '500',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: '300',
|
||||
});
|
||||
});
|
||||
it('should pick variable range closest to 400', () => {
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{
|
||||
weight: '100 300',
|
||||
},
|
||||
{
|
||||
weight: '600 900',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: '100 300',
|
||||
});
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{ weight: '100 200' },
|
||||
{
|
||||
weight: '500 800',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: '500 800',
|
||||
});
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{ weight: '100 900' },
|
||||
{
|
||||
weight: '300 399',
|
||||
},
|
||||
])).toEqual({
|
||||
weight: '100 900',
|
||||
});
|
||||
});
|
||||
it('should prefer normal style over italic', () => {
|
||||
expect((0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{ weight: '400', style: 'normal' },
|
||||
{ weight: '400', style: 'italic' },
|
||||
])).toEqual({ weight: '400', style: 'normal' });
|
||||
});
|
||||
it('should error on invalid weight in array', async () => {
|
||||
expect(() => (0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{ path: './font1.woff2', weight: 'normal bold' },
|
||||
{ path: './font2.woff2', weight: '400 bold' },
|
||||
{ path: './font3.woff2', weight: 'normal 700' },
|
||||
{ path: './font4.woff2', weight: '100 abc' },
|
||||
])).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Invalid weight value in src array: \`100 abc\`.
|
||||
Expected \`normal\`, \`bold\` or a number."
|
||||
`);
|
||||
});
|
||||
test('Invalid variable weight in array', async () => {
|
||||
expect(() => (0, pick_font_file_for_fallback_generation_1.pickFontFileForFallbackGeneration)([
|
||||
{ path: './font1.woff2', weight: 'normal bold' },
|
||||
{ path: './font2.woff2', weight: '400 bold' },
|
||||
{ path: './font3.woff2', weight: 'normal 700' },
|
||||
{ path: './font4.woff2', weight: '100 abc' },
|
||||
])).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Invalid weight value in src array: \`100 abc\`.
|
||||
Expected \`normal\`, \`bold\` or a number."
|
||||
`);
|
||||
});
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
type FontOptions = {
|
||||
src: Array<{
|
||||
path: string;
|
||||
weight?: string;
|
||||
style?: string;
|
||||
ext: string;
|
||||
format: string;
|
||||
}>;
|
||||
display: string;
|
||||
weight?: string;
|
||||
style?: string;
|
||||
fallback?: string[];
|
||||
preload: boolean;
|
||||
variable?: string;
|
||||
adjustFontFallback?: string | false;
|
||||
declarations?: Array<{
|
||||
prop: string;
|
||||
value: string;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* Validate the data recieved from next-swc next-transform-font on next/font/local calls
|
||||
*/
|
||||
export declare function validateLocalFontFunctionCall(functionName: string, fontData: any): FontOptions;
|
||||
export {};
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.validateLocalFontFunctionCall = void 0;
|
||||
const constants_1 = require("../constants");
|
||||
const format_available_values_1 = require("../format-available-values");
|
||||
const next_font_error_1 = require("../next-font-error");
|
||||
const extToFormat = {
|
||||
woff: 'woff',
|
||||
woff2: 'woff2',
|
||||
ttf: 'truetype',
|
||||
otf: 'opentype',
|
||||
eot: 'embedded-opentype',
|
||||
};
|
||||
/**
|
||||
* Validate the data recieved from next-swc next-transform-font on next/font/local calls
|
||||
*/
|
||||
function validateLocalFontFunctionCall(functionName, fontData) {
|
||||
if (functionName) {
|
||||
(0, next_font_error_1.nextFontError)(`next/font/local has no named exports`);
|
||||
}
|
||||
let { src, display = 'swap', weight, style, fallback, preload = true, variable, adjustFontFallback, declarations, } = fontData || {};
|
||||
if (!constants_1.allowedDisplayValues.includes(display)) {
|
||||
(0, next_font_error_1.nextFontError)(`Invalid display value \`${display}\`.\nAvailable display values: ${(0, format_available_values_1.formatAvailableValues)(constants_1.allowedDisplayValues)}`);
|
||||
}
|
||||
if (!src) {
|
||||
(0, next_font_error_1.nextFontError)('Missing required `src` property');
|
||||
}
|
||||
if (!Array.isArray(src)) {
|
||||
src = [{ path: src, weight, style }];
|
||||
}
|
||||
else {
|
||||
if (src.length === 0) {
|
||||
(0, next_font_error_1.nextFontError)('Unexpected empty `src` array.');
|
||||
}
|
||||
}
|
||||
src = src.map((fontFile) => {
|
||||
var _a;
|
||||
const ext = (_a = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile.path)) === null || _a === void 0 ? void 0 : _a[1];
|
||||
if (!ext) {
|
||||
(0, next_font_error_1.nextFontError)(`Unexpected file \`${fontFile.path}\``);
|
||||
}
|
||||
return {
|
||||
...fontFile,
|
||||
ext,
|
||||
format: extToFormat[ext],
|
||||
};
|
||||
});
|
||||
if (Array.isArray(declarations)) {
|
||||
declarations.forEach((declaration) => {
|
||||
if ([
|
||||
'font-family',
|
||||
'src',
|
||||
'font-display',
|
||||
'font-weight',
|
||||
'font-style',
|
||||
].includes(declaration === null || declaration === void 0 ? void 0 : declaration.prop)) {
|
||||
(0, next_font_error_1.nextFontError)(`Invalid declaration prop: \`${declaration.prop}\``);
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
src,
|
||||
display,
|
||||
weight,
|
||||
style,
|
||||
fallback,
|
||||
preload,
|
||||
variable,
|
||||
adjustFontFallback,
|
||||
declarations,
|
||||
};
|
||||
}
|
||||
exports.validateLocalFontFunctionCall = validateLocalFontFunctionCall;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const validate_local_font_function_call_1 = require("./validate-local-font-function-call");
|
||||
describe('validateLocalFontFunctionCall', () => {
|
||||
test('Not using default export', async () => {
|
||||
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('Named', {})).toThrowErrorMatchingInlineSnapshot(`"next/font/local has no named exports"`);
|
||||
});
|
||||
test('Missing src', async () => {
|
||||
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {})).toThrowErrorMatchingInlineSnapshot(`"Missing required \`src\` property"`);
|
||||
});
|
||||
test('Invalid file extension', async () => {
|
||||
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', { src: './font/font-file.abc' })).toThrowErrorMatchingInlineSnapshot(`"Unexpected file \`./font/font-file.abc\`"`);
|
||||
});
|
||||
test('Invalid display value', async () => {
|
||||
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {
|
||||
src: './font-file.woff2',
|
||||
display: 'invalid',
|
||||
})).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Invalid display value \`invalid\`.
|
||||
Available display values: \`auto\`, \`block\`, \`swap\`, \`fallback\`, \`optional\`"
|
||||
`);
|
||||
});
|
||||
test('Invalid declaration', async () => {
|
||||
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {
|
||||
src: './font-file.woff2',
|
||||
declarations: [{ prop: 'src', value: '/hello.woff2' }],
|
||||
})).toThrowErrorMatchingInlineSnapshot(`"Invalid declaration prop: \`src\`"`);
|
||||
});
|
||||
test('Empty src array', async () => {
|
||||
expect(() => (0, validate_local_font_function_call_1.validateLocalFontFunctionCall)('', {
|
||||
src: [],
|
||||
})).toThrowErrorMatchingInlineSnapshot(`"Unexpected empty \`src\` array."`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user