Skip to content

Commit 4ede704

Browse files
add: variable font support
1 parent b9a20c7 commit 4ede704

12 files changed

+124
-54
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.DS_Store
12
node_modules/
23
npm-debug.log
34
yarn-error.log

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [1.1.0] - 2023-04-08
9+
10+
### Added
11+
12+
- Variable font support.
913

1014
## [1.0.3] - 2023-04-06
1115

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,26 @@
66

77
Only font files with extensions woff2, woff, ttf, and otf will be recognized and used by the plugin. Any other files with different extensions in the same directory will not be considered. Additionally, the files in the directory must be named in accordance with a specific convention.
88

9+
### Filename convetion
10+
911
The filename should have two parts. The first part should consist of the font name or font names written without any spaces and with the first letter of each word capitalized. The second part should be separated by a hyphen sign (-) and should contain the font weight and font style. The font style should only be included when the font is italic. The font weight can be one of the following: `Thin`, `ExtraLight`, `Light`, `Regular`, `Medium`, `SemiBold`, `Bold`, `ExtraBold` and `Black`. The two parts should be separated again by a capital letter. For example: `PragueSpecial-SemiBoldItalic.woff2.`
1012

13+
### Variable fonts
14+
15+
Fontify supports Variable Fonts, a new font technology that enables designers and developers to create a single font file with multiple variations of the font, including different weights, styles, and other properties. This technology allows for a more efficient and flexible way of delivering fonts to websites and other digital applications, reducing the file size and allowing for a more customized user experience.
16+
17+
The Variable Fonts page on the Adobe Type team's official website provides a more detailed explanation and examples: [https://typekit.com/variablefonts](https://typekit.com/variablefonts).
18+
19+
To correctly define the @font-face, Variable Fonts are only supported in the `woff2` format and require additional information about the `font-weight` range, which is expressed as number values. The name convention for Variable Fonts requires the keyword 'Var' followed by the font-weight start and end value, separated by an underscore.
20+
21+
If you need to determine which weights your Variable Fonts support or convert Variable font `ttf` to `woff2`, here are some helpful links:
22+
23+
- [Axis Praxis](https://www.axis-praxis.org/) A website for playing with OpenType Variable Fonts
24+
- [Wakamai Fondue](https://wakamaifondue.com/) A tool for revealing all font information
25+
- [V-Fonts](https://v-fonts.com/) A simple resource for finding and trying variable fonts
26+
- [Every Things Fonts](https://everythingfonts.com/ttf-to-woff2)A font converter and compression tool
27+
- [Woff2 Converter](https://github.com/google/woff2) A command line font compression tool from Google
28+
1129
## Input
1230

1331
From the font files in the specified folder:
@@ -21,6 +39,7 @@ From the font files in the specified folder:
2139
│ │ ├── PragueSpecial-SemiBoldItalic.ttf
2240
│ │ ├── PragueSpecial-SemiBold.woff2
2341
│ │ ├── PragueSpecial-SemiBold.woff
42+
│ │ ├── PragueVariable-Var100_900.woff2
2443
```
2544

2645
## Output
@@ -48,6 +67,15 @@ The following `@font-face` declaration is created:
4867
font-weight: 600;
4968
font-display: swap;
5069
}
70+
71+
@font-face {
72+
family-name: "Prague Variable";
73+
src: local("Prague Variable"),
74+
url("/fonts/PragueVariable-SemiBold.woff2") format("woff2-variations");
75+
font-style: normal;
76+
font-weight: 100 900;
77+
font-display: swap;
78+
}
5179
```
5280

5381
The font folder can contain multiple font files with the same name and different formats. They will be all used in the `src` property.
File renamed without changes.
File renamed without changes.

fonts/Prague/Prague-Var0_1000Italic.woff2

Whitespace-only changes.

fonts/Prague/Prague-Var300.woff2

Whitespace-only changes.

index.js

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = (opts = {}) => {
2727
};
2828

2929
return {
30-
postcssPlugin: 'postcss-font-face-generator',
30+
postcssPlugin: 'postcss-fontify',
3131

3232
Root (root, postcss) {
3333
const fontsDir = options.fontsDir;
@@ -39,51 +39,60 @@ module.exports = (opts = {}) => {
3939

4040
const fontFamilies = new Map();
4141

42-
fontFiles.forEach((file) => {
43-
const ext = path.extname(file);
44-
const fontName = path.basename(file, ext);
42+
fontFiles.forEach(file => {
43+
const extension = path.extname(file);
44+
const fileName = path.basename(file, extension);
4545
const relativePath = path.relative(fontsDir, file);
46-
const fontFilePath = path.join(options.fontPath, relativePath);
46+
const srcPath = path.join(options.fontPath, relativePath);
4747

48+
let [font, style] = fileName.split('-');
49+
const fontFamily = font.replace(/([a-z])([A-Z])/g, '$1 $2');
4850
let fontWeight = '400';
4951
let fontStyle = 'normal';
50-
let fontFamily = fontName;
52+
let srcFormat = extension;
5153

52-
if (fontName.includes('-')) {
53-
const parts = fontName.split('-');
54-
fontFamily = parts[0];
54+
if (style && style.includes('Italic')) {
55+
fontStyle = 'italic';
56+
style = style.replace('Italic', '');
57+
}
58+
59+
if (style && style.includes('Var')) {
60+
const [start = Number.NaN, end = Number.NaN] = style.split('Var')[1].split('_').map(x => x === '' ? Number.NaN : Number(x));
5561

56-
if (parts[1].includes('Italic')) {
57-
fontStyle = 'italic';
58-
fontWeight = parts[1].replace('Italic', '');
59-
} else {
60-
fontWeight = parts[1];
62+
if (!Object.is(start, Number.NaN) && !Object.is(end, Number.NaN)) {
63+
fontWeight = `${start} ${end}`;
6164
}
6265

63-
fontWeightMap[fontWeight] ? fontWeight = fontWeightMap[fontWeight] : fontWeight;
66+
if (!Object.is(start, Number.NaN) && Object.is(end, Number.NaN)) {
67+
fontWeight = `${start}`;
68+
}
69+
70+
srcFormat = 'woff2-variations';
6471
}
6572

66-
fontFamily = fontFamily.replace(/([a-z])([A-Z])/g, '$1 $2');
73+
if (style && fontWeightMap[style]) {
74+
fontWeight = fontWeightMap[style];
75+
}
6776

68-
if (!fontFamilies.has(fontName)) {
69-
fontFamilies.set(fontName, {
77+
if (!fontFamilies.has(fileName)) {
78+
fontFamilies.set(fileName, {
7079
fontFamily,
71-
fontFile: [{ ext, fontFilePath }],
80+
fontSrc: [{ srcFormat, srcPath }],
7281
fontWeight,
7382
fontStyle
7483
});
7584
} else {
76-
const font = fontFamilies.get(fontName);
85+
const font = fontFamilies.get(fileName);
7786

78-
if (!font.fontFile.some(obj => obj.ext === ext)) {
79-
font.fontFile.push({ ext, fontFilePath });
87+
if (!font.fontSrc.some(obj => obj.srcFormat === srcFormat)) {
88+
font.fontSrc.push({ srcFormat, srcPath });
8089
}
81-
fontFamilies.set(fontName, font);
90+
fontFamilies.set(fileName, font);
8291
}
8392
});
8493

8594
fontFamilies.forEach((fontFace) => {
86-
let fontSrc = fontFace.fontFile.sort(sortFiles).map((fontFile) => `url("${fontFile.fontFilePath}") format("${fontFile.ext.slice(1)}")`).join(', ').toString();
95+
let fontSrc = fontFace.fontSrc.sort(sortFiles).map((src) => `url("${src.srcPath}") format("${src.srcFormat.slice(1)}")`).join(', ').toString();
8796

8897
if (options.local) {
8998
fontSrc = `local("${fontFace.fontFamily}"), ${fontSrc}`;

index.test.js

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,45 @@ async function run (input, output, opts = {}) {
1212
describe('sortFiles', () => {
1313
it('should sort files based on weights', () => {
1414
const files = [
15-
{ name: 'file1', ext: 'ttf' },
16-
{ name: 'file2', ext: 'woff2' },
17-
{ name: 'file3', ext: 'woff' },
18-
{ name: 'file4', ext: 'otf' },
15+
{ name: 'file1', srcFormat: 'ttf' },
16+
{ name: 'file2', srcFormat: 'woff2' },
17+
{ name: 'file3', srcFormat: 'woff' },
18+
{ name: 'file4', srcFormat: 'otf' },
1919
];
2020

2121
const sortedFiles = files.sort(sortFiles);
2222

2323
expect(sortedFiles).toEqual([
24-
{ name: 'file2', ext: 'woff2' },
25-
{ name: 'file3', ext: 'woff' },
26-
{ name: 'file1', ext: 'ttf' },
27-
{ name: 'file4', ext: 'otf' },
24+
{ name: 'file2', srcFormat: 'woff2' },
25+
{ name: 'file3', srcFormat: 'woff' },
26+
{ name: 'file1', srcFormat: 'ttf' },
27+
{ name: 'file4', srcFormat: 'otf' },
2828
]);
2929
});
3030

3131
it('should sort files with the same weight based on extension', () => {
3232
const files = [
33-
{ name: 'file1', ext: 'woff' },
34-
{ name: 'file2', ext: 'woff2' },
35-
{ name: 'file3', ext: 'otf' },
36-
{ name: 'file4', ext: 'ttf' },
37-
{ name: 'file5', ext: 'woff2' },
38-
{ name: 'file6', ext: 'ttf' },
39-
{ name: 'file7', ext: 'woff' },
40-
{ name: 'file8', ext: 'otf' },
33+
{ name: 'file1', srcFormat: 'woff' },
34+
{ name: 'file2', srcFormat: 'woff2' },
35+
{ name: 'file3', srcFormat: 'otf' },
36+
{ name: 'file4', srcFormat: 'ttf' },
37+
{ name: 'file5', srcFormat: 'woff2' },
38+
{ name: 'file6', srcFormat: 'ttf' },
39+
{ name: 'file7', srcFormat: 'woff' },
40+
{ name: 'file8', srcFormat: 'otf' },
4141
];
4242

4343
const sortedFiles = files.sort(sortFiles);
4444

4545
expect(sortedFiles).toEqual([
46-
{ name: 'file2', ext: 'woff2' },
47-
{ name: 'file5', ext: 'woff2' },
48-
{ name: 'file1', ext: 'woff' },
49-
{ name: 'file7', ext: 'woff' },
50-
{ name: 'file4', ext: 'ttf' },
51-
{ name: 'file6', ext: 'ttf' },
52-
{ name: 'file3', ext: 'otf' },
53-
{ name: 'file8', ext: 'otf' },
46+
{ name: 'file2', srcFormat: 'woff2' },
47+
{ name: 'file5', srcFormat: 'woff2' },
48+
{ name: 'file1', srcFormat: 'woff' },
49+
{ name: 'file7', srcFormat: 'woff' },
50+
{ name: 'file4', srcFormat: 'ttf' },
51+
{ name: 'file6', srcFormat: 'ttf' },
52+
{ name: 'file3', srcFormat: 'otf' },
53+
{ name: 'file8', srcFormat: 'otf' },
5454
]);
5555
});
5656
});
@@ -68,6 +68,34 @@ it('Create default output from files in font folder', async () => {
6868
font-weight: 900;
6969
font-style: normal;
7070
font-display: swap
71+
}
72+
@font-face {
73+
font-family: "Prague";
74+
src: local("Prague"), url("/fonts/Prague-Var.woff2") format("off2-variations");
75+
font-weight: 400;
76+
font-style: normal;
77+
font-display: swap
78+
}
79+
@font-face {
80+
font-family: "Prague";
81+
src: local("Prague"), url("/fonts/Prague-Var0_1000.woff2") format("off2-variations");
82+
font-weight: 0 1000;
83+
font-style: normal;
84+
font-display: swap
85+
}
86+
@font-face {
87+
font-family: "Prague";
88+
src: local("Prague"), url("/fonts/Prague-Var0_1000Italic.woff2") format("off2-variations");
89+
font-weight: 0 1000;
90+
font-style: italic;
91+
font-display: swap
92+
}
93+
@font-face {
94+
font-family: "Prague";
95+
src: local("Prague"), url("/fonts/Prague-Var300.woff2") format("off2-variations");
96+
font-weight: 300;
97+
font-style: normal;
98+
font-display: swap
7199
}`;
72100
await run('', output, { fontsDir: './fonts/Prague/' });
73101
});

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)