python-fontbro 0.26.0__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.0/python_fontbro.egg-info → python_fontbro-0.27.0}/PKG-INFO +2 -2
  2. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/font.py +75 -14
  3. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/metadata.py +1 -1
  4. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/pyproject.toml +1 -1
  5. {python_fontbro-0.26.0 → python_fontbro-0.27.0/python_fontbro.egg-info}/PKG-INFO +2 -2
  6. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/python_fontbro.egg-info/requires.txt +1 -1
  7. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_clone.py +15 -0
  8. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_filename.py +18 -8
  9. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_instantiation.py +15 -0
  10. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_sanitize.py +17 -0
  11. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_save.py +30 -0
  12. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_str.py +2 -2
  13. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/LICENSE.txt +0 -0
  14. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/MANIFEST.in +0 -0
  15. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/README.md +0 -0
  16. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/__init__.py +0 -0
  17. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/data/family-classifications.json +0 -0
  18. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/data/features.json +0 -0
  19. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/data/unicode-blocks.json +0 -0
  20. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/data/unicode-scripts.json +0 -0
  21. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/exceptions.py +0 -0
  22. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/flags.py +0 -0
  23. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/math.py +0 -0
  24. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/py.typed +0 -0
  25. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/subset.py +0 -0
  26. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/fontbro/utils.py +0 -0
  27. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/python_fontbro.egg-info/SOURCES.txt +0 -0
  28. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
  29. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/python_fontbro.egg-info/top_level.txt +0 -0
  30. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/setup.cfg +0 -0
  31. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/setup.py +0 -0
  32. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_characters.py +0 -0
  33. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_close.py +0 -0
  34. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_collection.py +0 -0
  35. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_color.py +0 -0
  36. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_context_manager.py +0 -0
  37. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_family_classification.py +0 -0
  38. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_family_name.py +0 -0
  39. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_features.py +0 -0
  40. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_fingerprint.py +0 -0
  41. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_format.py +0 -0
  42. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_glyphs.py +0 -0
  43. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_image.py +0 -0
  44. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_init.py +0 -0
  45. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_issues.py +0 -0
  46. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_italic_angle.py +0 -0
  47. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_monospace.py +0 -0
  48. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_names.py +0 -0
  49. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_rename.py +0 -0
  50. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_style_flags.py +0 -0
  51. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_style_name.py +0 -0
  52. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_subset.py +0 -0
  53. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_svg.py +0 -0
  54. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_unicode_blocks_and_scripts.py +0 -0
  55. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_update_unicode_data.py +0 -0
  56. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_variable.py +0 -0
  57. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_version.py +0 -0
  58. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_vertical_metrics.py +0 -0
  59. {python_fontbro-0.26.0 → python_fontbro-0.27.0}/tests/test_weight.py +0 -0
  60. {python_fontbro-0.26.0 → 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.0
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
 
@@ -8,7 +8,6 @@ import sys
8
8
  import tempfile
9
9
  from collections import Counter
10
10
  from collections.abc import Generator
11
- from curses import ascii
12
11
  from io import BytesIO
13
12
  from pathlib import Path
14
13
  from typing import IO, Any, cast
@@ -489,9 +488,13 @@ class Font:
489
488
  self,
490
489
  ) -> Font:
491
490
  """
492
- 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.
493
493
  """
494
- 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
495
498
 
