Skip to content

Commit

Permalink
Shape: add align and center method, change functionality of move method
Browse files Browse the repository at this point in the history
  • Loading branch information
Funami580 committed Feb 28, 2022
1 parent b995f9d commit 0e8eafe
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 16 deletions.
121 changes: 107 additions & 14 deletions pyonfx/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ def bounding(self, exact: bool = True) -> Tuple[float, float, float, float]:
Examples:
.. code-block:: python3
print("Left-top: %d %d\\nRight-bottom: %d %d" % (Shape("m 10 5 l 25 5 25 42 10 42").bounding()))
print(Shape("m 313 312 b 254 287 482 38 277 212 l 436 269 b 378 388 461 671 260 481").bounding())
print(Shape("m 313 312 b 254 287 482 38 277 212 l 436 269 b 378 388 461 671 260 481").bounding(exact=False))
print( "Left-top: %d %d\\nRight-bottom: %d %d" % ( Shape("m 10 5 l 25 5 25 42 10 42").bounding() ) )
print( Shape("m 313 312 b 254 287 482 38 277 212 l 436 269 b 378 388 461 671 260 481").bounding() )
print( Shape("m 313 312 b 254 287 482 38 277 212 l 436 269 b 378 388 461 671 260 481").bounding(exact=False) )
>>> Left-top: 10 5
>>> Right-bottom: 25 42
Expand Down Expand Up @@ -426,11 +426,10 @@ def update_min_max(x, y):

return x_min, y_min, x_max, y_max

def move(self, x: float = None, y: float = None) -> Shape:
def move(self, x: float, y: float) -> Shape:
"""Moves shape coordinates in given direction.
| If neither x and y are passed, it will automatically center the shape to the origin (0,0).
| This function is an high level function, it just uses Shape.map, which is more advanced. Additionally, it is an easy way to center a shape.
| This function is a high level function, it just uses Shape.map, which is more advanced.
Parameters:
x (int or float): Displacement along the x-axis.
Expand All @@ -446,17 +445,111 @@ def move(self, x: float = None, y: float = None) -> Shape:
>>> m -5 10 l 25 10 25 30 -5 30
"""
if x is None and y is None:
x, y = [-1 * el for el in self.bounding()[0:2]]
elif x is None:
x = 0
elif y is None:
y = 0

# Update shape
self.map(lambda cx, cy: (cx + x, cy + y))
return self

def align(self, anchor: int, an: int = 5) -> Shape:
"""Aligns the shape.
Parameters:
anchor (int): The shape's position relative to the position anchor. If anchor=7, the position anchor would be at the top left of the shape's bounding box. If anchor=5, it would be in the center. If anchor=3, it would be at the bottom right.
an (int): Alignment used for the shape (e.g. for {\\\\an8} you would use an=8).
Returns:
A pointer to the current object.
Examples:
.. code-block:: python3
print( Shape("m 10 10 l 30 10 30 20 10 20").align(anchor=5) )
>>> m 0 0 l 20 0 20 10 0 10
"""

y_axis_anchor, x_axis_anchor = divmod(anchor - 1, 3)
y_axis_an, x_axis_an = divmod(an - 1, 3)
x_min, y_min, x_max, y_max = self.bounding()
libass_boundings = self.bounding(exact=False)
x_min_libass, y_min_libass, x_max_libass, y_max_libass = libass_boundings
x_move = -x_min
y_move = -y_min

# Center shape along x-axis
if x_axis_an == 0: # left
# center shape
x_move -= (x_max - x_min) / 2
elif x_axis_an == 1: # center
# adjust for imprecise calculation from libass (when using bezier curves)
x_move -= (x_max - x_min) / 2 - (x_max_libass - x_min_libass) / 2
elif x_axis_an == 2: # right
# center shape
x_move += (x_max - x_min) / 2
# adjust for imprecise calculation from libass (when using bezier curves)
x_move -= (x_max - x_min) - (x_max_libass - x_min_libass)
else:
raise ValueError("an must be an integer from 1 to 9")

