Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fontTools/ttLib/ttGlyphSet.py: 20%

197 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:33 +0000

1"""GlyphSets returned by a TTFont.""" 

2 

3from abc import ABC, abstractmethod 

4from collections.abc import Mapping 

5from contextlib import contextmanager 

6from copy import copy 

7from types import SimpleNamespace 

8from fontTools.misc.fixedTools import otRound 

9from fontTools.misc.loggingTools import deprecateFunction 

10from fontTools.misc.transform import Transform 

11from fontTools.pens.transformPen import TransformPen, TransformPointPen 

12 

13 

14class _TTGlyphSet(Mapping): 

15 

16 """Generic dict-like GlyphSet class that pulls metrics from hmtx and 

17 glyph shape from TrueType or CFF. 

18 """ 

19 

20 def __init__(self, font, location, glyphsMapping): 

21 self.font = font 

22 self.defaultLocationNormalized = ( 

23 {axis.axisTag: 0 for axis in self.font["fvar"].axes} 

24 if "fvar" in self.font 

25 else {} 

26 ) 

27 self.location = location if location is not None else {} 

28 self.rawLocation = {} # VarComponent-only location 

29 self.originalLocation = location if location is not None else {} 

30 self.depth = 0 

31 self.locationStack = [] 

32 self.rawLocationStack = [] 

33 self.glyphsMapping = glyphsMapping 

34 self.hMetrics = font["hmtx"].metrics 

35 self.vMetrics = getattr(font.get("vmtx"), "metrics", None) 

36 self.hvarTable = None 

37 if location: 

38 from fontTools.varLib.varStore import VarStoreInstancer 

39 

40 self.hvarTable = getattr(font.get("HVAR"), "table", None) 

41 if self.hvarTable is not None: 

42 self.hvarInstancer = VarStoreInstancer( 

43 self.hvarTable.VarStore, font["fvar"].axes, location 

44 ) 

45 # TODO VVAR, VORG 

46 

47 @contextmanager 

48 def pushLocation(self, location, reset: bool): 

49 self.locationStack.append(self.location) 

50 self.rawLocationStack.append(self.rawLocation) 

51 if reset: 

52 self.location = self.originalLocation.copy() 

53 self.rawLocation = self.defaultLocationNormalized.copy() 

54 else: 

55 self.location = self.location.copy() 

56 self.rawLocation = {} 

57 self.location.update(location) 

58 self.rawLocation.update(location) 

59 

60 try: 

61 yield None 

62 finally: 

63 self.location = self.locationStack.pop() 

64 self.rawLocation = self.rawLocationStack.pop() 

65 

66 @contextmanager 

67 def pushDepth(self): 

68 try: 

69 depth = self.depth 

70 self.depth += 1 

71 yield depth 

72 finally: 

73 self.depth -= 1 

74 

75 def __contains__(self, glyphName): 

76 return glyphName in self.glyphsMapping 

77 

78 def __iter__(self): 

79 return iter(self.glyphsMapping.keys()) 

80 

81 def __len__(self): 

82 return len(self.glyphsMapping) 

83 

84 @deprecateFunction( 

85 "use 'glyphName in glyphSet' instead", category=DeprecationWarning 

86 ) 

87 def has_key(self, glyphName): 

88 return glyphName in self.glyphsMapping 

89 

90 

91class _TTGlyphSetGlyf(_TTGlyphSet): 

92 def __init__(self, font, location): 

93 self.glyfTable = font["glyf"] 

94 super().__init__(font, location, self.glyfTable) 

95 self.gvarTable = font.get("gvar") 

96 

97 def __getitem__(self, glyphName): 

98 return _TTGlyphGlyf(self, glyphName) 

99 

100 

101class _TTGlyphSetCFF(_TTGlyphSet): 

102 def __init__(self, font, location): 

103 tableTag = "CFF2" if "CFF2" in font else "CFF " 

104 self.charStrings = list(font[tableTag].cff.values())[0].CharStrings 

105 super().__init__(font, location, self.charStrings) 

106 self.blender = None 

107 if location: 

