Source code for fontParts.base.component

from fontTools.misc import transform
from fontParts.base import normalizers
from fontParts.base.errors import FontPartsError
from fontParts.base.base import (
    BaseObject,
    TransformationMixin,
    InterpolationMixin,
    PointPositionMixin,
    SelectionMixin,
    IdentifierMixin,
    dynamicProperty,
    reference
)
from fontParts.base.compatibility import ComponentCompatibilityReporter
from fontParts.base.deprecated import DeprecatedComponent, RemovedComponent


[docs]class BaseComponent( BaseObject, TransformationMixin, InterpolationMixin, SelectionMixin, IdentifierMixin, DeprecatedComponent, RemovedComponent ): copyAttributes = ( "baseGlyph", "transformation" ) def _reprContents(self): contents = [ "baseGlyph='%s'" % self.baseGlyph, "offset='({x}, {y})'".format(x=self.offset[0], y=self.offset[1]), ] if self.glyph is not None: contents.append("in glyph") contents += self.glyph._reprContents() return contents # ------- # Parents # ------- # Glyph _glyph = None glyph = dynamicProperty("glyph", "The component's parent glyph.") def _get_glyph(self): if self._glyph is None: return None return self._glyph() def _set_glyph(self, glyph): if self._glyph is not None: raise AssertionError("glyph for component already set") if glyph is not None: glyph = reference(glyph) self._glyph = glyph # Layer layer = dynamicProperty("layer", "The component's parent layer.") def _get_layer(self): if self._glyph is None: return None return self.glyph.layer # Font font = dynamicProperty("font", "The component's parent font.") def _get_font(self): if self._glyph is None: return None return self.glyph.font # ---------- # Attributes # ---------- # baseGlyph baseGlyph = dynamicProperty("base_baseGlyph", "The name of the glyph the component references.") def _get_base_baseGlyph(self): value = self._get_baseGlyph() # if the component does not belong to a layer, # it is allowed to have None as its baseGlyph if value is None and self.layer is None: pass else: value = normalizers.normalizeGlyphName(value) return value def _set_base_baseGlyph(self, value): value = normalizers.normalizeGlyphName(value) self._set_baseGlyph(value)
[docs] def _get_baseGlyph(self): """ Subclasses must override this method. """ self.raiseNotImplementedError()
[docs] def _set_baseGlyph(self, value): """ Subclasses must override this method. """ self.raiseNotImplementedError()
# transformation transformation = dynamicProperty("base_transformation", "The component's transformation matrix.") def _get_base_transformation(self): value = self._get_transformation() value = normalizers.normalizeTransformationMatrix(value) return value def _set_base_transformation(self, value): value = normalizers.normalizeTransformationMatrix(value) self._set_transformation(value)
[docs] def _get_transformation(self): """ Subclasses must override this method. """ self.raiseNotImplementedError()
[docs] def _set_transformation(self, value): """ Subclasses must override this method. """ self.raiseNotImplementedError()
# offset offset = dynamicProperty("base_offset", "The component's offset.") def _get_base_offset(self): value = self._get_offset() value = normalizers.normalizeTransformationOffset(value) return value def _set_base_offset(self, value): value = normalizers.normalizeTransformationOffset(value) self._set_offset(value)
[docs] def _get_offset(self): """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation return ox, oy
[docs] def _set_offset(self, value): """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation ox, oy = value self.transformation = sx, sxy, syx, sy, ox, oy
# scale scale = dynamicProperty("base_scale", "The component's scale.") def _get_base_scale(self): value = self._get_scale() value = normalizers.normalizeComponentScale(value) return value def _set_base_scale(self, value): value = normalizers.normalizeComponentScale(value) self._set_scale(value)
[docs] def _get_scale(self): """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation return sx, sy
[docs] def _set_scale(self, value): """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation sx, sy = value self.transformation = sx, sxy, syx, sy, ox, oy
# -------------- # Identification # -------------- # index index = dynamicProperty("base_index", ("The index of the component within the " "ordered list of the parent glyph's components.")) def _get_base_index(self): glyph = self.glyph if glyph is None: return None value = self._get_index() value = normalizers.normalizeIndex(value) return value def _set_base_index(self, value): glyph = self.glyph if glyph is None: raise FontPartsError("The component does not belong to a glyph.") value = normalizers.normalizeIndex(value) componentCount = len(glyph.components) if value < 0: value = -(value % componentCount) if value >= componentCount: value = componentCount self._set_index(value)
[docs] def _get_index(self): """ Subclasses may override this method. """ glyph = self.glyph return glyph.components.index(self)
[docs] def _set_index(self, value): """ Subclasses must override this method. """ self.raiseNotImplementedError()
# ---- # Pens # ----
[docs] def draw(self, pen): """ Draw the component with the given Pen. """ self._draw(pen)
[docs] def _draw(self, pen, **kwargs): """ Subclasses may override this method. """ from fontTools.ufoLib.pointPen import PointToSegmentPen adapter = PointToSegmentPen(pen) self.drawPoints(adapter)
[docs] def drawPoints(self, pen): """ Draw the contour with the given PointPen. """ self._drawPoints(pen)
[docs] def _drawPoints(self, pen, **kwargs): """ Subclasses may override this method. """ # The try: ... except TypeError: ... # handles backwards compatibility with # point pens that have not been upgraded # to point pen protocol 2. try: pen.addComponent(self.baseGlyph, self.transformation, identifier=self.identifier, **kwargs) except TypeError: pen.addComponent(self.baseGlyph, self.transformation, **kwargs)
# -------------- # Transformation # --------------
[docs] def _transformBy(self, matrix, **kwargs): """ Subclasses may override this method. """ t = transform.Transform(*matrix) transformation = t.transform(self.transformation) self.transformation = tuple(transformation)
# ------------- # Normalization # -------------
[docs] def round(self): """ Round offset coordinates. """ self._round()
[docs] def _round(self): """ Subclasses may override this method. """ x, y = self.offset x = normalizers.normalizeVisualRounding(x) y = normalizers.normalizeVisualRounding(y) self.offset = (x, y)
[docs] def decompose(self): """ Decompose the component. """ glyph = self.glyph if glyph is None: raise FontPartsError("The component does not belong to a glyph.") self._decompose()
[docs] def _decompose(self): """ Subclasses must override this method. """ self.raiseNotImplementedError()
# ------------- # Interpolation # ------------- compatibilityReporterClass = ComponentCompatibilityReporter def isCompatible(self, other): """ Evaluate interpolation compatibility with **other**. :: >>> compatible, report = self.isCompatible(otherComponent) >>> compatible True >>> compatible [Warning] Component: "A" + "B" [Warning] Component: "A" has name A | "B" has name B This will return a ``bool`` indicating if the component is compatible for interpolation with **other** and a :ref:`type-string` of compatibility notes. """ return super(BaseComponent, self).isCompatible(other, BaseComponent) def _isCompatible(self, other, reporter): """ This is the environment implementation of :meth:`BaseComponent.isCompatible`. Subclasses may override this method. """ component1 = self component2 = other # base glyphs if component1.baseName != component2.baseName: reporter.baseDifference = True reporter.warning = True # ------------ # Data Queries # ------------
[docs] def pointInside(self, point): """ Determine if point is in the black or white of the component. point must be an (x, y) tuple. """ point = normalizers.normalizeCoordinateTuple(point) return self._pointInside(point)
[docs] def _pointInside(self, point): """ Subclasses may override this method. """ from fontTools.pens.pointInsidePen import PointInsidePen pen = PointInsidePen(glyphSet=self.layer, testPoint=point, evenOdd=False) self.draw(pen) return pen.getResult()
bounds = dynamicProperty("base_bounds", ("The bounds of the component: " "(xMin, yMin, xMax, yMax) or None.")) def _get_base_bounds(self): value = self._get_bounds() if value is not None: value = normalizers.normalizeBoundingBox(value) return value
[docs] def _get_bounds(self): """ Subclasses may override this method. """ from fontTools.pens.boundsPen import BoundsPen pen = BoundsPen(self.layer) self.draw(pen) return pen.bounds