# Center shape along y-axis
if y_axis_an == 0: # bottom
# center shape
y_move += (y_max - y_min) / 2
# adjust for imprecise calculation from libass (when using bezier curves)
y_move -= (y_max - y_min) - (y_max_libass - y_min_libass)
elif y_axis_an == 1: # middle
# adjust for imprecise calculation from libass (when using bezier curves)
y_move -= (y_max - y_min) / 2 - (y_max_libass - y_min_libass) / 2
elif y_axis_an == 2: # top
# center shape
y_move -= (y_max - y_min) / 2
else:
raise ValueError("an must be an integer from 1 to 9")

# Set anchor along x-axis
if x_axis_anchor == 0: # left
x_move += (x_max - x_min) / 2
elif x_axis_anchor == 1: # center
pass
elif x_axis_anchor == 2: # right
x_move -= (x_max - x_min) / 2
else:
raise ValueError("anchor must be an integer from 1 to 9")

# Set anchor along y-axis
if y_axis_anchor == 0: # bottom
y_move -= (y_max - y_min) / 2
elif y_axis_anchor == 1: # middle
pass
elif y_axis_anchor == 2: # top
y_move += (y_max - y_min) / 2
else:
raise ValueError("anchor must be an integer from 1 to 9")

# Update shape
self.map(lambda cx, cy: (cx + x_move, cy + y_move))
return self

def center(self, an: int = 5) -> Shape:
"""Centers the shape.
| This function is a high level function, it just uses Shape.align, which is more advanced.
Parameters:
an (int): Alignment used for the shape (e.g. for {\\\\an8} you would use an=8).
Returns:
A pointer to the current object.
Examples:
.. code-block:: python3
print( Shape("m 10 10 l 30 10 30 20 10 20").center() )
>>> m 0 0 l 20 0 20 10 0 10
"""

return self.align(anchor=5, an=an)

