Skip to content

Commit

Permalink
Merge pull request #31 from fonttools/clockwise-option
Browse files Browse the repository at this point in the history
add 'clockwise' boolean option to control outermost contour direction
  • Loading branch information
anthrotype authored Sep 27, 2020
2 parents 0efea4f + cbc63dd commit 088c5e8
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 25 deletions.
22 changes: 17 additions & 5 deletions src/python/pathops/_pathops.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ cdef class Path:

cpdef reverse(self)

cpdef simplify(self, bint fix_winding=*, keep_starting_points=*)
cpdef simplify(
self,
bint fix_winding=*,
bint keep_starting_points=*,
bint clockwise=*,
)

cpdef convertConicsToQuads(self, float tolerance=*)

Expand Down Expand Up @@ -279,7 +284,7 @@ cdef int path_is_inside(const SkPath& self, const SkPath& other) except -1
cpdef int restore_starting_points(Path path, list points) except -1


cpdef bint winding_from_even_odd(Path path, bint truetype=*) except False
cpdef bint winding_from_even_odd(Path path, bint clockwise=*) except False


cdef list _decompose_quadratic_segment(tuple points)
Expand Down Expand Up @@ -307,12 +312,18 @@ cpdef Path op(
Path one,
Path two,
SkPathOp operator,
fix_winding=*,
keep_starting_points=*,
bint fix_winding=*,
bint keep_starting_points=*,
bint clockwise=*,
)


cpdef Path simplify(Path path, fix_winding=*, keep_starting_points=*)
cpdef Path simplify(
Path path,
bint fix_winding=*,
bint keep_starting_points=*,
bint clockwise=*,
)


cdef class OpBuilder:
Expand All @@ -321,6 +332,7 @@ cdef class OpBuilder:
cdef bint fix_winding
cdef bint keep_starting_points
cdef list first_points
cdef bint clockwise

cpdef add(self, Path path, SkPathOp operator)

Expand Down
51 changes: 34 additions & 17 deletions src/python/pathops/_pathops.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,19 @@ cdef class Path:
skpath.addPath(contour.path)
self.path = skpath

cpdef simplify(self, bint fix_winding=True, keep_starting_points=True):
cpdef simplify(
self,
bint fix_winding=True,
bint keep_starting_points=True,
bint clockwise=False,
):
cdef list first_points
if keep_starting_points:
first_points = self.firstPoints
if not Simplify(self.path, &self.path):
raise PathOpsError("simplify operation did not succeed")
if fix_winding:
winding_from_even_odd(self)
winding_from_even_odd(self, clockwise)
if keep_starting_points:
restore_starting_points(self, first_points)

