foundrytools-cli 2.0.5__tar.gz → 2.1.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 (38) hide show
  1. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/PKG-INFO +4 -3
  2. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/pyproject.toml +7 -6
  3. foundrytools_cli-2.1.0/src/foundrytools_cli/__init__.py +3 -0
  4. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/converter/cli.py +1 -1
  5. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/hhea.py +3 -1
  6. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/otf.py +12 -5
  7. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/utils/cli.py +2 -1
  8. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/utils/font_renamer.py +62 -0
  9. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/utils/__init__.py +3 -3
  10. foundrytools_cli-2.0.5/src/foundrytools_cli/__init__.py +0 -3
  11. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/README.md +0 -0
  12. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/__main__.py +0 -0
  13. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/__init__.py +0 -0
  14. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/cff.py +0 -0
  15. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/cmap.py +0 -0
  16. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/converter/__init__.py +0 -0
  17. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/converter/ttf_to_otf.py +0 -0
  18. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/fix/__init__.py +0 -0
  19. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/fix/cli.py +0 -0
  20. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/fix/legacy_accents.py +0 -0
  21. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/fix/vertical_metrics.py +0 -0
  22. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/font.py +0 -0
  23. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/gsub.py +0 -0
  24. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/name.py +0 -0
  25. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/os_2.py +0 -0
  26. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/post.py +0 -0
  27. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/print/__init__.py +0 -0
  28. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/print/cli.py +0 -0
  29. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/print/font_info.py +0 -0
  30. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/print/font_names.py +0 -0
  31. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/print/vf_instances.py +0 -0
  32. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/ttf.py +0 -0
  33. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/utils/__init__.py +0 -0
  34. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/utils/font_organizer.py +0 -0
  35. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/commands/utils/sync_timestamps.py +0 -0
  36. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/utils/logger.py +0 -0
  37. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/utils/task_runner.py +0 -0
  38. {foundrytools_cli-2.0.5 → foundrytools_cli-2.1.0}/src/foundrytools_cli/utils/timer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foundrytools-cli
3
- Version: 2.0.5
3
+ Version: 2.1.0
4
4
  Summary: A set of command line tools to inspect, manipulate and convert font files
5
5
  Author: Cesare Gilento
6
6
  Author-email: Cesare Gilento <ftcli@proton.me>
@@ -15,14 +15,15 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
18
19
  Classifier: License :: OSI Approved :: MIT License
19
20
  Classifier: Topic :: Text Processing :: Fonts
20
21
  Requires-Dist: afdko>=4.0.3
21
22
  Requires-Dist: click>=8.3.1
22
- Requires-Dist: foundrytools>=0.1.5
23
+ Requires-Dist: foundrytools>=0.1.6
23
24
  Requires-Dist: loguru>=0.7.3
24
25
  Requires-Dist: pathvalidate>=3.3.1
25
- Requires-Dist: rich>=14.2.0
26
+ Requires-Dist: rich>=14.3.0
26
27
  Requires-Dist: ufolib2>=0.18.1
27
28
  Requires-Dist: win32-setctime>=1.2.0 ; sys_platform >= 'win32'
28
29
  Requires-Python: >=3.10
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "foundrytools-cli"
7
- version = "2.0.5"
7
+ version = "2.1.0"
8
8
  description = "A set of command line tools to inspect, manipulate and convert font files"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.11",
25
25
  "Programming Language :: Python :: 3.12",
26
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
27
28
  "License :: OSI Approved :: MIT License",
28
29
  "Topic :: Text Processing :: Fonts",
29
30
  ]
