python-fontbro 0.26.1__tar.gz → 0.27.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 (60) hide show
  1. {python_fontbro-0.26.1/python_fontbro.egg-info → python_fontbro-0.27.0}/PKG-INFO +2 -2
  2. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/font.py +72 -12
  3. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/metadata.py +1 -1
  4. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/pyproject.toml +1 -1
  5. {python_fontbro-0.26.1 → python_fontbro-0.27.0/python_fontbro.egg-info}/PKG-INFO +2 -2
  6. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/python_fontbro.egg-info/requires.txt +1 -1
  7. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_clone.py +15 -0
  8. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_filename.py +18 -8
  9. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_instantiation.py +15 -0
  10. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_sanitize.py +17 -0
  11. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_save.py +30 -0
  12. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/LICENSE.txt +0 -0
  13. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/MANIFEST.in +0 -0
  14. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/README.md +0 -0
  15. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/__init__.py +0 -0
  16. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/data/family-classifications.json +0 -0
  17. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/data/features.json +0 -0
  18. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/data/unicode-blocks.json +0 -0
  19. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/data/unicode-scripts.json +0 -0
  20. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/exceptions.py +0 -0
  21. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/flags.py +0 -0
  22. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/math.py +0 -0
  23. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/py.typed +0 -0
  24. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/subset.py +0 -0
  25. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/fontbro/utils.py +0 -0
  26. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/python_fontbro.egg-info/SOURCES.txt +0 -0
  27. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
  28. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/python_fontbro.egg-info/top_level.txt +0 -0
  29. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/setup.cfg +0 -0
  30. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/setup.py +0 -0
  31. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_characters.py +0 -0
  32. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_close.py +0 -0
  33. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_collection.py +0 -0
  34. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_color.py +0 -0
  35. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_context_manager.py +0 -0
  36. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_family_classification.py +0 -0
  37. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_family_name.py +0 -0
  38. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_features.py +0 -0
  39. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_fingerprint.py +0 -0
  40. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_format.py +0 -0
  41. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_glyphs.py +0 -0
  42. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_image.py +0 -0
  43. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_init.py +0 -0
  44. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_issues.py +0 -0
  45. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_italic_angle.py +0 -0
  46. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_monospace.py +0 -0
  47. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_names.py +0 -0
  48. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_rename.py +0 -0
  49. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_str.py +0 -0
  50. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_style_flags.py +0 -0
  51. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_style_name.py +0 -0
  52. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_subset.py +0 -0
  53. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_svg.py +0 -0
  54. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_unicode_blocks_and_scripts.py +0 -0
  55. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_update_unicode_data.py +0 -0
  56. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_variable.py +0 -0
  57. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_version.py +0 -0
  58. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_vertical_metrics.py +0 -0
  59. {python_fontbro-0.26.1 → python_fontbro-0.27.0}/tests/test_weight.py +0 -0
  60. {python_fontbro-0.26.1 → python_fontbro-0.27.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.26.1
3
+ Version: 0.27.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>
@@ -57,7 +57,7 @@ License-File: LICENSE.txt
57
57
  Requires-Dist: fonttools[lxml,pathops,unicode,woff]<5.0,>=4.43.0
58
58
  Requires-Dist: imagehash<5.0.0,>=4.2.1
59
59
  Requires-Dist: opentype-sanitizer<10.0.0,>=9.1.0
60
- Requires-Dist: pillow<13.0.0,>=8.4.0
60
+ Requires-Dist: pillow<13.0.0,>=12.2.0
61
61
  Requires-Dist: python-fsutil<1.0.0,>=0.16.0
62
62
  Dynamic: license-file
63
63
 
@@ -488,9 +488,13 @@ class Font:
488
488
  self,
489
489
  ) -> Font:
490
490
  """
491
- Creates a new Font instance reading the same binary file.
491
+ Creates a new Font instance with the current in-memory state,
492
+ including any modifications made to the current instance.
492
493
  """
493
- return Font(self._filepath or self._fileobject, **self._kwargs)
494
+ font = self.get_ttfont()
495
+ font_clone = Font(font, **self._kwargs)
496
+ font_clone._filepath = self._filepath
497
+ return font_clone
494
498
 
