Skip to content

Commit

Permalink
Merge pull request #264 from googlefonts/fix-relative-move-after-empt…
Browse files Browse the repository at this point in the history
…y-subpath

make subpath moveto absolute before removing empty ones
  • Loading branch information
rsheeter authored Feb 1, 2022
2 parents a855a25 + e9f7b81 commit 1f6780a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 2 deletions.
14 changes: 13 additions & 1 deletion src/picosvg/svg_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ def _relative_to_absolute(curr_pos, cmd, args):
)


def _relative_to_absolute_moveto(curr_pos, cmd, args):
if cmd in ("M", "m"):
return _relative_to_absolute(curr_pos, cmd, args)
return (cmd, args)


def _absolute_to_relative(curr_pos, cmd, args):
return _rewrite_coords(
lambda cmd: cmd.lower(), lambda curr_scaler: -curr_scaler, curr_pos, cmd, args
Expand Down Expand Up @@ -516,7 +522,9 @@ def subpaths_callback(subpath_start, curr_pos, cmd, args, *_unused):
subpaths.append(SVGPath())
return ((cmd, args),) # unmodified

self.walk(subpaths_callback)
# make all moveto absolute so each subpath is independent from the
# precending ones
self.absolute_moveto().walk(subpaths_callback)

return tuple(s.d for s in subpaths if s.d)

Expand Down Expand Up @@ -578,6 +586,10 @@ def absolute(self, inplace=False) -> "SVGPath":
"""Returns equivalent path with only absolute commands."""
return self._rewrite_path(_relative_to_absolute, inplace)

def absolute_moveto(self, inplace=False) -> "SVGPath":
"""Returns equivalent path with absolute moveto commands."""
return self._rewrite_path(_relative_to_absolute_moveto, inplace)

def relative(self, inplace=False) -> "SVGPath":
"""Returns equivalent path with only relative commands."""
return self._rewrite_path(_absolute_to_relative, inplace)
Expand Down
34 changes: 33 additions & 1 deletion tests/svg_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ def test_path_absolute(path: str, expected_result: str):
assert actual == expected_result


@pytest.mark.parametrize(
"path, expected_result",
[
(
"m0,0 l1,1 l0,-1 m1,2 l1,1 l0,-1 z M3,3 l1,1 l0,-1 Z",
"M0,0 l1,1 l0,-1 M2,2 l1,1 l0,-1 z M3,3 l1,1 l0,-1 Z",
),
],
)
def test_path_absolute_moveto(path: str, expected_result: str):
actual = (
SVGPath(d=path).absolute_moveto(inplace=True).round_floats(3, inplace=True).d
)
print(f"A: {actual}")
print(f"E: {expected_result}")
assert actual == expected_result


@pytest.mark.parametrize(
"path, move, expected_result",
[
Expand Down Expand Up @@ -475,10 +493,24 @@ def test_gradient_from_element(el, view_box, expected):
("M1,2", ""),
("M1,2 z", ""),
("m1,1 2,2 1,3 z M465,550 Z M1,2", "M1,1 l2,2 l1,3 z"),
("m1,1 2,2 1,3 z M465,550 Z M1,2", "M1,1 l2,2 l1,3 z"),
# the following path (from noto-emoji "foot" emoji_u1f9b6.svg) contains a
# subpath starting with relative 'm' that follows an empty subpath. Check
# the latter is removed and the subsequent 'M' is correctly absolutized.
# https://github.com/googlefonts/picosvg/issues/252#issuecomment-1026839746
(
"M0,0 c0.59,-0.63 1.39,-0.97 2.25,-0.97 m103.99,90.92 m-103.99,-93.92 c-3.52,0 -6.3,2.97 -6.07,6.48",
"M0,0 c0.59,-0.63 1.39,-0.97 2.25,-0.97 M2.25,-3.97 c-3.52,0 -6.3,2.97 -6.07,6.48",
),
],
)
def test_remove_empty_subpaths(path: str, expected_result: str):
actual = SVGPath(d=path).remove_empty_subpaths(inplace=True).d
actual = (
SVGPath(d=path)
.remove_empty_subpaths(inplace=True)
.round_floats(3, inplace=True)
.d
)
print(f"A: {actual}")
print(f"E: {expected_result}")
assert actual == expected_result

0 comments on commit 1f6780a

Please sign in to comment.