subfont 6.9.0 → 6.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/lib/getCssRulesByProperty.js +50 -24
- package/lib/normalizeFontPropertyValue.js +3 -0
- package/lib/parseFontVariationSettings.js +39 -0
- package/lib/subsetFonts.js +310 -19
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
### v6.12.0 (2022-09-07)
|
|
2
|
+
|
|
3
|
+
- [Refactor repeated code into helper function](https://github.com/Munter/subfont/commit/5335d931957cadc150fed1c5d548ee6b6e245636) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
4
|
+
- [Clamp the used variation axis values to the available interval](https://github.com/Munter/subfont/commit/1429c0f35720ee8dadb691138eb4cfa3c1c86150) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
5
|
+
- [Detech unused slnt variation axis ranges](https://github.com/Munter/subfont/commit/dc4be1598ba1ba0e65d8269d0fa2d25aa528357d) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
6
|
+
- [Report variation axes that only use the default value as unused rather than underutilized](https://github.com/Munter/subfont/commit/12082590898407f558d9540347778272ddb9d197) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
7
|
+
- [Detect unused wdth variation axis ranges](https://github.com/Munter/subfont/commit/d7758eb3c9a45a49768aac12c36ed897a0c4b467) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
8
|
+
|
|
9
|
+
### v6.11.0 (2022-09-04)
|
|
10
|
+
|
|
11
|
+
- [Treat wdth as a standard axis \(but unsupported for now\)](https://github.com/Munter/subfont/commit/20d539750448134cca0a82f2cc98f85d9a6be068) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
12
|
+
- [Detect unused ital variation axis ranges](https://github.com/Munter/subfont/commit/b848b21b66cdbcd0ff313690ba49a4bc9b9f90a4) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
13
|
+
- [Don't assume that the display name of a variation axis equals its name](https://github.com/Munter/subfont/commit/4fcf7df801de01b2d3aef3d5f1ea3d697779ffda) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
14
|
+
- [Detect unused wght variation axis ranges](https://github.com/Munter/subfont/commit/6a964d609f21bf2c9dad96da1d16d9c32c28dbfd) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
15
|
+
|
|
16
|
+
### v6.10.0 (2022-08-29)
|
|
17
|
+
|
|
18
|
+
- [Update @hookun\/parse-animation-shorthand to ^0.1.4 to make sure we get the fix for hookhookun\/parse-animation-shorthand\#16](https://github.com/Munter/subfont/commit/4db17826245e289e987c6dc02d3f67ebf5891e6f) ([Andreas Lind](mailto:andreas.lind@workday.com))
|
|
19
|
+
- [Disable notice about unused variation axis ranges when there's an out-of-bounds cubic-bezier animation timing function in play](https://github.com/Munter/subfont/commit/1cf4ff46bb6099f8df8602968c7dc0fefb1c1f21) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
20
|
+
- [Update font-tracer to ^3.6.0](https://github.com/Munter/subfont/commit/2a47adc03122aad50ba2a01f116a2d1fe0f2b29e) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
21
|
+
- [Remove accidentally committed commented out code](https://github.com/Munter/subfont/commit/e0c677f9b2c7a9af123045cf29bc78a4f9a56550) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
22
|
+
- [Warn about unused variation axis ranges, not just fully unused axes](https://github.com/Munter/subfont/commit/32fc472327b1c8fc87090d4c1e5f1085533ad8fa) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
23
|
+
- [+13 more](https://github.com/Munter/subfont/compare/v6.9.0...v6.10.0)
|
|
24
|
+
|
|
1
25
|
### v6.9.0 (2022-08-07)
|
|
2
26
|
|
|
3
27
|
- [Update font-tracer to ^3.3.0 Adds support for tracing ::marker, fixes \#166](https://github.com/Munter/subfont/commit/e46c79bac9cb3e62c70deb357823b7963114863b) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const specificity = require('specificity');
|
|
2
2
|
const postcss = require('postcss');
|
|
3
3
|
const unquote = require('./unquote');
|
|
4
|
+
const parseAnimationShorthand = require('@hookun/parse-animation-shorthand');
|
|
4
5
|
|
|
5
6
|
const counterRendererNames = new Set([
|
|
6
7
|
'none',
|
|
@@ -144,34 +145,59 @@ function getCssRulesByProperty(properties, cssSource, existingPredicates) {
|
|
|
144
145
|
});
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
|
-
} else if (
|
|
148
|
-
propName === 'animation' &&
|
|
149
|
-
properties.includes('animation-name')
|
|
150
|
-
) {
|
|
148
|
+
} else if (propName === 'animation') {
|
|
151
149
|
// Shorthand
|
|
152
|
-
const
|
|
150
|
+
const parsedAnimation = parseAnimationShorthand.parseSingle(node.value);
|
|
153
151
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
152
|
+
if (properties.includes('animation-name')) {
|
|
153
|
+
// Split up combined selectors as they might have different specificity
|
|
154
|
+
specificity
|
|
155
|
+
.calculate(node.parent.selector)
|
|
156
|
+
.forEach((specificityObject) => {
|
|
157
|
+
const isStyleAttribute =
|
|
158
|
+
specificityObject.selector === 'bogusselector';
|
|
160
159
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
rulesByProperty['animation-name'].push({
|
|
161
|
+
predicates: getCurrentPredicates(),
|
|
162
|
+
namespaceURI: defaultNamespaceURI,
|
|
163
|
+
selector: isStyleAttribute
|
|
164
|
+
? undefined
|
|
165
|
+
: specificityObject.selector.trim(),
|
|
166
|
+
specificityArray: isStyleAttribute
|
|
167
|
+
? [1, 0, 0, 0]
|
|
168
|
+
: specificityObject.specificityArray,
|
|
169
|
+
prop: 'animation-name',
|
|
170
|
+
value: parsedAnimation.name,
|
|
171
|
+
important: !!node.important,
|
|
172
|
+
});
|
|
173
173
|
});
|
|
174
|
-
|
|
174
|
+
}
|
|
175
|
+
if (properties.includes('animation-timing-function')) {
|
|
176
|
+
// Split up combined selectors as they might have different specificity
|
|
177
|
+
specificity
|
|
178
|
+
.calculate(node.parent.selector)
|
|
179
|
+
.forEach((specificityObject) => {
|
|
180
|
+
const isStyleAttribute =
|
|
181
|
+
specificityObject.selector === 'bogusselector';
|
|
182
|
+
|
|
183
|
+
rulesByProperty['animation-timing-function'].push({
|
|
184
|
+
predicates: getCurrentPredicates(),
|
|
185
|
+
namespaceURI: defaultNamespaceURI,
|
|
186
|
+
selector: isStyleAttribute
|
|
187
|
+
? undefined
|
|
188
|
+
: specificityObject.selector.trim(),
|
|
189
|
+
specificityArray: isStyleAttribute
|
|
190
|
+
? [1, 0, 0, 0]
|
|
191
|
+
: specificityObject.specificityArray,
|
|
192
|
+
prop: 'animation-timing-function',
|
|
193
|
+
value: parseAnimationShorthand.serialize({
|
|
194
|
+
name: '',
|
|
195
|
+
timingFunction: parsedAnimation.timingFunction,
|
|
196
|
+
}),
|
|
197
|
+
important: !!node.important,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
175
201
|
} else if (propName === 'transition') {
|
|
176
202
|
// Shorthand
|
|
177
203
|
const transitionProperties = [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cssFontWeightNames = require('css-font-weight-names');
|
|
2
2
|
const initialValueByProp = require('./initialValueByProp');
|
|
3
3
|
const unquote = require('./unquote');
|
|
4
|
+
const normalizeFontStretch = require('font-snapper/lib/normalizeFontStretch');
|
|
4
5
|
|
|
5
6
|
function normalizeFontPropertyValue(propName, value) {
|
|
6
7
|
const propNameLowerCase = propName.toLowerCase();
|
|
@@ -22,6 +23,8 @@ function normalizeFontPropertyValue(propName, value) {
|
|
|
22
23
|
} else {
|
|
23
24
|
return value;
|
|
24
25
|
}
|
|
26
|
+
} else if (propNameLowerCase === 'font-stretch') {
|
|
27
|
+
return normalizeFontStretch(value);
|
|
25
28
|
} else if (typeof value === 'string' && propNameLowerCase !== 'src') {
|
|
26
29
|
return value.toLowerCase();
|
|
27
30
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const postcssValueParser = require('postcss-value-parser');
|
|
2
|
+
|
|
3
|
+
module.exports = function* parseFontVariationSettings(value) {
|
|
4
|
+
let state = 'BEFORE_AXIS_NAME';
|
|
5
|
+
let axisName;
|
|
6
|
+
for (const token of postcssValueParser(value).nodes) {
|
|
7
|
+
if (token.type === 'space') {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
switch (state) {
|
|
11
|
+
case 'BEFORE_AXIS_NAME': {
|
|
12
|
+
if (token.type !== 'string') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
axisName = token.value;
|
|
16
|
+
state = 'AFTER_AXIS_NAME';
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
case 'AFTER_AXIS_NAME': {
|
|
20
|
+
if (token.type === 'word') {
|
|
21
|
+
const axisValue = parseFloat(token.value);
|
|
22
|
+
if (!isNaN(axisValue)) {
|
|
23
|
+
yield [axisName, axisValue];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
state = 'AFTER_AXIS_VALUE';
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'AFTER_AXIS_VALUE': {
|
|
30
|
+
if (token.type !== 'div' || token.value !== ',') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
axisName = undefined;
|
|
34
|
+
state = 'BEFORE_AXIS_NAME';
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
package/lib/subsetFonts.js
CHANGED
|
@@ -13,13 +13,14 @@ const HeadlessBrowser = require('./HeadlessBrowser');
|
|
|
13
13
|
const gatherStylesheetsWithPredicates = require('./gatherStylesheetsWithPredicates');
|
|
14
14
|
const findCustomPropertyDefinitions = require('./findCustomPropertyDefinitions');
|
|
15
15
|
const extractReferencedCustomPropertyNames = require('./extractReferencedCustomPropertyNames');
|
|
16
|
+
const parseFontVariationSettings = require('./parseFontVariationSettings');
|
|
17
|
+
const parseAnimationShorthand = require('@hookun/parse-animation-shorthand');
|
|
16
18
|
const stripLocalTokens = require('./stripLocalTokens');
|
|
17
19
|
const injectSubsetDefinitions = require('./injectSubsetDefinitions');
|
|
18
20
|
const cssFontParser = require('css-font-parser');
|
|
19
21
|
const cssListHelpers = require('css-list-helpers');
|
|
20
22
|
const LinesAndColumns = require('lines-and-columns').default;
|
|
21
23
|
const fontkit = require('fontkit');
|
|
22
|
-
const fontFamily = require('font-family-papandreou');
|
|
23
24
|
const crypto = require('crypto');
|
|
24
25
|
|
|
25
26
|
const unquote = require('./unquote');
|
|
@@ -41,6 +42,14 @@ const contentTypeByFontFormat = {
|
|
|
41
42
|
truetype: 'font/ttf',
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
function stringifyFontFamily(name) {
|
|
46
|
+
if (/[^a-z0-9_-]/i.test(name)) {
|
|
47
|
+
return name.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
48
|
+
} else {
|
|
49
|
+
return name;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
function uniqueChars(text) {
|
|
45
54
|
return [...new Set([...text])].sort().join('');
|
|
46
55
|
}
|
|
@@ -79,6 +88,21 @@ function getPreferredFontUrl(cssFontFaceSrcRelations = []) {
|
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
function isOutOfBoundsAnimationTimingFunction(animationTimingFunctionStr) {
|
|
92
|
+
if (typeof animationTimingFunctionStr !== 'string') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const { timingFunction } = parseAnimationShorthand.parseSingle(
|
|
96
|
+
`${animationTimingFunctionStr} ignored-name`
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (timingFunction.type === 'cubic-bezier') {
|
|
100
|
+
const [, y1, , y2] = timingFunction.value;
|
|
101
|
+
return y1 > 1 || y1 < 0 || y2 > 1 || y2 < 0;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
82
106
|
// Hack to extract '@font-face { ... }' with all absolute urls
|
|
83
107
|
function getFontFaceDeclarationText(node, relations) {
|
|
84
108
|
const originalHrefTypeByRelation = new Map();
|
|
@@ -145,8 +169,8 @@ function groupTextsByFontFamilyProps(
|
|
|
145
169
|
return [];
|
|
146
170
|
}
|
|
147
171
|
// Find all the families in the traced font-family that we have @font-face declarations for:
|
|
148
|
-
const families =
|
|
149
|
-
.
|
|
172
|
+
const families = cssFontParser
|
|
173
|
+
.parseFontFamily(family)
|
|
150
174
|
.filter((family) =>
|
|
151
175
|
availableFontFaceDeclarations.some(
|
|
152
176
|
(fontFace) =>
|
|
@@ -159,7 +183,7 @@ function groupTextsByFontFamilyProps(
|
|
|
159
183
|
availableFontFaceDeclarations,
|
|
160
184
|
{
|
|
161
185
|
...textAndProps.props,
|
|
162
|
-
'font-family':
|
|
186
|
+
'font-family': stringifyFontFamily(family),
|
|
163
187
|
}
|
|
164
188
|
);
|
|
165
189
|
|
|
@@ -170,9 +194,31 @@ function groupTextsByFontFamilyProps(
|
|
|
170
194
|
const { relations, ...props } = activeFontFaceDeclaration;
|
|
171
195
|
const fontUrl = getPreferredFontUrl(relations);
|
|
172
196
|
|
|
197
|
+
const fontStyle = normalizeFontPropertyValue(
|
|
198
|
+
'font-style',
|
|
199
|
+
textAndProps.props['font-style']
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
let fontWeight = normalizeFontPropertyValue(
|
|
203
|
+
'font-weight',
|
|
204
|
+
textAndProps.props['font-weight']
|
|
205
|
+
);
|
|
206
|
+
if (fontWeight === 'normal') {
|
|
207
|
+
fontWeight = 400;
|
|
208
|
+
}
|
|
209
|
+
|
|
173
210
|
return {
|
|
174
211
|
htmlOrSvgAsset: textAndProps.htmlOrSvgAsset,
|
|
175
212
|
text: textAndProps.text,
|
|
213
|
+
fontVariationSettings: textAndProps.props['font-variation-settings'],
|
|
214
|
+
fontStyle,
|
|
215
|
+
fontWeight,
|
|
216
|
+
fontStretch: normalizeFontPropertyValue(
|
|
217
|
+
'font-stretch',
|
|
218
|
+
textAndProps.props['font-stretch']
|
|
219
|
+
),
|
|
220
|
+
animationTimingFunction:
|
|
221
|
+
textAndProps.props['animation-timing-function'],
|
|
176
222
|
props,
|
|
177
223
|
fontRelations: relations,
|
|
178
224
|
fontUrl,
|
|
@@ -190,6 +236,23 @@ function groupTextsByFontFamilyProps(
|
|
|
190
236
|
const fontFamilies = new Set(
|
|
191
237
|
textsPropsArray.map((obj) => obj.props['font-family'])
|
|
192
238
|
);
|
|
239
|
+
const fontStyles = new Set(textsPropsArray.map((obj) => obj.fontStyle));
|
|
240
|
+
const fontWeights = new Set(textsPropsArray.map((obj) => obj.fontWeight));
|
|
241
|
+
const fontStretches = new Set(
|
|
242
|
+
textsPropsArray.map((obj) => obj.fontStretch)
|
|
243
|
+
);
|
|
244
|
+
const fontVariationSettings = new Set(
|
|
245
|
+
textsPropsArray
|
|
246
|
+
.map((obj) => obj.fontVariationSettings)
|
|
247
|
+
.filter(
|
|
248
|
+
(fontVariationSettings) =>
|
|
249
|
+
fontVariationSettings &&
|
|
250
|
+
fontVariationSettings.toLowerCase() !== 'normal'
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
const hasOutOfBoundsAnimationTimingFunction = textsPropsArray.some((obj) =>
|
|
254
|
+
isOutOfBoundsAnimationTimingFunction(obj.animationTimingFunction)
|
|
255
|
+
);
|
|
193
256
|
|
|
194
257
|
let smallestOriginalSize;
|
|
195
258
|
let smallestOriginalFormat;
|
|
@@ -217,6 +280,11 @@ function groupTextsByFontFamilyProps(
|
|
|
217
280
|
props: { ...textsPropsArray[0].props },
|
|
218
281
|
fontUrl,
|
|
219
282
|
fontFamilies,
|
|
283
|
+
fontStyles,
|
|
284
|
+
fontStretches,
|
|
285
|
+
fontWeights,
|
|
286
|
+
fontVariationSettings,
|
|
287
|
+
hasOutOfBoundsAnimationTimingFunction,
|
|
220
288
|
preload,
|
|
221
289
|
};
|
|
222
290
|
});
|
|
@@ -497,9 +565,9 @@ async function createSelfHostedGoogleFontsCssAsset(
|
|
|
497
565
|
assetGraph,
|
|
498
566
|
googleFontsCssAsset,
|
|
499
567
|
formats,
|
|
500
|
-
hrefType
|
|
568
|
+
hrefType,
|
|
569
|
+
subsetUrl
|
|
501
570
|
) {
|
|
502
|
-
const baseUrl = assetGraph.resolveUrl(assetGraph.root, '/subfont/');
|
|
503
571
|
const lines = [];
|
|
504
572
|
for (const cssFontFaceSrc of assetGraph.findRelations({
|
|
505
573
|
from: googleFontsCssAsset,
|
|
@@ -516,9 +584,12 @@ async function createSelfHostedGoogleFontsCssAsset(
|
|
|
516
584
|
const srcFragments = [];
|
|
517
585
|
for (const format of formats) {
|
|
518
586
|
const rawSrc = await fontverter.convert(cssFontFaceSrc.to.rawSrc, format);
|
|
519
|
-
const url =
|
|
520
|
-
|
|
521
|
-
|
|
587
|
+
const url = assetGraph.resolveUrl(
|
|
588
|
+
subsetUrl,
|
|
589
|
+
`${cssFontFaceSrc.to.baseName}-${md5HexPrefix(rawSrc)}${
|
|
590
|
+
extensionByFormat[format]
|
|
591
|
+
}`
|
|
592
|
+
);
|
|
522
593
|
const fontAsset =
|
|
523
594
|
assetGraph.findAssets({ url })[0] ||
|
|
524
595
|
(await assetGraph.addAsset({
|
|
@@ -526,7 +597,7 @@ async function createSelfHostedGoogleFontsCssAsset(
|
|
|
526
597
|
rawSrc,
|
|
527
598
|
}));
|
|
528
599
|
srcFragments.push(
|
|
529
|
-
`url(${assetGraph.buildHref(fontAsset.url,
|
|
600
|
+
`url(${assetGraph.buildHref(fontAsset.url, subsetUrl, {
|
|
530
601
|
hrefType,
|
|
531
602
|
})}) format('${format}')`
|
|
532
603
|
);
|
|
@@ -542,7 +613,7 @@ async function createSelfHostedGoogleFontsCssAsset(
|
|
|
542
613
|
const text = lines.join('\n');
|
|
543
614
|
const fallbackAsset = assetGraph.addAsset({
|
|
544
615
|
type: 'Css',
|
|
545
|
-
url:
|
|
616
|
+
url: assetGraph.resolveUrl(subsetUrl, `fallback-${md5HexPrefix(text)}.css`),
|
|
546
617
|
text,
|
|
547
618
|
});
|
|
548
619
|
return fallbackAsset;
|
|
@@ -574,6 +645,46 @@ function cssAssetIsEmpty(cssAsset) {
|
|
|
574
645
|
);
|
|
575
646
|
}
|
|
576
647
|
|
|
648
|
+
function parseFontWeightRange(str) {
|
|
649
|
+
if (typeof str === 'undefined' || str === 'auto') {
|
|
650
|
+
return [-Infinity, Infinity];
|
|
651
|
+
}
|
|
652
|
+
let minFontWeight = 400;
|
|
653
|
+
let maxFontWeight = 400;
|
|
654
|
+
const fontWeightTokens = str.split(/\s+/).map((str) => parseFloat(str));
|
|
655
|
+
if (
|
|
656
|
+
[1, 2].includes(fontWeightTokens.length) &&
|
|
657
|
+
!fontWeightTokens.some(isNaN)
|
|
658
|
+
) {
|
|
659
|
+
minFontWeight = maxFontWeight = fontWeightTokens[0];
|
|
660
|
+
if (fontWeightTokens.length === 2) {
|
|
661
|
+
maxFontWeight = fontWeightTokens[1];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return [minFontWeight, maxFontWeight];
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function parseFontStretchRange(str) {
|
|
668
|
+
if (typeof str === 'undefined' || str.toLowerCase() === 'auto') {
|
|
669
|
+
return [-Infinity, Infinity];
|
|
670
|
+
}
|
|
671
|
+
let minFontStretch = 100;
|
|
672
|
+
let maxFontStretch = 100;
|
|
673
|
+
const fontStretchTokens = str
|
|
674
|
+
.split(/\s+/)
|
|
675
|
+
.map((str) => normalizeFontPropertyValue('font-stretch', str));
|
|
676
|
+
if (
|
|
677
|
+
[1, 2].includes(fontStretchTokens.length) &&
|
|
678
|
+
!fontStretchTokens.some(isNaN)
|
|
679
|
+
) {
|
|
680
|
+
minFontStretch = maxFontStretch = fontStretchTokens[0];
|
|
681
|
+
if (fontStretchTokens.length === 2) {
|
|
682
|
+
maxFontStretch = fontStretchTokens[1];
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return [minFontStretch, maxFontStretch];
|
|
686
|
+
}
|
|
687
|
+
|
|
577
688
|
function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) {
|
|
578
689
|
const missingGlyphsErrors = [];
|
|
579
690
|
|
|
@@ -664,6 +775,183 @@ These glyphs are used on your site, but they don't exist in the font you applied
|
|
|
664
775
|
}
|
|
665
776
|
}
|
|
666
777
|
|
|
778
|
+
const standardVariationAxes = new Set(['wght', 'wdth', 'ital', 'slnt', 'opsz']);
|
|
779
|
+
// It would be very hard to trace statically which values of opsz (font-optical-sizing)
|
|
780
|
+
// are going to be used, so we ignore that one:
|
|
781
|
+
const ignoredVariationAxes = new Set(['opsz']);
|
|
782
|
+
|
|
783
|
+
function renderNumberRange(min, max) {
|
|
784
|
+
if (min === max) {
|
|
785
|
+
return String(min);
|
|
786
|
+
} else {
|
|
787
|
+
return `${min}-${max}`;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function warnAboutUnusedVariationAxes(
|
|
792
|
+
htmlOrSvgAssetTextsWithProps,
|
|
793
|
+
assetGraph
|
|
794
|
+
) {
|
|
795
|
+
const seenAxisValuesByFontUrlAndAxisName = new Map();
|
|
796
|
+
const outOfBoundsAxesByFontUrl = new Map();
|
|
797
|
+
|
|
798
|
+
function noteUsedValue(fontUrl, axisName, axisValue) {
|
|
799
|
+
let seenAxes = seenAxisValuesByFontUrlAndAxisName.get(fontUrl);
|
|
800
|
+
if (!seenAxes) {
|
|
801
|
+
seenAxes = new Map();
|
|
802
|
+
seenAxisValuesByFontUrlAndAxisName.set(fontUrl, seenAxes);
|
|
803
|
+
}
|
|
804
|
+
if (seenAxes.has(axisName)) {
|
|
805
|
+
seenAxes.get(axisName).push(axisValue);
|
|
806
|
+
} else {
|
|
807
|
+
seenAxes.set(axisName, [axisValue]);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
for (const { fontUsages } of htmlOrSvgAssetTextsWithProps) {
|
|
812
|
+
for (const {
|
|
813
|
+
fontUrl,
|
|
814
|
+
fontStyles,
|
|
815
|
+
fontWeights,
|
|
816
|
+
fontStretches,
|
|
817
|
+
fontVariationSettings,
|
|
818
|
+
hasOutOfBoundsAnimationTimingFunction,
|
|
819
|
+
props,
|
|
820
|
+
} of fontUsages) {
|
|
821
|
+
if (fontStyles.has('italic')) {
|
|
822
|
+
noteUsedValue(fontUrl, 'ital', 1);
|
|
823
|
+
}
|
|
824
|
+
// If any font-style value except italic is seen (including normal or oblique)
|
|
825
|
+
// we're also utilizing value 0:
|
|
826
|
+
if (fontStyles.size > fontStyles.has('italic') ? 1 : 0) {
|
|
827
|
+
noteUsedValue(fontUrl, 'ital', 0);
|
|
828
|
+
}
|
|
829
|
+
if (fontStyles.has('oblique')) {
|
|
830
|
+
// https://www.w3.org/TR/css-fonts-4/#font-style-prop
|
|
831
|
+
// oblique <angle>?
|
|
832
|
+
// [...] The lack of an <angle> represents 14deg.
|
|
833
|
+
noteUsedValue(fontUrl, 'slnt', 14);
|
|
834
|
+
}
|
|
835
|
+
// If any font-style value except oblique is seen (including normal or italic)
|
|
836
|
+
// we're also utilizing value 0:
|
|
837
|
+
if (fontStyles.size > fontStyles.has('oblique') ? 1 : 0) {
|
|
838
|
+
noteUsedValue(fontUrl, 'slnt', 0);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const minMaxFontWeight = parseFontWeightRange(props['font-weight']);
|
|
842
|
+
for (const fontWeight of fontWeights) {
|
|
843
|
+
noteUsedValue(
|
|
844
|
+
fontUrl,
|
|
845
|
+
'wght',
|
|
846
|
+
_.clamp(fontWeight, ...minMaxFontWeight)
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const minMaxFontStretch = parseFontStretchRange(props['font-stretch']);
|
|
851
|
+
for (const fontStrech of fontStretches) {
|
|
852
|
+
noteUsedValue(
|
|
853
|
+
fontUrl,
|
|
854
|
+
'wdth',
|
|
855
|
+
_.clamp(fontStrech, ...minMaxFontStretch)
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
for (const fontVariationSettingsValue of fontVariationSettings) {
|
|
860
|
+
for (const [axisName, axisValue] of parseFontVariationSettings(
|
|
861
|
+
fontVariationSettingsValue
|
|
862
|
+
)) {
|
|
863
|
+
noteUsedValue(fontUrl, axisName, axisValue);
|
|
864
|
+
if (hasOutOfBoundsAnimationTimingFunction) {
|
|
865
|
+
let outOfBoundsAxes = outOfBoundsAxesByFontUrl.get(fontUrl);
|
|
866
|
+
if (!outOfBoundsAxes) {
|
|
867
|
+
outOfBoundsAxes = new Set();
|
|
868
|
+
outOfBoundsAxesByFontUrl.set(fontUrl, outOfBoundsAxes);
|
|
869
|
+
}
|
|
870
|
+
outOfBoundsAxes.add(axisName);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const warnings = [];
|
|
878
|
+
for (const [
|
|
879
|
+
fontUrl,
|
|
880
|
+
seenAxisValuesByAxisName,
|
|
881
|
+
] of seenAxisValuesByFontUrlAndAxisName.entries()) {
|
|
882
|
+
const outOfBoundsAxes = outOfBoundsAxesByFontUrl.get(fontUrl) || new Set();
|
|
883
|
+
let font;
|
|
884
|
+
try {
|
|
885
|
+
font = fontkit.create(assetGraph.findAssets({ url: fontUrl })[0].rawSrc);
|
|
886
|
+
} catch (err) {
|
|
887
|
+
// Don't break if we encounter an invalid font or one that's unsupported by fontkit
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
const unusedAxes = [];
|
|
891
|
+
const underutilizedAxes = [];
|
|
892
|
+
for (const [name, { min, max, default: defaultValue }] of Object.entries(
|
|
893
|
+
font.variationAxes
|
|
894
|
+
)) {
|
|
895
|
+
if (ignoredVariationAxes.has(name)) {
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
let usedValues = [];
|
|
899
|
+
if (seenAxisValuesByAxisName.has(name) && !outOfBoundsAxes.has(name)) {
|
|
900
|
+
usedValues = [...seenAxisValuesByAxisName.get(name)].map((usedValue) =>
|
|
901
|
+
_.clamp(usedValue, min, max)
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
if (!usedValues.every((value) => value === defaultValue)) {
|
|
905
|
+
if (!standardVariationAxes.has(name)) {
|
|
906
|
+
usedValues.push(defaultValue);
|
|
907
|
+
}
|
|
908
|
+
const minUsed = Math.min(...usedValues);
|
|
909
|
+
const maxUsed = Math.max(...usedValues);
|
|
910
|
+
if (minUsed > min || maxUsed < max) {
|
|
911
|
+
underutilizedAxes.push({
|
|
912
|
+
name,
|
|
913
|
+
minUsed,
|
|
914
|
+
maxUsed,
|
|
915
|
+
min,
|
|
916
|
+
max,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
} else {
|
|
920
|
+
unusedAxes.push(name);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (unusedAxes.length > 0 || underutilizedAxes.length > 0) {
|
|
925
|
+
let message = `${fontUrl}:\n`;
|
|
926
|
+
if (unusedAxes.length > 0) {
|
|
927
|
+
message += ` Unused axes: ${unusedAxes.join(', ')}\n`;
|
|
928
|
+
}
|
|
929
|
+
if (underutilizedAxes.length > 0) {
|
|
930
|
+
message += ` Underutilized axes:\n${underutilizedAxes
|
|
931
|
+
.map(
|
|
932
|
+
({ name, min, max, minUsed, maxUsed }) =>
|
|
933
|
+
` ${name}: ${renderNumberRange(
|
|
934
|
+
minUsed,
|
|
935
|
+
maxUsed
|
|
936
|
+
)} used (${min}-${max} available)`
|
|
937
|
+
)
|
|
938
|
+
.join('\n')}\n`;
|
|
939
|
+
}
|
|
940
|
+
warnings.push(message);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (warnings.length > 0) {
|
|
945
|
+
assetGraph.info(
|
|
946
|
+
new Error(`🪓 Unused variation axes detected in your variable fonts.
|
|
947
|
+
The below variable fonts contain custom axes that do not appear to be fully used on any of your pages.
|
|
948
|
+
This bloats your fonts and also the subset fonts that subfont creates.
|
|
949
|
+
Consider removing the unused axis ranges using a tool like Slice <https://slice-gui.netlify.app/>
|
|
950
|
+
${warnings.join('\n')}`)
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
667
955
|
async function collectTextsByPage(
|
|
668
956
|
assetGraph,
|
|
669
957
|
htmlOrSvgAssets,
|
|
@@ -722,9 +1010,8 @@ async function collectTextsByPage(
|
|
|
722
1010
|
node.walkDecls((declaration) => {
|
|
723
1011
|
const propName = declaration.prop.toLowerCase();
|
|
724
1012
|
if (propName === 'font-family') {
|
|
725
|
-
fontFaceDeclaration[propName] =
|
|
726
|
-
declaration.value
|
|
727
|
-
)[0];
|
|
1013
|
+
fontFaceDeclaration[propName] =
|
|
1014
|
+
cssFontParser.parseFontFamily(declaration.value)[0];
|
|
728
1015
|
} else {
|
|
729
1016
|
fontFaceDeclaration[propName] = declaration.value;
|
|
730
1017
|
}
|
|
@@ -951,6 +1238,7 @@ async function subsetFonts(
|
|
|
951
1238
|
);
|
|
952
1239
|
|
|
953
1240
|
warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph);
|
|
1241
|
+
warnAboutUnusedVariationAxes(htmlOrSvgAssetTextsWithProps, assetGraph);
|
|
954
1242
|
|
|
955
1243
|
// Insert subsets:
|
|
956
1244
|
|
|
@@ -1119,7 +1407,7 @@ async function subsetFonts(
|
|
|
1119
1407
|
|
|
1120
1408
|
if (
|
|
1121
1409
|
fontAsset.contentType === 'font/woff2' &&
|
|
1122
|
-
fontRelation.to.
|
|
1410
|
+
fontRelation.to.url.startsWith(subsetUrl)
|
|
1123
1411
|
) {
|
|
1124
1412
|
const fontFaceDeclaration = fontRelation.node;
|
|
1125
1413
|
const originalFontFamily = unquote(
|
|
@@ -1323,7 +1611,8 @@ async function subsetFonts(
|
|
|
1323
1611
|
assetGraph,
|
|
1324
1612
|
googleFontStylesheetRelation.to,
|
|
1325
1613
|
formats,
|
|
1326
|
-
hrefType
|
|
1614
|
+
hrefType,
|
|
1615
|
+
subsetUrl
|
|
1327
1616
|
);
|
|
1328
1617
|
await selfHostedGoogleFontsCssAsset.minify();
|
|
1329
1618
|
selfHostedGoogleCssByUrl.set(
|
|
@@ -1388,7 +1677,9 @@ async function subsetFonts(
|
|
|
1388
1677
|
);
|
|
1389
1678
|
for (let i = 0; i < fontFamilies.length; i += 1) {
|
|
1390
1679
|
const subsetFontFamily =
|
|
1391
|
-
webfontNameMap[
|
|
1680
|
+
webfontNameMap[
|
|
1681
|
+
cssFontParser.parseFontFamily(fontFamilies[i])[0].toLowerCase()
|
|
1682
|
+
];
|
|
1392
1683
|
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
1393
1684
|
fontFamilies.splice(
|
|
1394
1685
|
i,
|
|
@@ -1448,7 +1739,7 @@ async function subsetFonts(
|
|
|
1448
1739
|
for (let i = 0; i < fontFamilies.length; i += 1) {
|
|
1449
1740
|
const subsetFontFamily =
|
|
1450
1741
|
webfontNameMap[
|
|
1451
|
-
|
|
1742
|
+
cssFontParser.parseFontFamily(fontFamilies[i])[0].toLowerCase()
|
|
1452
1743
|
];
|
|
1453
1744
|
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
1454
1745
|
fontFamilies.splice(
|
|
@@ -1462,7 +1753,7 @@ async function subsetFonts(
|
|
|
1462
1753
|
}
|
|
1463
1754
|
}
|
|
1464
1755
|
} else if (propName === 'font') {
|
|
1465
|
-
const fontProperties = cssFontParser(cssRule.value);
|
|
1756
|
+
const fontProperties = cssFontParser.parseFont(cssRule.value);
|
|
1466
1757
|
const fontFamilies =
|
|
1467
1758
|
fontProperties && fontProperties['font-family'].map(unquote);
|
|
1468
1759
|
if (fontFamilies) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subfont",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.12.0",
|
|
4
4
|
"description": "Speeds up your pages initial paint by automatically subsetting local or Google fonts and loading them optimally",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=10.0.0"
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"homepage": "https://github.com/Munter/subfont#readme",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@gustavnikolaj/async-main-wrap": "^3.0.1",
|
|
50
|
+
"@hookun/parse-animation-shorthand": "^0.1.4",
|
|
50
51
|
"assetgraph": "^7.8.1",
|
|
51
52
|
"browserslist": "^4.13.0",
|
|
52
|
-
"css-font-parser": "^0.
|
|
53
|
+
"css-font-parser": "^2.0.0",
|
|
53
54
|
"css-font-weight-names": "^0.2.1",
|
|
54
55
|
"css-list-helpers": "^2.0.0",
|
|
55
|
-
"font-family-papandreou": "^0.2.0-patch2",
|
|
56
56
|
"font-snapper": "^1.2.0",
|
|
57
|
-
"font-tracer": "^3.
|
|
57
|
+
"font-tracer": "^3.6.0",
|
|
58
58
|
"fontkit": "^1.8.0",
|
|
59
59
|
"fontverter": "^2.0.0",
|
|
60
60
|
"gettemporaryfilepath": "^1.0.1",
|