drawlib 0.2.1__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {drawlib-0.2.1 → drawlib-0.2.3}/PKG-INFO +1 -1
  2. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/__init__.py +1 -1
  3. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/apis.py +5 -0
  4. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/command.py +2 -2
  5. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/model.py +19 -1
  6. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/util.py +59 -1
  7. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/base.py +53 -1
  8. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/canvas.py +8 -1
  9. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/line.py +196 -3
  10. drawlib-0.2.3/drawlib/v0_2/private/core_canvas/original_arrow.py +718 -0
  11. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/patches.py +19 -69
  12. drawlib-0.2.3/drawlib/v0_2/private/smartarts/boxlist.py +253 -0
  13. drawlib-0.2.3/drawlib/v0_2/private/smartarts/boxtree.py +114 -0
  14. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/smartarts/bubblespeech.py +16 -16
  15. drawlib-0.2.3/drawlib/v0_2/private/smartarts/bulletpoints.py +188 -0
  16. drawlib-0.2.3/drawlib/v0_2/private/smartarts/dsart.py +25 -0
  17. drawlib-0.2.3/drawlib/v0_2/private/smartarts/gridlayout.py +327 -0
  18. drawlib-0.2.3/drawlib/v0_2/private/smartarts/pyramid.py +431 -0
  19. drawlib-0.2.3/drawlib/v0_2/private/smartarts/table.py +683 -0
  20. drawlib-0.2.3/drawlib/v0_2/private/smartarts/tree.py +309 -0
  21. drawlib-0.2.3/drawlib/v0_2/private/umls/activity.py +10 -0
  22. drawlib-0.2.3/drawlib/v0_2/private/umls/class_.py +10 -0
  23. drawlib-0.2.3/drawlib/v0_2/private/umls/communication.py +10 -0
  24. drawlib-0.2.3/drawlib/v0_2/private/umls/component.py +10 -0
  25. drawlib-0.2.3/drawlib/v0_2/private/umls/deployment.py +10 -0
  26. drawlib-0.2.3/drawlib/v0_2/private/umls/duml.py +12 -0
  27. drawlib-0.2.3/drawlib/v0_2/private/umls/object.py +10 -0
  28. drawlib-0.2.3/drawlib/v0_2/private/umls/package.py +10 -0
  29. drawlib-0.2.3/drawlib/v0_2/private/umls/sequence.py +10 -0
  30. drawlib-0.2.3/drawlib/v0_2/private/umls/state_machine.py +10 -0
  31. drawlib-0.2.3/drawlib/v0_2/private/umls/usecase.py +10 -0
  32. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/util.py +116 -1
  33. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/args.py +26 -11
  34. {drawlib-0.2.1 → drawlib-0.2.3}/pyproject.toml +3 -2
  35. drawlib-0.2.1/drawlib/v0_2/private/core_canvas/original_arrow.py +0 -139
  36. drawlib-0.2.1/drawlib/v0_2/private/smartarts/dsart.py +0 -15
  37. {drawlib-0.2.1 → drawlib-0.2.3}/LICENSE.txt +0 -0
  38. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/__main__.py +0 -0
  39. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/apis.py +0 -0
  40. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/assets/__init__.py +0 -0
  41. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/assets/v0_2/__init__.py +0 -0
  42. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/assets/v0_2/fonticons/__init__.py +0 -0
  43. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/assets/v0_2/fonts/__init__.py +0 -0
  44. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/py.typed +0 -0
  45. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/__init__.py +0 -0
  46. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/__main__.py +0 -0
  47. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/__init__.py +0 -0
  48. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/__init__.py +0 -0
  49. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/colors.py +0 -0
  50. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/dimage.py +0 -0
  51. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/fonts.py +0 -0
  52. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/model_system_default.py +0 -0
  53. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/theme.py +0 -0
  54. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/theme_officials.py +0 -0
  55. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core/theme_style_caches.py +0 -0
  56. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/__init__.py +0 -0
  57. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/image.py +0 -0
  58. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/original_polygon.py +0 -0
  59. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/text.py +0 -0
  60. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/download.py +0 -0
  61. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/__init__.py +0 -0
  62. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/dutil_canvas.py +0 -0
  63. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/dutil_color.py +0 -0
  64. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/dutil_script.py +0 -0
  65. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/settings.py +0 -0
  66. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/icons/__init__.py +0 -0
  67. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/icons/phosphor.py +0 -0
  68. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/icons/util.py +0 -0
  69. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/logging.py +0 -0
  70. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/smartarts/__init__.py +0 -0
  71. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/smartarts/sourcecode.py +0 -0
  72. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/__init__.py +0 -0
  73. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/color.py +0 -0
  74. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/coordinate.py +0 -0
  75. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/icon.py +0 -0
  76. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/image.py +0 -0
  77. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/line.py +0 -0
  78. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/shape.py +0 -0
  79. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/smartarts.py +0 -0
  80. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/style.py +0 -0
  81. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/text.py +0 -0
  82. {drawlib-0.2.1 → drawlib-0.2.3}/drawlib/v0_2/private/validators/types.py +0 -0
  83. {drawlib-0.2.1 → drawlib-0.2.3}/pypi.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: drawlib
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Python drawing library. Illustration as Code.
5
5
  Home-page: https://www.drawlib.com
