drawlib 0.2.3.dev2__tar.gz → 0.2.3.dev4__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.

Potentially problematic release.


This version of drawlib might be problematic. Click here for more details.

Files changed (83) hide show
  1. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/PKG-INFO +1 -1
  2. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/__init__.py +1 -1
  3. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/apis.py +5 -0
  4. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/model.py +19 -1
  5. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/util.py +6 -0
  6. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/base.py +53 -1
  7. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/canvas.py +8 -1
  8. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/line.py +192 -1
  9. drawlib-0.2.3.dev4/drawlib/v0_2/private/core_canvas/original_arrow.py +615 -0
  10. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/patches.py +19 -69
  11. drawlib-0.2.3.dev4/drawlib/v0_2/private/smartarts/boxlist.py +178 -0
  12. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/bubblespeech.py +16 -16
  13. drawlib-0.2.3.dev4/drawlib/v0_2/private/smartarts/bulletpoints.py +188 -0
  14. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/dsart.py +7 -0
  15. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/gridlayout.py +47 -26
  16. drawlib-0.2.3.dev4/drawlib/v0_2/private/smartarts/pyramid.py +401 -0
  17. drawlib-0.2.3.dev4/drawlib/v0_2/private/smartarts/table.py +683 -0
  18. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/tree.py +10 -5
  19. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/util.py +116 -1
  20. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/args.py +26 -11
  21. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/pyproject.toml +3 -2
  22. drawlib-0.2.3.dev2/drawlib/v0_2/private/core_canvas/original_arrow.py +0 -139
  23. drawlib-0.2.3.dev2/drawlib/v0_2/private/smartarts/pyramid.py +0 -189
  24. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/LICENSE.txt +0 -0
  25. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/__main__.py +0 -0
  26. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/apis.py +0 -0
  27. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/assets/__init__.py +0 -0
  28. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/assets/v0_2/__init__.py +0 -0
  29. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/assets/v0_2/fonticons/__init__.py +0 -0
  30. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/assets/v0_2/fonts/__init__.py +0 -0
  31. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/py.typed +0 -0
  32. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/__init__.py +0 -0
  33. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/__main__.py +0 -0
  34. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/__init__.py +0 -0
  35. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/command.py +0 -0
  36. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/__init__.py +0 -0
  37. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/colors.py +0 -0
  38. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/dimage.py +0 -0
  39. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/fonts.py +0 -0
  40. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/model_system_default.py +0 -0
  41. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/theme.py +0 -0
  42. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/theme_officials.py +0 -0
  43. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core/theme_style_caches.py +0 -0
  44. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/__init__.py +0 -0
  45. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/image.py +0 -0
  46. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/original_polygon.py +0 -0
  47. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/core_canvas/text.py +0 -0
  48. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/download.py +0 -0
  49. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/dutil/__init__.py +0 -0
  50. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/dutil/dutil_canvas.py +0 -0
  51. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/dutil/dutil_color.py +0 -0
  52. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/dutil/dutil_script.py +0 -0
  53. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/dutil/settings.py +0 -0
  54. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/icons/__init__.py +0 -0
  55. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/icons/phosphor.py +0 -0
  56. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/icons/util.py +0 -0
  57. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/logging.py +0 -0
  58. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/__init__.py +0 -0
  59. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/boxtree.py +0 -0
  60. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/smartarts/sourcecode.py +0 -0
  61. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/activity.py +0 -0
  62. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/class_.py +0 -0
  63. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/communication.py +0 -0
  64. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/component.py +0 -0
  65. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/deployment.py +0 -0
  66. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/duml.py +0 -0
  67. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/object.py +0 -0
  68. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/package.py +0 -0
  69. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/sequence.py +0 -0
  70. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/state_machine.py +0 -0
  71. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/umls/usecase.py +0 -0
  72. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/__init__.py +0 -0
  73. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/color.py +0 -0
  74. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/coordinate.py +0 -0
  75. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/icon.py +0 -0
  76. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/image.py +0 -0
  77. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/line.py +0 -0
  78. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/shape.py +0 -0
  79. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/smartarts.py +0 -0
  80. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/style.py +0 -0
  81. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/text.py +0 -0
  82. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/drawlib/v0_2/private/validators/types.py +0 -0
  83. {drawlib-0.2.3.dev2 → drawlib-0.2.3.dev4}/pypi.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: drawlib
3
- Version: 0.2.3.dev2
3
+ Version: 0.2.3.dev4
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.3.dev2"
17
+ LIB_VERSION = "0.2.3.dev4"
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,
@@ -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:
@@ -498,6 +498,7 @@ class ShapeUtil:
498
498
  text_angle = (text_angle + 180) % 360
499
499
 
500
500
  x, y = xy
501
+ # shift
501
502
  if style.xy_shift is not None:
502
503
  x_shift, y_shift = style.xy_shift
503
504
  if shape_angle == 0:
@@ -510,6 +511,11 @@ class ShapeUtil:
510
511
  x += rotated_x_shift