495
499
  def close(
496
500
  self,
@@ -708,20 +712,23 @@ class Font:
708
712
  def get_filename(
709
713
  self,
710
714
  *,
711
- variable_suffix: str = "Variable",
715
+ variable_suffix: str = "",
712
716
  variable_axes_tags: bool = True,
713
717
  variable_axes_values: bool = False,
714
718
  ) -> str:
715
719
  """
716
720
  Gets the filename to use for saving the font to file-system.
717
721
 
718
- :param variable_suffix: The variable suffix, default "Variable"
722
+ :param variable_suffix: The variable suffix, default empty string (no suffix).
723
+ Suffixes like 'Variable' or 'VF' or any other are not recommended
724
+ per Google Fonts naming convention. Pass a custom suffix only if needed,
725
+ e.g. variable_suffix='Variable' -> 'FamilyName-Variable[wght].ttf'.
719
726
  :type variable_suffix: str
720
727
  :param variable_axes_tags: The variable axes tags flag,
721
- if True, the axes tags will be appended, eg '[wght,wdth]'
728
+ if True, the axes tags will be appended, eg '[wdth,wght]'
722
729
  :type variable_axes_tags: bool
723
- :param variable_axes_values: The variable axes values flag
724
- if True, each axis values will be appended, eg '[wght(100,100,900),wdth(75,100,125)]'
730
+ :param variable_axes_values: The variable axes values flag,
731
+ if True, each axis values will be appended, eg '[wdth(75,100,125),wght(100,400,900)]'
725
732
  :type variable_axes_values: bool
726
733
 
727
734
  :returns: The filename.
@@ -743,7 +750,7 @@ class Font:
743
750
  basename = f"{basename}-{variable_suffix}"
744
751
  # append axis tags stringified suffix, eg. [wdth,wght,slnt]
745
752
  if variable_axes_tags:
746
- axes = self.get_variable_axes() or []
753
+ axes = self.get_variable_axes(sort=True) or []
747
754
  axes_str_parts = []
748
755
  for axis in axes:
749
756
  axis_tag = axis["tag"]
@@ -1283,17 +1290,23 @@ class Font:
1283
1290
 
1284
1291
  def get_variable_axes(
1285
1292
  self,
1293
+ *,
1294
+ sort: bool = False,
1286
1295
  ) -> list[dict[str, Any]] | None:
1287
1296
  """
1288
1297
  Gets the font variable axes.
1289
1298
 
1299
+ :param sort: If True, axes are sorted following the Google Fonts naming convention:
1300
+ uppercase custom axes first, then lowercase standard axes, each group alphabetical.
1301
+ :type sort: bool
1302
+
1290
1303
  :returns: The list of axes if the font is a variable font otherwise None.
1291
1304
  :rtype: list of dict or None
1292
1305
  """
1293
1306
  if not self.is_variable():
1294
1307
  return None
1295
1308
  font = self.get_ttfont()
1296
- return [
1309
+ axes = [
1297
1310
  {
1298
1311
  "tag": axis.axisTag,
1299
1312
  "name": self._VARIABLE_AXES_BY_TAG.get(axis.axisTag, {}).get(
@@ -1305,6 +1318,12 @@ class Font:
1305
1318
  }
1306
1319
  for axis in font["fvar"].axes
1307
1320
  ]
1321
+ if sort:
1322
+ axes = sorted(
1323
+ axes,
1324
+ key=lambda axis: (axis["tag"].islower(), axis["tag"]),
1325
+ )
1326
+ return axes
1308
1327
 
1309
1328
  def get_variable_axis_by_tag(
1310
1329
  self,
@@ -1760,7 +1779,7 @@ class Font:
1760
1779
  def _save_with_flavor(
1761
1780
  self,
1762
1781
  *,
1763
- flavor: str,
1782
+ flavor: str | None,
1764
1783
  filepath: str | Path | None = None,
1765
1784
  overwrite: bool = True,
1766
1785
  ) -> str:
@@ -1777,6 +1796,30 @@ class Font:
1777
1796
  # return file path
1778
1797
  return saved_font_filepath
1779
1798
 
1799
+ def save_as_ttf(
1800
+ self,
1801
+ filepath: str | Path | None = None,
1802
+ *,
1803
+ overwrite: bool = True,
1804
+ ) -> str:
1805
+ """
1806
+ Saves font without web compression (woff/woff2 flavor is removed).
1807
+ The resulting format will be ttf or otf depending on the source font.
1808
+
1809
+ :param filepath: The filepath
1810
+ :type filepath: str or None
1811
+ :param overwrite: The overwrite, if True the source font file can be overwritten
1812
+ :type overwrite: bool
1813
+
1814
+ :returns: The filepath where the font has been saved to.
1815
+ :rtype: str
1816
+ """
1817
+ return self._save_with_flavor(
1818
+ flavor=None,
1819
+ filepath=filepath,
1820
+ overwrite=overwrite,
1821
+ )
1822
+
1780
1823
  def save_as_woff(
1781
1824
  self,
1782
1825
  filepath: str | Path | None = None,
@@ -1878,7 +1921,7 @@ class Font:
1878
1921
  fsutil.assert_not_file(dirpath)
1879
1922
  fsutil.make_dirs(dirpath)
1880
1923
 
1881
- instances_format = self.get_format()
1924
+ instances_format = self.get_format(ignore_flavor=True)
1882
1925
  instances_saved = []
1883
1926
  instances = self.get_variable_instances() or []
1884
1927
  for instance in instances:
@@ -1897,7 +1940,7 @@ class Font:
1897
1940
  Font.FORMAT_WOFF2: None,
1898
1941
  Font.FORMAT_WOFF: None,
1899
1942
  }
1900
- instance_files[instances_format] = instance_font.save(
1943
+ instance_files[instances_format] = instance_font.save_as_ttf(
1901
1944
  dirpath,
1902
1945
  overwrite=overwrite,
1903
1946
  )
@@ -2318,6 +2361,10 @@ class Font:
2318
2361
  # instantiate the static font
2319
2362
  instancer.instantiateVariableFont(font, coordinates, **options)
2320
2363
 
2364
+ # remove STAT table
2365
+ # not useful in static fonts and after instancing it may contain incorrect values
2366
+ self._remove_stat_table()
2367
+
2321
2368
  # update name records and style flags based on instance style name
2322
2369
  if instance and update_names:
2323
2370
  self.rename(
@@ -2332,6 +2379,19 @@ class Font:
2332
2379
  if has_italic or has_slant:
2333
2380
  self.set_style_flags(regular=False, italic=True)
2334
2381
 
2382
+ def _remove_stat_table(self) -> bool:
2383
+ """
2384
+ Removes the STAT table from the font.
2385
+
2386
+ :returns: True if the STAT table was removed, False if it was not present.
2387
+ :rtype: bool
2388
+ """
2389
+ font = self.get_ttfont()
2390
+ if "STAT" in font:
2391
+ del font["STAT"]
2392
+ return True
2393
+ return False
2394
+
2335
2395
  def __str__(
2336
2396
  self,
2337
2397
  ) -> str:
@@ -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.26.1"
7
+ __version__ = "0.27.0"
@@ -54,7 +54,7 @@ dependencies = [
54
54
  "fonttools[lxml,woff,unicode,pathops] >= 4.43.0, < 5.0",
55
55
  "imagehash >= 4.2.1, < 5.0.0",
56
56
  "opentype-sanitizer >= 9.1.0, < 10.0.0",
57
- "pillow >= 8.4.0, < 13.0.0",
57
+ "pillow >= 12.2.0, < 13.0.0",
58
58
  "python-fsutil >= 0.16.0, < 1.0.0",
59
59
  ]
60
60
  dynamic = ["version"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fontbro
3
- Version: 0.26.1
3
+ Version: 0.27.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>
@@ -57,7 +57,7 @@ License-File: LICENSE.txt
57
57
  Requires-Dist: fonttools[lxml,pathops,unicode,woff]<5.0,>=4.43.0
58
58
  Requires-Dist: imagehash<5.0.0,>=4.2.1
59
59
  Requires-Dist: opentype-sanitizer<10.0.0,>=9.1.0
60
- Requires-Dist: pillow<13.0.0,>=8.4.0
60
+ Requires-Dist: pillow<13.0.0,>=12.2.0
61
61
  Requires-Dist: python-fsutil<1.0.0,>=0.16.0
62
62
  Dynamic: license-file
63
63
 
@@ -1,5 +1,5 @@
1
1
  fonttools[lxml,pathops,unicode,woff]<5.0,>=4.43.0
2
2
  imagehash<5.0.0,>=4.2.1
3
3
  opentype-sanitizer<10.0.0,>=9.1.0
4
- pillow<13.0.0,>=8.4.0
4
+ pillow<13.0.0,>=12.2.0
5
5
  python-fsutil<1.0.0,>=0.16.0
@@ -45,3 +45,18 @@ class CloneTestCase(AbstractTestCase):
45
45
  font_clone = font2.clone()
46
46
  self.assertFalse(font2 == font_clone)
47
47
  self.assertEqual(f"{font2}", f"{font_clone}")
48
+
49
+ def test_clone_preserves_in_memory_changes(self):
50
+ filepath = self._get_font_path("/Noto_Sans_TC/NotoSansTC-Regular.otf")
51
+ font = Font(filepath)
52
+ font.set_name(Font.NAME_FAMILY_NAME, "NotoSansTCCustom")
53
+ font_clone = font.clone()
54
+ self.assertEqual(
55
+ font_clone.get_name(Font.NAME_FAMILY_NAME),
56
+ font.get_name(Font.NAME_FAMILY_NAME),
57
+ )
58
+ self.assertEqual(
59
+ font_clone.get_name(Font.NAME_FAMILY_NAME),
60
+ "NotoSansTCCustom",
61
+ )
62
+ self.assertEqual(font_clone._filepath, filepath)
@@ -23,28 +23,38 @@ class FilenameTestCase(AbstractTestCase):
23
23
  font = self._get_font("/Tourney/Tourney-Italic-VariableFont_wdth,wght.ttf")
24
24
  self.assertEqual(
25
25
  font.get_filename(),
26
- "Tourney-Italic-Variable[wght,wdth].ttf",
26
+ "Tourney-Italic[wdth,wght].ttf",
27
27
  )
28
28
 
29
29
  font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
30
30
  self.assertEqual(
31
31
  font.get_filename(),
32
- "Tourney-Variable[wght,wdth].ttf",
32
+ "Tourney[wdth,wght].ttf",
33
33
  )
34
34
 
35
35
  font = self._get_font("/Roboto_Mono/RobotoMono-Italic-VariableFont_wght.ttf")
36
36
  self.assertEqual(
37
37
  font.get_filename(),
38
- "RobotoMono-Italic-Variable[wght].ttf",
38
+ "RobotoMono-Italic[wght].ttf",
39
39
  )
40
40
 
41
41
  font = self._get_font("/Roboto_Mono/RobotoMono-VariableFont_wght.ttf")
42
42
  self.assertEqual(
43
43
  font.get_filename(),
44
- "RobotoMono-Variable[wght].ttf",
44
+ "RobotoMono[wght].ttf",
45
45
  )
46
46
 
47
47
  def test_get_filename_with_variable_font_and_custom_suffixes(self):
48
+ font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
49
+ self.assertEqual(
50
+ font.get_filename(
51
+ variable_suffix="Variable",
52
+ variable_axes_tags=False,
53
+ variable_axes_values=False,
54
+ ),
55
+ "Tourney-Variable.ttf",
56
+ )
57
+
48
58
  font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
49
59
  self.assertEqual(
50
60
  font.get_filename(
@@ -71,7 +81,7 @@ class FilenameTestCase(AbstractTestCase):
71
81
  variable_suffix="VF",
72
82
  variable_axes_tags=True,
73
83
  ),
74
- "Tourney-VF[wght,wdth].ttf",
84
+ "Tourney-VF[wdth,wght].ttf",
75
85
  )
76
86
 
77
87
  font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
@@ -81,7 +91,7 @@ class FilenameTestCase(AbstractTestCase):
81
91
  variable_axes_tags=True,
82
92
  variable_axes_values=True,
83
93
  ),
84
- "Tourney-VF[wght(100,100,900),wdth(75,100,125)].ttf",
94
+ "Tourney-VF[wdth(75,100,125),wght(100,100,900)].ttf",
85
95
  )
86
96
 
87
97
  font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
@@ -90,7 +100,7 @@ class FilenameTestCase(AbstractTestCase):
90
100
  variable_suffix="",
91
101
  variable_axes_tags=True,
92
102
  ),
93
- "Tourney[wght,wdth].ttf",
103
+ "Tourney[wdth,wght].ttf",
94
104
  )
95
105
 
96
106
  font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
@@ -100,7 +110,7 @@ class FilenameTestCase(AbstractTestCase):
100
110
  variable_axes_tags=True,
101
111
  variable_axes_values=True,
102
112
  ),
103
- "Tourney[wght(100,100,900),wdth(75,100,125)].ttf",
113
+ "Tourney[wdth(75,100,125),wght(100,100,900)].ttf",
104
114
  )
105
115
 
106
116
  font = self._get_font("/Tourney/Tourney-VariableFont_wdth,wght.ttf")
@@ -1,3 +1,4 @@
1
+ from fontbro import Font
1
2
  from tests import AbstractTestCase
2
3
 
3
4
 
@@ -131,6 +132,20 @@ class InstantiationTestCase(AbstractTestCase):
131
132
  )
132
133
  self.assertFalse(font.get_style_flag("italic"))
133
134
 
135
+ def test_to_static_removes_stat_table(self):
136
+ font = self._get_variable_font()
137
+ self.assertIn("STAT", font.get_ttfont())
138
+ font.to_static(coordinates={"wght": 400, "wdth": 100})
139
+ self.assertNotIn("STAT", font.get_ttfont())
140
+
141
+ def test_to_static_stat_table_not_present_in_saved_file(self):
142
+ font = self._get_variable_font()
143
+ font.to_static(coordinates={"wght": 400, "wdth": 100})
144
+ output_filepath = self._get_font_temp_path("")
145
+ font_saved_filepath = font.save(output_filepath, overwrite=True)
146
+ font_saved = Font(font_saved_filepath)
147
+ self.assertNotIn("STAT", font_saved.get_ttfont())
148
+
134
149
  def test_to_sliced_variable_with_static_font(self):
135
150
  font = self._get_static_font()
136
151
  with self.assertRaises(TypeError):
@@ -1,3 +1,5 @@
1
+ import logging
2
+
1
3
  import fsutil
2
4
 
3
5
  from fontbro import Font
@@ -9,6 +11,21 @@ class SanitizeTestCase(AbstractTestCase):
9
11
  This class describes a sanitize test case.
10
12
  """
11
13
 
14
+ # fontTools uses log.error()/log.warning() when parsing malformed tables
15
+ # (e.g. "skipping malformed name record", "timestamp out of range").
16
+ # NullHandler prevents Python's lastResort from printing them to stderr.
17
+ _fonttools_null_handler = logging.NullHandler()
18
+
19
+ @classmethod
20
+ def setUpClass(cls):
21
+ super().setUpClass()
22
+ logging.getLogger("fontTools").addHandler(cls._fonttools_null_handler)
23
+
24
+ @classmethod
25
+ def tearDownClass(cls):
26
+ logging.getLogger("fontTools").removeHandler(cls._fonttools_null_handler)
27
+ super().tearDownClass()
28
+
12
29
  def _test_sanitize(
13
30
  self,
14
31
  dirpath,
@@ -191,3 +191,33 @@ class SaveTestCase(AbstractTestCase):
191
191
  output_dirpath = self._get_font_temp_path("")
192
192
  with self.assertRaises(TypeError):
193
193
  font.save_variable_instances(output_dirpath)
194
+
195
+ def test_save_as_ttf_from_woff(self):
196
+ # create woff2 on the fly
197
+ font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
198
+ woff_filepath = self._get_font_temp_path("RobotoMono-Regular.woff")
199
+ font.save_as_woff(woff_filepath, overwrite=True)
200
+ # create font instance from woff file
201
+ font_woff = Font(woff_filepath)
202
+ output_filepath = self._get_font_temp_path("")
203
+ font_saved_filepath = font_woff.save_as_ttf(output_filepath)
204
+ self.assertTrue(font_saved_filepath.endswith(".ttf"))
205
+ # ensure that the original font format is not changed
206
+ self.assertEqual(font_woff.get_format(), Font.FORMAT_WOFF)
207
+ font_saved = Font(font_saved_filepath)
208
+ self.assertEqual(font_saved.get_format(), Font.FORMAT_TTF)
209
+
210
+ def test_save_as_ttf_from_woff2(self):
211
+ # create woff2 on the fly
212
+ font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
213
+ woff2_filepath = self._get_font_temp_path("RobotoMono-Regular.woff2")
214
+ font.save_as_woff2(woff2_filepath, overwrite=True)
215
+ # create font instance from woff2 file
216
+ font_woff2 = Font(woff2_filepath)
217
+ output_filepath = self._get_font_temp_path("")
218
+ font_saved_filepath = font_woff2.save_as_ttf(output_filepath)
219
+ self.assertTrue(font_saved_filepath.endswith(".ttf"))
220
+ # ensure that the original font format is not changed
221
+ self.assertEqual(font_woff2.get_format(), Font.FORMAT_WOFF2)
222
+ font_saved = Font(font_saved_filepath)
223
+ self.assertEqual(font_saved.get_format(), Font.FORMAT_TTF)