pdfdancer-client-python 0.2.17__py3-none-any.whl → 0.2.18__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.

Potentially problematic release.


This version of pdfdancer-client-python might be problematic. Click here for more details.

@@ -0,0 +1,557 @@
1
+ """
2
+ PathBuilder for the PDFDancer Python client.
3
+ Provides fluent interface for constructing vector paths with lines and curves.
4
+ """
5
+
6
+ from typing import Optional, List
7
+
8
+ from .exceptions import ValidationException
9
+ from .models import Path, Line, Bezier, PathSegment, Point, Color, Position
10
+
11
+
12
+ class PathBuilder:
13
+ """
14
+ Builder class for constructing Path objects with fluent interface.
15
+ Allows building complex vector paths from multiple line and bezier segments.
16
+ All coordinates are absolute page coordinates.
17
+ """
18
+
19
+ def __init__(self, client: 'PDFDancer', page_index: int):
20
+ """
21
+ Initialize the path builder with a client reference and page index.
22
+
23
+ Args:
24
+ client: The PDFDancer instance for adding the path
25
+ page_index: The page number (0-indexed)
26
+ """
27
+ if client is None:
28
+ raise ValidationException("Client cannot be null")
29
+
30
+ self._client = client
31
+ self._page_index = page_index
32
+ self._segments: List[PathSegment] = []
33
+ self._even_odd_fill: bool = False
34
+ self._current_stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
35
+ self._current_fill_color: Optional[Color] = None
36
+ self._current_stroke_width: float = 1.0
37
+ self._current_dash_array: Optional[List[float]] = None
38
+ self._current_dash_phase: Optional[float] = None
39
+
40
+ def stroke_color(self, color: Color) -> 'PathBuilder':
41
+ """
42
+ Set the stroke color for subsequent segments.
43
+
44
+ Args:
45
+ color: The stroke color
46
+
47
+ Returns:
48
+ Self for method chaining
49
+ """
50
+ self._current_stroke_color = color
51
+ return self
52
+
53
+ def fill_color(self, color: Color) -> 'PathBuilder':
54
+ """
55
+ Set the fill color for subsequent segments.
56
+
57
+ Args:
58
+ color: The fill color
59
+
60
+ Returns:
61
+ Self for method chaining
62
+ """
63
+ self._current_fill_color = color
64
+ return self
65
+
66
+ def stroke_width(self, width: float) -> 'PathBuilder':
67
+ """
68
+ Set the stroke width for subsequent segments.
69
+
70
+ Args:
71
+ width: The stroke width in points
72
+
73
+ Returns:
74
+ Self for method chaining
75
+ """
76
+ if width <= 0:
77
+ raise ValidationException("Stroke width must be positive")
78
+ self._current_stroke_width = width
79
+ return self
80
+
81
+ def dash_pattern(self, dash_array: List[float], dash_phase: float = 0.0) -> 'PathBuilder':
82
+ """
83
+ Set a dash pattern for subsequent segments.
84
+
85
+ Args:
86
+ dash_array: List of on/off lengths (e.g., [10, 5] = 10pt on, 5pt off)
87
+ dash_phase: Offset into the pattern
88
+
89
+ Returns:
90
+ Self for method chaining
91
+ """
92
+ self._current_dash_array = dash_array
93
+ self._current_dash_phase = dash_phase
94
+ return self
95
+
96
+ def solid(self) -> 'PathBuilder':
97
+ """
98
+ Set segments to solid (no dash pattern).
99
+
100
+ Returns:
101
+ Self for method chaining
102
+ """
103
+ self._current_dash_array = None
104
+ self._current_dash_phase = None
105
+ return self
106
+
107
+ def add_line(self, p0: Point, p1: Point) -> 'PathBuilder':
108
+ """
109
+ Add a straight line segment to the path.
110
+
111
+ Args:
112
+ p0: Starting point
113
+ p1: Ending point
114
+
115
+ Returns:
116
+ Self for method chaining
117
+ """
118
+ line = Line(
119
+ p0=p0,
120
+ p1=p1,
121
+ stroke_color=self._current_stroke_color,
122
+ fill_color=self._current_fill_color,
123
+ stroke_width=self._current_stroke_width,
124
+ dash_array=self._current_dash_array,
125
+ dash_phase=self._current_dash_phase
126
+ )
127
+ self._segments.append(line)
128
+ return self
129
+
130
+ def add_bezier(self, p0: Point, p1: Point, p2: Point, p3: Point) -> 'PathBuilder':
131
+ """
132
+ Add a cubic Bezier curve segment to the path.
133
+
134
+ Args:
135
+ p0: Starting point
136
+ p1: First control point
137
+ p2: Second control point
138
+ p3: Ending point
139
+
140
+ Returns:
141
+ Self for method chaining
142
+ """
143
+ bezier = Bezier(
144
+ p0=p0,
145
+ p1=p1,
146
+ p2=p2,
147
+ p3=p3,
148
+ stroke_color=self._current_stroke_color,
149
+ fill_color=self._current_fill_color,
150
+ stroke_width=self._current_stroke_width,
151
+ dash_array=self._current_dash_array,
152
+ dash_phase=self._current_dash_phase
153
+ )
154
+ self._segments.append(bezier)
155
+ return self
156
+
157
+ def even_odd_fill(self, enabled: bool = True) -> 'PathBuilder':
158
+ """
159
+ Set the fill rule to even-odd (vs nonzero winding).
160
+
161
+ Args:
162
+ enabled: True for even-odd, False for nonzero winding
163
+
164
+ Returns:
165
+ Self for method chaining
166
+ """
167
+ self._even_odd_fill = enabled
168
+ return self
169
+
170
+ def add(self) -> bool:
171
+ """
172
+ Build the path and add it to the PDF document.
173
+
174
+ Returns:
175
+ True if successful
176
+
177
+ Raises:
178
+ ValidationException: If required properties are missing
179
+ """
180
+ if not self._segments:
181
+ raise ValidationException("Path must have at least one segment")
182
+
183
+ # Create position with only page index set
184
+ position = Position.at_page_coordinates(self._page_index, 0, 0)
185
+
186
+ # Build the Path object
187
+ path = Path(
188
+ position=position,
189
+ path_segments=self._segments,
190
+ even_odd_fill=self._even_odd_fill
191
+ )
192
+
193
+ # Add it using the client's internal method
194
+ # noinspection PyProtectedMember
195
+ return self._client._add_path(path)
196
+
197
+
198
+ class LineBuilder:
199
+ """
200
+ Builder class for constructing Line objects with fluent interface.
201
+ Mirrors the Java client LineBuilder API.
202
+ """
203
+
204
+ def __init__(self, client: 'PDFDancer', page_index: int):
205
+ """
206
+ Initialize the line builder.
207
+
208
+ Args:
209
+ client: The PDFDancer instance for adding the line
210
+ page_index: The page number (0-indexed)
211
+ """
212
+ if client is None:
213
+ raise ValidationException("Client cannot be null")
214
+
215
+ self._client = client
216
+ self._page_index = page_index
217
+ self._p0: Optional[Point] = None
218
+ self._p1: Optional[Point] = None
219
+ self._stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
220
+ self._fill_color: Optional[Color] = None
221
+ self._stroke_width: float = 1.0
222
+ self._dash_array: Optional[List[float]] = None
223
+ self._dash_phase: Optional[float] = None
224
+
225
+ def from_point(self, x: float, y: float) -> 'LineBuilder':
226
+ """
227
+ Set the starting point of the line (absolute page coordinates).
228
+
229
+ Args:
230
+ x: X coordinate on the page
231
+ y: Y coordinate on the page
232
+
233
+ Returns:
234
+ Self for method chaining
235
+ """
236
+ self._p0 = Point(x, y)
237
+ return self
238
+
239
+ def to_point(self, x: float, y: float) -> 'LineBuilder':
240
+ """
241
+ Set the ending point of the line (absolute page coordinates).
242
+
243
+ Args:
244
+ x: X coordinate on the page
245
+ y: Y coordinate on the page
246
+
247
+ Returns:
248
+ Self for method chaining
249
+ """
250
+ self._p1 = Point(x, y)
251
+ return self
252
+
253
+ def stroke_color(self, color: Color) -> 'LineBuilder':
254
+ """
255
+ Set the stroke color.
256
+
257
+ Args:
258
+ color: The stroke color
259
+
260
+ Returns:
261
+ Self for method chaining
262
+ """
263
+ self._stroke_color = color
264
+ return self
265
+
266
+ def fill_color(self, color: Color) -> 'LineBuilder':
267
+ """
268
+ Set the fill color.
269
+
270
+ Args:
271
+ color: The fill color
272
+
273
+ Returns:
274
+ Self for method chaining
275
+ """
276
+ self._fill_color = color
277
+ return self
278
+
279
+ def stroke_width(self, width: float) -> 'LineBuilder':
280
+ """
281
+ Set the stroke width.
282
+
283
+ Args:
284
+ width: The stroke width in points
285
+
286
+ Returns:
287
+ Self for method chaining
288
+ """
289
+ if width <= 0:
290
+ raise ValidationException("Stroke width must be positive")
291
+ self._stroke_width = width
292
+ return self
293
+
294
+ def dash_pattern(self, dash_array: List[float], dash_phase: float = 0.0) -> 'LineBuilder':
295
+ """
296
+ Set a dash pattern.
297
+
298
+ Args:
299
+ dash_array: List of on/off lengths (e.g., [10, 5] = 10pt on, 5pt off)
300
+ dash_phase: Offset into the pattern
301
+
302
+ Returns:
303
+ Self for method chaining
304
+ """
305
+ self._dash_array = dash_array
306
+ self._dash_phase = dash_phase
307
+ return self
308
+
309
+ def solid(self) -> 'LineBuilder':
310
+ """
311
+ Set line to solid (no dash pattern).
312
+
313
+ Returns:
314
+ Self for method chaining
315
+ """
316
+ self._dash_array = None
317
+ self._dash_phase = None
318
+ return self
319
+
320
+ def add(self) -> bool:
321
+ """
322
+ Build the line and add it to the PDF document.
323
+
324
+ Returns:
325
+ True if successful
326
+
327
+ Raises:
328
+ ValidationException: If required properties are missing
329
+ """
330
+ if self._p0 is None:
331
+ raise ValidationException("Line start point must be set using from_point()")
332
+ if self._p1 is None:
333
+ raise ValidationException("Line end point must be set using to_point()")
334
+
335
+ # Build the Line object
336
+ line = Line(
337
+ p0=self._p0,
338
+ p1=self._p1,
339
+ stroke_color=self._stroke_color,
340
+ fill_color=self._fill_color,
341
+ stroke_width=self._stroke_width,
342
+ dash_array=self._dash_array,
343
+ dash_phase=self._dash_phase
344
+ )
345
+
346
+ # Create position with only page index set
347
+ position = Position.at_page_coordinates(self._page_index, 0, 0)
348
+
349
+ # Wrap in Path with single segment
350
+ path = Path(
351
+ position=position,
352
+ path_segments=[line],
353
+ even_odd_fill=False
354
+ )
355
+
356
+ # Add it using the client's internal method
357
+ # noinspection PyProtectedMember
358
+ return self._client._add_path(path)
359
+
360
+
361
+ class BezierBuilder:
362
+ """
363
+ Builder class for constructing Bezier curve objects with fluent interface.
364
+ Mirrors the Java client BezierBuilder API.
365
+ """
366
+
367
+ def __init__(self, client: 'PDFDancer', page_index: int):
368
+ """
369
+ Initialize the bezier builder.
370
+
371
+ Args:
372
+ client: The PDFDancer instance for adding the bezier
373
+ page_index: The page number (0-indexed)
374
+ """
375
+ if client is None:
376
+ raise ValidationException("Client cannot be null")
377
+
378
+ self._client = client
379
+ self._page_index = page_index
380
+ self._p0: Optional[Point] = None
381
+ self._p1: Optional[Point] = None
382
+ self._p2: Optional[Point] = None
383
+ self._p3: Optional[Point] = None
384
+ self._stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
385
+ self._fill_color: Optional[Color] = None
386
+ self._stroke_width: float = 1.0
387
+ self._dash_array: Optional[List[float]] = None
388
+ self._dash_phase: Optional[float] = None
389
+
390
+ def from_point(self, x: float, y: float) -> 'BezierBuilder':
391
+ """
392
+ Set the starting point of the curve (absolute page coordinates).
393
+
394
+ Args:
395
+ x: X coordinate on the page
396
+ y: Y coordinate on the page
397
+
398
+ Returns:
399
+ Self for method chaining
400
+ """
401
+ self._p0 = Point(x, y)
402
+ return self
403
+
404
+ def control_point_1(self, x: float, y: float) -> 'BezierBuilder':
405
+ """
406
+ Set the first control point (absolute page coordinates).
407
+
408
+ Args:
409
+ x: X coordinate on the page
410
+ y: Y coordinate on the page
411
+
412
+ Returns:
413
+ Self for method chaining
414
+ """
415
+ self._p1 = Point(x, y)
416
+ return self
417
+
418
+ def control_point_2(self, x: float, y: float) -> 'BezierBuilder':
419
+ """
420
+ Set the second control point (absolute page coordinates).
421
+
422
+ Args:
423
+ x: X coordinate on the page
424
+ y: Y coordinate on the page
425
+
426
+ Returns:
427
+ Self for method chaining
428
+ """
429
+ self._p2 = Point(x, y)
430
+ return self
431
+
432
+ def to_point(self, x: float, y: float) -> 'BezierBuilder':
433
+ """
434
+ Set the ending point of the curve (absolute page coordinates).
435
+
436
+ Args:
437
+ x: X coordinate on the page
438
+ y: Y coordinate on the page
439
+
440
+ Returns:
441
+ Self for method chaining
442
+ """
443
+ self._p3 = Point(x, y)
444
+ return self
445
+
446
+ def stroke_color(self, color: Color) -> 'BezierBuilder':
447
+ """
448
+ Set the stroke color.
449
+
450
+ Args:
451
+ color: The stroke color
452
+
453
+ Returns:
454
+ Self for method chaining
455
+ """
456
+ self._stroke_color = color
457
+ return self
458
+
459
+ def fill_color(self, color: Color) -> 'BezierBuilder':
460
+ """
461
+ Set the fill color.
462
+
463
+ Args:
464
+ color: The fill color
465
+
466
+ Returns:
467
+ Self for method chaining
468
+ """
469
+ self._fill_color = color
470
+ return self
471
+
472
+ def stroke_width(self, width: float) -> 'BezierBuilder':
473
+ """
474
+ Set the stroke width.
475
+
476
+ Args:
477
+ width: The stroke width in points
478
+
479
+ Returns:
480
+ Self for method chaining
481
+ """
482
+ if width <= 0:
483
+ raise ValidationException("Stroke width must be positive")
484
+ self._stroke_width = width
485
+ return self
486
+
487
+ def dash_pattern(self, dash_array: List[float], dash_phase: float = 0.0) -> 'BezierBuilder':
488
+ """
489
+ Set a dash pattern.
490
+
491
+ Args:
492
+ dash_array: List of on/off lengths (e.g., [10, 5] = 10pt on, 5pt off)
493
+ dash_phase: Offset into the pattern
494
+
495
+ Returns:
496
+ Self for method chaining
497
+ """
498
+ self._dash_array = dash_array
499
+ self._dash_phase = dash_phase
500
+ return self
501
+
502
+ def solid(self) -> 'BezierBuilder':
503
+ """
504
+ Set curve to solid (no dash pattern).
505
+
506
+ Returns:
507
+ Self for method chaining
508
+ """
509
+ self._dash_array = None
510
+ self._dash_phase = None
511
+ return self
512
+
513
+ def add(self) -> bool:
514
+ """
515
+ Build the bezier curve and add it to the PDF document.
516
+
517
+ Returns:
518
+ True if successful
519
+
520
+ Raises:
521
+ ValidationException: If required properties are missing
522
+ """
523
+ if self._p0 is None:
524
+ raise ValidationException("Bezier start point must be set using from_point()")
525
+ if self._p1 is None:
526
+ raise ValidationException("Bezier first control point must be set using control_point_1()")
527
+ if self._p2 is None:
528
+ raise ValidationException("Bezier second control point must be set using control_point_2()")
529
+ if self._p3 is None:
530
+ raise ValidationException("Bezier end point must be set using to_point()")
531
+
532
+ # Build the Bezier object
533
+ bezier = Bezier(
534
+ p0=self._p0,
535
+ p1=self._p1,
536
+ p2=self._p2,
537
+ p3=self._p3,
538
+ stroke_color=self._stroke_color,
539
+ fill_color=self._fill_color,
540
+ stroke_width=self._stroke_width,
541
+ dash_array=self._dash_array,
542
+ dash_phase=self._dash_phase
543
+ )
544
+
545
+ # Create position with only page index set
546
+ position = Position.at_page_coordinates(self._page_index, 0, 0)
547
+
548
+ # Wrap in Path with single segment
549
+ path = Path(
550
+ position=position,
551
+ path_segments=[bezier],
552
+ even_odd_fill=False
553
+ )
554
+
555
+ # Add it using the client's internal method
556
+ # noinspection PyProtectedMember
557
+ return self._client._add_path(path)