pdfdancer-client-python 0.3.14__py3-none-any.whl → 3.0.1__py3-none-any.whl

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.
pdfdancer/page_builder.py CHANGED
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Optional
3
+ from typing import TYPE_CHECKING, Any, Mapping, Optional, Union
4
4
 
5
5
  from .exceptions import ValidationException
6
6
  from .models import AddPageRequest, Orientation, PageRef, PageSize
7
7
 
8
8
  if TYPE_CHECKING:
9
- from .pdfdancer_v1 import PDFDancer
9
+ from .pdfdancer_v2 import PDFDancer
10
10
 
11
11
 
12
12
  class PageBuilder:
@@ -47,20 +47,7 @@ class PageBuilder:
47
47
  self._page_number = int(page_number)
48
48
  return self
49
49
 
50
- def at_index(self, page_number: int) -> "PageBuilder":
51
- """
52
- Deprecated: Use at_page() instead. This method will be removed in a future release.
53
- """
54
- import warnings
55
-
56
- warnings.warn(
57
- "at_index() is deprecated, use at_page() instead",
58
- DeprecationWarning,
59
- stacklevel=2,
60
- )
61
- return self.at_page(page_number + 1)
62
-
63
- def orientation(self, orientation: Orientation) -> "PageBuilder":
50
+ def orientation(self, orientation: Union[Orientation, str]) -> "PageBuilder":
64
51
  if orientation is None:
65
52
  raise ValidationException("Orientation cannot be null")
66
53
  if isinstance(orientation, str):
@@ -77,7 +64,9 @@ class PageBuilder:
77
64
  self._orientation = Orientation.LANDSCAPE
78
65
  return self
79
66
 
80
- def page_size(self, page_size: PageSize) -> "PageBuilder":
67
+ def page_size(
68
+ self, page_size: Union[PageSize, str, Mapping[str, Any]]
69
+ ) -> "PageBuilder":
81
70
  if page_size is None:
82
71
  raise ValidationException("Page size cannot be null")
83
72
  self._page_size = PageSize.coerce(page_size)
pdfdancer/path_builder.py CHANGED
@@ -5,13 +5,14 @@ Provides fluent interface for constructing vector paths with lines and curves.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import math
8
9
  from typing import TYPE_CHECKING, List, Optional
9
10
 
10
11
  from .exceptions import ValidationException
11
12
  from .models import Bezier, Color, Line, Path, PathSegment, Point, Position
12
13
 
13
14
  if TYPE_CHECKING:
14
- from .pdfdancer_v1 import PDFDancer
15
+ from .pdfdancer_v2 import PDFDancer
15
16
 
16
17
 
17
18
  class PathBuilder:
@@ -31,16 +32,20 @@ class PathBuilder:
31
32
  """
32
33
  if client is None:
33
34
  raise ValidationException("Client cannot be null")
35
+ if page_number < 1:
36
+ raise ValidationException("Page number must be >= 1")
34
37
 
35
38
  self._client = client
36
39
  self._page_number = page_number
37
40
  self._segments: List[PathSegment] = []
38
41
  self._even_odd_fill: bool = False
39
- self._current_stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
42
+ self._current_stroke_color: Optional[Color] = Color.BLACK
40
43
  self._current_fill_color: Optional[Color] = None
41
44
  self._current_stroke_width: float = 1.0
42
45
  self._current_dash_array: Optional[List[float]] = None
43
46
  self._current_dash_phase: Optional[float] = None
47
+ self._current_point: Optional[Point] = None
48
+ self._subpath_start: Optional[Point] = None
44
49
 
45
50
  def stroke_color(self, color: Color) -> "PathBuilder":
46
51
  """
@@ -78,8 +83,8 @@ class PathBuilder:
78
83
  Returns:
79
84
  Self for method chaining
80
85
  """
81
- if width <= 0:
82
- raise ValidationException("Stroke width must be positive")
86
+ if not math.isfinite(width) or width < 0:
87
+ raise ValidationException("Stroke width must be finite and nonnegative")
83
88
  self._current_stroke_width = width
84
89
  return self
85
90
 
@@ -96,10 +101,62 @@ class PathBuilder:
96
101
  Returns:
97
102
  Self for method chaining
98
103
  """
99
- self._current_dash_array = dash_array
104
+ self._validate_dash(dash_array, dash_phase)
105
+ self._current_dash_array = list(dash_array)
100
106
  self._current_dash_phase = dash_phase
101
107
  return self
102
108
 
