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.
Files changed (89) hide show
  1. {python_fontbro-0.31.0/python_fontbro.egg-info → python_fontbro-0.32.0}/PKG-INFO +10 -10
  2. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/README.md +8 -8
  3. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/embedding_permissions.py +3 -4
  4. python_fontbro-0.32.0/fontbro/exceptions.py +53 -0
  5. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/family_classification.py +3 -4
  6. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/font.py +12 -11
  7. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/metadata.py +1 -1
  8. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/monospace.py +3 -1
  9. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/names.py +6 -3
  10. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/pixel.py +2 -1
  11. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/render.py +3 -2
  12. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/tables.py +15 -0
  13. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/variable.py +5 -4
  14. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/pyproject.toml +1 -1
  15. {python_fontbro-0.31.0 → python_fontbro-0.32.0/python_fontbro.egg-info}/PKG-INFO +10 -10
  16. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/SOURCES.txt +1 -0
  17. python_fontbro-0.32.0/tests/test_exceptions.py +134 -0
  18. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_instantiation.py +9 -0
  19. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_rename.py +30 -0
  20. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_save.py +7 -5
  21. python_fontbro-0.32.0/tests/test_update_unicode_data.py +20 -0
  22. python_fontbro-0.31.0/fontbro/exceptions.py +0 -14
  23. python_fontbro-0.31.0/tests/test_update_unicode_data.py +0 -11
  24. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/LICENSE.txt +0 -0
  25. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/MANIFEST.in +0 -0
  26. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/__init__.py +0 -0
  27. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/bitmap.py +0 -0
  28. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/color.py +0 -0
  29. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/family-classifications.json +0 -0
  30. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/features.json +0 -0
  31. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/unicode-blocks.json +0 -0
  32. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/data/unicode-scripts.json +0 -0
  33. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/features.py +0 -0
  34. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/files.py +0 -0
  35. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/fingerprint.py +0 -0
  36. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/flags.py +0 -0
  37. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/glyphs.py +0 -0
  38. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/math.py +0 -0
  39. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/metrics.py +0 -0
  40. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/py.typed +0 -0
  41. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/sanitize.py +0 -0
  42. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/style_flags.py +0 -0
  43. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/subset.py +0 -0
  44. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/support.py +0 -0
  45. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/unicode.py +0 -0
  46. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/utils.py +0 -0
  47. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/fontbro/version.py +0 -0
  48. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
  49. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/requires.txt +0 -0
  50. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/top_level.txt +0 -0
  51. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/setup.cfg +0 -0
  52. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/setup.py +0 -0
  53. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_bitmap.py +0 -0
  54. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_characters.py +0 -0
  55. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_clone.py +0 -0
  56. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_close.py +0 -0
  57. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_collection.py +0 -0
  58. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_color.py +0 -0
  59. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_context_manager.py +0 -0
  60. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_embedding_permissions.py +0 -0
  61. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_family_classification.py +0 -0
  62. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_family_name.py +0 -0
  63. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_features.py +0 -0
  64. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_filename.py +0 -0
  65. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_fingerprint.py +0 -0
  66. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_format.py +0 -0
  67. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_glyphs.py +0 -0
  68. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_image.py +0 -0
  69. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_init.py +0 -0
  70. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_issues.py +0 -0
  71. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_italic_angle.py +0 -0
  72. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_monospace.py +0 -0
  73. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_names.py +0 -0
  74. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_pixel.py +0 -0
  75. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_sanitize.py +0 -0
  76. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_str.py +0 -0
  77. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_style_flags.py +0 -0
  78. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_style_name.py +0 -0
  79. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_subset.py +0 -0
  80. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_support.py +0 -0
  81. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_svg.py +0 -0
  82. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_tables.py +0 -0
  83. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_unicode.py +0 -0
  84. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_unicode_blocks_and_scripts.py +0 -0
  85. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_variable.py +0 -0
  86. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_version.py +0 -0
  87. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_vertical_metrics.py +0 -0
  88. {python_fontbro-0.31.0 → python_fontbro-0.32.0}/tests/test_weight.py +0 -0
  89. {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.31.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 OperationError,
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 OperationError: If the font has been closed.
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 OperationError: if the font is not a variable font.
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 TypeError: If the font is not a variable font.
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 OperationError: If the OS/2 table is not available in the font.
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 OperationError: If the OS/2 table is not available in the font.
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 TypeError: If the font is not a variable font
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 TypeError: If the font is not a variable font
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 OperationError,
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 OperationError: If the font has been closed.
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 OperationError: if the font is not a variable font.
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 TypeError: If the font is not a variable font.
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 OperationError: If the OS/2 table is not available in the font.
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 OperationError: If the OS/2 table is not available in the font.
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 TypeError: If the font is not a variable font
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 TypeError: If the font is not a variable font
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, OperationError
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.get("OS/2")
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, OperationError
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.get("OS/2")
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
- OperationError,
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 OperationError,
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 OperationError: If the font has been closed.
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 OperationError("Invalid operation, the font is closed.")
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 OperationError: if the font is not a variable font.
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 TypeError: If the font is not a variable font.
1516
+ :raises VariableFontRequiredError: If the font is not a variable font.
1516
1517
  """
1517
1518
  if not self.is_variable():
1518
- raise OperationError("Only a variable font can be instantiated.")
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 OperationError: If the OS/2 table is not available in the font.
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 OperationError: If the OS/2 table is not available in the font.
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 TypeError: If the font is not a variable font
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 TypeError: If the font is not a variable font
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()
@@ -4,4 +4,4 @@ __description__ = "friendly font operations on top of fontTools."
4
4
  __email__ = "fabio.caccamo@gmail.com"
5
5
  __license__ = "MIT"
6
6
  __title__ = "python-fontbro"
7
- __version__ = "0.31.0"
7
+ __version__ = "0.32.0"
@@ -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 ttfont["hmtx"].metrics.items()
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, OperationError
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["name"]
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 OperationError("Only the instances of a variable font can be renamed.")
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["head"].unitsPerEm
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["head"].unitsPerEm
55
+ units_per_em = get_table_or_raise(ttfont, "head").unitsPerEm
55
56
  scale = size / units_per_em
56
- hhea = ttfont["hhea"]
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, OperationError
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["name"]
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 OperationError("Only a variable font can be sliced.")
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 OperationError("Only a variable font can be made static.")
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.31.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 OperationError,
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 OperationError: If the font has been closed.
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 OperationError: if the font is not a variable font.
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 TypeError: If the font is not a variable font.
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 OperationError: If the OS/2 table is not available in the font.
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 OperationError: If the OS/2 table is not available in the font.
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 TypeError: If the font is not a variable font
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 TypeError: If the font is not a variable font
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
 
@@ -49,6 +49,7 @@ tests/test_collection.py
49
49
  tests/test_color.py
50
50
  tests/test_context_manager.py
51
51
  tests/test_embedding_permissions.py
52
+ tests/test_exceptions.py
52
53
  tests/test_family_classification.py
53
54
  tests/test_family_name.py
54
55
  tests/test_features.py
@@ -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._get_font_path(
15
+ font_filepath = self._get_font_temp_copy_path(
16
16
  "/Roboto_Mono/static/RobotoMono-Regular.ttf"
17
17
  )
18
- font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
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._get_font_path(
23
+ font_filepath = self._get_font_temp_copy_path(
24
24
  "/Roboto_Mono/static/RobotoMono-Regular.ttf"
25
25
  )
26
- font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
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._get_font_path("/Noto_Sans_TC/NotoSansTC-Regular.otf")
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,14 +0,0 @@
1
- class ArgumentError(ValueError):
2
- pass
3
-
4
-
5
- class DataError(ValueError):
6
- pass
7
-
8
-
9
- class OperationError(TypeError):
10
- pass
11
-
12
-
13
- class SanitizationError(Exception):
14
- pass
@@ -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()