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