11import argparse
2+ from typing import Sequence , Tuple
23
34import pathops # type: ignore
45from fontTools .ttLib import TTFont # type: ignore
6+ from fontTools .ttLib .tables ._g_l_y_f import Glyph # type: ignore
57
68from .bridge import ttfont_glyph_to_skia_path
79from .stringbuilder import direction_result
810from .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
1415def 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
0 commit comments