pdfdancer-client-python 0.3.5__py3-none-any.whl → 0.3.7__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 +2 -0
- pdfdancer/models.py +89 -0
- pdfdancer/pdfdancer_v1.py +142 -1
- {pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/METADATA +1 -1
- {pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/RECORD +9 -9
- {pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/WHEEL +0 -0
- {pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/licenses/NOTICE +0 -0
- {pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/top_level.txt +0 -0
pdfdancer/__init__.py
CHANGED
|
@@ -40,6 +40,7 @@ from .models import (
|
|
|
40
40
|
PositionMode,
|
|
41
41
|
RedactResponse,
|
|
42
42
|
RedactTarget,
|
|
43
|
+
ReflowPreset,
|
|
43
44
|
ShapeType,
|
|
44
45
|
Size,
|
|
45
46
|
StandardFonts,
|
|
@@ -90,6 +91,7 @@ __all__ = [
|
|
|
90
91
|
"Path",
|
|
91
92
|
"RedactTarget",
|
|
92
93
|
"RedactResponse",
|
|
94
|
+
"ReflowPreset",
|
|
93
95
|
"PdfDancerException",
|
|
94
96
|
"FontNotFoundException",
|
|
95
97
|
"ValidationException",
|
pdfdancer/models.py
CHANGED
|
@@ -1662,6 +1662,95 @@ class ImageFlipDirection(Enum):
|
|
|
1662
1662
|
BOTH = "BOTH"
|
|
1663
1663
|
|
|
1664
1664
|
|
|
1665
|
+
class ReflowPreset(Enum):
|
|
1666
|
+
"""Reflow preset for template replacement operations.
|
|
1667
|
+
|
|
1668
|
+
Controls how text reflow is handled when replacement text differs in length
|
|
1669
|
+
from the original placeholder.
|
|
1670
|
+
|
|
1671
|
+
Values:
|
|
1672
|
+
- BEST_EFFORT: Attempt to reflow text, but proceed even if it doesn't fit perfectly.
|
|
1673
|
+
- FIT_OR_FAIL: Reflow must succeed or the operation fails.
|
|
1674
|
+
- NONE: No reflow - replacement text is placed as-is.
|
|
1675
|
+
"""
|
|
1676
|
+
|
|
1677
|
+
BEST_EFFORT = "BEST_EFFORT"
|
|
1678
|
+
FIT_OR_FAIL = "FIT_OR_FAIL"
|
|
1679
|
+
NONE = "NONE"
|
|
1680
|
+
|
|
1681
|
+
|
|
1682
|
+
@dataclass
|
|
1683
|
+
class TemplateReplacement:
|
|
1684
|
+
"""A single template placeholder replacement.
|
|
1685
|
+
|
|
1686
|
+
Parameters:
|
|
1687
|
+
- placeholder: The exact text to find and replace in the PDF.
|
|
1688
|
+
- text: The text to replace the placeholder with.
|
|
1689
|
+
- font: Optional font for the replacement text.
|
|
1690
|
+
- color: Optional color for the replacement text.
|
|
1691
|
+
"""
|
|
1692
|
+
|
|
1693
|
+
placeholder: str
|
|
1694
|
+
text: str
|
|
1695
|
+
font: Optional[Font] = None
|
|
1696
|
+
color: Optional[Color] = None
|
|
1697
|
+
|
|
1698
|
+
def to_dict(self) -> dict:
|
|
1699
|
+
"""Convert to dictionary for JSON serialization."""
|
|
1700
|
+
result: Dict[str, Any] = {
|
|
1701
|
+
"placeholder": self.placeholder,
|
|
1702
|
+
"text": self.text,
|
|
1703
|
+
}
|
|
1704
|
+
if self.font:
|
|
1705
|
+
result["font"] = {"name": self.font.name, "size": self.font.size}
|
|
1706
|
+
if self.color:
|
|
1707
|
+
result["color"] = {
|
|
1708
|
+
"red": self.color.r,
|
|
1709
|
+
"green": self.color.g,
|
|
1710
|
+
"blue": self.color.b,
|
|
1711
|
+
"alpha": self.color.a,
|
|
1712
|
+
}
|
|
1713
|
+
return result
|
|
1714
|
+
|
|
1715
|
+
|
|
1716
|
+
@dataclass
|
|
1717
|
+
class TemplateReplaceRequest:
|
|
1718
|
+
"""Request for batch template placeholder replacements.
|
|
1719
|
+
|
|
1720
|
+
Parameters:
|
|
1721
|
+
- replacements: List of TemplateReplacement objects.
|
|
1722
|
+
- page_index: Optional 0-based page index. If None, applies to all pages.
|
|
1723
|
+
- reflow_preset: Optional ReflowPreset for text reflow behavior.
|
|
1724
|
+
|
|
1725
|
+
Example:
|
|
1726
|
+
```python
|
|
1727
|
+
request = TemplateReplaceRequest(
|
|
1728
|
+
replacements=[
|
|
1729
|
+
TemplateReplacement("{{NAME}}", "John Doe"),
|
|
1730
|
+
TemplateReplacement("{{DATE}}", "2025-01-15"),
|
|
1731
|
+
],
|
|
1732
|
+
page_index=0, # First page (0-based)
|
|
1733
|
+
reflow_preset=ReflowPreset.BEST_EFFORT,
|
|
1734
|
+
)
|
|
1735
|
+
```
|
|
1736
|
+
"""
|
|
1737
|
+
|
|
1738
|
+
replacements: List[TemplateReplacement]
|
|
1739
|
+
page_index: Optional[int] = None
|
|
1740
|
+
reflow_preset: Optional[ReflowPreset] = None
|
|
1741
|
+
|
|
1742
|
+
def to_dict(self) -> dict:
|
|
1743
|
+
"""Convert to dictionary for JSON serialization."""
|
|
1744
|
+
result: Dict[str, Any] = {
|
|
1745
|
+
"replacements": [r.to_dict() for r in self.replacements],
|
|
1746
|
+
}
|
|
1747
|
+
if self.page_index is not None:
|
|
1748
|
+
result["pageIndex"] = self.page_index
|
|
1749
|
+
if self.reflow_preset is not None:
|
|
1750
|
+
result["reflowPreset"] = self.reflow_preset.value
|
|
1751
|
+
return result
|
|
1752
|
+
|
|
1753
|
+
|
|
1665
1754
|
@dataclass
|
|
1666
1755
|
class ImageTransformRequest:
|
|
1667
1756
|
"""Request to transform an image in the PDF document.
|
pdfdancer/pdfdancer_v1.py
CHANGED
|
@@ -16,7 +16,7 @@ import time
|
|
|
16
16
|
from datetime import datetime, timezone
|
|
17
17
|
from importlib.metadata import version as get_package_version
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import TYPE_CHECKING, Any, BinaryIO, List, Mapping, Optional, Union
|
|
19
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, List, Mapping, Optional, Union
|
|
20
20
|
|
|
21
21
|
import httpx
|
|
22
22
|
from dotenv import load_dotenv
|
|
@@ -62,7 +62,10 @@ from .models import (
|
|
|
62
62
|
RedactRequest,
|
|
63
63
|
RedactResponse,
|
|
64
64
|
RedactTarget,
|
|
65
|
+
ReflowPreset,
|
|
65
66
|
ShapeType,
|
|
67
|
+
TemplateReplacement,
|
|
68
|
+
TemplateReplaceRequest,
|
|
66
69
|
TextLine,
|
|
67
70
|
TextObjectRef,
|
|
68
71
|
)
|
|
@@ -120,6 +123,26 @@ DEFAULT_RETRY_BACKOFF_FACTOR = float(
|
|
|
120
123
|
)
|
|
121
124
|
|
|
122
125
|
|
|
126
|
+
def _dict_to_replacements(
|
|
127
|
+
replacements: Dict[str, Union[str, dict]]
|
|
128
|
+
) -> List[TemplateReplacement]:
|
|
129
|
+
"""Convert dict-based replacements to TemplateReplacement list."""
|
|
130
|
+
result = []
|
|
131
|
+
for placeholder, value in replacements.items():
|
|
132
|
+
if isinstance(value, str):
|
|
133
|
+
result.append(TemplateReplacement(placeholder=placeholder, text=value))
|
|
134
|
+
else:
|
|
135
|
+
result.append(
|
|
136
|
+
TemplateReplacement(
|
|
137
|
+
placeholder=placeholder,
|
|
138
|
+
text=value["text"],
|
|
139
|
+
font=value.get("font"),
|
|
140
|
+
color=value.get("color"),
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
123
146
|
def _generate_timestamp() -> str:
|
|
124
147
|
"""
|
|
125
148
|
Generate a timestamp string in the format expected by the API.
|
|
@@ -606,6 +629,44 @@ class PageClient:
|
|
|
606
629
|
self.position = Position.at_page(target_page_number)
|
|
607
630
|
return moved
|
|
608
631
|
|
|
632
|
+
def apply_replacements(
|
|
633
|
+
self,
|
|
634
|
+
replacements: Dict[str, Union[str, dict]],
|
|
635
|
+
reflow_preset: Optional[ReflowPreset] = None,
|
|
636
|
+
) -> bool:
|
|
637
|
+
"""
|
|
638
|
+
Replace template placeholders on this page.
|
|
639
|
+
|
|
640
|
+
Finds exact text matches for placeholders and replaces them with specified
|
|
641
|
+
content. All placeholders must be found or the operation fails atomically.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
replacements: Dict mapping placeholder strings to replacement values.
|
|
645
|
+
- Simple: {"{{NAME}}": "John Doe"}
|
|
646
|
+
- With options: {"{{NAME}}": {"text": "John", "font": Font(...), "color": Color(...)}}
|
|
647
|
+
reflow_preset: Optional ReflowPreset to control text reflow behavior.
|
|
648
|
+
- BEST_EFFORT: Attempt to reflow, proceed even if imperfect
|
|
649
|
+
- FIT_OR_FAIL: Reflow must succeed or operation fails
|
|
650
|
+
- NONE: No reflow, replacement placed as-is
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
True if all replacements were successful
|
|
654
|
+
|
|
655
|
+
Example:
|
|
656
|
+
```python
|
|
657
|
+
page.apply_replacements({
|
|
658
|
+
"{{NAME}}": "John Doe",
|
|
659
|
+
})
|
|
660
|
+
```
|
|
661
|
+
"""
|
|
662
|
+
replacement_list = _dict_to_replacements(replacements)
|
|
663
|
+
# noinspection PyProtectedMember
|
|
664
|
+
return self.root._apply_replacements(
|
|
665
|
+
replacements=replacement_list,
|
|
666
|
+
page_number=self.page_number,
|
|
667
|
+
reflow_preset=reflow_preset,
|
|
668
|
+
)
|
|
669
|
+
|
|
609
670
|
def _ref(self):
|
|
610
671
|
return ObjectRef(
|
|
611
672
|
internal_id=self.internal_id, position=self.position, type=self.object_type
|
|
@@ -2163,6 +2224,86 @@ class PDFDancer:
|
|
|
2163
2224
|
targets = [RedactTarget(obj.internal_id, replacement) for obj in objects]
|
|
2164
2225
|
return self._redact(targets, replacement, placeholder_color)
|
|
2165
2226
|
|
|
2227
|
+
# Template Replacement Operations
|
|
2228
|
+
|
|
2229
|
+
def _apply_replacements(
|
|
2230
|
+
self,
|
|
2231
|
+
replacements: List[TemplateReplacement],
|
|
2232
|
+
page_number: Optional[int] = None,
|
|
2233
|
+
reflow_preset: Optional[ReflowPreset] = None,
|
|
2234
|
+
) -> bool:
|
|
2235
|
+
"""
|
|
2236
|
+
Internal method to replace template placeholders in the PDF.
|
|
2237
|
+
|
|
2238
|
+
Args:
|
|
2239
|
+
replacements: List of TemplateReplacement objects
|
|
2240
|
+
page_number: Optional 1-based page number. If None, applies to all pages.
|
|
2241
|
+
reflow_preset: Optional reflow behavior preset
|
|
2242
|
+
|
|
2243
|
+
Returns:
|
|
2244
|
+
True if replacement was successful
|
|
2245
|
+
"""
|
|
2246
|
+
if not replacements:
|
|
2247
|
+
raise ValidationException("At least one replacement is required")
|
|
2248
|
+
|
|
2249
|
+
# Convert 1-based page_number to 0-based page_index for API
|
|
2250
|
+
page_index = None
|
|
2251
|
+
if page_number is not None:
|
|
2252
|
+
page_index = page_number - 1
|
|
2253
|
+
|
|
2254
|
+
request = TemplateReplaceRequest(
|
|
2255
|
+
replacements=replacements,
|
|
2256
|
+
page_index=page_index,
|
|
2257
|
+
reflow_preset=reflow_preset,
|
|
2258
|
+
)
|
|
2259
|
+
response = self._make_request(
|
|
2260
|
+
"POST", "/template/replace", data=request.to_dict()
|
|
2261
|
+
)
|
|
2262
|
+
result = response.json()
|
|
2263
|
+
|
|
2264
|
+
if result:
|
|
2265
|
+
self._invalidate_snapshots()
|
|
2266
|
+
|
|
2267
|
+
return result
|
|
2268
|
+
|
|
2269
|
+
def apply_replacements(
|
|
2270
|
+
self,
|
|
2271
|
+
replacements: Dict[str, Union[str, dict]],
|
|
2272
|
+
reflow_preset: Optional[ReflowPreset] = None,
|
|
2273
|
+
) -> bool:
|
|
2274
|
+
"""
|
|
2275
|
+
Replace template placeholders in the PDF document.
|
|
2276
|
+
|
|
2277
|
+
Finds exact text matches for placeholders and replaces them with specified
|
|
2278
|
+
content. All placeholders must be found or the operation fails atomically.
|
|
2279
|
+
|
|
2280
|
+
Args:
|
|
2281
|
+
replacements: Dict mapping placeholder strings to replacement values.
|
|
2282
|
+
- Simple: {"{{NAME}}": "John Doe"}
|
|
2283
|
+
- With options: {"{{NAME}}": {"text": "John", "font": Font(...), "color": Color(...)}}
|
|
2284
|
+
reflow_preset: Optional ReflowPreset to control text reflow behavior.
|
|
2285
|
+
- BEST_EFFORT: Attempt to reflow, proceed even if imperfect
|
|
2286
|
+
- FIT_OR_FAIL: Reflow must succeed or operation fails
|
|
2287
|
+
- NONE: No reflow, replacement placed as-is
|
|
2288
|
+
|
|
2289
|
+
Returns:
|
|
2290
|
+
True if all replacements were successful
|
|
2291
|
+
|
|
2292
|
+
Example:
|
|
2293
|
+
```python
|
|
2294
|
+
pdf.apply_replacements({
|
|
2295
|
+
"{{NAME}}": "John Doe",
|
|
2296
|
+
"{{DATE}}": "2025-01-15",
|
|
2297
|
+
})
|
|
2298
|
+
```
|
|
2299
|
+
"""
|
|
2300
|
+
replacement_list = _dict_to_replacements(replacements)
|
|
2301
|
+
return self._apply_replacements(
|
|
2302
|
+
replacements=replacement_list,
|
|
2303
|
+
page_number=None,
|
|
2304
|
+
reflow_preset=reflow_preset,
|
|
2305
|
+
)
|
|
2306
|
+
|
|
2166
2307
|
# Add Operations
|
|
2167
2308
|
|
|
2168
2309
|
def _add_image(self, image: Image, position: Optional[Position] = None) -> bool:
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
pdfdancer/__init__.py,sha256=
|
|
1
|
+
pdfdancer/__init__.py,sha256=I4dxD9zvsj9poA8JRcnuU3ETI9kjaaNjFlSj13Dc3Ug,2723
|
|
2
2
|
pdfdancer/exceptions.py,sha256=mJacmUJTPGUsB8Bo_FgfeXYkvZkH5OPJCVBfilBVmQo,2058
|
|
3
3
|
pdfdancer/fingerprint.py,sha256=eL3PHPgv-knMya7s95RXg3qzzpkAA1aevxqb6tuOb34,3061
|
|
4
4
|
pdfdancer/image_builder.py,sha256=aBxMFAMFzzbGTjlVH0hi94mA81cH8tp37Pk84HRPV00,1892
|
|
5
|
-
pdfdancer/models.py,sha256=
|
|
5
|
+
pdfdancer/models.py,sha256=nhyVIOr70PMrp3etwrtaduzRLC7jUrJxTa9ZuJXhj9Y,58371
|
|
6
6
|
pdfdancer/page_builder.py,sha256=ARWLRtlrLGbES-0nbiigTOsRVmodRX0DNK8YIAkA9Ig,3850
|
|
7
7
|
pdfdancer/paragraph_builder.py,sha256=OmhzYazMnq0n0rhrjWcKbo0LQfEC7BZoiLB29ycF630,20504
|
|
8
8
|
pdfdancer/path_builder.py,sha256=safKb_IeHRWlQbyBTIXfcoBfXxUZhuzYvBIob5Tbp-8,23938
|
|
9
|
-
pdfdancer/pdfdancer_v1.py,sha256=
|
|
9
|
+
pdfdancer/pdfdancer_v1.py,sha256=xr_JdiXnv1X3gIt5qsy-WrlLNy_DJZHFZSF94NurI2M,130060
|
|
10
10
|
pdfdancer/text_line_builder.py,sha256=8jYknV4hw0fyzwX0OI_oLvnh5aMmCV3jXaVkmYLu6MQ,10273
|
|
11
11
|
pdfdancer/types.py,sha256=GJpfp7nuIUbnDBmQ3yoyGyQYFuFO1ppHmo7fcfCM0Ks,21554
|
|
12
|
-
pdfdancer_client_python-0.3.
|
|
13
|
-
pdfdancer_client_python-0.3.
|
|
14
|
-
pdfdancer_client_python-0.3.
|
|
15
|
-
pdfdancer_client_python-0.3.
|
|
16
|
-
pdfdancer_client_python-0.3.
|
|
17
|
-
pdfdancer_client_python-0.3.
|
|
12
|
+
pdfdancer_client_python-0.3.7.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
13
|
+
pdfdancer_client_python-0.3.7.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
|
|
14
|
+
pdfdancer_client_python-0.3.7.dist-info/METADATA,sha256=ANHM-uaHat85Owb6YPSxyMSxB9cGfOkfxlpBEf0YPaQ,27907
|
|
15
|
+
pdfdancer_client_python-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
+
pdfdancer_client_python-0.3.7.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
|
|
17
|
+
pdfdancer_client_python-0.3.7.dist-info/RECORD,,
|
|
File without changes
|
{pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/licenses/NOTICE
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.5.dist-info → pdfdancer_client_python-0.3.7.dist-info}/top_level.txt
RENAMED
|
File without changes
|