var2stat 0.2.0__tar.gz → 0.2.2__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.2"}
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.2](https://github.com/decipher3114/Var2Stat/compare/var2stat-v0.2.1...var2stat-v0.2.2) (2026-05-13)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * use standard naming convention ([48697b2](https://github.com/decipher3114/Var2Stat/commit/48697b204f2c4f58cdfae5e1d9f4e971673b9c07))
9
+
10
+ ## [0.2.1](https://github.com/decipher3114/Var2Stat/compare/var2stat-v0.2.0...var2stat-v0.2.1) (2026-05-13)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * force canonical and valid name format ([4263ad3](https://github.com/decipher3114/Var2Stat/commit/4263ad306f92f163459cab390f22e3892896ee63))
16
+
3
17
  ## [0.2.0](https://github.com/decipher3114/Var2Stat/compare/var2stat-v0.1.1...var2stat-v0.2.0) (2026-04-13)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: var2stat
3
- Version: 0.2.0
3
+ Version: 0.2.2
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.2"
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,
@@ -3,15 +3,13 @@
3
3
  import json
4
4
  import os
5
5
  from pathlib import Path
6
- from typing import Any, Dict, Optional, Union
6
+ from typing import Any, Dict
7
7
 
8
8
  import click
9
9
  import jsonschema
10
10
  from fontTools.ttLib import TTFont
11
11
  from fontTools.varLib import instancer
12
12
 
13
- SCHEMA_URL = "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json"
14
-
15
13
 
16
14
  def load_config(config_path: str) -> Dict[str, Any]:
17
15
  if not os.path.exists(config_path):
@@ -24,20 +22,20 @@ def load_config(config_path: str) -> Dict[str, Any]:
24
22
  except json.JSONDecodeError as e:
25
23
  raise ValueError(f"Invalid JSON syntax at line {e.lineno}, column {e.colno}")
26
24
 
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)
25
+ # Validate against the packaged schema.
26
+ schema_path = Path(__file__).resolve().with_name("schema.json")
27
+ with open(schema_path, "r", encoding="utf-8") as f:
28
+ schema = json.load(f)
29
+
30
+ try:
31
+ jsonschema.validate(config_data, schema)
32
+ print("[INFO]: Config validated against schema")
33
+ except jsonschema.ValidationError as e:
34
+ error_msg = e.message
35
+ if e.absolute_path:
36
+ path_str = " -> ".join(str(p) for p in e.absolute_path)
37
+ error_msg += f" at {path_str}"
38
+ raise ValueError(error_msg)
41
39
 
42
40
  return config_data
43
41
 
@@ -51,17 +49,14 @@ def remove_variation_tables(font: TTFont) -> None:
51
49
  del font[table]
52
50
 
53
51
 
54
- 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
52
+ def canonicalize(value: str, fallback: str) -> str:
53
+ """Canonicalize a string for filenames/PostScript names.
54
+
55
+ Removes all non-alphabetic characters (spaces, '-', '_', digits, punctuation, etc.).
56
+ """
57
+
58
+ canonical = "".join(ch for ch in (value or "").strip() if ch.isalpha())
59
+ return canonical or fallback
65
60
 
66
61
 
67
62
  def encode(record, text: str) -> bytes:
@@ -73,8 +68,9 @@ def encode(record, text: str) -> bytes:
73
68
  else:
74
69
  return text.encode("utf-16-be")
75
70
 
71
+
76
72
  def update_font_names(
77
- font: TTFont, font_name: str, variant_name: str, axes: Dict[str, Union[int, float]]
73
+ font: TTFont, font_name: str, variant_name: str, axes: Dict[str, float | int]
78
74
  ) -> None:
79
75
  # Update font metadata and naming information
80
76
  name_table = font["name"]
@@ -96,7 +92,7 @@ def update_font_names(
96
92
  record.string = encode(record, full_name)
97
93
 
98
94
  elif record.nameID == 6: # PostScript name
99
- ps_name = f"{font_name.replace(' ', '')}-{variant_name}"
95
+ ps_name = f"{canonicalize(font_name, 'Font')}-{canonicalize(variant_name, 'Variant')}"
100
96
  record.string = encode(record, ps_name)
101
97
 
102
98
  # Update OS/2 weight class
@@ -134,8 +130,8 @@ def generate_variant(
134
130
  font: TTFont,
135
131
  font_name: str,
136
132
  variant_name: str,
137
- variant_config: Dict[str, Union[int, float]],
138
- global_axes: Dict[str, Optional[Union[int, float]]],
133
+ variant_config: Dict[str, float | int],
134
+ global_axes: Dict[str, float | int | None],
139
135
  font_defaults: Dict[str, float],
140
136
  output_folder: str,
141
137
  ) -> bool:
@@ -162,8 +158,9 @@ def generate_variant(
162
158
  update_font_names(static_font, font_name, variant_name, resolved_axes)
163
159
 
164
160
  # Save font
165
- canonical_font_name = canonicalize_font_name(font_name)
166
- output_filename = f"{canonical_font_name}_{variant_name}.ttf"
161
+ canonical_font_name = canonicalize(font_name, fallback="Font")
162
+ canonical_variant_name = canonicalize(variant_name, fallback="Variant")
163
+ output_filename = f"{canonical_font_name}-{canonical_variant_name}.ttf"
167
164
  static_font.save(os.path.join(output_folder, output_filename))
168
165
 
169
166
  return True
@@ -174,7 +171,7 @@ def generate_variant(
174
171
 
175
172
 
176
173
  def resolve_output_directory(font_name: str) -> str:
177
- canonical_font_name = canonicalize_font_name(font_name)
174
+ canonical_font_name = canonicalize(font_name, fallback="Font")
178
175
  os.makedirs(canonical_font_name, exist_ok=True)
179
176
  return canonical_font_name
180
177
 
@@ -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