from __future__ import annotations
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
from fontTools.misc import transform
from fontParts.base import normalizers
from fontParts.base.annotations import AffineTransformationLike, IntFloatType
from fontParts.base.base import (
BaseObject,
IdentifierMixin,
PointPositionMixin,
SelectionMixin,
TransformationMixin,
dynamicProperty,
reference,
)
from fontParts.base.deprecated import DeprecatedPoint, RemovedPoint
if TYPE_CHECKING:
from fontParts.base.contour import BaseContour
from fontParts.base.font import BaseFont
from fontParts.base.glyph import BaseGlyph
from fontParts.base.lib import BaseLib
[docs]
class BasePoint(
BaseObject,
TransformationMixin,
PointPositionMixin,
SelectionMixin,
IdentifierMixin,
DeprecatedPoint,
RemovedPoint,
):
"""Represent the basis for a point object.
This object is almost always created with :meth:`BaseContour.appendPoint`,
the pen returned by :meth:`BaseGlyph.getPen` or the point pen returned
by :meth:`BaseGlyph.getPointPen`.
An orphan point can be created like this::
>>> point = RPoint()
"""
copyAttributes: tuple[str, str, str, str, str] = (
"type",
"smooth",
"x",
"y",
"name",
)
def _reprContents(self) -> list[str]:
contents = [f"{self.type}", f"({self.x}, {self.y})"]
if self.name is not None:
contents.append(f"name='{self.name}'")
if self.smooth:
contents.append(f"smooth={self.smooth!r}")
return contents
# -------
# Parents
# -------
# Contour
_contour: Callable[[], BaseContour] | None = None
contour: dynamicProperty = dynamicProperty(
"contour",
"""Get or set the point's parent contour object.
The value must be a :class:`BaseContour` instance or :obj:`None`.
:return: The :class:`BaseContour` instance containing the point
or :obj:`None`.
:raises AssertionError: If attempting to set the contour when it
has already been set.
Example::
>>> contour = point.contour
""",
)
def _get_contour(self) -> BaseContour | None:
if self._contour is None:
return None
return self._contour()
def _set_contour(
self, contour: BaseContour | Callable[[], BaseContour] | None
) -> None:
if self._contour is not None:
raise AssertionError("contour for point already set")
if contour is not None:
contour = reference(contour)
self._contour = contour
# Glyph
glyph: dynamicProperty = dynamicProperty(
"glyph",
"""Get the point's parent glyph object.
This property is read-only.
The value must be a :class:`BaseGlyph` instance or :obj:`None`.
:return: The :class:`BaseGlyph` instance containing the point
or :obj:`None`.
Example::
>>> glyph = point.glyph
""",
)
def _get_glyph(self) -> BaseObject | None:
if self._contour is None:
return None
return self.contour.glyph
# Layer
layer: dynamicProperty = dynamicProperty(
"layer",
"""Get the point's parent layer object.
This property is read-only.
:return: The :class:`BaseLayer` instance containing the point
or :obj:`None`.
Example::
>>> layer = point.layer
""",
)
def _get_layer(self) -> BaseObject | None:
if self._contour is None:
return None
return self.glyph.layer
# Font
font: dynamicProperty = dynamicProperty(
"font",
"""Get the point's parent font object.
This property is read-only.
:return: The :class:`BaseFont` instance containing the point
or :obj:`None`.
Example::
>>> font = point.font
""",
)
def _get_font(self) -> BaseObject | None:
if self._contour is None:
return None
return self.glyph.font
# ----------
# Attributes
# ----------
# type
type: dynamicProperty = dynamicProperty(
"base_type",
"""Get or set the point's type.
The value must be a :class:`str` containing one of the following
alternatives:
+----------------+---------------------------------+
| Type | Description |
+----------------+---------------------------------+
| ``'move'`` | An on-curve move to. |
| ``'line'`` | An on-curve line to. |
| ``'curve'`` | An on-curve cubic curve to. |
| ``'qcurve'`` | An on-curve quadratic curve to. |
| ``'offcurve'`` | An off-curve. |
+----------------+---------------------------------+
:return: A :class:`str` representing the type of the point.
""",
)
def _get_base_type(self) -> str:
value = self._get_type()
value = normalizers.normalizePointType(value)
return value
def _set_base_type(self, value: str) -> None:
value = normalizers.normalizePointType(value)
self._set_type(value)
[docs]
def _get_type(self) -> str: # type: ignore[return]
"""Get the native point's type.
This is the environment implementation of the :attr:`BasePoint.type`
property getter.
:return: A :class:`str` representing the type of the point. The value
will be normalized with :func:`normalizers.normalizePointType`.
:raises NotImplementedError: If the method has not been overridden by
a subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs]
def _set_type(self, value: str) -> None:
"""Set the native point's type.
Description
This is the environment implementation of the :attr:`BasePoint.type`
property setter.
:param value: The point type definition as a :class:`str`. The value
will have been normalized with :func:`normalizers.normalizePointType`.
:raises NotImplementedError: If the method has not been overridden by
a subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# smooth
smooth: dynamicProperty = dynamicProperty(
"base_smooth",
"""Get or set the point's smooth state.
The value must be a :class:`bool` indicating the point's smooth state.
:return: :obj:`True` if the point is smooth, :obj:`False` if it is sharp.
Example::
>>> point.smooth
False
>>> point.smooth = True
""",
)
def _get_base_smooth(self) -> bool:
value = self._get_smooth()
value = normalizers.normalizeBoolean(value)
return value
def _set_base_smooth(self, value: bool) -> None:
value = normalizers.normalizeBoolean(value)
self._set_smooth(value)
[docs]
def _get_smooth(self) -> bool: # type: ignore[return]
"""Get the native point's smooth state.
This is the environment implementation of the :attr:`BasePoint.smooth`
property getter.
:return: A :class:`bool` indicating the point's smooth state. The value
will be normalized with :func:`normalizers.normalizeBoolean`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs]
def _set_smooth(self, value: bool) -> None:
"""Set the native point's smooth state.
This is the environment implementation of the :attr:`BasePoint.smooth`
property setter.
:param value: The point's smooth state as a :class:`bool`. The value
will have been normalized with :func:`normalizers.normalizeBoolean`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# x
x: dynamicProperty = dynamicProperty(
"base_x",
"""Get or set the x coordinate of the point.
The value must be an :class:`int` or a :class:`float`.
:return: An :class:`int` or a :class:`float` representing the point's
x coordinate.
Example::
>>> point.x
100
>>> point.x = 101
""",
)
def _get_base_x(self) -> IntFloatType:
value = self._get_x()
value = normalizers.normalizeX(value)
return value
def _set_base_x(self, value: IntFloatType) -> None:
value = normalizers.normalizeX(value)
self._set_x(value)
[docs]
def _get_x(self) -> IntFloatType: # type: ignore[return]
"""Get the x coordinate of the native point.
Description
This is the environment implementation of the :attr:`BasePoint.x`
property getter.
:return: An :class:`int` or a :class:`float` representing the point's
x coordinate. The value will be normalized with
:func:`normalizers.normalizeX`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs]
def _set_x(self, value: IntFloatType) -> None:
"""Set the x coordinate of the native point.
This is the environment implementation of the :attr:`BasePoint.x`
property setter.
:param value: The point's x coodinate to set as an :class:`int`
or :class:`float`. The value will have been normalized
with :func:`normalizers.normalizeX`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# y
y: dynamicProperty = dynamicProperty(
"base_y",
"""Get or set the y coordinate of the point.
The value must be an :class:`int` or a :class:`float`.
:return: An :class:`int` or a :class:`float` representing the point's
y coordinate.
Example::
>>> point.y
100
>>> point.y = 101
""",
)
def _get_base_y(self) -> IntFloatType:
value = self._get_y()
value = normalizers.normalizeY(value)
return value
def _set_base_y(self, value: IntFloatType) -> None:
value = normalizers.normalizeY(value)
self._set_y(value)
[docs]
def _get_y(self) -> IntFloatType: # type: ignore[return]
"""Get the y coordinate of the native point.
This is the environment implementation of the :attr:`BasePoint.y`
property getter.
:return: An :class:`int` or a :class:`float` representing the point's
y coordinate. The value will be normalized
with :func:`normalizers.normalizeY`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs]
def _set_y(self, value: IntFloatType) -> None:
"""Set the y coordinate of the native point.
This is the environment implementation of the :attr:`BasePoint.y`
property setter.
:param value: The point's y coordinate as an :class:`int`
or :class:`float`. The value will have been normalized
with :func:`normalizers.normalizeY`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# --------------
# Identification
# --------------
# index
index: dynamicProperty = dynamicProperty(
"base_index",
"""Get the index of the point.
This property is read-only.
:return: An :class:`int` representing the point's index within an
ordered list of the parent contour's points, or :obj:`None` if the
point does not belong to a contour.
Example::
>>> point.index
0
""",
)
def _get_base_index(self) -> int | None:
value = self._get_index()
value = normalizers.normalizeIndex(value)
return value
[docs]
def _get_index(self) -> int | None:
"""Get the index of the native point.
This is the environment implementation of the :attr:`BasePoint.index`
property getter.
:return: An :class:`int` representing the point's index within an
ordered list of the parent contour's points, or :obj:`None` if the
point does not belong to a contour. The value will be
normalized with :func:`normalizers.normalizeIndex`.
.. note::
Subclasses may override this method.
"""
contour = self.contour
if contour is None:
return None
return contour.points.index(self)
# name
name: dynamicProperty = dynamicProperty(
"base_name",
"""Get or set the name of the point.
The value must be a :class:`str` or :obj:`None`.
:return: A :class:`str` representing the point's name or :obj:`None`.
Example::
>>> point.name
'my point'
>>> point.name = None
""",
)
def _get_base_name(self) -> str | None:
value = self._get_name()
if value is not None:
value = normalizers.normalizePointName(value)
return value
def _set_base_name(self, value: str) -> None:
if value is not None:
value = normalizers.normalizePointName(value)
self._set_name(value)
[docs]
def _get_name(self) -> str | None: # type: ignore[return]
"""Get the name of the native point.
This is the environment implementation of the :attr:`BasePoint.name`
property getter.
:return: A :class:`str` representing the point's name or :obj:`None`.
The value will be normalized with :func:`normalizers.normalizePointName`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs]
def _set_name(self, value: str) -> None:
"""Set the name of the native point.
This is the environment implementation of the :attr:`BasePoint.name`
property setter.
:param value: The point name as a :class:`str`. The value
will have been normalized with :func:`normalizers.normalizePointName`.
:raises NotImplementedError: If the method has not been overridden by a
subclass.
.. important::
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# --------------
# Transformation
# --------------
# -------------
# Normalization
# -------------
[docs]
def round(self) -> None:
"""Round the point's coordinates.
This applies to:
- :attr:`x`
- :attr:`y`
Example::
>>> point.round()
"""
self._round()
[docs]
def _round(self, **kwargs: Any) -> None:
r"""Round the native point's coordinates.
This is the environment implementation of :meth:`BasePoint.round`.
:param \**kwargs: Additional keyword arguments.
.. note::
Subclasses may override this method.
"""
self.x = normalizers.normalizeVisualRounding(self.x)
self.y = normalizers.normalizeVisualRounding(self.y)