# Source code for fontParts.base.kerning

    from fontParts.base.base import (
        BaseDict,
        dynamicProperty,
        interpolate,
        reference
    )
    from fontParts.base import normalizers
    from fontParts.base.deprecated import DeprecatedKerning, RemovedKerning

    class BaseKerning(BaseDict, DeprecatedKerning, RemovedKerning):

        """
        A Kerning object. This object normally created as part of a
        :class:`BaseFont`. An orphan Kerning object can be created
        like this::

            >>> groups = RKerning()

        This object behaves like a Python dictionary. Most of the
        dictionary functionality comes from :class:`BaseDict`, look at
        that object for the required environment implementation details.

        Kerning uses :func:`normalizers.normalizeKerningKey` to normalize the
        key of the ``dict``, and :func:`normalizers.normalizeKerningValue`
        to normalize the the value of the ``dict``.
        """

        keyNormalizer = normalizers.normalizeKerningKey
        valueNormalizer = normalizers.normalizeKerningValue

        def _reprContents(self):
            contents = []
            if self.font is not None:
                contents.append("for font")
                contents += self.font._reprContents()
            return contents

        # -------
        # Parents
        # -------

        # Font

        _font = None

        font = dynamicProperty("font", "The Kerning's parent :class:`BaseFont`.")

        def _get_font(self):
            if self._font is None:
                return None
            return self._font()

        def _set_font(self, font):
            if self._font is not None and self._font() != font:
                raise AssertionError("font for kerning already set and is not same as font")
            if font is not None:
                font = reference(font)
            self._font = font

        # --------------
        # Transformation
        # --------------

        def scaleBy(self, factor):
            """
            Scales all kerning values by **factor**. **factor** will be an
            :ref:`type-int-float`, ``tuple`` or ``list``. The first value of the
            **factor** will be used to scale the kerning values.

                >>> myKerning.scaleBy(2)
                >>> myKerning.scaleBy((2,3))
            """
            factor = normalizers.normalizeTransformationScale(factor)
            self._scale(factor)

        def _scale(self, factor):
            """
            This is the environment implementation of :meth:`BaseKerning.scaleBy`.
            **factor** will be a ``tuple``.

            Subclasses may override this method.
            """
            factor = factor[0]
            for k, v in self.items():
                v *= factor
                self[k] = v

        # -------------
        # Normalization
        # -------------

        def round(self, multiple=1):
            """
            Rounds the kerning values to increments of **multiple**,
            which will be an ``int``.

            The default behavior is to round to increments of 1.
            """
            if not isinstance(multiple, int):
                raise TypeError("The round multiple must be an int not %s."
                                % multiple.__class__.__name__)
            self._round(multiple)

        def _round(self, multiple=1):
            """
            This is the environment implementation of
            :meth:`BaseKerning.round`. **multiple** will be an ``int``.

            Subclasses may override this method.
            """
            for pair, value in self.items():
                value = int(normalizers.normalizeVisualRounding(
                            value / float(multiple))) * multiple
                self[pair] = value

        # -------------
        # Interpolation
        # -------------

        def interpolate(self, factor, minKerning, maxKerning, round=True, suppressError=True):
            r"""
            Interpolates all pairs between two :class:`BaseKerning` objects:

                >>> myKerning.interpolate(kerningOne, kerningTwo)

            **minKerning** and **maxKerning**. The interpolation occurs on a
            0 to 1.0 range where **minKerning** is located at 0 and
            **maxKerning** is located at 1.0. The kerning data is replaced by
            the interpolated kerning.

            * **factor** is the interpolation value. It may be less than 0
              and greater than 1.0. It may be an :ref:`type-int-float`,
              ``tuple`` or ``list``. If it is a ``tuple`` or ``list``,
              the first number indicates the x factor and the second number
              indicates the y factor.
            * **round** is a ``bool`` indicating if the result should be rounded to
              ``int``\s. The default behavior is to round interpolated kerning.
            * **suppressError** is a ``bool`` indicating if incompatible data should
              be ignored or if an error should be raised when such incompatibilities
              are found. The default behavior is to ignore incompatible data.
            """
            factor = normalizers.normalizeInterpolationFactor(factor)
            if not isinstance(minKerning, BaseKerning):
                raise TypeError(("Interpolation to an instance of %r can not be "
                                 "performed from an instance of %r.") % (
                    self.__class__.__name__, minKerning.__class__.__name__))
            if not isinstance(maxKerning, BaseKerning):
                raise TypeError(("Interpolation to an instance of %r can not be "
                                 "performed from an instance of %r.") % (
                    self.__class__.__name__, maxKerning.__class__.__name__))
            round = normalizers.normalizeBoolean(round)
            suppressError = normalizers.normalizeBoolean(suppressError)
            self._interpolate(factor, minKerning, maxKerning,
                              round=round, suppressError=suppressError)

        def _interpolate(self, factor, minKerning, maxKerning,
                         round=True, suppressError=True):
            """
            This is the environment implementation of :meth:`BaseKerning.interpolate`.

            * **factor** will be an :ref:`type-int-float`, ``tuple`` or ``list``.
            * **minKerning** will be a :class:`BaseKerning` object.
            * **maxKerning** will be a :class:`BaseKerning` object.
            * **round** will be a ``bool`` indicating if the interpolated kerning
              should be rounded.
            * **suppressError** will be a ``bool`` indicating if incompatible data
              should be ignored.

            Subclasses may override this method.
            """
            import fontMath
            from fontMath.mathFunctions import setRoundIntegerFunction

            setRoundIntegerFunction(normalizers.normalizeVisualRounding)
            kerningGroupCompatibility = self._testKerningGroupCompatibility(
                                                            minKerning,
                                                            maxKerning,
                                                            suppressError=suppressError
                                                            )
            if not kerningGroupCompatibility:
                self.clear()
            else:
                minKerning = fontMath.MathKerning(
                    kerning=minKerning, groups=minKerning.font.groups)
                maxKerning = fontMath.MathKerning(
                    kerning=maxKerning, groups=maxKerning.font.groups)
                result = interpolate(minKerning, maxKerning, factor)
                if round:
                    result.round()
                self.clear()
                result.extractKerning(self.font)

        @staticmethod
        def _testKerningGroupCompatibility(minKerning, maxKerning, suppressError=False):
            minGroups = minKerning.font.groups
            maxGroups = maxKerning.font.groups
            match = True
            while match:
                for _, sideAttr in (
                        ("side 1", "side1KerningGroups"),
                        ("side 2", "side2KerningGroups")
                        ):
                    minSideGroups = getattr(minGroups, sideAttr)
                    maxSideGroups = getattr(maxGroups, sideAttr)
                    if minSideGroups.keys() != maxSideGroups.keys():
                        match = False
                    else:
                        for name in minSideGroups.keys():
                            minGroup = minSideGroups[name]
                            maxGroup = maxSideGroups[name]
                            if set(minGroup) != set(maxGroup):
                                match = False
                break
            if not match and not suppressError:
                raise ValueError("The kerning groups must be exactly the same.")
            return match

        # ---------------------
        # RoboFab Compatibility
        # ---------------------

        def remove(self, pair):
            r"""
            Removes a pair from the Kerning. **pair** will
            be a ``tuple`` of two :ref:`type-string`\s.

            This is a backwards compatibility method.
            """
            del self[pair]

        def asDict(self, returnIntegers=True):
            """
            Return the Kerning as a ``dict``.

            This is a backwards compatibility method.
            """
            d = {}
            for k, v in self.items():
                d[k] = v if not returnIntegers else normalizers.normalizeVisualRounding(v)
            return d

        # -------------------
        # Inherited Functions
        # -------------------

        def __contains__(self, pair):
            r"""
            Tests to see if a pair is in the Kerning.
            **pair** will be a ``tuple`` of two :ref:`type-string`\s.

            This returns a ``bool`` indicating if the **pair**
            is in the Kerning. ::

                >>> ("A", "V") in font.kerning
                True
            """
            return super(BaseKerning, self).__contains__(pair)

        def __delitem__(self, pair):
            r"""
            Removes **pair** from the Kerning. **pair** is a ``tuple`` of two
            :ref:`type-string`\s.::

                >>> del font.kerning[("A","V")]
            """
            super(BaseKerning, self).__delitem__(pair)

        def __getitem__(self, pair):
            r"""
            Returns the kerning value of the pair. **pair** is a ``tuple`` of
            two :ref:`type-string`\s.

            The returned value will be a :ref:`type-int-float`.::

                >>> font.kerning[("A", "V")]
                -15

            It is important to understand that any changes to the returned value
            will not be reflected in the Kerning object. If one wants to make a change to
            the value, one should do the following::

                >>> value = font.kerning[("A", "V")]
                >>> value += 10
                >>> font.kerning[("A", "V")] = value
            """
            return super(BaseKerning, self).__getitem__(pair)

        def __iter__(self):
            """
            Iterates through the Kerning, giving the pair for each iteration. The order that
            the Kerning will iterate though is not fixed nor is it ordered.::

                >>> for pair in font.kerning:
                >>>     print pair
                ("A", "Y")
                ("A", "V")
                ("A", "W")
            """
            return super(BaseKerning, self).__iter__()

        def __len__(self):
            """
            Returns the number of pairs in Kerning as an ``int``.::

                >>> len(font.kerning)
                5
            """
            return super(BaseKerning, self).__len__()

        def __setitem__(self, pair, value):
            r"""
            Sets the **pair** to the list of **value**. **pair** is the
            pair as a ``tuple`` of two :ref:`type-string`\s and **value**

            is a :ref:`type-int-float`.

                >>> font.kerning[("A", "V")] = -20
                >>> font.kerning[("A", "W")] = -10.5
            """
            super(BaseKerning, self).__setitem__(pair, value)

        def clear(self):
            """
            Removes all information from Kerning,
            resetting the Kerning to an empty dictionary. ::

                >>> font.kerning.clear()
            """
            super(BaseKerning, self).clear()

        def get(self, pair, default=None):
            r"""
            Returns the value for the kerning pair.
            **pair** is a ``tuple`` of two :ref:`type-string`\s, and the returned
            values will either be :ref:`type-int-float` or ``None``
            if no pair was found. ::

                >>> font.kerning[("A", "V")]
                -25

            It is important to understand that any changes to the returned value
            will not be reflected in the Kerning object. If one wants to make a change to
            the value, one should do the following::

                >>> value = font.kerning[("A", "V")]
                >>> value += 10
                >>> font.kerning[("A", "V")] = value
            """
            return super(BaseKerning, self).get(pair, default)

        def find(self, pair, default=None):
            r"""
            Returns the value for the kerning pair - even if the pair only exists
            implicitly (one or both sides may be members of a kerning group).

            **pair** is a ``tuple`` of two :ref:`type-string`\s, and the returned
            values will either be :ref:`type-int-float` or ``None``
            if no pair was found. ::

                >>> font.kerning[("A", "V")]
                -25
            """
            pair = normalizers.normalizeKerningKey(pair)
            value = self._find(pair, default)
            if value != default:
                value = normalizers.normalizeKerningValue(value)
            return value

        def _find(self, pair, default=None):
            """
            This is the environment implementation of
            :attr:`BaseKerning.find`. This must return an
            :ref:`type-int-float` or `default`.
            """
            from fontTools.ufoLib.kerning import lookupKerningValue
            font = self.font
            groups = font.groups
            return lookupKerningValue(pair, self, groups, fallback=default)

        def items(self):
            r"""
            Returns a list of ``tuple``\s of each pair and value. Pairs are a
            ``tuple`` of two :ref:`type-string`\s and values are :ref:`type-int-float`.

            The initial list will be unordered.

                >>> font.kerning.items()
                [(("A", "V"), -30), (("A", "W"), -10)]
            """
            return super(BaseKerning, self).items()

        def keys(self):
            """
            Returns a ``list`` of all the pairs in Kerning. This list will be
            unordered.::

                >>> font.kerning.keys()
                [("A", "Y"), ("A", "V"), ("A", "W")]
            """
            return super(BaseKerning, self).keys()

        def pop(self, pair, default=None):
            r"""
            Removes the **pair** from the Kerning and returns the value as an ``int``.
            If no pair is found, **default** is returned. **pair** is a
            ``tuple`` of two :ref:`type-string`\s. This must return either

            **default** or a :ref:`type-int-float`.

                >>> font.kerning.pop(("A", "V"))
                -20
                >>> font.kerning.pop(("A", "W"))
                -10.5
            """
            return super(BaseKerning, self).pop(pair, default)

        def update(self, otherKerning):
            """
            Updates the Kerning based on **otherKerning**. **otherKerning** is a ``dict`` of
            kerning information. If a pair from **otherKerning** is in Kerning, the pair
            value will be replaced by the value from **otherKerning**. If a pair
            from **otherKerning** is not in the Kerning, it is added to the pairs. If Kerning
            contains a pair that is not in **otherKerning**, it is not changed.

                >>> font.kerning.update(newKerning)
            """
            super(BaseKerning, self).update(otherKerning)

        def values(self):
            r"""
            Returns a ``list`` of each pair's values, the values will be
            :ref:`type-int-float`\s.

            The list will be unordered.

                >>> font.kerning.items()
                [-20, -15, 5, 3.5]
            """
            return super(BaseKerning, self).values()