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
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:33 +0000
1"""GlyphSets returned by a TTFont."""
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
14class _TTGlyphSet(Mapping):
16 """Generic dict-like GlyphSet class that pulls metrics from hmtx and
17 glyph shape from TrueType or CFF.
18 """
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
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
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)
60 try:
61 yield None
62 finally:
63 self.location = self.locationStack.pop()
64 self.rawLocation = self.rawLocationStack.pop()
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
75 def __contains__(self, glyphName):
76 return glyphName in self.glyphsMapping
78 def __iter__(self):
79 return iter(self.glyphsMapping.keys())
81 def __len__(self):
82 return len(self.glyphsMapping)
84 @deprecateFunction(
85 "use 'glyphName in glyphSet' instead", category=DeprecationWarning
86 )
87 def has_key(self, glyphName):
88 return glyphName in self.glyphsMapping
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")
97 def __getitem__(self, glyphName):
98 return _TTGlyphGlyf(self, glyphName)
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
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
117 def __getitem__(self, glyphName):
118 return _TTGlyphCFF(self, glyphName)
121class _TTGlyph(ABC):
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.
128 If the font contains a 'vmtx' table, there will also be 'height' and 'tsb'
129 attributes.
130 """
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
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
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
162 self.draw(SegmentToPointPen(pen))
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()
172 with self.glyphSet.pushDepth() as depth:
174 if depth:
175 offset = 0 # Offset should only apply at top-level
177 if glyph.isVarComposite():
178 self._drawVarComposite(glyph, pen, False)
179 return
181 glyph.draw(pen, self.glyphSet.glyfTable, offset)
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()
189 with self.glyphSet.pushDepth() as depth:
191 if depth:
192 offset = 0 # Offset should only apply at top-level
194 if glyph.isVarComposite():
195 self._drawVarComposite(glyph, pen, True)
196 return
198 glyph.drawPoints(pen, self.glyphSet.glyfTable, offset)
200 def _drawVarComposite(self, glyph, pen, isPointPen):
202 from fontTools.ttLib.tables._g_l_y_f import (
203 VarComponentFlags,
204 VAR_COMPONENT_TRANSFORM_MAPPING,
205 )
207 for comp in glyph.components:
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)
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]
231 offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
232 return glyph, offset
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
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
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
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)
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]
291 for _ in range(4):
292 del coord[-1]
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
311 glyph.recalcBounds(glyfTable)
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 )