1from fontTools.misc import sstruct 2from fontTools.misc.textTools import safeEval 3from fontTools.misc.fixedTools import ( 4 ensureVersionIsLong as fi2ve, versionToFixed as ve2fi) 5from . import DefaultTable 6import math 7 8 9vheaFormat = """ 10 > # big endian 11 tableVersion: L 12 ascent: h 13 descent: h 14 lineGap: h 15 advanceHeightMax: H 16 minTopSideBearing: h 17 minBottomSideBearing: h 18 yMaxExtent: h 19 caretSlopeRise: h 20 caretSlopeRun: h 21 caretOffset: h 22 reserved1: h 23 reserved2: h 24 reserved3: h 25 reserved4: h 26 metricDataFormat: h 27 numberOfVMetrics: H 28""" 29 30class table__v_h_e_a(DefaultTable.DefaultTable): 31 32 # Note: Keep in sync with table__h_h_e_a 33 34 dependencies = ['vmtx', 'glyf', 'CFF ', 'CFF2'] 35 36 def decompile(self, data, ttFont): 37 sstruct.unpack(vheaFormat, data, self) 38 39 def compile(self, ttFont): 40 if ttFont.recalcBBoxes and (ttFont.isLoaded('glyf') or ttFont.isLoaded('CFF ') or ttFont.isLoaded('CFF2')): 41 self.recalc(ttFont) 42 self.tableVersion = fi2ve(self.tableVersion) 43 return sstruct.pack(vheaFormat, self) 44 45 def recalc(self, ttFont): 46 if 'vmtx' in ttFont: 47 vmtxTable = ttFont['vmtx'] 48 self.advanceHeightMax = max(adv for adv, _ in vmtxTable.metrics.values()) 49 50 boundsHeightDict = {} 51 if 'glyf' in ttFont: 52 glyfTable = ttFont['glyf'] 53 for name in ttFont.getGlyphOrder(): 54 g = glyfTable[name] 55 if g.numberOfContours == 0: 56 continue 57 if g.numberOfContours < 0 and not hasattr(g, "yMax"): 58 # Composite glyph without extents set. 59 # Calculate those. 60 g.recalcBounds(glyfTable) 61 boundsHeightDict[name] = g.yMax - g.yMin 62 elif 'CFF ' in ttFont or 'CFF2' in ttFont: 63 if 'CFF ' in ttFont: 64 topDict = ttFont['CFF '].cff.topDictIndex[0] 65 else: 66 topDict = ttFont['CFF2'].cff.topDictIndex[0] 67 charStrings = topDict.CharStrings 68 for name in ttFont.getGlyphOrder(): 69 cs = charStrings[name] 70 bounds = cs.calcBounds(charStrings) 71 if bounds is not None: 72 boundsHeightDict[name] = int( 73 math.ceil(bounds[3]) - math.floor(bounds[1])) 74 75 if boundsHeightDict: 76 minTopSideBearing = float('inf') 77 minBottomSideBearing = float('inf') 78 yMaxExtent = -float('inf') 79 for name, boundsHeight in boundsHeightDict.items(): 80 advanceHeight, tsb = vmtxTable[name] 81 bsb = advanceHeight - tsb - boundsHeight 82 extent = tsb + boundsHeight 83 minTopSideBearing = min(minTopSideBearing, tsb) 84 minBottomSideBearing = min(minBottomSideBearing, bsb) 85 yMaxExtent = max(yMaxExtent, extent) 86 self.minTopSideBearing = minTopSideBearing 87 self.minBottomSideBearing = minBottomSideBearing 88 self.yMaxExtent = yMaxExtent 89 90 else: # No glyph has outlines. 91 self.minTopSideBearing = 0 92 self.minBottomSideBearing = 0 93 self.yMaxExtent = 0 94 95 def toXML(self, writer, ttFont): 96 formatstring, names, fixes = sstruct.getformat(vheaFormat) 97 for name in names: 98 value = getattr(self, name) 99 if name == "tableVersion": 100 value = fi2ve(value) 101 value = "0x%08x" % value 102 writer.simpletag(name, value=value) 103 writer.newline() 104 105 def fromXML(self, name, attrs, content, ttFont): 106 if name == "tableVersion": 107 setattr(self, name, ve2fi(attrs["value"])) 108 return 109 setattr(self, name, safeEval(attrs["value"])) 110 111 # reserved0 is caretOffset for legacy reasons 112 @property 113 def reserved0(self): 114 return self.caretOffset 115 116 @reserved0.setter 117 def reserved0(self, value): 118 self.caretOffset = value 119