diff --git a/foundryToolsCLI/CLI/ftcli_assistant.py b/foundryToolsCLI/CLI/ftcli_assistant.py index dd2902e..b639823 100644 --- a/foundryToolsCLI/CLI/ftcli_assistant.py +++ b/foundryToolsCLI/CLI/ftcli_assistant.py @@ -1,3 +1,5 @@ +import typing as t + from copy import copy, deepcopy from pathlib import Path @@ -16,6 +18,8 @@ add_file_or_path_argument, add_common_options, file_overwrite_prompt, + linked_styles_callback, + choice_to_int_callback, ) from foundryToolsCLI.Lib.utils.logger import logger, Logs from foundryToolsCLI.Lib.utils.timer import Timer @@ -88,6 +92,7 @@ def init(input_path: Path, quiet: bool = False): "-ls", "--linked-styles", type=(click.IntRange(1, 1000), click.IntRange(1, 1000)), + callback=linked_styles_callback, help="""Use this option to activate linked styles. If this option is active, linked styles must be specified. For example: -ls 400 700, or -ls 300 600. """, @@ -97,6 +102,7 @@ def init(input_path: Path, quiet: bool = False): "--exclude-namerecords", type=click.Choice(choices=["1", "2", "3", "4", "5", "6", "16", "17", "18"]), multiple=True, + callback=choice_to_int_callback, help=""" Name IDs to skip. The specified name IDs won't be recalculated. This option can be repeated (for example: -x 3 -x 5 -x 6...). @@ -107,6 +113,7 @@ def init(input_path: Path, quiet: bool = False): "--shorten-width", type=click.Choice(choices=["1", "4", "6", "16", "17"]), multiple=True, + callback=choice_to_int_callback, help=""" Name IDs where to use the short word for width style name (for example, 'Cn' instead of 'Condensed'). This option can be repeated (for example: -swdt 1 -swdt 5, -swdt 16...). @@ -117,11 +124,23 @@ def init(input_path: Path, quiet: bool = False): "--shorten-weight", type=click.Choice(choices=["1", "4", "6", "17"]), multiple=True, + callback=choice_to_int_callback, help=""" Name IDs where to use the short word for weight style name (for example, 'Md' instead of 'Medium'). This option can be repeated (for example: -swgt 1 -swgt 5 -swgt 6...). """, ) +@click.option( + "-sslp", + "--shorten-slope", + type=click.Choice(choices=["4", "6", "16", "17"]), + multiple=True, + callback=choice_to_int_callback, + help=""" + Name IDs where to use the short word for slope style name (for example, 'It' instead of 'Italic'). This option can + be repeated (for example: -sslp 3 -sslp 5 -sslp 6...). + """, +) @click.option( "-kwdt", "--keep-width-elidable", @@ -138,16 +157,6 @@ def init(input_path: Path, quiet: bool = False): Doesn't remove the weight elidable words (by default, "Rg" and "Regular"). """, ) -@click.option( - "-sslp", - "--shorten-slope", - type=click.Choice(choices=["4", "6", "16", "17"]), - multiple=True, - help=""" - Name IDs where to use the short word for slope style name (for example, 'It' instead of 'Italic'). This option can - be repeated (for example: -sslp 3 -sslp 5 -sslp 6...). - """, -) @click.option( "-sf", "--super-family", @@ -201,23 +210,23 @@ def init(input_path: Path, quiet: bool = False): @Timer(logger=logger.info) def commit( input_path: Path, - width_elidable: str, - weight_elidable: str, - linked_styles: list = None, - exclude_namerecords=None, - shorten_width=None, - shorten_weight=None, - keep_width_elidable=False, - keep_weight_elidable=False, - shorten_slope=None, - super_family=False, - alt_uid=False, - oblique_not_italic=False, - auto_shorten=True, - cff=False, - output_dir=None, - recalc_timestamp=False, - overwrite=True, + linked_styles: t.Optional[t.Tuple[int, int]] = None, + exclude_namerecords: t.Union[t.Tuple[int], t.Tuple[()]] = (), + shorten_width: t.Union[t.Tuple[int], t.Tuple[()]] = (), + shorten_weight: t.Union[t.Tuple[int], t.Tuple[()]] = (), + shorten_slope: t.Union[t.Tuple[int], t.Tuple[()]] = (), + width_elidable: str = "Normal", + weight_elidable: str = "Regular", + keep_width_elidable: bool = False, + keep_weight_elidable: bool = False, + super_family: bool = False, + alt_uid: bool = False, + oblique_not_italic: bool = False, + auto_shorten: bool = True, + cff: bool = False, + output_dir: t.Optional[Path] = None, + recalc_timestamp: bool = False, + overwrite: bool = True, ): """ Writes data from CSV to fonts. @@ -229,17 +238,6 @@ def commit( except Exception as e: logger.exception(e) - if linked_styles: - linked_styles = sorted(linked_styles) - if exclude_namerecords: - exclude_namerecords = sorted(set(int(i) for i in exclude_namerecords)) - if shorten_width: - shorten_width = sorted(set(int(i) for i in shorten_width)) - if shorten_weight: - shorten_weight = sorted(set(int(i) for i in shorten_weight)) - if shorten_slope: - shorten_slope = sorted(set(int(i) for i in shorten_slope)) - try: fonts_data = FontsData(get_fonts_data_path(input_path)) data = fonts_data.get_data() diff --git a/foundryToolsCLI/CLI/ftcli_cff.py b/foundryToolsCLI/CLI/ftcli_cff.py index a5cfc66..248772e 100644 --- a/foundryToolsCLI/CLI/ftcli_cff.py +++ b/foundryToolsCLI/CLI/ftcli_cff.py @@ -1,3 +1,5 @@ +import typing as t + from copy import deepcopy from pathlib import Path @@ -32,7 +34,7 @@ def del_names( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, overwrite: bool = True, **kwargs ): @@ -99,7 +101,7 @@ def set_names( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, overwrite: bool = True, **kwargs ): @@ -163,7 +165,7 @@ def find_replace( old_string: str, new_string: str, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): diff --git a/foundryToolsCLI/CLI/ftcli_converter.py b/foundryToolsCLI/CLI/ftcli_converter.py index a4ae798..e56bab4 100644 --- a/foundryToolsCLI/CLI/ftcli_converter.py +++ b/foundryToolsCLI/CLI/ftcli_converter.py @@ -1,3 +1,5 @@ +import typing as t + from pathlib import Path import click @@ -42,11 +44,11 @@ def ttf2otf( input_path: Path, tolerance: float = 1.0, - scale_upm: int = None, + scale_upm: t.Optional[int] = None, subroutinize: bool = True, recalc_timestamp: bool = False, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, overwrite: bool = True, verbose: bool = True, ): @@ -92,7 +94,7 @@ def otf2ttf( max_err: float = 1.0, recalc_timestamp: bool = False, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, overwrite: bool = True, ): """ @@ -157,7 +159,7 @@ def vf2i( cleanup: bool = True, update_name_table: bool = True, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -228,9 +230,9 @@ def vf2i( @Timer(logger=logger.info) def wf2ft( input_path: Path, - flavor: str = None, + flavor: t.Optional[str] = None, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -282,7 +284,14 @@ def wf2ft( @add_recursive_option() @add_common_options() @Timer(logger=logger.info) -def ft2wf(input_path, flavor=None, recursive: bool = False, output_dir=None, recalc_timestamp=False, overwrite=True): +def ft2wf( + input_path: Path, + flavor: t.Optional[str] = None, + recursive: bool = False, + output_dir: t.Optional[Path] = None, + recalc_timestamp=False, + overwrite=True, +): """ Converts SFNT fonts (TTF or OTF) to web fonts (WOFF and/or WOFF2) """ @@ -315,7 +324,7 @@ def ft2wf(input_path, flavor=None, recursive: bool = False, output_dir=None, rec @Timer(logger=logger.info) def ttc2sfnt( input_path: Path, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): diff --git a/foundryToolsCLI/CLI/ftcli_fix.py b/foundryToolsCLI/CLI/ftcli_fix.py index 8c614c3..c959ae1 100644 --- a/foundryToolsCLI/CLI/ftcli_fix.py +++ b/foundryToolsCLI/CLI/ftcli_fix.py @@ -1,3 +1,5 @@ +from typing import Optional + from copy import deepcopy from pathlib import Path @@ -28,7 +30,7 @@ def monospace( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -175,7 +177,7 @@ def monospace( def nbsp_width( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -267,7 +269,7 @@ def italic_angle( input_path: Path, mode: int = 1, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, min_slant: float = 2.0, @@ -442,7 +444,7 @@ def italic_angle( def nbsp_missing( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -499,7 +501,7 @@ def nbsp_missing( def decompose_transformed( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -605,7 +607,7 @@ def decompose_transformed( def duplicate_components( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -681,7 +683,7 @@ def duplicate_components( def kern_table( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -768,7 +770,7 @@ def kern_table( def strip_names( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -821,7 +823,7 @@ def strip_names( @Timer(logger=logger.info) def empty_names( input_path: Path, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): diff --git a/foundryToolsCLI/CLI/ftcli_hhea.py b/foundryToolsCLI/CLI/ftcli_hhea.py index 2b051c2..8e09649 100644 --- a/foundryToolsCLI/CLI/ftcli_hhea.py +++ b/foundryToolsCLI/CLI/ftcli_hhea.py @@ -1,4 +1,5 @@ import os +import typing as t from copy import copy from pathlib import Path @@ -32,16 +33,20 @@ def cli( input_path: Path, recursive: bool = False, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = True, - output_dir: Path = None, overwrite: bool = True, - **kwargs + rise: t.Optional[int] = None, + run: t.Optional[int] = None, + offset: t.Optional[int] = None, + ascent: t.Optional[int] = None, + descent: t.Optional[int] = None, + linegap: t.Optional[int] = None, + recalc_offset: t.Optional[bool] = False, ): """A command line tool to manipulate the ``hhea`` table.""" - params = {k: v for k, v in kwargs.items() if v is not None} - - if len(params) == 0: + if not any([rise, run, offset, ascent, descent, linegap, recalc_offset]): logger.error(Logs.no_parameter) return @@ -58,25 +63,25 @@ def cli( hhea_table: TableHhea = font["hhea"] hhea_table_copy = copy(hhea_table) - if "rise" in params.keys(): - hhea_table.set_caret_slope_rise(params.get("rise")) + if rise: + hhea_table.set_caret_slope_rise(rise) - if "run" in params.keys(): - hhea_table.set_caret_slope_run(params.get("run")) + if run: + hhea_table.set_caret_slope_run(run) - if "offset" in params.keys(): - hhea_table.set_caret_offset(params.get("offset")) + if offset: + hhea_table.set_caret_offset(offset) - if "ascent" in params.keys(): - hhea_table.set_ascent(params.get("ascent")) + if ascent: + hhea_table.set_ascent(ascent) - if "descent" in params.keys(): - hhea_table.set_descent(params.get("descent")) + if descent: + hhea_table.set_descent(descent) - if "linegap" in params.keys(): - hhea_table.set_linegap(params.get("linegap")) + if linegap: + hhea_table.set_linegap(linegap) - if params.get("recalc_offset") is True: + if recalc_offset: temp_otf_fd, temp_otf_file = font.make_temp_otf() temp_font = Font(temp_otf_file) calculated_offset = temp_font["hhea"].caretOffset @@ -87,7 +92,7 @@ def cli( if hhea_table_copy.compile(font) != hhea_table.compile(font): font.save(output_file) - logger.info(Logs.file_saved, file=output_file) + logger.success(Logs.file_saved, file=output_file) else: logger.skip(Logs.file_not_changed, file=output_file) diff --git a/foundryToolsCLI/CLI/ftcli_metrics.py b/foundryToolsCLI/CLI/ftcli_metrics.py index 6e96b40..ce2508f 100644 --- a/foundryToolsCLI/CLI/ftcli_metrics.py +++ b/foundryToolsCLI/CLI/ftcli_metrics.py @@ -1,5 +1,6 @@ from copy import copy from pathlib import Path +from typing import Optional import click from fontTools.misc.cliTools import makeOutputFileName @@ -27,7 +28,7 @@ def set_linegap( input_path: Path, percent: int, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -88,7 +89,7 @@ def set_linegap( def align( input_path: Path, with_linegap: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -202,7 +203,7 @@ def align( def copy_metrics( source_file: Path, destination: Path, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): diff --git a/foundryToolsCLI/CLI/ftcli_name.py b/foundryToolsCLI/CLI/ftcli_name.py index 04e4c68..7ff7ca4 100644 --- a/foundryToolsCLI/CLI/ftcli_name.py +++ b/foundryToolsCLI/CLI/ftcli_name.py @@ -1,5 +1,6 @@ from copy import deepcopy from pathlib import Path +from typing import Optional import click from fontTools.misc.cliTools import makeOutputFileName @@ -60,7 +61,7 @@ def set_name( language_string: str, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ) -> None: """ @@ -143,7 +144,7 @@ def del_names( language_string: str, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -235,7 +236,7 @@ def find_replace( platform_id: int, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -288,7 +289,7 @@ def del_mac_names( del_all: bool = False, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: bool = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -381,7 +382,7 @@ def append( suffix: str, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ diff --git a/foundryToolsCLI/CLI/ftcli_os2.py b/foundryToolsCLI/CLI/ftcli_os2.py index 0eb8287..7d2ac3a 100644 --- a/foundryToolsCLI/CLI/ftcli_os2.py +++ b/foundryToolsCLI/CLI/ftcli_os2.py @@ -1,6 +1,7 @@ import os from copy import deepcopy, copy from pathlib import Path +from typing import Optional import click from fontTools.misc.cliTools import makeOutputFileName @@ -29,7 +30,7 @@ def recalc_x_height( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -76,7 +77,7 @@ def recalc_cap_height( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -123,7 +124,7 @@ def recalc_max_context( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -171,7 +172,7 @@ def recalc_max_context( def recalc_ranges( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -370,7 +371,7 @@ def set_flags( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, **kwargs, ): @@ -449,7 +450,7 @@ def set_version( input_path: Path, target_version: int, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -538,7 +539,7 @@ def set_weight( input_path: Path, weight: int, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -591,7 +592,7 @@ def set_width( input_path: Path, width: int, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -646,7 +647,7 @@ def panose( input_path: Path, recalc_timestamp, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, **kwargs, ): diff --git a/foundryToolsCLI/CLI/ftcli_otf.py b/foundryToolsCLI/CLI/ftcli_otf.py index 66c83a2..37b126b 100644 --- a/foundryToolsCLI/CLI/ftcli_otf.py +++ b/foundryToolsCLI/CLI/ftcli_otf.py @@ -1,5 +1,6 @@ from copy import deepcopy from pathlib import Path +from typing import Optional, List import click from fontTools.misc.cliTools import makeOutputFileName @@ -84,7 +85,7 @@ @Timer(logger=logger.info) def autohint( input_path: Path, - reference_font: Path = None, + reference_font: Optional[Path] = None, allow_changes: bool = False, decimal: bool = False, no_flex: bool = False, @@ -92,7 +93,7 @@ def autohint( no_zones_stems: bool = False, optimize: bool = True, recursive: bool = False, - output_dir: bool = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -213,7 +214,7 @@ def dehint( dehinter: str = "tx", subroutinize: bool = True, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -261,12 +262,12 @@ def dehint( else: input_file = file - tx_command = ["tx", "-cff", "-n", "+b"] + tx_command: List[str] = ["tx", "-cff", "-n", "+b"] if subroutinize: tx_command.append("+S") else: tx_command.append("-S") - tx_command.extend([input_file, temp_cff_file]) + tx_command.extend([input_file.as_posix(), temp_cff_file.as_posix()]) run_shell_command(tx_command, suppress_output=True) sfntedit_command = ["sfntedit", "-a", f"CFF={temp_cff_file}", input_file] @@ -312,7 +313,7 @@ def fix_contours( subroutinize: bool = True, verbose: bool = True, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -357,7 +358,7 @@ def fix_version( input_path: Path, recalc_timestamp: bool = False, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -406,7 +407,7 @@ def fix_version( def subr( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -442,7 +443,7 @@ def subr( def desubr( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -480,7 +481,7 @@ def check_outlines( input_path: Path, quiet_mode: bool = False, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): diff --git a/foundryToolsCLI/CLI/ftcli_post.py b/foundryToolsCLI/CLI/ftcli_post.py index 7d7126b..7d9b757 100644 --- a/foundryToolsCLI/CLI/ftcli_post.py +++ b/foundryToolsCLI/CLI/ftcli_post.py @@ -1,5 +1,6 @@ from copy import copy from pathlib import Path +from typing import Optional import click from fontTools.misc.cliTools import makeOutputFileName @@ -44,15 +45,16 @@ def cli( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, - overwrite: bool = None, - **kwargs + output_dir: Optional[Path] = None, + overwrite: bool = False, + italic_angle: Optional[float] = None, + ul_position: Optional[int] = None, + ul_thickness: Optional[int] = None, + fixed_pitch: Optional[bool] = None, ): """A command line tool to manipulate the 'post' table.""" - params = {k: v for k, v in kwargs.items() if v is not None} - - if len(params) == 0: + if not any([italic_angle, ul_position, ul_thickness, fixed_pitch]): logger.error(Logs.no_parameter) return @@ -75,19 +77,19 @@ def cli( cff_table = cff_table_copy = None # Process the arguments - if "italic_angle" in params.keys(): - post_table.set_italic_angle(params.get("italic_angle")) + if italic_angle: + post_table.set_italic_angle(italic_angle) if font.is_otf: - font["CFF "].cff.topDictIndex[0].ItalicAngle = int(params.get("italic_angle")) + font["CFF "].cff.topDictIndex[0].ItalicAngle = round(italic_angle) - if "ul_position" in params.keys(): - post_table.set_underline_position(params.get("ul_position")) + if ul_position: + post_table.set_underline_position(ul_position) - if "ul_thickness" in params.keys(): - post_table.set_underline_thickness(params.get("ul_thickness")) + if ul_thickness: + post_table.set_underline_thickness(ul_thickness) - if "fixed_pitch" in params.keys(): - post_table.set_fixed_pitch(params.get("fixed_pitch")) + if fixed_pitch is not None: + post_table.set_fixed_pitch(fixed_pitch) # Check if tables have changed before saving the font. No need to compile here. if post_table_copy.compile(font) != post_table.compile(font): diff --git a/foundryToolsCLI/CLI/ftcli_print.py b/foundryToolsCLI/CLI/ftcli_print.py index 7cb8b54..c50572c 100644 --- a/foundryToolsCLI/CLI/ftcli_print.py +++ b/foundryToolsCLI/CLI/ftcli_print.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Optional import click @@ -44,7 +45,7 @@ def font_info(input_path: Path): Prints a minimal set of NameRecords, omitting the ones with nameID not in 1, 2, 3, 4, 5, 6, 16, 17, 18, 21, 22, 25 """, ) -def font_names(input_path: Path, max_lines: int = None, minimal: bool = False): +def font_names(input_path: Path, max_lines: Optional[int] = None, minimal: bool = False): """ Prints the 'name' table and, if the font is CFF, the names in the 'CFF' table topDict. """ diff --git a/foundryToolsCLI/CLI/ftcli_ttf.py b/foundryToolsCLI/CLI/ftcli_ttf.py index 60276d6..8681a14 100644 --- a/foundryToolsCLI/CLI/ftcli_ttf.py +++ b/foundryToolsCLI/CLI/ftcli_ttf.py @@ -1,5 +1,6 @@ from io import BytesIO from pathlib import Path +from typing import Optional import click from fontTools.misc.cliTools import makeOutputFileName @@ -28,7 +29,7 @@ def autohint( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -77,7 +78,7 @@ def autohint( def decompose( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -115,7 +116,7 @@ def dehint( input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -173,7 +174,7 @@ def fix_contours( remove_hinting: bool = True, verbose: bool = True, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -229,7 +230,7 @@ def remove_overlaps( remove_hinting: bool = True, ignore_errors: bool = False, recursive: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -284,7 +285,7 @@ def scale_upm( upm: int, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -342,7 +343,7 @@ def rename_glyph( new_glyph_name: str, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: Optional[Path] = None, overwrite: bool = True, ): """ @@ -362,7 +363,7 @@ def rename_glyph( glyph_names = font.getGlyphOrder() modified = False - for i in range(len(glyph_names)): + for i, _ in enumerate(glyph_names): if glyph_names[i] == old_glyph_name: glyph_names[i] = new_glyph_name logger.info(f"{old_glyph_name} renamed to {new_glyph_name}") diff --git a/foundryToolsCLI/CLI/ftcli_utils.py b/foundryToolsCLI/CLI/ftcli_utils.py index 55ac674..dc83c6e 100644 --- a/foundryToolsCLI/CLI/ftcli_utils.py +++ b/foundryToolsCLI/CLI/ftcli_utils.py @@ -1,5 +1,6 @@ import os import tempfile +import typing as t from copy import deepcopy from pathlib import Path @@ -17,6 +18,7 @@ add_file_or_path_argument, add_recursive_option, add_common_options, + choice_to_int_callback, ) from foundryToolsCLI.Lib.utils.logger import logger, Logs from foundryToolsCLI.Lib.utils.timer import Timer @@ -32,7 +34,7 @@ def add_dsig( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -82,10 +84,10 @@ def add_dsig( @Timer(logger=logger.info) def del_table( input_path: Path, - table_tags: tuple, + table_tags: t.Tuple, recursive: bool = False, recalc_timestamp: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, overwrite: bool = True, ): """ @@ -96,7 +98,7 @@ def del_table( if not initial_check_pass(fonts=fonts, output_dir=output_dir): return - table_tags = [tag.ljust(4, " ") for tag in table_tags] + table_tags = tuple(set(tag.ljust(4, " ") for tag in table_tags)) for font in fonts: removed_tables_counter = 0 @@ -193,11 +195,6 @@ def font_organizer( logger.opt(colors=True).success(f"{file} --> {target}") - # generic_info_message( - # f"{file.relative_to(input_path).name} {click.style('-->', fg='bright_magenta')} " - # f"{target.relative_to(input_path)}" - # ) - except Exception as e: logger.exception(e) finally: @@ -211,6 +208,7 @@ def font_organizer( "--source", type=click.Choice(choices=["1", "2", "3", "4", "5"]), default="1", + callback=choice_to_int_callback, help=""" The source string(s) from which to extract the new file name. Default is 1 (FamilyName-StyleName), used also as fallback name when 4 or 5 are passed but the font is TrueType @@ -234,9 +232,6 @@ def font_renamer(input_path: Path, source: str, recursive: bool = False): if not initial_check_pass(fonts=fonts): return - # click.Choice() only accepts strings as choices, so we need to convert to integer - source = int(source) - for font in fonts: file = Path(font.reader.file.name) @@ -272,7 +267,7 @@ def font_renamer(input_path: Path, source: str, recursive: bool = False): def rebuild( input_path: Path, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): @@ -322,12 +317,12 @@ def rebuild( @Timer(logger=logger.info) def set_revision( input_path: Path, - major: int = None, - minor: int = None, + major: t.Optional[int] = None, + minor: t.Optional[int] = None, unique_identifier: bool = False, version_string: bool = False, recursive: bool = False, - output_dir: Path = None, + output_dir: t.Optional[Path] = None, recalc_timestamp: bool = False, overwrite: bool = True, ): diff --git a/foundryToolsCLI/Lib/Font.py b/foundryToolsCLI/Lib/Font.py index 5065c02..6458a8c 100644 --- a/foundryToolsCLI/Lib/Font.py +++ b/foundryToolsCLI/Lib/Font.py @@ -1,6 +1,7 @@ import math import os import tempfile +import typing as t from collections import Counter from pathlib import Path @@ -21,7 +22,9 @@ # The Font class is a subclass of the fontTools.ttLib.TTFont class. class Font(TTFont): - def __init__(self, file=None, recalcBBoxes: bool = True, recalcTimestamp: bool = False, lazy: bool = None): + def __init__( + self, file=None, recalcBBoxes: bool = True, recalcTimestamp: bool = False, lazy: t.Optional[bool] = None + ): super().__init__(file=file, recalcBBoxes=recalcBBoxes, recalcTimestamp=recalcTimestamp, lazy=lazy) @property @@ -345,12 +348,13 @@ def recalc_x_height(self) -> int: def recalc_cap_height(self) -> int: return self.get_glyph_bounds("H")["yMax"] - def recalc_max_context(self) -> int: - if self["OS/2"].version >= 2: - from fontTools.otlLib.maxContextCalc import maxCtxFont + def recalc_max_context(self) -> t.Optional[int]: + if self["OS/2"].version < 2: + return None + from fontTools.otlLib.maxContextCalc import maxCtxFont - max_context = maxCtxFont(self) - return max_context + max_context = maxCtxFont(self) + return max_context def set_created_timestamp(self, timestamp: int) -> None: """ @@ -372,7 +376,7 @@ def set_modified_timestamp(self, timestamp: int) -> None: """ self["head"].modified = timestamp - def get_real_extension(self) -> str: + def get_real_extension(self) -> t.Optional[str]: """ This function returns the file extension of a font file based on its flavor or type. :return: A string representing the file extension of a font file. If the font has a flavor, the @@ -385,6 +389,7 @@ def get_real_extension(self) -> str: return ".ttf" elif self.is_otf: return ".otf" + return None def ttf_decomponentize(self) -> None: """ @@ -640,7 +645,7 @@ def get_ui_name_ids(self) -> list: ui_name_ids.append(record.Feature.FeatureParams.UINameID) return sorted(set(ui_name_ids)) - def reorder_ui_name_ids(self): + def reorder_ui_name_ids(self) -> None: """ Takes the IDs of the UI names in the name table and reorders them to start at 256 """ @@ -894,14 +899,13 @@ def get_font_v_metrics(self) -> dict: return font_v_metrics - def get_font_feature_tags(self) -> list: + def get_font_feature_tags(self) -> t.List[str]: """ Returns a sorted list of all the feature tags in the font's GSUB and GPOS tables :return: A list of feature tags. """ - - feature_tags = set() + feature_tags = set[str]() for table_tag in ("GSUB", "GPOS"): if table_tag in self: if not self[table_tag].table.ScriptList or not self[table_tag].table.FeatureList: @@ -917,7 +921,7 @@ def calculate_italic_angle(self, min_slant: float = 2.0) -> int: for g in ("H", "uni0048"): try: glyph_set[g].draw(pen) - italic_angle = -1 * round(math.degrees(math.atan(pen.slant))) + italic_angle: int = -1 * round(math.degrees(math.atan(pen.slant))) if abs(italic_angle) >= abs(min_slant): return italic_angle else: diff --git a/foundryToolsCLI/Lib/assistant/UI.py b/foundryToolsCLI/Lib/assistant/UI.py index 2ae44ef..b985620 100644 --- a/foundryToolsCLI/Lib/assistant/UI.py +++ b/foundryToolsCLI/Lib/assistant/UI.py @@ -1,6 +1,7 @@ import os.path import sys from pathlib import Path +from typing import Optional import click from rich import box @@ -487,7 +488,7 @@ def __prompt_for_int_range( text: str, min_value: int, max_value: int, - default: int = None, + default: Optional[int] = None, bold: bool = True, fg_color: str = "cyan", ) -> int: @@ -575,33 +576,3 @@ def get_fonts_rows(fonts: list[Font]) -> list: except Exception as e: logger.exception(e) return rows - - -def get_fonts_list_table(rows: list) -> Table: - table = Table(box=box.HORIZONTALS) - table.add_column("#", justify="right") - table.add_column("File name") - table.add_column("Family name") - table.add_column("Style name") - table.add_column("Width", justify="right") - table.add_column("Weight", justify="right") - table.add_column("Regular", justify="right") - table.add_column("Italic", justify="right") - table.add_column("Bold", justify="right") - table.add_column("Oblique", justify="right") - - for i, row in enumerate(rows, start=1): - table.add_row( - str(i), - row["file_name"], - row["family_name"], - row["subfamily_name"], - row["us_width_class"], - row["us_weight_class"], - row["is_regular"], - row["is_italic"], - row["is_bold"], - row["is_oblique"], - ) - - return table diff --git a/foundryToolsCLI/Lib/assistant/fonts_data.py b/foundryToolsCLI/Lib/assistant/fonts_data.py index 96e8693..8a83455 100644 --- a/foundryToolsCLI/Lib/assistant/fonts_data.py +++ b/foundryToolsCLI/Lib/assistant/fonts_data.py @@ -1,4 +1,6 @@ import csv +import typing as t + from pathlib import Path import click @@ -148,20 +150,20 @@ def write_data_to_font( self, font: Font, row: dict, + linked_styles: t.Optional[t.Tuple[int, int]] = None, + shorten_width: t.Union[t.Tuple[int], t.Tuple[()]] = (), + shorten_weight: t.Union[t.Tuple[int], t.Tuple[()]] = (), + shorten_slope: t.Union[t.Tuple[int], t.Tuple[()]] = (), + exclude_namerecords: t.Union[t.Tuple[int], t.Tuple[()]] = (), width_elidable: str = "Normal", weight_elidable: str = "Regular", keep_width_elidable: bool = False, keep_weight_elidable: bool = False, - linked_styles: list = None, - exclude_namerecords: tuple = None, - shorten_width=None, - shorten_weight=None, - shorten_slope=None, - super_family=False, - alt_uid=False, - oblique_not_italic=False, - auto_shorten=True, - cff=False, + super_family: bool = False, + alt_uid: bool = False, + oblique_not_italic: bool = False, + auto_shorten: bool = True, + cff: bool = False, ): family_name = row["family_name"] is_italic = bool(int(row["is_italic"])) @@ -254,7 +256,6 @@ def write_data_to_font( if us_weight_class in linked_styles: family_name_win = family_name_win.replace(weight, "").replace(" ", " ").strip() - linked_styles.sort() if us_weight_class == linked_styles[1]: # The bold bit is set HERE AND ONLY HERE. font.set_bold_flag(True) diff --git a/foundryToolsCLI/Lib/converters/variable_to_static.py b/foundryToolsCLI/Lib/converters/variable_to_static.py index 5a674d7..268ba66 100644 --- a/foundryToolsCLI/Lib/converters/variable_to_static.py +++ b/foundryToolsCLI/Lib/converters/variable_to_static.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Optional, List from fontTools.misc.cliTools import makeOutputFileName from fontTools.varLib.instancer import instantiateVariableFont, OverlapMode @@ -14,7 +15,7 @@ class VariableToStatic(object): def __init__(self): self.options = Var2StaticOptions() - def run(self, variable_font: VariableFont, instances: list = None): + def run(self, variable_font: VariableFont, instances: Optional[List] = None): if not instances: instances = variable_font.get_instances() diff --git a/foundryToolsCLI/Lib/printer/UI.py b/foundryToolsCLI/Lib/printer/UI.py index 8dfa057..44d4779 100644 --- a/foundryToolsCLI/Lib/printer/UI.py +++ b/foundryToolsCLI/Lib/printer/UI.py @@ -1,3 +1,5 @@ +from typing import Optional + import os.path from shutil import get_terminal_size @@ -189,13 +191,13 @@ def print_font_info(font: Font): console.print(table) -def print_font_names(font: Font, max_lines: int = None, minimal: bool = False): +def print_font_names(font: Font, max_lines: Optional[int] = None, minimal: bool = False): """ Prints the names in the name table and in the CFF table if present :param font: The Font object to print the names from :type font: Font - :param max_lines: The maximum number of lines to print for each namerecord string. If the name string is longer than + :param max_lines: The maximum number of lines to print for each NameRecord string. If the name string is longer than this, it will be truncated :param minimal: Prints only namerecords with nameID in 1, 2, 3, 4, 5, 6, 16, 17, 18, 21, 22, 25 :type minimal: bool diff --git a/foundryToolsCLI/Lib/tables/OS_2.py b/foundryToolsCLI/Lib/tables/OS_2.py index 72b1baf..c0fb2b4 100644 --- a/foundryToolsCLI/Lib/tables/OS_2.py +++ b/foundryToolsCLI/Lib/tables/OS_2.py @@ -1,3 +1,5 @@ +import typing as t + from fontTools.misc.textTools import num2binary from fontTools.ttLib import registerCustomTableClass from fontTools.ttLib.tables.O_S_2f_2 import table_O_S_2f_2 @@ -366,9 +368,10 @@ def set_codepage_ranges(self, codepage_ranges) -> None: setattr(self, "ulCodePageRange1", codepage_ranges[0]) setattr(self, "ulCodePageRange2", codepage_ranges[1]) - def get_codepage_ranges(self) -> tuple[int, int]: - if self.version >= 1: - return getattr(self, "ulCodePageRange1"), getattr(self, "ulCodePageRange2") + def get_codepage_ranges(self) -> t.Optional[t.Tuple[int, int]]: + if self.version < 1: + return None + return getattr(self, "ulCodePageRange1"), getattr(self, "ulCodePageRange2") def to_dict(self) -> dict: """ @@ -400,9 +403,9 @@ def to_dict(self) -> dict: def panose_to_dict(self) -> dict: panose_dict = {} - panose_family_type = self.panose.bFamilyType + panose_family_type: int = self.panose.bFamilyType panose_data = PANOSE_STRUCT["bFamilyType"].get(panose_family_type) - family_description = panose_data["description"] + family_description: str = panose_data["description"] panose_dict.update({"bFamilyType": f"Family Type: {panose_family_type} - {family_description}"}) family_sub_digits = panose_data["sub_digits"] diff --git a/foundryToolsCLI/Lib/utils/cli_tools.py b/foundryToolsCLI/Lib/utils/cli_tools.py index b86287e..fa3c4b7 100644 --- a/foundryToolsCLI/Lib/utils/cli_tools.py +++ b/foundryToolsCLI/Lib/utils/cli_tools.py @@ -1,4 +1,6 @@ -import pathlib +from typing import Optional + +from pathlib import Path from fontTools.ttLib import TTLibError @@ -7,7 +9,7 @@ from foundryToolsCLI.Lib.utils.logger import logger -def get_files_in_path(input_path: pathlib.Path, recursive: bool = False) -> list[pathlib.Path]: +def get_files_in_path(input_path: Path, recursive: bool = False) -> list[Path]: """ Get a list of files from a path. If the path is a directory, the function will return a list of all files found in the directory. If ``recursive`` is True, the function will recursively search for files in subdirectories. If the @@ -29,7 +31,7 @@ def get_files_in_path(input_path: pathlib.Path, recursive: bool = False) -> list def get_variable_fonts_in_path( - input_path: pathlib.Path, recursive: bool = False, recalc_timestamp: bool = False + input_path: Path, recursive: bool = False, recalc_timestamp: bool = False ) -> list[VariableFont]: """ Get a list of VariableFont objects from a path. If the path is a directory, the function will return a list of all @@ -56,7 +58,7 @@ def get_variable_fonts_in_path( def get_fonts_in_path( - input_path: pathlib.Path, + input_path: Path, recursive: bool = False, recalc_timestamp: bool = False, allow_extensions: list = None, @@ -115,28 +117,7 @@ def get_fonts_in_path( return fonts -# Actually, this function is not used anywhere -def get_output_dir(input_path: pathlib.Path, output_dir: pathlib.Path = None) -> pathlib.Path: - """ - If the output directory is not specified, then the output directory is the directory of the input file if the input - is a file, or the input directory if the input is a directory - - :param input_path: The path to the input file or directory - :type input_path: str - :param output_dir: The output directory, if specified - :type output_dir: str - :return: The output directory. - """ - if output_dir is not None: - return output_dir.resolve() - else: - if input_path.is_file(): - return input_path.parent.resolve() - else: - return input_path.resolve() - - -def initial_check_pass(fonts: list, output_dir: pathlib.Path = None) -> bool: +def initial_check_pass(fonts: list, output_dir: Optional[Path] = None) -> bool: """ Checks if the list of fonts is not empty and if the output directory is writable. @@ -158,7 +139,7 @@ def initial_check_pass(fonts: list, output_dir: pathlib.Path = None) -> bool: return True -def get_project_files_path(input_path: pathlib.Path) -> pathlib.Path: +def get_project_files_path(input_path: Path) -> Path: """ Get the path to the directory containing the project files (styles_mapping.json, fonts_data.csv). :param input_path: @@ -172,25 +153,25 @@ def get_project_files_path(input_path: pathlib.Path) -> pathlib.Path: return project_files_path -def get_style_mapping_path(input_path: pathlib.Path) -> pathlib.Path: +def get_style_mapping_path(input_path: Path) -> Path: """ Get the path to the styles_mapping.json file. :param input_path: Path to the input file or directory :return: A pathlib.Path object representing the styles_mapping.json file """ project_files_dir = get_project_files_path(input_path) - styles_mapping_file = pathlib.Path.joinpath(project_files_dir, "styles_mapping.json") + styles_mapping_file = Path.joinpath(project_files_dir, "styles_mapping.json") return styles_mapping_file -def get_fonts_data_path(input_path: pathlib.Path) -> pathlib.Path: +def get_fonts_data_path(input_path: Path) -> Path: """ Get the path to the fonts_data.csv file. :param input_path: Path to the input file or directory :return: A pathlib.Path object representing the fonts_data.csv file """ project_files_dir = get_project_files_path(input_path) - fonts_data_file = pathlib.Path.joinpath(project_files_dir, "fonts_data.csv") + fonts_data_file = Path.joinpath(project_files_dir, "fonts_data.csv") return fonts_data_file diff --git a/foundryToolsCLI/Lib/utils/click_tools.py b/foundryToolsCLI/Lib/utils/click_tools.py index a4a43ae..8eaf3e3 100644 --- a/foundryToolsCLI/Lib/utils/click_tools.py +++ b/foundryToolsCLI/Lib/utils/click_tools.py @@ -1,9 +1,45 @@ -import pathlib +import typing as t from pathlib import Path import click +def choice_to_int_callback( + ctx: click.Context, param: click.Parameter, value: t.Union[str, t.Tuple] +) -> t.Union[int, t.Tuple]: + """ + Converts a click choice to an integer, or a tuple of integers for multiple choice. + :param ctx: click Context + :param param: click Parameter + :param value: string or tuple of strings to convert + :return: an int or a tuple of ints + """ + if not value or ctx.resilient_parsing: + return tuple() + if param.multiple: + return tuple(sorted(set(int(v) for v in value))) + else: + return int(value) + + +def linked_styles_callback( + ctx: click.Context, _: click.Parameter, value: t.Optional[t.Tuple[int, int]] +) -> t.Optional[t.Tuple]: + """ + Callback for --linked-styles option. + :param ctx: click Context + :param _: click Parameter. Not used + :param value: a tuple of 2 ints + :return: a sorted tuple of 2 ints + """ + if not value or ctx.resilient_parsing: + return None + value = tuple(sorted(set(int(v) for v in value))) + if len(value) != 2: + raise click.BadParameter(f"Expected 2 different values for --linked-styles, got {len(value)}") + return value + + def add_options(options): def _add_options(func): for option in reversed(options): @@ -17,9 +53,7 @@ def add_file_or_path_argument(dir_okay=True, file_okay=True): _file_or_path_argument = [ click.argument( "input_path", - type=click.Path( - exists=True, resolve_path=True, path_type=pathlib.Path, dir_okay=dir_okay, file_okay=file_okay - ), + type=click.Path(exists=True, resolve_path=True, path_type=Path, dir_okay=dir_okay, file_okay=file_okay), ) ] return add_options(_file_or_path_argument) @@ -45,7 +79,7 @@ def add_common_options(): click.option( "-out", "--output-dir", - type=click.Path(path_type=pathlib.Path, file_okay=False, resolve_path=True), + type=click.Path(path_type=Path, file_okay=False, resolve_path=True), default=None, help=""" Specify the directory where output files are to be saved. If the directory doesn't exist, will be created.