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


[docs]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 # --------------
[docs] 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)
[docs] 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 # -------------
[docs] 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)
[docs] 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 # -------------
[docs] def interpolate(self, factor, minKerning, maxKerning, round=True, suppressError=True): """ 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)
[docs] 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): """ 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 # -------------------
[docs] def __contains__(self, pair): """ 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)
[docs] def __delitem__(self, pair): """ 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)
[docs] def __getitem__(self, pair): """ 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)
[docs] 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__()
[docs] def __len__(self): """ Returns the number of pairs in Kerning as an ``int``.:: >>> len(font.kerning) 5 """ return super(BaseKerning, self).__len__()
[docs] def __setitem__(self, pair, value): """ 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)
[docs] def clear(self): """ Removes all information from Kerning, resetting the Kerning to an empty dictionary. :: >>> font.kerning.clear() """ super(BaseKerning, self).clear()
[docs] def get(self, pair, default=None): """ 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)
[docs] def find(self, pair, default=None): """ 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)
[docs] def items(self): """ 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()
[docs] 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()
[docs] def pop(self, pair, default=None): """ 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)
[docs] 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)
[docs] def values(self): """ 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()