import math
from fontTools.misc import transform
from fontParts.base.base import (
BaseObject,
TransformationMixin,
InterpolationMixin,
SelectionMixin,
PointPositionMixin,
IdentifierMixin,
dynamicProperty,
reference
)
from fontParts.base import normalizers
from fontParts.base.compatibility import GuidelineCompatibilityReporter
from fontParts.base.color import Color
from fontParts.base.deprecated import DeprecatedGuideline, RemovedGuideline
[docs]class BaseGuideline(
BaseObject,
TransformationMixin,
DeprecatedGuideline,
RemovedGuideline,
PointPositionMixin,
InterpolationMixin,
IdentifierMixin,
SelectionMixin
):
"""
A guideline object. This object is almost always
created with :meth:`BaseGlyph.appendGuideline`.
An orphan guideline can be created like this::
>>> guideline = RGuideline()
"""
copyAttributes = (
"x",
"y",
"angle",
"name",
"color"
)
def _reprContents(self):
contents = []
if self.name is not None:
contents.append("'%s'" % self.name)
if self.layer is not None:
contents.append("('%s')" % self.layer.name)
return contents
# -------
# Parents
# -------
# Glyph
_glyph = None
glyph = dynamicProperty("glyph", "The guideline's parent :class:`BaseGlyph`.")
def _get_glyph(self):
if self._glyph is None:
return None
return self._glyph()
def _set_glyph(self, glyph):
if self._font is not None:
raise AssertionError("font for guideline already set")
if self._glyph is not None:
raise AssertionError("glyph for guideline already set")
if glyph is not None:
glyph = reference(glyph)
self._glyph = glyph
# Layer
layer = dynamicProperty("layer", "The guideline's parent :class:`BaseLayer`.")
def _get_layer(self):
if self._glyph is None:
return None
return self.glyph.layer
# Font
_font = None
font = dynamicProperty("font", "The guideline's parent :class:`BaseFont`.")
def _get_font(self):
if self._font is not None:
return self._font()
elif self._glyph is not None:
return self.glyph.font
return None
def _set_font(self, font):
if self._font is not None:
raise AssertionError("font for guideline already set")
if self._glyph is not None:
raise AssertionError("glyph for guideline already set")
if font is not None:
font = reference(font)
self._font = font
# --------
# Position
# --------
# x
x = dynamicProperty(
"base_x",
"""
The x coordinate of the guideline.
It must be an :ref:`type-int-float`. ::
>>> guideline.x
100
>>> guideline.x = 101
"""
)
def _get_base_x(self):
value = self._get_x()
if value is None:
return 0
value = normalizers.normalizeX(value)
return value
def _set_base_x(self, value):
if value is None:
value = 0
else:
value = normalizers.normalizeX(value)
self._set_x(value)
[docs] def _get_x(self):
"""
This is the environment implementation of
:attr:`BaseGuideline.x`. This must return an
:ref:`type-int-float`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def _set_x(self, value):
"""
This is the environment implementation of
:attr:`BaseGuideline.x`. **value** will be
an :ref:`type-int-float`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# y
y = dynamicProperty(
"base_y",
"""
The y coordinate of the guideline.
It must be an :ref:`type-int-float`. ::
>>> guideline.y
100
>>> guideline.y = 101
"""
)
def _get_base_y(self):
value = self._get_y()
if value is None:
return 0
value = normalizers.normalizeY(value)
return value
def _set_base_y(self, value):
if value is None:
value = 0
else:
value = normalizers.normalizeY(value)
self._set_y(value)
[docs] def _get_y(self):
"""
This is the environment implementation of
:attr:`BaseGuideline.y`. This must return an
:ref:`type-int-float`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def _set_y(self, value):
"""
This is the environment implementation of
:attr:`BaseGuideline.y`. **value** will be
an :ref:`type-int-float`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# angle
angle = dynamicProperty(
"base_angle",
"""
The angle of the guideline.
It must be an :ref:`type-angle`.
Please check how :func:`normalizers.normalizeRotationAngle`
handles the angle. There is a special case, when angle is ``None``.
If so, when x and y are not 0, the angle will be 0. If x is 0 but y
is not, the angle will be 0. If y is 0 and x is not, the
angle will be 90. If both x and y are 0, the angle will be 0.
::
>>> guideline.angle
45.0
>>> guideline.angle = 90
"""
)
def _get_base_angle(self):
value = self._get_angle()
if value is None:
if self._get_x() != 0 and self._get_y() != 0:
value = 0
elif self._get_x() != 0 and self._get_y() == 0:
value = 90
elif self._get_x() == 0 and self._get_y() != 0:
value = 0
else:
value = 0
value = normalizers.normalizeRotationAngle(value)
return value
def _set_base_angle(self, value):
if value is None:
if self._get_x() != 0 and self._get_y() != 0:
value = 0
elif self._get_x() != 0 and self._get_y() == 0:
value = 90
elif self._get_x() == 0 and self._get_y() != 0:
value = 0
else:
value = 0
value = normalizers.normalizeRotationAngle(value)
self._set_angle(value)
[docs] def _get_angle(self):
"""
This is the environment implementation of
:attr:`BaseGuideline.angle`. This must return an
:ref:`type-angle`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def _set_angle(self, value):
"""
This is the environment implementation of
:attr:`BaseGuideline.angle`. **value** will be
an :ref:`type-angle`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# --------------
# Identification
# --------------
# index
index = dynamicProperty(
"base_index",
"""
The index of the guideline within the ordered
list of the parent glyph's guidelines. This
attribute is read only. ::
>>> guideline.index
0
"""
)
def _get_base_index(self):
value = self._get_index()
value = normalizers.normalizeIndex(value)
return value
[docs] def _get_index(self):
"""
Get the guideline's index.
This must return an ``int``.
Subclasses may override this method.
"""
glyph = self.glyph
if glyph is not None:
parent = glyph
else:
parent = self.font
if parent is None:
return None
return parent.guidelines.index(self)
# name
name = dynamicProperty(
"base_name",
"""
The name of the guideline. This will be a
:ref:`type-string` or ``None``.
>>> guideline.name
'my guideline'
>>> guideline.name = None
"""
)
def _get_base_name(self):
value = self._get_name()
if value is not None:
value = normalizers.normalizeGuidelineName(value)
return value
def _set_base_name(self, value):
if value is not None:
value = normalizers.normalizeGuidelineName(value)
self._set_name(value)
[docs] def _get_name(self):
"""
This is the environment implementation of
:attr:`BaseGuideline.name`. This must return a
:ref:`type-string` or ``None``. The returned
value will be normalized with
:func:`normalizers.normalizeGuidelineName`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def _set_name(self, value):
"""
This is the environment implementation of
:attr:`BaseGuideline.name`. **value** will be
a :ref:`type-string` or ``None``. It will
have been normalized with
:func:`normalizers.normalizeGuidelineName`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# color
color = dynamicProperty(
"base_color",
""""
The guideline's color. This will be a
:ref:`type-color` or ``None``. ::
>>> guideline.color
None
>>> guideline.color = (1, 0, 0, 0.5)
"""
)
def _get_base_color(self):
value = self._get_color()
if value is not None:
value = normalizers.normalizeColor(value)
value = Color(value)
return value
def _set_base_color(self, value):
if value is not None:
value = normalizers.normalizeColor(value)
self._set_color(value)
[docs] def _get_color(self):
"""
This is the environment implementation of
:attr:`BaseGuideline.color`. This must return
a :ref:`type-color` or ``None``. The
returned value will be normalized with
:func:`normalizers.normalizeColor`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def _set_color(self, value):
"""
This is the environment implementation of
:attr:`BaseGuideline.color`. **value** will
be a :ref:`type-color` or ``None``.
It will have been normalized with
:func:`normalizers.normalizeColor`.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# --------------
# Transformation
# --------------
# -------------
# Interpolation
# -------------
compatibilityReporterClass = GuidelineCompatibilityReporter
def isCompatible(self, other):
"""
Evaluate interpolation compatibility with **other**. ::
>>> compatible, report = self.isCompatible(otherGuideline)
>>> compatible
True
>>> compatible
[Warning] Guideline: "xheight" + "cap_height"
[Warning] Guideline: "xheight" has name xheight | "cap_height" has
name cap_height
This will return a ``bool`` indicating if the guideline is
compatible for interpolation with **other** and a
:ref:`type-string` of compatibility notes.
"""
return super(BaseGuideline, self).isCompatible(other, BaseGuideline)
def _isCompatible(self, other, reporter):
"""
This is the environment implementation of
:meth:`BaseGuideline.isCompatible`.
Subclasses may override this method.
"""
guideline1 = self
guideline2 = other
# guideline names
if guideline1.name != guideline2.name:
reporter.nameDifference = True
reporter.warning = True
# -------------
# Normalization
# -------------
[docs] def round(self):
"""
Round the guideline's coordinate.
>>> guideline.round()
This applies to the following:
* x
* y
It does not apply to
* angle
"""
self._round()
[docs] def _round(self, **kwargs):
"""
This is the environment implementation of
:meth:`BaseGuideline.round`.
Subclasses may override this method.
"""
self.x = normalizers.normalizeVisualRounding(self.x)
self.y = normalizers.normalizeVisualRounding(self.y)