Source code for fontParts.base.bPoint

from fontTools.misc import transform
from fontParts.base.base import (
    BaseObject,
    TransformationMixin,
    SelectionMixin,
    IdentifierMixin,
    dynamicProperty,
    reference
)
from fontParts.base.errors import FontPartsError
from fontParts.base import normalizers
from fontParts.base.deprecated import DeprecatedBPoint, RemovedBPoint


[docs]class BaseBPoint( BaseObject, TransformationMixin, SelectionMixin, DeprecatedBPoint, IdentifierMixin, RemovedBPoint ): def _reprContents(self): contents = [ "%s" % self.type, "anchor='({x}, {y})'".format(x=self.anchor[0], y=self.anchor[1]), ] return contents def _setPoint(self, point): if hasattr(self, "_point"): raise AssertionError("point for bPoint already set") self._point = point def __eq__(self, other): if hasattr(other, "_point"): return self._point == other._point return NotImplemented # this class should not be used in hashable # collections since it is dynamically generated. __hash__ = None # ------- # Parents # ------- # identifier def _get_identifier(self): """ Subclasses may override this method. """ return self._point.identifier def _getIdentifier(self): """ Subclasses may override this method. """ return self._point.getIdentifier() # Segment _segment = dynamicProperty("base_segment") def _get_base_segment(self): point = self._point for segment in self.contour.segments: if segment.onCurve == point: return segment _nextSegment = dynamicProperty("base_nextSegment") def _get_base_nextSegment(self): contour = self.contour if contour is None: return None segments = contour.segments segment = self._segment i = segments.index(segment) + 1 if i >= len(segments): i = i % len(segments) nextSegment = segments[i] return nextSegment # Contour _contour = None contour = dynamicProperty("contour", "The bPoint's parent contour.") def _get_contour(self): if self._contour is None: return None return self._contour() def _set_contour(self, contour): if self._contour is not None: raise AssertionError("contour for bPoint already set") if contour is not None: contour = reference(contour) self._contour = contour # Glyph glyph = dynamicProperty("glyph", "The bPoint's parent glyph.") def _get_glyph(self): if self._contour is None: return None return self.contour.glyph # Layer layer = dynamicProperty("layer", "The bPoint's parent layer.") def _get_layer(self): if self._contour is None: return None return self.glyph.layer # Font font = dynamicProperty("font", "The bPoint's parent font.") def _get_font(self): if self._contour is None: return None return self.glyph.font # ---------- # Attributes # ---------- # anchor anchor = dynamicProperty("base_anchor", "The anchor point.") def _get_base_anchor(self): value = self._get_anchor() value = normalizers.normalizeCoordinateTuple(value) return value def _set_base_anchor(self, value): value = normalizers.normalizeCoordinateTuple(value) self._set_anchor(value)
[docs] def _get_anchor(self): """ Subclasses may override this method. """ point = self._point return (point.x, point.y)
[docs] def _set_anchor(self, value): """ Subclasses may override this method. """ pX, pY = self.anchor x, y = value dX = x - pX dY = y - pY self.moveBy((dX, dY))
# bcp in bcpIn = dynamicProperty("base_bcpIn", "The incoming off curve.") def _get_base_bcpIn(self): value = self._get_bcpIn() value = normalizers.normalizeCoordinateTuple(value) return value def _set_base_bcpIn(self, value): value = normalizers.normalizeCoordinateTuple(value) self._set_bcpIn(value)
[docs] def _get_bcpIn(self): """ Subclasses may override this method. """ segment = self._segment offCurves = segment.offCurve if offCurves: bcp = offCurves[-1] x, y = relativeBCPIn(self.anchor, (bcp.x, bcp.y)) else: x = y = 0 return (x, y)
[docs] def _set_bcpIn(self, value): """ Subclasses may override this method. """ x, y = absoluteBCPIn(self.anchor, value) segment = self._segment if segment.type == "move" and value != (0, 0): raise FontPartsError(("Cannot set the bcpIn for the first " "point in an open contour.") ) else: offCurves = segment.offCurve if offCurves: # if the two off curves are located at the anchor # coordinates we can switch to a line segment type. if value == (0, 0) and self.bcpOut == (0, 0): segment.type = "line" segment.smooth = False else: offCurves[-1].x = x offCurves[-1].y = y elif value != (0, 0): segment.type = "curve" offCurves = segment.offCurve offCurves[-1].x = x offCurves[-1].y = y
# bcp out bcpOut = dynamicProperty("base_bcpOut", "The outgoing off curve.") def _get_base_bcpOut(self): value = self._get_bcpOut() value = normalizers.normalizeCoordinateTuple(value) return value def _set_base_bcpOut(self, value): value = normalizers.normalizeCoordinateTuple(value) self._set_bcpOut(value)
[docs] def _get_bcpOut(self): """ Subclasses may override this method. """ nextSegment = self._nextSegment offCurves = nextSegment.offCurve if offCurves: bcp = offCurves[0] x, y = relativeBCPOut(self.anchor, (bcp.x, bcp.y)) else: x = y = 0 return (x, y)
[docs] def _set_bcpOut(self, value): """ Subclasses may override this method. """ x, y = absoluteBCPOut(self.anchor, value) segment = self._segment nextSegment = self._nextSegment if nextSegment.type == "move" and value != (0, 0): raise FontPartsError(("Cannot set the bcpOut for the last " "point in an open contour.") ) else: offCurves = nextSegment.offCurve if offCurves: # if the off curves are located at the anchor coordinates # we can switch to a "line" segment type if value == (0, 0) and self.bcpIn == (0, 0): segment.type = "line" segment.smooth = False else: offCurves[0].x = x offCurves[0].y = y elif value != (0, 0): nextSegment.type = "curve" offCurves = nextSegment.offCurve offCurves[0].x = x offCurves[0].y = y
# type type = dynamicProperty("base_type", "The bPoint type.") def _get_base_type(self): value = self._get_type() value = normalizers.normalizeBPointType(value) return value def _set_base_type(self, value): value = normalizers.normalizeBPointType(value) self._set_type(value)
[docs] def _get_type(self): """ Subclasses may override this method. """ point = self._point typ = point.type bType = None if point.smooth: if typ == "curve": bType = "curve" elif typ == "line" or typ == "move": nextSegment = self._nextSegment if nextSegment is not None and nextSegment.type == "curve": bType = "curve" else: bType = "corner" elif typ in ("move", "line", "curve"): bType = "corner" if bType is None: raise FontPartsError("A %s point can not be converted to a bPoint." % typ) return bType
[docs] def _set_type(self, value): """ Subclasses may override this method. """ point = self._point # convert corner to curve if value == "curve" and point.type == "line": # This needs to insert off curves without # generating unnecessary points in the # following segment. The segment object # implements this logic, so delegate the # change to the corresponding segment. segment = self._segment segment.type = "curve" segment.smooth = True # convert curve to corner elif value == "corner" and point.type == "curve": point.smooth = False
# -------------- # Identification # -------------- index = dynamicProperty("index", ("The index of the bPoint within the ordered " "list of the parent contour's bPoints. None " "if the bPoint does not belong to a contour.") ) def _get_base_index(self): if self.contour is None: return None value = self._get_index() value = normalizers.normalizeIndex(value) return value
[docs] def _get_index(self): """ Subclasses may override this method. """ contour = self.contour value = contour.bPoints.index(self) return value
# -------------- # Transformation # --------------
[docs] def _transformBy(self, matrix, **kwargs): """ Subclasses may override this method. """ anchor = self.anchor bcpIn = absoluteBCPIn(anchor, self.bcpIn) bcpOut = absoluteBCPOut(anchor, self.bcpOut) points = [bcpIn, anchor, bcpOut] t = transform.Transform(*matrix) bcpIn, anchor, bcpOut = t.transformPoints(points) x, y = anchor self._point.x = x self._point.y = y self.bcpIn = relativeBCPIn(anchor, bcpIn) self.bcpOut = relativeBCPOut(anchor, bcpOut)
# ---- # Misc # ----
[docs] def round(self): """ Round coordinates. """ x, y = self.anchor self.anchor = (normalizers.normalizeVisualRounding(x), normalizers.normalizeVisualRounding(y)) x, y = self.bcpIn self.bcpIn = (normalizers.normalizeVisualRounding(x), normalizers.normalizeVisualRounding(y)) x, y = self.bcpOut self.bcpOut = (normalizers.normalizeVisualRounding(x), normalizers.normalizeVisualRounding(y))
def relativeBCPIn(anchor, BCPIn): """convert absolute incoming bcp value to a relative value""" return (BCPIn[0] - anchor[0], BCPIn[1] - anchor[1]) def absoluteBCPIn(anchor, BCPIn): """convert relative incoming bcp value to an absolute value""" return (BCPIn[0] + anchor[0], BCPIn[1] + anchor[1]) def relativeBCPOut(anchor, BCPOut): """convert absolute outgoing bcp value to a relative value""" return (BCPOut[0] - anchor[0], BCPOut[1] - anchor[1]) def absoluteBCPOut(anchor, BCPOut): """convert relative outgoing bcp value to an absolute value""" return (BCPOut[0] + anchor[0], BCPOut[1] + anchor[1])