subfont 6.10.0 → 6.12.1

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 CHANGED
@@ -1,3 +1,24 @@
1
+ ### v6.12.1 (2022-09-11)
2
+
3
+ - [Map font-style: oblique to font-variation-settings: slnt -14](https://github.com/Munter/subfont/commit/ea96284729e0d100fcb87d09c63979641439b169) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
4
+ - [Fix typo in test case](https://github.com/Munter/subfont/commit/b07273ff0daff38a23ef9b0ce77d5a114c4e2154) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
5
+ - [Improve slnt axis test case with a copy of the Extendomatic font Should be OK according to the license: https:\/\/djr.com\/license\/\#server https:\/\/twitter.com\/djrrb\/status\/1568539078416539654?s=20&t=P3w5SKI\_UHV3zzrBMj47lQ](https://github.com/Munter/subfont/commit/f6692fc3ad17a745bda13d1eb78ccd5fb340b369) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
6
+
7
+ ### v6.12.0 (2022-09-07)
8
+
9
+ - [Refactor repeated code into helper function](https://github.com/Munter/subfont/commit/5335d931957cadc150fed1c5d548ee6b6e245636) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
10
+ - [Clamp the used variation axis values to the available interval](https://github.com/Munter/subfont/commit/1429c0f35720ee8dadb691138eb4cfa3c1c86150) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
11
+ - [Detech unused slnt variation axis ranges](https://github.com/Munter/subfont/commit/dc4be1598ba1ba0e65d8269d0fa2d25aa528357d) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
12
+ - [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))
13
+ - [Detect unused wdth variation axis ranges](https://github.com/Munter/subfont/commit/d7758eb3c9a45a49768aac12c36ed897a0c4b467) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
14
+
15
+ ### v6.11.0 (2022-09-04)
16
+
17
+ - [Treat wdth as a standard axis \(but unsupported for now\)](https://github.com/Munter/subfont/commit/20d539750448134cca0a82f2cc98f85d9a6be068) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
18
+ - [Detect unused ital variation axis ranges](https://github.com/Munter/subfont/commit/b848b21b66cdbcd0ff313690ba49a4bc9b9f90a4) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
19
+ - [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))
20
+ - [Detect unused wght variation axis ranges](https://github.com/Munter/subfont/commit/6a964d609f21bf2c9dad96da1d16d9c32c28dbfd) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
21
+
1
22
  ### v6.10.0 (2022-08-29)
2
23
 
3
24
  - [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))
@@ -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
  }
@@ -12,7 +12,7 @@ module.exports = function* parseFontVariationSettings(value) {
12
12
  if (token.type !== 'string') {
13
13
  return;
14
14
  }
15
- axisName = token.value.toUpperCase();
15
+ axisName = token.value;
16
16
  state = 'AFTER_AXIS_NAME';
17
17
  break;
18
18
  }
@@ -194,10 +194,29 @@ function groupTextsByFontFamilyProps(
194
194
  const { relations, ...props } = activeFontFaceDeclaration;
195
195
  const fontUrl = getPreferredFontUrl(relations);
196
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
+
197
210
  return {
198
211
  htmlOrSvgAsset: textAndProps.htmlOrSvgAsset,
199
212
  text: textAndProps.text,
200
213
  fontVariationSettings: textAndProps.props['font-variation-settings'],
214
+ fontStyle,
215
+ fontWeight,
216
+ fontStretch: normalizeFontPropertyValue(
217
+ 'font-stretch',
218
+ textAndProps.props['font-stretch']
219
+ ),
201
220
  animationTimingFunction:
202
221
  textAndProps.props['animation-timing-function'],
203
222
  props,
@@ -217,6 +236,11 @@ function groupTextsByFontFamilyProps(
217
236
  const fontFamilies = new Set(
218
237
  textsPropsArray.map((obj) => obj.props['font-family'])
219
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
+ );
220
244
  const fontVariationSettings = new Set(
221
245
  textsPropsArray
222
246
  .map((obj) => obj.fontVariationSettings)
@@ -256,6 +280,9 @@ function groupTextsByFontFamilyProps(
256
280
  props: { ...textsPropsArray[0].props },
257
281
  fontUrl,
258
282
  fontFamilies,
283
+ fontStyles,
284
+ fontStretches,
285
+ fontWeights,
259
286
  fontVariationSettings,
260
287
  hasOutOfBoundsAnimationTimingFunction,
261
288
  preload,
@@ -618,6 +645,46 @@ function cssAssetIsEmpty(cssAsset) {
618
645
  );
619
646
  }
620
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
+
621
688
  function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) {
622
689
  const missingGlyphsErrors = [];
623
690
 
@@ -708,37 +775,95 @@ These glyphs are used on your site, but they don't exist in the font you applied
708
775
  }
709
776
  }
710
777
 
711
- const standardVariationAxes = new Set(['WGHT', 'ITAL', 'SLNT', 'OPSZ']);
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']);
712
782
 
713
- function warnAboutUnusedCustomVariationAxes(
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(
714
792
  htmlOrSvgAssetTextsWithProps,
715
793
  assetGraph
716
794
  ) {
717
795
  const seenAxisValuesByFontUrlAndAxisName = new Map();
718
796
  const outOfBoundsAxesByFontUrl = new Map();
719
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
+
720
811
  for (const { fontUsages } of htmlOrSvgAssetTextsWithProps) {
721
812
  for (const {
722
813
  fontUrl,
814
+ fontStyles,
815
+ fontWeights,
816
+ fontStretches,
723
817
  fontVariationSettings,
724
818
  hasOutOfBoundsAnimationTimingFunction,
819
+ props,
725
820
  } of fontUsages) {
726
- let seenAxes = seenAxisValuesByFontUrlAndAxisName.get(fontUrl);
727
- if (!seenAxes) {
728
- seenAxes = new Map();
729
- seenAxisValuesByFontUrlAndAxisName.set(fontUrl, seenAxes);
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
+ // And also:
834
+ // Note: the OpenType slnt axis is defined with a positive angle meaning a counter-clockwise slant, the opposite direction to CSS.
835
+ // sThe CSS implementation will take this into account when using variations to produce oblique faces.
836
+ noteUsedValue(fontUrl, 'slnt', -14);
837
+ }
838
+ // If any font-style value except oblique is seen (including normal or italic)
839
+ // we're also utilizing value 0:
840
+ if (fontStyles.size > fontStyles.has('oblique') ? 1 : 0) {
841
+ noteUsedValue(fontUrl, 'slnt', 0);
842
+ }
843
+
844
+ const minMaxFontWeight = parseFontWeightRange(props['font-weight']);
845
+ for (const fontWeight of fontWeights) {
846
+ noteUsedValue(
847
+ fontUrl,
848
+ 'wght',
849
+ _.clamp(fontWeight, ...minMaxFontWeight)
850
+ );
851
+ }
852
+
853
+ const minMaxFontStretch = parseFontStretchRange(props['font-stretch']);
854
+ for (const fontStrech of fontStretches) {
855
+ noteUsedValue(
856
+ fontUrl,
857
+ 'wdth',
858
+ _.clamp(fontStrech, ...minMaxFontStretch)
859
+ );
730
860
  }
731
861
 
732
862
  for (const fontVariationSettingsValue of fontVariationSettings) {
733
863
  for (const [axisName, axisValue] of parseFontVariationSettings(
734
864
  fontVariationSettingsValue
735
865
  )) {
736
- const seenAxisValues = seenAxes.get(axisName);
737
- if (seenAxisValues) {
738
- seenAxisValues.push(axisValue);
739
- } else {
740
- seenAxes.set(axisName, [axisValue]);
741
- }
866
+ noteUsedValue(fontUrl, axisName, axisValue);
742
867
  if (hasOutOfBoundsAnimationTimingFunction) {
743
868
  let outOfBoundsAxes = outOfBoundsAxesByFontUrl.get(fontUrl);
744
869
  if (!outOfBoundsAxes) {
@@ -767,26 +892,27 @@ function warnAboutUnusedCustomVariationAxes(
767
892
  }
768
893
  const unusedAxes = [];
769
894
  const underutilizedAxes = [];
770
- for (const { name, min, max, default: defaultValue } of Object.values(
895
+ for (const [name, { min, max, default: defaultValue }] of Object.entries(
771
896
  font.variationAxes
772
897
  )) {
773
- const axisName = name.toUpperCase();
774
- if (standardVariationAxes.has(axisName)) {
898
+ if (ignoredVariationAxes.has(name)) {
775
899
  continue;
776
900
  }
777
- if (
778
- seenAxisValuesByAxisName.has(axisName) &&
779
- !outOfBoundsAxes.has(axisName)
780
- ) {
781
- const usedValues = [
782
- defaultValue,
783
- ...seenAxisValuesByAxisName.get(axisName),
784
- ];
901
+ let usedValues = [];
902
+ if (seenAxisValuesByAxisName.has(name) && !outOfBoundsAxes.has(name)) {
903
+ usedValues = [...seenAxisValuesByAxisName.get(name)].map((usedValue) =>
904
+ _.clamp(usedValue, min, max)
905
+ );
906
+ }
907
+ if (!usedValues.every((value) => value === defaultValue)) {
908
+ if (!standardVariationAxes.has(name)) {
909
+ usedValues.push(defaultValue);
910
+ }
785
911
  const minUsed = Math.min(...usedValues);
786
912
  const maxUsed = Math.max(...usedValues);
787
913
  if (minUsed > min || maxUsed < max) {
788
914
  underutilizedAxes.push({
789
- name: axisName,
915
+ name,
790
916
  minUsed,
791
917
  maxUsed,
792
918
  min,
@@ -794,7 +920,7 @@ function warnAboutUnusedCustomVariationAxes(
794
920
  });
795
921
  }
796
922
  } else {
797
- unusedAxes.push(axisName);
923
+ unusedAxes.push(name);
798
924
  }
799
925
  }
800
926
 
@@ -807,7 +933,10 @@ function warnAboutUnusedCustomVariationAxes(
807
933
  message += ` Underutilized axes:\n${underutilizedAxes
808
934
  .map(
809
935
  ({ name, min, max, minUsed, maxUsed }) =>
810
- ` ${name}: ${minUsed}-${maxUsed} used (${min}-${max} available)`
936
+ ` ${name}: ${renderNumberRange(
937
+ minUsed,
938
+ maxUsed
939
+ )} used (${min}-${max} available)`
811
940
  )
812
941
  .join('\n')}\n`;
813
942
  }
@@ -817,7 +946,7 @@ function warnAboutUnusedCustomVariationAxes(
817
946
 
818
947
  if (warnings.length > 0) {
819
948
  assetGraph.info(
820
- new Error(`🪓 Unused custom variation axes detected in your variable fonts.
949
+ new Error(`🪓 Unused variation axes detected in your variable fonts.
821
950
  The below variable fonts contain custom axes that do not appear to be fully used on any of your pages.
822
951
  This bloats your fonts and also the subset fonts that subfont creates.
823
952
  Consider removing the unused axis ranges using a tool like Slice <https://slice-gui.netlify.app/>
@@ -1112,7 +1241,7 @@ async function subsetFonts(
1112
1241
  );
1113
1242
 
1114
1243
  warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph);
1115
- warnAboutUnusedCustomVariationAxes(htmlOrSvgAssetTextsWithProps, assetGraph);
1244
+ warnAboutUnusedVariationAxes(htmlOrSvgAssetTextsWithProps, assetGraph);
1116
1245
 
1117
1246
  // Insert subsets:
1118
1247
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subfont",
3
- "version": "6.10.0",
3
+ "version": "6.12.1",
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"