@webstudio-is/fonts 0.2.0 → 0.3.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/lib/cjs/constants.cjs +37 -16
- package/lib/cjs/font-data.cjs +61 -44
- package/lib/cjs/font-weights.cjs +68 -48
- package/lib/cjs/get-font-faces.cjs +44 -27
- package/lib/cjs/index.cjs +19 -18
- package/lib/cjs/index.server.cjs +22 -18
- package/lib/cjs/schema.cjs +35 -8
- package/lib/constants.js +18 -14
- package/lib/font-data.js +43 -40
- package/lib/font-weights.js +49 -46
- package/lib/get-font-faces.js +25 -24
- package/lib/index.js +1 -1
- package/lib/index.server.js +4 -2
- package/lib/schema.js +14 -4
- package/package.json +7 -16
- package/src/__snapshots__/get-font-faces.test.ts.snap +51 -0
- package/src/constants.ts +21 -0
- package/src/font-data.test.ts +69 -0
- package/src/font-data.ts +74 -0
- package/src/font-weights.ts +52 -0
- package/src/get-font-faces.test.ts +75 -0
- package/src/get-font-faces.ts +53 -0
- package/src/index.server.ts +1 -0
- package/{lib/cjs/index.d.ts → src/index.ts} +1 -2
- package/src/schema.ts +17 -0
- package/lib/cjs/constants.d.ts +0 -6
- package/lib/cjs/constants.d.ts.map +0 -1
- package/lib/cjs/font-data.d.ts +0 -23
- package/lib/cjs/font-data.d.ts.map +0 -1
- package/lib/cjs/font-data.test.cjs +0 -67
- package/lib/cjs/font-data.test.d.ts +0 -2
- package/lib/cjs/font-data.test.d.ts.map +0 -1
- package/lib/cjs/font-weights.d.ts +0 -50
- package/lib/cjs/font-weights.d.ts.map +0 -1
- package/lib/cjs/get-font-faces.d.ts +0 -16
- package/lib/cjs/get-font-faces.d.ts.map +0 -1
- package/lib/cjs/get-font-faces.test.cjs +0 -74
- package/lib/cjs/get-font-faces.test.d.ts +0 -2
- package/lib/cjs/get-font-faces.test.d.ts.map +0 -1
- package/lib/cjs/index.d.ts.map +0 -1
- package/lib/cjs/index.server.d.ts +0 -3
- package/lib/cjs/index.server.d.ts.map +0 -1
- package/lib/cjs/schema.d.ts +0 -16
- package/lib/cjs/schema.d.ts.map +0 -1
- package/lib/cjs/types.cjs +0 -2
- package/lib/cjs/types.d.ts +0 -2
- package/lib/cjs/types.d.ts.map +0 -1
- package/lib/constants.d.ts +0 -6
- package/lib/constants.d.ts.map +0 -1
- package/lib/font-data.d.ts +0 -23
- package/lib/font-data.d.ts.map +0 -1
- package/lib/font-data.test.d.ts +0 -2
- package/lib/font-data.test.d.ts.map +0 -1
- package/lib/font-data.test.js +0 -65
- package/lib/font-weights.d.ts +0 -50
- package/lib/font-weights.d.ts.map +0 -1
- package/lib/get-font-faces.d.ts +0 -16
- package/lib/get-font-faces.d.ts.map +0 -1
- package/lib/get-font-faces.test.d.ts +0 -2
- package/lib/get-font-faces.test.d.ts.map +0 -1
- package/lib/get-font-faces.test.js +0 -72
- package/lib/index.d.ts +0 -5
- package/lib/index.d.ts.map +0 -1
- package/lib/index.server.d.ts +0 -3
- package/lib/index.server.d.ts.map +0 -1
- package/lib/schema.d.ts +0 -16
- package/lib/schema.d.ts.map +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
- package/lib/types.d.ts +0 -2
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -1
package/lib/font-weights.js
CHANGED
|
@@ -1,47 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
const fontWeights = {
|
|
2
|
+
"100": {
|
|
3
|
+
label: "Thin",
|
|
4
|
+
name: "thin",
|
|
5
|
+
alt: "hairline"
|
|
6
|
+
},
|
|
7
|
+
"200": {
|
|
8
|
+
label: "Extra Light",
|
|
9
|
+
name: "extra light",
|
|
10
|
+
alt: "ultra light"
|
|
11
|
+
},
|
|
12
|
+
"300": {
|
|
13
|
+
label: "Light",
|
|
14
|
+
name: "light",
|
|
15
|
+
alt: "light"
|
|
16
|
+
},
|
|
17
|
+
"400": {
|
|
18
|
+
label: "Normal",
|
|
19
|
+
name: "normal",
|
|
20
|
+
alt: "normal"
|
|
21
|
+
},
|
|
22
|
+
"500": {
|
|
23
|
+
label: "Medium",
|
|
24
|
+
name: "medium",
|
|
25
|
+
alt: "medium"
|
|
26
|
+
},
|
|
27
|
+
"600": {
|
|
28
|
+
label: "Semi Bold",
|
|
29
|
+
name: "semi bold",
|
|
30
|
+
alt: "demi bold"
|
|
31
|
+
},
|
|
32
|
+
"700": {
|
|
33
|
+
label: "Bold",
|
|
34
|
+
name: "bold",
|
|
35
|
+
alt: "bold"
|
|
36
|
+
},
|
|
37
|
+
"800": {
|
|
38
|
+
label: "Extra Bold",
|
|
39
|
+
name: "extra bold",
|
|
40
|
+
alt: "ultra bold"
|
|
41
|
+
},
|
|
42
|
+
"900": {
|
|
43
|
+
label: "Black",
|
|
44
|
+
name: "black",
|
|
45
|
+
alt: "heavy"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
export {
|
|
49
|
+
fontWeights
|
|
47
50
|
};
|
package/lib/get-font-faces.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
import { FONT_FORMATS } from "./constants";
|
|
2
2
|
const formatFace = (asset, format) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
return {
|
|
4
|
+
fontFamily: asset.meta.family,
|
|
5
|
+
fontStyle: asset.meta.style,
|
|
6
|
+
fontWeight: asset.meta.weight,
|
|
7
|
+
fontDisplay: "swap",
|
|
8
|
+
src: `url('${asset.path}') format('${format}')`
|
|
9
|
+
};
|
|
10
10
|
};
|
|
11
11
|
const getKey = (asset) => asset.meta.family + asset.meta.style + asset.meta.weight;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (face === undefined) {
|
|
22
|
-
const face = formatFace(asset, format);
|
|
23
|
-
faces.set(getKey(asset), face);
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
// We already have that font face, so we need to add the new src
|
|
27
|
-
face.src += `, url('${asset.path}') format('${format}')`;
|
|
12
|
+
const getFontFaces = (assets) => {
|
|
13
|
+
const faces = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const asset of assets) {
|
|
15
|
+
const face = faces.get(getKey(asset));
|
|
16
|
+
const format = FONT_FORMATS.get(asset.format);
|
|
17
|
+
if (format === void 0) {
|
|
18
|
+
continue;
|
|
28
19
|
}
|
|
29
|
-
|
|
20
|
+
if (face === void 0) {
|
|
21
|
+
const face2 = formatFace(asset, format);
|
|
22
|
+
faces.set(getKey(asset), face2);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
face.src += `, url('${asset.path}') format('${format}')`;
|
|
26
|
+
}
|
|
27
|
+
return Array.from(faces.values());
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
getFontFaces
|
|
30
31
|
};
|
package/lib/index.js
CHANGED
package/lib/index.server.js
CHANGED
package/lib/schema.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { styles } from "./font-data";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const FontFormat = z.union([
|
|
4
|
+
z.literal("ttf"),
|
|
5
|
+
z.literal("woff"),
|
|
6
|
+
z.literal("woff2"),
|
|
7
|
+
z.literal("otf")
|
|
8
|
+
]);
|
|
9
|
+
const FontMeta = z.object({
|
|
10
|
+
family: z.string(),
|
|
11
|
+
style: z.enum(styles),
|
|
12
|
+
weight: z.number()
|
|
7
13
|
});
|
|
14
|
+
export {
|
|
15
|
+
FontFormat,
|
|
16
|
+
FontMeta
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstudio-is/fonts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Fonts utils",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
"typecheck": "tsc --noEmit",
|
|
10
10
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
11
11
|
"checks": "yarn typecheck && yarn lint && yarn test",
|
|
12
|
-
"dev": "
|
|
13
|
-
"build
|
|
14
|
-
"build": "rm -fr lib tsconfig.tsbuildinfo && tsc && yarn build:cjs",
|
|
12
|
+
"dev": "build-package --watch",
|
|
13
|
+
"build": "build-package",
|
|
15
14
|
"lint": "eslint ./src --ext .ts,.tsx --max-warnings 0",
|
|
16
15
|
"publish-to-npm": "bash ../../bin/publish-to-npm.sh"
|
|
17
16
|
},
|
|
@@ -22,7 +21,7 @@
|
|
|
22
21
|
"@types/fontkit": "^2.0.0",
|
|
23
22
|
"@webstudio-is/design-system": "*",
|
|
24
23
|
"@webstudio-is/jest-config": "*",
|
|
25
|
-
"
|
|
24
|
+
"@webstudio-is/scripts": "*",
|
|
26
25
|
"typescript": "4.7.4",
|
|
27
26
|
"zod": "^3.19.1"
|
|
28
27
|
},
|
|
@@ -37,21 +36,13 @@
|
|
|
37
36
|
},
|
|
38
37
|
"./server": "./server.js"
|
|
39
38
|
},
|
|
40
|
-
"types": "
|
|
39
|
+
"types": "src/index.ts",
|
|
41
40
|
"files": [
|
|
42
41
|
"lib/*",
|
|
43
|
-
"
|
|
42
|
+
"src/*",
|
|
44
43
|
"!*.test.*"
|
|
45
44
|
],
|
|
46
45
|
"license": "MIT",
|
|
47
46
|
"private": false,
|
|
48
|
-
"sideEffects": false
|
|
49
|
-
"tsup": {
|
|
50
|
-
"entry": [
|
|
51
|
-
"src/index.ts",
|
|
52
|
-
"src/index.server.ts"
|
|
53
|
-
],
|
|
54
|
-
"format": "esm",
|
|
55
|
-
"outDir": "lib"
|
|
56
|
-
}
|
|
47
|
+
"sideEffects": false
|
|
57
48
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`getFontFaces() different formats 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"fontDisplay": "swap",
|
|
7
|
+
"fontFamily": "Roboto",
|
|
8
|
+
"fontStyle": "normal",
|
|
9
|
+
"fontWeight": 400,
|
|
10
|
+
"src": "url('/fonts/roboto.woff') format('woff'), url('/fonts/roboto.ttf') format('truetype')",
|
|
11
|
+
},
|
|
12
|
+
]
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
exports[`getFontFaces() different style 1`] = `
|
|
16
|
+
[
|
|
17
|
+
{
|
|
18
|
+
"fontDisplay": "swap",
|
|
19
|
+
"fontFamily": "Roboto",
|
|
20
|
+
"fontStyle": "normal",
|
|
21
|
+
"fontWeight": 400,
|
|
22
|
+
"src": "url('/fonts/roboto.ttf') format('truetype')",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"fontDisplay": "swap",
|
|
26
|
+
"fontFamily": "Roboto",
|
|
27
|
+
"fontStyle": "italic",
|
|
28
|
+
"fontWeight": 400,
|
|
29
|
+
"src": "url('/fonts/roboto-italic.ttf') format('truetype')",
|
|
30
|
+
},
|
|
31
|
+
]
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
exports[`getFontFaces() different weight 1`] = `
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
"fontDisplay": "swap",
|
|
38
|
+
"fontFamily": "Roboto",
|
|
39
|
+
"fontStyle": "normal",
|
|
40
|
+
"fontWeight": 400,
|
|
41
|
+
"src": "url('/fonts/roboto.ttf') format('truetype')",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"fontDisplay": "swap",
|
|
45
|
+
"fontFamily": "Roboto",
|
|
46
|
+
"fontStyle": "normal",
|
|
47
|
+
"fontWeight": 500,
|
|
48
|
+
"src": "url('/fonts/roboto-bold.ttf') format('truetype')",
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
`;
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FontFormat } from "./schema";
|
|
2
|
+
|
|
3
|
+
export const SYSTEM_FONTS = new Map([
|
|
4
|
+
["Arial", ["sans-serif"]],
|
|
5
|
+
["Times New Roman", ["sans"]],
|
|
6
|
+
["Courier New", ["monospace"]],
|
|
7
|
+
["system-ui", []],
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_FONT_FALLBACK = "sans-serif";
|
|
11
|
+
|
|
12
|
+
export const FONT_FORMATS: Map<FontFormat, string> = new Map([
|
|
13
|
+
["woff", "woff"],
|
|
14
|
+
["woff2", "woff2"],
|
|
15
|
+
["ttf", "truetype"],
|
|
16
|
+
["otf", "opentype"],
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const FONT_MIME_TYPES = Array.from(FONT_FORMATS.keys())
|
|
20
|
+
.map((format) => `.${format}`)
|
|
21
|
+
.join(", ");
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { parseSubfamily, normalizeFamily } from "./font-data";
|
|
2
|
+
|
|
3
|
+
describe("font-data", () => {
|
|
4
|
+
describe("parseSubfamily()", () => {
|
|
5
|
+
test("Black Italic", () => {
|
|
6
|
+
expect(parseSubfamily("Black Italic")).toEqual({
|
|
7
|
+
style: "italic",
|
|
8
|
+
weight: 900,
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
test("Bold", () => {
|
|
12
|
+
expect(parseSubfamily("Bold")).toEqual({
|
|
13
|
+
style: "normal",
|
|
14
|
+
weight: 700,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
test("Demi Bold Italic", () => {
|
|
18
|
+
expect(parseSubfamily("Demi Bold Italic")).toEqual({
|
|
19
|
+
style: "italic",
|
|
20
|
+
weight: 600,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
test("Light", () => {
|
|
24
|
+
expect(parseSubfamily("Light")).toEqual({
|
|
25
|
+
style: "normal",
|
|
26
|
+
weight: 300,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
test("Extra Light", () => {
|
|
30
|
+
expect(parseSubfamily("Extra Light")).toEqual({
|
|
31
|
+
style: "normal",
|
|
32
|
+
weight: 200,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
test("Extra Light Italic", () => {
|
|
36
|
+
expect(parseSubfamily("Extra Light Italic")).toEqual({
|
|
37
|
+
style: "italic",
|
|
38
|
+
weight: 200,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
test("Heavy Italic", () => {
|
|
42
|
+
expect(parseSubfamily("Heavy Italic")).toEqual({
|
|
43
|
+
style: "italic",
|
|
44
|
+
weight: 900,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
test("Medium Italic", () => {
|
|
48
|
+
expect(parseSubfamily("Medium Italic")).toEqual({
|
|
49
|
+
style: "italic",
|
|
50
|
+
weight: 500,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("normalizeFamily()", () => {
|
|
56
|
+
test("basic", () => {
|
|
57
|
+
expect(normalizeFamily("Roboto Black", "Black")).toBe("Roboto");
|
|
58
|
+
expect(normalizeFamily("Roboto Light", "Light Italic")).toBe("Roboto");
|
|
59
|
+
expect(normalizeFamily("Robolder Bold", "Bold")).toBe("Robolder");
|
|
60
|
+
expect(normalizeFamily(" Roboto X Bold ", "Bold")).toBe("Roboto X");
|
|
61
|
+
expect(normalizeFamily(" 'Roboto X' Bold ", "Bold")).toBe("'Roboto X'");
|
|
62
|
+
expect(normalizeFamily(` "Roboto X" Bold `, "Bold")).toBe(`"Roboto X"`);
|
|
63
|
+
expect(normalizeFamily(`"Roboto Bold"`, "Bold")).toBe(`"Roboto Bold"`);
|
|
64
|
+
expect(normalizeFamily(`"Roboto Bold" Bold`, "Bold")).toBe(
|
|
65
|
+
`"Roboto Bold"`
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/font-data.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FontFormat } from "./schema";
|
|
2
|
+
import { create as createFontKit } from "fontkit";
|
|
3
|
+
import { FontWeight, fontWeights } from "./font-weights";
|
|
4
|
+
|
|
5
|
+
// @todo sumbit this to definitely typed, they are not up to date
|
|
6
|
+
declare module "fontkit" {
|
|
7
|
+
export interface Font {
|
|
8
|
+
type: string;
|
|
9
|
+
getName: (name: string) => string;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const styles = ["normal", "italic", "oblique"] as const;
|
|
14
|
+
type Style = typeof styles[number];
|
|
15
|
+
|
|
16
|
+
export const parseSubfamily = (subfamily: string) => {
|
|
17
|
+
const subfamilyLow = subfamily.toLowerCase();
|
|
18
|
+
let style: Style = "normal";
|
|
19
|
+
for (const possibleStyle of styles) {
|
|
20
|
+
if (subfamilyLow.includes(possibleStyle)) {
|
|
21
|
+
style = possibleStyle;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let weight: FontWeight = "400";
|
|
27
|
+
for (weight in fontWeights) {
|
|
28
|
+
const { name } = fontWeights[weight];
|
|
29
|
+
const { alt } = fontWeights[weight];
|
|
30
|
+
if (subfamilyLow.includes(name) || subfamilyLow.includes(alt)) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { style, weight: Number(weight) };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const splitAndTrim = (string: string) =>
|
|
38
|
+
string
|
|
39
|
+
.split(" ")
|
|
40
|
+
.map((part) => part.trim())
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
|
|
43
|
+
// Family name can contain additional information like "Roboto Black" or "Roboto Bold", though we need pure family name "Roboto", because the rest is already encoded in weight and style.
|
|
44
|
+
// We need a name we can reference in CSS font-family property, while CSS matches it with the right font-face considering the weight and style.
|
|
45
|
+
export const normalizeFamily = (family: string, subfamily: string) => {
|
|
46
|
+
const familyParts = splitAndTrim(family);
|
|
47
|
+
const subfamilyParts = splitAndTrim(subfamily.toLowerCase());
|
|
48
|
+
const familyPartsNormalized = familyParts.filter(
|
|
49
|
+
(familyPart) => subfamilyParts.includes(familyPart.toLowerCase()) === false
|
|
50
|
+
);
|
|
51
|
+
return familyPartsNormalized.join(" ");
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type FontData = {
|
|
55
|
+
format: FontFormat;
|
|
56
|
+
family: string;
|
|
57
|
+
style: Style;
|
|
58
|
+
weight: number;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getFontData = (data: Uint8Array): FontData => {
|
|
62
|
+
const font = createFontKit(data as Buffer);
|
|
63
|
+
const format = font.type.toLowerCase() as FontData["format"];
|
|
64
|
+
const originalFamily = font.getName("fontFamily");
|
|
65
|
+
const subfamily =
|
|
66
|
+
font.getName("preferredSubfamily") ?? font.getName("fontSubfamily");
|
|
67
|
+
const parsedSubfamily = parseSubfamily(subfamily);
|
|
68
|
+
const family = normalizeFamily(originalFamily, subfamily);
|
|
69
|
+
return {
|
|
70
|
+
format,
|
|
71
|
+
family,
|
|
72
|
+
...parsedSubfamily,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const fontWeights = {
|
|
2
|
+
"100": {
|
|
3
|
+
label: "Thin",
|
|
4
|
+
name: "thin",
|
|
5
|
+
alt: "hairline",
|
|
6
|
+
},
|
|
7
|
+
"200": {
|
|
8
|
+
label: "Extra Light",
|
|
9
|
+
name: "extra light",
|
|
10
|
+
alt: "ultra light",
|
|
11
|
+
},
|
|
12
|
+
"300": {
|
|
13
|
+
label: "Light",
|
|
14
|
+
name: "light",
|
|
15
|
+
alt: "light",
|
|
16
|
+
},
|
|
17
|
+
"400": {
|
|
18
|
+
label: "Normal",
|
|
19
|
+
name: "normal",
|
|
20
|
+
alt: "normal",
|
|
21
|
+
},
|
|
22
|
+
"500": {
|
|
23
|
+
label: "Medium",
|
|
24
|
+
name: "medium",
|
|
25
|
+
alt: "medium",
|
|
26
|
+
},
|
|
27
|
+
"600": {
|
|
28
|
+
label: "Semi Bold",
|
|
29
|
+
name: "semi bold",
|
|
30
|
+
alt: "demi bold",
|
|
31
|
+
},
|
|
32
|
+
"700": {
|
|
33
|
+
label: "Bold",
|
|
34
|
+
name: "bold",
|
|
35
|
+
alt: "bold",
|
|
36
|
+
},
|
|
37
|
+
"800": {
|
|
38
|
+
label: "Extra Bold",
|
|
39
|
+
name: "extra bold",
|
|
40
|
+
alt: "ultra bold",
|
|
41
|
+
},
|
|
42
|
+
"900": {
|
|
43
|
+
label: "Black",
|
|
44
|
+
name: "black",
|
|
45
|
+
alt: "heavy",
|
|
46
|
+
},
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
export type FontWeight = keyof typeof fontWeights;
|
|
50
|
+
export type FontWeightKeyword =
|
|
51
|
+
| typeof fontWeights[FontWeight]["name"]
|
|
52
|
+
| typeof fontWeights[FontWeight]["alt"];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getFontFaces, type PartialFontAsset } from "./get-font-faces";
|
|
2
|
+
|
|
3
|
+
describe("getFontFaces()", () => {
|
|
4
|
+
test("different formats", () => {
|
|
5
|
+
const assets: Array<PartialFontAsset> = [
|
|
6
|
+
{
|
|
7
|
+
format: "woff",
|
|
8
|
+
meta: {
|
|
9
|
+
family: "Roboto",
|
|
10
|
+
style: "normal",
|
|
11
|
+
weight: 400,
|
|
12
|
+
},
|
|
13
|
+
path: "/fonts/roboto.woff",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
format: "ttf",
|
|
17
|
+
meta: {
|
|
18
|
+
family: "Roboto",
|
|
19
|
+
style: "normal",
|
|
20
|
+
weight: 400,
|
|
21
|
+
},
|
|
22
|
+
path: "/fonts/roboto.ttf",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
expect(getFontFaces(assets)).toMatchSnapshot();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("different style", () => {
|
|
29
|
+
const assets: Array<PartialFontAsset> = [
|
|
30
|
+
{
|
|
31
|
+
format: "ttf",
|
|
32
|
+
meta: {
|
|
33
|
+
family: "Roboto",
|
|
34
|
+
style: "normal",
|
|
35
|
+
weight: 400,
|
|
36
|
+
},
|
|
37
|
+
path: "/fonts/roboto.ttf",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
format: "ttf",
|
|
41
|
+
meta: {
|
|
42
|
+
family: "Roboto",
|
|
43
|
+
style: "italic",
|
|
44
|
+
weight: 400,
|
|
45
|
+
},
|
|
46
|
+
path: "/fonts/roboto-italic.ttf",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
expect(getFontFaces(assets)).toMatchSnapshot();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("different weight", () => {
|
|
53
|
+
const assets: Array<PartialFontAsset> = [
|
|
54
|
+
{
|
|
55
|
+
format: "ttf",
|
|
56
|
+
meta: {
|
|
57
|
+
family: "Roboto",
|
|
58
|
+
style: "normal",
|
|
59
|
+
weight: 400,
|
|
60
|
+
},
|
|
61
|
+
path: "/fonts/roboto.ttf",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
format: "ttf",
|
|
65
|
+
meta: {
|
|
66
|
+
family: "Roboto",
|
|
67
|
+
style: "normal",
|
|
68
|
+
weight: 500,
|
|
69
|
+
},
|
|
70
|
+
path: "/fonts/roboto-bold.ttf",
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
expect(getFontFaces(assets)).toMatchSnapshot();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FONT_FORMATS } from "./constants";
|
|
2
|
+
import type { FontMeta, FontFormat } from "./schema";
|
|
3
|
+
|
|
4
|
+
export type PartialFontAsset = {
|
|
5
|
+
format: FontFormat;
|
|
6
|
+
meta: FontMeta;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type FontFace = {
|
|
11
|
+
fontFamily: string;
|
|
12
|
+
fontStyle: FontMeta["style"];
|
|
13
|
+
fontWeight: number;
|
|
14
|
+
fontDisplay: "swap" | "auto" | "block" | "fallback" | "optional";
|
|
15
|
+
src: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const formatFace = (asset: PartialFontAsset, format: string) => {
|
|
19
|
+
return {
|
|
20
|
+
fontFamily: asset.meta.family,
|
|
21
|
+
fontStyle: asset.meta.style,
|
|
22
|
+
fontWeight: asset.meta.weight,
|
|
23
|
+
fontDisplay: "swap",
|
|
24
|
+
src: `url('${asset.path}') format('${format}')`,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getKey = (asset: PartialFontAsset) =>
|
|
29
|
+
asset.meta.family + asset.meta.style + asset.meta.weight;
|
|
30
|
+
|
|
31
|
+
export const getFontFaces = (
|
|
32
|
+
assets: Array<PartialFontAsset>
|
|
33
|
+
): Array<FontFace> => {
|
|
34
|
+
const faces = new Map();
|
|
35
|
+
for (const asset of assets) {
|
|
36
|
+
const face = faces.get(getKey(asset));
|
|
37
|
+
const format = FONT_FORMATS.get(asset.format);
|
|
38
|
+
if (format === undefined) {
|
|
39
|
+
// Should never happen since we allow only uploading formats we support
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (face === undefined) {
|
|
44
|
+
const face = formatFace(asset, format);
|
|
45
|
+
faces.set(getKey(asset), face);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// We already have that font face, so we need to add the new src
|
|
50
|
+
face.src += `, url('${asset.path}') format('${format}')`;
|
|
51
|
+
}
|
|
52
|
+
return Array.from(faces.values());
|
|
53
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getFontData } from "./font-data";
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { styles } from "./font-data";
|
|
3
|
+
|
|
4
|
+
export const FontFormat = z.union([
|
|
5
|
+
z.literal("ttf"),
|
|
6
|
+
z.literal("woff"),
|
|
7
|
+
z.literal("woff2"),
|
|
8
|
+
z.literal("otf"),
|
|
9
|
+
]);
|
|
10
|
+
export type FontFormat = z.infer<typeof FontFormat>;
|
|
11
|
+
|
|
12
|
+
export const FontMeta = z.object({
|
|
13
|
+
family: z.string(),
|
|
14
|
+
style: z.enum(styles),
|
|
15
|
+
weight: z.number(),
|
|
16
|
+
});
|
|
17
|
+
export type FontMeta = z.infer<typeof FontMeta>;
|
package/lib/cjs/constants.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { FontFormat } from "./types";
|
|
2
|
-
export declare const SYSTEM_FONTS: Map<string, string[]>;
|
|
3
|
-
export declare const DEFAULT_FONT_FALLBACK = "sans-serif";
|
|
4
|
-
export declare const FONT_FORMATS: Map<FontFormat, string>;
|
|
5
|
-
export declare const FONT_MIME_TYPES: string;
|
|
6
|
-
//# sourceMappingURL=constants.d.ts.map
|