Source code for fontParts.base.info

from fontParts.base.base import (
    BaseObject,
    dynamicProperty,
    interpolate,
    reference
)
from fontParts.base import normalizers
from fontParts.base.errors import FontPartsError
from fontParts.base.deprecated import DeprecatedInfo, RemovedInfo


[docs]class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): from fontTools.ufoLib import fontInfoAttributesVersion3 copyAttributes = set(fontInfoAttributesVersion3) copyAttributes.remove("guidelines") copyAttributes = tuple(copyAttributes) 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 info's parent font.") 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 info already set and is not same as font") if font is not None: font = reference(font) self._font = font # ---------- # Validation # ---------- @staticmethod def _validateFontInfoAttributeValue(attr, value): from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute valid = validateFontInfoVersion3ValueForAttribute(attr, value) if not valid: raise ValueError("Invalid value %s for attribute '%s'." % (value, attr)) return value # ---------- # Attributes # ---------- # has def __hasattr__(self, attr): from fontTools.ufoLib import fontInfoAttributesVersion3 if attr in fontInfoAttributesVersion3: return True return super(BaseInfo, self).__hasattr__(attr) # get def __getattribute__(self, attr): from fontTools.ufoLib import fontInfoAttributesVersion3 if attr != "guidelines" and attr in fontInfoAttributesVersion3: value = self._getAttr(attr) if value is not None: value = self._validateFontInfoAttributeValue(attr, value) return value return super(BaseInfo, self).__getattribute__(attr)
[docs] def _getAttr(self, attr): """ Subclasses may override this method. If a subclass does not override this method, it must implement '_get_attributeName' methods for all Info methods. """ meth = "_get_%s" % attr if not hasattr(self, meth): raise AttributeError("No getter for attribute '%s'." % attr) meth = getattr(self, meth) value = meth() return value
# set def __setattr__(self, attr, value): from fontTools.ufoLib import fontInfoAttributesVersion3 if attr != "guidelines" and attr in fontInfoAttributesVersion3: if value is not None: value = self._validateFontInfoAttributeValue(attr, value) return self._setAttr(attr, value) return super(BaseInfo, self).__setattr__(attr, value)
[docs] def _setAttr(self, attr, value): """ Subclasses may override this method. If a subclass does not override this method, it must implement '_set_attributeName' methods for all Info methods. """ meth = "_set_%s" % attr if not hasattr(self, meth): raise AttributeError("No setter for attribute '%s'." % attr) meth = getattr(self, meth) meth(value)
# ------------- # Normalization # -------------
[docs] def round(self): """ Round the following attributes to integers: - unitsPerEm - descender - xHeight - capHeight - ascender - openTypeHeadLowestRecPPEM - openTypeHheaAscender - openTypeHheaDescender - openTypeHheaLineGap - openTypeHheaCaretSlopeRise - openTypeHheaCaretSlopeRun - openTypeHheaCaretOffset - openTypeOS2WidthClass - openTypeOS2WeightClass - openTypeOS2TypoAscender - openTypeOS2TypoDescender - openTypeOS2TypoLineGap - openTypeOS2WinAscent - openTypeOS2WinDescent - openTypeOS2SubscriptXSize - openTypeOS2SubscriptYSize - openTypeOS2SubscriptXOffset - openTypeOS2SubscriptYOffset - openTypeOS2SuperscriptXSize - openTypeOS2SuperscriptYSize - openTypeOS2SuperscriptXOffset - openTypeOS2SuperscriptYOffset - openTypeOS2StrikeoutSize - openTypeOS2StrikeoutPosition - openTypeVheaVertTypoAscender - openTypeVheaVertTypoDescender - openTypeVheaVertTypoLineGap - openTypeVheaCaretSlopeRise - openTypeVheaCaretSlopeRun - openTypeVheaCaretOffset - postscriptSlantAngle - postscriptUnderlineThickness - postscriptUnderlinePosition - postscriptBlueValues - postscriptOtherBlues - postscriptFamilyBlues - postscriptFamilyOtherBlues - postscriptStemSnapH - postscriptStemSnapV - postscriptBlueFuzz - postscriptBlueShift - postscriptDefaultWidthX - postscriptNominalWidthX """ self._round()
[docs] def _round(self, **kwargs): """ Subclasses may override this method. """ from fontMath.mathFunctions import setRoundIntegerFunction setRoundIntegerFunction(normalizers.normalizeVisualRounding) mathInfo = self._toMathInfo(guidelines=False) mathInfo = mathInfo.round() self._fromMathInfo(mathInfo, guidelines=False)
# -------- # Updating # --------
[docs] def update(self, other): """ Update this object with the values from **otherInfo**. """ self._update(other)
def _update(self, other): """ Subclasses may override this method. """ from fontTools.ufoLib import fontInfoAttributesVersion3 for attr in fontInfoAttributesVersion3: if attr == "guidelines": continue value = getattr(other, attr) setattr(self, attr, value) # ------------- # Interpolation # ------------- def toMathInfo(self, guidelines=True): """ Returns the info as an object that follows the `MathGlyph protocol <https://github.com/typesupply/fontMath>`_. >>> mg = font.info.toMathInfo() """ return self._toMathInfo(guidelines=guidelines) def fromMathInfo(self, mathInfo, guidelines=True): """ Replaces the contents of this info object with the contents of ``mathInfo``. >>> font.fromMathInfo(mg) ``mathInfo`` must be an object following the `MathInfo protocol <https://github.com/typesupply/fontMath>`_. """ return self._fromMathInfo(mathInfo, guidelines=guidelines) def _toMathInfo(self, guidelines=True): """ Subclasses may override this method. """ import fontMath # A little trickery is needed here because MathInfo # handles font level guidelines. Those are not in this # object so we temporarily fake them just enough for # MathInfo and then move them back to the proper place. self.guidelines = [] if guidelines: for guideline in self.font.guidelines: d = dict( x=guideline.x, y=guideline.y, angle=guideline.angle, name=guideline.name, identifier=guideline.identifier, color=guideline.color ) self.guidelines.append(d) info = fontMath.MathInfo(self) del self.guidelines return info def _fromMathInfo(self, mathInfo, guidelines=True): """ Subclasses may override this method. """ self.guidelines = [] mathInfo.extractInfo(self) font = self.font if guidelines: for guideline in self.guidelines: font.appendGuideline( position=(guideline["x"], guideline["y"]), angle=guideline["angle"], name=guideline["name"], color=guideline["color"] # XXX identifier is lost ) del self.guidelines
[docs] def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Interpolate all pairs between minInfo and maxInfo. The interpolation occurs on a 0 to 1.0 range where minInfo is located at 0 and maxInfo is located at 1.0. factor is the interpolation value. It may be less than 0 and greater than 1.0. It may be a number (integer, float) or a tuple of two numbers. If it is a tuple, the first number indicates the x factor and the second number indicates the y factor. round indicates if the result should be rounded to integers. suppressError indicates if incompatible data should be ignored or if an error should be raised when such incompatibilities are found. """ factor = normalizers.normalizeInterpolationFactor(factor) if not isinstance(minInfo, BaseInfo): raise TypeError(("Interpolation to an instance of %r can not be " "performed from an instance of %r.") % (self.__class__.__name__, minInfo.__class__.__name__)) if not isinstance(maxInfo, BaseInfo): raise TypeError(("Interpolation to an instance of %r can not be " "performed from an instance of %r.") % (self.__class__.__name__, maxInfo.__class__.__name__)) round = normalizers.normalizeBoolean(round) suppressError = normalizers.normalizeBoolean(suppressError) self._interpolate(factor, minInfo, maxInfo, round=round, suppressError=suppressError)
[docs] def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Subclasses may override this method. """ from fontMath.mathFunctions import setRoundIntegerFunction setRoundIntegerFunction(normalizers.normalizeVisualRounding) minInfo = minInfo._toMathInfo() maxInfo = maxInfo._toMathInfo() result = interpolate(minInfo, maxInfo, factor) if result is None and not suppressError: raise FontPartsError(("Info from font '%s' and font '%s' could not be " "interpolated.") % (minInfo.font.name, maxInfo.font.name)) if round: result = result.round() self._fromMathInfo(result)