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 11hheaFormat = """ 12 > # big endian 13 tableVersion: L 14 ascent: h 15 descent: h 16 lineGap: h 17 advanceWidthMax: H 18 minLeftSideBearing: h 19 minRightSideBearing: h 20 xMaxExtent: h 21 caretSlopeRise: h 22 caretSlopeRun: h 23 caretOffset: h 24 reserved0: h 25 reserved1: h 26 reserved2: h 27 reserved3: h 28 metricDataFormat: h 29 numberOfHMetrics: H 30""" 31 32 33class table__h_h_e_a(DefaultTable.DefaultTable): 34 # Note: Keep in sync with table__v_h_e_a 35 36 dependencies = ["hmtx", "glyf", "CFF ", "CFF2"] 37 38 # OpenType spec renamed these, add aliases for compatibility 39 @property 40 def ascender(self): 41 return self.ascent 42 43 @ascender.setter 44 def ascender(self, value): 45 self.ascent = value 46 47 @property 48 def descender(self): 49 return self.descent 50 51 @descender.setter 52 def descender(self, value): 53 self.descent = value 54 55 def decompile(self, data, ttFont): 56 sstruct.unpack(hheaFormat, data, self) 57 58 def compile(self, ttFont): 59 if ttFont.recalcBBoxes and ( 60 ttFont.isLoaded("glyf") 61 or ttFont.isLoaded("CFF ") 62 or ttFont.isLoaded("CFF2") 63 ): 64 self.recalc(ttFont) 65 self.tableVersion = fi2ve(self.tableVersion) 66 return sstruct.pack(hheaFormat, self) 67 68 def recalc(self, ttFont): 69 if "hmtx" not in ttFont: 70 return 71 72 hmtxTable = ttFont["hmtx"] 73 self.advanceWidthMax = max(adv for adv, _ in hmtxTable.metrics.values()) 74 75 boundsWidthDict = {} 76 if "glyf" in ttFont: 77 glyfTable = ttFont["glyf"] 78 for name in ttFont.getGlyphOrder(): 79 g = glyfTable[name] 80 if g.numberOfContours == 0: 81 continue 82 if g.numberOfContours < 0 and not hasattr(g, "xMax"): 83 # Composite glyph without extents set. 84 # Calculate those. 85 g.recalcBounds(glyfTable) 86 boundsWidthDict[name] = g.xMax - g.xMin 87 elif "CFF " in ttFont or "CFF2" in ttFont: 88 if "CFF " in ttFont: 89 topDict = ttFont["CFF "].cff.topDictIndex[0] 90 else: 91 topDict = ttFont["CFF2"].cff.topDictIndex[0] 92 charStrings = topDict.CharStrings 93 for name in ttFont.getGlyphOrder(): 94 cs = charStrings[name] 95 bounds = cs.calcBounds(charStrings) 96 if bounds is not None: 97 boundsWidthDict[name] = int( 98 math.ceil(bounds[2]) - math.floor(bounds[0]) 99 ) 100 101 if boundsWidthDict: 102 minLeftSideBearing = float("inf") 103 minRightSideBearing = float("inf") 104 xMaxExtent = -float("inf") 105 for name, boundsWidth in boundsWidthDict.items(): 106 advanceWidth, lsb = hmtxTable[name] 107 rsb = advanceWidth - lsb - boundsWidth 108 extent = lsb + boundsWidth 109 minLeftSideBearing = min(minLeftSideBearing, lsb) 110 minRightSideBearing = min(minRightSideBearing, rsb) 111 xMaxExtent = max(xMaxExtent, extent) 112 self.minLeftSideBearing = minLeftSideBearing 113 self.minRightSideBearing = minRightSideBearing 114 self.xMaxExtent = xMaxExtent 115 116 else: # No glyph has outlines. 117 self.minLeftSideBearing = 0 118 self.minRightSideBearing = 0 119 self.xMaxExtent = 0 120 121 def toXML(self, writer, ttFont): 122 formatstring, names, fixes = sstruct.getformat(hheaFormat) 123 for name in names: 124 value = getattr(self, name) 125 if name == "tableVersion": 126 value = fi2ve(value) 127 value = "0x%08x" % value 128 writer.simpletag(name, value=value) 129 writer.newline() 130 131 def fromXML(self, name, attrs, content, ttFont): 132 if name == "tableVersion": 133 setattr(self, name, ve2fi(attrs["value"])) 134 return 135 setattr(self, name, safeEval(attrs["value"])) 136