108 from fontTools.varLib.varStore import VarStoreInstancer 

109 

110 varStore = getattr(self.charStrings, "varStore", None) 

111 if varStore is not None: 

112 instancer = VarStoreInstancer( 

113 varStore.otVarStore, font["fvar"].axes, location 

114 ) 

115 self.blender = instancer.interpolateFromDeltas 

116 

117 def __getitem__(self, glyphName): 

118 return _TTGlyphCFF(self, glyphName) 

119 

120 

121class _TTGlyph(ABC): 

122 

123 """Glyph object that supports the Pen protocol, meaning that it has 

124 .draw() and .drawPoints() methods that take a pen object as their only 

125 argument. Additionally there are 'width' and 'lsb' attributes, read from 

126 the 'hmtx' table. 

127 

128 If the font contains a 'vmtx' table, there will also be 'height' and 'tsb' 

129 attributes. 

130 """ 

131 

132 def __init__(self, glyphSet, glyphName): 

133 self.glyphSet = glyphSet 

134 self.name = glyphName 

135 self.width, self.lsb = glyphSet.hMetrics[glyphName] 

136 if glyphSet.vMetrics is not None: 

137 self.height, self.tsb = glyphSet.vMetrics[glyphName] 

138 else: 

139 self.height, self.tsb = None, None 

140 if glyphSet.location and glyphSet.hvarTable is not None: 

141 varidx = ( 

142 glyphSet.font.getGlyphID(glyphName) 

143 if glyphSet.hvarTable.AdvWidthMap is None 

144 else glyphSet.hvarTable.AdvWidthMap.mapping[glyphName] 

145 ) 

146 self.width += glyphSet.hvarInstancer[varidx] 

147 # TODO: VVAR/VORG 

148 

149 @abstractmethod 

150 def draw(self, pen): 

151 """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details 

152 how that works. 

153 """ 

154 raise NotImplementedError 

155 

156 def drawPoints(self, pen): 

157 """Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details 

158 how that works. 

159 """ 

160 from fontTools.pens.pointPen import SegmentToPointPen 

161 

162 self.draw(SegmentToPointPen(pen)) 

163 

164 

165class _TTGlyphGlyf(_TTGlyph): 

166 def draw(self, pen): 

167 """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details 

168 how that works. 

169 """ 

170 glyph, offset = self._getGlyphAndOffset() 

171 

172 with self.glyphSet.pushDepth() as depth: 

173 

174 if depth: 

175 offset = 0 # Offset should only apply at top-level 

176 

177 if glyph.isVarComposite(): 

178 self._drawVarComposite(glyph, pen, False) 

179 return 

180 

181 glyph.draw(pen, self.glyphSet.glyfTable, offset) 

182 

183 def drawPoints(self, pen): 

184 """Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details 

185 how that works. 

186 """ 

187 glyph, offset = self._getGlyphAndOffset() 

188 

189 with self.glyphSet.pushDepth() as depth: 

190 

191 if depth: 

192 offset = 0 # Offset should only apply at top-level 

193 

194 if glyph.isVarComposite(): 

195 self._drawVarComposite(glyph, pen, True) 

196 return 

197 

198 glyph.drawPoints(pen, self.glyphSet.glyfTable, offset) 

199 

200 def _drawVarComposite(self, glyph, pen, isPointPen): 

201 

202 from fontTools.ttLib.tables._g_l_y_f import ( 

203 VarComponentFlags, 

204 VAR_COMPONENT_TRANSFORM_MAPPING, 

205 ) 

206 

207 for comp in glyph.components: 

208 

209 with self.glyphSet.pushLocation( 

210 comp.location, comp.flags & VarComponentFlags.RESET_UNSPECIFIED_AXES 

211 ): 

212 try: 

213 pen.addVarComponent( 

214 comp.glyphName, comp.transform, self.glyphSet.rawLocation 

215 ) 

216 except AttributeError: 

217 t = comp.transform.toTransform() 

218 if isPointPen: 

219 tPen = TransformPointPen(pen, t) 

220 self.glyphSet[comp.glyphName].drawPoints(tPen) 

