convertCurves.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. #! /usr/bin/env python
  2. """
  3. Converts a cubic bezier curve to a quadratic spline with
  4. exactly two off curve points.
  5. """
  6. import numpy
  7. from numpy import array,cross,dot
  8. from fontTools.misc import bezierTools
  9. from robofab.objects.objectsRF import RSegment
  10. def replaceSegments(contour, segments):
  11. while len(contour):
  12. contour.removeSegment(0)
  13. for s in segments:
  14. contour.appendSegment(s.type, [(p.x, p.y) for p in s.points], s.smooth)
  15. def calcIntersect(a,b,c,d):
  16. numpy.seterr(all='raise')
  17. e = b-a
  18. f = d-c
  19. p = array([-e[1], e[0]])
  20. try:
  21. h = dot((a-c),p) / dot(f,p)
  22. except:
  23. print a,b,c,d
  24. raise
  25. return c + dot(f,h)
  26. def simpleConvertToQuadratic(p0,p1,p2,p3):
  27. p = [array(i.x,i.y) for i in [p0,p1,p2,p3]]
  28. off = calcIntersect(p[0],p[1],p[2],p[3])
  29. # OFFCURVE_VECTOR_CORRECTION = -.015
  30. OFFCURVE_VECTOR_CORRECTION = 0
  31. def convertToQuadratic(p0,p1,p2,p3):
  32. # TODO: test for accuracy and subdivide further if needed
  33. p = [(i.x,i.y) for i in [p0,p1,p2,p3]]
  34. # if p[0][0] == p[1][0] and p[0][0] == p[2][0] and p[0][0] == p[2][0] and p[0][0] == p[3][0]:
  35. # return (p[0],p[1],p[2],p[3])
  36. # if p[0][1] == p[1][1] and p[0][1] == p[2][1] and p[0][1] == p[2][1] and p[0][1] == p[3][1]:
  37. # return (p[0],p[1],p[2],p[3])
  38. seg1,seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5)
  39. pts1 = [array([i[0], i[1]]) for i in seg1]
  40. pts2 = [array([i[0], i[1]]) for i in seg2]
  41. on1 = seg1[0]
  42. on2 = seg2[3]
  43. try:
  44. off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3])
  45. off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3])
  46. except:
  47. return (p[0],p[1],p[2],p[3])
  48. off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1
  49. off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2
  50. return (on1,off1,off2,on2)
  51. def cubicSegmentToQuadratic(c,sid):
  52. segment = c[sid]
  53. if (segment.type != "curve"):
  54. print "Segment type not curve"
  55. return
  56. #pSegment,junk = getPrevAnchor(c,sid)
  57. pSegment = c[sid-1] #assumes that a curve type will always be proceeded by another point on the same contour
  58. points = convertToQuadratic(pSegment.points[-1],segment.points[0],
  59. segment.points[1],segment.points[2])
  60. return RSegment(
  61. 'qcurve', [[int(i) for i in p] for p in points[1:]], segment.smooth)
  62. def glyphCurvesToQuadratic(g):
  63. for c in g:
  64. segments = []
  65. for i in range(len(c)):
  66. s = c[i]
  67. if s.type == "curve":
  68. try:
  69. segments.append(cubicSegmentToQuadratic(c, i))
  70. except Exception:
  71. print g.name, i
  72. raise
  73. else:
  74. segments.append(s)
  75. replaceSegments(c, segments)