python-fontbro 0.31.0__tar.gz → 0.32.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.31.0/python_fontbro.egg-info → python_fontbro-0.32.0}/PKG-INFO +10 -10
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/README.md +8 -8
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/embedding_permissions.py +3 -4
- python_fontbro-0.32.0/fontbro/exceptions.py +53 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/family_classification.py +3 -4
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/font.py +12 -11
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/metadata.py +1 -1
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/monospace.py +3 -1
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/names.py +6 -3
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/pixel.py +2 -1
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/render.py +3 -2
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/tables.py +15 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/variable.py +5 -4
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/pyproject.toml +1 -1
- {python_fontbro-0.31.0 → python_fontbro-0.32.0/python_fontbro.egg-info}/PKG-INFO +10 -10
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/SOURCES.txt +1 -0
- python_fontbro-0.32.0/tests/test_exceptions.py +134 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_instantiation.py +9 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_rename.py +30 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_save.py +7 -5
- python_fontbro-0.32.0/tests/test_update_unicode_data.py +20 -0
- python_fontbro-0.31.0/fontbro/exceptions.py +0 -14
- python_fontbro-0.31.0/tests/test_update_unicode_data.py +0 -11
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/LICENSE.txt +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/MANIFEST.in +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/__init__.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/bitmap.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/color.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/family-classifications.json +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/features.json +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/unicode-blocks.json +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/unicode-scripts.json +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/features.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/files.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/fingerprint.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/flags.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/glyphs.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/math.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/metrics.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/py.typed +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/sanitize.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/style_flags.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/subset.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/support.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/unicode.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/utils.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/version.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/requires.txt +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/top_level.txt +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/setup.cfg +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/setup.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_bitmap.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_characters.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_clone.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_close.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_collection.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_color.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_context_manager.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_embedding_permissions.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_family_classification.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_family_name.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_features.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_filename.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_fingerprint.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_format.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_glyphs.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_image.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_init.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_issues.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_italic_angle.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_monospace.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_names.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_pixel.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_sanitize.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_str.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_style_flags.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_style_name.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_subset.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_support.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_svg.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_tables.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_unicode.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_unicode_blocks_and_scripts.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_variable.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_version.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_vertical_metrics.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_weight.py +0 -0
- {python_fontbro-0.31.0 → python_fontbro-0.32.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.32.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>
|
|
@@ -43,7 +43,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
43
43
|
Classifier: Natural Language :: English
|
|
44
44
|
Classifier: Operating System :: OS Independent
|
|
45
45
|
Classifier: Programming Language :: Python :: 3
|
|
46
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
47
46
|
Classifier: Programming Language :: Python :: 3.11
|
|
48
47
|
Classifier: Programming Language :: Python :: 3.12
|
|
49
48
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -52,6 +51,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
52
51
|
Classifier: Topic :: Text Processing :: Fonts
|
|
53
52
|
Classifier: Topic :: Multimedia :: Graphics
|
|
54
53
|
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
54
|
+
Requires-Python: >=3.11
|
|
55
55
|
Description-Content-Type: text/markdown
|
|
56
56
|
License-File: LICENSE.txt
|
|
57
57
|
Requires-Dist: fonttools[lxml,pathops,unicode,woff]<5.0,>=4.43.0
|
|
@@ -184,7 +184,7 @@ font_clone = font.clone()
|
|
|
184
184
|
```python
|
|
185
185
|
"""
|
|
186
186
|
Close the wrapped TTFont instance.
|
|
187
|
-
After closing, any operation on the font raises
|
|
187
|
+
After closing, any operation on the font raises ClosedFontError,
|
|
188
188
|
closing an already closed font does nothing.
|
|
189
189
|
"""
|
|
190
190
|
|
|
@@ -631,7 +631,7 @@ Gets the wrapped TTFont instance.
|
|
|
631
631
|
:returns: The TTFont instance.
|
|
632
632
|
:rtype: TTFont
|
|
633
633
|
|
|
634
|
-
:raises
|
|
634
|
+
:raises ClosedFontError: If the font has been closed.
|
|
635
635
|
"""
|
|
636
636
|
|
|
637
637
|
ttfont = font.get_ttfont()
|
|
@@ -1011,7 +1011,7 @@ If family_name is not defined it will be auto-detected.
|
|
|
1011
1011
|
(only if the default instance style name is renamed)
|
|
1012
1012
|
:type update_style_flags: bool
|
|
1013
1013
|
|
|
1014
|
-
:raises
|
|
1014
|
+
:raises VariableFontRequiredError: if the font is not a variable font.
|
|
1015
1015
|
"""
|
|
1016
1016
|
|
|
1017
1017
|
font.rename_variable_instances(
|
|
@@ -1131,7 +1131,7 @@ Save all instances of a variable font to specified directory in one or more form
|
|
|
1131
1131
|
includes 'instance' (containing instance metadata) and 'files' (a dictionary
|
|
1132
1132
|
with file formats as keys and file-paths as values).
|
|
1133
1133
|
|
|
1134
|
-
:raises
|
|
1134
|
+
:raises VariableFontRequiredError: If the font is not a variable font.
|
|
1135
1135
|
"""
|
|
1136
1136
|
|
|
1137
1137
|
saved_fonts = font.save_variable_instances(
|
|
@@ -1167,7 +1167,7 @@ The fsType field is left untouched if any argument is invalid.
|
|
|
1167
1167
|
:raises ArgumentError: If a value is not a bool or None, if more than one
|
|
1168
1168
|
of the mutually exclusive usage permissions is set to True, or if
|
|
1169
1169
|
installable is set to False while no other usage permission is in effect.
|
|
1170
|
-
:raises
|
|
1170
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1171
1171
|
"""
|
|
1172
1172
|
|
|
1173
1173
|
font.set_embedding_permissions(
|
|
@@ -1187,7 +1187,7 @@ based on provided class_id and subclass_id.
|
|
|
1187
1187
|
|
|
1188
1188
|
:param class_id: Numeric identifier of the font family class.
|
|
1189
1189
|
:param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
|
|
1190
|
-
:raises
|
|
1190
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1191
1191
|
:raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
|
|
1192
1192
|
"""
|
|
1193
1193
|
|
|
@@ -1405,7 +1405,7 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1405
1405
|
:param options: The options for the fontTools.varLib.instancer
|
|
1406
1406
|
:type options: dictionary
|
|
1407
1407
|
|
|
1408
|
-
:raises
|
|
1408
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1409
1409
|
:raises ValueError: If the coordinates are not defined (empty)
|
|
1410
1410
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1411
1411
|
"""
|
|
@@ -1438,7 +1438,7 @@ If coordinates are not specified each axis will be pinned at its default value.
|
|
|
1438
1438
|
:param options: The options for the fontTools.varLib.instancer
|
|
1439
1439
|
:type options: dictionary
|
|
1440
1440
|
|
|
1441
|
-
:raises
|
|
1441
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1442
1442
|
:raises ValueError: If the coordinates axes are not all pinned
|
|
1443
1443
|
"""
|
|
1444
1444
|
|
|
@@ -120,7 +120,7 @@ font_clone = font.clone()
|
|
|
120
120
|
```python
|
|
121
121
|
"""
|
|
122
122
|
Close the wrapped TTFont instance.
|
|
123
|
-
After closing, any operation on the font raises
|
|
123
|
+
After closing, any operation on the font raises ClosedFontError,
|
|
124
124
|
closing an already closed font does nothing.
|
|
125
125
|
"""
|
|
126
126
|
|
|
@@ -567,7 +567,7 @@ Gets the wrapped TTFont instance.
|
|
|
567
567
|
:returns: The TTFont instance.
|
|
568
568
|
:rtype: TTFont
|
|
569
569
|
|
|
570
|
-
:raises
|
|
570
|
+
:raises ClosedFontError: If the font has been closed.
|
|
571
571
|
"""
|
|
572
572
|
|
|
573
573
|
ttfont = font.get_ttfont()
|
|
@@ -947,7 +947,7 @@ If family_name is not defined it will be auto-detected.
|
|
|
947
947
|
(only if the default instance style name is renamed)
|
|
948
948
|
:type update_style_flags: bool
|
|
949
949
|
|
|
950
|
-
:raises
|
|
950
|
+
:raises VariableFontRequiredError: if the font is not a variable font.
|
|
951
951
|
"""
|
|
952
952
|
|
|
953
953
|
font.rename_variable_instances(
|
|
@@ -1067,7 +1067,7 @@ Save all instances of a variable font to specified directory in one or more form
|
|
|
1067
1067
|
includes 'instance' (containing instance metadata) and 'files' (a dictionary
|
|
1068
1068
|
with file formats as keys and file-paths as values).
|
|
1069
1069
|
|
|
1070
|
-
:raises
|
|
1070
|
+
:raises VariableFontRequiredError: If the font is not a variable font.
|
|
1071
1071
|
"""
|
|
1072
1072
|
|
|
1073
1073
|
saved_fonts = font.save_variable_instances(
|
|
@@ -1103,7 +1103,7 @@ The fsType field is left untouched if any argument is invalid.
|
|
|
1103
1103
|
:raises ArgumentError: If a value is not a bool or None, if more than one
|
|
1104
1104
|
of the mutually exclusive usage permissions is set to True, or if
|
|
1105
1105
|
installable is set to False while no other usage permission is in effect.
|
|
1106
|
-
:raises
|
|
1106
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1107
1107
|
"""
|
|
1108
1108
|
|
|
1109
1109
|
font.set_embedding_permissions(
|
|
@@ -1123,7 +1123,7 @@ based on provided class_id and subclass_id.
|
|
|
1123
1123
|
|
|
1124
1124
|
:param class_id: Numeric identifier of the font family class.
|
|
1125
1125
|
:param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
|
|
1126
|
-
:raises
|
|
1126
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1127
1127
|
:raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
|
|
1128
1128
|
"""
|
|
1129
1129
|
|
|
@@ -1341,7 +1341,7 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1341
1341
|
:param options: The options for the fontTools.varLib.instancer
|
|
1342
1342
|
:type options: dictionary
|
|
1343
1343
|
|
|
1344
|
-
:raises
|
|
1344
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1345
1345
|
:raises ValueError: If the coordinates are not defined (empty)
|
|
1346
1346
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1347
1347
|
"""
|
|
@@ -1374,7 +1374,7 @@ If coordinates are not specified each axis will be pinned at its default value.
|
|
|
1374
1374
|
:param options: The options for the fontTools.varLib.instancer
|
|
1375
1375
|
:type options: dictionary
|
|
1376
1376
|
|
|
1377
|
-
:raises
|
|
1377
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1378
1378
|
:raises ValueError: If the coordinates axes are not all pinned
|
|
1379
1379
|
"""
|
|
1380
1380
|
|
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from fontTools.ttLib import TTFont
|
|
4
4
|
|
|
5
|
-
from fontbro.exceptions import ArgumentError
|
|
5
|
+
from fontbro.exceptions import ArgumentError
|
|
6
6
|
from fontbro.flags import get_flag, set_flag
|
|
7
|
+
from fontbro.tables import get_table_or_raise
|
|
7
8
|
|
|
8
9
|
# Embedding Permissions:
|
|
9
10
|
# https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fstype
|
|
@@ -95,9 +96,7 @@ def set_embedding_permissions(
|
|
|
95
96
|
Sets the embedding permissions in the OS/2 fsType field of the given font,
|
|
96
97
|
the fsType field is left untouched if any argument is invalid.
|
|
97
98
|
"""
|
|
98
|
-
os2 = ttfont
|
|
99
|
-
if not os2:
|
|
100
|
-
raise OperationError("Invalid OS/2 table (doesn't exist).")
|
|
99
|
+
os2 = get_table_or_raise(ttfont, "OS/2")
|
|
101
100
|
|
|
102
101
|
usage_permissions = {
|
|
103
102
|
"installable": installable,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ArgumentError(ValueError):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DataError(ValueError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OperationError(TypeError):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClosedFontError(OperationError):
|
|
17
|
+
"""
|
|
18
|
+
The operation was called on a closed font.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MissingTableError(OperationError, KeyError):
|
|
23
|
+
"""
|
|
24
|
+
The operation requires a table the font doesn't have.
|
|
25
|
+
It's also a KeyError, raised before when accessing the missing table.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
table_tag: str,
|
|
31
|
+
message: str | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
self.table_tag = table_tag
|
|
34
|
+
super().__init__(message or f"Invalid {table_tag} table (doesn't exist).")
|
|
35
|
+
|
|
36
|
+
def __reduce__(self) -> tuple[type[MissingTableError], tuple[str, str]]:
|
|
37
|
+
# pickle and copy recreate the exception passing its args (the message
|
|
38
|
+
# only) to __init__, the table tag must be passed too
|
|
39
|
+
return (self.__class__, (self.table_tag, str(self)))
|
|
40
|
+
|
|
41
|
+
def __str__(self) -> str:
|
|
42
|
+
# KeyError wraps the message in quotes
|
|
43
|
+
return str(self.args[0])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class VariableFontRequiredError(OperationError):
|
|
47
|
+
"""
|
|
48
|
+
The operation requires a variable font.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SanitizationError(Exception):
|
|
53
|
+
pass
|
|
@@ -4,7 +4,8 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from fontTools.ttLib import TTFont
|
|
6
6
|
|
|
7
|
-
from fontbro.exceptions import ArgumentError
|
|
7
|
+
from fontbro.exceptions import ArgumentError
|
|
8
|
+
from fontbro.tables import get_table_or_raise
|
|
8
9
|
from fontbro.utils import concat_names, find_item, read_json
|
|
9
10
|
|
|
10
11
|
# Family Classification:
|
|
@@ -152,9 +153,7 @@ def set_family_classification(
|
|
|
152
153
|
"""
|
|
153
154
|
Sets the family classification of the given font in the OS/2.sFamilyClass field.
|
|
154
155
|
"""
|
|
155
|
-
os2 = ttfont
|
|
156
|
-
if not os2:
|
|
157
|
-
raise OperationError("Invalid OS/2 table (doesn't exist).")
|
|
156
|
+
os2 = get_table_or_raise(ttfont, "OS/2")
|
|
158
157
|
|
|
159
158
|
# validate class key and subclass key
|
|
160
159
|
class_item, subclass_item = _get_family_classification_items(
|
|
@@ -32,7 +32,8 @@ from fontbro import variable as _variable
|
|
|
32
32
|
from fontbro import version as _version
|
|
33
33
|
from fontbro.exceptions import (
|
|
34
34
|
ArgumentError,
|
|
35
|
-
|
|
35
|
+
ClosedFontError,
|
|
36
|
+
VariableFontRequiredError,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
|
|
@@ -325,7 +326,7 @@ class Font:
|
|
|
325
326
|
) -> None:
|
|
326
327
|
"""
|
|
327
328
|
Close the wrapped TTFont instance.
|
|
328
|
-
After closing, any operation on the font raises
|
|
329
|
+
After closing, any operation on the font raises ClosedFontError,
|
|
329
330
|
closing an already closed font does nothing.
|
|
330
331
|
"""
|
|
331
332
|
# TTFont.close() is idempotent (it closes the reader and sets it to None)
|
|
@@ -847,13 +848,13 @@ class Font:
|
|
|
847
848
|
:returns: The TTFont instance.
|
|
848
849
|
:rtype: TTFont
|
|
849
850
|
|
|
850
|
-
:raises
|
|
851
|
+
:raises ClosedFontError: If the font has been closed.
|
|
851
852
|
"""
|
|
852
853
|
# all the font operations get the TTFont instance through this method,
|
|
853
854
|
# the TTFont can also be closed directly (bypassing this instance):
|
|
854
855
|
# when closed, fontTools sets its reader to None
|
|
855
856
|
if self._ttfont is None or self._ttfont.reader is None:
|
|
856
|
-
raise
|
|
857
|
+
raise ClosedFontError("Invalid operation, the font is closed.")
|
|
857
858
|
return self._ttfont
|
|
858
859
|
|
|
859
860
|
def get_unicode_block_by_name(
|
|
@@ -1277,7 +1278,7 @@ class Font:
|
|
|
1277
1278
|
(only if the default instance style name is renamed)
|
|
1278
1279
|
:type update_style_flags: bool
|
|
1279
1280
|
|
|
1280
|
-
:raises
|
|
1281
|
+
:raises VariableFontRequiredError: if the font is not a variable font.
|
|
1281
1282
|
:return: None
|
|
1282
1283
|
"""
|
|
1283
1284
|
ttfont = self.get_ttfont()
|
|
@@ -1512,10 +1513,10 @@ class Font:
|
|
|
1512
1513
|
includes 'instance' (containing instance metadata) and 'files' (a dictionary
|
|
1513
1514
|
with file formats as keys and file-paths as values).
|
|
1514
1515
|
|
|
1515
|
-
:raises
|
|
1516
|
+
:raises VariableFontRequiredError: If the font is not a variable font.
|
|
1516
1517
|
"""
|
|
1517
1518
|
if not self.is_variable():
|
|
1518
|
-
raise
|
|
1519
|
+
raise VariableFontRequiredError("Only a variable font can be instantiated.")
|
|
1519
1520
|
|
|
1520
1521
|
fsutil.assert_not_file(dirpath)
|
|
1521
1522
|
fsutil.make_dirs(dirpath)
|
|
@@ -1570,7 +1571,7 @@ class Font:
|
|
|
1570
1571
|
|
|
1571
1572
|
:param class_id: Numeric identifier of the font family class.
|
|
1572
1573
|
:param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
|
|
1573
|
-
:raises
|
|
1574
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1574
1575
|
:raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
|
|
1575
1576
|
"""
|
|
1576
1577
|
ttfont = self.get_ttfont()
|
|
@@ -1723,7 +1724,7 @@ class Font:
|
|
|
1723
1724
|
:raises ArgumentError: If a value is not a bool or None, if more than one
|
|
1724
1725
|
of the mutually exclusive usage permissions is set to True, or if
|
|
1725
1726
|
installable is set to False while no other usage permission is in effect.
|
|
1726
|
-
:raises
|
|
1727
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1727
1728
|
"""
|
|
1728
1729
|
ttfont = self.get_ttfont()
|
|
1729
1730
|
_embedding_permissions.set_embedding_permissions(
|
|
@@ -1860,7 +1861,7 @@ class Font:
|
|
|
1860
1861
|
:param options: The options for the fontTools.varLib.instancer
|
|
1861
1862
|
:type options: dictionary
|
|
1862
1863
|
|
|
1863
|
-
:raises
|
|
1864
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1864
1865
|
:raises ValueError: If the coordinates are not defined (blank)
|
|
1865
1866
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1866
1867
|
"""
|
|
@@ -1900,7 +1901,7 @@ class Font:
|
|
|
1900
1901
|
:param options: The options for the fontTools.varLib.instancer
|
|
1901
1902
|
:type options: dictionary
|
|
1902
1903
|
|
|
1903
|
-
:raises
|
|
1904
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1904
1905
|
:raises ValueError: If the coordinates axes are not all pinned
|
|
1905
1906
|
"""
|
|
1906
1907
|
ttfont = self.get_ttfont()
|
|
@@ -6,6 +6,7 @@ from collections import Counter
|
|
|
6
6
|
from fontTools import unicodedata
|
|
7
7
|
from fontTools.ttLib import TTFont
|
|
8
8
|
|
|
9
|
+
from fontbro.tables import get_table_or_raise
|
|
9
10
|
from fontbro.unicode import get_best_cmap
|
|
10
11
|
|
|
11
12
|
# same criteria used by Fontbakery (glyph_metrics_stats condition):
|
|
@@ -36,9 +37,10 @@ def _get_widths(
|
|
|
36
37
|
"""
|
|
37
38
|
Gets the advance widths of the given glyphs, zero widths are ignored.
|
|
38
39
|
"""
|
|
40
|
+
hmtx = get_table_or_raise(ttfont, "hmtx")
|
|
39
41
|
return [
|
|
40
42
|
advance
|
|
41
|
-
for glyph_name, (advance, _) in
|
|
43
|
+
for glyph_name, (advance, _) in hmtx.metrics.items()
|
|
42
44
|
if glyph_name in glyphs_names and advance != 0
|
|
43
45
|
]
|
|
44
46
|
|
|
@@ -6,7 +6,8 @@ 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, VariableFontRequiredError
|
|
10
|
+
from fontbro.tables import get_table_or_raise
|
|
10
11
|
from fontbro.utils import concat_names, remove_spaces
|
|
11
12
|
|
|
12
13
|
# Names:
|
|
@@ -193,7 +194,7 @@ def set_name(
|
|
|
193
194
|
and unicode flags.
|
|
194
195
|
"""
|
|
195
196
|
name_id = _get_name_id(key)
|
|
196
|
-
name_table = ttfont
|
|
197
|
+
name_table = get_table_or_raise(ttfont, "name")
|
|
197
198
|
if windows:
|
|
198
199
|
# https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/ttLib/tables/_n_a_m_e.py#L568
|
|
199
200
|
# the windows record is always written, it's the one read first by get_name
|
|
@@ -313,7 +314,9 @@ def rename_variable_instances(
|
|
|
313
314
|
so renaming it renames the font style too.
|
|
314
315
|
"""
|
|
315
316
|
if "fvar" not in ttfont:
|
|
316
|
-
raise
|
|
317
|
+
raise VariableFontRequiredError(
|
|
318
|
+
"Only the instances of a variable font can be renamed."
|
|
319
|
+
)
|
|
317
320
|
# style names are matched case-insensitively
|
|
318
321
|
style_names = {key.lower(): value for key, value in (style_names or {}).items()}
|
|
319
322
|
default_style_name = _get_default_instance_style_name(ttfont).lower()
|
|
@@ -9,6 +9,7 @@ from fontTools import unicodedata
|
|
|
9
9
|
from fontTools.pens.recordingPen import DecomposingRecordingPen
|
|
10
10
|
from fontTools.ttLib import TTFont
|
|
11
11
|
|
|
12
|
+
from fontbro.tables import get_table_or_raise
|
|
12
13
|
from fontbro.unicode import get_best_cmap
|
|
13
14
|
|
|
14
15
|
Segment = tuple[float, float]
|
|
@@ -219,7 +220,7 @@ def is_pixel(
|
|
|
219
220
|
|
|
220
221
|
# 2nd check: segments aligned to a plausible pixel grid,
|
|
221
222
|
# it excludes squared ("techno") fonts
|
|
222
|
-
units_per_em = ttfont
|
|
223
|
+
units_per_em = get_table_or_raise(ttfont, "head").unitsPerEm
|
|
223
224
|
pixel_width, pixel_height = (
|
|
224
225
|
_get_pixel_dimension(
|
|
225
226
|
orthogonal_glyphs_segments, axis, units_per_em, min_glyphs_count
|
|
@@ -9,6 +9,7 @@ from fontTools.pens.svgPathPen import SVGPathPen
|
|
|
9
9
|
from fontTools.ttLib import TTFont
|
|
10
10
|
from PIL import Image, ImageDraw, ImageFont
|
|
11
11
|
|
|
12
|
+
from fontbro.tables import get_table_or_raise
|
|
12
13
|
from fontbro.unicode import get_best_cmap_or_raise
|
|
13
14
|
|
|
14
15
|
|
|
@@ -51,9 +52,9 @@ def get_svg(
|
|
|
51
52
|
Gets an SVG representation of the given font rendering some text.
|
|
52
53
|
"""
|
|
53
54
|
# get font metrics
|
|
54
|
-
units_per_em = ttfont
|
|
55
|
+
units_per_em = get_table_or_raise(ttfont, "head").unitsPerEm
|
|
55
56
|
scale = size / units_per_em
|
|
56
|
-
hhea = ttfont
|
|
57
|
+
hhea = get_table_or_raise(ttfont, "hhea")
|
|
57
58
|
ascent = hhea.ascent * scale
|
|
58
59
|
descent = hhea.descent * scale
|
|
59
60
|
width = 0
|
|
@@ -4,6 +4,8 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from fontTools.ttLib import TTFont
|
|
6
6
|
|
|
7
|
+
from fontbro.exceptions import MissingTableError
|
|
8
|
+
|
|
7
9
|
# https://learn.microsoft.com/en-us/typography/opentype/spec/otff#font-tables
|
|
8
10
|
_TABLE_NAMES_BY_TAG: dict[str, str] = {
|
|
9
11
|
# required tables
|
|
@@ -95,6 +97,19 @@ _TABLE_NAMES_BY_TAG: dict[str, str] = {
|
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
|
|
100
|
+
def get_table_or_raise(
|
|
101
|
+
ttfont: TTFont,
|
|
102
|
+
tag: str,
|
|
103
|
+
) -> Any:
|
|
104
|
+
"""
|
|
105
|
+
Gets the table with the given tag of the given font,
|
|
106
|
+
MissingTableError is raised if the font has no table with the given tag.
|
|
107
|
+
"""
|
|
108
|
+
if tag not in ttfont:
|
|
109
|
+
raise MissingTableError(tag)
|
|
110
|
+
return ttfont[tag]
|
|
111
|
+
|
|
112
|
+
|
|
98
113
|
def get_tables_tags(
|
|
99
114
|
ttfont: TTFont,
|
|
100
115
|
*,
|
|
@@ -9,8 +9,9 @@ from fontTools.varLib.instancer import OverlapMode
|
|
|
9
9
|
|
|
10
10
|
from fontbro import names as _names
|
|
11
11
|
from fontbro import style_flags as _style_flags
|
|
12
|
-
from fontbro.exceptions import ArgumentError,
|
|
12
|
+
from fontbro.exceptions import ArgumentError, VariableFontRequiredError
|
|
13
13
|
from fontbro.math import get_euclidean_distance
|
|
14
|
+
from fontbro.tables import get_table_or_raise
|
|
14
15
|
from fontbro.utils import slugify
|
|
15
16
|
|
|
16
17
|
# Variable Axes:
|
|
@@ -159,7 +160,7 @@ def get_variable_instances(
|
|
|
159
160
|
"""
|
|
160
161
|
if not is_variable(ttfont):
|
|
161
162
|
return None
|
|
162
|
-
name_table = ttfont
|
|
163
|
+
name_table = get_table_or_raise(ttfont, "name")
|
|
163
164
|
return [
|
|
164
165
|
{
|
|
165
166
|
"coordinates": instance.coordinates,
|
|
@@ -320,7 +321,7 @@ def to_sliced_variable(
|
|
|
320
321
|
the variable axes at the given coordinates.
|
|
321
322
|
"""
|
|
322
323
|
if not is_variable(ttfont):
|
|
323
|
-
raise
|
|
324
|
+
raise VariableFontRequiredError("Only a variable font can be sliced.")
|
|
324
325
|
|
|
325
326
|
coordinates = get_sliced_coordinates(ttfont, coordinates)
|
|
326
327
|
|
|
@@ -364,7 +365,7 @@ def to_static(
|
|
|
364
365
|
the variable axes at the given coordinates.
|
|
365
366
|
"""
|
|
366
367
|
if not is_variable(ttfont):
|
|
367
|
-
raise
|
|
368
|
+
raise VariableFontRequiredError("Only a variable font can be made static.")
|
|
368
369
|
|
|
369
370
|
coordinates = get_static_coordinates(
|
|
370
371
|
ttfont,
|
|
@@ -40,7 +40,6 @@ classifiers = [
|
|
|
40
40
|
"Natural Language :: English",
|
|
41
41
|
"Operating System :: OS Independent",
|
|
42
42
|
"Programming Language :: Python :: 3",
|
|
43
|
-
"Programming Language :: Python :: 3.10",
|
|
44
43
|
"Programming Language :: Python :: 3.11",
|
|
45
44
|
"Programming Language :: Python :: 3.12",
|
|
46
45
|
"Programming Language :: Python :: 3.13",
|
|
@@ -59,6 +58,7 @@ dependencies = [
|
|
|
59
58
|
"python-fsutil >= 0.16.0, < 1.0.0",
|
|
60
59
|
]
|
|
61
60
|
dynamic = ["version"]
|
|
61
|
+
requires-python = ">=3.11"
|
|
62
62
|
maintainers = [
|
|
63
63
|
{ name = "Fabio Caccamo", email = "fabio.caccamo@gmail.com" },
|
|
64
64
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fontbro
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.32.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>
|
|
@@ -43,7 +43,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
43
43
|
Classifier: Natural Language :: English
|
|
44
44
|
Classifier: Operating System :: OS Independent
|
|
45
45
|
Classifier: Programming Language :: Python :: 3
|
|
46
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
47
46
|
Classifier: Programming Language :: Python :: 3.11
|
|
48
47
|
Classifier: Programming Language :: Python :: 3.12
|
|
49
48
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -52,6 +51,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
52
51
|
Classifier: Topic :: Text Processing :: Fonts
|
|
53
52
|
Classifier: Topic :: Multimedia :: Graphics
|
|
54
53
|
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
54
|
+
Requires-Python: >=3.11
|
|
55
55
|
Description-Content-Type: text/markdown
|
|
56
56
|
License-File: LICENSE.txt
|
|
57
57
|
Requires-Dist: fonttools[lxml,pathops,unicode,woff]<5.0,>=4.43.0
|
|
@@ -184,7 +184,7 @@ font_clone = font.clone()
|
|
|
184
184
|
```python
|
|
185
185
|
"""
|
|
186
186
|
Close the wrapped TTFont instance.
|
|
187
|
-
After closing, any operation on the font raises
|
|
187
|
+
After closing, any operation on the font raises ClosedFontError,
|
|
188
188
|
closing an already closed font does nothing.
|
|
189
189
|
"""
|
|
190
190
|
|
|
@@ -631,7 +631,7 @@ Gets the wrapped TTFont instance.
|
|
|
631
631
|
:returns: The TTFont instance.
|
|
632
632
|
:rtype: TTFont
|
|
633
633
|
|
|
634
|
-
:raises
|
|
634
|
+
:raises ClosedFontError: If the font has been closed.
|
|
635
635
|
"""
|
|
636
636
|
|
|
637
637
|
ttfont = font.get_ttfont()
|
|
@@ -1011,7 +1011,7 @@ If family_name is not defined it will be auto-detected.
|
|
|
1011
1011
|
(only if the default instance style name is renamed)
|
|
1012
1012
|
:type update_style_flags: bool
|
|
1013
1013
|
|
|
1014
|
-
:raises
|
|
1014
|
+
:raises VariableFontRequiredError: if the font is not a variable font.
|
|
1015
1015
|
"""
|
|
1016
1016
|
|
|
1017
1017
|
font.rename_variable_instances(
|
|
@@ -1131,7 +1131,7 @@ Save all instances of a variable font to specified directory in one or more form
|
|
|
1131
1131
|
includes 'instance' (containing instance metadata) and 'files' (a dictionary
|
|
1132
1132
|
with file formats as keys and file-paths as values).
|
|
1133
1133
|
|
|
1134
|
-
:raises
|
|
1134
|
+
:raises VariableFontRequiredError: If the font is not a variable font.
|
|
1135
1135
|
"""
|
|
1136
1136
|
|
|
1137
1137
|
saved_fonts = font.save_variable_instances(
|
|
@@ -1167,7 +1167,7 @@ The fsType field is left untouched if any argument is invalid.
|
|
|
1167
1167
|
:raises ArgumentError: If a value is not a bool or None, if more than one
|
|
1168
1168
|
of the mutually exclusive usage permissions is set to True, or if
|
|
1169
1169
|
installable is set to False while no other usage permission is in effect.
|
|
1170
|
-
:raises
|
|
1170
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1171
1171
|
"""
|
|
1172
1172
|
|
|
1173
1173
|
font.set_embedding_permissions(
|
|
@@ -1187,7 +1187,7 @@ based on provided class_id and subclass_id.
|
|
|
1187
1187
|
|
|
1188
1188
|
:param class_id: Numeric identifier of the font family class.
|
|
1189
1189
|
:param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
|
|
1190
|
-
:raises
|
|
1190
|
+
:raises MissingTableError: If the OS/2 table is not available in the font.
|
|
1191
1191
|
:raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
|
|
1192
1192
|
"""
|
|
1193
1193
|
|
|
@@ -1405,7 +1405,7 @@ If an axis min and max values are equal, the axis will be pinned.
|
|
|
1405
1405
|
:param options: The options for the fontTools.varLib.instancer
|
|
1406
1406
|
:type options: dictionary
|
|
1407
1407
|
|
|
1408
|
-
:raises
|
|
1408
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1409
1409
|
:raises ValueError: If the coordinates are not defined (empty)
|
|
1410
1410
|
:raises ValueError: If the coordinates axes are all pinned
|
|
1411
1411
|
"""
|
|
@@ -1438,7 +1438,7 @@ If coordinates are not specified each axis will be pinned at its default value.
|
|
|
1438
1438
|
:param options: The options for the fontTools.varLib.instancer
|
|
1439
1439
|
:type options: dictionary
|
|
1440
1440
|
|
|
1441
|
-
:raises
|
|
1441
|
+
:raises VariableFontRequiredError: If the font is not a variable font
|
|
1442
1442
|
:raises ValueError: If the coordinates axes are not all pinned
|
|
1443
1443
|
"""
|
|
1444
1444
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import pickle
|
|
3
|
+
|
|
4
|
+
from fontbro.exceptions import (
|
|
5
|
+
ClosedFontError,
|
|
6
|
+
MissingTableError,
|
|
7
|
+
OperationError,
|
|
8
|
+
VariableFontRequiredError,
|
|
9
|
+
)
|
|
10
|
+
from tests import AbstractTestCase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExceptionsTestCase(AbstractTestCase):
|
|
14
|
+
"""
|
|
15
|
+
Test case for the specific exceptions (subclasses of OperationError).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def _get_static_font(self):
|
|
19
|
+
return self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
|
|
20
|
+
|
|
21
|
+
def _get_variable_font(self):
|
|
22
|
+
return self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
|
|
23
|
+
|
|
24
|
+
def _assert_missing_table_error(self, context, table_tag):
|
|
25
|
+
error = context.exception
|
|
26
|
+
self.assertIsInstance(error, OperationError)
|
|
27
|
+
self.assertIsInstance(error, KeyError)
|
|
28
|
+
self.assertEqual(error.table_tag, table_tag)
|
|
29
|
+
self.assertEqual(str(error), f"Invalid {table_tag} table (doesn't exist).")
|
|
30
|
+
|
|
31
|
+
def test_closed_font_error(self):
|
|
32
|
+
font = self._get_static_font()
|
|
33
|
+
font.close()
|
|
34
|
+
with self.assertRaises(ClosedFontError) as context:
|
|
35
|
+
font.get_names()
|
|
36
|
+
self.assertIsInstance(context.exception, OperationError)
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
str(context.exception), "Invalid operation, the font is closed."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def test_missing_table_error_with_custom_message(self):
|
|
42
|
+
error = MissingTableError("OS/2", "Custom message.")
|
|
43
|
+
self.assertEqual(error.table_tag, "OS/2")
|
|
44
|
+
self.assertEqual(str(error), "Custom message.")
|
|
45
|
+
|
|
46
|
+
def test_missing_table_error_pickle_and_copy(self):
|
|
47
|
+
# regression: the exception was recreated passing the message as table tag
|
|
48
|
+
for message in (None, "Custom message."):
|
|
49
|
+
error = MissingTableError("OS/2", message)
|
|
50
|
+
for error_copy in (pickle.loads(pickle.dumps(error)), copy.copy(error)):
|
|
51
|
+
with self.subTest(message=message):
|
|
52
|
+
self.assertEqual(error_copy.table_tag, "OS/2")
|
|
53
|
+
self.assertEqual(str(error_copy), str(error))
|
|
54
|
+
|
|
55
|
+
def test_missing_table_error_set_embedding_permissions(self):
|
|
56
|
+
font = self._get_static_font()
|
|
57
|
+
del font.get_ttfont()["OS/2"]
|
|
58
|
+
with self.assertRaises(MissingTableError) as context:
|
|
59
|
+
font.set_embedding_permissions(installable=True)
|
|
60
|
+
self._assert_missing_table_error(context, "OS/2")
|
|
61
|
+
|
|
62
|
+
def test_missing_table_error_set_family_classification(self):
|
|
63
|
+
font = self._get_static_font()
|
|
64
|
+
del font.get_ttfont()["OS/2"]
|
|
65
|
+
with self.assertRaises(MissingTableError) as context:
|
|
66
|
+
font.set_family_classification(class_id=1)
|
|
67
|
+
self._assert_missing_table_error(context, "OS/2")
|
|
68
|
+
|
|
69
|
+
def test_missing_table_error_set_name(self):
|
|
70
|
+
font = self._get_static_font()
|
|
71
|
+
del font.get_ttfont()["name"]
|
|
72
|
+
with self.assertRaises(MissingTableError) as context:
|
|
73
|
+
font.set_name("family_name", "Roboto Mono New")
|
|
74
|
+
self._assert_missing_table_error(context, "name")
|
|
75
|
+
|
|
76
|
+
def test_missing_table_error_get_variable_instances(self):
|
|
77
|
+
font = self._get_variable_font()
|
|
78
|
+
del font.get_ttfont()["name"]
|
|
79
|
+
with self.assertRaises(MissingTableError) as context:
|
|
80
|
+
font.get_variable_instances()
|
|
81
|
+
self._assert_missing_table_error(context, "name")
|
|
82
|
+
|
|
83
|
+
def test_missing_table_error_is_monospace(self):
|
|
84
|
+
font = self._get_static_font()
|
|
85
|
+
del font.get_ttfont()["hmtx"]
|
|
86
|
+
with self.assertRaises(MissingTableError) as context:
|
|
87
|
+
font.is_monospace()
|
|
88
|
+
self._assert_missing_table_error(context, "hmtx")
|
|
89
|
+
|
|
90
|
+
def test_missing_table_error_is_pixel(self):
|
|
91
|
+
font = self._get_font("/Silkscreen/Silkscreen-Regular.ttf")
|
|
92
|
+
ttfont = font.get_ttfont()
|
|
93
|
+
# load the glyphs before removing the head table (needed to load them)
|
|
94
|
+
ttfont["glyf"].ensureDecompiled()
|
|
95
|
+
del ttfont["head"]
|
|
96
|
+
with self.assertRaises(MissingTableError) as context:
|
|
97
|
+
font.is_pixel()
|
|
98
|
+
self._assert_missing_table_error(context, "head")
|
|
99
|
+
|
|
100
|
+
def test_missing_table_error_get_svg(self):
|
|
101
|
+
for table_tag in ("head", "hhea"):
|
|
102
|
+
with self.subTest(table_tag=table_tag):
|
|
103
|
+
font = self._get_static_font()
|
|
104
|
+
del font.get_ttfont()[table_tag]
|
|
105
|
+
with self.assertRaises(MissingTableError) as context:
|
|
106
|
+
font.get_svg(text="Hello", size=12)
|
|
107
|
+
self._assert_missing_table_error(context, table_tag)
|
|
108
|
+
|
|
109
|
+
def test_variable_font_required_error(self):
|
|
110
|
+
operations = [
|
|
111
|
+
(
|
|
112
|
+
lambda font: font.rename_variable_instances(family_name="New"),
|
|
113
|
+
"Only the instances of a variable font can be renamed.",
|
|
114
|
+
),
|
|
115
|
+
(
|
|
116
|
+
lambda font: font.save_variable_instances(self._get_font_temp_path()),
|
|
117
|
+
"Only a variable font can be instantiated.",
|
|
118
|
+
),
|
|
119
|
+
(
|
|
120
|
+
lambda font: font.to_sliced_variable(coordinates={"wght": (100, 400)}),
|
|
121
|
+
"Only a variable font can be sliced.",
|
|
122
|
+
),
|
|
123
|
+
(
|
|
124
|
+
lambda font: font.to_static(),
|
|
125
|
+
"Only a variable font can be made static.",
|
|
126
|
+
),
|
|
127
|
+
]
|
|
128
|
+
for operation, message in operations:
|
|
129
|
+
with self.subTest(message=message):
|
|
130
|
+
font = self._get_static_font()
|
|
131
|
+
with self.assertRaises(VariableFontRequiredError) as context:
|
|
132
|
+
operation(font)
|
|
133
|
+
self.assertIsInstance(context.exception, OperationError)
|
|
134
|
+
self.assertEqual(str(context.exception), message)
|
|
@@ -76,6 +76,15 @@ class InstantiationTestCase(AbstractTestCase):
|
|
|
76
76
|
name_ids = {record.nameID for record in font.get_ttfont()["name"].names}
|
|
77
77
|
self.assertEqual({name_id for name_id in name_ids if name_id > 255}, set())
|
|
78
78
|
|
|
79
|
+
def test_to_static_without_stat_table(self):
|
|
80
|
+
font = self._get_variable_font()
|
|
81
|
+
ttfont = font.get_ttfont()
|
|
82
|
+
del ttfont["STAT"]
|
|
83
|
+
font.to_static(style_name="Black")
|
|
84
|
+
self.assertFalse(font.is_variable())
|
|
85
|
+
self.assertNotIn("STAT", ttfont)
|
|
86
|
+
self.assertEqual(font.get_name(Font.NAME_POSTSCRIPT_NAME), "Tourney-Black")
|
|
87
|
+
|
|
79
88
|
def test_to_static_with_style_name_invalid(self):
|
|
80
89
|
font = self._get_variable_font()
|
|
81
90
|
with self.assertRaises(ValueError):
|
|
@@ -448,6 +448,36 @@ class RenameTestCase(AbstractTestCase):
|
|
|
448
448
|
}
|
|
449
449
|
self.assertEqual(names["Hair Italic"], "TourneyItalic-Hair")
|
|
450
450
|
|
|
451
|
+
def test_rename_variable_font_without_default_instance(self):
|
|
452
|
+
font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
|
|
453
|
+
fvar = font.get_ttfont()["fvar"]
|
|
454
|
+
# remove the default instance ("Thin")
|
|
455
|
+
fvar.instances.pop(0)
|
|
456
|
+
font.rename(style_name="Hair Line")
|
|
457
|
+
self.assertEqual(font.get_name(Font.NAME_POSTSCRIPT_NAME), "Tourney-HairLine")
|
|
458
|
+
self.assertEqual(font.get_name(fvar.instances[0].subfamilyNameID), "ExtraLight")
|
|
459
|
+
|
|
460
|
+
def test_rename_variable_font_without_instances_postscript_names(self):
|
|
461
|
+
font = self._get_font("/Inter/Inter-VariableFont_slnt,wght.ttf")
|
|
462
|
+
fvar = font.get_ttfont()["fvar"]
|
|
463
|
+
font.rename(family_name="Inter New")
|
|
464
|
+
self.assertEqual(font.get_name(Font.NAME_POSTSCRIPT_NAME), "InterNew-Regular")
|
|
465
|
+
for instance in fvar.instances:
|
|
466
|
+
self.assertEqual(instance.postscriptNameID, 0xFFFF)
|
|
467
|
+
|
|
468
|
+
def test_rename_variable_instances_with_style_name_shared_with_font_style_name(
|
|
469
|
+
self,
|
|
470
|
+
):
|
|
471
|
+
# the instance style name record is the font subfamily name (2),
|
|
472
|
+
# so a new name record is added for the instance
|
|
473
|
+
font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
|
|
474
|
+
instance = font.get_ttfont()["fvar"].instances[1]
|
|
475
|
+
instance.subfamilyNameID = 2
|
|
476
|
+
font.rename_variable_instances(style_names={"regular": "Normal"})
|
|
477
|
+
self.assertGreater(instance.subfamilyNameID, 255)
|
|
478
|
+
self.assertEqual(font.get_name(instance.subfamilyNameID), "Normal")
|
|
479
|
+
self.assertEqual(font.get_name(Font.NAME_SUBFAMILY_NAME), "Regular")
|
|
480
|
+
|
|
451
481
|
def test_rename_variable_instances_with_static_font(self):
|
|
452
482
|
font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
|
|
453
483
|
with self.assertRaises(TypeError):
|
|
@@ -12,18 +12,18 @@ class SaveTestCase(AbstractTestCase):
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
def test_save_with_font_src_path_as_filepath_with_overwrite(self):
|
|
15
|
-
font_filepath = self.
|
|
15
|
+
font_filepath = self._get_font_temp_copy_path(
|
|
16
16
|
"/Roboto_Mono/static/RobotoMono-Regular.ttf"
|
|
17
17
|
)
|
|
18
|
-
font =
|
|
18
|
+
font = Font(font_filepath)
|
|
19
19
|
font_saved_path = font.save(font_filepath, overwrite=True)
|
|
20
20
|
self.assertEqual(font_saved_path, font_filepath)
|
|
21
21
|
|
|
22
22
|
def test_save_without_filepath_with_overwrite(self):
|
|
23
|
-
font_filepath = self.
|
|
23
|
+
font_filepath = self._get_font_temp_copy_path(
|
|
24
24
|
"/Roboto_Mono/static/RobotoMono-Regular.ttf"
|
|
25
25
|
)
|
|
26
|
-
font =
|
|
26
|
+
font = Font(font_filepath)
|
|
27
27
|
font_saved_path = font.save(overwrite=True)
|
|
28
28
|
self.assertEqual(font_saved_path, font_filepath)
|
|
29
29
|
|
|
@@ -41,7 +41,9 @@ class SaveTestCase(AbstractTestCase):
|
|
|
41
41
|
font.save(font_filepath)
|
|
42
42
|
|
|
43
43
|
def test_save_fileobject(self):
|
|
44
|
-
font_filepath = self.
|
|
44
|
+
font_filepath = self._get_font_temp_copy_path(
|
|
45
|
+
"/Noto_Sans_TC/NotoSansTC-Regular.otf"
|
|
46
|
+
)
|
|
45
47
|
with open(font_filepath, "rb") as fh:
|
|
46
48
|
font = Font(fh)
|
|
47
49
|
font_saved_path = font.save(font_filepath, overwrite=True)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fsutil
|
|
2
|
+
|
|
3
|
+
from scripts.update_data import update_unicode_data
|
|
4
|
+
from tests import AbstractTestCase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UpdateUnicodeDataTestCase(AbstractTestCase):
|
|
8
|
+
"""
|
|
9
|
+
Test case for updating the pre-computed unicode data.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def test_update_unicode_data(self):
|
|
13
|
+
# the data files are written to the temp directory,
|
|
14
|
+
# the package data files are not modified
|
|
15
|
+
dirpath = self._get_font_temp_path()
|
|
16
|
+
update_unicode_data(dirpath=dirpath)
|
|
17
|
+
for filename in ("unicode-blocks.json", "unicode-scripts.json"):
|
|
18
|
+
data = fsutil.read_file_json(fsutil.join_path(dirpath, filename))
|
|
19
|
+
self.assertTrue(len(data) > 0)
|
|
20
|
+
self.assertTrue(all(item["characters_total"] > 0 for item in data))
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from scripts.update_data import update_unicode_data
|
|
2
|
-
from tests import AbstractTestCase
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class UpdateUnicodeDataTestCase(AbstractTestCase):
|
|
6
|
-
"""
|
|
7
|
-
Test case for updating the pre-computed unicode data.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
def test_update_unicode_data(self):
|
|
11
|
-
update_unicode_data()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|