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