don't keep track of y-mirroring separately from x

This commit is contained in:
jan 2023-04-14 22:19:56 -07:00
commit 91465b7175
20 changed files with 190 additions and 213 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'{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}>'