@@ -31,10 +32,10 @@ classifiers = [
31
32
  dependencies = [
32
33
  "afdko>=4.0.3",
33
34
  "click>=8.3.1",
34
- "foundrytools>=0.1.5",
35
+ "foundrytools>=0.1.6",
35
36
  "loguru>=0.7.3",
36
37
  "pathvalidate>=3.3.1",
37
- "rich>=14.2.0",
38
+ "rich>=14.3.0",
38
39
  "ufolib2>=0.18.1",
39
40
  "win32-setctime>=1.2.0; sys_platform >= 'win32'",
40
41
  ]
@@ -48,7 +49,7 @@ dev = [
48
49
  ]
49
50
  docs = [
50
51
  "sphinx-click>=6.2.0",
51
- "sphinx-rtd-theme>=3.0.2",
52
+ "sphinx-rtd-theme>=3.1.0",
52
53
  ]
53
54
 
54
55
  [project.scripts]
@@ -58,9 +59,9 @@ ftcli = "foundrytools_cli.__main__:cli"
58
59
  Homepage = "https://github.com/ftCLI/FoundryTools-CLI"
59
60
 
60
61
  [tool.bumpversion]
61
- current_version = "2.0.5"
62
+ current_version = "2.1.0"
62
63
  commit = false
63
- tag = true
64
+ tag = false
64
65
 
65
66
  [[tool.bumpversion.files]]
66
67
  filename = "src/foundrytools_cli/__init__.py"
@@ -0,0 +1,3 @@
1
+ VERSION = __version__ = "2.1.0"
2
+
3
+ __all__ = ["VERSION"]
@@ -142,7 +142,7 @@ def otf_to_ttf(input_path: Path, **options: dict[str, Any]) -> None:
142
142
  """,
143
143
  )
144
144
  @click.option(
145
- "-cc/--no-cc",
145
+ "-cc/-no-cc",
146
146
  "--correct-contours/--no-correct-contours",
147
147
  is_flag=True,
148
148
  default=True,
@@ -50,7 +50,9 @@ def recalc_caret_offset(input_path: Path, **options: dict[str, Any]) -> None:
50
50
  source_file = font.file
51
51
  else:
52
52
  font.ttfont.flavor = None
53
- source_file = font.temp_file
53
+ temp_file = get_temp_file_path()
54
+ font.save(temp_file)
55
+ source_file = temp_file
54
56
  font.save(source_file)
55
57
 
56
58
  command_1 = ["tx", "-t1", source_file, temp_t1_file]
@@ -4,6 +4,7 @@ from typing import Any, cast
4
4
 
5
5
  import click
6
6
  from foundrytools import Font
7
+ from foundrytools.app.otf_autohint import OTFAutohintError
7
8
  from foundrytools.app.otf_autohint import run as otf_autohint
8
9
  from foundrytools.app.otf_check_outlines import run as otf_check_outlines
9
10
  from foundrytools.app.otf_dehint import run as otf_dehint
@@ -58,8 +59,8 @@ def subroutinize_flag() -> Callable:
58
59
  """,
59
60
  )