109
+ def move_to(self, x: float, y: float) -> "PathBuilder":
110
+ self._validate_coordinates(x, y)
111
+ self._current_point = Point(x, y)
112
+ self._subpath_start = self._current_point
113
+ return self
114
+
115
+ def line_to(self, x: float, y: float) -> "PathBuilder":
116
+ if self._current_point is None:
117
+ raise ValidationException("Call move_to() before line_to()")
118
+ self._validate_coordinates(x, y)
119
+ next_point = Point(x, y)
120
+ self.add_line(self._current_point, next_point)
121
+ self._current_point = next_point
122
+ return self
123
+
124
+ def bezier_to(
125
+ self,
126
+ control1_x: float,
127
+ control1_y: float,
128
+ control2_x: float,
129
+ control2_y: float,
130
+ x: float,
131
+ y: float,
132
+ ) -> "PathBuilder":
133
+ if self._current_point is None:
134
+ raise ValidationException("Call move_to() before bezier_to()")
135
+ self._validate_coordinates(control1_x, control1_y, control2_x, control2_y, x, y)
136
+ end = Point(x, y)
137
+ self.add_bezier(
138
+ self._current_point,
139
+ Point(control1_x, control1_y),
140
+ Point(control2_x, control2_y),
141
+ end,
142
+ )
143
+ self._current_point = end
144
+ return self
145
+
146
+ def close_path(self) -> "PathBuilder":
147
+ if self._current_point is None or self._subpath_start is None:
148
+ raise ValidationException("Call move_to() before close_path()")
149
+ if self._current_point != self._subpath_start:
150
+ self.line_to(self._subpath_start.x, self._subpath_start.y)
151
+ self._current_point = self._subpath_start
152
+ return self
153
+
154
+ def add_segment(self, segment: PathSegment) -> "PathBuilder":
155
+ if segment is None:
156
+ raise ValidationException("Path segment cannot be null")
157
+ self._segments.append(segment)
158
+ return self
159
+
103
160
  def solid(self) -> "PathBuilder":
104
161
  """
105
162
  Set segments to solid (no dash pattern).
@@ -176,6 +233,7 @@ class PathBuilder:
176
233
  Returns:
177
234
  Self for method chaining
178
235
  """
236
+ self._validate_coordinates(x, y, width, height)
179
237
  if width <= 0:
180
238
  raise ValidationException("Rectangle width must be positive")
181
239
  if height <= 0:
@@ -195,6 +253,69 @@ class PathBuilder:
195
253
 
196
254
  return self
197
255
 
256
+ def rectangle(
257
+ self, x: float, y: float, width: float, height: float
258
+ ) -> "PathBuilder":
259
+ return self.add_rectangle(x, y, width, height)
260
+
261
+ def circle(self, center_x: float, center_y: float, radius: float) -> "PathBuilder":
262
+ self._validate_coordinates(center_x, center_y, radius)
263
+ if radius <= 0:
264
+ raise ValidationException("Circle radius must be positive")
265
+ kappa = 0.5522847498 * radius
266
+ return (
267
+ self.move_to(center_x, center_y + radius)
268
+ .bezier_to(
269
+ center_x + kappa,
270
+ center_y + radius,
271
+ center_x + radius,
272
+ center_y + kappa,
273
+ center_x + radius,
274
+ center_y,
275
+ )
276
+ .bezier_to(
277
+ center_x + radius,
278
+ center_y - kappa,
279
+ center_x + kappa,
280
+ center_y - radius,
281
+ center_x,
282
+ center_y - radius,
283
+ )
284
+ .bezier_to(
285
+ center_x - kappa,
286
+ center_y - radius,
287
+ center_x - radius,
288
+ center_y - kappa,
289
+ center_x - radius,
290
+ center_y,
291
+ )
292
+ .bezier_to(
293
+ center_x - radius,
294
+ center_y + kappa,
295
+ center_x - kappa,
296
+ center_y + radius,
297
+ center_x,
298
+ center_y + radius,
299
+ )
300
+ .close_path()
301
+ )
302
+
303
+ @staticmethod
304
+ def _validate_coordinates(*values: float) -> None:
305
+ if any(not math.isfinite(value) for value in values):
306
+ raise ValidationException("Coordinates must be finite numbers")
307
+
308
+ @staticmethod
309
+ def _validate_dash(dash_array: List[float], dash_phase: float) -> None:
310
+ if dash_array is None:
311
+ raise ValidationException("Dash pattern cannot be null")
312
+ if any(not math.isfinite(value) or value < 0 for value in dash_array):
313
+ raise ValidationException("Dash values must be finite and nonnegative")
314
+ if dash_array and all(value == 0 for value in dash_array):
315
+ raise ValidationException("Dash pattern cannot be all zero")
316
+ if not math.isfinite(dash_phase) or dash_phase < 0:
317
+ raise ValidationException("Dash phase must be finite and nonnegative")
318
+
198
319
  def even_odd_fill(self, enabled: bool = True) -> "PathBuilder":
199
320
  """
200
321
  Set the fill rule to even-odd (vs nonzero winding).
@@ -772,7 +893,7 @@ class RectangleBuilder:
772
893
  top_left = Point(self._x, self._y + self._height)
773
894
 
774
895
  # Create four lines forming the rectangle
775
- lines = [
896
+ lines: List[PathSegment] = [
776
897
  Line(
777
898
  p0=bottom_left,
778
899
  p1=bottom_right,