from fontParts.base.errors import FontPartsError
from fontParts.base.base import (
BaseObject,
TransformationMixin,
InterpolationMixin,
SelectionMixin,
dynamicProperty,
reference
)
from fontParts.base import normalizers
from fontParts.base.deprecated import DeprecatedSegment, RemovedSegment
from fontParts.base.compatibility import SegmentCompatibilityReporter
[docs]
class BaseSegment(
BaseObject,
TransformationMixin,
InterpolationMixin,
SelectionMixin,
DeprecatedSegment,
RemovedSegment
):
def _setPoints(self, points):
if hasattr(self, "_points"):
raise AssertionError("segment has points")
self._points = points
def _reprContents(self):
contents = [
"%s" % self.type,
]
if self.index is not None:
contents.append("index='%r'" % self.index)
return contents
# this class should not be used in hashable
# collections since it is dynamically generated.
__hash__ = None
# -------
# Parents
# -------
# Contour
_contour = None
contour = dynamicProperty("contour", "The segment'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 segment already set")
if contour is not None:
contour = reference(contour)
self._contour = contour
# Glyph
glyph = dynamicProperty("glyph", "The segment's parent glyph.")
def _get_glyph(self):
if self._contour is None:
return None
return self.contour.glyph
# Layer
layer = dynamicProperty("layer", "The segment's parent layer.")
def _get_layer(self):
if self._contour is None:
return None
return self.glyph.layer
# Font
font = dynamicProperty("font", "The segment's parent font.")
def _get_font(self):
if self._contour is None:
return None
return self.glyph.font
# --------
# Equality
# --------
def __eq__(self, other):
"""
The :meth:`BaseObject.__eq__` method can't be used here
because the :class:`BaseContour` implementation contructs
segment objects without assigning an underlying ``naked``
object. Therefore, comparisons will always fail. This
method overrides the base method and compares the
:class:`BasePoint` contained by the segment.
Subclasses may override this method.
"""
if isinstance(other, self.__class__):
return self.points == other.points
return NotImplemented
# --------------
# Identification
# --------------
index = dynamicProperty("base_index",
("The index of the segment within the ordered "
"list of the parent contour's segments.")
)
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.segments.index(self)
return value
# ----------
# Attributes
# ----------
type = dynamicProperty("base_type",
("The segment type. The possible types are "
"move, line, curve, qcurve.")
)
def _get_base_type(self):
value = self._get_type()
value = normalizers.normalizeSegmentType(value)
return value
def _set_base_type(self, value):
value = normalizers.normalizeSegmentType(value)
self._set_type(value)
[docs]
def _get_type(self):
"""
Subclasses may override this method.
"""
onCurve = self.onCurve
if onCurve is None:
return "qcurve"
return onCurve.type
[docs]
def _set_type(self, newType):
"""
Subclasses may override this method.
"""
oldType = self.type
if oldType == newType:
return
if self.onCurve is None:
# special case with a single qcurve segment
# and only offcurves, don't convert
return
contour = self.contour
if contour is None:
raise FontPartsError("The segment does not belong to a contour.")
# converting line <-> move
if newType in ("move", "line") and oldType in ("move", "line"):
pass
# converting to a move or line
elif newType not in ("curve", "qcurve"):
offCurves = self.offCurve
for point in offCurves:
contour.removePoint(point)
# converting a line/move to a curve/qcurve
else:
segments = contour.segments
i = segments.index(self)
prev = segments[i - 1].onCurve
on = self.onCurve
x = on.x
y = on.y
points = contour.points
i = points.index(on)
contour.insertPoint(i, (x, y), "offcurve")
off2 = contour.points[i]
contour.insertPoint(i, (prev.x, prev.y), "offcurve")
off1 = contour.points[i]
del self._points
self._setPoints((off1, off2, on))
self.onCurve.type = newType
smooth = dynamicProperty("base_smooth",
("Boolean indicating if the segment is "
"smooth or not.")
)
def _get_base_smooth(self):
value = self._get_smooth()
value = normalizers.normalizeBoolean(value)
return value
def _set_base_smooth(self, value):
value = normalizers.normalizeBoolean(value)
self._set_smooth(value)
[docs]
def _get_smooth(self):
"""
Subclasses may override this method.
"""
onCurve = self.onCurve
if onCurve is None:
return True
return onCurve.smooth
[docs]
def _set_smooth(self, value):
"""
Subclasses may override this method.
"""
onCurve = self.onCurve
if onCurve is not None:
self.onCurve.smooth = value
# ------
# Points
# ------
def __getitem__(self, index):
return self._getItem(index)
[docs]
def _getItem(self, index):
"""
Subclasses may override this method.
"""
return self.points[index]
def __iter__(self):
return self._iterPoints()
[docs]
def _iterPoints(self, **kwargs):
"""
Subclasses may override this method.
"""
points = self.points
count = len(points)
index = 0
while count:
yield points[index]
count -= 1
index += 1
def __len__(self):
return self._len()
[docs]
def _len(self, **kwargs):
"""
Subclasses may override this method.
"""
return len(self.points)
points = dynamicProperty("base_points",
"A list of points in the segment.")
def _get_base_points(self):
return tuple(self._get_points())
[docs]
def _get_points(self):
"""
Subclasses may override this method.
"""
if not hasattr(self, "_points"):
return tuple()
return tuple(self._points)
onCurve = dynamicProperty("base_onCurve",
"The on curve point in the segment.")
def _get_base_onCurve(self):
return self._get_onCurve()
[docs]
def _get_onCurve(self):
"""
Subclasses may override this method.
"""
value = self.points[-1]
if value.type == "offcurve":
return None
return value
offCurve = dynamicProperty("base_offCurve",
"The off curve points in the segment.")
[docs]
def _get_base_offCurve(self):
"""
Subclasses may override this method.
"""
return self._get_offCurve()
[docs]
def _get_offCurve(self):
"""
Subclasses may override this method.
"""
if self.points and self.points[-1].type == "offcurve":
return self.points
return self.points[:-1]
# --------------
# Transformation
# --------------
# -------------
# Interpolation
# -------------
compatibilityReporterClass = SegmentCompatibilityReporter
def isCompatible(self, other):
"""
Evaluate interpolation compatibility with **other**. ::
>>> compatible, report = self.isCompatible(otherSegment)
>>> compatible
False
>>> compatible
[Fatal] Segment: [0] + [0]
[Fatal] Segment: [0] is line | [0] is move
[Fatal] Segment: [1] + [1]
[Fatal] Segment: [1] is line | [1] is qcurve
This will return a ``bool`` indicating if the segment is
compatible for interpolation with **other** and a
:ref:`type-string` of compatibility notes.
"""
return super(BaseSegment, self).isCompatible(other, BaseSegment)
def _isCompatible(self, other, reporter):
"""
This is the environment implementation of
:meth:`BaseSegment.isCompatible`.
Subclasses may override this method.
"""
segment1 = self
segment2 = other
# type
if segment1.type != segment2.type:
# line <-> curve can be converted
if set((segment1.type, segment2.type)) != set(("curve", "line")):
reporter.typeDifference = True
reporter.fatal = True
# ----
# Misc
# ----
[docs]
def round(self):
"""
Round coordinates in all points.
"""
for point in self.points:
point.round()