511
512
  y += rotated_y_shift
512
513
 
514
+ # absolute shift
515
+ if style.xy_abs_shift is not None:
516
+ x += style.xy_abs_shift[0]
517
+ y += style.xy_abs_shift[1]
518
+
513
519
  # only check color. ignore alignment
514
520
  options = TextUtil.get_text_options(style)
515
521
  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,
@@ -332,6 +388,141 @@ class CanvasLineFeature(CanvasBase):
332
388
  self._artists.append(FancyArrowPatch(path=path, **options))
333
389
 
334
390
 
391
+ class LineArcHelper:
392
+ """Internal class"""
393
+
394
+ @classmethod
395
+ def get_point_on_ellipse(
396
+ cls,
397
+ xy: Tuple[float, float],
398
+ width: float,
399
+ height: float,
400
+ angle: float,
401
+ ) -> Tuple[float, float]:
402
+ """Internal function"""
403
+ x, y = xy
404
+ # Convert angle from degrees to radians
405
+ angle = math.radians(angle)
406
+
407
+ # Calculate the coordinates of the point
408
+ point_x = x + width / 2 * math.cos(angle)
409
+ point_y = y + height / 2 * math.sin(angle)
410
+
411
+ return point_x, point_y
412
+
413
+ @classmethod
414
+ def get_ellipse_path_points(
415
+ cls,
416
+ xy: Tuple[float, float],
417
+ width: float,
418
+ height: float,
419
+ angle_start: float,
420
+ angle_end: float,
421
+ ) -> List[
422
+ Union[
423
+ Tuple[float, float],
424
+ Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
425
+ ]
426
+ ]:
427
+ """Internal function"""
428
+ diff = angle_end - angle_start
429
+ if abs(diff) > 270:
430
+ # having +2 for avoiding situation next_mid_angle == angle_end
431
+ step = int(diff / 3)
432
+ elif abs(diff) > 135:
433
+ step = int(diff / 2)
434
+ else:
435
+ p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
436
+ xy,
437
+ width,
438
+ height,
439
+ angle_start,
440
+ angle_end,
441
+ )
442
+ return [p1, (p2, p3, p4)]
443
+ if diff >= 0:
444
+ diff += 2
445
+ else:
446
+ diff -= 2
447
+
448
+ i = 0
449
+ start = None
450
+ path_points = []
451
+ while True:
452
+ last_mid_angle = angle_start + step * i
453
+ next_mid_angle = angle_start + step * (i + 1)
454
+
455
+ if angle_start < angle_end:
456
+ # anti clock wise
457
+ is_last = next_mid_angle > angle_end
458
+ else:
459
+ # clock wise
460
+ is_last = angle_end > next_mid_angle
461
+
462
+ if is_last:
463
+ p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
464
+ xy,
465
+ width,
466
+ height,
467
+ last_mid_angle,
468
+ angle_end,
469
+ )
470
+ if start is None:
471
+ start = p1
472
+ path_points.append((p2, p3, p4))
473
+ break
474
+
475
+ else:
476
+ p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
477
+ xy,
478
+ width,
479
+ height,
480
+ last_mid_angle,
481
+ next_mid_angle,
482
+ )
483
+ if start is None:
484
+ start = p1
485
+ path_points.append((p2, p3, p4))
486
+
487
+ i += 1
488
+
489
+ path_points.insert(0, start)
490
+ return path_points
491
+
492
+ @classmethod
493
+ def bezier_ellipse_arc_approximation(
494
+ cls,
495
+ xy: Tuple[float, float],
496
+ width: float,
497
+ height: float,
498
+ start_angle: float,
499
+ end_angle: float,
500
+ ) -> Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float], Tuple[float, float]]:
501
+ """Internal function"""
502
+ x, y = xy
503
+
504
+ # Convert angles from degrees to radians
505
+ start_angle = math.radians(start_angle)
506
+ end_angle = math.radians(end_angle)
507
+
508
+ # Calculate the points on the ellipse at the start and end angles
509
+ start_point = (x + width / 2 * math.cos(start_angle), y + height / 2 * math.sin(start_angle))
510
+ end_point = (x + width / 2 * math.cos(end_angle), y + height / 2 * math.sin(end_angle))
511
+
512
+ # Calculate the control points for the Bezier curve
513
+ t = (4 / 3) * math.tan((end_angle - start_angle) / 4)
514
+ control_point1 = (
515
+ start_point[0] - t * width / 2 * math.sin(start_angle),
516
+ start_point[1] + t * height / 2 * math.cos(start_angle),
517
+ )
518
+ control_point2 = (
519
+ end_point[0] + t * width / 2 * math.sin(end_angle),
520
+ end_point[1] - t * height / 2 * math.cos(end_angle),
521
+ )
522
+
523
+ return start_point, control_point1, control_point2, end_point
524
+
525
+
335
526
  def _get_mid_points(
336
527
  a: Tuple[float, float],
337
528
  b: Tuple[float, float],