convertCurves.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #! /usr/bin/env python
  2. #
  3. # Copyright 2015 Google Inc. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """
  17. Converts a cubic bezier curve to a quadratic spline with
  18. exactly two off curve points.
  19. """
  20. import numpy
  21. from numpy import array,cross,dot
  22. from fontTools.misc import bezierTools
  23. from robofab.objects.objectsRF import RSegment
  24. def replaceSegments(contour, segments):
  25. while len(contour):
  26. contour.removeSegment(0)
  27. for s in segments:
  28. contour.appendSegment(s.type, [(p.x, p.y) for p in s.points], s.smooth)
  29. def calcIntersect(a,b,c,d):
  30. numpy.seterr(all='raise')
  31. e = b-a
  32. f = d-c
  33. p = array([-e[1], e[0]])
  34. try:
  35. h = dot((a-c),p) / dot(f,p)
  36. except:
  37. print a,b,c,d
  38. raise
  39. return c + dot(f,h)
  40. def simpleConvertToQuadratic(p0,p1,p2,p3):
  41. p = [array(i.x,i.y) for i in [p0,p1,p2,p3]]
  42. off = calcIntersect(p[0],p[1],p[2],p[3])
  43. # OFFCURVE_VECTOR_CORRECTION = -.015
  44. OFFCURVE_VECTOR_CORRECTION = 0
  45. def convertToQuadratic(p0,p1,p2,p3):
  46. # TODO: test for accuracy and subdivide further if needed
  47. p = [(i.x,i.y) for i in [p0,p1,p2,p3]]
  48. # 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]:
  49. # return (p[0],p[1],p[2],p[3])
  50. # 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]:
  51. # return (p[0],p[1],p[2],p[3])
  52. seg1,seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5)
  53. pts1 = [array([i[0], i[1]]) for i in seg1]
  54. pts2 = [array([i[0], i[1]]) for i in seg2]
  55. on1 = seg1[0]
  56. on2 = seg2[3]
  57. try:
  58. off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3])
  59. off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3])
  60. except:
  61. return (p[0],p[1],p[2],p[3])
  62. off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1
  63. off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2
  64. return (on1,off1,off2,on2)
  65. def cubicSegmentToQuadratic(c,sid):
  66. segment = c[sid]
  67. if (segment.type != "curve"):
  68. print "Segment type not curve"
  69. return
  70. #pSegment,junk = getPrevAnchor(c,sid)
  71. pSegment = c[sid-1] #assumes that a curve type will always be proceeded by another point on the same contour
  72. points = convertToQuadratic(pSegment.points[-1],segment.points[0],
  73. segment.points[1],segment.points[2])
  74. return RSegment(
  75. 'qcurve', [[int(i) for i in p] for p in points[1:]], segment.smooth)
  76. def glyphCurvesToQuadratic(g):
  77. for c in g:
  78. segments = []
  79. for i in range(len(c)):
  80. s = c[i]
  81. if s.type == "curve":
  82. try:
  83. segments.append(cubicSegmentToQuadratic(c, i))
  84. except Exception:
  85. print g.name, i
  86. raise
  87. else:
  88. segments.append(s)
  89. replaceSegments(c, segments)