1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
"""
UI-constrained point management methods.
"""
from PyQt5.QtCore import QLineF, QPointF
def _getOffCurveSiblingPoints(contour, point):
index = contour.index(point)
for d in (-1, 1):
sibling = contour.getPoint(index + d)
if sibling.segmentType is not None:
sSibling = contour.getPoint(index + 2 * d)
return sibling, sSibling
raise IndexError
def moveUIPoint(contour, point, delta):
if point.segmentType is None:
# point is an offCurve. Get its sibling onCurve and the other
# offCurve.
onCurve, otherPoint = _getOffCurveSiblingPoints(contour, point)
# if the onCurve is selected, the offCurve will move along with it
if onCurve.selected:
return
point.move(delta)
if not onCurve.smooth:
contour.dirty = True
return
# if the onCurve is smooth, we need to either...
if otherPoint.segmentType is None and not otherPoint.selected:
# keep the other offCurve inline
line = QLineF(point.x, point.y, onCurve.x, onCurve.y)
otherLine = QLineF(
onCurve.x, onCurve.y, otherPoint.x, otherPoint.y)
line.setLength(line.length() + otherLine.length())
otherPoint.x = line.x2()
otherPoint.y = line.y2()
else:
# keep point in tangency with onCurve -> otherPoint segment,
# ie. do an orthogonal projection
line = QLineF(otherPoint.x, otherPoint.y, onCurve.x, onCurve.y)
n = line.normalVector()
n.translate(QPointF(point.x, point.y) - n.p1())
targetPoint = QPointF()
n.intersect(line, targetPoint)
# check that targetPoint is beyond its neighbor onCurve
# we do this by calculating position of the offCurve and second
# onCurve relative to the first onCurve. If there is no symmetry
# in at least one of the axis, then we need to clamp
onCurvePoint = line.p2()
onDistance = line.p1() - onCurvePoint
newDistance = targetPoint - onCurvePoint
if (onDistance.x() >= 0) != (newDistance.x() <= 0) or \
(onDistance.y() >= 0) != (newDistance.y() <= 0):
targetPoint = onCurvePoint
# ok, now set pos
point.x, point.y = targetPoint.x(), targetPoint.y()
else:
# point is an onCurve. Move its offCurves along with it.
index = contour.index(point)
point.move(delta)
for d in (-1, 1):
# edge-case: contour open, trailing offCurve and moving first
# onCurve in contour
if contour.open and index == 0 and d == -1:
continue
pt = contour.getPoint(index + d)
if pt.segmentType is None:
pt.move(delta)
contour.dirty = True
def moveUISelection(contour, delta):
for point in contour.selection:
moveUIPoint(contour, point, delta)
def removeUISelection(contour, preserveShape=True):
segments = contour.segments
# the last segments contains the first point, make sure to process it last
# so as to not offset indexes
toFirstPoint = segments[-1]
toIter = list(enumerate(segments))
toIter.insert(0, toIter.pop())
# moonwalk through segments
for index, segment in reversed(toIter):
if segment == toFirstPoint:
index = len(segments) - 1
onCurve = segment[-1]
# if the onCurve is selected, wipe it
if onCurve.selected:
# remove the contour if we have exhausted segments
if len(segments) < 2:
glyph = contour.glyph
glyph.removeContour(contour)
return
# using preserveShape at the edge of an open contour will traceback
if preserveShape and contour.open:
if index in (0, len(segments) - 1):
preserveShape = False
contour.removeSegment(index, preserveShape)
# remove segment so we can keep track of how many remain
del segments[index]
elif len(segment) == 2:
# move with trailing offCurve
offCurve = segment[0]
if offCurve.selected:
assert(offCurve.segmentType is None)
contour.removePoint(offCurve)
elif len(segment) == 3:
# if offCurve selected, wipe them
for i in (0, 1):
if segment[i].selected:
contour.removePoint(segment[0])
contour.removePoint(segment[1])
segment[2].segmentType = "line"
break
|