pdfdancer-client-python 0.2.19__py3-none-any.whl → 0.2.20__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.
- pdfdancer/__init__.py +2 -0
- pdfdancer/image_builder.py +30 -0
- pdfdancer/models.py +446 -35
- pdfdancer/page_builder.py +92 -0
- pdfdancer/path_builder.py +252 -0
- pdfdancer/pdfdancer_v1.py +49 -15
- {pdfdancer_client_python-0.2.19.dist-info → pdfdancer_client_python-0.2.20.dist-info}/METADATA +1 -1
- pdfdancer_client_python-0.2.20.dist-info/RECORD +16 -0
- pdfdancer_client_python-0.2.19.dist-info/RECORD +0 -15
- {pdfdancer_client_python-0.2.19.dist-info → pdfdancer_client_python-0.2.20.dist-info}/WHEEL +0 -0
- {pdfdancer_client_python-0.2.19.dist-info → pdfdancer_client_python-0.2.20.dist-info}/licenses/LICENSE +0 -0
- {pdfdancer_client_python-0.2.19.dist-info → pdfdancer_client_python-0.2.20.dist-info}/licenses/NOTICE +0 -0
- {pdfdancer_client_python-0.2.19.dist-info → pdfdancer_client_python-0.2.20.dist-info}/top_level.txt +0 -0
pdfdancer/__init__.py
CHANGED
|
@@ -16,12 +16,14 @@ from .models import (
|
|
|
16
16
|
FontType, PathSegment, Line, Bezier, Path
|
|
17
17
|
)
|
|
18
18
|
from .paragraph_builder import ParagraphBuilder
|
|
19
|
+
from .page_builder import PageBuilder
|
|
19
20
|
from .path_builder import PathBuilder, LineBuilder, BezierBuilder
|
|
20
21
|
|
|
21
22
|
__version__ = "1.0.0"
|
|
22
23
|
__all__ = [
|
|
23
24
|
"PDFDancer",
|
|
24
25
|
"ParagraphBuilder",
|
|
26
|
+
"PageBuilder",
|
|
25
27
|
"PathBuilder",
|
|
26
28
|
"LineBuilder",
|
|
27
29
|
"BezierBuilder",
|
pdfdancer/image_builder.py
CHANGED
|
@@ -27,4 +27,34 @@ class ImageBuilder:
|
|
|
27
27
|
return self
|
|
28
28
|
|
|
29
29
|
def add(self) -> bool:
|
|
30
|
+
# noinspection PyProtectedMember
|
|
31
|
+
return self._client._add_image(self._image, self._image.position)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ImageOnPageBuilder:
|
|
35
|
+
|
|
36
|
+
def __init__(self, client: 'PDFDancer', page_index: int):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the image builder with a client reference.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
client: The PDFDancer instance for font registration
|
|
42
|
+
"""
|
|
43
|
+
if client is None:
|
|
44
|
+
raise ValidationException("Client cannot be null")
|
|
45
|
+
|
|
46
|
+
self._client = client
|
|
47
|
+
self._image = Image()
|
|
48
|
+
self._page_index = page_index
|
|
49
|
+
|
|
50
|
+
def from_file(self, img_path: Path) -> 'ImageOnPageBuilder':
|
|
51
|
+
self._image.data = img_path.read_bytes()
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def at(self, x, y) -> 'ImageOnPageBuilder':
|
|
55
|
+
self._image.position = Position.at_page_coordinates(self._page_index, x, y)
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def add(self) -> bool:
|
|
59
|
+
# noinspection PyProtectedMember
|
|
30
60
|
return self._client._add_image(self._image, self._image.position)
|
pdfdancer/models.py
CHANGED
|
@@ -9,7 +9,36 @@ from typing import Optional, List, Any, Dict, Mapping, Tuple, ClassVar, Union
|
|
|
9
9
|
|
|
10
10
|
@dataclass(frozen=True)
|
|
11
11
|
class PageSize:
|
|
12
|
-
"""Represents a page size specification, covering both standard and custom dimensions.
|
|
12
|
+
"""Represents a page size specification, covering both standard and custom dimensions.
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
- name: Optional canonical name of the size (e.g. "A4", "LETTER"). Will be upper‑cased.
|
|
16
|
+
- width: Page width in PDF points (1/72 inch).
|
|
17
|
+
- height: Page height in PDF points (1/72 inch).
|
|
18
|
+
|
|
19
|
+
Notes:
|
|
20
|
+
- Use `PageSize.from_name()` or the convenience constants (e.g. `PageSize.A4`) for common sizes.
|
|
21
|
+
- `width` and `height` must be positive numbers and are validated in `__post_init__`.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
- From standard name:
|
|
25
|
+
```python
|
|
26
|
+
size = PageSize.from_name("A4") # or PageSize.A4
|
|
27
|
+
```
|
|
28
|
+
- From custom dimensions:
|
|
29
|
+
```python
|
|
30
|
+
size = PageSize(name=None, width=500.0, height=700.0)
|
|
31
|
+
```
|
|
32
|
+
- From dict (e.g. deserialized JSON):
|
|
33
|
+
```python
|
|
34
|
+
size = PageSize.from_dict({"width": 612, "height": 792, "name": "letter"})
|
|
35
|
+
```
|
|
36
|
+
- Coercion utility:
|
|
37
|
+
```python
|
|
38
|
+
size = PageSize.coerce("A4")
|
|
39
|
+
size = PageSize.coerce({"width": 300, "height": 300})
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
13
42
|
|
|
14
43
|
name: Optional[str]
|
|
15
44
|
width: float
|
|
@@ -154,6 +183,7 @@ class StandardFonts(Enum):
|
|
|
154
183
|
|
|
155
184
|
|
|
156
185
|
class ObjectType(Enum):
|
|
186
|
+
"""Server object type discriminator used in refs, requests, and snapshots."""
|
|
157
187
|
FORM_FIELD = "FORM_FIELD"
|
|
158
188
|
IMAGE = "IMAGE"
|
|
159
189
|
FORM_X_OBJECT = "FORM_X_OBJECT"
|
|
@@ -215,7 +245,39 @@ class BoundingRect:
|
|
|
215
245
|
@dataclass
|
|
216
246
|
class Position:
|
|
217
247
|
"""
|
|
218
|
-
|
|
248
|
+
Spatial locator used to find or place objects on a page.
|
|
249
|
+
|
|
250
|
+
Parameters:
|
|
251
|
+
- page_index: Zero-based page index this position refers to. Required for most operations
|
|
252
|
+
that place or search on a specific page; use `Position.at_page()` as a shortcut.
|
|
253
|
+
- shape: Optional geometric shape used when matching by area (`POINT`, `LINE`, `CIRCLE`, `RECT`).
|
|
254
|
+
- mode: How to match objects relative to the shape (`INTERSECT` or `CONTAINS`).
|
|
255
|
+
- bounding_rect: Rectangle describing the area or point (for `POINT`, width/height are 0).
|
|
256
|
+
- text_starts_with: Filter for text objects whose content starts with this string.
|
|
257
|
+
- text_pattern: Regex pattern to match text content.
|
|
258
|
+
- name: Named anchor or element name to target (e.g. form field name).
|
|
259
|
+
|
|
260
|
+
Builder helpers:
|
|
261
|
+
- `Position.at_page(page_index)` – target a whole page.
|
|
262
|
+
- `Position.at_page_coordinates(page_index, x, y)` – target a point on a page.
|
|
263
|
+
- `Position.by_name(name)` – target object(s) by name.
|
|
264
|
+
- `pos.at_coordinates(Point(x, y))` – switch to a point on the current page.
|
|
265
|
+
- `pos.move_x(dx)`, `pos.move_y(dy)` – offset the current coordinates.
|
|
266
|
+
|
|
267
|
+
Examples:
|
|
268
|
+
```python
|
|
269
|
+
# A point on page 0
|
|
270
|
+
pos = Position.at_page_coordinates(0, x=72, y=720)
|
|
271
|
+
|
|
272
|
+
# Search by name (e.g. a form field) and then move down 12 points
|
|
273
|
+
pos = Position.by_name("Email").move_y(-12)
|
|
274
|
+
|
|
275
|
+
# Match anything intersecting a rectangular area on page 1
|
|
276
|
+
pos = Position.at_page(1)
|
|
277
|
+
pos.shape = ShapeType.RECT
|
|
278
|
+
pos.mode = PositionMode.INTERSECT
|
|
279
|
+
pos.bounding_rect = BoundingRect(x=100, y=100, width=200, height=50)
|
|
280
|
+
```
|
|
219
281
|
"""
|
|
220
282
|
page_index: Optional[int] = None
|
|
221
283
|
shape: Optional[ShapeType] = None
|
|
@@ -287,7 +349,24 @@ class Position:
|
|
|
287
349
|
@dataclass
|
|
288
350
|
class ObjectRef:
|
|
289
351
|
"""
|
|
290
|
-
|
|
352
|
+
Reference to an object in a PDF document returned by the server.
|
|
353
|
+
|
|
354
|
+
Parameters:
|
|
355
|
+
- internal_id: Server-side identifier for the object.
|
|
356
|
+
- position: Position information describing where the object is.
|
|
357
|
+
- type: Object type (see `ObjectType`).
|
|
358
|
+
|
|
359
|
+
Usage:
|
|
360
|
+
- Instances are typically returned in snapshots or find results.
|
|
361
|
+
- Pass an `ObjectRef` to request objects such as `MoveRequest`, `DeleteRequest`,
|
|
362
|
+
`ModifyRequest`, or `ModifyTextRequest`.
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
```python
|
|
366
|
+
# Move an object to a new position
|
|
367
|
+
new_pos = Position.at_page_coordinates(0, 120, 500)
|
|
368
|
+
payload = MoveRequest(object_ref=obj_ref, position=new_pos).to_dict()
|
|
369
|
+
```
|
|
291
370
|
"""
|
|
292
371
|
internal_id: str
|
|
293
372
|
position: Position
|
|
@@ -325,7 +404,23 @@ class ObjectRef:
|
|
|
325
404
|
|
|
326
405
|
@dataclass
|
|
327
406
|
class Color:
|
|
328
|
-
"""
|
|
407
|
+
"""RGB color with optional alpha channel.
|
|
408
|
+
|
|
409
|
+
Parameters:
|
|
410
|
+
- r: Red component (0-255)
|
|
411
|
+
- g: Green component (0-255)
|
|
412
|
+
- b: Blue component (0-255)
|
|
413
|
+
- a: Alpha component (0-255), default 255 (opaque)
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
- ValueError: If any component is outside 0-255.
|
|
417
|
+
|
|
418
|
+
Example:
|
|
419
|
+
```python
|
|
420
|
+
red = Color(255, 0, 0)
|
|
421
|
+
semi_transparent_black = Color(0, 0, 0, a=128)
|
|
422
|
+
```
|
|
423
|
+
"""
|
|
329
424
|
r: int
|
|
330
425
|
g: int
|
|
331
426
|
b: int
|
|
@@ -339,7 +434,23 @@ class Color:
|
|
|
339
434
|
|
|
340
435
|
@dataclass
|
|
341
436
|
class Font:
|
|
342
|
-
"""
|
|
437
|
+
"""Font face and size.
|
|
438
|
+
|
|
439
|
+
Parameters:
|
|
440
|
+
- name: Font family name. Can be one of `StandardFonts` values or any embedded font name.
|
|
441
|
+
- size: Font size in points (> 0).
|
|
442
|
+
|
|
443
|
+
Raises:
|
|
444
|
+
- ValueError: If `size` is not positive.
|
|
445
|
+
|
|
446
|
+
Example:
|
|
447
|
+
```python
|
|
448
|
+
from pdfdancer.models import Font, StandardFonts
|
|
449
|
+
|
|
450
|
+
title_font = Font(name=StandardFonts.HELVETICA_BOLD.value, size=16)
|
|
451
|
+
body_font = Font(name="MyEmbeddedFont", size=10.5)
|
|
452
|
+
```
|
|
453
|
+
"""
|
|
343
454
|
name: str
|
|
344
455
|
size: float
|
|
345
456
|
|
|
@@ -351,10 +462,18 @@ class Font:
|
|
|
351
462
|
@dataclass
|
|
352
463
|
class PathSegment:
|
|
353
464
|
"""
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
465
|
+
Base class for vector path segments.
|
|
466
|
+
|
|
467
|
+
Parameters:
|
|
468
|
+
- stroke_color: Outline color for the segment (`Color`).
|
|
469
|
+
- fill_color: Fill color for closed shapes when applicable (`Color`).
|
|
470
|
+
- stroke_width: Line width in points.
|
|
471
|
+
- dash_array: Dash pattern (e.g. `[3, 2]` for 3 on, 2 off). None or empty for solid.
|
|
472
|
+
- dash_phase: Offset into the dash pattern.
|
|
473
|
+
|
|
474
|
+
Notes:
|
|
475
|
+
- Concrete subclasses are `Line` and `Bezier`.
|
|
476
|
+
- Used inside `Path.path_segments` and serialized by `AddRequest`.
|
|
358
477
|
"""
|
|
359
478
|
stroke_color: Optional[Color] = None
|
|
360
479
|
fill_color: Optional[Color] = None
|
|
@@ -386,9 +505,19 @@ class PathSegment:
|
|
|
386
505
|
@dataclass
|
|
387
506
|
class Line(PathSegment):
|
|
388
507
|
"""
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
508
|
+
Straight line segment between two points.
|
|
509
|
+
|
|
510
|
+
Parameters:
|
|
511
|
+
- p0: Start point.
|
|
512
|
+
- p1: End point.
|
|
513
|
+
|
|
514
|
+
Example:
|
|
515
|
+
```python
|
|
516
|
+
from pdfdancer.models import Line, Point, Path
|
|
517
|
+
|
|
518
|
+
line = Line(p0=Point(10, 10), p1=Point(100, 10))
|
|
519
|
+
path = Path(path_segments=[line])
|
|
520
|
+
```
|
|
392
521
|
"""
|
|
393
522
|
p0: Optional[Point] = None
|
|
394
523
|
p1: Optional[Point] = None
|
|
@@ -405,9 +534,20 @@ class Line(PathSegment):
|
|
|
405
534
|
@dataclass
|
|
406
535
|
class Bezier(PathSegment):
|
|
407
536
|
"""
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
537
|
+
Cubic Bezier curve segment defined by 4 points.
|
|
538
|
+
|
|
539
|
+
Parameters:
|
|
540
|
+
- p0: Start point.
|
|
541
|
+
- p1: First control point.
|
|
542
|
+
- p2: Second control point.
|
|
543
|
+
- p3: End point.
|
|
544
|
+
|
|
545
|
+
Example:
|
|
546
|
+
```python
|
|
547
|
+
curve = Bezier(
|
|
548
|
+
p0=Point(10, 10), p1=Point(50, 80), p2=Point(80, 50), p3=Point(120, 10)
|
|
549
|
+
)
|
|
550
|
+
```
|
|
411
551
|
"""
|
|
412
552
|
p0: Optional[Point] = None
|
|
413
553
|
p1: Optional[Point] = None
|
|
@@ -434,9 +574,28 @@ class Bezier(PathSegment):
|
|
|
434
574
|
@dataclass
|
|
435
575
|
class Path:
|
|
436
576
|
"""
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
577
|
+
Vector path composed of one or more `PathSegment`s.
|
|
578
|
+
|
|
579
|
+
Parameters:
|
|
580
|
+
- position: Where to place the path on the page.
|
|
581
|
+
- path_segments: List of `Line` and/or `Bezier` segments.
|
|
582
|
+
- even_odd_fill: If True, use even-odd rule for fills; otherwise nonzero winding.
|
|
583
|
+
|
|
584
|
+
Example (adding a triangle to a page):
|
|
585
|
+
```python
|
|
586
|
+
from pdfdancer.models import Path, Line, Point, Position, AddRequest
|
|
587
|
+
|
|
588
|
+
tri = Path(
|
|
589
|
+
position=Position.at_page_coordinates(0, 100, 100),
|
|
590
|
+
path_segments=[
|
|
591
|
+
Line(Point(0, 0), Point(50, 100)),
|
|
592
|
+
Line(Point(50, 100), Point(100, 0)),
|
|
593
|
+
Line(Point(100, 0), Point(0, 0)),
|
|
594
|
+
],
|
|
595
|
+
even_odd_fill=True,
|
|
596
|
+
)
|
|
597
|
+
payload = AddRequest(tri).to_dict()
|
|
598
|
+
```
|
|
440
599
|
"""
|
|
441
600
|
position: Optional[Position] = None
|
|
442
601
|
path_segments: Optional[List[PathSegment]] = None
|
|
@@ -462,7 +621,28 @@ class Path:
|
|
|
462
621
|
@dataclass
|
|
463
622
|
class Image:
|
|
464
623
|
"""
|
|
465
|
-
|
|
624
|
+
Raster image to be placed on a page.
|
|
625
|
+
|
|
626
|
+
Parameters:
|
|
627
|
+
- position: Where to place the image. Use `Position.at_page_coordinates(page, x, y)`.
|
|
628
|
+
- format: Image format hint for the server (e.g. "PNG", "JPEG"). Optional.
|
|
629
|
+
- width: Target width in points. Optional; server may infer from data.
|
|
630
|
+
- height: Target height in points. Optional; server may infer from data.
|
|
631
|
+
- data: Raw image bytes. If provided, it will be base64-encoded in `AddRequest.to_dict()`.
|
|
632
|
+
|
|
633
|
+
Example:
|
|
634
|
+
```python
|
|
635
|
+
from pdfdancer.models import Image, Position, AddRequest
|
|
636
|
+
|
|
637
|
+
img = Image(
|
|
638
|
+
position=Position.at_page_coordinates(0, 72, 600),
|
|
639
|
+
format="PNG",
|
|
640
|
+
width=128,
|
|
641
|
+
height=64,
|
|
642
|
+
data=open("/path/logo.png", "rb").read(),
|
|
643
|
+
)
|
|
644
|
+
payload = AddRequest(img).to_dict()
|
|
645
|
+
```
|
|
466
646
|
"""
|
|
467
647
|
position: Optional[Position] = None
|
|
468
648
|
format: Optional[str] = None
|
|
@@ -482,7 +662,29 @@ class Image:
|
|
|
482
662
|
@dataclass
|
|
483
663
|
class Paragraph:
|
|
484
664
|
"""
|
|
485
|
-
|
|
665
|
+
Multi-line text paragraph to add to a page.
|
|
666
|
+
|
|
667
|
+
Parameters:
|
|
668
|
+
- position: Anchor position where the first line begins.
|
|
669
|
+
- text_lines: List of strings, one per line. Use `\n` within a string only if desired; normally
|
|
670
|
+
provide separate entries for multiple lines.
|
|
671
|
+
- font: Font to use for all text elements unless overridden later.
|
|
672
|
+
- color: Text color.
|
|
673
|
+
- line_spacing: Distance multiplier between lines. Server expects a list, handled for you by `AddRequest`.
|
|
674
|
+
|
|
675
|
+
Example:
|
|
676
|
+
```python
|
|
677
|
+
from pdfdancer.models import Paragraph, Position, Font, Color, StandardFonts, AddRequest
|
|
678
|
+
|
|
679
|
+
para = Paragraph(
|
|
680
|
+
position=Position.at_page_coordinates(0, 72, 700),
|
|
681
|
+
text_lines=["Hello", "PDFDancer!"],
|
|
682
|
+
font=Font(StandardFonts.HELVETICA.value, 12),
|
|
683
|
+
color=Color(50, 50, 50),
|
|
684
|
+
line_spacing=1.4,
|
|
685
|
+
)
|
|
686
|
+
payload = AddRequest(para).to_dict()
|
|
687
|
+
```
|
|
486
688
|
"""
|
|
487
689
|
position: Optional[Position] = None
|
|
488
690
|
text_lines: Optional[List[str]] = None
|
|
@@ -502,7 +704,22 @@ class Paragraph:
|
|
|
502
704
|
# Request classes for API communication
|
|
503
705
|
@dataclass
|
|
504
706
|
class FindRequest:
|
|
505
|
-
"""Request
|
|
707
|
+
"""Request for locating objects.
|
|
708
|
+
|
|
709
|
+
Parameters:
|
|
710
|
+
- object_type: Filter by `ObjectType` (optional). If None, all types may be returned.
|
|
711
|
+
- position: `Position` describing where/how to search.
|
|
712
|
+
- hint: Optional backend hint or free-form note to influence matching.
|
|
713
|
+
|
|
714
|
+
Usage:
|
|
715
|
+
```python
|
|
716
|
+
req = FindRequest(
|
|
717
|
+
object_type=ObjectType.TEXT_LINE,
|
|
718
|
+
position=Position.at_page_coordinates(0, 72, 700).with_text_starts("Hello"),
|
|
719
|
+
)
|
|
720
|
+
payload = req.to_dict()
|
|
721
|
+
```
|
|
722
|
+
"""
|
|
506
723
|
object_type: Optional[ObjectType]
|
|
507
724
|
position: Optional[Position]
|
|
508
725
|
hint: Optional[str] = None
|
|
@@ -541,7 +758,16 @@ class FindRequest:
|
|
|
541
758
|
|
|
542
759
|
@dataclass
|
|
543
760
|
class DeleteRequest:
|
|
544
|
-
"""Request
|
|
761
|
+
"""Request to delete an existing object.
|
|
762
|
+
|
|
763
|
+
Parameters:
|
|
764
|
+
- object_ref: The object to delete.
|
|
765
|
+
|
|
766
|
+
Example:
|
|
767
|
+
```python
|
|
768
|
+
payload = DeleteRequest(object_ref=obj_ref).to_dict()
|
|
769
|
+
```
|
|
770
|
+
"""
|
|
545
771
|
object_ref: ObjectRef
|
|
546
772
|
|
|
547
773
|
def to_dict(self) -> dict:
|
|
@@ -554,7 +780,19 @@ class DeleteRequest:
|
|
|
554
780
|
|
|
555
781
|
@dataclass
|
|
556
782
|
class MoveRequest:
|
|
557
|
-
"""Request object
|
|
783
|
+
"""Request to move an existing object to a new position.
|
|
784
|
+
|
|
785
|
+
Parameters:
|
|
786
|
+
- object_ref: The object to move (obtained from a snapshot or find call).
|
|
787
|
+
- position: The new target `Position` (commonly a point created with `Position.at_page_coordinates`).
|
|
788
|
+
|
|
789
|
+
Example:
|
|
790
|
+
```python
|
|
791
|
+
new_pos = Position.at_page_coordinates(0, 200, 500)
|
|
792
|
+
req = MoveRequest(object_ref=obj_ref, position=new_pos)
|
|
793
|
+
payload = req.to_dict()
|
|
794
|
+
```
|
|
795
|
+
"""
|
|
558
796
|
object_ref: ObjectRef
|
|
559
797
|
position: Position
|
|
560
798
|
|
|
@@ -570,7 +808,19 @@ class MoveRequest:
|
|
|
570
808
|
|
|
571
809
|
@dataclass
|
|
572
810
|
class PageMoveRequest:
|
|
573
|
-
"""Request
|
|
811
|
+
"""Request to reorder pages.
|
|
812
|
+
|
|
813
|
+
Parameters:
|
|
814
|
+
- from_page_index: Zero-based index of the page to move.
|
|
815
|
+
- to_page_index: Zero-based destination index.
|
|
816
|
+
|
|
817
|
+
Example:
|
|
818
|
+
```python
|
|
819
|
+
# Move first page to the end
|
|
820
|
+
req = PageMoveRequest(from_page_index=0, to_page_index=doc_page_count - 1)
|
|
821
|
+
payload = req.to_dict()
|
|
822
|
+
```
|
|
823
|
+
"""
|
|
574
824
|
from_page_index: int
|
|
575
825
|
to_page_index: int
|
|
576
826
|
|
|
@@ -581,9 +831,60 @@ class PageMoveRequest:
|
|
|
581
831
|
}
|
|
582
832
|
|
|
583
833
|
|
|
834
|
+
@dataclass
|
|
835
|
+
class AddPageRequest:
|
|
836
|
+
"""Request to add a new page to the document.
|
|
837
|
+
|
|
838
|
+
Parameters:
|
|
839
|
+
- page_index: Optional zero-based index where the new page should be inserted.
|
|
840
|
+
- orientation: Optional page orientation (portrait or landscape).
|
|
841
|
+
- page_size: Optional size of the page.
|
|
842
|
+
|
|
843
|
+
Only populated fields are sent to the server to maintain backward compatibility
|
|
844
|
+
with default server behavior.
|
|
845
|
+
"""
|
|
846
|
+
page_index: Optional[int] = None
|
|
847
|
+
orientation: Optional[Orientation] = None
|
|
848
|
+
page_size: Optional[PageSize] = None
|
|
849
|
+
|
|
850
|
+
def to_dict(self) -> dict:
|
|
851
|
+
payload: Dict[str, Any] = {}
|
|
852
|
+
if self.page_index is not None:
|
|
853
|
+
payload["pageIndex"] = int(self.page_index)
|
|
854
|
+
if self.orientation is not None:
|
|
855
|
+
orientation_value: Orientation
|
|
856
|
+
if isinstance(self.orientation, Orientation):
|
|
857
|
+
orientation_value = self.orientation
|
|
858
|
+
elif isinstance(self.orientation, str):
|
|
859
|
+
normalized = self.orientation.strip().upper()
|
|
860
|
+
orientation_value = Orientation(normalized)
|
|
861
|
+
else:
|
|
862
|
+
raise TypeError("Orientation must be an Orientation enum or string value")
|
|
863
|
+
payload["orientation"] = orientation_value.value
|
|
864
|
+
if self.page_size is not None:
|
|
865
|
+
page_size = PageSize.coerce(self.page_size)
|
|
866
|
+
payload["pageSize"] = page_size.to_dict()
|
|
867
|
+
return payload
|
|
868
|
+
|
|
869
|
+
|
|
584
870
|
@dataclass
|
|
585
871
|
class AddRequest:
|
|
586
|
-
"""Request object
|
|
872
|
+
"""Request to add a new object to the document.
|
|
873
|
+
|
|
874
|
+
Parameters:
|
|
875
|
+
- pdf_object: The object to add (e.g. `Image`, `Paragraph`, or `Path`).
|
|
876
|
+
|
|
877
|
+
Usage:
|
|
878
|
+
```python
|
|
879
|
+
para = Paragraph(position=Position.at_page_coordinates(0, 72, 700), text_lines=["Hello"])
|
|
880
|
+
req = AddRequest(para)
|
|
881
|
+
payload = req.to_dict() # ready to send to the server API
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
Notes:
|
|
885
|
+
- Serialization details (like base64 for image `data`, or per-segment position for paths)
|
|
886
|
+
are handled for you in `to_dict()`.
|
|
887
|
+
"""
|
|
587
888
|
pdf_object: Any # Can be Image, Paragraph, etc.
|
|
588
889
|
|
|
589
890
|
def to_dict(self) -> dict:
|
|
@@ -727,7 +1028,19 @@ class AddRequest:
|
|
|
727
1028
|
|
|
728
1029
|
@dataclass
|
|
729
1030
|
class ModifyRequest:
|
|
730
|
-
"""Request object
|
|
1031
|
+
"""Request to replace an object with a new one of possibly different type.
|
|
1032
|
+
|
|
1033
|
+
Parameters:
|
|
1034
|
+
- object_ref: The existing object to replace.
|
|
1035
|
+
- new_object: The replacement object (e.g. `Paragraph`, `Image`, or `Path`).
|
|
1036
|
+
|
|
1037
|
+
Example:
|
|
1038
|
+
```python
|
|
1039
|
+
new_para = Paragraph(position=old.position, text_lines=["Updated text"])
|
|
1040
|
+
req = ModifyRequest(object_ref=old, new_object=new_para)
|
|
1041
|
+
payload = req.to_dict()
|
|
1042
|
+
```
|
|
1043
|
+
"""
|
|
731
1044
|
object_ref: ObjectRef
|
|
732
1045
|
new_object: Any
|
|
733
1046
|
|
|
@@ -742,7 +1055,18 @@ class ModifyRequest:
|
|
|
742
1055
|
|
|
743
1056
|
@dataclass
|
|
744
1057
|
class ModifyTextRequest:
|
|
745
|
-
"""Request
|
|
1058
|
+
"""Request to change the text content of a text object.
|
|
1059
|
+
|
|
1060
|
+
Parameters:
|
|
1061
|
+
- object_ref: The text object to modify (e.g. a `TextObjectRef`).
|
|
1062
|
+
- new_text: Replacement text content.
|
|
1063
|
+
|
|
1064
|
+
Example:
|
|
1065
|
+
```python
|
|
1066
|
+
req = ModifyTextRequest(object_ref=text_ref, new_text="Hello world")
|
|
1067
|
+
payload = req.to_dict()
|
|
1068
|
+
```
|
|
1069
|
+
"""
|
|
746
1070
|
object_ref: ObjectRef
|
|
747
1071
|
new_text: str
|
|
748
1072
|
|
|
@@ -757,6 +1081,19 @@ class ModifyTextRequest:
|
|
|
757
1081
|
|
|
758
1082
|
@dataclass
|
|
759
1083
|
class ChangeFormFieldRequest:
|
|
1084
|
+
"""Request to set a form field's value.
|
|
1085
|
+
|
|
1086
|
+
Parameters:
|
|
1087
|
+
- object_ref: A `FormFieldRef` (or generic `ObjectRef`) identifying the field.
|
|
1088
|
+
- value: The new value as a string. For checkboxes/radio buttons, use the
|
|
1089
|
+
appropriate on/off/selection string per the document's field options.
|
|
1090
|
+
|
|
1091
|
+
Example:
|
|
1092
|
+
```python
|
|
1093
|
+
req = ChangeFormFieldRequest(object_ref=field_ref, value="Jane Doe")
|
|
1094
|
+
payload = req.to_dict()
|
|
1095
|
+
```
|
|
1096
|
+
"""
|
|
760
1097
|
object_ref: ObjectRef
|
|
761
1098
|
value: str
|
|
762
1099
|
|
|
@@ -772,8 +1109,20 @@ class ChangeFormFieldRequest:
|
|
|
772
1109
|
@dataclass
|
|
773
1110
|
class FormFieldRef(ObjectRef):
|
|
774
1111
|
"""
|
|
775
|
-
|
|
776
|
-
|
|
1112
|
+
Reference to a form field object with name and value.
|
|
1113
|
+
|
|
1114
|
+
Parameters (usually provided by the server):
|
|
1115
|
+
- internal_id: Identifier of the form field object.
|
|
1116
|
+
- position: Position of the field.
|
|
1117
|
+
- type: One of `ObjectType.TEXT_FIELD`, `ObjectType.CHECK_BOX`, etc.
|
|
1118
|
+
- name: Field name (as defined inside the PDF).
|
|
1119
|
+
- value: Current field value (string representation).
|
|
1120
|
+
|
|
1121
|
+
Usage:
|
|
1122
|
+
- You can pass a `FormFieldRef` to `ChangeFormFieldRequest` to update its value.
|
|
1123
|
+
```python
|
|
1124
|
+
payload = ChangeFormFieldRequest(object_ref=field_ref, value="john@doe.com").to_dict()
|
|
1125
|
+
```
|
|
777
1126
|
"""
|
|
778
1127
|
name: Optional[str] = None
|
|
779
1128
|
value: Optional[str] = None
|
|
@@ -841,8 +1190,23 @@ class TextStatus:
|
|
|
841
1190
|
|
|
842
1191
|
class TextObjectRef(ObjectRef):
|
|
843
1192
|
"""
|
|
844
|
-
Represents a text object
|
|
845
|
-
|
|
1193
|
+
Represents a text object with additional properties and optional hierarchy.
|
|
1194
|
+
|
|
1195
|
+
Parameters (typically provided by the server):
|
|
1196
|
+
- internal_id: Identifier of the text object.
|
|
1197
|
+
- position: Position of the text object.
|
|
1198
|
+
- object_type: `ObjectType.TEXT_LINE` or another text-related type.
|
|
1199
|
+
- text: Text content, when available.
|
|
1200
|
+
- font_name: Name of the font used for the text.
|
|
1201
|
+
- font_size: Size of the font in points.
|
|
1202
|
+
- line_spacings: Optional list of line spacing values for multi-line objects.
|
|
1203
|
+
- color: Text color.
|
|
1204
|
+
- status: `TextStatus` providing modification/encoding info.
|
|
1205
|
+
|
|
1206
|
+
Usage:
|
|
1207
|
+
- Instances are returned by find/snapshot APIs. You generally should not instantiate
|
|
1208
|
+
them manually, but you may read their properties or pass their `ObjectRef`-like
|
|
1209
|
+
identity to modification requests (e.g., `ModifyTextRequest`).
|
|
846
1210
|
"""
|
|
847
1211
|
|
|
848
1212
|
def __init__(self, internal_id: str, position: Position, object_type: ObjectType,
|
|
@@ -890,8 +1254,18 @@ class TextObjectRef(ObjectRef):
|
|
|
890
1254
|
@dataclass
|
|
891
1255
|
class PageRef(ObjectRef):
|
|
892
1256
|
"""
|
|
893
|
-
|
|
894
|
-
|
|
1257
|
+
Reference to a page with size and orientation metadata.
|
|
1258
|
+
|
|
1259
|
+
Parameters (usually provided by the server):
|
|
1260
|
+
- internal_id: Identifier of the page object.
|
|
1261
|
+
- position: Position referencing the page (often via `Position.at_page(page_index)`).
|
|
1262
|
+
- type: Should be `ObjectType.PAGE`.
|
|
1263
|
+
- page_size: `PageSize` of the page.
|
|
1264
|
+
- orientation: `Orientation.PORTRAIT` or `Orientation.LANDSCAPE`.
|
|
1265
|
+
|
|
1266
|
+
Usage:
|
|
1267
|
+
- Returned inside `PageSnapshot` objects. You can inspect page size/orientation
|
|
1268
|
+
and use the page index for subsequent operations.
|
|
895
1269
|
"""
|
|
896
1270
|
page_size: Optional[PageSize]
|
|
897
1271
|
orientation: Optional[Orientation]
|
|
@@ -908,7 +1282,22 @@ class PageRef(ObjectRef):
|
|
|
908
1282
|
@dataclass
|
|
909
1283
|
class CommandResult:
|
|
910
1284
|
"""
|
|
911
|
-
|
|
1285
|
+
Outcome returned by certain API endpoints.
|
|
1286
|
+
|
|
1287
|
+
Parameters:
|
|
1288
|
+
- command_name: Name of the executed command on the server.
|
|
1289
|
+
- element_id: Optional related element ID (when applicable).
|
|
1290
|
+
- message: Informational message or error description.
|
|
1291
|
+
- success: Whether the command succeeded.
|
|
1292
|
+
- warning: Optional warning details.
|
|
1293
|
+
|
|
1294
|
+
Example:
|
|
1295
|
+
```python
|
|
1296
|
+
# Parse from a server JSON response dict
|
|
1297
|
+
result = CommandResult.from_dict(resp_json)
|
|
1298
|
+
if not result.success:
|
|
1299
|
+
print("Operation failed:", result.message)
|
|
1300
|
+
```
|
|
912
1301
|
"""
|
|
913
1302
|
command_name: str
|
|
914
1303
|
element_id: str | None
|
|
@@ -936,6 +1325,14 @@ class CommandResult:
|
|
|
936
1325
|
class PageSnapshot:
|
|
937
1326
|
"""
|
|
938
1327
|
Snapshot of a single page containing all elements and page metadata.
|
|
1328
|
+
|
|
1329
|
+
Parameters (provided by the server):
|
|
1330
|
+
- page_ref: `PageRef` describing the page (size, orientation, etc.).
|
|
1331
|
+
- elements: List of `ObjectRef` (and subclasses) present on the page.
|
|
1332
|
+
|
|
1333
|
+
Usage:
|
|
1334
|
+
- Iterate over `elements` to find items to modify or move.
|
|
1335
|
+
- Use `page_ref.position.page_index` as the page index for follow-up operations.
|
|
939
1336
|
"""
|
|
940
1337
|
page_ref: PageRef
|
|
941
1338
|
elements: List[ObjectRef]
|
|
@@ -952,7 +1349,21 @@ class PageSnapshot:
|
|
|
952
1349
|
@dataclass
|
|
953
1350
|
class DocumentSnapshot:
|
|
954
1351
|
"""
|
|
955
|
-
Snapshot of
|
|
1352
|
+
Snapshot of a document including pages and fonts used.
|
|
1353
|
+
|
|
1354
|
+
Parameters (provided by the server):
|
|
1355
|
+
- page_count: Number of pages in the document.
|
|
1356
|
+
- fonts: List of `FontRecommendation` entries summarizing fonts in the document.
|
|
1357
|
+
- pages: Ordered list of `PageSnapshot` objects, one per page.
|
|
1358
|
+
|
|
1359
|
+
Usage:
|
|
1360
|
+
```python
|
|
1361
|
+
# Iterate pages and elements
|
|
1362
|
+
for page in snapshot.pages:
|
|
1363
|
+
for el in page.elements:
|
|
1364
|
+
if isinstance(el, TextObjectRef) and el.get_text():
|
|
1365
|
+
print(el.get_text())
|
|
1366
|
+
```
|
|
956
1367
|
"""
|
|
957
1368
|
page_count: int
|
|
958
1369
|
fonts: List[FontRecommendation]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .exceptions import ValidationException
|
|
6
|
+
from .models import Orientation, PageRef, PageSize, AddPageRequest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageBuilder:
|
|
10
|
+
"""
|
|
11
|
+
Fluent builder for adding pages with optional orientation, size, and index.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
pdf.new_page().at_index(1).landscape().a4().add()
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: 'PDFDancer') -> None:
|
|
18
|
+
if client is None:
|
|
19
|
+
raise ValidationException("Client cannot be null")
|
|
20
|
+
|
|
21
|
+
self._client = client
|
|
22
|
+
self._page_index: Optional[int] = None
|
|
23
|
+
self._orientation: Optional[Orientation] = None
|
|
24
|
+
self._page_size: Optional[PageSize] = None
|
|
25
|
+
|
|
26
|
+
def at_index(self, page_index: int) -> 'PageBuilder':
|
|
27
|
+
if page_index is None:
|
|
28
|
+
raise ValidationException("Page index cannot be null")
|
|
29
|
+
if page_index < 0:
|
|
30
|
+
raise ValidationException("Page index must be greater than or equal to 0")
|
|
31
|
+
self._page_index = int(page_index)
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
def orientation(self, orientation: Orientation) -> 'PageBuilder':
|
|
35
|
+
if orientation is None:
|
|
36
|
+
raise ValidationException("Orientation cannot be null")
|
|
37
|
+
if isinstance(orientation, str):
|
|
38
|
+
normalized = orientation.strip().upper()
|
|
39
|
+
orientation = Orientation(normalized)
|
|
40
|
+
self._orientation = orientation
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def portrait(self) -> 'PageBuilder':
|
|
44
|
+
self._orientation = Orientation.PORTRAIT
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def landscape(self) -> 'PageBuilder':
|
|
48
|
+
self._orientation = Orientation.LANDSCAPE
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def page_size(self, page_size: PageSize) -> 'PageBuilder':
|
|
52
|
+
if page_size is None:
|
|
53
|
+
raise ValidationException("Page size cannot be null")
|
|
54
|
+
self._page_size = PageSize.coerce(page_size)
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def a4(self) -> 'PageBuilder':
|
|
58
|
+
self._page_size = PageSize.A4
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def letter(self) -> 'PageBuilder':
|
|
62
|
+
self._page_size = PageSize.LETTER
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def a3(self) -> 'PageBuilder':
|
|
66
|
+
self._page_size = PageSize.A3
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def a5(self) -> 'PageBuilder':
|
|
70
|
+
self._page_size = PageSize.A5
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def legal(self) -> 'PageBuilder':
|
|
74
|
+
self._page_size = PageSize.LEGAL
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def custom_size(self, width: float, height: float) -> 'PageBuilder':
|
|
78
|
+
self._page_size = PageSize(name=None, width=width, height=height)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def add(self) -> PageRef:
|
|
82
|
+
request = self._build_request()
|
|
83
|
+
return self._client._add_page(request)
|
|
84
|
+
|
|
85
|
+
def _build_request(self) -> Optional[AddPageRequest]:
|
|
86
|
+
if self._page_index is None and self._orientation is None and self._page_size is None:
|
|
87
|
+
return None
|
|
88
|
+
return AddPageRequest(
|
|
89
|
+
page_index=self._page_index,
|
|
90
|
+
orientation=self._orientation,
|
|
91
|
+
page_size=self._page_size
|
|
92
|
+
)
|
pdfdancer/path_builder.py
CHANGED
|
@@ -154,6 +154,38 @@ class PathBuilder:
|
|
|
154
154
|
self._segments.append(bezier)
|
|
155
155
|
return self
|
|
156
156
|
|
|
157
|
+
def add_rectangle(self, x: float, y: float, width: float, height: float) -> 'PathBuilder':
|
|
158
|
+
"""
|
|
159
|
+
Convenient method to add a rectangle as four line segments to the path.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
x: X coordinate of bottom-left corner
|
|
163
|
+
y: Y coordinate of bottom-left corner
|
|
164
|
+
width: Rectangle width
|
|
165
|
+
height: Rectangle height
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Self for method chaining
|
|
169
|
+
"""
|
|
170
|
+
if width <= 0:
|
|
171
|
+
raise ValidationException("Rectangle width must be positive")
|
|
172
|
+
if height <= 0:
|
|
173
|
+
raise ValidationException("Rectangle height must be positive")
|
|
174
|
+
|
|
175
|
+
# Create rectangle as 4 line segments (clockwise from bottom-left)
|
|
176
|
+
bottom_left = Point(x, y)
|
|
177
|
+
bottom_right = Point(x + width, y)
|
|
178
|
+
top_right = Point(x + width, y + height)
|
|
179
|
+
top_left = Point(x, y + height)
|
|
180
|
+
|
|
181
|
+
# Add four lines forming the rectangle
|
|
182
|
+
self.add_line(bottom_left, bottom_right)
|
|
183
|
+
self.add_line(bottom_right, top_right)
|
|
184
|
+
self.add_line(top_right, top_left)
|
|
185
|
+
self.add_line(top_left, bottom_left)
|
|
186
|
+
|
|
187
|
+
return self
|
|
188
|
+
|
|
157
189
|
def even_odd_fill(self, enabled: bool = True) -> 'PathBuilder':
|
|
158
190
|
"""
|
|
159
191
|
Set the fill rule to even-odd (vs nonzero winding).
|
|
@@ -555,3 +587,223 @@ class BezierBuilder:
|
|
|
555
587
|
# Add it using the client's internal method
|
|
556
588
|
# noinspection PyProtectedMember
|
|
557
589
|
return self._client._add_path(path)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
class RectangleBuilder:
|
|
593
|
+
"""
|
|
594
|
+
Builder class for constructing Rectangle objects with fluent interface.
|
|
595
|
+
Provides a convenient way to create a rectangle path with a single builder.
|
|
596
|
+
"""
|
|
597
|
+
|
|
598
|
+
def __init__(self, client: 'PDFDancer', page_index: int):
|
|
599
|
+
"""
|
|
600
|
+
Initialize the rectangle builder.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
client: The PDFDancer instance for adding the rectangle
|
|
604
|
+
page_index: The page number (0-indexed)
|
|
605
|
+
"""
|
|
606
|
+
if client is None:
|
|
607
|
+
raise ValidationException("Client cannot be null")
|
|
608
|
+
|
|
609
|
+
self._client = client
|
|
610
|
+
self._page_index = page_index
|
|
611
|
+
self._x: Optional[float] = None
|
|
612
|
+
self._y: Optional[float] = None
|
|
613
|
+
self._width: Optional[float] = None
|
|
614
|
+
self._height: Optional[float] = None
|
|
615
|
+
self._stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
|
|
616
|
+
self._fill_color: Optional[Color] = None
|
|
617
|
+
self._stroke_width: float = 1.0
|
|
618
|
+
self._dash_array: Optional[List[float]] = None
|
|
619
|
+
self._dash_phase: Optional[float] = None
|
|
620
|
+
self._even_odd_fill: bool = False
|
|
621
|
+
|
|
622
|
+
def at_coordinates(self, x: float, y: float) -> 'RectangleBuilder':
|
|
623
|
+
"""
|
|
624
|
+
Set the bottom-left corner position of the rectangle.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
x: X coordinate on the page
|
|
628
|
+
y: Y coordinate on the page
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Self for method chaining
|
|
632
|
+
"""
|
|
633
|
+
self._x = x
|
|
634
|
+
self._y = y
|
|
635
|
+
return self
|
|
636
|
+
|
|
637
|
+
def with_size(self, width: float, height: float) -> 'RectangleBuilder':
|
|
638
|
+
"""
|
|
639
|
+
Set the dimensions of the rectangle.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
width: Rectangle width
|
|
643
|
+
height: Rectangle height
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
Self for method chaining
|
|
647
|
+
"""
|
|
648
|
+
self._width = width
|
|
649
|
+
self._height = height
|
|
650
|
+
return self
|
|
651
|
+
|
|
652
|
+
def stroke_color(self, color: Color) -> 'RectangleBuilder':
|
|
653
|
+
"""
|
|
654
|
+
Set the stroke color.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
color: The stroke color
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
Self for method chaining
|
|
661
|
+
"""
|
|
662
|
+
self._stroke_color = color
|
|
663
|
+
return self
|
|
664
|
+
|
|
665
|
+
def fill_color(self, color: Color) -> 'RectangleBuilder':
|
|
666
|
+
"""
|
|
667
|
+
Set the fill color.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
color: The fill color
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
Self for method chaining
|
|
674
|
+
"""
|
|
675
|
+
self._fill_color = color
|
|
676
|
+
return self
|
|
677
|
+
|
|
678
|
+
def stroke_width(self, width: float) -> 'RectangleBuilder':
|
|
679
|
+
"""
|
|
680
|
+
Set the stroke width.
|
|
681
|
+
|
|
682
|
+
Args:
|
|
683
|
+
width: The stroke width in points
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
Self for method chaining
|
|
687
|
+
"""
|
|
688
|
+
if width <= 0:
|
|
689
|
+
raise ValidationException("Stroke width must be positive")
|
|
690
|
+
self._stroke_width = width
|
|
691
|
+
return self
|
|
692
|
+
|
|
693
|
+
def dash_pattern(self, dash_array: List[float], dash_phase: float = 0.0) -> 'RectangleBuilder':
|
|
694
|
+
"""
|
|
695
|
+
Set a dash pattern.
|
|
696
|
+
|
|
697
|
+
Args:
|
|
698
|
+
dash_array: List of on/off lengths (e.g., [10, 5] = 10pt on, 5pt off)
|
|
699
|
+
dash_phase: Offset into the pattern
|
|
700
|
+
|
|
701
|
+
Returns:
|
|
702
|
+
Self for method chaining
|
|
703
|
+
"""
|
|
704
|
+
self._dash_array = dash_array
|
|
705
|
+
self._dash_phase = dash_phase
|
|
706
|
+
return self
|
|
707
|
+
|
|
708
|
+
def solid(self) -> 'RectangleBuilder':
|
|
709
|
+
"""
|
|
710
|
+
Set rectangle to solid (no dash pattern).
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
Self for method chaining
|
|
714
|
+
"""
|
|
715
|
+
self._dash_array = None
|
|
716
|
+
self._dash_phase = None
|
|
717
|
+
return self
|
|
718
|
+
|
|
719
|
+
def even_odd_fill(self, enabled: bool = True) -> 'RectangleBuilder':
|
|
720
|
+
"""
|
|
721
|
+
Set the fill rule to even-odd (vs nonzero winding).
|
|
722
|
+
|
|
723
|
+
Args:
|
|
724
|
+
enabled: True for even-odd, False for nonzero winding
|
|
725
|
+
|
|
726
|
+
Returns:
|
|
727
|
+
Self for method chaining
|
|
728
|
+
"""
|
|
729
|
+
self._even_odd_fill = enabled
|
|
730
|
+
return self
|
|
731
|
+
|
|
732
|
+
def add(self) -> bool:
|
|
733
|
+
"""
|
|
734
|
+
Build the rectangle and add it to the PDF document.
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
True if successful
|
|
738
|
+
|
|
739
|
+
Raises:
|
|
740
|
+
ValidationException: If required properties are missing
|
|
741
|
+
"""
|
|
742
|
+
if self._x is None or self._y is None:
|
|
743
|
+
raise ValidationException("Rectangle position must be set using at_coordinates()")
|
|
744
|
+
if self._width is None or self._height is None:
|
|
745
|
+
raise ValidationException("Rectangle dimensions must be set using with_size()")
|
|
746
|
+
if self._width <= 0:
|
|
747
|
+
raise ValidationException("Rectangle width must be positive")
|
|
748
|
+
if self._height <= 0:
|
|
749
|
+
raise ValidationException("Rectangle height must be positive")
|
|
750
|
+
|
|
751
|
+
# Create rectangle as 4 line segments
|
|
752
|
+
bottom_left = Point(self._x, self._y)
|
|
753
|
+
bottom_right = Point(self._x + self._width, self._y)
|
|
754
|
+
top_right = Point(self._x + self._width, self._y + self._height)
|
|
755
|
+
top_left = Point(self._x, self._y + self._height)
|
|
756
|
+
|
|
757
|
+
# Create four lines forming the rectangle
|
|
758
|
+
lines = [
|
|
759
|
+
Line(
|
|
760
|
+
p0=bottom_left,
|
|
761
|
+
p1=bottom_right,
|
|
762
|
+
stroke_color=self._stroke_color,
|
|
763
|
+
fill_color=self._fill_color,
|
|
764
|
+
stroke_width=self._stroke_width,
|
|
765
|
+
dash_array=self._dash_array,
|
|
766
|
+
dash_phase=self._dash_phase
|
|
767
|
+
),
|
|
768
|
+
Line(
|
|
769
|
+
p0=bottom_right,
|
|
770
|
+
p1=top_right,
|
|
771
|
+
stroke_color=self._stroke_color,
|
|
772
|
+
fill_color=self._fill_color,
|
|
773
|
+
stroke_width=self._stroke_width,
|
|
774
|
+
dash_array=self._dash_array,
|
|
775
|
+
dash_phase=self._dash_phase
|
|
776
|
+
),
|
|
777
|
+
Line(
|
|
778
|
+
p0=top_right,
|
|
779
|
+
p1=top_left,
|
|
780
|
+
stroke_color=self._stroke_color,
|
|
781
|
+
fill_color=self._fill_color,
|
|
782
|
+
stroke_width=self._stroke_width,
|
|
783
|
+
dash_array=self._dash_array,
|
|
784
|
+
dash_phase=self._dash_phase
|
|
785
|
+
),
|
|
786
|
+
Line(
|
|
787
|
+
p0=top_left,
|
|
788
|
+
p1=bottom_left,
|
|
789
|
+
stroke_color=self._stroke_color,
|
|
790
|
+
fill_color=self._fill_color,
|
|
791
|
+
stroke_width=self._stroke_width,
|
|
792
|
+
dash_array=self._dash_array,
|
|
793
|
+
dash_phase=self._dash_phase
|
|
794
|
+
)
|
|
795
|
+
]
|
|
796
|
+
|
|
797
|
+
# Create position with only page index set
|
|
798
|
+
position = Position.at_page_coordinates(self._page_index, 0, 0)
|
|
799
|
+
|
|
800
|
+
# Wrap in Path with four line segments
|
|
801
|
+
path = Path(
|
|
802
|
+
position=position,
|
|
803
|
+
path_segments=lines,
|
|
804
|
+
even_odd_fill=self._even_odd_fill
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# Add it using the client's internal method
|
|
808
|
+
# noinspection PyProtectedMember
|
|
809
|
+
return self._client._add_path(path)
|
pdfdancer/pdfdancer_v1.py
CHANGED
|
@@ -116,7 +116,7 @@ def _log_generated_at_header(response: httpx.Response, method: str, path: str) -
|
|
|
116
116
|
print(f"{time.time()}|{method} {path} - Header parse error: {e}")
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
from . import ParagraphBuilder
|
|
119
|
+
from . import ParagraphBuilder, BezierBuilder, PathBuilder, LineBuilder
|
|
120
120
|
from .exceptions import (
|
|
121
121
|
PdfDancerException,
|
|
122
122
|
FontNotFoundException,
|
|
@@ -124,15 +124,16 @@ from .exceptions import (
|
|
|
124
124
|
SessionException,
|
|
125
125
|
ValidationException
|
|
126
126
|
)
|
|
127
|
-
from .image_builder import ImageBuilder
|
|
127
|
+
from .image_builder import ImageBuilder, ImageOnPageBuilder
|
|
128
128
|
from .models import (
|
|
129
129
|
ObjectRef, Position, ObjectType, Font, Image, Paragraph, FormFieldRef, TextObjectRef, PageRef,
|
|
130
|
-
FindRequest, DeleteRequest, MoveRequest, PageMoveRequest, AddRequest, ModifyRequest, ModifyTextRequest,
|
|
130
|
+
FindRequest, DeleteRequest, MoveRequest, PageMoveRequest, AddPageRequest, AddRequest, ModifyRequest, ModifyTextRequest,
|
|
131
131
|
ChangeFormFieldRequest, CommandResult,
|
|
132
132
|
ShapeType, PositionMode, PageSize, Orientation,
|
|
133
133
|
PageSnapshot, DocumentSnapshot, FontRecommendation, FontType
|
|
134
134
|
)
|
|
135
135
|
from .paragraph_builder import ParagraphPageBuilder
|
|
136
|
+
from .page_builder import PageBuilder
|
|
136
137
|
from .types import PathObject, ParagraphObject, TextLineObject, ImageObject, FormObject, FormFieldObject
|
|
137
138
|
|
|
138
139
|
|
|
@@ -270,21 +271,25 @@ class PageClient:
|
|
|
270
271
|
def _ref(self):
|
|
271
272
|
return ObjectRef(internal_id=self.internal_id, position=self.position, type=self.object_type)
|
|
272
273
|
|
|
273
|
-
def new_paragraph(self):
|
|
274
|
+
def new_paragraph(self) -> ParagraphBuilder:
|
|
274
275
|
return ParagraphPageBuilder(self.root, self.page_index)
|
|
275
276
|
|
|
276
|
-
def new_path(self):
|
|
277
|
-
from .path_builder import PathBuilder
|
|
277
|
+
def new_path(self) -> PathBuilder:
|
|
278
278
|
return PathBuilder(self.root, self.page_index)
|
|
279
279
|
|
|
280
|
-
def
|
|
281
|
-
|
|
280
|
+
def new_image(self) -> ImageOnPageBuilder:
|
|
281
|
+
return ImageOnPageBuilder(self.root, self.page_index)
|
|
282
|
+
|
|
283
|
+
def new_line(self) -> LineBuilder:
|
|
282
284
|
return LineBuilder(self.root, self.page_index)
|
|
283
285
|
|
|
284
|
-
def new_bezier(self):
|
|
285
|
-
from .path_builder import BezierBuilder
|
|
286
|
+
def new_bezier(self) -> BezierBuilder:
|
|
286
287
|
return BezierBuilder(self.root, self.page_index)
|
|
287
288
|
|
|
289
|
+
def new_rectangle(self) -> 'RectangleBuilder':
|
|
290
|
+
from .path_builder import RectangleBuilder
|
|
291
|
+
return RectangleBuilder(self.root, self.page_index)
|
|
292
|
+
|
|
288
293
|
def select_paths(self):
|
|
289
294
|
# noinspection PyProtectedMember
|
|
290
295
|
return self.root._to_path_objects(self.root._find_paths(Position.at_page(self.page_index)))
|
|
@@ -906,6 +911,20 @@ class PDFDancer:
|
|
|
906
911
|
"""
|
|
907
912
|
return self._to_paragraph_objects(self._find_paragraphs(None))
|
|
908
913
|
|
|
914
|
+
def select_paragraphs_matching(self, pattern: str) -> List[ParagraphObject]:
|
|
915
|
+
"""
|
|
916
|
+
Searches for paragraph objects matching a regex pattern.
|
|
917
|
+
|
|
918
|
+
Args:
|
|
919
|
+
pattern: Regex pattern to match against paragraph text
|
|
920
|
+
|
|
921
|
+
Returns:
|
|
922
|
+
List of ParagraphObject instances matching the pattern
|
|
923
|
+
"""
|
|
924
|
+
position = Position()
|
|
925
|
+
position.text_pattern = pattern
|
|
926
|
+
return self._to_paragraph_objects(self._find_paragraphs(position))
|
|
927
|
+
|
|
909
928
|
def _find_paragraphs(self, position: Optional[Position] = None, tolerance: float = DEFAULT_TOLERANCE) -> List[
|
|
910
929
|
TextObjectRef]:
|
|
911
930
|
"""
|
|
@@ -1270,7 +1289,6 @@ class PDFDancer:
|
|
|
1270
1289
|
"""
|
|
1271
1290
|
Internal method to add a path to the document after validation.
|
|
1272
1291
|
"""
|
|
1273
|
-
from .models import Path as PathModel
|
|
1274
1292
|
|
|
1275
1293
|
if path is None:
|
|
1276
1294
|
raise ValidationException("Path cannot be null")
|
|
@@ -1299,11 +1317,16 @@ class PDFDancer:
|
|
|
1299
1317
|
|
|
1300
1318
|
return result
|
|
1301
1319
|
|
|
1302
|
-
def
|
|
1303
|
-
|
|
1320
|
+
def _add_page(self, request: Optional[AddPageRequest]) -> PageRef:
|
|
1321
|
+
"""
|
|
1322
|
+
Internal helper to add a page with optional parameters.
|
|
1323
|
+
"""
|
|
1324
|
+
request_data = None
|
|
1325
|
+
if request is not None:
|
|
1326
|
+
payload = request.to_dict()
|
|
1327
|
+
request_data = payload or None
|
|
1304
1328
|
|
|
1305
|
-
|
|
1306
|
-
response = self._make_request('POST', '/pdf/page/add', data=None)
|
|
1329
|
+
response = self._make_request('POST', '/pdf/page/add', data=request_data)
|
|
1307
1330
|
result = self._parse_page_ref(response.json())
|
|
1308
1331
|
|
|
1309
1332
|
# Invalidate snapshot caches after adding page
|
|
@@ -1311,6 +1334,17 @@ class PDFDancer:
|
|
|
1311
1334
|
|
|
1312
1335
|
return result
|
|
1313
1336
|
|
|
1337
|
+
def new_paragraph(self) -> ParagraphBuilder:
|
|
1338
|
+
return ParagraphBuilder(self)
|
|
1339
|
+
|
|
1340
|
+
def new_page(self, orientation=Orientation.PORTRAIT, size=PageSize.A4) -> PageBuilder:
|
|
1341
|
+
builder = PageBuilder(self)
|
|
1342
|
+
if orientation is not None:
|
|
1343
|
+
builder.orientation(orientation)
|
|
1344
|
+
if size is not None:
|
|
1345
|
+
builder.page_size(size)
|
|
1346
|
+
return builder
|
|
1347
|
+
|
|
1314
1348
|
def new_image(self) -> ImageBuilder:
|
|
1315
1349
|
return ImageBuilder(self)
|
|
1316
1350
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pdfdancer/__init__.py,sha256=NKvSJY10p4TCc4uRC9wkDnkYJdvSdm2ry_D-fBgGtX8,2207
|
|
2
|
+
pdfdancer/exceptions.py,sha256=WAcyTacykJwjiaURrQamEgizLxv0vSlSio6NMikg4D0,1558
|
|
3
|
+
pdfdancer/fingerprint.py,sha256=Ue5QzpqsKlbYefvKU0ULV4NgMU3AOTcseeV9HfiPJXI,3093
|
|
4
|
+
pdfdancer/image_builder.py,sha256=ee-y7IzjZqpMN8O8ZzEm8-lOnOnqoQ7LQTzVWg4mHWg,1760
|
|
5
|
+
pdfdancer/models.py,sha256=uyVYlswBUcHMSXSRjeQOa9WBc2tJU3-Q6nKquwlbT2c,45712
|
|
6
|
+
pdfdancer/page_builder.py,sha256=HiMEPYWhaIWS9dZ1jxIA-XIeAwgJVXMoEAYsm3TxOt8,2969
|
|
7
|
+
pdfdancer/paragraph_builder.py,sha256=x5XhPo-GUeovQuGbrH7MyyenITcy6in7bz6CD9r-ZqU,9458
|
|
8
|
+
pdfdancer/path_builder.py,sha256=G5vpVAH9OB3zV5gaSA0MKWIygmieVhwQ8dxnt_hgF7A,23693
|
|
9
|
+
pdfdancer/pdfdancer_v1.py,sha256=WMRdxQwpfnOvotsaP6jVcLw1_TYe4XG7M0AAoWeSkdo,88700
|
|
10
|
+
pdfdancer/types.py,sha256=fMYNmT73ism5bN8ij8G8hELKWQA8-8o7AlIX6YFcSEw,12652
|
|
11
|
+
pdfdancer_client_python-0.2.20.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
12
|
+
pdfdancer_client_python-0.2.20.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
|
|
13
|
+
pdfdancer_client_python-0.2.20.dist-info/METADATA,sha256=vup4o7ROABx7YHuxTlA1oSlmg3KYS9Oarl9rOELaj4g,24668
|
|
14
|
+
pdfdancer_client_python-0.2.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
pdfdancer_client_python-0.2.20.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
|
|
16
|
+
pdfdancer_client_python-0.2.20.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pdfdancer/__init__.py,sha256=ZZOarTVp9OJQUYA8PsBn8FWNCEHIaCcSHitd4dpBR5w,2150
|
|
2
|
-
pdfdancer/exceptions.py,sha256=WAcyTacykJwjiaURrQamEgizLxv0vSlSio6NMikg4D0,1558
|
|
3
|
-
pdfdancer/fingerprint.py,sha256=Ue5QzpqsKlbYefvKU0ULV4NgMU3AOTcseeV9HfiPJXI,3093
|
|
4
|
-
pdfdancer/image_builder.py,sha256=Omxc2LcieJ1MbvWBXR5_sfia--eAucTUe0KWgr22HYo,842
|
|
5
|
-
pdfdancer/models.py,sha256=kK2tIRwJ8XQJZZ__uSgvU6E0NRtj11Jzwwhy-d8EKSQ,32210
|
|
6
|
-
pdfdancer/paragraph_builder.py,sha256=x5XhPo-GUeovQuGbrH7MyyenITcy6in7bz6CD9r-ZqU,9458
|
|
7
|
-
pdfdancer/path_builder.py,sha256=KJC8toLhbCbtnxz8pphtpzgoBemGHlrcC1s02RAvCJ8,15862
|
|
8
|
-
pdfdancer/pdfdancer_v1.py,sha256=dSkrWfkbon-d5HcgOj1hoJwaFMnr7rWp_laGhXF64jE,87364
|
|
9
|
-
pdfdancer/types.py,sha256=fMYNmT73ism5bN8ij8G8hELKWQA8-8o7AlIX6YFcSEw,12652
|
|
10
|
-
pdfdancer_client_python-0.2.19.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
11
|
-
pdfdancer_client_python-0.2.19.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
|
|
12
|
-
pdfdancer_client_python-0.2.19.dist-info/METADATA,sha256=Ex5-8upudpJ8ZBvdppMnU_zB2OK58iIiD3um9xMfek4,24668
|
|
13
|
-
pdfdancer_client_python-0.2.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
pdfdancer_client_python-0.2.19.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
|
|
15
|
-
pdfdancer_client_python-0.2.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.19.dist-info → pdfdancer_client_python-0.2.20.dist-info}/top_level.txt
RENAMED
|
File without changes
|