221 else: 

222 tPen = TransformPen(pen, t) 

223 self.glyphSet[comp.glyphName].draw(tPen) 

224 

225 def _getGlyphAndOffset(self): 

226 if self.glyphSet.location and self.glyphSet.gvarTable is not None: 

227 glyph = self._getGlyphInstance() 

228 else: 

229 glyph = self.glyphSet.glyfTable[self.name] 

230 

231 offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0 

232 return glyph, offset 

233 

234 def _getGlyphInstance(self): 

235 from fontTools.varLib.iup import iup_delta 

236 from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates 

237 from fontTools.varLib.models import supportScalar 

238 

239 glyphSet = self.glyphSet 

240 glyfTable = glyphSet.glyfTable 

241 variations = glyphSet.gvarTable.variations[self.name] 

242 hMetrics = glyphSet.hMetrics 

243 vMetrics = glyphSet.vMetrics 

244 coordinates, _ = glyfTable._getCoordinatesAndControls( 

245 self.name, hMetrics, vMetrics 

246 ) 

247 origCoords, endPts = None, None 

248 for var in variations: 

249 scalar = supportScalar(glyphSet.location, var.axes) 

250 if not scalar: 

251 continue 

252 delta = var.coordinates 

253 if None in delta: 

254 if origCoords is None: 

255 origCoords, control = glyfTable._getCoordinatesAndControls( 

256 self.name, hMetrics, vMetrics 

257 ) 

258 endPts = ( 

259 control[1] if control[0] >= 1 else list(range(len(control[1]))) 

260 ) 

261 delta = iup_delta(delta, origCoords, endPts) 

262 coordinates += GlyphCoordinates(delta) * scalar 

263 

264 glyph = copy(glyfTable[self.name]) # Shallow copy 

265 width, lsb, height, tsb = _setCoordinates(glyph, coordinates, glyfTable) 

266 self.lsb = lsb 

267 self.tsb = tsb 

268 if glyphSet.hvarTable is None: 

269 # no HVAR: let's set metrics from the phantom points 

270 self.width = width 

271 self.height = height 

272 return glyph 

273 

274 

275class _TTGlyphCFF(_TTGlyph): 

276 def draw(self, pen): 

277 """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details 

278 how that works. 

279 """ 

280 self.glyphSet.charStrings[self.name].draw(pen, self.glyphSet.blender) 

281 

282 

283def _setCoordinates(glyph, coord, glyfTable): 

284 # Handle phantom points for (left, right, top, bottom) positions. 

285 assert len(coord) >= 4 

286 leftSideX = coord[-4][0] 

287 rightSideX = coord[-3][0] 

288 topSideY = coord[-2][1] 

289 bottomSideY = coord[-1][1] 

290 

291 for _ in range(4): 

292 del coord[-1] 

293 

294 if glyph.isComposite(): 

295 assert len(coord) == len(glyph.components) 

296 glyph.components = [copy(comp) for comp in glyph.components] # Shallow copy 

297 for p, comp in zip(coord, glyph.components): 

298 if hasattr(comp, "x"): 

299 comp.x, comp.y = p 

300 elif glyph.isVarComposite(): 

301 glyph.components = [copy(comp) for comp in glyph.components] # Shallow copy 

302 for comp in glyph.components: 

303 coord = comp.setCoordinates(coord) 

304 assert not coord 

305 elif glyph.numberOfContours == 0: 

306 assert len(coord) == 0 

307 else: 

308 assert len(coord) == len(glyph.coordinates) 

309 glyph.coordinates = coord 

310 

311 glyph.recalcBounds(glyfTable) 

312 

313 horizontalAdvanceWidth = otRound(rightSideX - leftSideX) 

314 verticalAdvanceWidth = otRound(topSideY - bottomSideY) 

315 leftSideBearing = otRound(glyph.xMin - leftSideX) 

316 topSideBearing = otRound(topSideY - glyph.yMax) 

317 return ( 

318 horizontalAdvanceWidth, 

319 leftSideBearing, 

320 verticalAdvanceWidth, 

321 topSideBearing, 

322 )