python-fontbro 0.30.0__tar.gz → 0.31.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.
- {python_fontbro-0.30.0/python_fontbro.egg-info → python_fontbro-0.31.0}/PKG-INFO +49 -2
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/README.md +48 -1
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/font.py +59 -1
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/metadata.py +1 -1
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/names.py +253 -17
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/variable.py +32 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0/python_fontbro.egg-info}/PKG-INFO +49 -2
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_instantiation.py +46 -0
- python_fontbro-0.31.0/tests/test_rename.py +454 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_unicode_blocks_and_scripts.py +16 -16
- python_fontbro-0.30.0/tests/test_rename.py +0 -163
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/LICENSE.txt +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/MANIFEST.in +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/__init__.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/bitmap.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/color.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/data/family-classifications.json +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/data/features.json +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/data/unicode-blocks.json +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/data/unicode-scripts.json +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/embedding_permissions.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/exceptions.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/family_classification.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/features.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/files.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/fingerprint.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/flags.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/glyphs.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/math.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/metrics.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/monospace.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/pixel.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/py.typed +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/render.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/sanitize.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/style_flags.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/subset.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/support.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/tables.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/unicode.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/utils.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/fontbro/version.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/pyproject.toml +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/python_fontbro.egg-info/SOURCES.txt +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/python_fontbro.egg-info/requires.txt +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/python_fontbro.egg-info/top_level.txt +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/setup.cfg +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/setup.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_bitmap.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_characters.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_clone.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_close.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_collection.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_color.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_context_manager.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_embedding_permissions.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_family_classification.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_family_name.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_features.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_filename.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_fingerprint.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_format.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_glyphs.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_image.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_init.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_issues.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_italic_angle.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_monospace.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_names.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_pixel.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_sanitize.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_save.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_str.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_style_flags.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_style_name.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_subset.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_support.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_svg.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_tables.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_unicode.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_update_unicode_data.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_variable.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_version.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_vertical_metrics.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_weight.py +0 -0
- {python_fontbro-0.30.0 → python_fontbro-0.31.0}/tests/test_width.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fontbro
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.0
|
|
4
4
|
Summary: friendly font operations on top of fontTools.
|
|
5
5
|
Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
|
|
6
6
|
Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
|
|
@@ -149,6 +149,7 @@ with open("fonts/MyFont.ttf") as fh:
|
|
|
149
149
|
- [`is_static`](#is_static)
|
|
150
150
|
- [`is_variable`](#is_variable)
|
|
151
151
|
- [`rename`](#rename)
|
|
152
|
+
- [`rename_variable_instances`](#rename_variable_instances)
|
|
152
153
|
- [`sanitize`](#sanitize)
|
|
153
154
|
- [`save`](#save)
|
|
154
155
|
- [`save_as_woff`](#save_as_woff)
|
|
@@ -966,6 +967,8 @@ variable = font.is_variable()
|
|
|
966
967
|
"""
|
|
967
968
|
Renames the font names records (1, 2, 4, 6, 16, 17) according to
|
|
968
969
|
the given family_name and style_name (subfamily_name).
|
|
970
|
+
The names records (18, 25), the fvar named instances PostScript names
|
|
971
|
+
and the CFF table names are updated too, only if already defined.
|
|
969
972
|
|
|
970
973
|
If family_name is not defined it will be auto-detected.
|
|
971
974
|
If style_name is not defined it will be auto-detected.
|
|
@@ -985,6 +988,39 @@ font.rename(
|
|
|
985
988
|
)
|
|
986
989
|
```
|
|
987
990
|
|
|
991
|
+
#### `rename_variable_instances`
|
|
992
|
+
```python
|
|
993
|
+
"""
|
|
994
|
+
Renames the named instances of the variable font.
|
|
995
|
+
|
|
996
|
+
The family_name replaces the family part of the instances PostScript names
|
|
997
|
+
(and of the variations PostScript name prefix, if defined).
|
|
998
|
+
The style_names replace the instances style names and the style part
|
|
999
|
+
of their PostScript names. The axes names are unchanged.
|
|
1000
|
+
The default instance style name must match the font style name,
|
|
1001
|
+
so renaming it renames the font style too.
|
|
1002
|
+
|
|
1003
|
+
If family_name is not defined it will be auto-detected.
|
|
1004
|
+
|
|
1005
|
+
:param family_name: The family name
|
|
1006
|
+
:type family_name: str
|
|
1007
|
+
:param style_names: The new style names mapped by the old ones (case-insensitive),
|
|
1008
|
+
eg. {"Thin": "Hairline"}
|
|
1009
|
+
:type style_names: dict or None
|
|
1010
|
+
:param update_style_flags: if True the style flags will be updated by subfamily name
|
|
1011
|
+
(only if the default instance style name is renamed)
|
|
1012
|
+
:type update_style_flags: bool
|
|
1013
|
+
|
|
1014
|
+
:raises OperationError: if the font is not a variable font.
|
|
1015
|
+
"""
|
|
1016
|
+
|
|
1017
|
+
font.rename_variable_instances(
|
|
1018
|
+
family_name="My Font New",
|
|
1019
|
+
style_names={"Thin": "Hairline"},
|
|
1020
|
+
update_style_flags=True,
|
|
1021
|
+
)
|
|
1022
|
+
```
|
|
1023
|
+
|
|
988
1024
|
#### `sanitize`
|
|
989
1025
|
```python
|
|
990
1026
|
"""
|
|
@@ -1360,6 +1396,12 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1360
1396
|
{'wdth':100, 'wght':[100,600], 'ital':[30,65,70]} or
|
|
1361
1397
|
{'wdth':100, 'wght':{'min':100,'max':600}, 'ital':{'min':30,'default':65,'max':70}}
|
|
1362
1398
|
:type coordinates: dict
|
|
1399
|
+
:param update_names: if True the name records will be updated based on
|
|
1400
|
+
the closest instance to the new default coordinates (only if changed)
|
|
1401
|
+
:type update_names: bool
|
|
1402
|
+
:param update_style_flags: if True the style flags will be updated based on
|
|
1403
|
+
the closest instance to the new default coordinates (only if changed)
|
|
1404
|
+
:type update_style_flags: bool
|
|
1363
1405
|
:param options: The options for the fontTools.varLib.instancer
|
|
1364
1406
|
:type options: dictionary
|
|
1365
1407
|
|
|
@@ -1368,7 +1410,12 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1368
1410
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1369
1411
|
"""
|
|
1370
1412
|
|
|
1371
|
-
font.to_sliced_variable(
|
|
1413
|
+
font.to_sliced_variable(
|
|
1414
|
+
coordinates,
|
|
1415
|
+
update_names=True,
|
|
1416
|
+
update_style_flags=True,
|
|
1417
|
+
**options,
|
|
1418
|
+
)
|
|
1372
1419
|
```
|
|
1373
1420
|
|
|
1374
1421
|
#### `to_static`
|
|
@@ -85,6 +85,7 @@ with open("fonts/MyFont.ttf") as fh:
|
|
|
85
85
|
- [`is_static`](#is_static)
|
|
86
86
|
- [`is_variable`](#is_variable)
|
|
87
87
|
- [`rename`](#rename)
|
|
88
|
+
- [`rename_variable_instances`](#rename_variable_instances)
|
|
88
89
|
- [`sanitize`](#sanitize)
|
|
89
90
|
- [`save`](#save)
|
|
90
91
|
- [`save_as_woff`](#save_as_woff)
|
|
@@ -902,6 +903,8 @@ variable = font.is_variable()
|
|
|
902
903
|
"""
|
|
903
904
|
Renames the font names records (1, 2, 4, 6, 16, 17) according to
|
|
904
905
|
the given family_name and style_name (subfamily_name).
|
|
906
|
+
The names records (18, 25), the fvar named instances PostScript names
|
|
907
|
+
and the CFF table names are updated too, only if already defined.
|
|
905
908
|
|
|
906
909
|
If family_name is not defined it will be auto-detected.
|
|
907
910
|
If style_name is not defined it will be auto-detected.
|
|
@@ -921,6 +924,39 @@ font.rename(
|
|
|
921
924
|
)
|
|
922
925
|
```
|
|
923
926
|
|
|
927
|
+
#### `rename_variable_instances`
|
|
928
|
+
```python
|
|
929
|
+
"""
|
|
930
|
+
Renames the named instances of the variable font.
|
|
931
|
+
|
|
932
|
+
The family_name replaces the family part of the instances PostScript names
|
|
933
|
+
(and of the variations PostScript name prefix, if defined).
|
|
934
|
+
The style_names replace the instances style names and the style part
|
|
935
|
+
of their PostScript names. The axes names are unchanged.
|
|
936
|
+
The default instance style name must match the font style name,
|
|
937
|
+
so renaming it renames the font style too.
|
|
938
|
+
|
|
939
|
+
If family_name is not defined it will be auto-detected.
|
|
940
|
+
|
|
941
|
+
:param family_name: The family name
|
|
942
|
+
:type family_name: str
|
|
943
|
+
:param style_names: The new style names mapped by the old ones (case-insensitive),
|
|
944
|
+
eg. {"Thin": "Hairline"}
|
|
945
|
+
:type style_names: dict or None
|
|
946
|
+
:param update_style_flags: if True the style flags will be updated by subfamily name
|
|
947
|
+
(only if the default instance style name is renamed)
|
|
948
|
+
:type update_style_flags: bool
|
|
949
|
+
|
|
950
|
+
:raises OperationError: if the font is not a variable font.
|
|
951
|
+
"""
|
|
952
|
+
|
|
953
|
+
font.rename_variable_instances(
|
|
954
|
+
family_name="My Font New",
|
|
955
|
+
style_names={"Thin": "Hairline"},
|
|
956
|
+
update_style_flags=True,
|
|
957
|
+
)
|
|
958
|
+
```
|
|
959
|
+
|
|
924
960
|
#### `sanitize`
|
|
925
961
|
```python
|
|
926
962
|
"""
|
|
@@ -1296,6 +1332,12 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1296
1332
|
{'wdth':100, 'wght':[100,600], 'ital':[30,65,70]} or
|
|
1297
1333
|
{'wdth':100, 'wght':{'min':100,'max':600}, 'ital':{'min':30,'default':65,'max':70}}
|
|
1298
1334
|
:type coordinates: dict
|
|
1335
|
+
:param update_names: if True the name records will be updated based on
|
|
1336
|
+
the closest instance to the new default coordinates (only if changed)
|
|
1337
|
+
:type update_names: bool
|
|
1338
|
+
:param update_style_flags: if True the style flags will be updated based on
|
|
1339
|
+
the closest instance to the new default coordinates (only if changed)
|
|
1340
|
+
:type update_style_flags: bool
|
|
1299
1341
|
:param options: The options for the fontTools.varLib.instancer
|
|
1300
1342
|
:type options: dictionary
|
|
1301
1343
|
|
|
@@ -1304,7 +1346,12 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1304
1346
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1305
1347
|
"""
|
|
1306
1348
|
|
|
1307
|
-
font.to_sliced_variable(
|
|
1349
|
+
font.to_sliced_variable(
|
|
1350
|
+
coordinates,
|
|
1351
|
+
update_names=True,
|
|
1352
|
+
update_style_flags=True,
|
|
1353
|
+
**options,
|
|
1354
|
+
)
|
|
1308
1355
|
```
|
|
1309
1356
|
|
|
1310
1357
|
#### `to_static`
|
|
@@ -1227,6 +1227,8 @@ class Font:
|
|
|
1227
1227
|
"""
|
|
1228
1228
|
Renames the font names records (1, 2, 4, 6, 16, 17) according to
|
|
1229
1229
|
the given family_name and style_name (subfamily_name).
|
|
1230
|
+
The names records (18, 25), the fvar named instances PostScript names
|
|
1231
|
+
and the CFF table names are updated too, only if already defined.
|
|
1230
1232
|
|
|
1231
1233
|
If family_name is not defined it will be auto-detected.
|
|
1232
1234
|
If style_name is not defined it will be auto-detected.
|
|
@@ -1247,6 +1249,48 @@ class Font:
|
|
|
1247
1249
|
if update_style_flags:
|
|
1248
1250
|
self.set_style_flags_by_subfamily_name()
|
|
1249
1251
|
|
|
1252
|
+
def rename_variable_instances(
|
|
1253
|
+
self,
|
|
1254
|
+
*,
|
|
1255
|
+
family_name: str = "",
|
|
1256
|
+
style_names: dict[str, str] | None = None,
|
|
1257
|
+
update_style_flags: bool = True,
|
|
1258
|
+
) -> None:
|
|
1259
|
+
"""
|
|
1260
|
+
Renames the named instances of the variable font.
|
|
1261
|
+
|
|
1262
|
+
The family_name replaces the family part of the instances PostScript names
|
|
1263
|
+
(and of the variations PostScript name prefix, if defined).
|
|
1264
|
+
The style_names replace the instances style names and the style part
|
|
1265
|
+
of their PostScript names. The axes names are unchanged.
|
|
1266
|
+
The default instance style name must match the font style name,
|
|
1267
|
+
so renaming it renames the font style too.
|
|
1268
|
+
|
|
1269
|
+
If family_name is not defined it will be auto-detected.
|
|
1270
|
+
|
|
1271
|
+
:param family_name: The family name
|
|
1272
|
+
:type family_name: str
|
|
1273
|
+
:param style_names: The new style names mapped by the old ones (case-insensitive),
|
|
1274
|
+
eg. {"Thin": "Hairline"}
|
|
1275
|
+
:type style_names: dict or None
|
|
1276
|
+
:param update_style_flags: if True the style flags will be updated by subfamily name
|
|
1277
|
+
(only if the default instance style name is renamed)
|
|
1278
|
+
:type update_style_flags: bool
|
|
1279
|
+
|
|
1280
|
+
:raises OperationError: if the font is not a variable font.
|
|
1281
|
+
:return: None
|
|
1282
|
+
"""
|
|
1283
|
+
ttfont = self.get_ttfont()
|
|
1284
|
+
style_name = self.get_style_name()
|
|
1285
|
+
_names.rename_variable_instances(
|
|
1286
|
+
ttfont,
|
|
1287
|
+
family_name=family_name,
|
|
1288
|
+
style_names=style_names,
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
if update_style_flags and self.get_style_name() != style_name:
|
|
1292
|
+
self.set_style_flags_by_subfamily_name()
|
|
1293
|
+
|
|
1250
1294
|
def sanitize(
|
|
1251
1295
|
self,
|
|
1252
1296
|
*,
|
|
@@ -1791,6 +1835,8 @@ class Font:
|
|
|
1791
1835
|
self,
|
|
1792
1836
|
*,
|
|
1793
1837
|
coordinates: dict[str, Any],
|
|
1838
|
+
update_names: bool = True,
|
|
1839
|
+
update_style_flags: bool = True,
|
|
1794
1840
|
**options: Any,
|
|
1795
1841
|
) -> None:
|
|
1796
1842
|
"""
|
|
@@ -1805,6 +1851,12 @@ class Font:
|
|
|
1805
1851
|
{'wdth':100, 'wght':[100,600], 'ital':[30,65,70]} or
|
|
1806
1852
|
{'wdth':100, 'wght':{'min':100,'max':600}, 'ital':{'min':30,'default':65,'max':70}}
|
|
1807
1853
|
:type coordinates: dict
|
|
1854
|
+
:param update_names: if True the name records will be updated based on
|
|
1855
|
+
the closest instance to the new default coordinates (only if changed)
|
|
1856
|
+
:type update_names: bool
|
|
1857
|
+
:param update_style_flags: if True the style flags will be updated based on
|
|
1858
|
+
the closest instance to the new default coordinates (only if changed)
|
|
1859
|
+
:type update_style_flags: bool
|
|
1808
1860
|
:param options: The options for the fontTools.varLib.instancer
|
|
1809
1861
|
:type options: dictionary
|
|
1810
1862
|
|
|
@@ -1813,7 +1865,13 @@ class Font:
|
|
|
1813
1865
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1814
1866
|
"""
|
|
1815
1867
|
ttfont = self.get_ttfont()
|
|
1816
|
-
_variable.to_sliced_variable(
|
|
1868
|
+
_variable.to_sliced_variable(
|
|
1869
|
+
ttfont,
|
|
1870
|
+
coordinates=coordinates,
|
|
1871
|
+
update_names=update_names,
|
|
1872
|
+
update_style_flags=update_style_flags,
|
|
1873
|
+
**options,
|
|
1874
|
+
)
|
|
1817
1875
|
|
|
1818
1876
|
def to_static(
|
|
1819
1877
|
self,
|
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
from fontTools.misc.encodingTools import getEncoding
|
|
7
7
|
from fontTools.ttLib import TTFont
|
|
8
8
|
|
|
9
|
-
from fontbro.exceptions import ArgumentError
|
|
9
|
+
from fontbro.exceptions import ArgumentError, OperationError
|
|
10
10
|
from fontbro.utils import concat_names, remove_spaces
|
|
11
11
|
|
|
12
12
|
# Names:
|
|
@@ -182,26 +182,41 @@ def set_name(
|
|
|
182
182
|
ttfont: TTFont,
|
|
183
183
|
key: int | str,
|
|
184
184
|
value: str,
|
|
185
|
+
*,
|
|
186
|
+
windows: bool = True,
|
|
187
|
+
mac: bool = True,
|
|
188
|
+
unicode: bool = True,
|
|
185
189
|
) -> None:
|
|
186
190
|
"""
|
|
187
|
-
Sets the name by its identifier in the name table of the given font
|
|
191
|
+
Sets the name by its identifier in the name table of the given font,
|
|
192
|
+
only in the records of the platforms enabled by the windows, mac
|
|
193
|
+
and unicode flags.
|
|
188
194
|
"""
|
|
189
195
|
name_id = _get_name_id(key)
|
|
190
196
|
name_table = ttfont["name"]
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
if windows:
|
|
198
|
+
# https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/ttLib/tables/_n_a_m_e.py#L568
|
|
199
|
+
# the windows record is always written, it's the one read first by get_name
|
|
200
|
+
name_table.setName(value, name_id, **_NAMES_WIN_IDS)
|
|
194
201
|
# the other records of the same name id are updated only if the font
|
|
195
202
|
# already has them (mac and unicode records are legacy, modern tools don't
|
|
196
203
|
# produce them anymore, so they are never added): a record left with its
|
|
197
204
|
# previous value would contradict the record just written
|
|
198
|
-
|
|
205
|
+
platforms = [
|
|
206
|
+
platform_ids
|
|
207
|
+
for platform_ids, enabled in (
|
|
208
|
+
(_NAMES_WIN_IDS, windows),
|
|
209
|
+
(_NAMES_UNI_IDS, unicode),
|
|
210
|
+
(_NAMES_MAC_IDS, mac),
|
|
211
|
+
)
|
|
212
|
+
if enabled
|
|
213
|
+
]
|
|
214
|
+
for platform_ids in platforms:
|
|
199
215
|
for record_ids in _get_name_records_ids(name_table, name_id, platform_ids):
|
|
200
216
|
# the value is written only if the record encoding can encode it
|
|
201
217
|
# (eg. mac roman can't encode greek, cyrillic, cjk), otherwise the
|
|
202
218
|
# record is removed (as fontTools addMultilingualName does),
|
|
203
|
-
# because the font would raise UnicodeEncodeError when saved
|
|
204
|
-
# removing it is safe because the windows record has just been written
|
|
219
|
+
# because the font would raise UnicodeEncodeError when saved
|
|
205
220
|
if _is_encodable(value, record_ids):
|
|
206
221
|
name_table.setName(value, name_id, **record_ids)
|
|
207
222
|
else:
|
|
@@ -249,6 +264,185 @@ def get_style_name(
|
|
|
249
264
|
)
|
|
250
265
|
|
|
251
266
|
|
|
267
|
+
def _get_postscript_name(
|
|
268
|
+
value: str,
|
|
269
|
+
) -> str:
|
|
270
|
+
"""
|
|
271
|
+
Gets a PostScript name from the given value,
|
|
272
|
+
replacing the characters not allowed with hyphens.
|
|
273
|
+
"""
|
|
274
|
+
# keep only printable ASCII subset:
|
|
275
|
+
# https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids
|
|
276
|
+
# postscript_name_allowed_chars = {chr(code) for code in range(33, 127)}
|
|
277
|
+
# !"#$&'*+,-.0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\^_`abcdefghijklmnopqrstuvwxyz|~
|
|
278
|
+
postscript_name_pattern = (
|
|
279
|
+
r"[^0-9A-Za-z\!\"\#\$\&\'\*\+\,\-\.\:\;\=\?\@\\\^\_\`\|\~]"
|
|
280
|
+
)
|
|
281
|
+
postscript_name = re.sub(postscript_name_pattern, "-", value)
|
|
282
|
+
postscript_name = re.sub(r"[\-]+", "-", postscript_name).strip("-")
|
|
283
|
+
return postscript_name
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _get_default_instance_style_name(
|
|
287
|
+
ttfont: TTFont,
|
|
288
|
+
) -> str:
|
|
289
|
+
"""
|
|
290
|
+
Gets the style name of the fvar named instance at the default coordinates.
|
|
291
|
+
"""
|
|
292
|
+
fvar = ttfont["fvar"]
|
|
293
|
+
coordinates = {axis.axisTag: axis.defaultValue for axis in fvar.axes}
|
|
294
|
+
for instance in fvar.instances:
|
|
295
|
+
if instance.coordinates == coordinates:
|
|
296
|
+
return get_name(ttfont, instance.subfamilyNameID) or ""
|
|
297
|
+
return ""
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def rename_variable_instances(
|
|
301
|
+
ttfont: TTFont,
|
|
302
|
+
*,
|
|
303
|
+
family_name: str = "",
|
|
304
|
+
style_names: dict[str, str] | None = None,
|
|
305
|
+
) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Renames the named instances of the given variable font: the family_name
|
|
308
|
+
replaces the family part of the instances PostScript names (and of the
|
|
309
|
+
variations PostScript name prefix, 25), the style_names (old style name
|
|
310
|
+
to new style name) replace the instances style names and the style part
|
|
311
|
+
of their PostScript names. The axes names are unchanged.
|
|
312
|
+
The default instance style name must match the font style name,
|
|
313
|
+
so renaming it renames the font style too.
|
|
314
|
+
"""
|
|
315
|
+
if "fvar" not in ttfont:
|
|
316
|
+
raise OperationError("Only the instances of a variable font can be renamed.")
|
|
317
|
+
# style names are matched case-insensitively
|
|
318
|
+
style_names = {key.lower(): value for key, value in (style_names or {}).items()}
|
|
319
|
+
default_style_name = _get_default_instance_style_name(ttfont).lower()
|
|
320
|
+
default_style_name_new = style_names.get(default_style_name, "")
|
|
321
|
+
if default_style_name_new:
|
|
322
|
+
rename(ttfont, style_name=default_style_name_new)
|
|
323
|
+
family_name = (family_name or "").strip() or get_family_name(ttfont)
|
|
324
|
+
_rename_variable_instances(ttfont, family_name, style_names)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _get_instance_postscript_name(
|
|
328
|
+
prefix: str,
|
|
329
|
+
style_name: str,
|
|
330
|
+
) -> str:
|
|
331
|
+
"""
|
|
332
|
+
Gets the PostScript name of a variable font named instance, the "Italic"
|
|
333
|
+
style suffix is omitted if the prefix already ends with it
|
|
334
|
+
(eg. "TourneyItalic-Thin" instead of "TourneyItalic-ThinItalic").
|
|
335
|
+
"""
|
|
336
|
+
style_name = remove_spaces(style_name)
|
|
337
|
+
if prefix.lower().endswith("italic"):
|
|
338
|
+
style_name = re.sub(r"italic$", "", style_name, flags=re.IGNORECASE)
|
|
339
|
+
style_name = style_name or "Regular"
|
|
340
|
+
return _get_postscript_name(f"{prefix}-{style_name}")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _rename_variable_instances(
|
|
344
|
+
ttfont: TTFont,
|
|
345
|
+
family_name: str,
|
|
346
|
+
style_names: dict[str, str],
|
|
347
|
+
) -> None:
|
|
348
|
+
"""
|
|
349
|
+
Renames the named instances of the given variable font,
|
|
350
|
+
the style_names keys must be lowercase.
|
|
351
|
+
"""
|
|
352
|
+
name_table = ttfont["name"]
|
|
353
|
+
|
|
354
|
+
# the old family name is read before updating it (rename calls this function
|
|
355
|
+
# before updating the name records), the old prefixes are computed from it
|
|
356
|
+
family_name_old = get_family_name(ttfont)
|
|
357
|
+
prefix_old = get_name(ttfont, NAME_VARIATIONS_POSTSCRIPT_NAME_PREFIX) or ""
|
|
358
|
+
if prefix_old:
|
|
359
|
+
# variations postscript name prefix: only ASCII letters and digits allowed,
|
|
360
|
+
# its suffix is kept (eg. "Roman", "Italic") to avoid the same instances
|
|
361
|
+
# postscript names in the roman and italic fonts
|
|
362
|
+
suffix = ""
|
|
363
|
+
family_name_old_prefix = re.sub(r"[^0-9A-Za-z]", "", family_name_old)
|
|
364
|
+
if family_name_old_prefix and prefix_old.startswith(family_name_old_prefix):
|
|
365
|
+
suffix = prefix_old[len(family_name_old_prefix) :]
|
|
366
|
+
elif "italic" in get_style_name(ttfont).lower() and not (
|
|
367
|
+
family_name.lower().endswith("italic")
|
|
368
|
+
):
|
|
369
|
+
suffix = "Italic"
|
|
370
|
+
prefix = re.sub(r"[^0-9A-Za-z]", "", f"{family_name}{suffix}")
|
|
371
|
+
# the windows record is written only if the font already has it
|
|
372
|
+
name_id = _get_name_id(NAME_VARIATIONS_POSTSCRIPT_NAME_PREFIX)
|
|
373
|
+
windows = bool(name_table.getName(name_id, **_NAMES_WIN_IDS))
|
|
374
|
+
set_name(
|
|
375
|
+
ttfont, NAME_VARIATIONS_POSTSCRIPT_NAME_PREFIX, prefix, windows=windows
|
|
376
|
+
)
|
|
377
|
+
else:
|
|
378
|
+
prefix_old = _get_postscript_name(remove_spaces(family_name_old))
|
|
379
|
+
prefix = _get_postscript_name(remove_spaces(family_name))
|
|
380
|
+
|
|
381
|
+
# the style names are read before updating them,
|
|
382
|
+
# because the same name record can be shared by more instances
|
|
383
|
+
instances = ttfont["fvar"].instances
|
|
384
|
+
instances_style_names = [
|
|
385
|
+
get_name(ttfont, instance.subfamilyNameID) or "" for instance in instances
|
|
386
|
+
]
|
|
387
|
+
for instance, style_name in zip(instances, instances_style_names, strict=True):
|
|
388
|
+
style_name_new = style_names.get(style_name.lower(), "")
|
|
389
|
+
if style_name_new and instance.subfamilyNameID < 256:
|
|
390
|
+
# name ids 2 and 17 are the font style names (updated by rename),
|
|
391
|
+
# so a new name record is added for the instance
|
|
392
|
+
instance.subfamilyNameID = name_table.addName(
|
|
393
|
+
style_name_new, platforms=((3, 1, 0x409),)
|
|
394
|
+
)
|
|
395
|
+
elif style_name_new:
|
|
396
|
+
# the existing name record is updated, also where it is shared
|
|
397
|
+
# (eg. the STAT table axis values), the windows record is written
|
|
398
|
+
# only if the font already has it
|
|
399
|
+
name_id = instance.subfamilyNameID
|
|
400
|
+
windows = bool(name_table.getName(name_id, **_NAMES_WIN_IDS))
|
|
401
|
+
set_name(ttfont, name_id, style_name_new, windows=windows)
|
|
402
|
+
name_id = instance.postscriptNameID
|
|
403
|
+
name = get_name(ttfont, name_id) if 255 < name_id < 0xFFFF else None
|
|
404
|
+
if not name:
|
|
405
|
+
continue
|
|
406
|
+
if not style_name_new and prefix_old and name.startswith(prefix_old):
|
|
407
|
+
# only the prefix is replaced, to keep the font naming convention
|
|
408
|
+
name = prefix + name[len(prefix_old) :]
|
|
409
|
+
else:
|
|
410
|
+
# the name is generated if the style is renamed,
|
|
411
|
+
# or if it doesn't start with the old prefix (inconsistent font)
|
|
412
|
+
name = _get_instance_postscript_name(prefix, style_name_new or style_name)
|
|
413
|
+
set_name(ttfont, name_id, name)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _rename_cff(
|
|
417
|
+
ttfont: TTFont,
|
|
418
|
+
postscript_name: str,
|
|
419
|
+
full_name: str,
|
|
420
|
+
family_name: str,
|
|
421
|
+
weight: str,
|
|
422
|
+
) -> None:
|
|
423
|
+
"""
|
|
424
|
+
Renames the CFF table names (otf fonts): the font name and the FDArray
|
|
425
|
+
font names (CID-keyed fonts) prefixed by it, FullName, FamilyName and
|
|
426
|
+
Weight (only if not empty) only if already defined.
|
|
427
|
+
"""
|
|
428
|
+
if "CFF " not in ttfont:
|
|
429
|
+
return
|
|
430
|
+
cff = ttfont["CFF "].cff
|
|
431
|
+
postscript_name_old = cff.fontNames[0]
|
|
432
|
+
cff.fontNames[0] = postscript_name
|
|
433
|
+
top_dict = cff.topDictIndex[0]
|
|
434
|
+
for font_dict in getattr(top_dict, "FDArray", []):
|
|
435
|
+
font_name = getattr(font_dict, "FontName", None)
|
|
436
|
+
if font_name and font_name.startswith(postscript_name_old):
|
|
437
|
+
font_dict.FontName = postscript_name + font_name[len(postscript_name_old) :]
|
|
438
|
+
if getattr(top_dict, "FullName", None):
|
|
439
|
+
top_dict.FullName = full_name
|
|
440
|
+
if getattr(top_dict, "FamilyName", None):
|
|
441
|
+
top_dict.FamilyName = family_name
|
|
442
|
+
if weight and getattr(top_dict, "Weight", None):
|
|
443
|
+
top_dict.Weight = weight
|
|
444
|
+
|
|
445
|
+
|
|
252
446
|
def rename(
|
|
253
447
|
ttfont: TTFont,
|
|
254
448
|
*,
|
|
@@ -259,6 +453,8 @@ def rename(
|
|
|
259
453
|
Renames the names records (1, 2, 3, 4, 6, 16, 17, 21, 22) of the given font
|
|
260
454
|
according to the given family_name and style_name (subfamily_name),
|
|
261
455
|
if not defined they will be auto-detected.
|
|
456
|
+
The records 18 and 25, the fvar instances PostScript names and the CFF
|
|
457
|
+
names are updated only if already defined.
|
|
262
458
|
"""
|
|
263
459
|
family_name = (family_name or "").strip() or get_family_name(ttfont)
|
|
264
460
|
style_name = (style_name or "").strip() or get_style_name(ttfont)
|
|
@@ -289,15 +485,7 @@ def rename(
|
|
|
289
485
|
remove_spaces(typographic_subfamily_name),
|
|
290
486
|
)
|
|
291
487
|
|
|
292
|
-
|
|
293
|
-
# https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids
|
|
294
|
-
# postscript_name_allowed_chars = {chr(code) for code in range(33, 127)}
|
|
295
|
-
# !"#$&'*+,-.0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\^_`abcdefghijklmnopqrstuvwxyz|~
|
|
296
|
-
postscript_name_pattern = (
|
|
297
|
-
r"[^0-9A-Za-z\!\"\#\$\&\'\*\+\,\-\.\:\;\=\?\@\\\^\_\`\|\~]"
|
|
298
|
-
)
|
|
299
|
-
postscript_name = re.sub(postscript_name_pattern, "-", postscript_name)
|
|
300
|
-
postscript_name = re.sub(r"[\-]+", "-", postscript_name).strip("-")
|
|
488
|
+
postscript_name = _get_postscript_name(postscript_name)
|
|
301
489
|
postscript_name_length = len(postscript_name)
|
|
302
490
|
if postscript_name_length > 63:
|
|
303
491
|
raise ArgumentError(
|
|
@@ -325,4 +513,52 @@ def rename(
|
|
|
325
513
|
NAME_WWS_FAMILY_NAME: wws_family_name,
|
|
326
514
|
NAME_WWS_SUBFAMILY_NAME: wws_subfamily_name,
|
|
327
515
|
}
|
|
516
|
+
|
|
517
|
+
# compatible full name: if it's not the full name (eg. a designer-defined
|
|
518
|
+
# short menu name), only the family name in it is replaced
|
|
519
|
+
compatible_full_name = get_name(ttfont, NAME_COMPATIBLE_FULL) or ""
|
|
520
|
+
if compatible_full_name:
|
|
521
|
+
family_name_old = get_family_name(ttfont)
|
|
522
|
+
full_name_old = concat_names(family_name_old, get_style_name(ttfont))
|
|
523
|
+
if (
|
|
524
|
+
compatible_full_name == full_name_old
|
|
525
|
+
or not family_name_old
|
|
526
|
+
or family_name_old not in compatible_full_name
|
|
527
|
+
):
|
|
528
|
+
compatible_full_name = full_name
|
|
529
|
+
else:
|
|
530
|
+
compatible_full_name = compatible_full_name.replace(
|
|
531
|
+
family_name_old, typographic_family_name, 1
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# cff weight (eg. "Bold" for "Bold Italic"), updated only if the style changes
|
|
535
|
+
style_name_old = get_style_name(ttfont)
|
|
536
|
+
weight = ""
|
|
537
|
+
if style_name_old != typographic_subfamily_name:
|
|
538
|
+
weight = re.sub(
|
|
539
|
+
r"\s*italic$", "", typographic_subfamily_name, flags=re.IGNORECASE
|
|
540
|
+
)
|
|
541
|
+
weight = weight or "Regular"
|
|
542
|
+
|
|
543
|
+
# fvar instances (and name id 25) need the old postscript name
|
|
544
|
+
if "fvar" in ttfont:
|
|
545
|
+
# the default instance style name must match the font style name, it's
|
|
546
|
+
# renamed only if it matches the old one (otherwise a family rename of
|
|
547
|
+
# an inconsistent font would rename it, and the STAT values sharing it)
|
|
548
|
+
style_names = {}
|
|
549
|
+
if (
|
|
550
|
+
style_name_old != typographic_subfamily_name
|
|
551
|
+
and _get_default_instance_style_name(ttfont) == style_name_old
|
|
552
|
+
):
|
|
553
|
+
style_names[style_name_old.lower()] = typographic_subfamily_name
|
|
554
|
+
_rename_variable_instances(ttfont, typographic_family_name, style_names)
|
|
555
|
+
|
|
328
556
|
set_names(ttfont, names)
|
|
557
|
+
# name id 18 (macintosh only) is updated only if already defined,
|
|
558
|
+
# the windows record is written only if the font already has it
|
|
559
|
+
if compatible_full_name:
|
|
560
|
+
name_id = _get_name_id(NAME_COMPATIBLE_FULL)
|
|
561
|
+
windows = bool(ttfont["name"].getName(name_id, **_NAMES_WIN_IDS))
|
|
562
|
+
set_name(ttfont, NAME_COMPATIBLE_FULL, compatible_full_name, windows=windows)
|
|
563
|
+
|
|
564
|
+
_rename_cff(ttfont, postscript_name, full_name, typographic_family_name, weight)
|
|
@@ -301,10 +301,18 @@ def _remove_stat_table(
|
|
|
301
301
|
return False
|
|
302
302
|
|
|
303
303
|
|
|
304
|
+
def _get_default_coordinates(
|
|
305
|
+
ttfont: TTFont,
|
|
306
|
+
) -> dict[str, float]:
|
|
307
|
+
return {axis.axisTag: axis.defaultValue for axis in ttfont["fvar"].axes}
|
|
308
|
+
|
|
309
|
+
|
|
304
310
|
def to_sliced_variable(
|
|
305
311
|
ttfont: TTFont,
|
|
306
312
|
*,
|
|
307
313
|
coordinates: dict[str, Any],
|
|
314
|
+
update_names: bool = True,
|
|
315
|
+
update_style_flags: bool = True,
|
|
308
316
|
**options: Any,
|
|
309
317
|
) -> None:
|
|
310
318
|
"""
|
|
@@ -322,8 +330,25 @@ def to_sliced_variable(
|
|
|
322
330
|
options.setdefault("updateFontNames", False)
|
|
323
331
|
|
|
324
332
|
# instantiate the sliced variable font
|
|
333
|
+
default_coordinates = _get_default_coordinates(ttfont)
|
|
325
334
|
instancer.instantiateVariableFont(ttfont, coordinates, inplace=True, **options)
|
|
326
335
|
|
|
336
|
+
# update name records and style flags if the default instance changed
|
|
337
|
+
# (unless they have been updated by fontTools using the STAT table)
|
|
338
|
+
default_coordinates_new = _get_default_coordinates(ttfont)
|
|
339
|
+
if (
|
|
340
|
+
update_names
|
|
341
|
+
and default_coordinates_new != default_coordinates
|
|
342
|
+
and not options["updateFontNames"]
|
|
343
|
+
):
|
|
344
|
+
instance = get_variable_instance_closest_to_coordinates(
|
|
345
|
+
ttfont, default_coordinates_new
|
|
346
|
+
)
|
|
347
|
+
if instance:
|
|
348
|
+
_names.rename(ttfont, style_name=instance["style_name"])
|
|
349
|
+
if update_style_flags:
|
|
350
|
+
_style_flags.set_style_flags_by_subfamily_name(ttfont)
|
|
351
|
+
|
|
327
352
|
|
|
328
353
|
def to_static(
|
|
329
354
|
ttfont: TTFont,
|
|
@@ -363,6 +388,13 @@ def to_static(
|
|
|
363
388
|
# not useful in static fonts and after instancing it may contain incorrect values
|
|
364
389
|
_remove_stat_table(ttfont)
|
|
365
390
|
|
|
391
|
+
# remove variations postscript name prefix (25), used only by variable fonts
|
|
392
|
+
# (as fontTools does when instancing with updateFontNames=True)
|
|
393
|
+
# and the names used only by the removed fvar and STAT tables
|
|
394
|
+
if "name" in ttfont:
|
|
395
|
+
ttfont["name"].removeNames(nameID=25)
|
|
396
|
+
ttfont["name"].removeUnusedNames(ttfont)
|
|
397
|
+
|
|
366
398
|
# update name records and style flags based on instance style name
|
|
367
399
|
if instance and update_names:
|
|
368
400
|
_names.rename(ttfont, style_name=instance["style_name"])
|