def flatten(self, tolerance: float = 1.0) -> Shape:
"""Splits shape's bezier curves into lines.
Expand Down Expand Up @@ -1068,7 +1161,7 @@ def rotate_on_axis_z(point, theta):
shape = Shape(" ".join(shape))

# Return result centered
return shape.move()
return shape.center()

@staticmethod
def star(edges: int, inner_size: float, outer_size: float) -> Shape:
Expand Down
97 changes: 95 additions & 2 deletions tests/test_shape.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
from pyonfx import *
from copy import copy


def test_transform():
Expand Down Expand Up @@ -76,9 +77,101 @@ def test_move():
dest = Shape("m -95.5 -2 l 105 -2 b 105 98 -95 98 -95.5 -2 c")
assert original.move(5, -2) == dest


def test_align():
# Test an
original = Shape("m 0 0 l 500 0 500 200 0 200")
assert copy(original).align(anchor=5, an=5) == original
dest1 = Shape("m -250 100 l 250 100 250 300 -250 300")
assert original.align(anchor=5, an=1) == dest1
dest2 = Shape("m 0 100 l 500 100 500 300 0 300")
assert original.align(anchor=5, an=2) == dest2
dest3 = Shape("m 250 100 l 750 100 750 300 250 300")
assert original.align(anchor=5, an=3) == dest3
dest4 = Shape("m -250 0 l 250 0 250 200 -250 200")
assert original.align(anchor=5, an=4) == dest4
dest6 = Shape("m 250 0 l 750 0 750 200 250 200")
assert original.align(anchor=5, an=6) == dest6
dest7 = Shape("m -250 -100 l 250 -100 250 100 -250 100")
assert original.align(anchor=5, an=7) == dest7
dest8 = Shape("m 0 -100 l 500 -100 500 100 0 100")
assert original.align(anchor=5, an=8) == dest8
dest9 = Shape("m 250 -100 l 750 -100 750 100 250 100")
assert original.align(anchor=5, an=9) == dest9

original = Shape(
"m 411.87 306.36 b 385.63 228.63 445.78 147.2 536.77 144.41 630.18 147.77 697 236.33 665.81 310.49 591.86 453.18 437.07 395.59 416 316.68"
)
dest3 = Shape(
"m 183.614 344.04 b 157.374 266.31 217.524 184.88 308.514 182.09 401.924 185.45 468.744 274.01 437.554 348.17 363.604 490.86 208.814 433.27 187.744 354.36"
)
assert original.align(anchor=5, an=3) == dest3
dest5 = Shape(
"m 27.929 189.655 b 1.689 111.925 61.839 30.495 152.829 27.705 246.239 31.065 313.059 119.625 281.869 193.785 207.919 336.475 53.129 278.885 32.059 199.975"
)
assert original.align(anchor=5, an=5) == dest5
dest7 = Shape(
"m -127.756 35.27 b -153.996 -42.46 -93.846 -123.89 -2.856 -126.68 90.554 -123.32 157.374 -34.76 126.184 39.4 52.234 182.09 -102.556 124.5 -123.626 45.59"
)
assert original.align(anchor=5, an=7) == dest7

# Test anchor
original = Shape("m 0 0 l 500 0 500 200 0 200")
dest1 = Shape("m 250 -100 l 750 -100 750 100 250 100")
assert original.align(anchor=1, an=5) == dest1
dest2 = Shape("m 0 -100 l 500 -100 500 100 0 100")
assert original.align(anchor=2, an=5) == dest2
dest3 = Shape("m -250 -100 l 250 -100 250 100 -250 100")
assert original.align(anchor=3, an=5) == dest3
dest4 = Shape("m 250 0 l 750 0 750 200 250 200")
assert original.align(anchor=4, an=5) == dest4
dest5 = Shape("m 0 0 l 500 0 500 200 0 200")
assert original.align(anchor=5, an=5) == dest5
dest6 = Shape("m -250 0 l 250 0 250 200 -250 200")
assert original.align(anchor=6, an=5) == dest6
dest7 = Shape("m 250 100 l 750 100 750 300 250 300")
assert original.align(anchor=7, an=5) == dest7
dest8 = Shape("m 0 100 l 500 100 500 300 0 300")
assert original.align(anchor=8, an=5) == dest8
dest9 = Shape("m -250 100 l 250 100 250 300 -250 300")
assert original.align(anchor=9, an=5) == dest9

# Test anchor + an
original = Shape("m 342 352 l 338 544 734 536 736 350 b 784 320 1157 167 930 232")
dest_anchor_7_an_5 = Shape(
"m 413.5 324.427 l 409.5 516.427 805.5 508.427 807.5 322.427 b 855.5 292.427 1228.5 139.427 1001.5 204.427"
)
assert original.align(anchor=7, an=5) == dest_anchor_7_an_5
dest_anchor_9_an_1 = Shape(
"m -660.664 512.927 l -664.664 704.927 -268.664 696.927 -266.664 510.927 b -218.664 480.927 154.336 327.927 -72.664 392.927"
)
assert original.align(anchor=9, an=1) == dest_anchor_9_an_1
dest_anchor_9_an_5 = Shape(
"m -251.164 324.427 l -255.164 516.427 140.836 508.427 142.836 322.427 b 190.836 292.427 563.836 139.427 336.836 204.427"
)
assert original.align(anchor=9, an=5) == dest_anchor_9_an_5
dest_anchor_9_an_9 = Shape(
"m 158.336 135.927 l 154.336 327.927 550.336 319.927 552.336 133.927 b 600.336 103.927 973.336 -49.073 746.336 15.927"
)
assert original.align(anchor=9, an=9) == dest_anchor_9_an_9
dest_anchor_3_an_5 = Shape(
"m -251.164 -3.5 l -255.164 188.5 140.836 180.5 142.836 -5.5 b 190.836 -35.5 563.836 -188.5 336.836 -123.5"
)
assert original.align(anchor=3, an=5) == dest_anchor_3_an_5
dest_anchor_5_an_5 = Shape(
"m 81.168 160.464 l 77.168 352.464 473.168 344.464 475.168 158.464 b 523.168 128.464 896.168 -24.536 669.168 40.464"
)
assert original.align(anchor=5, an=5) == dest_anchor_5_an_5


def test_center():
original = Shape("m 0 0 l 20 0 20 10 0 10")
assert original.move() == original
assert original.move(10, 400).move() == original
assert copy(original).center() == original
assert copy(original).move(10, 400).center() == original

original = Shape("m 0 0 l 500 0 500 200 0 200")
dest = Shape("m 250 -100 l 750 -100 750 100 250 100")
assert original.center(an=9) == dest


def test_flatten():
Expand Down

0 comments on commit 0e8eafe

Please sign in to comment.