Expand Down Expand Up @@ -626,10 +631,10 @@ cdef class Path:
>>> affine = (2, 0, 0, 2, 0, 0)
>>> p2 = p1.transform(*affine)
>>> list(p2.segments) == [
('moveTo', ((2.0, 4.0),)),
('lineTo', ((6.0, 8.0),)),
('endPath', ()),
]
... ('moveTo', ((2.0, 4.0),)),
... ('lineTo', ((6.0, 8.0),)),
... ('endPath', ()),
... ]
True
"""
cdef SkMatrix matrix = SkMatrix.MakeAll(
Expand Down Expand Up @@ -1116,23 +1121,23 @@ DEF DEBUG_WINDING = False

@cython.wraparound(False)
@cython.boundscheck(False)
cpdef bint winding_from_even_odd(Path path, bint truetype=False) except False:
cpdef bint winding_from_even_odd(Path path, bint clockwise=False) except False:
""" Take a simplified path (without overlaps) and set the contours
directions according to the non-zero winding fill type.
The outermost contours are set to counter-clockwise direction, unless
'truetype' is True.
'clockwise' is True.
"""
# TODO re-enable this once the new feature is stabilized in upstream skia
# https://github.com/fonttools/skia-pathops/issues/10
# if AsWinding(path.path, &path.path):
# if path.clockwise ^ truetype:
# if path.clockwise ^ clockwise:
# path.reverse()
# return True
#
# # in the unlikely event the built-in method fails, try our naive approach

cdef int i, j
cdef bint inverse = not truetype
cdef bint inverse = not clockwise
cdef bint is_clockwise, is_even
cdef Path contour, other

Expand Down Expand Up @@ -1374,8 +1379,9 @@ cpdef Path op(
Path one,
Path two,
SkPathOp operator,
fix_winding=True,
keep_starting_points=True
bint fix_winding=True,
bint keep_starting_points=True,
bint clockwise=False,
):
cdef list first_points
if keep_starting_points:
Expand All @@ -1384,32 +1390,43 @@ cpdef Path op(
if not Op(one.path, two.path, operator, &result.path):
raise PathOpsError("operation did not succeed")
if fix_winding:
winding_from_even_odd(result)
winding_from_even_odd(result, clockwise)
if keep_starting_points:
restore_starting_points(result, first_points)
return result


cpdef Path simplify(Path path, fix_winding=True, keep_starting_points=True):
cpdef Path simplify(
Path path,
bint fix_winding=True,
bint keep_starting_points=True,
bint clockwise=False,
):
cdef list first_points
if keep_starting_points:
first_points = path.firstPoints
cdef Path result = Path()
if not Simplify(path.path, &result.path):
raise PathOpsError("operation did not succeed")
if fix_winding:
winding_from_even_odd(result)
winding_from_even_odd(result, clockwise)
if keep_starting_points:
restore_starting_points(result, first_points)
return result


cdef class OpBuilder:

def __init__(self, bint fix_winding=True, keep_starting_points=True):
def __init__(
self,
bint fix_winding=True,
bint keep_starting_points=True,
bint clockwise=False,
):
self.fix_winding = fix_winding
self.keep_starting_points = keep_starting_points
self.first_points = []
self.clockwise = clockwise

cpdef add(self, Path path, SkPathOp operator):
self.builder.add(path.path, operator)
Expand All @@ -1421,7 +1438,7 @@ cdef class OpBuilder:
if not self.builder.resolve(&result.path):
raise PathOpsError("operation did not succeed")
if self.fix_winding:
winding_from_even_odd(result)
winding_from_even_odd(result, self.clockwise)
if self.keep_starting_points:
restore_starting_points(result, self.first_points)
return result
Expand Down
21 changes: 18 additions & 3 deletions src/python/pathops/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ def _draw(contours):
return path


def union(contours, outpen, fix_winding=True, keep_starting_points=True):
def union(
contours,
outpen,
fix_winding=True,
keep_starting_points=True,
clockwise=False,
):
if not contours:
return
path = _draw(contours)
path.simplify(
fix_winding=fix_winding,
keep_starting_points=keep_starting_points
keep_starting_points=keep_starting_points,
clockwise=clockwise,
)
path.draw(outpen)

Expand All @@ -37,10 +44,18 @@ def _do(
outpen,
fix_winding=True,
keep_starting_points=True,
clockwise=False,
):
one = _draw(subject_contours)
two = _draw(clip_contours)
result = op(one, two, operator, fix_winding, keep_starting_points)
result = op(
one,
two,
operator,
fix_winding=fix_winding,
keep_starting_points=keep_starting_points,
clockwise=clockwise,
)
result.draw(outpen)


Expand Down
60 changes: 60 additions & 0 deletions tests/pathops_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
float2bits,
ArcSize,
Direction,
simplify,
)

import pytest
Expand Down Expand Up @@ -790,3 +791,62 @@ def test_path_operation(message, operations, expected):
round_pts.append(tuple(round(c, 2) for c in pt))
rounded.append((verb, tuple(round_pts)))
assert tuple(rounded) == expected, message



@pytest.fixture
def overlapping_path():
path = Path()
path.moveTo(0, 0)
path.lineTo(10, 0)
path.lineTo(10, 10)
path.lineTo(0, 10)
path.close()
path.moveTo(5, 5)
path.lineTo(15, 5)
path.lineTo(15, 15)
path.lineTo(5, 15)
path.close()
return path


def test_simplify(overlapping_path):
result = simplify(overlapping_path)

assert overlapping_path != result
assert list(result) == [
(PathVerb.MOVE, ((0, 0),)),
(PathVerb.LINE, ((10, 0),)),
(PathVerb.LINE, ((10, 5),)),
(PathVerb.LINE, ((15, 5),)),
(PathVerb.LINE, ((15, 15),)),
(PathVerb.LINE, ((5, 15),)),
(PathVerb.LINE, ((5, 10),)),
(PathVerb.LINE, ((0, 10),)),
(PathVerb.CLOSE, ()),
]

overlapping_path.simplify()

assert overlapping_path == result


def test_simplify_clockwise(overlapping_path):
result = simplify(overlapping_path, clockwise=True)

assert overlapping_path != result
assert list(result) == [
(PathVerb.MOVE, ((0, 0),)),
(PathVerb.LINE, ((0, 10),)),
(PathVerb.LINE, ((5, 10),)),
(PathVerb.LINE, ((5, 15),)),
(PathVerb.LINE, ((15, 15),)),
(PathVerb.LINE, ((15, 5),)),
(PathVerb.LINE, ((10, 5),)),
(PathVerb.LINE, ((10, 0),)),
(PathVerb.CLOSE, ()),
]

overlapping_path.simplify(clockwise=True)

assert overlapping_path == result

0 comments on commit 088c5e8

Please sign in to comment.