Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Keep track of on-curve points in the original path #47

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/python/pathops/_pathops.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ cdef class Path:

cdef SkPath path

cdef set originalOnCurvePoints

@staticmethod
cdef Path create(const SkPath& path)

Expand Down Expand Up @@ -224,6 +226,7 @@ cdef class RawPathIterator:

cdef class SegmentPenIterator:

cdef set originalOnCurvePoints
cdef _SkPointArray pa
cdef SkPoint *pts
cdef _VerbArray va
Expand Down
21 changes: 19 additions & 2 deletions src/python/pathops/_pathops.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ cdef class Path:

def __init__(self, other=None, fillType=None):
cdef Path static_path
self.originalOnCurvePoints = set()
if other is not None:
if isinstance(other, Path):
static_path = other
Expand Down Expand Up @@ -254,7 +255,7 @@ cdef class Path:
print(self._to_string(as_hex=as_hex)) # Python

def _to_string(self, as_hex=False):
# return a text repesentation as Python code
# return a text representation as Python code
if self.path.isEmpty():
return ""
if as_hex:
Expand Down Expand Up @@ -669,6 +670,12 @@ cdef class Path:
)
cdef Path result = Path.__new__(Path)
self.path.transform(matrix, &result.path)
# TODO: figure out how to transform the original oncurve points using the matrix
# result.originalOnCurvePoints = set(
# transform_tuple_point(matrix, pt)
# for pt in self.originalOnCurvePoints
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SkMatrix has mapPoint and mapPoints (for array of SkPoints) methods:
https://api.skia.org/classSkMatrix.html#a545fc5d678f0c07c40636bc4cb699017

# )
result.originalOnCurvePoints = self.originalOnCurvePoints # FIXME: DUMMY just to make test pass
return result


Expand Down Expand Up @@ -753,6 +760,7 @@ cdef tuple CLOSE_PATH = ("closePath", NO_POINTS)
cdef class SegmentPenIterator:

def __cinit__(self, Path path):
self.originalOnCurvePoints = path.originalOnCurvePoints
self.pa = _SkPointArray.create(path.path)
self.pts = self.pa.data
self.va = _VerbArray.create(path.path)
Expand Down Expand Up @@ -850,7 +858,11 @@ cdef class SegmentPenIterator:
next_verb_ptr = verbs + 1
if next_verb_ptr != self.verb_stop:
if next_verb_ptr[0] == kQuad_Verb:
if is_middle_point(pts[0], pts[1], pts[2]):
if (
is_middle_point(pts[0], pts[1], pts[2])
# Don't delete on-curve points that were present in the original curve
and (pts[1].x(), pts[1].y()) not in self.originalOnCurvePoints
):
# skip TrueType "implied" on-curve point, and keep
# evaluating the next quadratic segment
verbs = next_verb_ptr
Expand Down Expand Up @@ -887,20 +899,24 @@ cdef class PathPen:

cpdef moveTo(self, pt):
self.path.moveTo(pt[0], pt[1])
self.path.originalOnCurvePoints.add(pt)

cpdef lineTo(self, pt):
self.path.lineTo(pt[0], pt[1])
self.path.originalOnCurvePoints.add(pt)

cpdef curveTo(self, pt1, pt2, pt3):
# support BasePen "super-beziers"? Nah.
self.path.cubicTo(
pt1[0], pt1[1],
pt2[0], pt2[1],
pt3[0], pt3[1])
self.path.originalOnCurvePoints.add(pt3)

def qCurveTo(self, *points):
for pt1, pt2 in _decompose_quadratic_segment(points):
self._qCurveToOne(pt1, pt2)
self.path.originalOnCurvePoints.add(points[-1])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize now that I could probably only keep track of the original on-curves at the end of qCurves, instead of all types of original on-curves?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, if we have to remember oncurve points, it's probably better to only do it for when we actually need it, i.e. for qCurve segments only where they can be "implied", the other segment types in pen protocol are all explicit


cdef _qCurveToOne(self, pt1, pt2):
self.path.quadTo(pt1[0], pt1[1], pt2[0], pt2[1])
Expand All @@ -922,6 +938,7 @@ cdef class PathPen:
cdef Path component_path = base_path.transform(*transformation)

self.path.addPath(component_path)
self.path.originalOnCurvePoints.update(component_path.originalOnCurvePoints)


cdef double get_path_area(const SkPath& path) except? -1234567:
Expand Down
24 changes: 24 additions & 0 deletions tests/pathops_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,30 @@ def test_decompose_join_quadratic_segments(self):
('qCurveTo', ((1.0, 1.0), (2.0, 2.0), (3.0, 3.0))),
('closePath', ())]

def test_decompose_join_quadratic_segments_preserve_original_oncurves(self):
path = Path()
pen = path.getPen()
pen.moveTo((0, 0))
pen.qCurveTo((1, 1), (1.5, 1.5)) # This time the on-curve is explicit
pen.qCurveTo((2, 2), (3, 3))
pen.closePath()

items = list(path)
assert len(items) == 4
# the TrueType quadratic spline with N off-curves is stored internally
# as N atomic quadratic Bezier segments
assert items[1][0] == PathVerb.QUAD
assert items[1][1] == ((1.0, 1.0), (1.5, 1.5))
assert items[2][0] == PathVerb.QUAD
assert items[2][1] == ((2.0, 2.0), (3.0, 3.0))

# when drawn back onto a SegmentPen, the implicit on-curves are omitted
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# when drawn back onto a SegmentPen, the implicit on-curves are omitted
# when drawn back onto a SegmentPen, the explicit on-curves are preserved

assert list(path.segments) == [
('moveTo', ((0.0, 0.0),)),
('qCurveTo', ((1.0, 1.0), (1.5, 1.5))),
('qCurveTo', ((2.0, 2.0), (3.0, 3.0))),
('closePath', ())]

def test_last_implicit_lineTo(self):
# https://github.com/fonttools/skia-pathops/issues/6
path = Path()
Expand Down