60
61
  @click.option(
61
- "-d",
62
- "--decimal",
62
+ "-d/-no-d",
63
+ "--decimal/--no-decimal",
63
64
  "roundCoords",
64
65
  is_flag=True,
65
66
  default=True,
@@ -95,7 +96,12 @@ def autohint(input_path: Path, **options: dict[str, Any]) -> None:
95
96
 
96
97
  def task(font: Font, subroutinize: bool = True, **kwargs: dict[str, Any]) -> bool:
97
98
  logger.info("Autohinting...")
98
- otf_autohint(font, **kwargs)
99
+
100
+ try:
101
+ otf_autohint(font, **kwargs)
102
+ except OTFAutohintError as e:
103
+ logger.error(f"Autohinting failed: {e}")
104
+ return False
99
105
 
100
106
  if subroutinize:
101
107
  font.reload() # DO NOT REMOVE
@@ -270,8 +276,9 @@ def recalc_stems(input_path: Path, **options: dict[str, Any]) -> None:
270
276
  temp_file = get_temp_file_path()
271
277
  if flavor is not None:
272
278
  font.ttfont.flavor = None
273
- font.save(font.temp_file)
274
- input_file = font.temp_file
279
+ temp_file = get_temp_file_path()
280
+ font.save(temp_file)
281
+ input_file = temp_file
275
282
  else:
276
283
  input_file = font.file
277
284
 
@@ -39,7 +39,7 @@ cli = click.Group(help="Miscellaneous utilities.")
39
39
  @click.option(
40
40
  "-s",
41
41
  "--source",
42
- type=click.Choice(choices=["1", "2", "3", "4", "5"]),
42
+ type=click.Choice(choices=["1", "2", "3", "4", "5", "6"]),
43
43
  default="1",
44
44
  callback=choice_to_int_callback,
45
45
  help="""
@@ -53,6 +53,7 @@ cli = click.Group(help="Miscellaneous utilities.")
53
53
  3: Full Font Name
54
54
  4: CFF fontNames (CFF fonts only)
55
55
  5: CFF TopDict FullName (CFF fonts only)
56
+ 6: Macintosh LWFN (LaserWriter Font Naming) using 5:3:3 rule
56
57
  """,
57
58
  )
58
59
  @click.option(
@@ -1,3 +1,5 @@
1
+ # ruff: noqa: C901
2
+ import re
1
3
  from pathlib import Path
2
4
 
3
5
  from foundrytools import Font
@@ -6,6 +8,59 @@ from pathvalidate import sanitize_filename
6
8
  from foundrytools_cli.utils.logger import logger
7
9
 
8
10
 
11
+ def _apply_533_rule(postscript_name: str) -> str:
12
+ """
13
+ Apply the Macintosh LaserWriter Font Naming (LWFN) 5:3:3 rule.
14
+
15
+ The rule divides the font name into "words" where each uppercase letter begins
16
+ a new word. The file name is derived by:
17
+ - Taking the initial uppercase letter and up to 4 lowercase letters in the first word
18
+ - Taking the initial uppercase letter and up to 2 lowercase letters for subsequent words
19
+ - Dropping hyphens between family and style portions
20
+
21
+ Args:
22
+ postscript_name: The PostScript name (e.g., "Helvetica-BoldOblique")
23
+
24
+ Returns:
25
+ The abbreviated filename following the 5:3:3 rule (e.g., "HelveBolObl")
26
+
27
+ Examples:
28
+ >>> _apply_533_rule("Helvetica-BoldOblique")
29
+ 'HelveBolObl'
30
+ >>> _apply_533_rule("AGaramond-Bold")
31
+ 'AGarBol'
32
+ >>> _apply_533_rule("Palatino-Roman")
33
+ 'PalatRom'
34
+ >>> _apply_533_rule("Optima")
35
+ 'Optim'
36
+ """
37
+ # Remove hyphens to get a continuous string
38
+ name_without_hyphens = postscript_name.replace("-", "")
39
+
40
+ # Split into words where each uppercase letter begins a new word
41
+ # This regex finds: uppercase letter followed by any number of lowercase letters
42
+ words = re.findall(r"[A-Z][a-z]*", name_without_hyphens)
43
+
44
+ if not words:
45
+ # Fallback for invalid/unusual PostScript names without uppercase letters.
46
+ # PostScript font names should start with uppercase per Adobe spec, but we handle
47
+ # the edge case gracefully. The 11-char limit approximates typical LWFN length
48
+ # (5+3+3 for a three-word name like FamilyStyleVariant).
49
+ MAX_FALLBACK_LENGTH = 11
50
+ return name_without_hyphens[:MAX_FALLBACK_LENGTH]
51
+
52
+ result = []
53
+ for i, word in enumerate(words):
54
+ if i == 0:
55
+ # First word: take up to 5 characters (1 uppercase + 4 lowercase)
56
+ result.append(word[:5])
57
+ else:
58
+ # Subsequent words: take up to 3 characters (1 uppercase + 2 lowercase)
59
+ result.append(word[:3])
60
+
61
+ return "".join(result)
62
+
63
+
9
64
  def _get_file_stem(font: Font, source: int = 1) -> str:
10
65
  """
11
66
  Get the best file name for a font.
@@ -20,6 +75,7 @@ def _get_file_stem(font: Font, source: int = 1) -> str:
20
75
  3: Full Font Name
21
76
  4: CFF fontNames (CFF fonts only)
22
77
  5: CFF TopDict FullName (CFF fonts only)
78
+ 6: Macintosh LWFN (LaserWriter Font Naming) using 5:3:3 rule
23
79
 
24
80
  Returns:
25
81
  A sanitized file name for the font.
@@ -49,6 +105,12 @@ def _get_file_stem(font: Font, source: int = 1) -> str:
49
105
  file_name = font.t_cff_.table.cff.fontNames[0]
50
106
  elif source == 5 and font.is_ps:
51
107
  file_name = font.t_cff_.top_dict.FullName
108
+ elif source == 6:
109
+ # Build name from family and subfamily for LWFN 5:3:3 rule
110
+ family_name = str(font.t_name.table.getBestFamilyName())
111
+ subfamily_name = str(font.t_name.table.getBestSubFamilyName())
112
+ font_name = f"{family_name}-{subfamily_name}".replace(" ", "").replace(".", "")
113
+ file_name = _apply_533_rule(font_name)
52
114
  else:
53
115
  raise ValueError("Invalid source value.")
54
116
  return sanitize_filename(file_name, platform="auto")
@@ -42,7 +42,7 @@ class BaseCommand(click.Command):
42
42
  """,
43
43
  ),
44
44
  click.Option(
45
- ["-no-ow", "--no-overwrite", "overwrite"],
45
+ ["-ow/-no-ow", "--overwrite/--no-overwrite"],
46
46
  is_flag=True,
47
47
  default=True,
48
48
  help="""
@@ -55,7 +55,7 @@ class BaseCommand(click.Command):
55
55
  """,
56
56
  ),
57
57
  click.Option(
58
- ["-no-rbb", "--no-recalc-bboxes", "recalc_bboxes"],
58
+ ["-rbb/-no-rbb", "--recalc-bboxes/--no-recalc-bboxes"],
59
59
  is_flag=True,
60
60
  default=True,
61
61
  help="""
@@ -67,7 +67,7 @@ class BaseCommand(click.Command):
67
67
  """,
68
68
  ),
69
69
  click.Option(
70
- ["-rtb", "--reorder-tables", "reorder_tables"],
70
+ ["-rtb", "--reorder-tables"],
71
71
  is_flag=True,
72
72
  default=False,
73
73
  help="""
@@ -1,3 +0,0 @@
1
- VERSION = __version__ = "2.0.5"
2
-
3
- __all__ = ["VERSION"]