@webstudio-is/fonts 0.26.0 → 0.28.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.
@@ -60,11 +60,18 @@ const getFontData = (data) => {
60
60
  const format = font.type.toLowerCase();
61
61
  const originalFamily = font.getName("fontFamily");
62
62
  const subfamily = font.getName("preferredSubfamily") ?? font.getName("fontSubfamily");
63
- const parsedSubfamily = parseSubfamily(subfamily);
64
63
  const family = normalizeFamily(originalFamily, subfamily);
64
+ const isVariable = Object.keys(font.variationAxes).length !== 0;
65
+ if (isVariable) {
66
+ return {
67
+ format,
68
+ family,
69
+ variationAxes: font.variationAxes
70
+ };
71
+ }
65
72
  return {
66
73
  format,
67
74
  family,
68
- ...parsedSubfamily
75
+ ...parseSubfamily(subfamily)
69
76
  };
70
77
  };
@@ -23,6 +23,17 @@ __export(get_font_faces_exports, {
23
23
  module.exports = __toCommonJS(get_font_faces_exports);
24
24
  var import_constants = require("./constants");
25
25
  const formatFace = (asset, format) => {
26
+ if ("variationAxes" in asset.meta) {
27
+ const { wght, wdth } = asset.meta?.variationAxes ?? {};
28
+ return {
29
+ fontFamily: asset.meta.family,
30
+ fontStyle: "normal",
31
+ fontDisplay: "swap",
32
+ src: `url('${asset.path}') format('${format}')`,
33
+ fontStretch: wdth ? `${wdth.min}% ${wdth.max}%` : void 0,
34
+ fontWeight: wght ? `${wght.min} ${wght.max}` : void 0
35
+ };
36
+ }
26
37
  return {
27
38
  fontFamily: asset.meta.family,
28
39
  fontStyle: asset.meta.style,
@@ -31,18 +42,24 @@ const formatFace = (asset, format) => {
31
42
  src: `url('${asset.path}') format('${format}')`
32
43
  };
33
44
  };
34
- const getKey = (asset) => asset.meta.family + asset.meta.style + asset.meta.weight;
45
+ const getKey = (asset) => {
46
+ if ("variationAxes" in asset.meta) {
47
+ return asset.meta.family + Object.values(asset.meta.variationAxes).join("");
48
+ }
49
+ return asset.meta.family + asset.meta.style + asset.meta.weight;
50
+ };
35
51
  const getFontFaces = (assets) => {
36
52
  const faces = /* @__PURE__ */ new Map();
37
53
  for (const asset of assets) {
38
- const face = faces.get(getKey(asset));
54
+ const assetKey = getKey(asset);
55
+ const face = faces.get(assetKey);
39
56
  const format = import_constants.FONT_FORMATS.get(asset.format);
40
57
  if (format === void 0) {
41
58
  continue;
42
59
  }
43
60
  if (face === void 0) {
44
61
  const face2 = formatFace(asset, format);
45
- faces.set(getKey(asset), face2);
62
+ faces.set(assetKey, face2);
46
63
  continue;
47
64
  }
48
65
  face.src += `, url('${asset.path}') format('${format}')`;
@@ -19,7 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var schema_exports = {};
20
20
  __export(schema_exports, {
21
21
  FontFormat: () => FontFormat,
22
- FontMeta: () => FontMeta
22
+ FontMeta: () => FontMeta,
23
+ FontMetaStatic: () => FontMetaStatic
23
24
  });
24
25
  module.exports = __toCommonJS(schema_exports);
25
26
  var import_zod = require("zod");
@@ -30,8 +31,38 @@ const FontFormat = import_zod.z.union([
30
31
  import_zod.z.literal("woff2"),
31
32
  import_zod.z.literal("otf")
32
33
  ]);
33
- const FontMeta = import_zod.z.object({
34
+ const AxisName = import_zod.z.enum([
35
+ "wght",
36
+ "wdth",
37
+ "slnt",
38
+ "opsz",
39
+ "ital",
40
+ "GRAD",
41
+ "XTRA",
42
+ "XOPQ",
43
+ "YOPQ",
44
+ "YTLC",
45
+ "YTUC",
46
+ "YTAS",
47
+ "YTDE",
48
+ "YTFI"
49
+ ]);
50
+ const VariationAxes = import_zod.z.record(
51
+ AxisName,
52
+ import_zod.z.object({
53
+ name: import_zod.z.string(),
54
+ min: import_zod.z.number(),
55
+ default: import_zod.z.number(),
56
+ max: import_zod.z.number()
57
+ })
58
+ );
59
+ const FontMetaStatic = import_zod.z.object({
34
60
  family: import_zod.z.string(),
35
61
  style: import_zod.z.enum(import_font_data.styles),
36
62
  weight: import_zod.z.number()
37
63
  });
64
+ const FontMetaVariable = import_zod.z.object({
65
+ family: import_zod.z.string(),
66
+ variationAxes: VariationAxes
67
+ });
68
+ const FontMeta = import_zod.z.union([FontMetaStatic, FontMetaVariable]);
package/lib/font-data.js CHANGED
@@ -34,12 +34,19 @@ const getFontData = (data) => {
34
34
  const format = font.type.toLowerCase();
35
35
  const originalFamily = font.getName("fontFamily");
36
36
  const subfamily = font.getName("preferredSubfamily") ?? font.getName("fontSubfamily");
37
- const parsedSubfamily = parseSubfamily(subfamily);
38
37
  const family = normalizeFamily(originalFamily, subfamily);
38
+ const isVariable = Object.keys(font.variationAxes).length !== 0;
39
+ if (isVariable) {
40
+ return {
41
+ format,
42
+ family,
43
+ variationAxes: font.variationAxes
44
+ };
45
+ }
39
46
  return {
40
47
  format,
41
48
  family,
42
- ...parsedSubfamily
49
+ ...parseSubfamily(subfamily)
43
50
  };
44
51
  };
45
52
  export {
@@ -1,5 +1,16 @@
1
1
  import { FONT_FORMATS } from "./constants";
2
2
  const formatFace = (asset, format) => {
3
+ if ("variationAxes" in asset.meta) {
4
+ const { wght, wdth } = asset.meta?.variationAxes ?? {};
5
+ return {
6
+ fontFamily: asset.meta.family,
7
+ fontStyle: "normal",
8
+ fontDisplay: "swap",
9
+ src: `url('${asset.path}') format('${format}')`,
10
+ fontStretch: wdth ? `${wdth.min}% ${wdth.max}%` : void 0,
11
+ fontWeight: wght ? `${wght.min} ${wght.max}` : void 0
12
+ };
13
+ }
3
14
  return {
4
15
  fontFamily: asset.meta.family,
5
16
  fontStyle: asset.meta.style,
@@ -8,18 +19,24 @@ const formatFace = (asset, format) => {
8
19
  src: `url('${asset.path}') format('${format}')`
9
20
  };
10
21
  };
11
- const getKey = (asset) => asset.meta.family + asset.meta.style + asset.meta.weight;
22
+ const getKey = (asset) => {
23
+ if ("variationAxes" in asset.meta) {
24
+ return asset.meta.family + Object.values(asset.meta.variationAxes).join("");
25
+ }
26
+ return asset.meta.family + asset.meta.style + asset.meta.weight;
27
+ };
12
28
  const getFontFaces = (assets) => {
13
29
  const faces = /* @__PURE__ */ new Map();
14
30
  for (const asset of assets) {
15
- const face = faces.get(getKey(asset));
31
+ const assetKey = getKey(asset);
32
+ const face = faces.get(assetKey);
16
33
  const format = FONT_FORMATS.get(asset.format);
17
34
  if (format === void 0) {
18
35
  continue;
19
36
  }
20
37
  if (face === void 0) {
21
38
  const face2 = formatFace(asset, format);
22
- faces.set(getKey(asset), face2);
39
+ faces.set(assetKey, face2);
23
40
  continue;
24
41
  }
25
42
  face.src += `, url('${asset.path}') format('${format}')`;
package/lib/schema.js CHANGED
@@ -6,12 +6,43 @@ const FontFormat = z.union([
6
6
  z.literal("woff2"),
7
7
  z.literal("otf")
8
8
  ]);
9
- const FontMeta = z.object({
9
+ const AxisName = z.enum([
10
+ "wght",
11
+ "wdth",
12
+ "slnt",
13
+ "opsz",
14
+ "ital",
15
+ "GRAD",
16
+ "XTRA",
17
+ "XOPQ",
18
+ "YOPQ",
19
+ "YTLC",
20
+ "YTUC",
21
+ "YTAS",
22
+ "YTDE",
23
+ "YTFI"
24
+ ]);
25
+ const VariationAxes = z.record(
26
+ AxisName,
27
+ z.object({
28
+ name: z.string(),
29
+ min: z.number(),
30
+ default: z.number(),
31
+ max: z.number()
32
+ })
33
+ );
34
+ const FontMetaStatic = z.object({
10
35
  family: z.string(),
11
36
  style: z.enum(styles),
12
37
  weight: z.number()
13
38
  });
39
+ const FontMetaVariable = z.object({
40
+ family: z.string(),
41
+ variationAxes: VariationAxes
42
+ });
43
+ const FontMeta = z.union([FontMetaStatic, FontMetaVariable]);
14
44
  export {
15
45
  FontFormat,
16
- FontMeta
46
+ FontMeta,
47
+ FontMetaStatic
17
48
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webstudio-is/fonts",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Fonts utils",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -10,11 +10,11 @@
10
10
  },
11
11
  "devDependencies": {
12
12
  "@jest/globals": "^29.3.1",
13
- "@types/fontkit": "^2.0.0",
13
+ "@types/fontkit": "^2.0.1",
14
14
  "jest": "^29.3.1",
15
15
  "typescript": "4.7.4",
16
16
  "zod": "^3.19.1",
17
- "@webstudio-is/design-system": "^0.26.0",
17
+ "@webstudio-is/design-system": "^0.28.0",
18
18
  "@webstudio-is/jest-config": "^1.0.2",
19
19
  "@webstudio-is/scripts": "^0.0.0",
20
20
  "@webstudio-is/tsconfig": "^1.0.1"
@@ -49,3 +49,16 @@ exports[`getFontFaces() different weight 1`] = `
49
49
  },
50
50
  ]
51
51
  `;
52
+
53
+ exports[`getFontFaces() variable font 1`] = `
54
+ [
55
+ {
56
+ "fontDisplay": "swap",
57
+ "fontFamily": "Inter",
58
+ "fontStretch": "25% 151%",
59
+ "fontStyle": "normal",
60
+ "fontWeight": "100 1000",
61
+ "src": "url('/fonts/inter.ttf') format('truetype')",
62
+ },
63
+ ]
64
+ `;
package/src/font-data.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FontFormat } from "./schema";
1
+ import type { FontFormat, VariationAxes } from "./schema";
2
2
  import { create as createFontKit } from "fontkit";
3
3
  import { FontWeight, fontWeights } from "./font-weights";
4
4
 
@@ -7,6 +7,7 @@ declare module "fontkit" {
7
7
  export interface Font {
8
8
  type: string;
9
9
  getName: (name: string) => string;
10
+ variationAxes: VariationAxes;
10
11
  }
11
12
  }
12
13
 
@@ -22,7 +23,6 @@ export const parseSubfamily = (subfamily: string) => {
22
23
  break;
23
24
  }
24
25
  }
25
-
26
26
  let weight: FontWeight = "400";
27
27
  for (weight in fontWeights) {
28
28
  const { name } = fontWeights[weight];
@@ -51,12 +51,18 @@ export const normalizeFamily = (family: string, subfamily: string) => {
51
51
  return familyPartsNormalized.join(" ");
52
52
  };
53
53
 
54
- type FontData = {
54
+ type FontDataStatic = {
55
55
  format: FontFormat;
56
56
  family: string;
57
57
  style: Style;
58
58
  weight: number;
59
59
  };
60
+ type FontDataVariable = {
61
+ format: FontFormat;
62
+ family: string;
63
+ variationAxes: VariationAxes;
64
+ };
65
+ type FontData = FontDataStatic | FontDataVariable;
60
66
 
61
67
  export const getFontData = (data: Uint8Array): FontData => {
62
68
  const font = createFontKit(data as Buffer);
@@ -64,11 +70,20 @@ export const getFontData = (data: Uint8Array): FontData => {
64
70
  const originalFamily = font.getName("fontFamily");
65
71
  const subfamily =
66
72
  font.getName("preferredSubfamily") ?? font.getName("fontSubfamily");
67
- const parsedSubfamily = parseSubfamily(subfamily);
68
73
  const family = normalizeFamily(originalFamily, subfamily);
74
+ const isVariable = Object.keys(font.variationAxes).length !== 0;
75
+
76
+ if (isVariable) {
77
+ return {
78
+ format,
79
+ family,
80
+ variationAxes: font.variationAxes,
81
+ };
82
+ }
83
+
69
84
  return {
70
85
  format,
71
86
  family,
72
- ...parsedSubfamily,
87
+ ...parseSubfamily(subfamily),
73
88
  };
74
89
  };
@@ -73,4 +73,32 @@ describe("getFontFaces()", () => {
73
73
  ];
74
74
  expect(getFontFaces(assets)).toMatchSnapshot();
75
75
  });
76
+
77
+ test("variable font", () => {
78
+ const assets: Array<PartialFontAsset> = [
79
+ {
80
+ format: "ttf",
81
+ meta: {
82
+ family: "Inter",
83
+ variationAxes: {
84
+ wght: { name: "wght", min: 100, default: 400, max: 1000 },
85
+ wdth: { name: "wdth", min: 25, default: 100, max: 151 },
86
+ opsz: { name: "opsz", min: 8, default: 14, max: 144 },
87
+ GRAD: { name: "GRAD", min: -200, default: 0, max: 150 },
88
+ slnt: { name: "slnt", min: -10, default: 0, max: 0 },
89
+ XTRA: { name: "XTRA", min: 323, default: 468, max: 603 },
90
+ XOPQ: { name: "XOPQ", min: 27, default: 96, max: 175 },
91
+ YOPQ: { name: "YOPQ", min: 25, default: 79, max: 135 },
92
+ YTLC: { name: "YTLC", min: 416, default: 514, max: 570 },
93
+ YTUC: { name: "YTUC", min: 528, default: 712, max: 760 },
94
+ YTAS: { name: "YTAS", min: 649, default: 750, max: 854 },
95
+ YTDE: { name: "YTDE", min: -305, default: -203, max: -98 },
96
+ YTFI: { name: "YTFI", min: 560, default: 738, max: 788 },
97
+ },
98
+ },
99
+ path: "/fonts/inter.ttf",
100
+ },
101
+ ];
102
+ expect(getFontFaces(assets)).toMatchSnapshot();
103
+ });
76
104
  });
@@ -1,5 +1,5 @@
1
1
  import { FONT_FORMATS } from "./constants";
2
- import type { FontMeta, FontFormat } from "./schema";
2
+ import type { FontMeta, FontFormat, FontMetaStatic } from "./schema";
3
3
 
4
4
  export type PartialFontAsset = {
5
5
  format: FontFormat;
@@ -9,13 +9,25 @@ export type PartialFontAsset = {
9
9
 
10
10
  export type FontFace = {
11
11
  fontFamily: string;
12
- fontStyle: FontMeta["style"];
13
- fontWeight: number;
14
12
  fontDisplay: "swap" | "auto" | "block" | "fallback" | "optional";
15
13
  src: string;
14
+ fontStyle?: FontMetaStatic["style"];
15
+ fontWeight?: number | string;
16
+ fontStretch?: string;
16
17
  };
17
18
 
18
- const formatFace = (asset: PartialFontAsset, format: string) => {
19
+ const formatFace = (asset: PartialFontAsset, format: string): FontFace => {
20
+ if ("variationAxes" in asset.meta) {
21
+ const { wght, wdth } = asset.meta?.variationAxes ?? {};
22
+ return {
23
+ fontFamily: asset.meta.family,
24
+ fontStyle: "normal",
25
+ fontDisplay: "swap",
26
+ src: `url('${asset.path}') format('${format}')`,
27
+ fontStretch: wdth ? `${wdth.min}% ${wdth.max}%` : undefined,
28
+ fontWeight: wght ? `${wght.min} ${wght.max}` : undefined,
29
+ };
30
+ }
19
31
  return {
20
32
  fontFamily: asset.meta.family,
21
33
  fontStyle: asset.meta.style,
@@ -25,15 +37,20 @@ const formatFace = (asset: PartialFontAsset, format: string) => {
25
37
  };
26
38
  };
27
39
 
28
- const getKey = (asset: PartialFontAsset) =>
29
- asset.meta.family + asset.meta.style + asset.meta.weight;
40
+ const getKey = (asset: PartialFontAsset) => {
41
+ if ("variationAxes" in asset.meta) {
42
+ return asset.meta.family + Object.values(asset.meta.variationAxes).join("");
43
+ }
44
+ return asset.meta.family + asset.meta.style + asset.meta.weight;
45
+ };
30
46
 
31
47
  export const getFontFaces = (
32
48
  assets: Array<PartialFontAsset>
33
49
  ): Array<FontFace> => {
34
50
  const faces = new Map();
35
51
  for (const asset of assets) {
36
- const face = faces.get(getKey(asset));
52
+ const assetKey = getKey(asset);
53
+ const face = faces.get(assetKey);
37
54
  const format = FONT_FORMATS.get(asset.format);
38
55
  if (format === undefined) {
39
56
  // Should never happen since we allow only uploading formats we support
@@ -42,7 +59,7 @@ export const getFontFaces = (
42
59
 
43
60
  if (face === undefined) {
44
61
  const face = formatFace(asset, format);
45
- faces.set(getKey(asset), face);
62
+ faces.set(assetKey, face);
46
63
  continue;
47
64
  }
48
65
 
package/src/schema.ts CHANGED
@@ -9,9 +9,48 @@ export const FontFormat = z.union([
9
9
  ]);
10
10
  export type FontFormat = z.infer<typeof FontFormat>;
11
11
 
12
- export const FontMeta = z.object({
12
+ const AxisName = z.enum([
13
+ "wght",
14
+ "wdth",
15
+ "slnt",
16
+ "opsz",
17
+ "ital",
18
+ "GRAD",
19
+ "XTRA",
20
+ "XOPQ",
21
+ "YOPQ",
22
+ "YTLC",
23
+ "YTUC",
24
+ "YTAS",
25
+ "YTDE",
26
+ "YTFI",
27
+ ]);
28
+
29
+ const VariationAxes = z.record(
30
+ AxisName,
31
+ z.object({
32
+ name: z.string(),
33
+ min: z.number(),
34
+ default: z.number(),
35
+ max: z.number(),
36
+ })
37
+ );
38
+
39
+ export type VariationAxes = z.infer<typeof VariationAxes>;
40
+
41
+ export const FontMetaStatic = z.object({
13
42
  family: z.string(),
14
43
  style: z.enum(styles),
15
44
  weight: z.number(),
16
45
  });
46
+
47
+ export type FontMetaStatic = z.infer<typeof FontMetaStatic>;
48
+
49
+ const FontMetaVariable = z.object({
50
+ family: z.string(),
51
+ variationAxes: VariationAxes,
52
+ });
53
+
54
+ export const FontMeta = z.union([FontMetaStatic, FontMetaVariable]);
55
+
17
56
  export type FontMeta = z.infer<typeof FontMeta>;