pdfdancer-client-python 0.3.13__py3-none-any.whl → 3.0.0__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/__init__.py +79 -22
- pdfdancer/_runtime_version.py +30 -0
- pdfdancer/_version.py +24 -0
- pdfdancer/image_builder.py +23 -3
- pdfdancer/models.py +127 -470
- pdfdancer/page_builder.py +6 -17
- pdfdancer/path_builder.py +127 -6
- pdfdancer/{pdfdancer_v1.py → pdfdancer_v2.py} +850 -1316
- pdfdancer/text_editing.py +1472 -0
- pdfdancer/types.py +94 -399
- pdfdancer_client_python-3.0.0.dist-info/METADATA +521 -0
- pdfdancer_client_python-3.0.0.dist-info/RECORD +18 -0
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/WHEEL +1 -1
- pdfdancer/paragraph_builder.py +0 -554
- pdfdancer/text_line_builder.py +0 -290
- pdfdancer_client_python-0.3.13.dist-info/METADATA +0 -685
- pdfdancer_client_python-0.3.13.dist-info/RECORD +0 -17
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/licenses/NOTICE +0 -0
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/top_level.txt +0 -0
pdfdancer/types.py
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import sys
|
|
4
3
|
from dataclasses import dataclass
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Type, cast
|
|
7
|
+
|
|
8
|
+
from . import (
|
|
9
|
+
FormFieldRef,
|
|
10
|
+
ObjectRef,
|
|
11
|
+
ObjectType,
|
|
12
|
+
PathObjectRef,
|
|
13
|
+
Position,
|
|
14
|
+
)
|
|
8
15
|
from .exceptions import ValidationException
|
|
16
|
+
from .models import BoundingRect as ModelBoundingRect
|
|
9
17
|
|
|
10
18
|
if TYPE_CHECKING:
|
|
11
|
-
from .models import Color, CommandResult, Image, ImageFlipDirection
|
|
12
|
-
from .
|
|
19
|
+
from .models import Color, CommandResult, Image, ImageFlipDirection, PathGroupInfo
|
|
20
|
+
from .pdfdancer_v2 import PDFDancer
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
@dataclass
|
|
@@ -27,7 +35,7 @@ class UnsupportedOperation(Exception):
|
|
|
27
35
|
|
|
28
36
|
class PDFObjectBase:
|
|
29
37
|
"""
|
|
30
|
-
Base class for
|
|
38
|
+
Base class for selectable PDF object references (paths, text lines, etc.)
|
|
31
39
|
providing shared behavior such as position, deletion, and movement.
|
|
32
40
|
"""
|
|
33
41
|
|
|
@@ -46,7 +54,7 @@ class PDFObjectBase:
|
|
|
46
54
|
@property
|
|
47
55
|
def page_number(self) -> int:
|
|
48
56
|
"""Page index where this object resides."""
|
|
49
|
-
return self.position.page_number
|
|
57
|
+
return cast(int, self.position.page_number)
|
|
50
58
|
|
|
51
59
|
def object_ref(self) -> ObjectRef:
|
|
52
60
|
return ObjectRef(self.internal_id, self.position, self.object_type)
|
|
@@ -62,21 +70,13 @@ class PDFObjectBase:
|
|
|
62
70
|
"""Move this object to a new position."""
|
|
63
71
|
return self._client._move(
|
|
64
72
|
self.object_ref(),
|
|
65
|
-
Position.at_page_coordinates(self.position.page_number, x, y),
|
|
73
|
+
Position.at_page_coordinates(cast(int, self.position.page_number), x, y),
|
|
66
74
|
)
|
|
67
75
|
|
|
68
76
|
def clear_clipping(self) -> bool:
|
|
69
77
|
"""Detach any active clipping path from this object."""
|
|
70
78
|
return self._client.clear_clipping(self.object_ref())
|
|
71
79
|
|
|
72
|
-
def redact(self, replacement: str = "[REDACTED]") -> bool:
|
|
73
|
-
"""Redact this object from the PDF document."""
|
|
74
|
-
from .models import RedactTarget
|
|
75
|
-
|
|
76
|
-
target = RedactTarget(self.internal_id, replacement)
|
|
77
|
-
result = self._client._redact([target], replacement)
|
|
78
|
-
return result.success
|
|
79
|
-
|
|
80
80
|
|
|
81
81
|
# -------------------------------------------------------------------
|
|
82
82
|
# Subclasses
|
|
@@ -86,7 +86,7 @@ class PDFObjectBase:
|
|
|
86
86
|
class PathObject(PDFObjectBase):
|
|
87
87
|
"""Represents a vector path object inside a PDF page."""
|
|
88
88
|
|
|
89
|
-
def __init__(self, client: "PDFDancer", object_ref):
|
|
89
|
+
def __init__(self, client: "PDFDancer", object_ref: ObjectRef):
|
|
90
90
|
"""
|
|
91
91
|
Initialize a PathObject.
|
|
92
92
|
|
|
@@ -100,7 +100,7 @@ class PathObject(PDFObjectBase):
|
|
|
100
100
|
self._object_ref = object_ref
|
|
101
101
|
|
|
102
102
|
@property
|
|
103
|
-
def bounding_box(self) -> Optional[
|
|
103
|
+
def bounding_box(self) -> Optional[ModelBoundingRect]:
|
|
104
104
|
"""Optional bounding rectangle (if available)."""
|
|
105
105
|
return self.position.bounding_rect
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ class PathObject(PDFObjectBase):
|
|
|
108
108
|
"""Start a fluent editing session to modify path colors."""
|
|
109
109
|
return PathEditSession(self._client, self.object_ref())
|
|
110
110
|
|
|
111
|
-
def object_ref(self):
|
|
111
|
+
def object_ref(self) -> ObjectRef:
|
|
112
112
|
"""Return an ObjectRef for this path."""
|
|
113
113
|
return self._object_ref
|
|
114
114
|
|
|
@@ -124,7 +124,7 @@ class PathObject(PDFObjectBase):
|
|
|
124
124
|
return self._object_ref.get_fill_color()
|
|
125
125
|
return None
|
|
126
126
|
|
|
127
|
-
def __eq__(self, other):
|
|
127
|
+
def __eq__(self, other: object) -> bool:
|
|
128
128
|
if not isinstance(other, PathObject):
|
|
129
129
|
return False
|
|
130
130
|
return (
|
|
@@ -137,6 +137,24 @@ class PathObject(PDFObjectBase):
|
|
|
137
137
|
class ImageObject(PDFObjectBase):
|
|
138
138
|
"""Represents an image object inside a PDF page."""
|
|
139
139
|
|
|
140
|
+
@property
|
|
141
|
+
def width(self) -> Optional[float]:
|
|
142
|
+
return (
|
|
143
|
+
self.position.bounding_rect.width if self.position.bounding_rect else None
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def height(self) -> Optional[float]:
|
|
148
|
+
return (
|
|
149
|
+
self.position.bounding_rect.height if self.position.bounding_rect else None
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def aspect_ratio(self) -> Optional[float]:
|
|
154
|
+
return (
|
|
155
|
+
self.width / self.height if self.width is not None and self.height else None
|
|
156
|
+
)
|
|
157
|
+
|
|
140
158
|
def scale(self, factor: float) -> "CommandResult":
|
|
141
159
|
"""Scale this image by a factor.
|
|
142
160
|
|
|
@@ -182,7 +200,7 @@ class ImageObject(PDFObjectBase):
|
|
|
182
200
|
"""Rotate this image by a specified angle.
|
|
183
201
|
|
|
184
202
|
Args:
|
|
185
|
-
angle: Rotation angle in degrees (positive =
|
|
203
|
+
angle: Rotation angle in degrees (positive = clockwise)
|
|
186
204
|
|
|
187
205
|
Returns:
|
|
188
206
|
CommandResult indicating success or failure
|
|
@@ -263,6 +281,16 @@ class ImageObject(PDFObjectBase):
|
|
|
263
281
|
)
|
|
264
282
|
return self._client._transform_image(request)
|
|
265
283
|
|
|
284
|
+
def flip_horizontal(self) -> "CommandResult":
|
|
285
|
+
from .models import ImageFlipDirection
|
|
286
|
+
|
|
287
|
+
return self.flip(ImageFlipDirection.HORIZONTAL)
|
|
288
|
+
|
|
289
|
+
def flip_vertical(self) -> "CommandResult":
|
|
290
|
+
from .models import ImageFlipDirection
|
|
291
|
+
|
|
292
|
+
return self.flip(ImageFlipDirection.VERTICAL)
|
|
293
|
+
|
|
266
294
|
def replace(self, new_image: "Image") -> "CommandResult":
|
|
267
295
|
"""Replace this image with a new image.
|
|
268
296
|
|
|
@@ -281,6 +309,17 @@ class ImageObject(PDFObjectBase):
|
|
|
281
309
|
)
|
|
282
310
|
return self._client._transform_image(request)
|
|
283
311
|
|
|
312
|
+
def replace_from_file(self, image_path: Path) -> "CommandResult":
|
|
313
|
+
from .models import Image
|
|
314
|
+
|
|
315
|
+
path = Path(image_path)
|
|
316
|
+
if not path.is_file():
|
|
317
|
+
raise ValidationException(f"Image file not found: {path}")
|
|
318
|
+
data = path.read_bytes()
|
|
319
|
+
if not data:
|
|
320
|
+
raise ValidationException("Image file cannot be empty")
|
|
321
|
+
return self.replace(Image(format=path.suffix.lstrip(".").upper(), data=data))
|
|
322
|
+
|
|
284
323
|
def fill_region(
|
|
285
324
|
self, x: int, y: int, width: int, height: int, color: "Color"
|
|
286
325
|
) -> "CommandResult":
|
|
@@ -319,7 +358,7 @@ class ImageObject(PDFObjectBase):
|
|
|
319
358
|
)
|
|
320
359
|
return self._client._transform_image(request)
|
|
321
360
|
|
|
322
|
-
def __eq__(self, other):
|
|
361
|
+
def __eq__(self, other: object) -> bool:
|
|
323
362
|
if not isinstance(other, ImageObject):
|
|
324
363
|
return False
|
|
325
364
|
return (
|
|
@@ -332,7 +371,9 @@ class ImageObject(PDFObjectBase):
|
|
|
332
371
|
class PathGroupObject:
|
|
333
372
|
"""Represents a group of vector paths that can be manipulated as a unit."""
|
|
334
373
|
|
|
335
|
-
def __init__(
|
|
374
|
+
def __init__(
|
|
375
|
+
self, client: "PDFDancer", page_index: int, info: "PathGroupInfo"
|
|
376
|
+
) -> None:
|
|
336
377
|
self._client = client
|
|
337
378
|
self._page_index = page_index
|
|
338
379
|
self._info = info
|
|
@@ -346,7 +387,7 @@ class PathGroupObject:
|
|
|
346
387
|
return self._info.path_count
|
|
347
388
|
|
|
348
389
|
@property
|
|
349
|
-
def bounding_box(self):
|
|
390
|
+
def bounding_box(self) -> Optional[dict[str, Any]]:
|
|
350
391
|
return self._info.bounding_box
|
|
351
392
|
|
|
352
393
|
@property
|
|
@@ -358,36 +399,33 @@ class PathGroupObject:
|
|
|
358
399
|
return self._info.y
|
|
359
400
|
|
|
360
401
|
def move_to(self, x: float, y: float) -> bool:
|
|
361
|
-
self._client._move_path_group(self._page_index, self.group_id, x, y)
|
|
362
|
-
return True
|
|
402
|
+
return self._client._move_path_group(self._page_index, self.group_id, x, y)
|
|
363
403
|
|
|
364
404
|
def scale(self, factor: float) -> bool:
|
|
365
|
-
self._client._scale_path_group(self._page_index, self.group_id, factor)
|
|
366
|
-
return True
|
|
405
|
+
return self._client._scale_path_group(self._page_index, self.group_id, factor)
|
|
367
406
|
|
|
368
407
|
def rotate(self, degrees: float) -> bool:
|
|
369
|
-
self._client._rotate_path_group(self._page_index, self.group_id, degrees)
|
|
370
|
-
return True
|
|
408
|
+
return self._client._rotate_path_group(self._page_index, self.group_id, degrees)
|
|
371
409
|
|
|
372
410
|
def resize(self, width: float, height: float) -> bool:
|
|
373
|
-
self._client._resize_path_group(
|
|
374
|
-
|
|
411
|
+
return self._client._resize_path_group(
|
|
412
|
+
self._page_index, self.group_id, width, height
|
|
413
|
+
)
|
|
375
414
|
|
|
376
415
|
def remove(self) -> bool:
|
|
377
|
-
self._client._remove_path_group(self._page_index, self.group_id)
|
|
378
|
-
return True
|
|
416
|
+
return self._client._remove_path_group(self._page_index, self.group_id)
|
|
379
417
|
|
|
380
418
|
def clear_clipping(self) -> bool:
|
|
381
419
|
return self._client.clear_path_group_clipping(
|
|
382
420
|
self._page_index + 1, self.group_id
|
|
383
421
|
)
|
|
384
422
|
|
|
385
|
-
def __repr__(self):
|
|
423
|
+
def __repr__(self) -> str:
|
|
386
424
|
return f"PathGroupObject(group_id={self.group_id!r}, path_count={self.path_count}, page_index={self._page_index})"
|
|
387
425
|
|
|
388
426
|
|
|
389
427
|
class FormObject(PDFObjectBase):
|
|
390
|
-
def __eq__(self, other):
|
|
428
|
+
def __eq__(self, other: object) -> bool:
|
|
391
429
|
if not isinstance(other, FormObject):
|
|
392
430
|
return False
|
|
393
431
|
return (
|
|
@@ -397,357 +435,6 @@ class FormObject(PDFObjectBase):
|
|
|
397
435
|
)
|
|
398
436
|
|
|
399
437
|
|
|
400
|
-
class BaseTextEdit:
|
|
401
|
-
"""Common base for text-like editable objects (Paragraph, TextLine, etc.)"""
|
|
402
|
-
|
|
403
|
-
def __init__(self, target_obj, object_ref):
|
|
404
|
-
self._color = None
|
|
405
|
-
self._position = None
|
|
406
|
-
self._font_size = None
|
|
407
|
-
self._font_name = None
|
|
408
|
-
self._new_text = None
|
|
409
|
-
self._target_obj = target_obj
|
|
410
|
-
self._object_ref = object_ref
|
|
411
|
-
|
|
412
|
-
def __enter__(self):
|
|
413
|
-
return self
|
|
414
|
-
|
|
415
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
416
|
-
if not exc_type:
|
|
417
|
-
self.apply()
|
|
418
|
-
|
|
419
|
-
# --- Common fluent configuration methods ---
|
|
420
|
-
|
|
421
|
-
def replace(self, text: str):
|
|
422
|
-
self._new_text = text
|
|
423
|
-
return self
|
|
424
|
-
|
|
425
|
-
def font(self, font_name: str, font_size: float):
|
|
426
|
-
self._font_name = font_name
|
|
427
|
-
self._font_size = font_size
|
|
428
|
-
return self
|
|
429
|
-
|
|
430
|
-
def color(self, color):
|
|
431
|
-
self._color = color
|
|
432
|
-
return self
|
|
433
|
-
|
|
434
|
-
def move_to(self, x: float, y: float):
|
|
435
|
-
self._position = Position().at_coordinates(Point(x, y))
|
|
436
|
-
return self
|
|
437
|
-
|
|
438
|
-
# --- Abstract method: implemented by subclass ---
|
|
439
|
-
def apply(self):
|
|
440
|
-
raise NotImplementedError("Subclasses must implement apply()")
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
class TextLineEdit(BaseTextEdit):
|
|
444
|
-
def apply(self) -> bool:
|
|
445
|
-
# If only text changed (no font, color, or position), use simple text modification
|
|
446
|
-
only_text_changed = (
|
|
447
|
-
self._new_text is not None
|
|
448
|
-
and self._font_name is None
|
|
449
|
-
and self._font_size is None
|
|
450
|
-
and self._color is None
|
|
451
|
-
and self._position is None
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
if only_text_changed:
|
|
455
|
-
# noinspection PyProtectedMember
|
|
456
|
-
result = self._target_obj._client._modify_text_line(
|
|
457
|
-
self._object_ref, self._new_text
|
|
458
|
-
)
|
|
459
|
-
if result.warning:
|
|
460
|
-
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
461
|
-
return result
|
|
462
|
-
|
|
463
|
-
# If only position changed (move operation)
|
|
464
|
-
only_move = (
|
|
465
|
-
self._position is not None
|
|
466
|
-
and self._new_text is None
|
|
467
|
-
and self._font_name is None
|
|
468
|
-
and self._font_size is None
|
|
469
|
-
and self._color is None
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
if only_move:
|
|
473
|
-
page_number = (
|
|
474
|
-
self._object_ref.position.page_number
|
|
475
|
-
if self._object_ref.position
|
|
476
|
-
else None
|
|
477
|
-
)
|
|
478
|
-
if page_number is None:
|
|
479
|
-
raise ValidationException(
|
|
480
|
-
"Text line position must include a page number to move"
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
# Extract x, y from self._position
|
|
484
|
-
x = self._position.x()
|
|
485
|
-
y = self._position.y()
|
|
486
|
-
if x is None or y is None:
|
|
487
|
-
raise ValidationException("Position must have x and y coordinates")
|
|
488
|
-
|
|
489
|
-
position = Position.at_page_coordinates(page_number, x, y)
|
|
490
|
-
# noinspection PyProtectedMember
|
|
491
|
-
result = self._target_obj._client._move(self._object_ref, position)
|
|
492
|
-
return result
|
|
493
|
-
|
|
494
|
-
# For font/color changes or combined operations, use TextLineBuilder
|
|
495
|
-
# This ensures proper handling of font/color fallbacks just like ParagraphEditSession
|
|
496
|
-
from .text_line_builder import TextLineBuilder
|
|
497
|
-
|
|
498
|
-
builder = TextLineBuilder.from_object_ref(
|
|
499
|
-
self._target_obj._client, self._object_ref
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
# Apply modifications to builder
|
|
503
|
-
# IMPORTANT: Always explicitly set text to ensure it's preserved
|
|
504
|
-
if self._new_text is not None:
|
|
505
|
-
builder.text(self._new_text)
|
|
506
|
-
elif hasattr(self._object_ref, "text") and self._object_ref.text:
|
|
507
|
-
# Preserve original text when only changing font/color/position
|
|
508
|
-
builder.text(self._object_ref.text)
|
|
509
|
-
|
|
510
|
-
# IMPORTANT: Always explicitly set font to ensure it's preserved
|
|
511
|
-
if self._font_name is not None and self._font_size is not None:
|
|
512
|
-
builder.font(self._font_name, self._font_size)
|
|
513
|
-
elif hasattr(self._object_ref, "font_name") and hasattr(
|
|
514
|
-
self._object_ref, "font_size"
|
|
515
|
-
):
|
|
516
|
-
if self._object_ref.font_name and self._object_ref.font_size:
|
|
517
|
-
# Preserve original font when only changing color/position
|
|
518
|
-
builder.font(self._object_ref.font_name, self._object_ref.font_size)
|
|
519
|
-
|
|
520
|
-
if self._color is not None:
|
|
521
|
-
builder.color(self._color)
|
|
522
|
-
if self._position is not None:
|
|
523
|
-
x = self._position.x()
|
|
524
|
-
y = self._position.y()
|
|
525
|
-
if x is None or y is None:
|
|
526
|
-
raise ValidationException("Position must have x and y coordinates")
|
|
527
|
-
page_number = (
|
|
528
|
-
self._object_ref.position.page_number
|
|
529
|
-
if self._object_ref.position
|
|
530
|
-
else None
|
|
531
|
-
)
|
|
532
|
-
if page_number is None:
|
|
533
|
-
raise ValidationException(
|
|
534
|
-
"Text line position must include a page number"
|
|
535
|
-
)
|
|
536
|
-
builder.at(page_number, x, y)
|
|
537
|
-
|
|
538
|
-
# Use builder's modify method which handles all the complexity
|
|
539
|
-
result = builder.modify(self._object_ref)
|
|
540
|
-
if result.warning:
|
|
541
|
-
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
542
|
-
return result
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
class ParagraphObject(PDFObjectBase):
|
|
546
|
-
"""Represents a paragraph text block inside a PDF page."""
|
|
547
|
-
|
|
548
|
-
def __init__(self, client: "PDFDancer", object_ref: TextObjectRef):
|
|
549
|
-
super().__init__(
|
|
550
|
-
client, object_ref.internal_id, object_ref.type, object_ref.position
|
|
551
|
-
)
|
|
552
|
-
self._object_ref = object_ref
|
|
553
|
-
|
|
554
|
-
def __getattr__(self, name):
|
|
555
|
-
"""
|
|
556
|
-
Automatically delegate attribute/method lookup to _object_ref
|
|
557
|
-
if it's not found on this object.
|
|
558
|
-
"""
|
|
559
|
-
return getattr(self._object_ref, name)
|
|
560
|
-
|
|
561
|
-
def edit(self):
|
|
562
|
-
return ParagraphEditSession(self._client, self.object_ref())
|
|
563
|
-
|
|
564
|
-
def object_ref(self) -> TextObjectRef:
|
|
565
|
-
return self._object_ref
|
|
566
|
-
|
|
567
|
-
def __eq__(self, other):
|
|
568
|
-
if not isinstance(other, ParagraphObject):
|
|
569
|
-
return False
|
|
570
|
-
return (
|
|
571
|
-
self.internal_id == other.internal_id
|
|
572
|
-
and self.object_type == other.object_type
|
|
573
|
-
and self.position == other.position
|
|
574
|
-
and self._object_ref.text == other._object_ref.text
|
|
575
|
-
and self._object_ref.font_name == other._object_ref.font_name
|
|
576
|
-
and self._object_ref.font_size == other._object_ref.font_size
|
|
577
|
-
and self._object_ref.line_spacings == other._object_ref.line_spacings
|
|
578
|
-
and self._object_ref.color == other._object_ref.color
|
|
579
|
-
and self._object_ref.children == other._object_ref.children
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
class TextLineObject(PDFObjectBase):
|
|
584
|
-
"""Represents a single line of text inside a PDF page."""
|
|
585
|
-
|
|
586
|
-
def __init__(self, client: "PDFDancer", object_ref: TextObjectRef):
|
|
587
|
-
super().__init__(
|
|
588
|
-
client, object_ref.internal_id, object_ref.type, object_ref.position
|
|
589
|
-
)
|
|
590
|
-
self._object_ref = object_ref
|
|
591
|
-
|
|
592
|
-
def __getattr__(self, name):
|
|
593
|
-
"""
|
|
594
|
-
Automatically delegate attribute/method lookup to _object_ref
|
|
595
|
-
if it's not found on this object.
|
|
596
|
-
"""
|
|
597
|
-
return getattr(self._object_ref, name)
|
|
598
|
-
|
|
599
|
-
def edit(self) -> TextLineEdit:
|
|
600
|
-
return TextLineEdit(self, self.object_ref())
|
|
601
|
-
|
|
602
|
-
def object_ref(self) -> TextObjectRef:
|
|
603
|
-
return self._object_ref
|
|
604
|
-
|
|
605
|
-
def __eq__(self, other):
|
|
606
|
-
if not isinstance(other, TextLineObject):
|
|
607
|
-
return False
|
|
608
|
-
return (
|
|
609
|
-
self.internal_id == other.internal_id
|
|
610
|
-
and self.object_type == other.object_type
|
|
611
|
-
and self.position == other.position
|
|
612
|
-
and self._object_ref.text == other._object_ref.text
|
|
613
|
-
and self._object_ref.font_name == other._object_ref.font_name
|
|
614
|
-
and self._object_ref.font_size == other._object_ref.font_size
|
|
615
|
-
and self._object_ref.line_spacings == other._object_ref.line_spacings
|
|
616
|
-
and self._object_ref.color == other._object_ref.color
|
|
617
|
-
and self._object_ref.children == other._object_ref.children
|
|
618
|
-
)
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
class ParagraphEditSession:
|
|
622
|
-
"""
|
|
623
|
-
Fluent editing helper that reuses ParagraphBuilder for modifications while preserving
|
|
624
|
-
the legacy context-manager workflow (replace/font/color/etc.).
|
|
625
|
-
"""
|
|
626
|
-
|
|
627
|
-
def __init__(self, client: "PDFDancer", object_ref: TextObjectRef):
|
|
628
|
-
self._client = client
|
|
629
|
-
self._object_ref = object_ref
|
|
630
|
-
self._new_text = None
|
|
631
|
-
self._font_name = None
|
|
632
|
-
self._font_size = None
|
|
633
|
-
self._color = None
|
|
634
|
-
self._line_spacing = None
|
|
635
|
-
self._new_position = None
|
|
636
|
-
self._has_changes = False
|
|
637
|
-
|
|
638
|
-
def __enter__(self):
|
|
639
|
-
return self
|
|
640
|
-
|
|
641
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
642
|
-
if exc_type:
|
|
643
|
-
return False
|
|
644
|
-
self.apply()
|
|
645
|
-
return False
|
|
646
|
-
|
|
647
|
-
def replace(self, text: str):
|
|
648
|
-
self._new_text = text
|
|
649
|
-
self._has_changes = True
|
|
650
|
-
return self
|
|
651
|
-
|
|
652
|
-
def font(self, font_name, font_size: float):
|
|
653
|
-
self._font_name = font_name
|
|
654
|
-
self._font_size = font_size
|
|
655
|
-
self._has_changes = True
|
|
656
|
-
return self
|
|
657
|
-
|
|
658
|
-
def color(self, color):
|
|
659
|
-
self._color = color
|
|
660
|
-
self._has_changes = True
|
|
661
|
-
return self
|
|
662
|
-
|
|
663
|
-
def line_spacing(self, spacing: float):
|
|
664
|
-
self._line_spacing = spacing
|
|
665
|
-
self._has_changes = True
|
|
666
|
-
return self
|
|
667
|
-
|
|
668
|
-
def move_to(self, x: float, y: float):
|
|
669
|
-
self._new_position = (x, y)
|
|
670
|
-
self._has_changes = True
|
|
671
|
-
return self
|
|
672
|
-
|
|
673
|
-
def apply(self):
|
|
674
|
-
if not self._has_changes:
|
|
675
|
-
return self._client._modify_paragraph(self._object_ref, None)
|
|
676
|
-
|
|
677
|
-
only_text_changed = (
|
|
678
|
-
self._new_text is not None
|
|
679
|
-
and self._font_name is None
|
|
680
|
-
and self._font_size is None
|
|
681
|
-
and self._color is None
|
|
682
|
-
and self._line_spacing is None
|
|
683
|
-
and self._new_position is None
|
|
684
|
-
)
|
|
685
|
-
|
|
686
|
-
if only_text_changed:
|
|
687
|
-
result = self._client._modify_paragraph(self._object_ref, self._new_text)
|
|
688
|
-
self._has_changes = False
|
|
689
|
-
return result
|
|
690
|
-
|
|
691
|
-
only_move = (
|
|
692
|
-
self._new_position is not None
|
|
693
|
-
and self._new_text is None
|
|
694
|
-
and self._font_name is None
|
|
695
|
-
and self._font_size is None
|
|
696
|
-
and self._color is None
|
|
697
|
-
and self._line_spacing is None
|
|
698
|
-
)
|
|
699
|
-
|
|
700
|
-
if only_move:
|
|
701
|
-
page_number = (
|
|
702
|
-
self._object_ref.position.page_number
|
|
703
|
-
if self._object_ref.position
|
|
704
|
-
else None
|
|
705
|
-
)
|
|
706
|
-
if page_number is None:
|
|
707
|
-
raise ValidationException(
|
|
708
|
-
"Paragraph position must include a page number to move"
|
|
709
|
-
)
|
|
710
|
-
position = Position.at_page_coordinates(page_number, *self._new_position)
|
|
711
|
-
result = self._client._move(self._object_ref, position)
|
|
712
|
-
self._has_changes = False
|
|
713
|
-
return result
|
|
714
|
-
|
|
715
|
-
from .paragraph_builder import ParagraphBuilder
|
|
716
|
-
|
|
717
|
-
builder = ParagraphBuilder.from_object_ref(self._client, self._object_ref)
|
|
718
|
-
|
|
719
|
-
if self._new_text is not None:
|
|
720
|
-
builder.text(self._new_text)
|
|
721
|
-
if self._font_name is not None and self._font_size is not None:
|
|
722
|
-
builder.font(self._font_name, self._font_size)
|
|
723
|
-
if self._color is not None:
|
|
724
|
-
builder.color(self._color)
|
|
725
|
-
if self._line_spacing is not None:
|
|
726
|
-
builder.line_spacing(self._line_spacing)
|
|
727
|
-
if self._new_position is not None:
|
|
728
|
-
builder.move_to(*self._new_position)
|
|
729
|
-
|
|
730
|
-
result = builder.modify(self._object_ref)
|
|
731
|
-
self._has_changes = False
|
|
732
|
-
return result
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
class FormFieldEdit:
|
|
736
|
-
def __init__(self, form_field: "FormFieldObject", object_ref: FormFieldRef):
|
|
737
|
-
self.form_field = form_field
|
|
738
|
-
self.object_ref = object_ref
|
|
739
|
-
|
|
740
|
-
def value(self, new_value: str) -> "FormFieldEdit":
|
|
741
|
-
self.form_field.value = new_value
|
|
742
|
-
return self
|
|
743
|
-
|
|
744
|
-
def apply(self) -> bool:
|
|
745
|
-
# noinspection PyProtectedMember
|
|
746
|
-
return self.form_field._client._change_form_field(
|
|
747
|
-
self.object_ref, self.form_field.value
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
|
|
751
438
|
class FormFieldObject(PDFObjectBase):
|
|
752
439
|
def __init__(
|
|
753
440
|
self,
|
|
@@ -762,8 +449,11 @@ class FormFieldObject(PDFObjectBase):
|
|
|
762
449
|
self.name = field_name
|
|
763
450
|
self.value = field_value
|
|
764
451
|
|
|
765
|
-
def
|
|
766
|
-
|
|
452
|
+
def set_value(self, value: str) -> bool:
|
|
453
|
+
result = self._client._change_form_field(self.object_ref(), value)
|
|
454
|
+
if result:
|
|
455
|
+
self.value = value
|
|
456
|
+
return result
|
|
767
457
|
|
|
768
458
|
def object_ref(self) -> FormFieldRef:
|
|
769
459
|
ref = FormFieldRef(self.internal_id, self.position, self.object_type)
|
|
@@ -771,7 +461,7 @@ class FormFieldObject(PDFObjectBase):
|
|
|
771
461
|
ref.value = self.value
|
|
772
462
|
return ref
|
|
773
463
|
|
|
774
|
-
def __eq__(self, other):
|
|
464
|
+
def __eq__(self, other: object) -> bool:
|
|
775
465
|
if not isinstance(other, FormFieldObject):
|
|
776
466
|
return False
|
|
777
467
|
return (
|
|
@@ -788,22 +478,27 @@ class PathEditSession:
|
|
|
788
478
|
Fluent editing helper for modifying path stroke and fill colors.
|
|
789
479
|
"""
|
|
790
480
|
|
|
791
|
-
def __init__(self, client: "PDFDancer", object_ref):
|
|
481
|
+
def __init__(self, client: "PDFDancer", object_ref: ObjectRef) -> None:
|
|
792
482
|
self._client = client
|
|
793
483
|
self._object_ref = object_ref
|
|
794
|
-
self._stroke_color = None
|
|
795
|
-
self._fill_color = None
|
|
484
|
+
self._stroke_color: Optional["Color"] = None
|
|
485
|
+
self._fill_color: Optional["Color"] = None
|
|
796
486
|
|
|
797
|
-
def __enter__(self):
|
|
487
|
+
def __enter__(self) -> "PathEditSession":
|
|
798
488
|
return self
|
|
799
489
|
|
|
800
|
-
def __exit__(
|
|
490
|
+
def __exit__(
|
|
491
|
+
self,
|
|
492
|
+
exc_type: Optional[Type[BaseException]],
|
|
493
|
+
exc_val: Optional[BaseException],
|
|
494
|
+
exc_tb: Optional[TracebackType],
|
|
495
|
+
) -> Literal[False]:
|
|
801
496
|
if exc_type:
|
|
802
497
|
return False
|
|
803
498
|
self.apply()
|
|
804
499
|
return False
|
|
805
500
|
|
|
806
|
-
def stroke_color(self, color) -> "PathEditSession":
|
|
501
|
+
def stroke_color(self, color: "Color") -> "PathEditSession":
|
|
807
502
|
"""
|
|
808
503
|
Set the stroke/outline color.
|
|
809
504
|
|
|
@@ -816,7 +511,7 @@ class PathEditSession:
|
|
|
816
511
|
self._stroke_color = color
|
|
817
512
|
return self
|
|
818
513
|
|
|
819
|
-
def fill_color(self, color) -> "PathEditSession":
|
|
514
|
+
def fill_color(self, color: "Color") -> "PathEditSession":
|
|
820
515
|
"""
|
|
821
516
|
Set the fill color.
|
|
822
517
|
|
|
@@ -829,7 +524,7 @@ class PathEditSession:
|
|
|
829
524
|
self._fill_color = color
|
|
830
525
|
return self
|
|
831
526
|
|
|
832
|
-
def apply(self):
|
|
527
|
+
def apply(self) -> "CommandResult":
|
|
833
528
|
"""
|
|
834
529
|
Apply the color modifications to the path.
|
|
835
530
|
|