6
6
  Author: Yuichi Ito
@@ -14,7 +14,7 @@ import sys
14
14
  from typing import Final, List
15
15
 
16
16
  # please update here when you release new version
17
- LIB_VERSION = "0.2.1"
17
+ LIB_VERSION: Final[str] = "0.2.3"
18
18
 
19
19
  # please list active main committers (1+ commits per month)
20
20
  AUTHORS: Final[List[str]] = [
@@ -54,6 +54,10 @@ from drawlib.v0_2.private.core_canvas.canvas import (
54
54
  arc,
55
55
  # shape original
56
56
  arrow,
57
+ arrow_arc,
58
+ arrow_l,
59
+ arrow_polyline,
60
+ arrow_u,
57
61
  chevron,
58
62
  circle,
59
63
  # base
@@ -66,6 +70,7 @@ from drawlib.v0_2.private.core_canvas.canvas import (
66
70
  image,
67
71
  # line
68
72
  line,
73
+ line_arc,
69
74
  line_bezier1,
70
75
  line_bezier2,
71
76
  line_curved,
@@ -47,8 +47,8 @@ def call() -> None:
47
47
 
48
48
  # show version
49
49
  if argparser.is_show_version():
50
- logger.critical(f"software={drawlib.VERSION}")
51
- logger.critical(f"api={drawlib.v0_2.VERSION}")
50
+ logger.critical(f"software={drawlib.LIB_VERSION}")
51
+ logger.critical(f"api={drawlib.v0_2.LIB_VERSION}")
52
52
  sys.exit(0)
53
53
 
54
54
  # purge font cache
@@ -883,6 +883,7 @@ class ShapeTextStyle:
883
883
  angle: Optional[float] = None
884
884
  flip: Optional[bool] = None
885
885
  xy_shift: Optional[Tuple[float, float]] = None
886
+ xy_abs_shift: Optional[Tuple[float, float]] = None
886
887
 
887
888
  @error_handler
888
889
  def copy(self) -> ShapeTextStyle:
@@ -1107,7 +1108,7 @@ class ShapeTextStyle:
1107
1108
 
1108
1109
  @xy_shift.setter
1109
1110
  @error_handler
1110
- def xy_shift(self, value: Optional[float]) -> None:
1111
+ def xy_shift(self, value: Optional[Tuple[float, float]]) -> None:
1111
1112
  """Setter of xy_shift"""
1112
1113
  if isinstance(value, property) or value is None:
1113
1114
  self._xy_shift = None
@@ -1116,6 +1117,23 @@ class ShapeTextStyle:
1116
1117
  coordinate_validator.validate_xy("ShapeTextStyle.xy_shift", value)
1117
1118
  self._xy_shift = value
1118
1119
 
1120
+ @property # type: ignore[no-redef]
1121
+ @error_handler
1122
+ def xy_abs_shift(self) -> Optional[Tuple[float, float]]:
1123
+ """Getter of xy_abs_shift"""
1124
+ return self._xy_abs_shift
1125
+
1126
+ @xy_abs_shift.setter
1127
+ @error_handler
1128
+ def xy_abs_shift(self, value: Optional[Tuple[float, float]]) -> None:
1129
+ """Setter of xy_abs_shift"""
1130
+ if isinstance(value, property) or value is None:
1131
+ self._xy_abs_shift = None
1132
+ return
1133
+
1134
+ coordinate_validator.validate_xy("ShapeTextStyle.xy_abs_shift", value)
1135
+ self._xy_abs_shift = value
1136
+
1119
1137
 
1120
1138
  @dataclasses.dataclass
1121
1139
  class TextStyle:
@@ -11,7 +11,7 @@
11
11
  """Utility module for converting drawlib data to matplotlib data."""
12
12
 
13
13
  import math
14
- from typing import Any, Callable, Dict, Literal, Optional, Tuple, Union
14
+ from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
15
15
 
16
16
  from matplotlib.font_manager import FontProperties
17
17
  from matplotlib.text import Text
@@ -238,6 +238,58 @@ class ImageUtil:
238
238
  class LineUtil:
239
239
  """A utility class for handling line styles and options."""
240
240
 
241
+ @staticmethod
242
+ def sanitize_xys(xys: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
243
+ def remove_consecutive_duplicates(xys: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
244
+ return [v for i, v in enumerate(xys) if i == 0 or v != xys[i - 1]]
245
+
246
+ def merge_straight_lines(xys: List[Tuple[float, float]]) -> List[Tuple[float, float]]: # noqa: C901
247
+ points: List[Tuple[float, float]] = []
248
+
249
+ skip_next = False
250
+ for i in range(len(xys) - 1):
251
+ if skip_next:
252
+ skip_next = False
253
+ continue
254
+
255
+ if i == 0:
256
+ points.append(xys[i])
257
+ continue
258
+
259
+ x0, y0 = points[-1]
260
+ x1, y1 = xys[i]
261
+ x2, y2 = xys[i + 1]
262
+ if x1 - x0 == 0:
263
+ if x2 - x1 == 0:
264
+ points.append(xys[i + 1])
265
+ skip_next = True
266
+ continue
267
+ else:
268
+ points.append(xys[i])
269
+ if i == len(xys) - 2:
270
+ points.append(xys[i + 1])
271
+ continue
272
+
273
+ if x2 - x1 == 0:
274
+ points.append(xys[i])
275
+ if i == len(xys) - 2:
276
+ points.append(xys[i + 1])
277
+ continue
278
+
279
+ m1 = (y1 - y0) / (x1 - x0)
280
+ m2 = (y2 - y1) / (x2 - x1)
281
+ if m1 == m2:
282
+ skip_next = True
283
+ points.append(xys[i + 1])
284
+ else:
285
+ points.append(xys[i])
286
+ if i == len(xys) - 2:
287
+ points.append(xys[i + 1])
288
+
289
+ return points
290
+
291
+ return merge_straight_lines(remove_consecutive_duplicates(xys))
292
+
241
293
  @staticmethod
242
294
  def format_style(
243
295
  style: Union[LineStyle, str, None],
@@ -498,6 +550,7 @@ class ShapeUtil:
498
550
  text_angle = (text_angle + 180) % 360
499
551
 
500
552
  x, y = xy
553
+ # shift
501
554
  if style.xy_shift is not None:
502
555
  x_shift, y_shift = style.xy_shift
503
556
  if shape_angle == 0:
@@ -510,6 +563,11 @@ class ShapeUtil:
510
563
  x += rotated_x_shift
511
564
  y += rotated_y_shift
512
565
 
566
+ # absolute shift
567
+ if style.xy_abs_shift is not None:
568
+ x += style.xy_abs_shift[0]
569
+ y += style.xy_abs_shift[1]
570
+
513
571
  # only check color. ignore alignment
514
572
  options = TextUtil.get_text_options(style)
515
573
  if "horizontalalignment" in options:
@@ -18,7 +18,10 @@ import matplotlib.lines
18
18
  import matplotlib.text
19
19
  import PIL.Image
20
20
  from matplotlib import pyplot
21
- from matplotlib.patches import PathPatch
21
+ from matplotlib.patches import (
22
+ PathPatch,
23
+ Polygon,
24
+ )
22
25
  from matplotlib.path import Path
23
26
 
24
27
  import drawlib.v0_2.private.validators.args as validator
@@ -235,6 +238,55 @@ class CanvasBase:
235
238
  # Shape
236
239
  #
237
240
 
241
+ @error_handler
242
+ def polygon(
243
+ self,
244
+ xys: List[Tuple[float, float]],
245
+ style: Union[ShapeStyle, str, None] = None,
246
+ text: str = "",
247
+ textsize: Optional[float] = None,
248
+ textstyle: Union[ShapeTextStyle, str, None] = None,
249
+ ) -> None:
250
+ """Draw a polygon on the canvas.
251
+
252
+ Args:
253
+ xys: List of vertices [(x1, y1), ...(x_n, y_n)].
254
+ style (optional): Style of the polygon.
255
+ text (optional): Text shown at the center of the polygon.
256
+ textsize (optional): Font size of the text.
257
+ textstyle (optional): Style of the text.
258
+
259
+ Returns:
260
+ None
261
+ """
262
+ style, textstyle = ShapeUtil.format_styles(
263
+ style,
264
+ textstyle,
265
+ dtheme.polygonstyles.get,
266
+ dtheme.polygontextstyles.get,
267
+ )
268
+ validator.validate_shape_args(locals())
269
+
270
+ if textsize is not None:
271
+ textstyle.size = textsize
272
+
273
+ style.halign = None
274
+ style.valign = None
275
+ options = ShapeUtil.get_shape_options(style)
276
+ self._artists.append(Polygon(xy=xys, closed=True, **options))
277
+
278
+ if not text:
279
+ return
280
+ center, (_, _) = get_center_and_size(xys)
281
+ self._artists.append(
282
+ ShapeUtil.get_shape_text(
283
+ center,
284
+ text=text,
285
+ angle=0,
286
+ style=textstyle,
287
+ ),
288
+ )
289
+
238
290
  @error_handler
239
291
  def shape( # noqa: C901
240
292
  self,
@@ -266,6 +266,7 @@ line = canvas.line
266
266
  line_curved = canvas.line_curved
267
267
  line_bezier1 = canvas.line_bezier1
268
268
  line_bezier2 = canvas.line_bezier2
269
+ line_arc = canvas.line_arc
269
270
  lines = canvas.lines
270
271
  lines_curved = canvas.lines_curved
271
272
  lines_bezier = canvas.lines_bezier
@@ -275,10 +276,16 @@ rhombus = canvas.rhombus
275
276
  trapezoid = canvas.trapezoid
276
277
  parallelogram = canvas.parallelogram
277
278
  triangle = canvas.triangle
278
- arrow = canvas.arrow
279
279
  star = canvas.star
280
280
  chevron = canvas.chevron
281
281
 
282
+ # original arrow
283
+ arrow = canvas.arrow
284
+ arrow_polyline = canvas.arrow_polyline
285
+ arrow_arc = canvas.arrow_arc
286
+ arrow_l = canvas.arrow_l
287
+ arrow_u = canvas.arrow_u
288
+
282
289
  # patches
283
290
  arc = canvas.arc
284
291
  circle = canvas.circle
@@ -20,7 +20,7 @@ import drawlib.v0_2.private.validators.args as validator
20
20
  from drawlib.v0_2.private.core.model import LineStyle
21
21
  from drawlib.v0_2.private.core.util import LineUtil
22
22
  from drawlib.v0_2.private.core_canvas.base import CanvasBase
23
- from drawlib.v0_2.private.util import error_handler
23
+ from drawlib.v0_2.private.util import error_handler, get_rotated_path_points
24
24
 
25
25
 
26
26
  class CanvasLineFeature(CanvasBase):
@@ -191,6 +191,62 @@ class CanvasLineFeature(CanvasBase):
191
191
  style=style,
192
192
  )
193
193
 
194
+ @error_handler
195
+ def line_arc(
196
+ self,
197
+ xy: Tuple[float, float],
198
+ width: float,
199
+ height: float,
200
+ angle_start: float = 0,
201
+ angle_end: float = 180,
202
+ angle: float = 0,
203
+ arrowhead: Literal["", "->", "<-", "<->"] = "",
204
+ lwidth: Optional[float] = None,
205
+ style: Union[LineStyle, str, None] = None,
206
+ ) -> None:
207
+ """Draw arc line on ellipse.
208
+
209
+ Args:
210
+ xy: Tuple[float, float]: The center point of the ellipse from which the arc is drawn.
211
+ width: float: The width of the ellipse.
212
+ height: float: The height of the ellipse
213
+ angle_start: float: The starting angle of the arc in degrees (default is 0).
214
+ angle_end: float: The ending angle of the arc in degrees (default is 180).
215
+ angle (float): The angle of ellipse.
216
+ arrowhead: Literal["", "->", "<-", "<->"]: Optional arrowhead style ("", "->", "<-", "<->").
217
+ lwidth: Optional[float]: Optional width of the line.
218
+ style: Union[LineStyle, str, None]: Optional line style.
219
+
220
+ Returns:
221
+ None
222
+ """
223
+ style = LineUtil.format_style(style)
224
+ # validator.validate_line_args(locals())
225
+
226
+ if angle_start > angle_end:
227
+ angle_start = -1 * (360 - angle_start)
228
+ path_points = LineArcHelper.get_ellipse_path_points(
229
+ xy,
230
+ width,
231
+ height,
232
+ angle_start,
233
+ angle_end,
234
+ )
235
+
236
+ if angle != 0:
237
+ path_points = get_rotated_path_points(path_points, xy, angle) # type: ignore
238
+
239
+ start_point = path_points[0]
240
+ path_points = path_points[1:]
241
+
242
+ self.lines_bezier(
243
+ start_point, # type: ignore
244
+ path_points=path_points, # type: ignore
245
+ width=lwidth,
246
+ arrowhead=arrowhead,
247
+ style=style,
248
+ )
249
+
194
250
  @error_handler
195
251
  def lines(
196
252
  self,
@@ -212,7 +268,7 @@ class CanvasLineFeature(CanvasBase):
212
268
  """
213
269
  style = LineUtil.format_style(style)
214
270
  validator.validate_line_args(locals())
215
-
271
+ xys = LineUtil.sanitize_xys(xys)
216
272
  self.lines_bezier(
217
273
  xy=xys[0],
218
274
  path_points=xys[1:], # type: ignore
@@ -244,10 +300,12 @@ class CanvasLineFeature(CanvasBase):
244
300
  """
245
301
  style = LineUtil.format_style(style)
246
302
  validator.validate_line_args(locals())
303
+
247
304
  if len(xys) == 2:
248
- self.lines(xys, width=width, arrowhead=arrowhead, style=style)
305
+ self.line(xys[0], xys[1], width=width, arrowhead=arrowhead, style=style)
249
306
  return
250
307
 
308
+ xys = LineUtil.sanitize_xys(xys)
251
309
  path_points = []
252
310
  last_i = len(xys) - 2
253
311
  # last_xy = (0, 0)
@@ -332,6 +390,141 @@ class CanvasLineFeature(CanvasBase):
332
390
  self._artists.append(FancyArrowPatch(path=path, **options))
333
391
 
334
392
 
393
+ class LineArcHelper:
394
+ """Internal class"""
395
+
396
+ @classmethod
397
+ def get_point_on_ellipse(
398
+ cls,
399
+ xy: Tuple[float, float],
400
+ width: float,
401
+ height: float,
402
+ angle: float,
403
+ ) -> Tuple[float, float]:
404
+ """Internal function"""
405
+ x, y = xy
406
+ # Convert angle from degrees to radians
407
+ angle = math.radians(angle)
408
+
409
+ # Calculate the coordinates of the point
410
+ point_x = x + width / 2 * math.cos(angle)
411
+ point_y = y + height / 2 * math.sin(angle)
412
+
413
+ return point_x, point_y
414
+
415
+ @classmethod
416
+ def get_ellipse_path_points(
417
+ cls,
418
+ xy: Tuple[float, float],
419
+ width: float,
420
+ height: float,
421
+ angle_start: float,
422
+ angle_end: float,
423
+ ) -> List[
424
+ Union[
425
+ Tuple[float, float],
426
+ Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
427
+ ]
428
+ ]:
429
+ """Internal function"""
430
+ diff = angle_end - angle_start
431
+ if abs(diff) > 270:
432
+ # having +2 for avoiding situation next_mid_angle == angle_end
433
+ step = int(diff / 3)
434
+ elif abs(diff) > 135:
435
+ step = int(diff / 2)
436
+ else:
437
+ p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
438
+ xy,
439
+ width,
440
+ height,
441
+ angle_start,
442
+ angle_end,
443
+ )
444
+ return [p1, (p2, p3, p4)]
445
+ if diff >= 0:
446
+ diff += 2
447
+ else:
448
+ diff -= 2
449
+
450
+ i = 0
451
+ start = None
452
+ path_points = []
453
+ while True:
454
+ last_mid_angle = angle_start + step * i
455
+ next_mid_angle = angle_start + step * (i + 1)
456
+
457
+ if angle_start < angle_end:
458
+ # anti clock wise
459
+ is_last = next_mid_angle > angle_end
460
+ else:
461
+ # clock wise
462
+ is_last = angle_end > next_mid_angle
463
+
464
+ if is_last:
465
+ p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
466
+ xy,
467
+ width,
468
+ height,
469
+ last_mid_angle,
470
+ angle_end,
471
+ )
472
+ if start is None:
473
+ start = p1
474
+ path_points.append((p2, p3, p4))
475
+ break
476
+
477
+ else:
478
+ p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
479
+ xy,
480
+ width,
481
+ height,
482
+ last_mid_angle,
483
+ next_mid_angle,
484
+ )
485
+ if start is None:
486
+ start = p1
487
+ path_points.append((p2, p3, p4))
488
+
489
+ i += 1
490
+
491
+ path_points.insert(0, start)
492
+ return path_points
493
+
494
+ @classmethod
495
+ def bezier_ellipse_arc_approximation(
496
+ cls,
497
+ xy: Tuple[float, float],
498
+ width: float,
499
+ height: float,
500
+ start_angle: float,
501
+ end_angle: float,
502
+ ) -> Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float], Tuple[float, float]]:
503
+ """Internal function"""
504
+ x, y = xy
505
+
506
+ # Convert angles from degrees to radians
507
+ start_angle = math.radians(start_angle)
508
+ end_angle = math.radians(end_angle)
509
+
510
+ # Calculate the points on the ellipse at the start and end angles
511
+ start_point = (x + width / 2 * math.cos(start_angle), y + height / 2 * math.sin(start_angle))
512
+ end_point = (x + width / 2 * math.cos(end_angle), y + height / 2 * math.sin(end_angle))
513
+
514
+ # Calculate the control points for the Bezier curve
515
+ t = (4 / 3) * math.tan((end_angle - start_angle) / 4)
516
+ control_point1 = (
517
+ start_point[0] - t * width / 2 * math.sin(start_angle),
518
+ start_point[1] + t * height / 2 * math.cos(start_angle),
519
+ )
520
+ control_point2 = (
521
+ end_point[0] + t * width / 2 * math.sin(end_angle),
522
+ end_point[1] - t * height / 2 * math.cos(end_angle),
523
+ )
524
+
525
+ return start_point, control_point1, control_point2, end_point
526
+
527
+
335
528
  def _get_mid_points(
336
529
  a: Tuple[float, float],
337
530
  b: Tuple[float, float],