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)
178 lines
5.7 KiB
Cython
178 lines
5.7 KiB
Cython
# Copyright 2015 Google Inc. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
import math
|
|
|
|
import numpy as np
|
|
from numpy.linalg import lstsq
|
|
|
|
|
|
def alignCorners(glyph, va, subsegments):
|
|
out = va.copy()
|
|
# for i,c in enumerate(subsegments):
|
|
# segmentCount = len(glyph.contours[i].segments) - 1
|
|
# n = len(c)
|
|
# for j,s in enumerate(c):
|
|
# if j < segmentCount:
|
|
# seg = glyph.contours[i].segments[j]
|
|
# if seg.type == "line":
|
|
# subIndex = subsegmentIndex(i,j,subsegments)
|
|
# out[subIndex] = alignPoints(va[subIndex])
|
|
|
|
for i,c in enumerate(subsegments):
|
|
segmentCount = len(glyph.contours[i].segments)
|
|
n = len(c)
|
|
for j,s in enumerate(c):
|
|
if j < segmentCount - 1:
|
|
segType = glyph.contours[i].segments[j].type
|
|
segnextType = glyph.contours[i].segments[j+1].type
|
|
next = j+1
|
|
elif j == segmentCount -1 and s[1] > 3:
|
|
segType = glyph.contours[i].segments[j].type
|
|
segNextType = "line"
|
|
next = j+1
|
|
elif j == segmentCount:
|
|
segType = "line"
|
|
segnextType = glyph.contours[i].segments[1].type
|
|
if glyph.name == "J":
|
|
print s[1]
|
|
print segnextType
|
|
next = 1
|
|
else:
|
|
break
|
|
if segType == "line" and segnextType == "line":
|
|
subIndex = subsegmentIndex(i,j,subsegments)
|
|
pts = va[subIndex]
|
|
ptsnext = va[subsegmentIndex(i,next,subsegments)]
|
|
# out[subIndex[-1]] = (out[subIndex[-1]] - 500) * 3 + 500 #findCorner(pts, ptsnext)
|
|
# print subIndex[-1], subIndex, subsegmentIndex(i,next,subsegments)
|
|
try:
|
|
out[subIndex[-1]] = findCorner(pts, ptsnext)
|
|
except:
|
|
pass
|
|
# print glyph.name, "Can't find corner: parallel lines"
|
|
return out
|
|
|
|
|
|
def subsegmentIndex(contourIndex, segmentIndex, subsegments):
|
|
# This whole thing is so dumb. Need a better data model for subsegments
|
|
|
|
contourOffset = 0
|
|
for i,c in enumerate(subsegments):
|
|
if i == contourIndex:
|
|
break
|
|
contourOffset += c[-1][0]
|
|
n = subsegments[contourIndex][-1][0]
|
|
# print contourIndex, contourOffset, n
|
|
startIndex = subsegments[contourIndex][segmentIndex-1][0]
|
|
segmentCount = subsegments[contourIndex][segmentIndex][1]
|
|
endIndex = (startIndex + segmentCount + 1) % (n)
|
|
|
|
indices = np.array([(startIndex + i) % (n) + contourOffset for i in range(segmentCount + 1)])
|
|
return indices
|
|
|
|
|
|
def alignPoints(pts, start=None, end=None):
|
|
if start == None or end == None:
|
|
start, end = fitLine(pts)
|
|
out = pts.copy()
|
|
for i,p in enumerate(pts):
|
|
out[i] = nearestPoint(start, end, p)
|
|
return out
|
|
|
|
|
|
def findCorner(pp, nn):
|
|
if len(pp) < 4 or len(nn) < 4:
|
|
assert 0, "line too short to fit"
|
|
pStart,pEnd = fitLine(pp)
|
|
nStart,nEnd = fitLine(nn)
|
|
prev = pEnd - pStart
|
|
next = nEnd - nStart
|
|
# print int(np.arctan2(prev[1],prev[0]) / math.pi * 180),
|
|
# print int(np.arctan2(next[1],next[0]) / math.pi * 180)
|
|
# if lines are parallel, return simple average of end and start points
|
|
if np.dot(prev / np.linalg.norm(prev),
|
|
next / np.linalg.norm(next)) > .999999:
|
|
# print "parallel lines", np.arctan2(prev[1],prev[0]), np.arctan2(next[1],next[0])
|
|
# print prev, next
|
|
assert 0, "parallel lines"
|
|
# if glyph.name is None:
|
|
# # Never happens, but here to fix a bug in Python 2.7 with -OO
|
|
# print ''
|
|
return lineIntersect(pStart, pEnd, nStart, nEnd)
|
|
|
|
|
|
def lineIntersect(p1, p2, p3, p4):
|
|
x1, y1 = p1
|
|
x2, y2 = p2
|
|
x3, y3 = p3
|
|
x4, y4 = p4
|
|
|
|
x12 = x1 - x2
|
|
x34 = x3 - x4
|
|
y12 = y1 - y2
|
|
y34 = y3 - y4
|
|
|
|
det = x12 * y34 - y12 * x34
|
|
if det == 0:
|
|
print "parallel!"
|
|
|
|
a = x1 * y2 - y1 * x2
|
|
b = x3 * y4 - y3 * x4
|
|
|
|
x = (a * x34 - b * x12) / det
|
|
y = (a * y34 - b * y12) / det
|
|
|
|
return (x,y)
|
|
|
|
|
|
def fitLineLSQ(pts):
|
|
"returns a line fit with least squares. Fails for vertical lines"
|
|
n = len(pts)
|
|
a = np.ones((n,2))
|
|
for i in range(n):
|
|
a[i,0] = pts[i,0]
|
|
line = lstsq(a,pts[:,1])[0]
|
|
return line
|
|
|
|
|
|
def fitLine(pts):
|
|
"""returns a start vector and direction vector
|
|
Assumes points segments that already form a somewhat smooth line
|
|
"""
|
|
n = len(pts)
|
|
if n < 1:
|
|
return (0,0),(0,0)
|
|
a = np.zeros((n-1,2))
|
|
for i in range(n-1):
|
|
v = pts[i] - pts[i+1]
|
|
a[i] = v / np.linalg.norm(v)
|
|
direction = np.mean(a[1:-1], axis=0)
|
|
start = np.mean(pts[1:-1], axis=0)
|
|
return start, start+direction
|
|
|
|
|
|
def nearestPoint(a,b,c):
|
|
"nearest point to point c on line a_b"
|
|
magnitude = np.linalg.norm(b-a)
|
|
if magnitude == 0:
|
|
raise Exception, "Line segment cannot be 0 length"
|
|
return (b-a) * np.dot((c-a) / magnitude, (b-a) / magnitude) + a
|
|
|
|
|
|
# pts = np.array([[1,1],[2,2],[3,3],[4,4]])
|
|
# pts2 = np.array([[1,0],[2,0],[3,0],[4,0]])
|
|
# print alignPoints(pts2, start = pts[0], end = pts[0]+pts[0])
|
|
# # print findCorner(pts,pts2)
|