• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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