don't keep track of y-mirroring separately from x
This commit is contained in:
parent
9bc8d29b85
commit
91465b7175
20 changed files with 190 additions and 213 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Sequence, Any
|
||||
from typing import Any
|
||||
import copy
|
||||
import math
|
||||
|
||||
|
|
@ -155,7 +155,6 @@ class Arc(Shape):
|
|||
*,
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t | None = None,
|
||||
raw: bool = False,
|
||||
|
|
@ -179,7 +178,6 @@ class Arc(Shape):
|
|||
self.rotation = rotation
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Arc':
|
||||
memo = {} if memo is None else memo
|
||||
|
|
@ -315,7 +313,7 @@ class Arc(Shape):
|
|||
self.rotation += theta
|
||||
return self
|
||||
|
||||
def mirror(self, axis: int) -> 'Arc':
|
||||
def mirror(self, axis: int = 0) -> 'Arc':
|
||||
self.offset[axis - 1] *= -1
|
||||
self.rotation *= -1
|
||||
self.rotation += axis * pi
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class Circle(Shape):
|
|||
def rotate(self, theta: float) -> 'Circle':
|
||||
return self
|
||||
|
||||
def mirror(self, axis: int) -> 'Circle':
|
||||
def mirror(self, axis: int = 0) -> 'Circle':
|
||||
self.offset *= -1
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Sequence, Any
|
||||
from typing import Any, Self
|
||||
import copy
|
||||
import math
|
||||
|
||||
|
|
@ -90,7 +90,6 @@ class Ellipse(Shape):
|
|||
*,
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t | None = None,
|
||||
raw: bool = False,
|
||||
|
|
@ -109,9 +108,8 @@ class Ellipse(Shape):
|
|||
self.rotation = rotation
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Ellipse':
|
||||
def __deepcopy__(self, memo: dict | None = None) -> Self:
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
new._offset = self._offset.copy()
|
||||
|
|
@ -157,17 +155,17 @@ class Ellipse(Shape):
|
|||
return numpy.vstack((self.offset - rot_radii[0],
|
||||
self.offset + rot_radii[1]))
|
||||
|
||||
def rotate(self, theta: float) -> 'Ellipse':
|
||||
def rotate(self, theta: float) -> Self:
|
||||
self.rotation += theta
|
||||
return self
|
||||
|
||||
def mirror(self, axis: int) -> 'Ellipse':
|
||||
def mirror(self, axis: int = 0) -> Self:
|
||||
self.offset[axis - 1] *= -1
|
||||
self.rotation *= -1
|
||||
self.rotation += axis * pi
|
||||
return self
|
||||
|
||||
def scale_by(self, c: float) -> 'Ellipse':
|
||||
def scale_by(self, c: float) -> Self:
|
||||
self.radii *= c
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ class Path(Shape):
|
|||
cap_extensions: ArrayLike | None = None,
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t | None = None,
|
||||
raw: bool = False,
|
||||
|
|
@ -180,7 +179,6 @@ class Path(Shape):
|
|||
self.cap = cap
|
||||
self.cap_extensions = cap_extensions
|
||||
self.rotate(rotation)
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Path':
|
||||
memo = {} if memo is None else memo
|
||||
|
|
@ -200,7 +198,6 @@ class Path(Shape):
|
|||
cap_extensions: tuple[float, float] | None = None,
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
) -> 'Path':
|
||||
"""
|
||||
Build a path by specifying the turn angles and travel distances
|
||||
|
|
@ -217,9 +214,6 @@ class Path(Shape):
|
|||
Default `(0, 0)` or `None`, depending on cap type
|
||||
offset: Offset, default `(0, 0)`
|
||||
rotation: Rotation counterclockwise, in radians. Default `0`
|
||||
mirrored: Whether to mirror across the x or y axes. For example,
|
||||
`mirrored=(True, False)` results in a reflection across the x-axis,
|
||||
multiplying the path's y-coordinates by -1. Default `(False, False)`
|
||||
|
||||
Returns:
|
||||
The resulting Path object
|
||||
|
|
@ -233,7 +227,7 @@ class Path(Shape):
|
|||
verts.append(verts[-1] + direction * distance)
|
||||
|
||||
return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
|
||||
offset=offset, rotation=rotation, mirrored=mirrored)
|
||||
offset=offset, rotation=rotation)
|
||||
|
||||
def to_polygons(
|
||||
self,
|
||||
|
|
@ -334,7 +328,7 @@ class Path(Shape):
|
|||
self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
|
||||
return self
|
||||
|
||||
def mirror(self, axis: int) -> 'Path':
|
||||
def mirror(self, axis: int = 0) -> 'Path':
|
||||
self.vertices[:, axis - 1] *= -1
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@ class Polygon(Shape):
|
|||
*,
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: Sequence[bool] = (False, False),
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t | None = None,
|
||||
raw: bool = False,
|
||||
|
|
@ -99,7 +98,6 @@ class Polygon(Shape):
|
|||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
self.rotate(rotation)
|
||||
[self.mirror(a) for a, do in enumerate(mirrored) if do]
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Polygon':
|
||||
memo = {} if memo is None else memo
|
||||
|
|
@ -336,7 +334,7 @@ class Polygon(Shape):
|
|||
self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
|
||||
return self
|
||||
|
||||
def mirror(self, axis: int) -> 'Polygon':
|
||||
def mirror(self, axis: int = 0) -> 'Polygon':
|
||||
self.vertices[:, axis - 1] *= -1
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Sequence, Any
|
||||
from typing import Self
|
||||
import copy
|
||||
|
||||
import numpy
|
||||
|
|
@ -9,8 +9,7 @@ from . import Shape, Polygon, normalized_shape_tuple
|
|||
from ..error import PatternError
|
||||
from ..repetition import Repetition
|
||||
from ..traits import RotatableImpl
|
||||
from ..utils import is_scalar, get_bit, normalize_mirror
|
||||
from ..utils import annotations_t
|
||||
from ..utils import is_scalar, get_bit, annotations_t
|
||||
|
||||
# Loaded on use:
|
||||
# from freetype import Face
|
||||
|
|
@ -30,7 +29,7 @@ class Text(RotatableImpl, Shape):
|
|||
|
||||
_string: str
|
||||
_height: float
|
||||
_mirrored: NDArray[numpy.bool_]
|
||||
_mirrored: bool
|
||||
font_path: str
|
||||
|
||||
# vertices property
|
||||
|
|
@ -53,16 +52,13 @@ class Text(RotatableImpl, Shape):
|
|||
raise PatternError('Height must be a scalar')
|
||||
self._height = val
|
||||
|
||||
# Mirrored property
|
||||
@property
|
||||
def mirrored(self) -> Any: # TODO mypy#3004 NDArray[numpy.bool_]:
|
||||
def mirrored(self) -> bool: # mypy#3004, should be bool
|
||||
return self._mirrored
|
||||
|
||||
@mirrored.setter
|
||||
def mirrored(self, val: Sequence[bool]) -> None:
|
||||
if is_scalar(val):
|
||||
raise PatternError('Mirrored must be a 2-element list of booleans')
|
||||
self._mirrored = numpy.array(val, dtype=bool, copy=True)
|
||||
def mirrored(self, val: bool) -> None:
|
||||
self._mirrored = bool(val)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -72,19 +68,16 @@ class Text(RotatableImpl, Shape):
|
|||
*,
|
||||
offset: ArrayLike = (0.0, 0.0),
|
||||
rotation: float = 0.0,
|
||||
mirrored: ArrayLike = (False, False),
|
||||
repetition: Repetition | None = None,
|
||||
annotations: annotations_t | None = None,
|
||||
raw: bool = False,
|
||||
) -> None:
|
||||
if raw:
|
||||
assert isinstance(offset, numpy.ndarray)
|
||||
assert isinstance(mirrored, numpy.ndarray)
|
||||
self._offset = offset
|
||||
self._string = string
|
||||
self._height = height
|
||||
self._rotation = rotation
|
||||
self._mirrored = mirrored
|
||||
self._repetition = repetition
|
||||
self._annotations = annotations if annotations is not None else {}
|
||||
else:
|
||||
|
|
@ -92,16 +85,14 @@ class Text(RotatableImpl, Shape):
|
|||
self.string = string
|
||||
self.height = height
|
||||
self.rotation = rotation
|
||||
self.mirrored = mirrored
|
||||
self.repetition = repetition
|
||||
self.annotations = annotations if annotations is not None else {}
|
||||
self.font_path = font_path
|
||||
|
||||
def __deepcopy__(self, memo: dict | None = None) -> 'Text':
|
||||
def __deepcopy__(self, memo: dict | None = None) -> Self:
|
||||
memo = {} if memo is None else memo
|
||||
new = copy.copy(self)
|
||||
new._offset = self._offset.copy()
|
||||
new._mirrored = copy.deepcopy(self._mirrored, memo)
|
||||
new._annotations = copy.deepcopy(self._annotations)
|
||||
return new
|
||||
|
||||
|
|
@ -118,7 +109,8 @@ class Text(RotatableImpl, Shape):
|
|||
# Move these polygons to the right of the previous letter
|
||||
for xys in raw_polys:
|
||||
poly = Polygon(xys)
|
||||
poly.mirror2d(self.mirrored)
|
||||
if self.mirrored:
|
||||
poly.mirror()
|
||||
poly.scale_by(self.height)
|
||||
poly.offset = self.offset + [total_advance, 0]
|
||||
poly.rotate_around(self.offset, self.rotation)
|
||||
|
|
@ -129,27 +121,27 @@ class Text(RotatableImpl, Shape):
|
|||
|
||||
return all_polygons
|
||||
|
||||
def mirror(self, axis: int) -> 'Text':
|
||||
self.mirrored[axis] = not self.mirrored[axis]
|
||||
def mirror(self, axis: int = 0) -> Self:
|
||||
self.mirrored = not self.mirrored
|
||||
if axis == 1:
|
||||
self.rotation += pi
|
||||
return self
|
||||
|
||||
def scale_by(self, c: float) -> 'Text':
|
||||
def scale_by(self, c: float) -> Self:
|
||||
self.height *= c
|
||||
return self
|
||||
|
||||
def normalized_form(self, norm_value: float) -> normalized_shape_tuple:
|
||||
mirror_x, rotation = normalize_mirror(self.mirrored)
|
||||
rotation += self.rotation
|
||||
rotation %= 2 * pi
|
||||
rotation = self.rotation % (2 * pi)
|
||||
return ((type(self), self.string, self.font_path),
|
||||
(self.offset, self.height / norm_value, rotation, mirror_x),
|
||||
(self.offset, self.height / norm_value, rotation, bool(self.mirrored)),
|
||||
lambda: Text(
|
||||
string=self.string,
|
||||
height=self.height * norm_value,
|
||||
font_path=self.font_path,
|
||||
rotation=rotation,
|
||||
mirrored=(mirror_x, False),
|
||||
))
|
||||
).mirror2d(across_x=self.mirrored),
|
||||
)
|
||||
|
||||
def get_bounds_single(self) -> NDArray[numpy.float64]:
|
||||
# rotation makes this a huge pain when using slot.advance and glyph.bbox(), so
|
||||
|
|
@ -258,5 +250,5 @@ def get_char_as_polygons(
|
|||
|
||||
def __repr__(self) -> str:
|
||||
rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
|
||||
mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
|
||||
mirrored = ' m{:d}' if self.mirrored else ''
|
||||
return f'<TextShape "{self.string}" o{self.offset} h{self.height:g}{rotation}{mirrored}>'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue