mix.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. # Copyright 2015 Google Inc. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from numpy import array, append
  15. import copy
  16. import json
  17. from robofab.objects.objectsRF import RPoint
  18. from robofab.world import OpenFont
  19. from decomposeGlyph import decomposeGlyph
  20. class FFont:
  21. "Font wrapper for floating point operations"
  22. def __init__(self,f=None):
  23. self.glyphs = {}
  24. self.hstems = []
  25. self.vstems = []
  26. self.kerning = {}
  27. if isinstance(f,FFont):
  28. #self.glyphs = [g.copy() for g in f.glyphs]
  29. for key,g in f.glyphs.iteritems():
  30. self.glyphs[key] = g.copy()
  31. self.hstems = list(f.hstems)
  32. self.vstems = list(f.vstems)
  33. self.kerning = dict(f.kerning)
  34. elif f != None:
  35. self.copyFromFont(f)
  36. def copyFromFont(self, f):
  37. for g in f:
  38. self.glyphs[g.name] = FGlyph(g)
  39. self.hstems = [s for s in f.info.postscriptStemSnapH]
  40. self.vstems = [s for s in f.info.postscriptStemSnapV]
  41. self.kerning = f.kerning.asDict()
  42. def getGlyph(self, gname):
  43. try:
  44. return self.glyphs[gname]
  45. except:
  46. return None
  47. def addDiff(self,b,c):
  48. newFont = FFont(self)
  49. for key,g in newFont.glyphs.iteritems():
  50. gB = b.getGlyph(key)
  51. gC = c.getGlyph(key)
  52. try:
  53. newFont.glyphs[key] = g.addDiff(gB,gC)
  54. except:
  55. print "Add diff failed for '%s'" %key
  56. return newFont
  57. class FGlyph:
  58. "provides a temporary floating point compatible glyph data structure"
  59. def __init__(self, g=None):
  60. self.contours = []
  61. self.width = 0.
  62. self.components = []
  63. self.anchors = []
  64. if g != None:
  65. self.copyFromGlyph(g)
  66. def copyFromGlyph(self,g):
  67. self.name = g.name
  68. valuesX = []
  69. valuesY = []
  70. self.width = len(valuesX)
  71. valuesX.append(g.width)
  72. for c in g.components:
  73. self.components.append((len(valuesX), len(valuesY)))
  74. valuesX.append(c.scale[0])
  75. valuesY.append(c.scale[1])
  76. valuesX.append(c.offset[0])
  77. valuesY.append(c.offset[1])
  78. for a in g.anchors:
  79. self.anchors.append((len(valuesX), len(valuesY)))
  80. valuesX.append(a.x)
  81. valuesY.append(a.y)
  82. for i in range(len(g)):
  83. self.contours.append([])
  84. for j in range (len(g[i].points)):
  85. self.contours[i].append((len(valuesX), len(valuesY)))
  86. valuesX.append(g[i].points[j].x)
  87. valuesY.append(g[i].points[j].y)
  88. self.dataX = array(valuesX, dtype=float)
  89. self.dataY = array(valuesY, dtype=float)
  90. def copyToGlyph(self,g):
  91. g.width = self._derefX(self.width)
  92. if len(g.components) == len(self.components):
  93. for i in range(len(self.components)):
  94. g.components[i].scale = (self._derefX(self.components[i][0] + 0, asInt=False),
  95. self._derefY(self.components[i][1] + 0, asInt=False))
  96. g.components[i].offset = (self._derefX(self.components[i][0] + 1),
  97. self._derefY(self.components[i][1] + 1))
  98. if len(g.anchors) == len(self.anchors):
  99. for i in range(len(self.anchors)):
  100. g.anchors[i].x = self._derefX( self.anchors[i][0])
  101. g.anchors[i].y = self._derefY( self.anchors[i][1])
  102. for i in range(len(g)) :
  103. for j in range (len(g[i].points)):
  104. g[i].points[j].x = self._derefX(self.contours[i][j][0])
  105. g[i].points[j].y = self._derefY(self.contours[i][j][1])
  106. def isCompatible(self, g):
  107. return (len(self.dataX) == len(g.dataX) and
  108. len(self.dataY) == len(g.dataY) and
  109. len(g.contours) == len(self.contours))
  110. def __add__(self,g):
  111. if self.isCompatible(g):
  112. newGlyph = self.copy()
  113. newGlyph.dataX = self.dataX + g.dataX
  114. newGlyph.dataY = self.dataY + g.dataY
  115. return newGlyph
  116. else:
  117. print "Add failed for '%s'" %(self.name)
  118. raise Exception
  119. def __sub__(self,g):
  120. if self.isCompatible(g):
  121. newGlyph = self.copy()
  122. newGlyph.dataX = self.dataX - g.dataX
  123. newGlyph.dataY = self.dataY - g.dataY
  124. return newGlyph
  125. else:
  126. print "Subtract failed for '%s'" %(self.name)
  127. raise Exception
  128. def __mul__(self,scalar):
  129. newGlyph = self.copy()
  130. newGlyph.dataX = self.dataX * scalar
  131. newGlyph.dataY = self.dataY * scalar
  132. return newGlyph
  133. def interp(self, g, v):
  134. gF = self.copy()
  135. if not self.isCompatible(g):
  136. print "Interpolate failed for '%s'; outlines incompatible" %(self.name)
  137. raise Exception
  138. gF.dataX += (g.dataX - gF.dataX) * v.x
  139. gF.dataY += (g.dataY - gF.dataY) * v.y
  140. return gF
  141. def copy(self):
  142. ng = FGlyph()
  143. ng.contours = list(self.contours)
  144. ng.width = self.width
  145. ng.components = list(self.components)
  146. ng.anchors = list(self.anchors)
  147. ng.dataX = self.dataX.copy()
  148. ng.dataY = self.dataY.copy()
  149. ng.name = self.name
  150. return ng
  151. def _derefX(self,id, asInt=True):
  152. val = self.dataX[id]
  153. return int(round(val)) if asInt else val
  154. def _derefY(self,id, asInt=True):
  155. val = self.dataY[id]
  156. return int(round(val)) if asInt else val
  157. def addDiff(self,gB,gC):
  158. newGlyph = self + (gB - gC)
  159. return newGlyph
  160. class Master:
  161. def __init__(self, font=None, v=0, kernlist=None, overlay=None):
  162. if isinstance(font, FFont):
  163. self.font = None
  164. self.ffont = font
  165. elif isinstance(font,str):
  166. self.openFont(font,overlay)
  167. elif isinstance(font,Mix):
  168. self.font = font
  169. else:
  170. self.font = font
  171. self.ffont = FFont(font)
  172. if isinstance(v,float) or isinstance(v,int):
  173. self.v = RPoint(v, v)
  174. else:
  175. self.v = v
  176. if kernlist != None:
  177. kerns = [i.strip().split() for i in open(kernlist).readlines()]
  178. self.kernlist = [{'left':k[0], 'right':k[1], 'value': k[2]}
  179. for k in kerns
  180. if not k[0].startswith("#")
  181. and not k[0] == ""]
  182. #TODO implement class based kerning / external kerning file
  183. def openFont(self, path, overlayPath=None):
  184. self.font = OpenFont(path)
  185. for g in self.font:
  186. size = len(g)
  187. csize = len(g.components)
  188. if (size > 0 and csize > 0):
  189. decomposeGlyph(self.font, g.name)
  190. if overlayPath != None:
  191. overlayFont = OpenFont(overlayPath)
  192. font = self.font
  193. for overlayGlyph in overlayFont:
  194. font.insertGlyph(overlayGlyph)
  195. self.ffont = FFont(self.font)
  196. class Mix:
  197. def __init__(self,masters,v):
  198. self.masters = masters
  199. if isinstance(v,float) or isinstance(v,int):
  200. self.v = RPoint(v,v)
  201. else:
  202. self.v = v
  203. def getFGlyph(self, master, gname):
  204. if isinstance(master.font, Mix):
  205. return font.mixGlyphs(gname)
  206. return master.ffont.getGlyph(gname)
  207. def getGlyphMasters(self,gname):
  208. masters = self.masters
  209. if len(masters) <= 2:
  210. return self.getFGlyph(masters[0], gname), self.getFGlyph(masters[-1], gname)
  211. def generateFFont(self):
  212. ffont = FFont(self.masters[0].ffont)
  213. for key,g in ffont.glyphs.iteritems():
  214. ffont.glyphs[key] = self.mixGlyphs(key)
  215. ffont.kerning = self.mixKerns()
  216. return ffont
  217. def generateFont(self, baseFont):
  218. newFont = baseFont.copy()
  219. for g in newFont:
  220. gF = self.mixGlyphs(g.name)
  221. if gF == None:
  222. g.mark = True
  223. else:
  224. gF.copyToGlyph(g)
  225. newFont.kerning.clear()
  226. newFont.kerning.update(self.mixKerns() or {})
  227. return newFont
  228. def mixGlyphs(self,gname):
  229. gA,gB = self.getGlyphMasters(gname)
  230. try:
  231. return gA.interp(gB,self.v)
  232. except:
  233. print "mixglyph failed for %s" %(gname)
  234. if gA != None:
  235. return gA.copy()
  236. def getKerning(self, master):
  237. if isinstance(master.font, Mix):
  238. return master.font.mixKerns()
  239. return master.ffont.kerning
  240. def mixKerns(self):
  241. masters = self.masters
  242. kA, kB = self.getKerning(masters[0]), self.getKerning(masters[-1])
  243. return interpolateKerns(kA, kB, self.v)
  244. def interpolate(a,b,v,e=0):
  245. if e == 0:
  246. return a+(b-a)*v
  247. qe = (b-a)*v*v*v + a #cubic easing
  248. le = a+(b-a)*v # linear easing
  249. return le + (qe-le) * e
  250. def interpolateKerns(kA, kB, v):
  251. kerns = {}
  252. for pair, val in kA.items():
  253. kerns[pair] = interpolate(val, kB.get(pair, 0), v.x)
  254. for pair, val in kB.items():
  255. lerped_val = interpolate(val, kA.get(pair, 0), 1 - v.x)
  256. if pair in kerns:
  257. assert abs(kerns[pair] - lerped_val) < 1e-6
  258. else:
  259. kerns[pair] = lerped_val
  260. return kerns