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.
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: var2stat
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Convert variable fonts to static font instances
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.14
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "var2stat"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Convert variable fonts to static font instances"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -7,7 +7,8 @@ from typing import Any, Dict
7
7
  import click
8
8
  from fontTools.ttLib import TTFont
9
9
 
10
- SCHEMA = "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json"
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": 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, Optional, Union
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 schema if available
28
- schema_ref = config_data.get("$schema")
29
- if schema_ref == SCHEMA_URL and os.path.exists("schema.json"):
30
- try:
31
- with open("schema.json", "r", encoding="utf-8") as f:
32
- schema = json.load(f)
33
- jsonschema.validate(config_data, schema)
34
- print("[INFO]: Config validated")
35
- except jsonschema.ValidationError as e:
36
- error_msg = e.message
37
- if e.absolute_path:
38
- path_str = " -> ".join(str(p) for p in e.absolute_path)
39
- error_msg += f" at {path_str}"
40
- raise ValueError(error_msg)
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
- # Create a canonical font name suitable for filenames/directories:
56
- # - replace invalid path characters with "_"
57
- # - remove spaces
58
- # - fallback to "Font" if empty
59
- canonical_font_name = "".join(
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, Union[int, float]]
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, Union[int, float]],
138
- global_axes: Dict[str, Optional[Union[int, float]]],
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
- output_filename = f"{canonical_font_name}_{variant_name}.ttf"
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
+ }
@@ -136,7 +136,7 @@ wheels = [
136
136
 
137
137
  [[package]]
138
138
  name = "var2stat"
139
- version = "0.1.0"
139
+ version = "0.2.0"
140
140
  source = { editable = "." }
141
141
  dependencies = [
142
142
  { name = "click" },
@@ -1 +0,0 @@
1
- {".":"0.2.0"}
@@ -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