python-fontbro 0.30.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 (90) hide show
  1. {python_fontbro-0.30.0/python_fontbro.egg-info → python_fontbro-0.32.0}/PKG-INFO +57 -10
  2. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/README.md +55 -8
  3. {python_fontbro-0.30.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.30.0 → python_fontbro-0.32.0}/fontbro/family_classification.py +3 -4
  6. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/font.py +70 -11
  7. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/metadata.py +1 -1
  8. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/monospace.py +3 -1
  9. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/names.py +257 -18
  10. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/pixel.py +2 -1
  11. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/render.py +3 -2
  12. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/tables.py +15 -0
  13. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/variable.py +37 -4
  14. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/pyproject.toml +1 -1
  15. {python_fontbro-0.30.0 → python_fontbro-0.32.0/python_fontbro.egg-info}/PKG-INFO +57 -10
  16. {python_fontbro-0.30.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.30.0 → python_fontbro-0.32.0}/tests/test_instantiation.py +55 -0
  19. python_fontbro-0.32.0/tests/test_rename.py +484 -0
  20. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_save.py +7 -5
  21. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_unicode_blocks_and_scripts.py +16 -16
  22. python_fontbro-0.32.0/tests/test_update_unicode_data.py +20 -0
  23. python_fontbro-0.30.0/fontbro/exceptions.py +0 -14
  24. python_fontbro-0.30.0/tests/test_rename.py +0 -163
  25. python_fontbro-0.30.0/tests/test_update_unicode_data.py +0 -11
  26. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/LICENSE.txt +0 -0
  27. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/MANIFEST.in +0 -0
  28. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/__init__.py +0 -0
  29. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/bitmap.py +0 -0
  30. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/color.py +0 -0
  31. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/data/family-classifications.json +0 -0
  32. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/data/features.json +0 -0
  33. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/data/unicode-blocks.json +0 -0
  34. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/data/unicode-scripts.json +0 -0
  35. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/features.py +0 -0
  36. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/files.py +0 -0
  37. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/fingerprint.py +0 -0
  38. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/flags.py +0 -0
  39. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/glyphs.py +0 -0
  40. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/math.py +0 -0
  41. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/metrics.py +0 -0
  42. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/py.typed +0 -0
  43. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/sanitize.py +0 -0
  44. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/style_flags.py +0 -0
  45. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/subset.py +0 -0
  46. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/support.py +0 -0
  47. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/unicode.py +0 -0
  48. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/utils.py +0 -0
  49. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/fontbro/version.py +0 -0
  50. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
  51. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/requires.txt +0 -0
  52. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/python_fontbro.egg-info/top_level.txt +0 -0
  53. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/setup.cfg +0 -0
  54. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/setup.py +0 -0
  55. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_bitmap.py +0 -0
  56. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_characters.py +0 -0
  57. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_clone.py +0 -0
  58. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_close.py +0 -0
  59. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_collection.py +0 -0
  60. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_color.py +0 -0
  61. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_context_manager.py +0 -0
  62. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_embedding_permissions.py +0 -0
  63. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_family_classification.py +0 -0
  64. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_family_name.py +0 -0
  65. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_features.py +0 -0
  66. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_filename.py +0 -0
  67. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_fingerprint.py +0 -0
  68. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_format.py +0 -0
  69. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_glyphs.py +0 -0
  70. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_image.py +0 -0
  71. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_init.py +0 -0
  72. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_issues.py +0 -0
  73. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_italic_angle.py +0 -0
  74. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_monospace.py +0 -0
  75. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_names.py +0 -0
  76. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_pixel.py +0 -0
  77. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_sanitize.py +0 -0
  78. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_str.py +0 -0
  79. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_style_flags.py +0 -0
  80. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_style_name.py +0 -0
  81. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_subset.py +0 -0
  82. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_support.py +0 -0
  83. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_svg.py +0 -0
  84. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_tables.py +0 -0
  85. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_unicode.py +0 -0
  86. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_variable.py +0 -0
  87. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_version.py +0 -0
  88. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_vertical_metrics.py +0 -0
  89. {python_fontbro-0.30.0 → python_fontbro-0.32.0}/tests/test_weight.py +0 -0
  90. {python_fontbro-0.30.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.30.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
@@ -149,6 +149,7 @@ with open("fonts/MyFont.ttf") as fh:
149
149
  - [`is_static`](#is_static)
150
150
  - [`is_variable`](#is_variable)
151
151
  - [`rename`](#rename)
152
+ - [`rename_variable_instances`](#rename_variable_instances)
152
153
  - [`sanitize`](#sanitize)
153
154
  - [`save`](#save)
154
155
  - [`save_as_woff`](#save_as_woff)
@@ -183,7 +184,7 @@ font_clone = font.clone()
183
184
  ```python
184
185
  """
185
186
  Close the wrapped TTFont instance.
186
- After closing, any operation on the font raises OperationError,
187
+ After closing, any operation on the font raises ClosedFontError,
187
188
  closing an already closed font does nothing.
188
189
  """
189
190
 
@@ -630,7 +631,7 @@ Gets the wrapped TTFont instance.
630
631
  :returns: The TTFont instance.
631
632
  :rtype: TTFont
632
633
 
633
- :raises OperationError: If the font has been closed.
634
+ :raises ClosedFontError: If the font has been closed.
634
635
  """
635
636
 
636
637
  ttfont = font.get_ttfont()
@@ -966,6 +967,8 @@ variable = font.is_variable()
966
967
  """
967
968
  Renames the font names records (1, 2, 4, 6, 16, 17) according to
968
969
  the given family_name and style_name (subfamily_name).
970
+ The names records (18, 25), the fvar named instances PostScript names
971
+ and the CFF table names are updated too, only if already defined.
969
972
 
970
973
  If family_name is not defined it will be auto-detected.
971
974
  If style_name is not defined it will be auto-detected.
@@ -985,6 +988,39 @@ font.rename(
985
988
  )
986
989
  ```
987
990
 
991
+ #### `rename_variable_instances`
992
+ ```python
993
+ """
994
+ Renames the named instances of the variable font.
995
+
996
+ The family_name replaces the family part of the instances PostScript names
997
+ (and of the variations PostScript name prefix, if defined).
998
+ The style_names replace the instances style names and the style part
999
+ of their PostScript names. The axes names are unchanged.
1000
+ The default instance style name must match the font style name,
1001
+ so renaming it renames the font style too.
1002
+
1003
+ If family_name is not defined it will be auto-detected.
1004
+
1005
+ :param family_name: The family name
1006
+ :type family_name: str
1007
+ :param style_names: The new style names mapped by the old ones (case-insensitive),
1008
+ eg. {"Thin": "Hairline"}
1009
+ :type style_names: dict or None
1010
+ :param update_style_flags: if True the style flags will be updated by subfamily name
1011
+ (only if the default instance style name is renamed)
1012
+ :type update_style_flags: bool
1013
+
1014
+ :raises VariableFontRequiredError: if the font is not a variable font.
1015
+ """
1016
+
1017
+ font.rename_variable_instances(
1018
+ family_name="My Font New",
1019
+ style_names={"Thin": "Hairline"},
1020
+ update_style_flags=True,
1021
+ )
1022
+ ```
1023
+
988
1024
  #### `sanitize`
989
1025
  ```python
990
1026
  """
@@ -1095,7 +1131,7 @@ Save all instances of a variable font to specified directory in one or more form
1095
1131
  includes 'instance' (containing instance metadata) and 'files' (a dictionary
1096
1132
  with file formats as keys and file-paths as values).
1097
1133
 
1098
- :raises TypeError: If the font is not a variable font.
1134
+ :raises VariableFontRequiredError: If the font is not a variable font.
1099
1135
  """
1100
1136
 
1101
1137
  saved_fonts = font.save_variable_instances(
@@ -1131,7 +1167,7 @@ The fsType field is left untouched if any argument is invalid.
1131
1167
  :raises ArgumentError: If a value is not a bool or None, if more than one
1132
1168
  of the mutually exclusive usage permissions is set to True, or if
1133
1169
  installable is set to False while no other usage permission is in effect.
1134
- :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.
1135
1171
  """
1136
1172
 
1137
1173
  font.set_embedding_permissions(
@@ -1151,7 +1187,7 @@ based on provided class_id and subclass_id.
1151
1187
 
1152
1188
  :param class_id: Numeric identifier of the font family class.
1153
1189
  :param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
1154
- :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.
1155
1191
  :raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
1156
1192
  """
1157
1193
 
@@ -1360,15 +1396,26 @@ If an axis min and max values are equal, the axis will be pinned.
1360
1396
  {'wdth':100, 'wght':[100,600], 'ital':[30,65,70]} or
1361
1397
  {'wdth':100, 'wght':{'min':100,'max':600}, 'ital':{'min':30,'default':65,'max':70}}
1362
1398
  :type coordinates: dict
1399
+ :param update_names: if True the name records will be updated based on
1400
+ the closest instance to the new default coordinates (only if changed)
1401
+ :type update_names: bool
1402
+ :param update_style_flags: if True the style flags will be updated based on
1403
+ the closest instance to the new default coordinates (only if changed)
1404
+ :type update_style_flags: bool
1363
1405
  :param options: The options for the fontTools.varLib.instancer
1364
1406
  :type options: dictionary
1365
1407
 
1366
- :raises TypeError: If the font is not a variable font
1408
+ :raises VariableFontRequiredError: If the font is not a variable font
1367
1409
  :raises ValueError: If the coordinates are not defined (empty)
1368
1410
  :raises ValueError: If the coordinates axes are all pinned
1369
1411
  """
1370
1412
 
1371
- font.to_sliced_variable(coordinates, **options)
1413
+ font.to_sliced_variable(
1414
+ coordinates,
1415
+ update_names=True,
1416
+ update_style_flags=True,
1417
+ **options,
1418
+ )
1372
1419
  ```
1373
1420
 
1374
1421
  #### `to_static`
@@ -1391,7 +1438,7 @@ If coordinates are not specified each axis will be pinned at its default value.
1391
1438
  :param options: The options for the fontTools.varLib.instancer
1392
1439
  :type options: dictionary
1393
1440
 
1394
- :raises TypeError: If the font is not a variable font
1441
+ :raises VariableFontRequiredError: If the font is not a variable font
1395
1442
  :raises ValueError: If the coordinates axes are not all pinned
1396
1443
  """
1397
1444
 
@@ -85,6 +85,7 @@ with open("fonts/MyFont.ttf") as fh:
85
85
  - [`is_static`](#is_static)
86
86
  - [`is_variable`](#is_variable)
87
87
  - [`rename`](#rename)
88
+ - [`rename_variable_instances`](#rename_variable_instances)
88
89
  - [`sanitize`](#sanitize)
89
90
  - [`save`](#save)
90
91
  - [`save_as_woff`](#save_as_woff)
@@ -119,7 +120,7 @@ font_clone = font.clone()
119
120
  ```python
120
121
  """
121
122
  Close the wrapped TTFont instance.
122
- After closing, any operation on the font raises OperationError,
123
+ After closing, any operation on the font raises ClosedFontError,
123
124
  closing an already closed font does nothing.
124
125
  """
125
126
 
@@ -566,7 +567,7 @@ Gets the wrapped TTFont instance.
566
567
  :returns: The TTFont instance.
567
568
  :rtype: TTFont
568
569
 
569
- :raises OperationError: If the font has been closed.
570
+ :raises ClosedFontError: If the font has been closed.
570
571
  """
571
572
 
572
573
  ttfont = font.get_ttfont()
@@ -902,6 +903,8 @@ variable = font.is_variable()
902
903
  """
903
904
  Renames the font names records (1, 2, 4, 6, 16, 17) according to
904
905
  the given family_name and style_name (subfamily_name).
906
+ The names records (18, 25), the fvar named instances PostScript names
907
+ and the CFF table names are updated too, only if already defined.
905
908
 
906
909
  If family_name is not defined it will be auto-detected.
907
910
  If style_name is not defined it will be auto-detected.
@@ -921,6 +924,39 @@ font.rename(
921
924
  )
922
925
  ```
923
926
 
927
+ #### `rename_variable_instances`
928
+ ```python
929
+ """
930
+ Renames the named instances of the variable font.
931
+
932
+ The family_name replaces the family part of the instances PostScript names
933
+ (and of the variations PostScript name prefix, if defined).
934
+ The style_names replace the instances style names and the style part
935
+ of their PostScript names. The axes names are unchanged.
936
+ The default instance style name must match the font style name,
937
+ so renaming it renames the font style too.
938
+
939
+ If family_name is not defined it will be auto-detected.
940
+
941
+ :param family_name: The family name
942
+ :type family_name: str
943
+ :param style_names: The new style names mapped by the old ones (case-insensitive),
944
+ eg. {"Thin": "Hairline"}
945
+ :type style_names: dict or None
946
+ :param update_style_flags: if True the style flags will be updated by subfamily name
947
+ (only if the default instance style name is renamed)
948
+ :type update_style_flags: bool
949
+
950
+ :raises VariableFontRequiredError: if the font is not a variable font.
951
+ """
952
+
953
+ font.rename_variable_instances(
954
+ family_name="My Font New",
955
+ style_names={"Thin": "Hairline"},
956
+ update_style_flags=True,
957
+ )
958
+ ```
959
+
924
960
  #### `sanitize`
925
961
  ```python
926
962
  """
@@ -1031,7 +1067,7 @@ Save all instances of a variable font to specified directory in one or more form
1031
1067
  includes 'instance' (containing instance metadata) and 'files' (a dictionary
1032
1068
  with file formats as keys and file-paths as values).
1033
1069
 
1034
- :raises TypeError: If the font is not a variable font.
1070
+ :raises VariableFontRequiredError: If the font is not a variable font.
1035
1071
  """
1036
1072
 
1037
1073
  saved_fonts = font.save_variable_instances(
@@ -1067,7 +1103,7 @@ The fsType field is left untouched if any argument is invalid.
1067
1103
  :raises ArgumentError: If a value is not a bool or None, if more than one
1068
1104
  of the mutually exclusive usage permissions is set to True, or if
1069
1105
  installable is set to False while no other usage permission is in effect.
1070
- :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.
1071
1107
  """
1072
1108
 
1073
1109
  font.set_embedding_permissions(
@@ -1087,7 +1123,7 @@ based on provided class_id and subclass_id.
1087
1123
 
1088
1124
  :param class_id: Numeric identifier of the font family class.
1089
1125
  :param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
1090
- :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.
1091
1127
  :raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
1092
1128
  """
1093
1129
 
@@ -1296,15 +1332,26 @@ If an axis min and max values are equal, the axis will be pinned.
1296
1332
  {'wdth':100, 'wght':[100,600], 'ital':[30,65,70]} or
1297
1333
  {'wdth':100, 'wght':{'min':100,'max':600}, 'ital':{'min':30,'default':65,'max':70}}
1298
1334
  :type coordinates: dict
1335
+ :param update_names: if True the name records will be updated based on
1336
+ the closest instance to the new default coordinates (only if changed)
1337
+ :type update_names: bool
1338
+ :param update_style_flags: if True the style flags will be updated based on
1339
+ the closest instance to the new default coordinates (only if changed)
1340
+ :type update_style_flags: bool
1299
1341
  :param options: The options for the fontTools.varLib.instancer
1300
1342
  :type options: dictionary
1301
1343
 
1302
- :raises TypeError: If the font is not a variable font
1344
+ :raises VariableFontRequiredError: If the font is not a variable font
1303
1345
  :raises ValueError: If the coordinates are not defined (empty)
1304
1346
  :raises ValueError: If the coordinates axes are all pinned
1305
1347
  """
1306
1348
 
1307
- font.to_sliced_variable(coordinates, **options)
1349
+ font.to_sliced_variable(
1350
+ coordinates,
1351
+ update_names=True,
1352
+ update_style_flags=True,
1353
+ **options,
1354
+ )
1308
1355
  ```
1309
1356
 
1310
1357
  #### `to_static`
@@ -1327,7 +1374,7 @@ If coordinates are not specified each axis will be pinned at its default value.
1327
1374
  :param options: The options for the fontTools.varLib.instancer
1328
1375
  :type options: dictionary
1329
1376
 
1330
- :raises TypeError: If the font is not a variable font
1377
+ :raises VariableFontRequiredError: If the font is not a variable font
1331
1378
  :raises ValueError: If the coordinates axes are not all pinned
1332
1379
  """
1333
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(
@@ -1227,6 +1228,8 @@ class Font:
1227
1228
  """
1228
1229
  Renames the font names records (1, 2, 4, 6, 16, 17) according to
1229
1230
  the given family_name and style_name (subfamily_name).
1231
+ The names records (18, 25), the fvar named instances PostScript names
1232
+ and the CFF table names are updated too, only if already defined.
1230
1233
 
1231
1234
  If family_name is not defined it will be auto-detected.
1232
1235
  If style_name is not defined it will be auto-detected.
@@ -1247,6 +1250,48 @@ class Font:
1247
1250
  if update_style_flags:
1248
1251
  self.set_style_flags_by_subfamily_name()
1249
1252
 
1253
+ def rename_variable_instances(
1254
+ self,
1255
+ *,
1256
+ family_name: str = "",
1257
+ style_names: dict[str, str] | None = None,
1258
+ update_style_flags: bool = True,
1259
+ ) -> None:
1260
+ """
1261
+ Renames the named instances of the variable font.
1262
+
1263
+ The family_name replaces the family part of the instances PostScript names
1264
+ (and of the variations PostScript name prefix, if defined).
1265
+ The style_names replace the instances style names and the style part
1266
+ of their PostScript names. The axes names are unchanged.
1267
+ The default instance style name must match the font style name,
1268
+ so renaming it renames the font style too.
1269
+
1270
+ If family_name is not defined it will be auto-detected.
1271
+
1272
+ :param family_name: The family name
1273
+ :type family_name: str
1274
+ :param style_names: The new style names mapped by the old ones (case-insensitive),
1275
+ eg. {"Thin": "Hairline"}
1276
+ :type style_names: dict or None
1277
+ :param update_style_flags: if True the style flags will be updated by subfamily name
1278
+ (only if the default instance style name is renamed)
1279
+ :type update_style_flags: bool
1280
+
1281
+ :raises VariableFontRequiredError: if the font is not a variable font.
1282
+ :return: None
1283
+ """
1284
+ ttfont = self.get_ttfont()
1285
+ style_name = self.get_style_name()
1286
+ _names.rename_variable_instances(
1287
+ ttfont,
1288
+ family_name=family_name,
1289
+ style_names=style_names,
1290
+ )
1291
+
1292
+ if update_style_flags and self.get_style_name() != style_name:
1293
+ self.set_style_flags_by_subfamily_name()
1294
+
1250
1295
  def sanitize(
1251
1296
  self,
1252
1297
  *,
@@ -1468,10 +1513,10 @@ class Font:
1468
1513
  includes 'instance' (containing instance metadata) and 'files' (a dictionary
1469
1514
  with file formats as keys and file-paths as values).
1470
1515
 
1471
- :raises TypeError: If the font is not a variable font.
1516
+ :raises VariableFontRequiredError: If the font is not a variable font.
1472
1517
  """
1473
1518
  if not self.is_variable():
1474
- raise OperationError("Only a variable font can be instantiated.")
1519
+ raise VariableFontRequiredError("Only a variable font can be instantiated.")
1475
1520
 
1476
1521
  fsutil.assert_not_file(dirpath)
1477
1522
  fsutil.make_dirs(dirpath)
@@ -1526,7 +1571,7 @@ class Font:
1526
1571
 
1527
1572
  :param class_id: Numeric identifier of the font family class.
1528
1573
  :param subclass_id: Optional numeric identifier of the font family subclass (default is 0).
1529
- :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.
1530
1575
  :raises ArgumentError: If class_id is invalid or subclass_id is specified but invalid.
1531
1576
  """
1532
1577
  ttfont = self.get_ttfont()
@@ -1679,7 +1724,7 @@ class Font:
1679
1724
  :raises ArgumentError: If a value is not a bool or None, if more than one
1680
1725
  of the mutually exclusive usage permissions is set to True, or if
1681
1726
  installable is set to False while no other usage permission is in effect.
1682
- :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.
1683
1728
  """
1684
1729
  ttfont = self.get_ttfont()
1685
1730
  _embedding_permissions.set_embedding_permissions(
@@ -1791,6 +1836,8 @@ class Font:
1791
1836
  self,
1792
1837
  *,
1793
1838
  coordinates: dict[str, Any],
1839
+ update_names: bool = True,
1840
+ update_style_flags: bool = True,
1794
1841
  **options: Any,
1795
1842
  ) -> None:
1796
1843
  """
@@ -1805,15 +1852,27 @@ class Font:
1805
1852
  {'wdth':100, 'wght':[100,600], 'ital':[30,65,70]} or
1806
1853
  {'wdth':100, 'wght':{'min':100,'max':600}, 'ital':{'min':30,'default':65,'max':70}}
1807
1854
  :type coordinates: dict
1855
+ :param update_names: if True the name records will be updated based on
1856
+ the closest instance to the new default coordinates (only if changed)
1857
+ :type update_names: bool
1858
+ :param update_style_flags: if True the style flags will be updated based on
1859
+ the closest instance to the new default coordinates (only if changed)
1860
+ :type update_style_flags: bool
1808
1861
  :param options: The options for the fontTools.varLib.instancer
1809
1862
  :type options: dictionary
1810
1863
 
1811
- :raises TypeError: If the font is not a variable font
1864
+ :raises VariableFontRequiredError: If the font is not a variable font
1812
1865
  :raises ValueError: If the coordinates are not defined (blank)
1813
1866
  :raises ValueError: If the coordinates axes are all pinned
1814
1867
  """
1815
1868
  ttfont = self.get_ttfont()
1816
- _variable.to_sliced_variable(ttfont, coordinates=coordinates, **options)
1869
+ _variable.to_sliced_variable(
1870
+ ttfont,
1871
+ coordinates=coordinates,
1872
+ update_names=update_names,
1873
+ update_style_flags=update_style_flags,
1874
+ **options,
1875
+ )
1817
1876
 
1818
1877
  def to_static(
1819
1878
  self,
@@ -1842,7 +1901,7 @@ class Font:
1842
1901
  :param options: The options for the fontTools.varLib.instancer
1843
1902
  :type options: dictionary
1844
1903
 
1845
- :raises TypeError: If the font is not a variable font
1904
+ :raises VariableFontRequiredError: If the font is not a variable font
1846
1905
  :raises ValueError: If the coordinates axes are not all pinned
1847
1906
  """
1848
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.30.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