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 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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.3.5
3
+ Version: 0.3.7
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -1,17 +1,17 @@
1
- pdfdancer/__init__.py,sha256=IOh8tTro6MQ3dnNT1TcNBtYMBFBdnWqAaEBSwf6hx7Q,2685
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=HztGaGHOH81qvca5aFhEwQ16xlORVWaT1mk9c0RtS58,55644
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=DURns2KrstV14WyzP3Swydzd0DgH6T7U-ULDcYf99Kc,125122
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.5.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
13
- pdfdancer_client_python-0.3.5.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
14
- pdfdancer_client_python-0.3.5.dist-info/METADATA,sha256=1ZSqvW_nOKKOH49Mcb14S-YoMFIQLfKne6BABqh5VIw,27907
15
- pdfdancer_client_python-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- pdfdancer_client_python-0.3.5.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
17
- pdfdancer_client_python-0.3.5.dist-info/RECORD,,
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,,