1717from fontTools .varLib .instancer import OverlapMode
1818from PIL import Image ,ImageDraw ,ImageFont
1919
20+ from fontbro .exceptions import (
21+ ArgumentError ,
22+ DataError ,
23+ OperationError ,
24+ SanitizationError ,
25+ )
2026from fontbro .flags import get_flag ,set_flag
2127from fontbro .math import get_euclidean_distance
2228from fontbro .subset import parse_unicodes
@@ -229,7 +235,7 @@ def __init__(self, filepath, **kwargs):
229235self ._init_with_ttfont (filepath ,** kwargs )
230236else :
231237filepath_type = type (filepath ).__name__
232- raise ValueError (
238+ raise ArgumentError (
233239f"Invalid filepath type: expected pathlib.Path or str, found{ filepath_type !r} ."
234240 )
235241
@@ -240,7 +246,7 @@ def _init_with_filepath(self, filepath, **kwargs):
240246self ._ttfont = TTFont (self ._filepath ,** kwargs )
241247
242248except TTLibError as error :
243- raise ValueError (f"Invalid font at filepath:{ filepath !r} ." )from error
249+ raise ArgumentError (f"Invalid font at filepath:{ filepath !r} ." )from error
244250
245251def _init_with_fileobject (self ,fileobject ,** kwargs ):
246252try :
@@ -249,7 +255,9 @@ def _init_with_fileobject(self, fileobject, **kwargs):
249255self ._ttfont = TTFont (self ._fileobject ,** kwargs )
250256
251257except TTLibError as error :
252- raise ValueError (f"Invalid font at fileobject:{ fileobject !r} ." )from error
258+ raise ArgumentError (
259+ f"Invalid font at fileobject:{ fileobject !r} ."
260+ )from error
253261
254262def _init_with_font (self ,font ,** kwargs ):
255263self ._init_with_ttfont (font .get_ttfont ())
@@ -311,7 +319,7 @@ def get_characters(self, *, ignore_blank=False):
311319font = self .get_ttfont ()
312320cmap = font .getBestCmap ()
313321if cmap is None :
314- raise TypeError ("Unable to find the 'best' unicode cmap dict." )
322+ raise DataError ("Unable to find the 'best' unicode cmap dict." )
315323glyfs = font .get ("glyf" )
316324for code ,char_name in cmap .items ():
317325code_hex = f"{ code :04X} "
@@ -515,7 +523,7 @@ def get_fingerprint_match(self, other, *, tolerance=10, text=""):
515523other_font = other
516524else :
517525other_type = type (other ).__name__
518- raise ValueError (
526+ raise ArgumentError (
519527"Invalid other filepath/font: expected str or Font instance, "
520528f"found{ other_type !r} ."
521529 )
@@ -551,7 +559,7 @@ def get_format(self, *, ignore_flavor=False):
551559elif version == "wOF2" :
552560format = self .FORMAT_WOFF2
553561if format is None :
554- raise TypeError ("Unable to get the font format." )
562+ raise DataError ("Unable to get the font format." )
555563return format
556564
557565def get_glyphs (self ):
@@ -644,8 +652,9 @@ def _get_name_id(cls, key):
644652elif isinstance (key ,str ):
645653return cls ._NAMES_BY_KEY [key ]["id" ]
646654else :
647- raise TypeError (
648- f"Invalid key type, expected int or str, found{ type (key ).__name__ !r} ."
655+ key_type = type (key ).__name__
656+ raise ArgumentError (
657+ f"Invalid key type, expected int or str, found{ key_type !r} ."
649658 )
650659
651660def get_name (self ,key ):
@@ -1107,8 +1116,8 @@ def rename(self, *, family_name="", style_name="", update_style_flags=True):
11071116postscript_name = re .sub (r"[\-]+" ,"-" ,postscript_name ).strip ("-" )
11081117postscript_name_length = len (postscript_name )
11091118if postscript_name_length > 63 :
1110- raise ValueError (
1111- "PostScript namemax-length ( 63 characters) exceeded "
1119+ raise ArgumentError (
1120+ "Computed PostScript nameexceeded 63 characters max-length "
11121121f" ({ postscript_name_length } characters)."
11131122 )
11141123
@@ -1165,17 +1174,18 @@ def sanitize(self, *, strict=True):
11651174error_code = result .returncode
11661175errors = result .stderr
11671176if error_code :
1168- exc = Exception (
1177+ raise SanitizationError (
11691178f"OpenType Sanitizer returned non-zero exit code ({ error_code } ):\n { errors } "
11701179 )
1171- print (exc )
1172- raise exc
1180+
11731181elif strict :
11741182warnings = result .stdout
11751183success_message = "File sanitized successfully!\n "
11761184if warnings != success_message :
11771185warnings = warnings .rstrip (success_message )
1178- raise Exception (f"OpenType Sanitizer warnings:\n { warnings } " )
1186+ raise SanitizationError (
1187+ f"OpenType Sanitizer warnings:\n { warnings } "
1188+ )
11791189
11801190def save (self ,filepath = None ,* ,overwrite = False ):
11811191"""
@@ -1196,7 +1206,7 @@ def save(self, filepath=None, *, overwrite=False):
11961206 not specififed.
11971207 """
11981208if not filepath and not self ._filepath :
1199- raise ValueError (
1209+ raise ArgumentError (
12001210"Font doesn't have a filepath. Please specify a filepath to save to."
12011211 )
12021212
@@ -1220,7 +1230,7 @@ def save(self, filepath=None, *, overwrite=False):
12201230filename = fsutil .join_filename (basename ,extension )
12211231filepath = fsutil .join_filepath (dirpath ,filename )
12221232if fsutil .is_file (filepath )and not overwrite :
1223- raise ValueError (
1233+ raise ArgumentError (
12241234f"Invalid filepath, a file already exists at{ filepath !r} "
12251235"and 'overwrite' option is 'False' (consider using 'overwrite=True')."
12261236 )
@@ -1313,7 +1323,7 @@ def save_variable_instances(
13131323 :raises TypeError: If the font is not a variable font.
13141324 """
13151325if not self .is_variable ():
1316- raise TypeError ("Only a variable font can be instantiated." )
1326+ raise OperationError ("Only a variable font can be instantiated." )
13171327
13181328fsutil .assert_not_file (dirpath )
13191329fsutil .make_dirs (dirpath )
@@ -1501,7 +1511,7 @@ def subset(self, *, unicodes="", glyphs=None, text="", **options):
15011511 """
15021512font = self .get_ttfont ()
15031513if not any ([unicodes ,glyphs ,text ]):
1504- raise ValueError (
1514+ raise ArgumentError (
15051515"Subsetting requires at least one of the following args: unicode,"
15061516" glyphs, text."
15071517 )
@@ -1555,7 +1565,7 @@ def to_sliced_variable(self, *, coordinates, **options):
15551565 :raises ValueError: If the coordinates axes are all pinned
15561566 """
15571567if not self .is_variable ():
1558- raise TypeError ("Only a variable font can be sliced." )
1568+ raise OperationError ("Only a variable font can be sliced." )
15591569
15601570font = self .get_ttfont ()
15611571coordinates = coordinates or {}
@@ -1576,10 +1586,10 @@ def to_sliced_variable(self, *, coordinates, **options):
15761586
15771587# ensure that coordinates axes are defined and that are not all pinned
15781588if len (coordinates_axes_tags )== 0 :
1579- raise ValueError ("Invalid coordinates: axes not defined." )
1589+ raise ArgumentError ("Invalid coordinates: axes not defined." )
15801590elif set (coordinates_axes_tags )== set (self .get_variable_axes_tags ()):
15811591if self ._all_axes_pinned (coordinates ):
1582- raise ValueError (
1592+ raise ArgumentError (
15831593"Invalid coordinates: all axes are pinned (use to_static method)."
15841594 )
15851595
@@ -1622,19 +1632,19 @@ def to_static(
16221632 :raises ValueError: If the coordinates axes are not all pinned
16231633 """
16241634if not self .is_variable ():
1625- raise TypeError ("Only a variable font can be made static." )
1635+ raise OperationError ("Only a variable font can be made static." )
16261636
16271637font = self .get_ttfont ()
16281638
16291639# take coordinates from instance with specified style name
16301640if style_name :
16311641if coordinates :
1632- raise ValueError (
1642+ raise ArgumentError (
16331643"Invalid arguments: 'coordinates' and 'style_name' are mutually exclusive."
16341644 )
16351645instance = self .get_variable_instance_by_style_name (style_name = style_name )
16361646if not instance :
1637- raise ValueError (
1647+ raise ArgumentError (
16381648f"Invalid style name: instance with style name{ style_name !r} not found."
16391649 )
16401650coordinates = instance ["coordinates" ].copy ()
@@ -1650,7 +1660,7 @@ def to_static(
16501660
16511661# ensure that coordinates axes are all pinned
16521662if not self ._all_axes_pinned (coordinates ):
1653- raise ValueError ("Invalid coordinates: all axes must be pinned." )
1663+ raise ArgumentError ("Invalid coordinates: all axes must be pinned." )
16541664
16551665# get instance closest to coordinates
16561666instance = self .get_variable_instance_closest_to_coordinates (coordinates )