# Source code for fontTools.ttLib.tables._a_v_a_r

    from fontTools.misc import sstruct
    from fontTools.misc.fixedTools import (
        fixedToFloat as fi2fl,
        floatToFixed as fl2fi,
        floatToFixedToStr as fl2str,
        strToFixedToFloat as str2fl,
    )
    from fontTools.misc.textTools import bytesjoin, safeEval
    from fontTools.misc.roundTools import otRound
    from fontTools.varLib.models import piecewiseLinearMap
    from fontTools.varLib.varStore import VarStoreInstancer, NO_VARIATION_INDEX
    from fontTools.ttLib import TTLibError
    from . import DefaultTable
    from . import otTables
    import struct
    import logging

    log = logging.getLogger(__name__)

    from .otBase import BaseTTXConverter

    class table__a_v_a_r(BaseTTXConverter):
        """Axis Variations table

        This class represents the ``avar`` table of a variable font. The object has one
        substantive attribute, ``segments``, which maps axis tags to a segments dictionary::

            >>> font["avar"].segments   # doctest: +SKIP
            {'wght': {-1.0: -1.0,
              0.0: 0.0,
              0.125: 0.11444091796875,
              0.25: 0.23492431640625,
              0.5: 0.35540771484375,
              0.625: 0.5,
              0.75: 0.6566162109375,
              0.875: 0.81927490234375,
              1.0: 1.0},
             'ital': {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}}

        Notice that the segments dictionary is made up of normalized values. A valid
        ``avar`` segment mapping must contain the entries ``-1.0: -1.0, 0.0: 0.0, 1.0: 1.0``.
        fontTools does not enforce this, so it is your responsibility to ensure that
        mappings are valid.

        See also https://learn.microsoft.com/en-us/typography/opentype/spec/avar
        """

        dependencies = ["fvar"]

        def __init__(self, tag=None):
            super().__init__(tag)
            self.segments = {}

        def compile(self, ttFont):
            axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
            if not hasattr(self, "table"):
                self.table = otTables.avar()
            if not hasattr(self.table, "Reserved"):
                self.table.Reserved = 0
            self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
                self, "minorVersion", 0
            )
            self.table.AxisCount = len(axisTags)
            self.table.AxisSegmentMap = []
            for axis in axisTags:
                mappings = self.segments[axis]
                segmentMap = otTables.AxisSegmentMap()
                segmentMap.PositionMapCount = len(mappings)
                segmentMap.AxisValueMap = []
                for key, value in sorted(mappings.items()):
                    valueMap = otTables.AxisValueMap()
                    valueMap.FromCoordinate = key
                    valueMap.ToCoordinate = value
                    segmentMap.AxisValueMap.append(valueMap)
                self.table.AxisSegmentMap.append(segmentMap)
            return super().compile(ttFont)

        def decompile(self, data, ttFont):
            super().decompile(data, ttFont)
            self.majorVersion = self.table.Version >> 16
            self.minorVersion = self.table.Version & 0xFFFF
            if self.majorVersion not in (1, 2):
                raise NotImplementedError("Unknown avar table version")
            axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
            for axis in axisTags:
                self.segments[axis] = {}
            for axis, segmentMap in zip(axisTags, self.table.AxisSegmentMap):
                segments = self.segments[axis] = {}
                for segment in segmentMap.AxisValueMap:
                    segments[segment.FromCoordinate] = segment.ToCoordinate

        def toXML(self, writer, ttFont):
            writer.simpletag(
                "version",
                major=getattr(self, "majorVersion", 1),
                minor=getattr(self, "minorVersion", 0),
            )
            writer.newline()
            axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
            for axis in axisTags:
                writer.begintag("segment", axis=axis)
                writer.newline()
                for key, value in sorted(self.segments[axis].items()):
                    key = fl2str(key, 14)
                    value = fl2str(value, 14)
                    writer.simpletag("mapping", **{"from": key, "to": value})
                    writer.newline()
                writer.endtag("segment")
                writer.newline()
            if getattr(self, "majorVersion", 1) >= 2:
                if self.table.VarIdxMap:
                    self.table.VarIdxMap.toXML(writer, ttFont, name="VarIdxMap")
                if self.table.VarStore:
                    self.table.VarStore.toXML(writer, ttFont)

        def fromXML(self, name, attrs, content, ttFont):
            if not hasattr(self, "table"):
                self.table = otTables.avar()
            if not hasattr(self.table, "Reserved"):
                self.table.Reserved = 0
            if name == "version":
                self.majorVersion = safeEval(attrs["major"])
                self.minorVersion = safeEval(attrs["minor"])
                self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
                    self, "minorVersion", 0
                )
            elif name == "segment":
                axis = attrs["axis"]
                segment = self.segments[axis] = {}
                for element in content:
                    if isinstance(element, tuple):
                        elementName, elementAttrs, _ = element
                        if elementName == "mapping":
                            fromValue = str2fl(elementAttrs["from"], 14)
                            toValue = str2fl(elementAttrs["to"], 14)
                            if fromValue in segment:
                                log.warning(
                                    "duplicate entry for %s in axis '%s'", fromValue, axis
                                )
                            segment[fromValue] = toValue
            else:
                super().fromXML(name, attrs, content, ttFont)

        def renormalizeLocation(self, location, font):

            majorVersion = getattr(self, "majorVersion", 1)

            if majorVersion not in (1, 2):
                raise NotImplementedError("Unknown avar table version")

            avarSegments = self.segments
            mappedLocation = {}
            for axisTag, value in location.items():
                avarMapping = avarSegments.get(axisTag, None)
                if avarMapping is not None:
                    value = piecewiseLinearMap(value, avarMapping)
                mappedLocation[axisTag] = value

            if majorVersion < 2:
                return mappedLocation

            # Version 2

            varIdxMap = self.table.VarIdxMap
            varStore = self.table.VarStore
            axes = font["fvar"].axes
            if varStore is not None:
                instancer = VarStoreInstancer(varStore, axes, mappedLocation)

            coords = list(fl2fi(mappedLocation.get(axis.axisTag, 0), 14) for axis in axes)

            out = []
            for varIdx, v in enumerate(coords):

                if varIdxMap is not None:
                    varIdx = varIdxMap[varIdx]

                if varStore is not None:
                    delta = instancer[varIdx]
                    v += otRound(delta)
                    v = min(max(v, -(1 << 14)), +(1 << 14))

                out.append(v)

            mappedLocation = {
                axis.axisTag: fi2fl(v, 14) for v, axis in zip(out, axes) if v != 0
            }

            return mappedLocation