var2stat 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- var2stat-0.2.1/.release-please-manifest.json +1 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/CHANGELOG.md +7 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/PKG-INFO +1 -1
- {var2stat-0.2.0 → var2stat-0.2.1}/pyproject.toml +1 -1
- {var2stat-0.2.0 → var2stat-0.2.1}/src/var2stat/config.py +3 -2
- {var2stat-0.2.0 → var2stat-0.2.1}/src/var2stat/generate.py +37 -31
- var2stat-0.2.1/src/var2stat/schema.json +182 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/uv.lock +1 -1
- var2stat-0.2.0/.release-please-manifest.json +0 -1
- var2stat-0.2.0/schema.json +0 -109
- {var2stat-0.2.0 → var2stat-0.2.1}/.github/workflows/publish.yml +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/.github/workflows/release.yml +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/.gitignore +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/.python-version +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/LICENSE +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/README.md +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/release-please-config.json +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/src/var2stat/__init__.py +0 -0
- {var2stat-0.2.0 → var2stat-0.2.1}/src/var2stat/__main__.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{".":"0.2.1"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1](https://github.com/decipher3114/Var2Stat/compare/var2stat-v0.2.0...var2stat-v0.2.1) (2026-05-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* force canonical and valid name format ([4263ad3](https://github.com/decipher3114/Var2Stat/commit/4263ad306f92f163459cab390f22e3892896ee63))
|
|
9
|
+
|
|
3
10
|
## [0.2.0](https://github.com/decipher3114/Var2Stat/compare/var2stat-v0.1.1...var2stat-v0.2.0) (2026-04-13)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -7,7 +7,8 @@ from typing import Any, Dict
|
|
|
7
7
|
import click
|
|
8
8
|
from fontTools.ttLib import TTFont
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
SCHEMA_URL = "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/var2stat/src/schema.json"
|
|
11
|
+
_SCHEMA_PATH = Path(__file__).resolve().with_name("schema.json")
|
|
11
12
|
|
|
12
13
|
WEIGHT_NAMES: Dict[int, str] = {
|
|
13
14
|
100: "Thin",
|
|
@@ -128,7 +129,7 @@ def create_config_structure(font_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
128
129
|
optimized_variants[variant_name] = optimized_variant
|
|
129
130
|
|
|
130
131
|
return {
|
|
131
|
-
"$schema":
|
|
132
|
+
"$schema": SCHEMA_URL,
|
|
132
133
|
"file": font_info["font_path"],
|
|
133
134
|
"font_name": font_info["font_name"],
|
|
134
135
|
"axes": global_axes,
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import Any, Dict
|
|
7
|
+
from typing import Any, Dict
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import jsonschema
|
|
10
11
|
from fontTools.ttLib import TTFont
|
|
11
12
|
from fontTools.varLib import instancer
|
|
12
13
|
|
|
13
|
-
SCHEMA_URL = "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json"
|
|
14
|
-
|
|
15
14
|
|
|
16
15
|
def load_config(config_path: str) -> Dict[str, Any]:
|
|
17
16
|
if not os.path.exists(config_path):
|
|
@@ -24,20 +23,20 @@ def load_config(config_path: str) -> Dict[str, Any]:
|
|
|
24
23
|
except json.JSONDecodeError as e:
|
|
25
24
|
raise ValueError(f"Invalid JSON syntax at line {e.lineno}, column {e.colno}")
|
|
26
25
|
|
|
27
|
-
# Validate against
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
# Validate against the packaged schema.
|
|
27
|
+
schema_path = Path(__file__).resolve().with_name("schema.json")
|
|
28
|
+
with open(schema_path, "r", encoding="utf-8") as f:
|
|
29
|
+
schema = json.load(f)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
jsonschema.validate(config_data, schema)
|
|
33
|
+
print("[INFO]: Config validated against schema")
|
|
34
|
+
except jsonschema.ValidationError as e:
|
|
35
|
+
error_msg = e.message
|
|
36
|
+
if e.absolute_path:
|
|
37
|
+
path_str = " -> ".join(str(p) for p in e.absolute_path)
|
|
38
|
+
error_msg += f" at {path_str}"
|
|
39
|
+
raise ValueError(error_msg)
|
|
41
40
|
|
|
42
41
|
return config_data
|
|
43
42
|
|
|
@@ -51,17 +50,22 @@ def remove_variation_tables(font: TTFont) -> None:
|
|
|
51
50
|
del font[table]
|
|
52
51
|
|
|
53
52
|
|
|
53
|
+
_CANONICAL_FILENAME_RE = re.compile(r"[^0-9A-Za-z]+")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def canonicalize_name_for_filename(value: str, fallback: str) -> str:
|
|
57
|
+
# Convert all non-alphanumeric characters (including spaces) into '_',
|
|
58
|
+
# and collapse runs via the regex '+'.
|
|
59
|
+
canonical = _CANONICAL_FILENAME_RE.sub("_", (value or "").strip()).strip("_")
|
|
60
|
+
return canonical or fallback
|
|
61
|
+
|
|
62
|
+
|
|
54
63
|
def canonicalize_font_name(font_name: str) -> str:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
ch if ch not in '<>:"/\\|?*' else "_" for ch in font_name
|
|
61
|
-
).replace(" ", "").strip()
|
|
62
|
-
if not canonical_font_name:
|
|
63
|
-
canonical_font_name = "Font"
|
|
64
|
-
return canonical_font_name
|
|
64
|
+
return canonicalize_name_for_filename(font_name, fallback="Font")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def canonicalize_variant(variant: str) -> str:
|
|
68
|
+
return canonicalize_name_for_filename(variant, fallback="Variant")
|
|
65
69
|
|
|
66
70
|
|
|
67
71
|
def encode(record, text: str) -> bytes:
|
|
@@ -73,8 +77,9 @@ def encode(record, text: str) -> bytes:
|
|
|
73
77
|
else:
|
|
74
78
|
return text.encode("utf-16-be")
|
|
75
79
|
|
|
80
|
+
|
|
76
81
|
def update_font_names(
|
|
77
|
-
font: TTFont, font_name: str, variant_name: str, axes: Dict[str,
|
|
82
|
+
font: TTFont, font_name: str, variant_name: str, axes: Dict[str, float | int]
|
|
78
83
|
) -> None:
|
|
79
84
|
# Update font metadata and naming information
|
|
80
85
|
name_table = font["name"]
|
|
@@ -134,8 +139,8 @@ def generate_variant(
|
|
|
134
139
|
font: TTFont,
|
|
135
140
|
font_name: str,
|
|
136
141
|
variant_name: str,
|
|
137
|
-
variant_config: Dict[str,
|
|
138
|
-
global_axes: Dict[str,
|
|
142
|
+
variant_config: Dict[str, float | int],
|
|
143
|
+
global_axes: Dict[str, float | int | None],
|
|
139
144
|
font_defaults: Dict[str, float],
|
|
140
145
|
output_folder: str,
|
|
141
146
|
) -> bool:
|
|
@@ -163,7 +168,8 @@ def generate_variant(
|
|
|
163
168
|
|
|
164
169
|
# Save font
|
|
165
170
|
canonical_font_name = canonicalize_font_name(font_name)
|
|
166
|
-
|
|
171
|
+
canonical_variant_name = canonicalize_variant(variant_name)
|
|
172
|
+
output_filename = f"{canonical_font_name}_{canonical_variant_name}.ttf"
|
|
167
173
|
static_font.save(os.path.join(output_folder, output_filename))
|
|
168
174
|
|
|
169
175
|
return True
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/decipher3114/Var2Stat",
|
|
4
|
+
"title": "Var2Stat Configuration",
|
|
5
|
+
"description": "Configuration schema for generating static fonts from variable fonts",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"file",
|
|
9
|
+
"font_name",
|
|
10
|
+
"axes",
|
|
11
|
+
"variants"
|
|
12
|
+
],
|
|
13
|
+
"definitions": {
|
|
14
|
+
"axes": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"wght": {
|
|
18
|
+
"type": [
|
|
19
|
+
"number",
|
|
20
|
+
"null"
|
|
21
|
+
],
|
|
22
|
+
"minimum": 1,
|
|
23
|
+
"maximum": 1000,
|
|
24
|
+
"description": "Weight axis (1-1000, typically 100-900)"
|
|
25
|
+
},
|
|
26
|
+
"wdth": {
|
|
27
|
+
"type": [
|
|
28
|
+
"number",
|
|
29
|
+
"null"
|
|
30
|
+
],
|
|
31
|
+
"minimum": 25,
|
|
32
|
+
"maximum": 200,
|
|
33
|
+
"description": "Width axis (25-200%, typically 50-200)"
|
|
34
|
+
},
|
|
35
|
+
"opsz": {
|
|
36
|
+
"type": [
|
|
37
|
+
"number",
|
|
38
|
+
"null"
|
|
39
|
+
],
|
|
40
|
+
"minimum": 6,
|
|
41
|
+
"maximum": 144,
|
|
42
|
+
"description": "Optical size axis (6-144pt)"
|
|
43
|
+
},
|
|
44
|
+
"slnt": {
|
|
45
|
+
"type": [
|
|
46
|
+
"number",
|
|
47
|
+
"null"
|
|
48
|
+
],
|
|
49
|
+
"minimum": -90,
|
|
50
|
+
"maximum": 90,
|
|
51
|
+
"description": "Slant axis (-90° to 90°)"
|
|
52
|
+
},
|
|
53
|
+
"ital": {
|
|
54
|
+
"anyOf": [
|
|
55
|
+
{
|
|
56
|
+
"enum": [
|
|
57
|
+
0,
|
|
58
|
+
1
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "null"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"description": "Italic axis (0=roman, 1=italic)"
|
|
66
|
+
},
|
|
67
|
+
"GRAD": {
|
|
68
|
+
"type": [
|
|
69
|
+
"number",
|
|
70
|
+
"null"
|
|
71
|
+
],
|
|
72
|
+
"minimum": -200,
|
|
73
|
+
"maximum": 300,
|
|
74
|
+
"description": "Grade axis (-200 to 300)"
|
|
75
|
+
},
|
|
76
|
+
"ROND": {
|
|
77
|
+
"type": [
|
|
78
|
+
"number",
|
|
79
|
+
"null"
|
|
80
|
+
],
|
|
81
|
+
"description": "Roundness axis"
|
|
82
|
+
},
|
|
83
|
+
"CASL": {
|
|
84
|
+
"type": [
|
|
85
|
+
"number",
|
|
86
|
+
"null"
|
|
87
|
+
],
|
|
88
|
+
"description": "Casual axis"
|
|
89
|
+
},
|
|
90
|
+
"CRSV": {
|
|
91
|
+
"type": [
|
|
92
|
+
"number",
|
|
93
|
+
"null"
|
|
94
|
+
],
|
|
95
|
+
"description": "Cursive axis"
|
|
96
|
+
},
|
|
97
|
+
"MONO": {
|
|
98
|
+
"type": [
|
|
99
|
+
"number",
|
|
100
|
+
"null"
|
|
101
|
+
],
|
|
102
|
+
"description": "Monospace axis"
|
|
103
|
+
},
|
|
104
|
+
"SOFT": {
|
|
105
|
+
"type": [
|
|
106
|
+
"number",
|
|
107
|
+
"null"
|
|
108
|
+
],
|
|
109
|
+
"description": "Softness axis"
|
|
110
|
+
},
|
|
111
|
+
"WONK": {
|
|
112
|
+
"type": [
|
|
113
|
+
"number",
|
|
114
|
+
"null"
|
|
115
|
+
],
|
|
116
|
+
"description": "Wonky axis"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"additionalProperties": {
|
|
120
|
+
"type": [
|
|
121
|
+
"number",
|
|
122
|
+
"null"
|
|
123
|
+
],
|
|
124
|
+
"description": "Custom axis value"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"properties": {
|
|
129
|
+
"$schema": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "JSON schema reference"
|
|
132
|
+
},
|
|
133
|
+
"file": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"description": "Path to the variable font file",
|
|
136
|
+
"pattern": "\\.(ttf|otf)$"
|
|
137
|
+
},
|
|
138
|
+
"font_name": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1,
|
|
141
|
+
"allOf": [
|
|
142
|
+
{
|
|
143
|
+
"pattern": "^\\S(?:.*\\S)?$"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"pattern": ".*[0-9A-Za-z].*"
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"description": "Target font family name for generated static fonts (non-empty after trimming; must contain at least one letter/digit)"
|
|
150
|
+
},
|
|
151
|
+
"axes": {
|
|
152
|
+
"allOf": [
|
|
153
|
+
{
|
|
154
|
+
"$ref": "#/definitions/axes"
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"description": "Global axis default values for all variants"
|
|
158
|
+
},
|
|
159
|
+
"variants": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"description": "Static font variants to generate (object keys are variant names)",
|
|
162
|
+
"minProperties": 1,
|
|
163
|
+
"propertyNames": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"minLength": 1,
|
|
166
|
+
"allOf": [
|
|
167
|
+
{
|
|
168
|
+
"pattern": "^\\S(?:.*\\S)?$"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"pattern": ".*[0-9A-Za-z].*"
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
"additionalProperties": {
|
|
176
|
+
"$ref": "#/definitions/axes",
|
|
177
|
+
"description": "Variant-specific axis values"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"additionalProperties": false
|
|
182
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{".":"0.2.0"}
|
var2stat-0.2.0/schema.json
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://github.com/decipher3114/Var2Stat",
|
|
4
|
-
"title": "Var2Stat Configuration",
|
|
5
|
-
"description": "Configuration schema for generating static fonts from variable fonts",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"required": ["file", "font_name", "axes", "variants"],
|
|
8
|
-
"definitions": {
|
|
9
|
-
"axes": {
|
|
10
|
-
"type": "object",
|
|
11
|
-
"properties": {
|
|
12
|
-
"wght": {
|
|
13
|
-
"type": ["number", "null"],
|
|
14
|
-
"minimum": 1,
|
|
15
|
-
"maximum": 1000,
|
|
16
|
-
"description": "Weight axis (1-1000, typically 100-900)"
|
|
17
|
-
},
|
|
18
|
-
"wdth": {
|
|
19
|
-
"type": ["number", "null"],
|
|
20
|
-
"minimum": 25,
|
|
21
|
-
"maximum": 200,
|
|
22
|
-
"description": "Width axis (25-200%, typically 50-200)"
|
|
23
|
-
},
|
|
24
|
-
"opsz": {
|
|
25
|
-
"type": ["number", "null"],
|
|
26
|
-
"minimum": 6,
|
|
27
|
-
"maximum": 144,
|
|
28
|
-
"description": "Optical size axis (6-144pt)"
|
|
29
|
-
},
|
|
30
|
-
"slnt": {
|
|
31
|
-
"type": ["number", "null"],
|
|
32
|
-
"minimum": -90,
|
|
33
|
-
"maximum": 90,
|
|
34
|
-
"description": "Slant axis (-90° to 90°)"
|
|
35
|
-
},
|
|
36
|
-
"ital": {
|
|
37
|
-
"anyOf": [{ "enum": [0, 1] }, { "type": "null" }],
|
|
38
|
-
"description": "Italic axis (0=roman, 1=italic)"
|
|
39
|
-
},
|
|
40
|
-
"GRAD": {
|
|
41
|
-
"type": ["number", "null"],
|
|
42
|
-
"minimum": -200,
|
|
43
|
-
"maximum": 300,
|
|
44
|
-
"description": "Grade axis (-200 to 300)"
|
|
45
|
-
},
|
|
46
|
-
"ROND": {
|
|
47
|
-
"type": ["number", "null"],
|
|
48
|
-
"description": "Roundness axis"
|
|
49
|
-
},
|
|
50
|
-
"CASL": {
|
|
51
|
-
"type": ["number", "null"],
|
|
52
|
-
"description": "Casual axis"
|
|
53
|
-
},
|
|
54
|
-
"CRSV": {
|
|
55
|
-
"type": ["number", "null"],
|
|
56
|
-
"description": "Cursive axis"
|
|
57
|
-
},
|
|
58
|
-
"MONO": {
|
|
59
|
-
"type": ["number", "null"],
|
|
60
|
-
"description": "Monospace axis"
|
|
61
|
-
},
|
|
62
|
-
"SOFT": {
|
|
63
|
-
"type": ["number", "null"],
|
|
64
|
-
"description": "Softness axis"
|
|
65
|
-
},
|
|
66
|
-
"WONK": {
|
|
67
|
-
"type": ["number", "null"],
|
|
68
|
-
"description": "Wonky axis"
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"additionalProperties": {
|
|
72
|
-
"type": ["number", "null"],
|
|
73
|
-
"description": "Custom axis value"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
"properties": {
|
|
78
|
-
"$schema": {
|
|
79
|
-
"type": "string",
|
|
80
|
-
"description": "JSON schema reference"
|
|
81
|
-
},
|
|
82
|
-
"file": {
|
|
83
|
-
"type": "string",
|
|
84
|
-
"description": "Path to the variable font file",
|
|
85
|
-
"pattern": "\\.(ttf|otf)$"
|
|
86
|
-
},
|
|
87
|
-
"font_name": {
|
|
88
|
-
"type": ["string", "null"],
|
|
89
|
-
"description": "Target font family name for generated static fonts"
|
|
90
|
-
},
|
|
91
|
-
"axes": {
|
|
92
|
-
"allOf": [{ "$ref": "#/definitions/axes" }],
|
|
93
|
-
"description": "Global axis default values for all variants"
|
|
94
|
-
},
|
|
95
|
-
"variants": {
|
|
96
|
-
"type": "object",
|
|
97
|
-
"description": "Static font variants to generate keys",
|
|
98
|
-
"minProperties": 1,
|
|
99
|
-
"patternProperties": {
|
|
100
|
-
"^[a-zA-Z ]+$": {
|
|
101
|
-
"$ref": "#/definitions/axes",
|
|
102
|
-
"description": "Variant-specific axis values"
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
"additionalProperties": false
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
"additionalProperties": false
|
|
109
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|