Skip to content

Commit c497ed7

Browse files
committed
initial implementation of direction sub-command
1 parent 99bb9d0 commit c497ed7

File tree

2 files changed

+94
-8
lines changed

2 files changed

+94
-8
lines changed

lib/pathins/direction.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
import argparse
2+
from typing import Sequence, Tuple
23

34
import pathops # type: ignore
45
from fontTools.ttLib import TTFont # type: ignore
6+
from fontTools.ttLib.tables._g_l_y_f import Glyph # type: ignore
57

68
from .bridge import ttfont_glyph_to_skia_path
79
from .stringbuilder import direction_result
810
from .validators import validate_fontpath, validate_glyph_in_font
911

10-
# TODO: add --check support to confirm all paths in same direction
1112
# TODO: add --summary to include total CW and CCW directions
1213

1314

1415
def direction_run(args: argparse.Namespace) -> None:
1516
"""
1617
Displays the direction of the outermost contour of one
1718
or more glyphs in a font. Results are expressed as either
18-
"clockwise" or "counter-clockwise".
19+
"clockwise" or "counter-clockwise". The report includes
20+
the x, y scaling factors for transfomed components of
21+
composite glyphs. This scaling *may* reverse the path
22+
direction that is reported for the decomposed outline.
1923
"""
2024
fontpath: str = args.fontpath
2125
glyphname: str = args.glyphname
@@ -32,23 +36,68 @@ def direction_run(args: argparse.Namespace) -> None:
3236
if glyphname:
3337
validate_glyph_in_font(glyphname, tt)
3438
skia_path = ttfont_glyph_to_skia_path(glyphname, tt)
39+
40+
# transformed components can change path direction
41+
# in the decomposed paths
42+
# (e.g. 180 degree Y-axis rotation = mirroring)
43+
# add base component glyph name and transform values
44+
# to the report if this is present
45+
glyph = tt["glyf"][glyphname]
46+
components_with_transforms: Sequence[Tuple] = []
47+
if glyph.isComposite():
48+
components_with_transforms = _get_components_with_transforms(glyph)
49+
3550
print(
3651
direction_result(
3752
glyphname,
3853
skia_path.clockwise,
3954
len(list(skia_path.contours)),
55+
components_with_transforms=components_with_transforms,
4056
nocolor=args.nocolor,
4157
)
4258
)
4359
else:
4460
glyph_names = tt.getGlyphOrder()
4561
for local_glyphname in glyph_names:
62+
glyph = tt["glyf"][local_glyphname]
63+
components_with_transforms = []
64+
65+
# transformed components can change path direction
66+
# in the decomposed paths
67+
# (e.g. 180 degree Y-axis rotation = mirroring)
68+
# add base component glyph name and transform values
69+
# to the report if this is present
70+
if glyph.isComposite():
71+
components_with_transforms = _get_components_with_transforms(glyph)
72+
4673
skia_path = ttfont_glyph_to_skia_path(local_glyphname, tt) # type: ignore
74+
4775
print(
4876
direction_result(
4977
str(local_glyphname),
5078
skia_path.clockwise,
5179
len(list(skia_path.contours)),
80+
components_with_transforms=components_with_transforms,
5281
nocolor=args.nocolor,
5382
)
5483
)
84+
85+
86+
def _get_components_with_transforms(glyph: Glyph) -> Sequence[Tuple]:
87+
"""
88+
Returns list with component glyph names and x,y transforms
89+
for composite glyphs with transforms. In all other cases,
90+
returns an empty list
91+
"""
92+
components_with_transforms = []
93+
if glyph.isComposite():
94+
for component in glyph.components:
95+
if hasattr(component, "transform"):
96+
a1 = round(component.transform[0][0], 3)
97+
a2 = round(component.transform[0][1], 3)
98+
b1 = round(component.transform[1][0], 3)
99+
b2 = round(component.transform[1][1], 3)
100+
components_with_transforms.append(
101+
(component.glyphName, [[a1, a2], [b1, b2]])
102+
)
103+
return components_with_transforms

lib/pathins/stringbuilder.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import sys
3-
from typing import Dict, Text
3+
from typing import Dict, Sequence, Text, Tuple
44

55
ansicolors: Dict[Text, Text] = {
66
"BLACK": "\033[30m",
@@ -59,19 +59,56 @@ def overlap_result(glyphname: str, test_pass: bool, nocolor=False) -> str:
5959

6060

6161
def direction_result(
62-
glyphname: str, direction_clockwise: bool, contours: int, nocolor=False
62+
glyphname: str,
63+
direction_clockwise: bool,
64+
contours: int,
65+
components_with_transforms: Sequence[Tuple] = [],
66+
nocolor=False,
6367
) -> str:
6468
if not nocolor and sys.stdout.isatty():
6569
if contours == 0:
6670
return f"[ {light_cyan_start}{glyphname}{reset} ]: no contours"
6771
if direction_clockwise:
68-
return f"[ {light_cyan_start}{glyphname}{reset} ]: clockwise"
72+
return (
73+
f"[ {light_cyan_start}{glyphname}{reset} ]: "
74+
f"clockwise"
75+
f"{_transformed_component(components_with_transforms)}"
76+
)
6977
else:
70-
return f"[ {light_cyan_start}{glyphname}{reset} ]: counter-clockwise"
78+
return (
79+
f"[ {light_cyan_start}{glyphname}{reset} ]: "
80+
f"counter-clockwise"
81+
f"{_transformed_component(components_with_transforms)}"
82+
)
7183
else:
7284
if contours == 0:
7385
return f"[ {glyphname} ]: no contours"
7486
if direction_clockwise:
75-
return f"[ {glyphname} ]: clockwise"
87+
return (
88+
f"[ {glyphname} ]: clockwise"
89+
f"{_transformed_component(components_with_transforms)}"
90+
)
7691
else:
77-
return f"[ {glyphname} ]: counter-clockwise"
92+
return (
93+
f"[ {glyphname} ]: counter-clockwise"
94+
f"{_transformed_component(components_with_transforms)}"
95+
)
96+
97+
98+
def _transformed_component(components_with_transforms: Sequence[Tuple]) -> str:
99+
if len(components_with_transforms) > 0:
100+
left_pad = " " * 10
101+
components_string = f"{os.linesep}"
102+
for x, component in enumerate(components_with_transforms):
103+
component_glyphname = component[0]
104+
component_transform = component[1]
105+
components_string += (
106+
f"{left_pad}with component '{component_glyphname}' transform: "
107+
f"{component_transform}"
108+
)
109+
if x + 1 < len(components_with_transforms):
110+
# add newline unless this is the last component in the list
111+
components_string += f"{os.linesep}"
112+
return components_string
113+
else:
114+
return ""

0 commit comments

Comments
 (0)