Cython is used to compile some hot paths into native Python extensions. These hot paths were identified through running ufocompile with the hotshot profiler and then converting file by file to Cython, starting with the "hottest" paths and continuing until returns were deminishing. This means that only a few Python files were converted to Cython. Closes #23 Closes #20 (really this time)
108 lines
3.1 KiB
Python
108 lines
3.1 KiB
Python
from robofab.pens.filterPen import flattenGlyph
|
|
from robofab.objects.objectsRF import RGlyph as _RGlyph
|
|
import math
|
|
|
|
_EPSILON = 1e-15
|
|
|
|
|
|
def normalise(a1, a2):
|
|
"""Normalise this vector to length 1"""
|
|
n = math.sqrt((a1*a1)+(a2*a2))
|
|
return (a1/n, a2/n)
|
|
|
|
def inbetween((a1, a2), (b1, b2), (c1, c2)):
|
|
"""Return True if point b is in between points a and c."""
|
|
x = (a1-_EPSILON<=b1<=c1+_EPSILON) or (a1+_EPSILON>=b1>=c1-_EPSILON)
|
|
y = (a2-_EPSILON<=b2<=c2+_EPSILON) or (a2+_EPSILON>=b2>=c2-_EPSILON)
|
|
return x == y == True
|
|
|
|
def sectlines((a1, a2), (p1, p2), (b1, b2), (q1, q2)):
|
|
'''Calculate the intersection point of two straight lines. Result in floats.'''
|
|
if (a1, a2) == (p1, p2):
|
|
return None
|
|
r1 = a1-p1
|
|
r2 = a2-p2
|
|
r1, r2 = normalise(r1, r2)
|
|
s1 = b1-q1
|
|
s2 = b2-q2
|
|
s1, s2 = normalise(s1, s2)
|
|
f = float(s1*r2 - s2*r1)
|
|
if f == 0:
|
|
return None
|
|
mu = (r1*(q2 - p2) + r2*(p1 - q1)) / f
|
|
m1 = mu*s1 + q1
|
|
m2 = mu*s2 + q2
|
|
if (m1, m2) == (a1, a2):
|
|
return None
|
|
if inbetween((a1, a2), (m1, m2), (p1,p2)) and inbetween((b1, b2), (m1, m2), (q1,q2)):
|
|
return m1, m2
|
|
else:
|
|
return None
|
|
|
|
def _makeFlat(aGlyph, segmentLength = 10):
|
|
"""Helper function to flatten the glyph with a given approximate segment length."""
|
|
return flattenGlyph(aGlyph, segmentLength)
|
|
|
|
def intersect(aGlyph, startPt, endPt, segmentLength=10):
|
|
"""Find the intersections between a glyph and a straight line."""
|
|
flat = _makeFlat(aGlyph)
|
|
return _intersect(flat, startPt, endPt, segmentLength)
|
|
|
|
def _intersect(flat, startPt, endPt, segmentLength=10):
|
|
"""Find the intersections between a flattened glyph and a straight line."""
|
|
if len(flat.contours) == 0:
|
|
return None
|
|
if startPt == endPt:
|
|
return None
|
|
sect = None
|
|
intersections = {}
|
|
# new contains the flattened outline
|
|
for c in flat:
|
|
l =len(c.points)
|
|
for i in range(l):
|
|
cur = c.points[i]
|
|
next = c.points[(i+1)%l]
|
|
sect = sectlines((cur.x, cur.y), (next.x, next.y), startPt, endPt)
|
|
if sect is None:
|
|
continue
|
|
intersections[sect] = 1
|
|
return intersections.keys()
|
|
|
|
def intersectGlyphs(glyphA, glyphB, segmentLength=10):
|
|
"""Approximate the intersection points between two glyphs by
|
|
flattening both glyphs and checking each tiny segment for
|
|
intersections. Slow, but perhaps more realistic then
|
|
solving the equasions.
|
|
|
|
Seems to work for basic curves and straights, but untested
|
|
for edges cases, alsmost hits, near hits, double points, crap like that.
|
|
"""
|
|
flatA = _makeFlat(glyphA)
|
|
flatB = _makeFlat(glyphB)
|
|
intersections = []
|
|
for c in flatA:
|
|
l =len(c.points)
|
|
for i in range(l):
|
|
cur = c.points[i]
|
|
next = c.points[(i+1)%l]
|
|
sect = _intersect(flatB, (cur.x, cur.y), (next.x, next.y))
|
|
if sect is None:
|
|
continue
|
|
intersections = intersections + sect
|
|
return intersections
|
|
|
|
def makeTestGlyph():
|
|
g = _RGlyph()
|
|
pen = g.getPen()
|
|
pen.moveTo((100, 100))
|
|
pen.lineTo((800, 100))
|
|
pen.curveTo((1000, 300), (1000, 600), (800, 800))
|
|
pen.lineTo((100, 800))
|
|
pen.lineTo((100, 100))
|
|
pen.closePath()
|
|
return g
|
|
|
|
if __name__ == "__main__":
|
|
g = makeTestGlyph()
|
|
print intersect(g, (-10, 200), (650, 150))
|
|
print intersect(g, (100, 100), (600, 600))
|