foundrytools-cli 2.2.0__py3-none-any.whl → 2.2.2__py3-none-any.whl
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.
- foundrytools_cli/__init__.py +1 -1
- foundrytools_cli/commands/font.py +40 -0
- foundrytools_cli/commands/otf.py +71 -16
- foundrytools_cli/commands/utils/font_organizer.py +10 -3
- {foundrytools_cli-2.2.0.dist-info → foundrytools_cli-2.2.2.dist-info}/METADATA +2 -2
- {foundrytools_cli-2.2.0.dist-info → foundrytools_cli-2.2.2.dist-info}/RECORD +8 -8
- {foundrytools_cli-2.2.0.dist-info → foundrytools_cli-2.2.2.dist-info}/WHEEL +1 -1
- {foundrytools_cli-2.2.0.dist-info → foundrytools_cli-2.2.2.dist-info}/entry_points.txt +0 -0
foundrytools_cli/__init__.py
CHANGED
|
@@ -11,6 +11,46 @@ from foundrytools_cli.utils.task_runner import TaskRunner
|
|
|
11
11
|
cli = click.Group(help="Font level utilities.")
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
@cli.command("add-extremes", cls=BaseCommand)
|
|
15
|
+
@click.option(
|
|
16
|
+
"--keep-hinting",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
help="Preserve hinting when adding extrema points.",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--tolerance",
|
|
22
|
+
type=click.FloatRange(min=0),
|
|
23
|
+
default=1.0,
|
|
24
|
+
show_default=True,
|
|
25
|
+
help="Do not add extrema points within this distance (font units) of existing points.",
|
|
26
|
+
)
|
|
27
|
+
def add_extremes(input_path: Path, **options: dict[str, Any]) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Add points at the horizontal and vertical extrema of glyph curves.
|
|
30
|
+
|
|
31
|
+
Composite glyphs in TrueType fonts and variable fonts are not modified.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def task(font: Font, keep_hinting: bool = False, tolerance: float = 1.0) -> bool:
|
|
35
|
+
modified_glyphs = font.add_extremes(
|
|
36
|
+
remove_hinting=not keep_hinting,
|
|
37
|
+
tolerance=tolerance,
|
|
38
|
+
)
|
|
39
|
+
if not modified_glyphs:
|
|
40
|
+
logger.info("No glyphs were modified")
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
logger.opt(colors=True).info(
|
|
44
|
+
f"{len(modified_glyphs)} glyphs were modified: "
|
|
45
|
+
f"<lc>{', '.join(sorted(modified_glyphs))}</lc>"
|
|
46
|
+
)
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
runner = TaskRunner(input_path=input_path, task=task, **options)
|
|
50
|
+
runner.filter.filter_out_variable = True
|
|
51
|
+
runner.run()
|
|
52
|
+
|
|
53
|
+
|
|
14
54
|
@cli.command("correct-contours", cls=BaseCommand)
|
|
15
55
|
@click.option(
|
|
16
56
|
"-ma",
|
foundrytools_cli/commands/otf.py
CHANGED
|
@@ -258,12 +258,53 @@ def round_coordinates(input_path: Path, **options: dict[str, Any]) -> None:
|
|
|
258
258
|
The number of vertical stem values to extract.
|
|
259
259
|
""",
|
|
260
260
|
)
|
|
261
|
+
@click.option(
|
|
262
|
+
"--use-samples/--all-glyphs",
|
|
263
|
+
default=True,
|
|
264
|
+
show_default=True,
|
|
265
|
+
help="Calculate each value from its default or custom glyph sample, or from the whole font.",
|
|
266
|
+
)
|
|
267
|
+
@click.option(
|
|
268
|
+
"--std-h-sample",
|
|
269
|
+
multiple=True,
|
|
270
|
+
help="Characters to sample for StdHW; may be repeated. Defaults to the built-in sample.",
|
|
271
|
+
)
|
|
272
|
+
@click.option(
|
|
273
|
+
"--std-v-sample",
|
|
274
|
+
multiple=True,
|
|
275
|
+
help="Characters to sample for StdVW; may be repeated. Defaults to the built-in sample.",
|
|
276
|
+
)
|
|
277
|
+
@click.option(
|
|
278
|
+
"--snap-h-sample",
|
|
279
|
+
multiple=True,
|
|
280
|
+
help="Characters to sample for StemSnapH; may be repeated. Defaults to the built-in sample.",
|
|
281
|
+
)
|
|
282
|
+
@click.option(
|
|
283
|
+
"--snap-v-sample",
|
|
284
|
+
multiple=True,
|
|
285
|
+
help="Characters to sample for StemSnapV; may be repeated. Defaults to the built-in sample.",
|
|
286
|
+
)
|
|
261
287
|
def recalc_stems(input_path: Path, **options: dict[str, Any]) -> None:
|
|
262
288
|
"""
|
|
263
289
|
Recalculate the hinting stems of OpenType-PS fonts.
|
|
264
290
|
"""
|
|
265
291
|
|
|
266
|
-
|
|
292
|
+
sample_options = ("std_h_sample", "std_v_sample", "snap_h_sample", "snap_v_sample")
|
|
293
|
+
if not options["use_samples"] and any(options[name] for name in sample_options):
|
|
294
|
+
raise click.UsageError("Custom glyph samples cannot be used with --all-glyphs.")
|
|
295
|
+
|
|
296
|
+
def task(
|
|
297
|
+
font: Font,
|
|
298
|
+
report_all_stems: bool = False,
|
|
299
|
+
max_distance: int = 1,
|
|
300
|
+
max_h_stems: int = 2,
|
|
301
|
+
max_v_stems: int = 2,
|
|
302
|
+
use_samples: bool = True,
|
|
303
|
+
std_h_sample: tuple[str, ...] = (),
|
|
304
|
+
std_v_sample: tuple[str, ...] = (),
|
|
305
|
+
snap_h_sample: tuple[str, ...] = (),
|
|
306
|
+
snap_v_sample: tuple[str, ...] = (),
|
|
307
|
+
) -> bool:
|
|
267
308
|
if not font.is_ps:
|
|
268
309
|
logger.error("Font is not a PostScript font")
|
|
269
310
|
return False
|
|
@@ -273,11 +314,17 @@ def recalc_stems(input_path: Path, **options: dict[str, Any]) -> None:
|
|
|
273
314
|
return False
|
|
274
315
|
|
|
275
316
|
flavor = font.ttfont.flavor
|
|
276
|
-
temp_file =
|
|
317
|
+
temp_file: Path | None = None
|
|
277
318
|
if flavor is not None:
|
|
278
319
|
font.ttfont.flavor = None
|
|
279
320
|
temp_file = get_temp_file_path()
|
|
280
|
-
|
|
321
|
+
try:
|
|
322
|
+
font.save(temp_file)
|
|
323
|
+
except Exception:
|
|
324
|
+
temp_file.unlink(missing_ok=True)
|
|
325
|
+
raise
|
|
326
|
+
finally:
|
|
327
|
+
font.ttfont.flavor = flavor
|
|
281
328
|
input_file = temp_file
|
|
282
329
|
else:
|
|
283
330
|
input_file = font.file
|
|
@@ -289,22 +336,31 @@ def recalc_stems(input_path: Path, **options: dict[str, Any]) -> None:
|
|
|
289
336
|
current_stem_snap_h = font.t_cff_.get_hinting_data().get("StemSnapH", None)
|
|
290
337
|
current_stem_snap_v = font.t_cff_.get_hinting_data().get("StemSnapV", None)
|
|
291
338
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
339
|
+
def to_codepoints(sample: tuple[str, ...]) -> set[int] | None:
|
|
340
|
+
codepoints = {ord(char) for text in sample for char in text}
|
|
341
|
+
return codepoints or None
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
std_h_w, std_v_w, stem_snap_h, stem_snap_v = get_stems(
|
|
345
|
+
input_file,
|
|
346
|
+
report_all_stems=report_all_stems,
|
|
347
|
+
max_distance=max_distance,
|
|
348
|
+
max_h_stems=max_h_stems,
|
|
349
|
+
max_v_stems=max_v_stems,
|
|
350
|
+
use_samples=use_samples,
|
|
351
|
+
std_h_codepoints=to_codepoints(std_h_sample),
|
|
352
|
+
std_v_codepoints=to_codepoints(std_v_sample),
|
|
353
|
+
snap_h_codepoints=to_codepoints(snap_h_sample),
|
|
354
|
+
snap_v_codepoints=to_codepoints(snap_v_sample),
|
|
355
|
+
)
|
|
356
|
+
finally:
|
|
357
|
+
if temp_file is not None:
|
|
358
|
+
temp_file.unlink(missing_ok=True)
|
|
359
|
+
|
|
303
360
|
logger.info(f"StdHW: {current_std_h_w} -> {std_h_w}")
|
|
304
361
|
logger.info(f"StdVW: {current_std_v_w} -> {std_v_w}")
|
|
305
362
|
logger.info(f"StemSnapH: {current_stem_snap_h} -> {stem_snap_h}")
|
|
306
363
|
logger.info(f"StemSnapV: {current_stem_snap_v} -> {stem_snap_v}")
|
|
307
|
-
temp_file.unlink(missing_ok=True)
|
|
308
364
|
|
|
309
365
|
if (current_std_h_w, current_std_v_w, current_stem_snap_h, current_stem_snap_v) == (
|
|
310
366
|
std_h_w,
|
|
@@ -321,7 +377,6 @@ def recalc_stems(input_path: Path, **options: dict[str, Any]) -> None:
|
|
|
321
377
|
"StemSnapV": stem_snap_v,
|
|
322
378
|
}
|
|
323
379
|
font.t_cff_.set_hinting_data(**hinting_data)
|
|
324
|
-
font.ttfont.flavor = flavor
|
|
325
380
|
return True
|
|
326
381
|
|
|
327
382
|
runner = TaskRunner(input_path=input_path, task=task, **options)
|
|
@@ -50,14 +50,20 @@ def _determine_output_directory(
|
|
|
50
50
|
return sanitize_filepath(out_dir, platform="auto")
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def _remove_empty_directories(directory: Path) -> None:
|
|
53
|
+
def _remove_empty_directories(directory: Path, stop_at: Path | None = None) -> None:
|
|
54
54
|
"""
|
|
55
55
|
Removes empty directories recursively.
|
|
56
56
|
|
|
57
57
|
Args:
|
|
58
58
|
directory (Path): The directory to start removing from.
|
|
59
|
+
stop_at (Path): The directory to stop at (never removed). Defaults to None, which
|
|
60
|
+
means no boundary. Callers should pass the input directory so cleanup never
|
|
61
|
+
climbs above it.
|
|
59
62
|
"""
|
|
60
|
-
|
|
63
|
+
stop = stop_at.resolve() if stop_at is not None else None
|
|
64
|
+
while directory.is_dir() and (stop is None or directory.resolve() != stop):
|
|
65
|
+
if any(directory.iterdir()):
|
|
66
|
+
break
|
|
61
67
|
directory.rmdir()
|
|
62
68
|
logger.opt(colors=True).success(f"{directory} <magenta>--></> <red>Deleted</>")
|
|
63
69
|
directory = directory.parent
|
|
@@ -115,4 +121,5 @@ def main(
|
|
|
115
121
|
)
|
|
116
122
|
|
|
117
123
|
if delete_empty_directories:
|
|
118
|
-
|
|
124
|
+
stop_at = in_path if in_path.is_dir() else in_path.parent
|
|
125
|
+
_remove_empty_directories(font.file.parent, stop_at=stop_at)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foundrytools-cli
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
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>
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
19
19
|
Classifier: Topic :: Text Processing :: Fonts
|
|
20
20
|
Requires-Dist: afdko==4.0.3
|
|
21
21
|
Requires-Dist: click>=8.5.0
|
|
22
|
-
Requires-Dist: foundrytools>=0.1.
|
|
22
|
+
Requires-Dist: foundrytools>=0.1.8
|
|
23
23
|
Requires-Dist: loguru>=0.7.3
|
|
24
24
|
Requires-Dist: pathvalidate>=3.3.1
|
|
25
25
|
Requires-Dist: rich>=15.0.0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
foundrytools_cli/__init__.py,sha256=
|
|
1
|
+
foundrytools_cli/__init__.py,sha256=kFf6kATo3E1f6TMp1TkglYgUcEhMpNJgM65FbPdfLXM,55
|
|
2
2
|
foundrytools_cli/__main__.py,sha256=wN2lDr2iQyzfnOP00_P3DyXIxEBbf6K_cuzX5YFH5Fg,1273
|
|
3
3
|
foundrytools_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
foundrytools_cli/commands/cff.py,sha256=6eEZydw0ZyISjYuV0JObmNBI7ijdEvVatKwWc8FM5vw,3386
|
|
@@ -10,12 +10,12 @@ foundrytools_cli/commands/fix/__init__.py,sha256=1kJOW26OAkCYYvYDG2H9sCSqqQUQEtw
|
|
|
10
10
|
foundrytools_cli/commands/fix/cli.py,sha256=bcmppksAZ_vkV5ZyHiCw-B3ukEbul20h5i3A5NfcYhA,17578
|
|
11
11
|
foundrytools_cli/commands/fix/legacy_accents.py,sha256=pUHWxV-a2GZgI8W_K2u-lu2yMQt5YKEpQHUD9FyKl8A,2094
|
|
12
12
|
foundrytools_cli/commands/fix/vertical_metrics.py,sha256=j563oWfe3znOKuEW333Yx60d_14Nt0cGdXg3mkCTIN8,691
|
|
13
|
-
foundrytools_cli/commands/font.py,sha256=
|
|
13
|
+
foundrytools_cli/commands/font.py,sha256=WIxz2qqcLloXWxiuvBMp1lAnXyftQ3lLJSKM_3Xhc08,9490
|
|
14
14
|
foundrytools_cli/commands/gsub.py,sha256=iaOAy4jqW6p_RIBxvixtwaEV5AQivaezy0k_Mcufoqg,1454
|
|
15
15
|
foundrytools_cli/commands/hhea.py,sha256=wRld1Cj2q6RnLspMgj_1Z62pIw6Vhqgrm1RO06_qwwc,3158
|
|
16
16
|
foundrytools_cli/commands/name.py,sha256=2EizKjc3puytua2BVnuCcRuP6NzuwNZpZryPZ8uNL-o,10354
|
|
17
17
|
foundrytools_cli/commands/os_2.py,sha256=htdQgD6QLM4v_opVhDtFf9Qhyrz7_gKToQ-xiMHCTCk,19495
|
|
18
|
-
foundrytools_cli/commands/otf.py,sha256=
|
|
18
|
+
foundrytools_cli/commands/otf.py,sha256=cb8lfxXooGs7nXOx2i33MfFYYgU8nVgTH7A20Q3doCM,12543
|
|
19
19
|
foundrytools_cli/commands/post.py,sha256=HpcQD_FKSw8Pr8a6gs62Ln_X9TtqODWHSKNZBmmMT40,2016
|
|
20
20
|
foundrytools_cli/commands/print/__init__.py,sha256=B1h44RtXGw3V4X5se75oCvQPWkh0RbQ0luHxK6z4ge8,71
|
|
21
21
|
foundrytools_cli/commands/print/cli.py,sha256=34CaQIby0u9tZ2vrsKFmvSVg5WH6eEgP5p9FZRoVDEU,1910
|
|
@@ -25,14 +25,14 @@ foundrytools_cli/commands/print/vf_instances.py,sha256=vpp3sMyCcNh9f6CBbOn5lnWr4
|
|
|
25
25
|
foundrytools_cli/commands/ttf.py,sha256=0ZJc36MM8m12_reMVD6GS5ZpSt84g_9RpXrKz_zqqfI,2151
|
|
26
26
|
foundrytools_cli/commands/utils/__init__.py,sha256=bF8Ue4I4QAaDplSpDSMvtb4AZMWjcYsTNIL5ZBp6-t8,71
|
|
27
27
|
foundrytools_cli/commands/utils/cli.py,sha256=OKX4FEzcDP9DZHyAk0tN2vuFoQ__BtmvIfP34hbsEYE,3685
|
|
28
|
-
foundrytools_cli/commands/utils/font_organizer.py,sha256=
|
|
28
|
+
foundrytools_cli/commands/utils/font_organizer.py,sha256=UL2y7Rauax3S3PxMX6CX1lV0Uk-EA8kKjhW2C7s_MpI,4576
|
|
29
29
|
foundrytools_cli/commands/utils/font_renamer.py,sha256=eEWT_1CCXu0mRXTOx5okUQ1NJrqs2RiG-TsRhbAMoRw,5755
|
|
30
30
|
foundrytools_cli/commands/utils/sync_timestamps.py,sha256=h9BMafxlmFllXKyrqBwibQIupS_d2Rp0TXEIfjk7UZQ,3989
|
|
31
31
|
foundrytools_cli/utils/__init__.py,sha256=m-jBbONfS-_WMbpql8GV_1KufNoWOLQuNQtqErH-WmQ,7962
|
|
32
32
|
foundrytools_cli/utils/logger.py,sha256=ZjVgZ1AmzwwH1GEnBU1HP-pFvZv0BKkVV93P2gYvnLc,525
|
|
33
33
|
foundrytools_cli/utils/task_runner.py,sha256=CXVItMJTslNuie_rhPpZrQBX5rgkWbAWX805dFIBInA,6965
|
|
34
34
|
foundrytools_cli/utils/timer.py,sha256=i_E1nM4KuooZwT2_2bkNJCxLrSKBaQCIMaHxTHY-Hzs,1870
|
|
35
|
-
foundrytools_cli-2.2.
|
|
36
|
-
foundrytools_cli-2.2.
|
|
37
|
-
foundrytools_cli-2.2.
|
|
38
|
-
foundrytools_cli-2.2.
|
|
35
|
+
foundrytools_cli-2.2.2.dist-info/WHEEL,sha256=MWx7psoF7KNST9G72pGtiBYuaXRXQXBk9pw5R6FzYko,80
|
|
36
|
+
foundrytools_cli-2.2.2.dist-info/entry_points.txt,sha256=sNn6z8yZnx3Koh85LS6BYDqzaXZSP4KEvN3AdFGYRao,57
|
|
37
|
+
foundrytools_cli-2.2.2.dist-info/METADATA,sha256=6tU5bWSXQHGA5ZfwJSJF06pr0_LYIkif7VKaxCkSLGs,3777
|
|
38
|
+
foundrytools_cli-2.2.2.dist-info/RECORD,,
|
|
File without changes
|