Skip to content

Commit

Permalink
draw_svg_path: auto-close open sub-paths when drawing svg paths with …
Browse files Browse the repository at this point in the history
…fonttools pen

The draw_svg_path function which we use to convert SVG paths to FontTools pen commands to create UFO glyphs from them, uses the presence/absence of 'z' to decide whether to call a pen's endPath (for open contours) and closePath (for closed ones). If a filled subpath does not explicily ends with 'z' (which is the case for some of our source files, e.g. 'one-o-clock.svg'), then upon converting it to UFO glyph using the FontTools pen protocol, the subpath is kept 'open' (i.e. the UFO GLIF contour's first point type is set to 'move').
Then, when the UFO glyph is converted to TrueType by ufo2ft with the TTGlyphPointPen, if the starting 'move' point and the last point of such 'open' contours are on top of each other (i.e. contour does a full loop despite being marked as 'open'), the result is a duplicate point at the end/start of the TrueType contour.
We fix this by adding an optional `close_subpath=False` parameter to draw_svg_path function, which when True signals that we want to interpret the SVG subpaths as if they were closed by an explicit 'z' command, which avoids the duplicate end/start points in the converted TrueType glyphs.
TrueType contours are always filled and automatically closed by an implicit line back to their staring point.
  • Loading branch information
anthrotype committed Jul 19, 2021
1 parent 53ba29c commit 87dcae5
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 6 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"pillow>=7.2.0",
"regex>=2020.4.4",
"toml>=0.10.1",
"ufo2ft[cffsubr]==2.21.0", # TEMPORARY VERSION LOCK
"ufo2ft[cffsubr]>=2.23.0",
"ufoLib2>=0.6.2",
],
extras_require=extras_require,
Expand Down
15 changes: 12 additions & 3 deletions src/nanoemoji/svg_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@


def draw_svg_path(
path: SVGPath, pen: AbstractPen, transform: Optional[Affine2D] = None
path: SVGPath,
pen: AbstractPen,
transform: Optional[Affine2D] = None,
close_subpaths: bool = False,
):
"""Draw SVGPath using a FontTools Segment Pen."""
if transform is not None:
Expand All @@ -44,7 +47,10 @@ def draw_svg_path(
for cmd, args in path.as_cmd_seq():
if cmd == "M":
if not closed:
pen.endPath()
if close_subpaths:
pen.closePath()
else:
pen.endPath()
closed = False

# pens expect args as 2-tuples; we use the 'grouper' itertools recipe
Expand All @@ -58,7 +64,10 @@ def draw_svg_path(
closed = True

if not closed:
pen.endPath()
if close_subpaths:
pen.closePath()
else:
pen.endPath()


class SVGPathPen(DecomposingPen):
Expand Down
10 changes: 8 additions & 2 deletions src/nanoemoji/write_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ def _create_glyph(color_glyph: ColorGlyph, painted_layer: PaintedLayer) -> Glyph
glyph_names.append(base_glyph.name)

draw_svg_path(
SVGPath(d=painted_layer.path), base_glyph.getPen(), svg_units_to_font_units
SVGPath(d=painted_layer.path),
base_glyph.getPen(),
svg_units_to_font_units,
close_subpaths=True,
)

glyph.components.append(
Expand All @@ -268,7 +271,10 @@ def _create_glyph(color_glyph: ColorGlyph, painted_layer: PaintedLayer) -> Glyph
else:
# Not a composite, just draw directly on the glyph
draw_svg_path(
SVGPath(d=painted_layer.path), glyph.getPen(), svg_units_to_font_units
SVGPath(d=painted_layer.path),
glyph.getPen(),
svg_units_to_font_units,
close_subpaths=True,
)

ufo.glyphOrder += glyph_names
Expand Down
7 changes: 7 additions & 0 deletions tests/svg_path_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,10 @@ def test_roundtrip_path_with_pen(d):
pen = SVGPathPen()
draw_svg_path(path, pen)
assert pen.path.d == d


def test_draw_svg_close_subpaths():
path = SVGPath(d="M0,0 L0,10 L10,10 L10,0 M12,0 L12,10 L22,10 L22,0")
pen = SVGPathPen()
draw_svg_path(path, pen, close_subpaths=True)
assert pen.path.d == "M0,0 L0,10 L10,10 L10,0 Z M12,0 L12,10 L22,10 L22,0 Z"

0 comments on commit 87dcae5

Please sign in to comment.