496
499
  def close(
497
500
  self,
@@ -550,7 +553,9 @@ class Font:
550
553
  char = chr(code)
551
554
  else:
552
555
  continue
553
- if ascii.iscntrl(char):
556
+ # check if is control character
557
+ char_code = ord(char)
558
+ if char_code < 0x20 or char_code == 0x7F:
554
559
  continue
555
560
  if glyfs and ignore_blank:
556
561
  glyf = glyfs.get(char_name)
@@ -707,20 +712,23 @@ class Font:
707
712
  def get_filename(
708
713
  self,
709
714
  *,
710
- variable_suffix: str = "Variable",
715
+ variable_suffix: str = "",
711
716
  variable_axes_tags: bool = True,
712
717
  variable_axes_values: bool = False,
713
718
  ) -> str:
714
719
  """
715
720
  Gets the filename to use for saving the font to file-system.
716
721
 
717
- :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'.
718
726
  :type variable_suffix: str
719
727
  :param variable_axes_tags: The variable axes tags flag,
720
- if True, the axes tags will be appended, eg '[wght,wdth]'
728
+ if True, the axes tags will be appended, eg '[wdth,wght]'
721
729
  :type variable_axes_tags: bool
722
- :param variable_axes_values: The variable axes values flag
723
- 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)]'
724
732
  :type variable_axes_values: bool
725
733
 
726
734
  :returns: The filename.
@@ -742,7 +750,7 @@ class Font:
742
750
  basename = f"{basename}-{variable_suffix}"
743
751
  # append axis tags stringified suffix, eg. [wdth,wght,slnt]
744
752
  if variable_axes_tags:
745
- axes = self.get_variable_axes() or []
753
+ axes = self.get_variable_axes(sort=True) or []
746
754
  axes_str_parts = []
747
755
  for axis in axes:
748
756
  axis_tag = axis["tag"]
@@ -1282,17 +1290,23 @@ class Font:
1282
1290
 
1283
1291
  def get_variable_axes(
1284
1292
  self,
1293
+ *,
1294
+ sort: bool = False,
1285
1295
  ) -> list[dict[str, Any]] | None:
1286
1296
  """
1287
1297
  Gets the font variable axes.
1288
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
+
1289
1303
  :returns: The list of axes if the font is a variable font otherwise None.
1290
1304
  :rtype: list of dict or None
1291
1305
  """
1292
1306
  if not self.is_variable():
1293
1307
  return None
1294
1308
  font = self.get_ttfont()
1295
- return [
1309
+ axes = [
1296
1310
  {
1297
1311
  "tag": axis.axisTag,
1298
1312
  "name": self._VARIABLE_AXES_BY_TAG.get(axis.axisTag, {}).get(
@@ -1304,6 +1318,12 @@ class Font:
1304
1318
  }
1305
1319
  for axis in font["fvar"].axes
1306
1320
  ]
1321
+ if sort:
1322
+ axes = sorted(
1323
+ axes,
1324
+ key=lambda axis: (axis["tag"].islower(), axis["tag"]),
1325
+ )
1326
+ return axes
1307
1327
 
1308
1328
  def get_variable_axis_by_tag(
1309
1329
  self,
@@ -1759,7 +1779,7 @@ class Font:
1759
1779
  def _save_with_flavor(
1760
1780
  self,
1761
1781
  *,
1762
- flavor: str,
1782
+ flavor: str | None,
1763
1783
  filepath: str | Path | None = None,
1764
1784
  overwrite: bool = True,
1765
1785
  ) -> str:
@@ -1776,6 +1796,30 @@ class Font:
1776
1796
  # return file path
1777
1797
  return saved_font_filepath
1778
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
+
1779
1823
  def save_as_woff(
1780
1824
  self,
1781
1825
  filepath: str | Path | None = None,
@@ -1877,7 +1921,7 @@ class Font:
1877
1921
  fsutil.assert_not_file(dirpath)
1878
1922
  fsutil.make_dirs(dirpath)
1879
1923
 
1880
- instances_format = self.get_format()
1924
+ instances_format = self.get_format(ignore_flavor=True)
1881
1925
  instances_saved = []
1882
1926
  instances = self.get_variable_instances() or []
1883
1927
  for instance in instances:
@@ -1896,7 +1940,7 @@ class Font:
1896
1940
  Font.FORMAT_WOFF2: None,
1897
1941
  Font.FORMAT_WOFF: None,
1898
1942
  }
1899
- instance_files[instances_format] = instance_font.save(
1943
+ instance_files[instances_format] = instance_font.save_as_ttf(
1900
1944
  dirpath,
1901
1945
  overwrite=overwrite,
1902
1946
  )
@@ -2317,6 +2361,10 @@ class Font:
2317
2361
  # instantiate the static font
2318
2362
  instancer.instantiateVariableFont(font, coordinates, **options)
2319
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
+
2320
2368
  # update name records and style flags based on instance style name
2321
2369
  if instance and update_names:
2322
2370
  self.rename(
@@ -2331,6 +2379,19 @@ class Font:
2331
2379
  if has_italic or has_slant:
2332
2380
  self.set_style_flags(regular=False, italic=True)
2333
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
+
2334
2395
  def __str__(
2335
2396
  self,
2336
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.0"
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.0
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)
@@ -10,5 +10,5 @@ class StrTestCase(AbstractTestCase):
10
10
  filepath = "/Roboto_Mono/static/RobotoMono-Regular.ttf"
11
11
  font = self._get_font(filepath)
12
12
  s = str(font)
13
- self.assertTrue(s.startswith("Font('"))
14
- self.assertTrue(s.endswith(filepath + "')"))
13
+ expected = f"Font('{font._filepath}')"
14
+ self.assertEqual(s, expected)