# Source code for fontTools.ttLib.tables._h_m_t_x

    from fontTools.misc.roundTools import otRound
    from fontTools import ttLib
    from fontTools.misc.textTools import safeEval
    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"

        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)
                )
                numberOfMetrics = numGlyphs
            numberOfSideBearings = numGlyphs - numberOfMetrics
            tableSize = 4 * numberOfMetrics + 2 * numberOfSideBearings
            if len(data) < tableSize:
                raise ttLib.TTLibError(
                    f"not enough '{self.tableTag}' table data: "
                    f"expected {tableSize} bytes, got {len(data)}"
                )
            # 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 :]
            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 = {}
            glyphOrder = ttFont.getGlyphOrder()
            for i in range(numberOfMetrics):
                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):
                glyphName = glyphOrder[i + numberOfMetrics]
                self.metrics[glyphName] = (lastAdvance, sideBearings[i])

        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 = []

            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)
                    )
                else:
                    raise
            additionalMetrics = array.array("h", additionalMetrics)
            if sys.byteorder != "big":
                additionalMetrics.byteswap()
            data = data + additionalMetrics.tobytes()
            return data

        def toXML(self, writer, ttFont):
            names = sorted(self.metrics.keys())
            for glyphName in names:
                advance, sb = self.metrics[glyphName]
                writer.simpletag(
                    "mtx",
                    [
                        ("name", glyphName),
                        (self.advanceName, advance),
                        (self.sideBearingName, sb),
                    ],
                )
                writer.newline()

        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]),
                )

        def __delitem__(self, glyphName):
            del self.metrics[glyphName]

        def __getitem__(self, glyphName):
            return self.metrics[glyphName]

        def __setitem__(self, glyphName, advance_sb_pair):
            self.metrics[glyphName] = tuple(advance_sb_pair)