Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

161 lines
5.9 KiB
Python
Raw Normal View History

from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
2013-11-27 02:34:11 -05:00
from . import DefaultTable
import sys
import struct
import array
import logging
log = logging.getLogger(__name__)
class table__h_m_t_x(DefaultTable.DefaultTable):
"""Horizontal Metrics table
The ``hmtx`` table contains per-glyph metrics for the glyphs in a
``glyf``, ``CFF ``, or ``CFF2`` table, as needed for horizontal text
layout.
See also https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx
"""
headerTag = "hhea"
advanceName = "width"
sideBearingName = "lsb"
numberOfMetricsName = "numberOfHMetrics"
longMetricFormat = "Hh"
2015-04-26 02:01:01 -04:00
def decompile(self, data, ttFont):
numGlyphs = ttFont["maxp"].numGlyphs
headerTable = ttFont.get(self.headerTag)
if headerTable is not None:
numberOfMetrics = int(getattr(headerTable, self.numberOfMetricsName))
else:
numberOfMetrics = numGlyphs
if numberOfMetrics > numGlyphs:
log.warning(
"The %s.%s exceeds the maxp.numGlyphs"
% (self.headerTag, self.numberOfMetricsName)
2022-12-13 11:26:36 +00:00
)
numberOfMetrics = numGlyphs
if len(data) < 4 * numberOfMetrics:
raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
# Note: advanceWidth is unsigned, but some font editors might
# read/write as signed. We can't be sure whether it was a mistake
# or not, so we read as unsigned but also issue a warning...
metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
metrics = struct.unpack(metricsFmt, data[: 4 * numberOfMetrics])
data = data[4 * numberOfMetrics :]
numberOfSideBearings = numGlyphs - numberOfMetrics
sideBearings = array.array("h", data[: 2 * numberOfSideBearings])
data = data[2 * numberOfSideBearings :]
if sys.byteorder != "big":
sideBearings.byteswap()
if data:
log.warning("too much '%s' table data" % self.tableTag)
self.metrics = {}
2014-05-14 00:30:02 -04:00
glyphOrder = ttFont.getGlyphOrder()
for i in range(numberOfMetrics):
2014-05-14 00:30:02 -04:00
glyphName = glyphOrder[i]
advanceWidth, lsb = metrics[i * 2 : i * 2 + 2]
if advanceWidth > 32767:
log.warning(
"Glyph %r has a huge advance %s (%d); is it intentional or "
"an (invalid) negative value?",
glyphName,
self.advanceName,
advanceWidth,
)
self.metrics[glyphName] = (advanceWidth, lsb)
lastAdvance = metrics[-2]
for i in range(numberOfSideBearings):
2014-05-14 00:30:02 -04:00
glyphName = glyphOrder[i + numberOfMetrics]
self.metrics[glyphName] = (lastAdvance, sideBearings[i])
2015-04-26 02:01:01 -04:00
def compile(self, ttFont):
metrics = []
hasNegativeAdvances = False
for glyphName in ttFont.getGlyphOrder():
advanceWidth, sideBearing = self.metrics[glyphName]
if advanceWidth < 0:
log.error(
"Glyph %r has negative advance %s" % (glyphName, self.advanceName)
)
hasNegativeAdvances = True
metrics.append([advanceWidth, sideBearing])
headerTable = ttFont.get(self.headerTag)
if headerTable is not None:
lastAdvance = metrics[-1][0]
lastIndex = len(metrics)
while metrics[lastIndex - 2][0] == lastAdvance:
lastIndex -= 1
if lastIndex <= 1:
# all advances are equal
lastIndex = 1
break
additionalMetrics = metrics[lastIndex:]
additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
metrics = metrics[:lastIndex]
numberOfMetrics = len(metrics)
setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
else:
# no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
numberOfMetrics = ttFont["maxp"].numGlyphs
additionalMetrics = []
2015-04-26 02:01:01 -04:00
allMetrics = []
for advance, sb in metrics:
allMetrics.extend([otRound(advance), otRound(sb)])
metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
try:
data = struct.pack(metricsFmt, *allMetrics)
except struct.error as e:
if "out of range" in str(e) and hasNegativeAdvances:
raise ttLib.TTLibError(
"'%s' table can't contain negative advance %ss"
% (self.tableTag, self.advanceName)
2022-12-13 11:26:36 +00:00
)
else:
raise
additionalMetrics = array.array("h", additionalMetrics)
if sys.byteorder != "big":
additionalMetrics.byteswap()
data = data + additionalMetrics.tobytes()
return data
2015-04-26 02:01:01 -04:00
def toXML(self, writer, ttFont):
2013-11-27 04:15:34 -05:00
names = sorted(self.metrics.keys())
for glyphName in names:
advance, sb = self.metrics[glyphName]
writer.simpletag(
"mtx",
[
2015-04-26 02:01:01 -04:00
("name", glyphName),
(self.advanceName, advance),
(self.sideBearingName, sb),
],
)
writer.newline()
2015-04-26 02:01:01 -04:00
def fromXML(self, name, attrs, content, ttFont):
if not hasattr(self, "metrics"):
self.metrics = {}
if name == "mtx":
self.metrics[attrs["name"]] = (
safeEval(attrs[self.advanceName]),
safeEval(attrs[self.sideBearingName]),
)
2014-05-14 13:51:10 -06:00
def __delitem__(self, glyphName):
del self.metrics[glyphName]
2015-04-26 02:01:01 -04:00
def __getitem__(self, glyphName):
return self.metrics[glyphName]
2015-04-26 02:01:01 -04:00
def __setitem__(self, glyphName, advance_sb_pair):
self.metrics[glyphName] = tuple(advance_sb_pair)