python-fontbro 0.28.1__tar.gz → 0.29.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.28.1/python_fontbro.egg-info → python_fontbro-0.29.0}/PKG-INFO +63 -4
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/README.md +62 -3
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/files.py +0 -11
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/font.py +74 -3
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/metadata.py +1 -1
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/names.py +46 -13
- python_fontbro-0.29.0/fontbro/version.py +253 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0/python_fontbro.egg-info}/PKG-INFO +63 -4
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/python_fontbro.egg-info/SOURCES.txt +1 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_names.py +93 -0
- python_fontbro-0.29.0/tests/test_version.py +409 -0
- python_fontbro-0.28.1/tests/test_version.py +0 -35
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/LICENSE.txt +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/MANIFEST.in +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/__init__.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/bitmap.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/color.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/data/family-classifications.json +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/data/features.json +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/data/unicode-blocks.json +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/data/unicode-scripts.json +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/embedding_permissions.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/exceptions.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/family_classification.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/features.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/fingerprint.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/flags.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/glyphs.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/math.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/metrics.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/monospace.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/pixel.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/py.typed +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/render.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/sanitize.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/style_flags.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/subset.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/support.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/tables.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/unicode.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/utils.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/fontbro/variable.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/pyproject.toml +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/python_fontbro.egg-info/requires.txt +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/python_fontbro.egg-info/top_level.txt +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/setup.cfg +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/setup.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_bitmap.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_characters.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_clone.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_close.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_collection.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_color.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_context_manager.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_embedding_permissions.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_family_classification.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_family_name.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_features.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_filename.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_fingerprint.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_format.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_glyphs.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_image.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_init.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_instantiation.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_issues.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_italic_angle.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_monospace.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_pixel.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_rename.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_sanitize.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_save.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_str.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_style_flags.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_style_name.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_subset.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_support.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_svg.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_tables.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_unicode.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_unicode_blocks_and_scripts.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_update_unicode_data.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_variable.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_vertical_metrics.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.0}/tests/test_weight.py +0 -0
- {python_fontbro-0.28.1 → python_fontbro-0.29.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.29.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>
|
|
@@ -138,6 +138,7 @@ with open("fonts/MyFont.ttf") as fh:
|
|
|
138
138
|
- [`get_variable_instance_by_style_name`](#get_variable_instance_by_style_name)
|
|
139
139
|
- [`get_variable_instance_closest_to_coordinates`](#get_variable_instance_closest_to_coordinates)
|
|
140
140
|
- [`get_version`](#get_version)
|
|
141
|
+
- [`get_version_formatted`](#get_version_formatted)
|
|
141
142
|
- [`get_vertical_metrics`](#get_vertical_metrics)
|
|
142
143
|
- [`get_weight`](#get_weight)
|
|
143
144
|
- [`get_width`](#get_width)
|
|
@@ -163,6 +164,7 @@ with open("fonts/MyFont.ttf") as fh:
|
|
|
163
164
|
- [`set_style_flags`](#set_style_flags)
|
|
164
165
|
- [`set_style_flags_by_subfamily_name`](#set_style_flags_by_subfamily_name)
|
|
165
166
|
- [`set_style_name`](#set_style_name)
|
|
167
|
+
- [`set_version`](#set_version)
|
|
166
168
|
- [`set_vertical_metrics`](#set_vertical_metrics)
|
|
167
169
|
- [`subset`](#subset)
|
|
168
170
|
- [`to_sliced_variable`](#to_sliced_variable)
|
|
@@ -784,13 +786,48 @@ instance = font.get_variable_instance_closest_to_coordinates(
|
|
|
784
786
|
#### `get_version`
|
|
785
787
|
```python
|
|
786
788
|
"""
|
|
787
|
-
Gets the font version.
|
|
789
|
+
Gets the font version reading it from head.fontRevision,
|
|
790
|
+
with fallback on the version name record (name id 5) parsed value,
|
|
791
|
+
eg. "Version 1.015;git-0a5106e0b" -> 1.015.
|
|
788
792
|
|
|
789
|
-
:
|
|
793
|
+
:param use_head_revision: If True, the head.fontRevision value is used (if set).
|
|
794
|
+
:type use_head_revision: bool
|
|
795
|
+
:param use_name_record: If True, the version name record value is used as fallback.
|
|
796
|
+
:type use_name_record: bool
|
|
797
|
+
|
|
798
|
+
:returns: The font version value, 0.0 if the version cannot be read.
|
|
790
799
|
:rtype: float
|
|
800
|
+
|
|
801
|
+
:raises ArgumentError: If both options are False.
|
|
791
802
|
"""
|
|
792
803
|
|
|
793
|
-
version = font.get_version()
|
|
804
|
+
version = font.get_version(use_head_revision=True, use_name_record=True)
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
#### `get_version_formatted`
|
|
808
|
+
```python
|
|
809
|
+
"""
|
|
810
|
+
Gets the font version formatted using the version name record convention,
|
|
811
|
+
with the given prefix and a minimum of 3 decimal digits, eg. "v1.015".
|
|
812
|
+
|
|
813
|
+
:param use_head_revision: If True, the head.fontRevision value is used (if set).
|
|
814
|
+
:type use_head_revision: bool
|
|
815
|
+
:param use_name_record: If True, the version name record value is used as fallback.
|
|
816
|
+
:type use_name_record: bool
|
|
817
|
+
:param prefix: The prefix to prepend to the version value.
|
|
818
|
+
:type prefix: str
|
|
819
|
+
|
|
820
|
+
:returns: The formatted font version value, eg. "v0.000" if the version cannot be read.
|
|
821
|
+
:rtype: str
|
|
822
|
+
|
|
823
|
+
:raises ArgumentError: If both source options are False.
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
version = font.get_version_formatted(
|
|
827
|
+
use_head_revision=True,
|
|
828
|
+
use_name_record=True,
|
|
829
|
+
prefix="v",
|
|
830
|
+
)
|
|
794
831
|
```
|
|
795
832
|
|
|
796
833
|
#### `get_vertical_metrics`
|
|
@@ -1240,6 +1277,28 @@ Sets the style name updating the related font names records.
|
|
|
1240
1277
|
font.set_style_name(name="Bold Italic")
|
|
1241
1278
|
```
|
|
1242
1279
|
|
|
1280
|
+
#### `set_version`
|
|
1281
|
+
```python
|
|
1282
|
+
"""
|
|
1283
|
+
Sets the font version, the version value can be a number (eg. 1.015)
|
|
1284
|
+
or a version string (eg. "Version 1.015"), parsed the same way the
|
|
1285
|
+
version name record value is parsed when read.
|
|
1286
|
+
The head.fontRevision and the version name record (name id 5) are
|
|
1287
|
+
updated, the latter is overwritten with the canonical "Version X.YYY"
|
|
1288
|
+
form (any extra info it contained is dropped).
|
|
1289
|
+
The unique identifier name record (name id 3) is updated only if its
|
|
1290
|
+
first ";" separated part holds the current version value, and only in
|
|
1291
|
+
that part. The CFF version is updated only if the font already has it.
|
|
1292
|
+
|
|
1293
|
+
:param version: The version value, eg. 1.015 or "Version 1.015"
|
|
1294
|
+
:type version: float or str
|
|
1295
|
+
|
|
1296
|
+
:raises ArgumentError: If the version value is not valid.
|
|
1297
|
+
"""
|
|
1298
|
+
|
|
1299
|
+
font.set_version(1.015)
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1243
1302
|
#### `set_vertical_metrics`
|
|
1244
1303
|
```python
|
|
1245
1304
|
"""
|
|
@@ -74,6 +74,7 @@ with open("fonts/MyFont.ttf") as fh:
|
|
|
74
74
|
- [`get_variable_instance_by_style_name`](#get_variable_instance_by_style_name)
|
|
75
75
|
- [`get_variable_instance_closest_to_coordinates`](#get_variable_instance_closest_to_coordinates)
|
|
76
76
|
- [`get_version`](#get_version)
|
|
77
|
+
- [`get_version_formatted`](#get_version_formatted)
|
|
77
78
|
- [`get_vertical_metrics`](#get_vertical_metrics)
|
|
78
79
|
- [`get_weight`](#get_weight)
|
|
79
80
|
- [`get_width`](#get_width)
|
|
@@ -99,6 +100,7 @@ with open("fonts/MyFont.ttf") as fh:
|
|
|
99
100
|
- [`set_style_flags`](#set_style_flags)
|
|
100
101
|
- [`set_style_flags_by_subfamily_name`](#set_style_flags_by_subfamily_name)
|
|
101
102
|
- [`set_style_name`](#set_style_name)
|
|
103
|
+
- [`set_version`](#set_version)
|
|
102
104
|
- [`set_vertical_metrics`](#set_vertical_metrics)
|
|
103
105
|
- [`subset`](#subset)
|
|
104
106
|
- [`to_sliced_variable`](#to_sliced_variable)
|
|
@@ -720,13 +722,48 @@ instance = font.get_variable_instance_closest_to_coordinates(
|
|
|
720
722
|
#### `get_version`
|
|
721
723
|
```python
|
|
722
724
|
"""
|
|
723
|
-
Gets the font version.
|
|
725
|
+
Gets the font version reading it from head.fontRevision,
|
|
726
|
+
with fallback on the version name record (name id 5) parsed value,
|
|
727
|
+
eg. "Version 1.015;git-0a5106e0b" -> 1.015.
|
|
724
728
|
|
|
725
|
-
:
|
|
729
|
+
:param use_head_revision: If True, the head.fontRevision value is used (if set).
|
|
730
|
+
:type use_head_revision: bool
|
|
731
|
+
:param use_name_record: If True, the version name record value is used as fallback.
|
|
732
|
+
:type use_name_record: bool
|
|
733
|
+
|
|
734
|
+
:returns: The font version value, 0.0 if the version cannot be read.
|
|
726
735
|
:rtype: float
|
|
736
|
+
|
|
737
|
+
:raises ArgumentError: If both options are False.
|
|
727
738
|
"""
|
|
728
739
|
|
|
729
|
-
version = font.get_version()
|
|
740
|
+
version = font.get_version(use_head_revision=True, use_name_record=True)
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
#### `get_version_formatted`
|
|
744
|
+
```python
|
|
745
|
+
"""
|
|
746
|
+
Gets the font version formatted using the version name record convention,
|
|
747
|
+
with the given prefix and a minimum of 3 decimal digits, eg. "v1.015".
|
|
748
|
+
|
|
749
|
+
:param use_head_revision: If True, the head.fontRevision value is used (if set).
|
|
750
|
+
:type use_head_revision: bool
|
|
751
|
+
:param use_name_record: If True, the version name record value is used as fallback.
|
|
752
|
+
:type use_name_record: bool
|
|
753
|
+
:param prefix: The prefix to prepend to the version value.
|
|
754
|
+
:type prefix: str
|
|
755
|
+
|
|
756
|
+
:returns: The formatted font version value, eg. "v0.000" if the version cannot be read.
|
|
757
|
+
:rtype: str
|
|
758
|
+
|
|
759
|
+
:raises ArgumentError: If both source options are False.
|
|
760
|
+
"""
|
|
761
|
+
|
|
762
|
+
version = font.get_version_formatted(
|
|
763
|
+
use_head_revision=True,
|
|
764
|
+
use_name_record=True,
|
|
765
|
+
prefix="v",
|
|
766
|
+
)
|
|
730
767
|
```
|
|
731
768
|
|
|
732
769
|
#### `get_vertical_metrics`
|
|
@@ -1176,6 +1213,28 @@ Sets the style name updating the related font names records.
|
|
|
1176
1213
|
font.set_style_name(name="Bold Italic")
|
|
1177
1214
|
```
|
|
1178
1215
|
|
|
1216
|
+
#### `set_version`
|
|
1217
|
+
```python
|
|
1218
|
+
"""
|
|
1219
|
+
Sets the font version, the version value can be a number (eg. 1.015)
|
|
1220
|
+
or a version string (eg. "Version 1.015"), parsed the same way the
|
|
1221
|
+
version name record value is parsed when read.
|
|
1222
|
+
The head.fontRevision and the version name record (name id 5) are
|
|
1223
|
+
updated, the latter is overwritten with the canonical "Version X.YYY"
|
|
1224
|
+
form (any extra info it contained is dropped).
|
|
1225
|
+
The unique identifier name record (name id 3) is updated only if its
|
|
1226
|
+
first ";" separated part holds the current version value, and only in
|
|
1227
|
+
that part. The CFF version is updated only if the font already has it.
|
|
1228
|
+
|
|
1229
|
+
:param version: The version value, eg. 1.015 or "Version 1.015"
|
|
1230
|
+
:type version: float or str
|
|
1231
|
+
|
|
1232
|
+
:raises ArgumentError: If the version value is not valid.
|
|
1233
|
+
"""
|
|
1234
|
+
|
|
1235
|
+
font.set_version(1.015)
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1179
1238
|
#### `set_vertical_metrics`
|
|
1180
1239
|
```python
|
|
1181
1240
|
"""
|
|
@@ -93,14 +93,3 @@ def get_filename(
|
|
|
93
93
|
extension = get_format(ttfont)
|
|
94
94
|
filename = f"{basename}.{extension}"
|
|
95
95
|
return filename
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def get_version(
|
|
99
|
-
ttfont: TTFont,
|
|
100
|
-
) -> float:
|
|
101
|
-
"""
|
|
102
|
-
Gets the version of the given font (head.fontRevision).
|
|
103
|
-
"""
|
|
104
|
-
head = ttfont.get("head")
|
|
105
|
-
version = float(head.fontRevision)
|
|
106
|
-
return version
|
|
@@ -29,6 +29,7 @@ from fontbro import support as _support
|
|
|
29
29
|
from fontbro import tables as _tables
|
|
30
30
|
from fontbro import unicode as _unicode
|
|
31
31
|
from fontbro import variable as _variable
|
|
32
|
+
from fontbro import version as _version
|
|
32
33
|
from fontbro.exceptions import (
|
|
33
34
|
ArgumentError,
|
|
34
35
|
OperationError,
|
|
@@ -1027,15 +1028,62 @@ class Font:
|
|
|
1027
1028
|
|
|
1028
1029
|
def get_version(
|
|
1029
1030
|
self,
|
|
1031
|
+
*,
|
|
1032
|
+
use_head_revision: bool = True,
|
|
1033
|
+
use_name_record: bool = True,
|
|
1030
1034
|
) -> float:
|
|
1031
1035
|
"""
|
|
1032
|
-
Gets the font version.
|
|
1036
|
+
Gets the font version reading it from head.fontRevision,
|
|
1037
|
+
with fallback on the version name record (name id 5) parsed value,
|
|
1038
|
+
eg. "Version 1.015;git-0a5106e0b" -> 1.015.
|
|
1039
|
+
|
|
1040
|
+
:param use_head_revision: If True, the head.fontRevision value is used (if set).
|
|
1041
|
+
:type use_head_revision: bool
|
|
1042
|
+
:param use_name_record: If True, the version name record value is used as fallback.
|
|
1043
|
+
:type use_name_record: bool
|
|
1033
1044
|
|
|
1034
|
-
:returns: The font version value.
|
|
1045
|
+
:returns: The font version value, 0.0 if the version cannot be read.
|
|
1035
1046
|
:rtype: float
|
|
1047
|
+
|
|
1048
|
+
:raises ArgumentError: If both options are False.
|
|
1049
|
+
"""
|
|
1050
|
+
ttfont = self.get_ttfont()
|
|
1051
|
+
return _version.get_version(
|
|
1052
|
+
ttfont,
|
|
1053
|
+
use_head_revision=use_head_revision,
|
|
1054
|
+
use_name_record=use_name_record,
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
def get_version_formatted(
|
|
1058
|
+
self,
|
|
1059
|
+
*,
|
|
1060
|
+
use_head_revision: bool = True,
|
|
1061
|
+
use_name_record: bool = True,
|
|
1062
|
+
prefix: str = "v",
|
|
1063
|
+
) -> str:
|
|
1064
|
+
"""
|
|
1065
|
+
Gets the font version formatted using the version name record convention,
|
|
1066
|
+
with the given prefix and a minimum of 3 decimal digits, eg. "v1.015".
|
|
1067
|
+
|
|
1068
|
+
:param use_head_revision: If True, the head.fontRevision value is used (if set).
|
|
1069
|
+
:type use_head_revision: bool
|
|
1070
|
+
:param use_name_record: If True, the version name record value is used as fallback.
|
|
1071
|
+
:type use_name_record: bool
|
|
1072
|
+
:param prefix: The prefix to prepend to the version value.
|
|
1073
|
+
:type prefix: str
|
|
1074
|
+
|
|
1075
|
+
:returns: The formatted font version value, eg. "v0.000" if the version cannot be read.
|
|
1076
|
+
:rtype: str
|
|
1077
|
+
|
|
1078
|
+
:raises ArgumentError: If both source options are False.
|
|
1036
1079
|
"""
|
|
1037
1080
|
ttfont = self.get_ttfont()
|
|
1038
|
-
return
|
|
1081
|
+
return _version.get_version_formatted(
|
|
1082
|
+
ttfont,
|
|
1083
|
+
use_head_revision=use_head_revision,
|
|
1084
|
+
use_name_record=use_name_record,
|
|
1085
|
+
prefix=prefix,
|
|
1086
|
+
)
|
|
1039
1087
|
|
|
1040
1088
|
def get_vertical_metrics(
|
|
1041
1089
|
self,
|
|
@@ -1670,6 +1718,29 @@ class Font:
|
|
|
1670
1718
|
style_name=name,
|
|
1671
1719
|
)
|
|
1672
1720
|
|
|
1721
|
+
def set_version(
|
|
1722
|
+
self,
|
|
1723
|
+
version: float | str,
|
|
1724
|
+
) -> None:
|
|
1725
|
+
"""
|
|
1726
|
+
Sets the font version, the version value can be a number (eg. 1.015)
|
|
1727
|
+
or a version string (eg. "Version 1.015"), parsed the same way the
|
|
1728
|
+
version name record value is parsed when read.
|
|
1729
|
+
The head.fontRevision and the version name record (name id 5) are
|
|
1730
|
+
updated, the latter is overwritten with the canonical "Version X.YYY"
|
|
1731
|
+
form (any extra info it contained is dropped).
|
|
1732
|
+
The unique identifier name record (name id 3) is updated only if its
|
|
1733
|
+
first ";" separated part holds the current version value, and only in
|
|
1734
|
+
that part. The CFF version is updated only if the font already has it.
|
|
1735
|
+
|
|
1736
|
+
:param version: The version value, eg. 1.015 or "Version 1.015"
|
|
1737
|
+
:type version: float or str
|
|
1738
|
+
|
|
1739
|
+
:raises ArgumentError: If the version value is not valid.
|
|
1740
|
+
"""
|
|
1741
|
+
ttfont = self.get_ttfont()
|
|
1742
|
+
_version.set_version(ttfont, version)
|
|
1743
|
+
|
|
1673
1744
|
def set_vertical_metrics(
|
|
1674
1745
|
self,
|
|
1675
1746
|
**vertical_metrics: Any,
|
|
@@ -72,6 +72,8 @@ _NAMES_BY_KEY: dict[str, dict[str, Any]] = {item["key"]: item for item in _NAMES
|
|
|
72
72
|
_NAMES_WIN_IDS: dict[str, Any] = {"platformID": 3, "platEncID": 1, "langID": 0x409}
|
|
73
73
|
# macintosh: platform 1, encoding 0 (roman), language 0 (english)
|
|
74
74
|
_NAMES_MAC_IDS: dict[str, Any] = {"platformID": 1, "platEncID": 0, "langID": 0x0}
|
|
75
|
+
# unicode: platform 0, encoding 3 (unicode bmp), language 0 (no language)
|
|
76
|
+
_NAMES_UNI_IDS: dict[str, Any] = {"platformID": 0, "platEncID": 3, "langID": 0x0}
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
def _get_name_id(
|
|
@@ -101,6 +103,10 @@ def _is_encodable(
|
|
|
101
103
|
platform_ids["platEncID"],
|
|
102
104
|
platform_ids["langID"],
|
|
103
105
|
)
|
|
106
|
+
if not encoding:
|
|
107
|
+
# the encoding of the name records is unknown (eg. the macintosh
|
|
108
|
+
# "uninterpreted" script), so the value cannot be written to them
|
|
109
|
+
return False
|
|
104
110
|
try:
|
|
105
111
|
value.encode(encoding)
|
|
106
112
|
except UnicodeEncodeError:
|
|
@@ -108,6 +114,31 @@ def _is_encodable(
|
|
|
108
114
|
return True
|
|
109
115
|
|
|
110
116
|
|
|
117
|
+
def _get_name_records_ids(
|
|
118
|
+
name_table: Any,
|
|
119
|
+
name_id: int,
|
|
120
|
+
platform_ids: dict[str, Any],
|
|
121
|
+
) -> list[dict[str, Any]]:
|
|
122
|
+
"""
|
|
123
|
+
Gets the ids of the existing name records with the given name id, matching
|
|
124
|
+
the platform and the language of the given platform ids. The encoding is
|
|
125
|
+
not matched: the same name id can have more than one record with different
|
|
126
|
+
encodings (eg. a windows symbol record and a windows unicode one).
|
|
127
|
+
Records in other languages are not matched, so they are never overwritten.
|
|
128
|
+
"""
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
"platformID": record.platformID,
|
|
132
|
+
"platEncID": record.platEncID,
|
|
133
|
+
"langID": record.langID,
|
|
134
|
+
}
|
|
135
|
+
for record in name_table.names
|
|
136
|
+
if record.nameID == name_id
|
|
137
|
+
and record.platformID == platform_ids["platformID"]
|
|
138
|
+
and record.langID == platform_ids["langID"]
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
111
142
|
def get_name(
|
|
112
143
|
ttfont: TTFont,
|
|
113
144
|
key: int | str,
|
|
@@ -160,19 +191,21 @@ def set_name(
|
|
|
160
191
|
# https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/ttLib/tables/_n_a_m_e.py#L568
|
|
161
192
|
# the windows record is always written, it's the one read first by get_name
|
|
162
193
|
name_table.setName(value, name_id, **_NAMES_WIN_IDS)
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
194
|
+
# the other records of the same name id are updated only if the font
|
|
195
|
+
# already has them (mac and unicode records are legacy, modern tools don't
|
|
196
|
+
# produce them anymore, so they are never added): a record left with its
|
|
197
|
+
# previous value would contradict the record just written
|
|
198
|
+
for platform_ids in (_NAMES_WIN_IDS, _NAMES_UNI_IDS, _NAMES_MAC_IDS):
|
|
199
|
+
for record_ids in _get_name_records_ids(name_table, name_id, platform_ids):
|
|
200
|
+
# the value is written only if the record encoding can encode it
|
|
201
|
+
# (eg. mac roman can't encode greek, cyrillic, cjk), otherwise the
|
|
202
|
+
# 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
|
|
205
|
+
if _is_encodable(value, record_ids):
|
|
206
|
+
name_table.setName(value, name_id, **record_ids)
|
|
207
|
+
else:
|
|
208
|
+
name_table.removeNames(nameID=name_id, **record_ids)
|
|
176
209
|
|
|
177
210
|
|
|
178
211
|
def set_names(
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fontTools.misc.fixedTools import fixedToStr, floatToFixed
|
|
7
|
+
from fontTools.ttLib import TTFont
|
|
8
|
+
|
|
9
|
+
from fontbro import names as _names
|
|
10
|
+
from fontbro.exceptions import ArgumentError
|
|
11
|
+
|
|
12
|
+
# the version name record conventionally starts with the version value,
|
|
13
|
+
# optionally prefixed by "Version" or "v", and it can be followed by
|
|
14
|
+
# extra info, eg. "Version 3.019;git-0a5106e0b"
|
|
15
|
+
# https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids
|
|
16
|
+
_VERSION_PATTERN: re.Pattern[str] = re.compile(
|
|
17
|
+
r"^(?:version|v\.?)?[\s\:]*(\d+)\.(\d+)",
|
|
18
|
+
flags=re.IGNORECASE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# head.fontRevision is a 16.16 fixed-point value
|
|
22
|
+
_VERSION_PRECISION_BITS: int = 16
|
|
23
|
+
|
|
24
|
+
# head.fontRevision is encoded as a signed 32 bits integer, so this is the
|
|
25
|
+
# greatest value it can hold: greater values cannot be stored in a font
|
|
26
|
+
# (and they would make fontTools fail when saving it)
|
|
27
|
+
_VERSION_MAX_VALUE: float = ((2**31) - 1) / (1 << _VERSION_PRECISION_BITS)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_version_str(
|
|
31
|
+
version: float,
|
|
32
|
+
) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Gets the shortest decimal representation of the given version value
|
|
35
|
+
that converts back to the same head.fontRevision fixed-point value.
|
|
36
|
+
"""
|
|
37
|
+
version_fixed = floatToFixed(version, precisionBits=_VERSION_PRECISION_BITS)
|
|
38
|
+
return str(fixedToStr(version_fixed, precisionBits=_VERSION_PRECISION_BITS))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def normalize_version(
|
|
42
|
+
version: float,
|
|
43
|
+
) -> float:
|
|
44
|
+
"""
|
|
45
|
+
Normalizes the given version value to the shortest decimal representation
|
|
46
|
+
that converts back to the same head.fontRevision fixed-point value,
|
|
47
|
+
eg. 1.0149993896484375 -> 1.015 (both are stored as 66519 in the head table).
|
|
48
|
+
"""
|
|
49
|
+
return float(_get_version_str(version))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_version_parts(
|
|
53
|
+
version: float,
|
|
54
|
+
) -> tuple[str, str]:
|
|
55
|
+
"""
|
|
56
|
+
Gets the (major, minor) parts of the given version value as digit strings,
|
|
57
|
+
with the minor part padded to a minimum of 3 digits, eg. 1.1 -> ("1", "100").
|
|
58
|
+
"""
|
|
59
|
+
major, _, minor = _get_version_str(version).partition(".")
|
|
60
|
+
return major, minor.ljust(3, "0")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def format_version(
|
|
64
|
+
version: float,
|
|
65
|
+
*,
|
|
66
|
+
prefix: str = "v",
|
|
67
|
+
) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Formats the given version value using the version name record convention,
|
|
70
|
+
with the given prefix and a minimum of 3 decimal digits, eg. 1.1 -> "v1.100".
|
|
71
|
+
More decimal digits are kept if the value needs them, eg. 1.0001 -> "v1.0001".
|
|
72
|
+
"""
|
|
73
|
+
major, minor = _get_version_parts(version)
|
|
74
|
+
return f"{prefix}{major}.{minor}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_version(
|
|
78
|
+
value: str | None,
|
|
79
|
+
) -> float:
|
|
80
|
+
"""
|
|
81
|
+
Parses the version value from the given version name record value,
|
|
82
|
+
eg. "Version 1.015;git-0a5106e0b" -> 1.015.
|
|
83
|
+
The value is matched from the beginning of the string to avoid reading
|
|
84
|
+
unrelated numbers (eg. a date or a tool version).
|
|
85
|
+
Returns 0.0 if the value cannot be parsed, or if it holds a value that
|
|
86
|
+
cannot be stored as a font version.
|
|
87
|
+
"""
|
|
88
|
+
match = _VERSION_PATTERN.match((value or "").strip())
|
|
89
|
+
if not match:
|
|
90
|
+
return 0.0
|
|
91
|
+
version = float(f"{match[1]}.{match[2]}")
|
|
92
|
+
if not 0 < version <= _VERSION_MAX_VALUE:
|
|
93
|
+
return 0.0
|
|
94
|
+
return normalize_version(version)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_version(
|
|
98
|
+
ttfont: TTFont,
|
|
99
|
+
*,
|
|
100
|
+
use_head_revision: bool = True,
|
|
101
|
+
use_name_record: bool = True,
|
|
102
|
+
) -> float:
|
|
103
|
+
"""
|
|
104
|
+
Gets the version of the given font reading it from head.fontRevision,
|
|
105
|
+
with fallback on the version name record (name id 5) parsed value.
|
|
106
|
+
The value is normalized to its shortest decimal representation,
|
|
107
|
+
so that it doesn't depend on the source it has been read from.
|
|
108
|
+
"""
|
|
109
|
+
if not use_head_revision and not use_name_record:
|
|
110
|
+
raise ArgumentError(
|
|
111
|
+
"Invalid arguments: at least one of 'use_head_revision' "
|
|
112
|
+
"and 'use_name_record' options must be True."
|
|
113
|
+
)
|
|
114
|
+
if use_head_revision:
|
|
115
|
+
# the head table is read only if the font has it
|
|
116
|
+
head = ttfont.get("head")
|
|
117
|
+
if head is not None:
|
|
118
|
+
# fontRevision is 0.0 when it has not been set
|
|
119
|
+
version = float(getattr(head, "fontRevision", None) or 0.0)
|
|
120
|
+
if 0 < version <= _VERSION_MAX_VALUE:
|
|
121
|
+
return normalize_version(version)
|
|
122
|
+
if use_name_record:
|
|
123
|
+
version = parse_version(_names.get_name(ttfont, _names.NAME_VERSION))
|
|
124
|
+
if version > 0:
|
|
125
|
+
return version
|
|
126
|
+
return 0.0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_version_formatted(
|
|
130
|
+
ttfont: TTFont,
|
|
131
|
+
*,
|
|
132
|
+
use_head_revision: bool = True,
|
|
133
|
+
use_name_record: bool = True,
|
|
134
|
+
prefix: str = "v",
|
|
135
|
+
) -> str:
|
|
136
|
+
"""
|
|
137
|
+
Gets the formatted version of the given font, eg. "v1.015".
|
|
138
|
+
"""
|
|
139
|
+
version = get_version(
|
|
140
|
+
ttfont,
|
|
141
|
+
use_head_revision=use_head_revision,
|
|
142
|
+
use_name_record=use_name_record,
|
|
143
|
+
)
|
|
144
|
+
return format_version(version, prefix=prefix)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _get_cff_top_dict(
|
|
148
|
+
ttfont: TTFont,
|
|
149
|
+
) -> Any | None:
|
|
150
|
+
"""
|
|
151
|
+
Gets the CFF top dict of the given font, None if the font has no CFF table,
|
|
152
|
+
or if its CFF holds no font, or if it holds more than one font (eg. in a
|
|
153
|
+
font collection) and the one matching the font postscript name is not found.
|
|
154
|
+
"""
|
|
155
|
+
cff_table = ttfont.get("CFF ")
|
|
156
|
+
if cff_table is None:
|
|
157
|
+
return None
|
|
158
|
+
cff = cff_table.cff
|
|
159
|
+
font_names = list(cff.fontNames)
|
|
160
|
+
if len(font_names) == 1:
|
|
161
|
+
font_index = 0
|
|
162
|
+
else:
|
|
163
|
+
# a cff can hold more than one font, in that case the font is looked up
|
|
164
|
+
# by its postscript name, to avoid writing to the wrong one
|
|
165
|
+
postscript_name = _names.get_name(ttfont, _names.NAME_POSTSCRIPT_NAME) or ""
|
|
166
|
+
if postscript_name not in font_names:
|
|
167
|
+
return None
|
|
168
|
+
font_index = font_names.index(postscript_name)
|
|
169
|
+
top_dict_index = cff.topDictIndex
|
|
170
|
+
if font_index >= len(top_dict_index):
|
|
171
|
+
return None
|
|
172
|
+
return top_dict_index[font_index]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _format_cff_version(
|
|
176
|
+
version: float,
|
|
177
|
+
) -> str:
|
|
178
|
+
"""
|
|
179
|
+
Formats the given version value using the CFF top dict convention,
|
|
180
|
+
eg. 1.015 -> "001.015".
|
|
181
|
+
"""
|
|
182
|
+
major, minor = _get_version_parts(version)
|
|
183
|
+
return f"{int(major):03d}.{minor}"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def set_version(
|
|
187
|
+
ttfont: TTFont,
|
|
188
|
+
version: float | str,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Sets the version of the given font, the version value can be a number
|
|
192
|
+
(eg. 1.015) or a version string (eg. "Version 1.015"), parsed the same
|
|
193
|
+
way the version name record value is parsed when read.
|
|
194
|
+
The head.fontRevision and the version name record (name id 5) are updated,
|
|
195
|
+
the latter is overwritten with the canonical "Version X.YYY" form
|
|
196
|
+
(any extra info it contained is dropped).
|
|
197
|
+
The unique identifier name record (name id 3) is updated only if its first
|
|
198
|
+
";" separated part holds the current version value, and only in that part.
|
|
199
|
+
The CFF version is updated only if the font already has it.
|
|
200
|
+
Each value is written only if the font has the table holding it.
|
|
201
|
+
"""
|
|
202
|
+
if isinstance(version, str):
|
|
203
|
+
# a version string is parsed as the version name record value
|
|
204
|
+
version_value = parse_version(version)
|
|
205
|
+
elif isinstance(version, (int, float)):
|
|
206
|
+
version_value = float(version)
|
|
207
|
+
else:
|
|
208
|
+
version_type = type(version).__name__
|
|
209
|
+
raise ArgumentError(
|
|
210
|
+
f"Invalid version type, expected float or str, found '{version_type}'."
|
|
211
|
+
)
|
|
212
|
+
new_version = 0.0
|
|
213
|
+
if 0 < version_value <= _VERSION_MAX_VALUE:
|
|
214
|
+
# a value too small to be stored is rounded to 0.0, ie. no version set,
|
|
215
|
+
# so it is rejected as well
|
|
216
|
+
new_version = normalize_version(version_value)
|
|
217
|
+
if new_version <= 0:
|
|
218
|
+
raise ArgumentError(
|
|
219
|
+
f"Invalid version value: {version!r}, expected a number greater than 0"
|
|
220
|
+
f" and not greater than {_VERSION_MAX_VALUE} (head.fontRevision is a"
|
|
221
|
+
" 16.16 fixed-point value), or a parsable version string,"
|
|
222
|
+
' eg. 1.015 or "Version 1.015".'
|
|
223
|
+
)
|
|
224
|
+
old_version = get_version(ttfont)
|
|
225
|
+
# head.fontRevision (only if the font has the head table)
|
|
226
|
+
head = ttfont.get("head")
|
|
227
|
+
if head is not None:
|
|
228
|
+
head.fontRevision = new_version
|
|
229
|
+
# name records (only if the font has the name table)
|
|
230
|
+
if ttfont.get("name") is not None:
|
|
231
|
+
# version name record (name id 5)
|
|
232
|
+
_names.set_name(
|
|
233
|
+
ttfont,
|
|
234
|
+
_names.NAME_VERSION,
|
|
235
|
+
format_version(new_version, prefix="Version "),
|
|
236
|
+
)
|
|
237
|
+
# unique identifier name record (name id 3): it has no canonical form,
|
|
238
|
+
# it conventionally holds the version value in its first ";" separated
|
|
239
|
+
# part, eg. "1.015;ETCO;Tourney-Regular", so only that part is replaced,
|
|
240
|
+
# and only if it holds the current version value
|
|
241
|
+
unique_id = _names.get_name(ttfont, _names.NAME_UNIQUE_IDENTIFIER) or ""
|
|
242
|
+
unique_id_version, separator, unique_id_info = unique_id.partition(";")
|
|
243
|
+
if old_version > 0 and parse_version(unique_id_version) == old_version:
|
|
244
|
+
unique_id_version = format_version(new_version, prefix="")
|
|
245
|
+
_names.set_name(
|
|
246
|
+
ttfont,
|
|
247
|
+
_names.NAME_UNIQUE_IDENTIFIER,
|
|
248
|
+
f"{unique_id_version}{separator}{unique_id_info}",
|
|
249
|
+
)
|
|
250
|
+
# cff version (only otf fonts that already have it)
|
|
251
|
+
top_dict = _get_cff_top_dict(ttfont)
|
|
252
|
+
if top_dict is not None and "version" in top_dict.rawDict:
|
|
253
|
+
top_dict.version = _format_cff_version(new_version)
|