var2stat 0.1.0__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.1.0/.github/workflows/publish.yml +31 -0
- var2stat-0.1.0/.github/workflows/release.yml +20 -0
- var2stat-0.1.0/.gitignore +10 -0
- var2stat-0.1.0/.python-version +1 -0
- var2stat-0.1.0/.release-please-manifest.json +1 -0
- var2stat-0.1.0/CHANGELOG.md +8 -0
- var2stat-0.1.0/LICENSE +21 -0
- var2stat-0.1.0/PKG-INFO +54 -0
- var2stat-0.1.0/README.md +43 -0
- var2stat-0.1.0/pyproject.toml +21 -0
- var2stat-0.1.0/release-please-config.json +10 -0
- var2stat-0.1.0/schema.json +167 -0
- var2stat-0.1.0/src/var2stat/__init__.py +3 -0
- var2stat-0.1.0/src/var2stat/__main__.py +20 -0
- var2stat-0.1.0/src/var2stat/config.py +141 -0
- var2stat-0.1.0/src/var2stat/generate.py +238 -0
- var2stat-0.1.0/uv.lock +152 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout
|
|
17
|
+
uses: actions/checkout@v6
|
|
18
|
+
|
|
19
|
+
- name: Setup uv
|
|
20
|
+
uses: astral-sh/setup-uv@v7
|
|
21
|
+
|
|
22
|
+
- name: Build package
|
|
23
|
+
run: uv build
|
|
24
|
+
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
env:
|
|
27
|
+
TWINE_USERNAME: __token__
|
|
28
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
|
29
|
+
run: |
|
|
30
|
+
uv tool install twine
|
|
31
|
+
twine upload dist/*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release-please:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Run release-please
|
|
17
|
+
id: release
|
|
18
|
+
uses: googleapis/release-please-action@v4
|
|
19
|
+
with:
|
|
20
|
+
token: ${{ secrets.RELEASE_PLEASE_PAT }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{".":"0.1.0"}
|
var2stat-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Var2Stat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
var2stat-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: var2stat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert variable fonts to static font instances
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Requires-Dist: click>=8.3.1
|
|
8
|
+
Requires-Dist: fonttools>=4.62.1
|
|
9
|
+
Requires-Dist: jsonschema>=4.26.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Var2Stat
|
|
13
|
+
|
|
14
|
+
Convert variable fonts to static font instances with configuration-based axis control.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- Extract axis information and generate optimized configuration files
|
|
19
|
+
- Smart axis inheritance (global defaults with variant overrides)
|
|
20
|
+
- True static output with all variation tables removed
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
- **Step 1**: Generate config from variable font (creates `{FontName}-config.json` in the same directory as the font):
|
|
25
|
+
|
|
26
|
+
```/dev/null/usage.sh#L1-1
|
|
27
|
+
uvx var2stat config <font_file.ttf>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- **Step 2**: Generate static fonts from a config file:
|
|
31
|
+
|
|
32
|
+
```/dev/null/usage.sh#L1-1
|
|
33
|
+
uvx var2stat generate <config_file.json>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Output is always saved to a folder named after the canonicalized font name, with files named `<canonical_font_name>_<variant>.ttf`.
|
|
37
|
+
|
|
38
|
+
## Config Structure
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"$schema": "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json",
|
|
43
|
+
"file": "font.ttf",
|
|
44
|
+
"font_name": "Font Name",
|
|
45
|
+
"axes": { "wght": null, "opsz": 16 },
|
|
46
|
+
"variants": {
|
|
47
|
+
"Regular": { "wght": 400 },
|
|
48
|
+
"Bold": { "wght": 700 }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Global axes with `null` use font defaults
|
|
54
|
+
- Variant axes override global values
|
var2stat-0.1.0/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Var2Stat
|
|
2
|
+
|
|
3
|
+
Convert variable fonts to static font instances with configuration-based axis control.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Extract axis information and generate optimized configuration files
|
|
8
|
+
- Smart axis inheritance (global defaults with variant overrides)
|
|
9
|
+
- True static output with all variation tables removed
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
- **Step 1**: Generate config from variable font (creates `{FontName}-config.json` in the same directory as the font):
|
|
14
|
+
|
|
15
|
+
```/dev/null/usage.sh#L1-1
|
|
16
|
+
uvx var2stat config <font_file.ttf>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Step 2**: Generate static fonts from a config file:
|
|
20
|
+
|
|
21
|
+
```/dev/null/usage.sh#L1-1
|
|
22
|
+
uvx var2stat generate <config_file.json>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Output is always saved to a folder named after the canonicalized font name, with files named `<canonical_font_name>_<variant>.ttf`.
|
|
26
|
+
|
|
27
|
+
## Config Structure
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"$schema": "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json",
|
|
32
|
+
"file": "font.ttf",
|
|
33
|
+
"font_name": "Font Name",
|
|
34
|
+
"axes": { "wght": null, "opsz": 16 },
|
|
35
|
+
"variants": {
|
|
36
|
+
"Regular": { "wght": 400 },
|
|
37
|
+
"Bold": { "wght": 700 }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- Global axes with `null` use font defaults
|
|
43
|
+
- Variant axes override global values
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "var2stat"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Convert variable fonts to static font instances"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.3.1",
|
|
9
|
+
"fonttools>=4.62.1",
|
|
10
|
+
"jsonschema>=4.26.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
var2stat = "var2stat.__main__:cli"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/var2stat"]
|
|
@@ -0,0 +1,167 @@
|
|
|
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": [
|
|
140
|
+
"string",
|
|
141
|
+
"null"
|
|
142
|
+
],
|
|
143
|
+
"description": "Target font family name for generated static fonts"
|
|
144
|
+
},
|
|
145
|
+
"axes": {
|
|
146
|
+
"allOf": [
|
|
147
|
+
{
|
|
148
|
+
"$ref": "#/definitions/axes"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"description": "Global axis default values for all variants"
|
|
152
|
+
},
|
|
153
|
+
"variants": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"description": "Static font variants to generate keys",
|
|
156
|
+
"minProperties": 1,
|
|
157
|
+
"patternProperties": {
|
|
158
|
+
"^[a-zA-Z0-9_-]+$": {
|
|
159
|
+
"$ref": "#/definitions/axes",
|
|
160
|
+
"description": "Variant-specific axis values"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"additionalProperties": false
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"additionalProperties": false
|
|
167
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from .config import config_command
|
|
6
|
+
from .generate import generate_command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def cli() -> None:
|
|
11
|
+
"""Convert variable fonts to static font instances."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
cli.add_command(config_command, name="config")
|
|
16
|
+
cli.add_command(generate_command, name="generate")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
cli()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from fontTools.ttLib import TTFont
|
|
9
|
+
|
|
10
|
+
SCHEMA = "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_font_info(font_path: str) -> Dict[str, Any]:
|
|
14
|
+
font_path_obj = Path(font_path).resolve()
|
|
15
|
+
if not font_path_obj.exists():
|
|
16
|
+
raise FileNotFoundError(f"Font file '{font_path}' not found")
|
|
17
|
+
|
|
18
|
+
font = TTFont(font_path)
|
|
19
|
+
print(f"[INFO]: Font file loaded: {font_path_obj.name}")
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
if "fvar" not in font:
|
|
23
|
+
raise ValueError("This is not a variable font (missing fvar table)")
|
|
24
|
+
|
|
25
|
+
# Extract original font family name
|
|
26
|
+
original_font_name = None
|
|
27
|
+
for record in font["name"].names:
|
|
28
|
+
if record.nameID == 1 and record.platformID == 3 and record.platEncID == 1:
|
|
29
|
+
original_font_name = record.toUnicode()
|
|
30
|
+
break
|
|
31
|
+
|
|
32
|
+
if not original_font_name:
|
|
33
|
+
original_font_name = font_path_obj.stem
|
|
34
|
+
|
|
35
|
+
# Process axis defaults
|
|
36
|
+
axes_defaults = {axis.axisTag: axis.defaultValue for axis in font["fvar"].axes}
|
|
37
|
+
|
|
38
|
+
# Process available instances
|
|
39
|
+
variants = {}
|
|
40
|
+
weight_names = {
|
|
41
|
+
100: "Thin",
|
|
42
|
+
200: "ExtraLight",
|
|
43
|
+
300: "Light",
|
|
44
|
+
400: "Regular",
|
|
45
|
+
500: "Medium",
|
|
46
|
+
600: "SemiBold",
|
|
47
|
+
700: "Bold",
|
|
48
|
+
800: "ExtraBold",
|
|
49
|
+
900: "Black",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for i, instance in enumerate(font["fvar"].instances):
|
|
53
|
+
coordinates = dict(instance.coordinates)
|
|
54
|
+
|
|
55
|
+
# Generate descriptive name from weight
|
|
56
|
+
if "wght" in coordinates:
|
|
57
|
+
weight_value = coordinates["wght"]
|
|
58
|
+
if weight_value in weight_names:
|
|
59
|
+
instance_name = weight_names[weight_value]
|
|
60
|
+
else:
|
|
61
|
+
closest = min(
|
|
62
|
+
weight_names.keys(), key=lambda x: abs(x - weight_value)
|
|
63
|
+
)
|
|
64
|
+
instance_name = f"{weight_names[closest]}{int(weight_value)}"
|
|
65
|
+
else:
|
|
66
|
+
instance_name = f"Instance{i + 1}"
|
|
67
|
+
|
|
68
|
+
variants[instance_name] = coordinates
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"font_path": str(font_path_obj),
|
|
72
|
+
"font_name": original_font_name,
|
|
73
|
+
"axes_defaults": axes_defaults,
|
|
74
|
+
"variants": variants,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
finally:
|
|
78
|
+
font.close()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_config_structure(font_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
82
|
+
variants = font_info["variants"]
|
|
83
|
+
available_axes = list(font_info["axes_defaults"].keys())
|
|
84
|
+
|
|
85
|
+
# Analyze common parameters across instances
|
|
86
|
+
global_axes = {}
|
|
87
|
+
for axis_tag in available_axes:
|
|
88
|
+
axis_values = [
|
|
89
|
+
variant_axes.get(axis_tag)
|
|
90
|
+
for variant_axes in variants.values()
|
|
91
|
+
if axis_tag in variant_axes
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
# Set global value if all instances have the same value
|
|
95
|
+
if axis_values and all(val == axis_values[0] for val in axis_values):
|
|
96
|
+
global_axes[axis_tag] = axis_values[0]
|
|
97
|
+
else:
|
|
98
|
+
global_axes[axis_tag] = None
|
|
99
|
+
|
|
100
|
+
# Build instance-specific configurations
|
|
101
|
+
optimized_variants = {}
|
|
102
|
+
for variant_name, variant_axes in variants.items():
|
|
103
|
+
optimized_variant = {
|
|
104
|
+
axis_tag: value
|
|
105
|
+
for axis_tag, value in variant_axes.items()
|
|
106
|
+
if global_axes.get(axis_tag) != value
|
|
107
|
+
}
|
|
108
|
+
optimized_variants[variant_name] = optimized_variant
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"$schema": SCHEMA,
|
|
112
|
+
"file": font_info["font_path"],
|
|
113
|
+
"font_name": font_info["font_name"],
|
|
114
|
+
"axes": global_axes,
|
|
115
|
+
"variants": optimized_variants,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@click.command("config")
|
|
120
|
+
@click.argument("font_file", type=str)
|
|
121
|
+
def config_command(font_file: str) -> None:
|
|
122
|
+
"""Generate configuration file for variable font.
|
|
123
|
+
|
|
124
|
+
FONT_FILE: Path to the variable font file (.ttf or .otf)
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
font_info = extract_font_info(font_file)
|
|
128
|
+
config = create_config_structure(font_info)
|
|
129
|
+
|
|
130
|
+
config_file = Path(font_file).stem + "-config.json"
|
|
131
|
+
with open(config_file, "w", encoding="utf-8") as f:
|
|
132
|
+
json.dump(config, f, indent=4, ensure_ascii=False)
|
|
133
|
+
|
|
134
|
+
print(f"[INFO]: Configuration saved to: {config_file}")
|
|
135
|
+
|
|
136
|
+
except KeyboardInterrupt:
|
|
137
|
+
print("\n[INFO]: Config generation interrupted by user")
|
|
138
|
+
raise click.Abort()
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f"[ERROR]: {e}")
|
|
141
|
+
raise click.Abort()
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, Optional, Union
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import jsonschema
|
|
10
|
+
from fontTools.ttLib import TTFont
|
|
11
|
+
from fontTools.varLib import instancer
|
|
12
|
+
|
|
13
|
+
SCHEMA_URL = "https://raw.githubusercontent.com/decipher3114/Var2Stat/refs/heads/main/schema.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_config(config_path: str) -> Dict[str, Any]:
|
|
17
|
+
if not os.path.exists(config_path):
|
|
18
|
+
raise FileNotFoundError(f"Configuration file '{config_path}' not found")
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
22
|
+
config_data = json.load(f)
|
|
23
|
+
print(f"[INFO]: Config file loaded: {Path(config_path).name}")
|
|
24
|
+
except json.JSONDecodeError as e:
|
|
25
|
+
raise ValueError(f"Invalid JSON syntax at line {e.lineno}, column {e.colno}")
|
|
26
|
+
|
|
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)
|
|
41
|
+
|
|
42
|
+
return config_data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remove_variation_tables(font: TTFont) -> None:
|
|
46
|
+
# Remove variable font tables to create static font
|
|
47
|
+
variation_tables = ["fvar", "avar", "gvar", "cvar", "HVAR", "VVAR", "MVAR", "STAT"]
|
|
48
|
+
|
|
49
|
+
for table in variation_tables:
|
|
50
|
+
if table in font:
|
|
51
|
+
del font[table]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def update_font_names(
|
|
55
|
+
font: TTFont, font_name: str, variant_name: str, axes: Dict[str, Union[int, float]]
|
|
56
|
+
) -> None:
|
|
57
|
+
# Update font metadata and naming information
|
|
58
|
+
name_table = font["name"]
|
|
59
|
+
weight_value = axes.get("wght", 400)
|
|
60
|
+
|
|
61
|
+
for record in name_table.names:
|
|
62
|
+
if record.platformID == 3 and record.platEncID == 1:
|
|
63
|
+
if record.nameID == 1: # Family name
|
|
64
|
+
record.string = font_name.encode("utf-16-be")
|
|
65
|
+
elif record.nameID == 2: # Subfamily name
|
|
66
|
+
record.string = variant_name.encode("utf-16-be")
|
|
67
|
+
elif record.nameID == 4: # Full name
|
|
68
|
+
full_name = (
|
|
69
|
+
font_name
|
|
70
|
+
if variant_name == "Regular"
|
|
71
|
+
else f"{font_name} {variant_name}"
|
|
72
|
+
)
|
|
73
|
+
record.string = full_name.encode("utf-16-be")
|
|
74
|
+
elif record.nameID == 6: # PostScript name
|
|
75
|
+
ps_name = f"{font_name.replace(' ', '')}-{variant_name}"
|
|
76
|
+
record.string = ps_name.encode("utf-16-be")
|
|
77
|
+
|
|
78
|
+
# Update OS/2 weight class
|
|
79
|
+
if "OS/2" in font:
|
|
80
|
+
font["OS/2"].usWeightClass = int(weight_value)
|
|
81
|
+
|
|
82
|
+
# Update head table macStyle for bold
|
|
83
|
+
if "head" in font:
|
|
84
|
+
if weight_value >= 700:
|
|
85
|
+
font["head"].macStyle |= 0x01
|
|
86
|
+
else:
|
|
87
|
+
font["head"].macStyle &= ~0x01
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def extract_font_info(font: TTFont) -> tuple[Dict[str, float], str]:
|
|
91
|
+
# Extract axis defaults and original font name from variable font
|
|
92
|
+
if "fvar" not in font:
|
|
93
|
+
raise ValueError("Not a variable font (missing fvar table)")
|
|
94
|
+
|
|
95
|
+
if "name" not in font:
|
|
96
|
+
raise ValueError("Font missing name table")
|
|
97
|
+
|
|
98
|
+
# Extract axis defaults
|
|
99
|
+
font_defaults = {axis.axisTag: axis.defaultValue for axis in font["fvar"].axes}
|
|
100
|
+
|
|
101
|
+
# Extract original font family name
|
|
102
|
+
for record in font["name"].names:
|
|
103
|
+
if record.nameID == 1 and record.platformID == 3 and record.platEncID == 1:
|
|
104
|
+
return font_defaults, record.toUnicode()
|
|
105
|
+
|
|
106
|
+
raise ValueError("Could not extract font family name from font file")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def generate_variant(
|
|
110
|
+
font: TTFont,
|
|
111
|
+
font_name: str,
|
|
112
|
+
variant_name: str,
|
|
113
|
+
variant_config: Dict[str, Union[int, float]],
|
|
114
|
+
global_axes: Dict[str, Optional[Union[int, float]]],
|
|
115
|
+
font_defaults: Dict[str, float],
|
|
116
|
+
output_folder: str,
|
|
117
|
+
) -> bool:
|
|
118
|
+
# Generate a single static font variant
|
|
119
|
+
try:
|
|
120
|
+
# Merge global and variant axes
|
|
121
|
+
merged_axes = global_axes.copy()
|
|
122
|
+
merged_axes.update(variant_config)
|
|
123
|
+
|
|
124
|
+
# Resolve axis values
|
|
125
|
+
resolved_axes = {
|
|
126
|
+
axis: font_defaults[axis] if value is None else value
|
|
127
|
+
for axis, value in merged_axes.items()
|
|
128
|
+
if axis in font_defaults
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Display variant info
|
|
132
|
+
axis_parts = [f"'{axis}'={value}" for axis, value in resolved_axes.items()]
|
|
133
|
+
print(f'- Variant: "{variant_name}", Axes: [{", ".join(axis_parts)}]')
|
|
134
|
+
|
|
135
|
+
# Generate static font
|
|
136
|
+
static_font = instancer.instantiateVariableFont(font, resolved_axes)
|
|
137
|
+
remove_variation_tables(static_font)
|
|
138
|
+
update_font_names(static_font, font_name, variant_name, resolved_axes)
|
|
139
|
+
|
|
140
|
+
# Save font
|
|
141
|
+
canonical_font_name = "".join(
|
|
142
|
+
ch if ch not in '<>:"/\\|?*' else "_" for ch in font_name
|
|
143
|
+
).strip()
|
|
144
|
+
if not canonical_font_name:
|
|
145
|
+
canonical_font_name = "Font"
|
|
146
|
+
output_filename = f"{canonical_font_name}_{variant_name}.ttf"
|
|
147
|
+
static_font.save(os.path.join(output_folder, output_filename))
|
|
148
|
+
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"[ERROR]: Failed to generate {variant_name}: {e}")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def resolve_output_directory(font_name: str) -> str:
|
|
157
|
+
sanitized_font_name = "".join(
|
|
158
|
+
ch if ch not in '<>:"/\\|?*' else "_" for ch in font_name
|
|
159
|
+
).strip()
|
|
160
|
+
if not sanitized_font_name:
|
|
161
|
+
sanitized_font_name = "Font"
|
|
162
|
+
os.makedirs(sanitized_font_name, exist_ok=True)
|
|
163
|
+
return sanitized_font_name
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def generate_from_config(config_path: str) -> None:
|
|
167
|
+
# Process configuration and generate static font files
|
|
168
|
+
config = load_config(config_path)
|
|
169
|
+
|
|
170
|
+
font_file = config["file"]
|
|
171
|
+
font_name = config["font_name"]
|
|
172
|
+
|
|
173
|
+
print("[INFO]: Font Info:")
|
|
174
|
+
|
|
175
|
+
if not os.path.exists(font_file):
|
|
176
|
+
raise FileNotFoundError(f"Font file '{font_file}' not found")
|
|
177
|
+
|
|
178
|
+
# Load and validate font
|
|
179
|
+
font = TTFont(font_file)
|
|
180
|
+
try:
|
|
181
|
+
font_defaults, original_font_name = extract_font_info(font)
|
|
182
|
+
|
|
183
|
+
# Use config font name or fallback to original
|
|
184
|
+
if not font_name:
|
|
185
|
+
font_name = original_font_name
|
|
186
|
+
|
|
187
|
+
# Display font information
|
|
188
|
+
if original_font_name == font_name:
|
|
189
|
+
print(f'- Font Name: "{font_name}"')
|
|
190
|
+
else:
|
|
191
|
+
print(f'- Original Font Name: "{original_font_name}"')
|
|
192
|
+
print(f'- Target Font Name: "{font_name}"')
|
|
193
|
+
print(f"- Available Axes: {list(font_defaults.keys())}")
|
|
194
|
+
|
|
195
|
+
output_dir = resolve_output_directory(font_name)
|
|
196
|
+
|
|
197
|
+
print("[INFO]: Variants:")
|
|
198
|
+
|
|
199
|
+
# Generate all variants
|
|
200
|
+
success_count = 0
|
|
201
|
+
for variant_name, variant_config in config["variants"].items():
|
|
202
|
+
if generate_variant(
|
|
203
|
+
font,
|
|
204
|
+
font_name,
|
|
205
|
+
variant_name,
|
|
206
|
+
variant_config,
|
|
207
|
+
config["axes"],
|
|
208
|
+
font_defaults,
|
|
209
|
+
output_dir,
|
|
210
|
+
):
|
|
211
|
+
success_count += 1
|
|
212
|
+
|
|
213
|
+
print(f"[INFO]: Generated {success_count}/{len(config['variants'])} variants")
|
|
214
|
+
print(f"[INFO]: Static font files saved to '{output_dir}' directory")
|
|
215
|
+
|
|
216
|
+
finally:
|
|
217
|
+
font.close()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@click.command("generate")
|
|
221
|
+
@click.argument("config", type=str)
|
|
222
|
+
def generate_command(config: str) -> None:
|
|
223
|
+
"""Generate static fonts from variable fonts.
|
|
224
|
+
|
|
225
|
+
CONFIG: Configuration file path (required)
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
generate_from_config(config)
|
|
229
|
+
except KeyboardInterrupt:
|
|
230
|
+
print("\n[INFO]: Generation interrupted by user")
|
|
231
|
+
raise click.Abort()
|
|
232
|
+
except (FileNotFoundError, ValueError) as e:
|
|
233
|
+
print(f"[ERROR]: {e}")
|
|
234
|
+
print("[INFO]: Generate config using: uvx var2stat config <font_file.ttf>")
|
|
235
|
+
raise click.Abort()
|
|
236
|
+
except Exception as e:
|
|
237
|
+
print(f"[ERROR]: Generation failed: {e}")
|
|
238
|
+
raise click.Abort()
|
var2stat-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.14"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "attrs"
|
|
7
|
+
version = "26.1.0"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "click"
|
|
16
|
+
version = "8.3.1"
|
|
17
|
+
source = { registry = "https://pypi.org/simple" }
|
|
18
|
+
dependencies = [
|
|
19
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
20
|
+
]
|
|
21
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
|
22
|
+
wheels = [
|
|
23
|
+
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "colorama"
|
|
28
|
+
version = "0.4.6"
|
|
29
|
+
source = { registry = "https://pypi.org/simple" }
|
|
30
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
31
|
+
wheels = [
|
|
32
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "fonttools"
|
|
37
|
+
version = "4.62.1"
|
|
38
|
+
source = { registry = "https://pypi.org/simple" }
|
|
39
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" }
|
|
40
|
+
wheels = [
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" },
|
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" },
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" },
|
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" },
|
|
45
|
+
{ url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" },
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" },
|
|
47
|
+
{ url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" },
|
|
48
|
+
{ url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" },
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" },
|
|
50
|
+
{ url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" },
|
|
51
|
+
{ url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" },
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" },
|
|
53
|
+
{ url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" },
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" },
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" },
|
|
56
|
+
{ url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" },
|
|
57
|
+
{ url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[[package]]
|
|
61
|
+
name = "jsonschema"
|
|
62
|
+
version = "4.26.0"
|
|
63
|
+
source = { registry = "https://pypi.org/simple" }
|
|
64
|
+
dependencies = [
|
|
65
|
+
{ name = "attrs" },
|
|
66
|
+
{ name = "jsonschema-specifications" },
|
|
67
|
+
{ name = "referencing" },
|
|
68
|
+
{ name = "rpds-py" },
|
|
69
|
+
]
|
|
70
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
|
|
71
|
+
wheels = [
|
|
72
|
+
{ url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[[package]]
|
|
76
|
+
name = "jsonschema-specifications"
|
|
77
|
+
version = "2025.9.1"
|
|
78
|
+
source = { registry = "https://pypi.org/simple" }
|
|
79
|
+
dependencies = [
|
|
80
|
+
{ name = "referencing" },
|
|
81
|
+
]
|
|
82
|
+
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
|
|
83
|
+
wheels = [
|
|
84
|
+
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[[package]]
|
|
88
|
+
name = "referencing"
|
|
89
|
+
version = "0.37.0"
|
|
90
|
+
source = { registry = "https://pypi.org/simple" }
|
|
91
|
+
dependencies = [
|
|
92
|
+
{ name = "attrs" },
|
|
93
|
+
{ name = "rpds-py" },
|
|
94
|
+
]
|
|
95
|
+
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
|
|
96
|
+
wheels = [
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
[[package]]
|
|
101
|
+
name = "rpds-py"
|
|
102
|
+
version = "0.30.0"
|
|
103
|
+
source = { registry = "https://pypi.org/simple" }
|
|
104
|
+
sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
|
|
105
|
+
wheels = [
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
|
|
109
|
+
{ url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
|
|
131
|
+
{ url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
|
|
132
|
+
{ url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
[[package]]
|
|
138
|
+
name = "var2stat"
|
|
139
|
+
version = "0.1.0"
|
|
140
|
+
source = { editable = "." }
|
|
141
|
+
dependencies = [
|
|
142
|
+
{ name = "click" },
|
|
143
|
+
{ name = "fonttools" },
|
|
144
|
+
{ name = "jsonschema" },
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
[package.metadata]
|
|
148
|
+
requires-dist = [
|
|
149
|
+
{ name = "click", specifier = ">=8.3.1" },
|
|
150
|
+
{ name = "fonttools", specifier = ">=4.62.1" },
|
|
151
|
+
{ name = "jsonschema", specifier = ">=4.26.0" },
|
|
152
|
+
]
|