pdfdancer-client-python 0.3.9__tar.gz → 0.3.10__tar.gz
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_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.gitignore +1 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/PKG-INFO +2 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/README.md +1 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/pyproject.toml +1 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/models.py +20 -2
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/pdfdancer_v1.py +32 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer_client_python.egg-info/PKG-INFO +2 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/__init__.py +2 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_line.py +1 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_template_replace.py +52 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_models.py +95 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.claude/commands/implement-new-api-features.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.flake8 +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.github/workflows/daily-tests.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/LICENSE +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/NOTICE +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/TODO.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/check.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/docs/api-schemas/v0.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/docs/api-schemas/v1.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/media/logo-orange-512h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/media/logo-orange-60h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/media/logo-silver-512h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/media/logo-silver-60h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/release.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/setup.cfg +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/__init__.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/exceptions.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/page_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/path_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/text_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/types.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/test.sh +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/pdf_assertions.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_acroform.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_bezier_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_context_manager.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_form_x_objects.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_image.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_image_transform.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_new_pdf.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_paragraph.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_path.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_path_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_path_builder_rectangle.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_path_comprehensive.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_rectangle_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_redact.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_singular_selection.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_snapshot.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_text_line_edit.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/Showcase.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_anonymous_token.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_path_models.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_pdf_object_equality.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_rate_limit.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_standard_fonts.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/update-api-spec.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdfdancer-client-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: Python client for PDFDancer API
|
|
5
5
|
Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
|
|
6
6
|
License:
|
|
@@ -672,6 +672,7 @@ Contributions are welcome via pull request. Please:
|
|
|
672
672
|
- [PyPI](https://pypi.org/project/pdfdancer-client-python/)
|
|
673
673
|
- [Changelog](https://www.pdfdancer.com/changelog/?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
674
674
|
- [Status](https://status.pdfdancer.com?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
675
|
+
- [Issue tracker](https://github.com/MenschMachine/pdfdancer)
|
|
675
676
|
|
|
676
677
|
## Related SDKs
|
|
677
678
|
|
|
@@ -433,6 +433,7 @@ Contributions are welcome via pull request. Please:
|
|
|
433
433
|
- [PyPI](https://pypi.org/project/pdfdancer-client-python/)
|
|
434
434
|
- [Changelog](https://www.pdfdancer.com/changelog/?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
435
435
|
- [Status](https://status.pdfdancer.com?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
436
|
+
- [Issue tracker](https://github.com/MenschMachine/pdfdancer)
|
|
436
437
|
|
|
437
438
|
## Related SDKs
|
|
438
439
|
|
|
@@ -1686,18 +1686,22 @@ class TemplateReplacement:
|
|
|
1686
1686
|
|
|
1687
1687
|
Parameters:
|
|
1688
1688
|
- placeholder: The exact text to find and replace in the PDF.
|
|
1689
|
-
- text: The text to replace the placeholder with.
|
|
1689
|
+
- text: The text to replace the placeholder with. None for image replacements.
|
|
1690
1690
|
- font: Optional font for the replacement text.
|
|
1691
1691
|
- color: Optional color for the replacement text.
|
|
1692
|
+
- image: Optional Image to replace the placeholder with. When set, text should be None.
|
|
1692
1693
|
"""
|
|
1693
1694
|
|
|
1694
1695
|
placeholder: str
|
|
1695
|
-
text: str
|
|
1696
|
+
text: Optional[str] = None
|
|
1696
1697
|
font: Optional[Font] = None
|
|
1697
1698
|
color: Optional[Color] = None
|
|
1699
|
+
image: Optional[Image] = None
|
|
1698
1700
|
|
|
1699
1701
|
def to_dict(self) -> dict:
|
|
1700
1702
|
"""Convert to dictionary for JSON serialization."""
|
|
1703
|
+
import base64
|
|
1704
|
+
|
|
1701
1705
|
result: Dict[str, Any] = {
|
|
1702
1706
|
"placeholder": self.placeholder,
|
|
1703
1707
|
"text": self.text,
|
|
@@ -1711,6 +1715,20 @@ class TemplateReplacement:
|
|
|
1711
1715
|
"blue": self.color.b,
|
|
1712
1716
|
"alpha": self.color.a,
|
|
1713
1717
|
}
|
|
1718
|
+
if self.image:
|
|
1719
|
+
image_dict: Dict[str, Any] = {}
|
|
1720
|
+
if self.image.data:
|
|
1721
|
+
image_dict["data"] = base64.b64encode(self.image.data).decode("utf-8")
|
|
1722
|
+
if self.image.format:
|
|
1723
|
+
image_dict["format"] = self.image.format
|
|
1724
|
+
if self.image.width is not None or self.image.height is not None:
|
|
1725
|
+
size: Dict[str, float] = {}
|
|
1726
|
+
if self.image.width is not None:
|
|
1727
|
+
size["width"] = self.image.width
|
|
1728
|
+
if self.image.height is not None:
|
|
1729
|
+
size["height"] = self.image.height
|
|
1730
|
+
image_dict["size"] = size
|
|
1731
|
+
result["image"] = image_dict
|
|
1714
1732
|
return result
|
|
1715
1733
|
|
|
1716
1734
|
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
@@ -131,6 +131,34 @@ def _dict_to_replacements(
|
|
|
131
131
|
for placeholder, value in replacements.items():
|
|
132
132
|
if isinstance(value, str):
|
|
133
133
|
result.append(TemplateReplacement(placeholder=placeholder, text=value))
|
|
134
|
+
elif "image" in value:
|
|
135
|
+
image_source = value["image"]
|
|
136
|
+
if isinstance(image_source, Path):
|
|
137
|
+
image_data = image_source.read_bytes()
|
|
138
|
+
image_format = image_source.suffix.lstrip(".").upper()
|
|
139
|
+
if image_format == "JPG":
|
|
140
|
+
image_format = "JPEG"
|
|
141
|
+
elif isinstance(image_source, bytes):
|
|
142
|
+
image_data = image_source
|
|
143
|
+
image_format = None
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"Unsupported image source type: {type(image_source)}. "
|
|
147
|
+
"Use a Path or bytes."
|
|
148
|
+
)
|
|
149
|
+
img = Image(
|
|
150
|
+
data=image_data,
|
|
151
|
+
format=value.get("format", image_format),
|
|
152
|
+
width=value.get("width"),
|
|
153
|
+
height=value.get("height"),
|
|
154
|
+
)
|
|
155
|
+
result.append(
|
|
156
|
+
TemplateReplacement(
|
|
157
|
+
placeholder=placeholder,
|
|
158
|
+
text=None,
|
|
159
|
+
image=img,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
134
162
|
else:
|
|
135
163
|
result.append(
|
|
136
164
|
TemplateReplacement(
|
|
@@ -644,6 +672,8 @@ class PageClient:
|
|
|
644
672
|
replacements: Dict mapping placeholder strings to replacement values.
|
|
645
673
|
- Simple: {"{{NAME}}": "John Doe"}
|
|
646
674
|
- With options: {"{{NAME}}": {"text": "John", "font": Font(...), "color": Color(...)}}
|
|
675
|
+
- With image: {"{{LOGO}}": {"image": Path("logo.png")}}
|
|
676
|
+
- With image and size: {"{{LOGO}}": {"image": Path("logo.png"), "width": 50, "height": 50}}
|
|
647
677
|
reflow_preset: Optional ReflowPreset to control text reflow behavior.
|
|
648
678
|
- BEST_EFFORT: Attempt to reflow, proceed even if imperfect
|
|
649
679
|
- FIT_OR_FAIL: Reflow must succeed or operation fails
|
|
@@ -2281,6 +2311,8 @@ class PDFDancer:
|
|
|
2281
2311
|
replacements: Dict mapping placeholder strings to replacement values.
|
|
2282
2312
|
- Simple: {"{{NAME}}": "John Doe"}
|
|
2283
2313
|
- With options: {"{{NAME}}": {"text": "John", "font": Font(...), "color": Color(...)}}
|
|
2314
|
+
- With image: {"{{LOGO}}": {"image": Path("logo.png")}}
|
|
2315
|
+
- With image and size: {"{{LOGO}}": {"image": Path("logo.png"), "width": 50, "height": 50}}
|
|
2284
2316
|
reflow_preset: Optional ReflowPreset to control text reflow behavior.
|
|
2285
2317
|
- BEST_EFFORT: Attempt to reflow, proceed even if imperfect
|
|
2286
2318
|
- FIT_OR_FAIL: Reflow must succeed or operation fails
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdfdancer-client-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: Python client for PDFDancer API
|
|
5
5
|
Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
|
|
6
6
|
License:
|
|
@@ -672,6 +672,7 @@ Contributions are welcome via pull request. Please:
|
|
|
672
672
|
- [PyPI](https://pypi.org/project/pdfdancer-client-python/)
|
|
673
673
|
- [Changelog](https://www.pdfdancer.com/changelog/?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
674
674
|
- [Status](https://status.pdfdancer.com?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
675
|
+
- [Issue tracker](https://github.com/MenschMachine/pdfdancer)
|
|
675
676
|
|
|
676
677
|
## Related SDKs
|
|
677
678
|
|
|
@@ -4,6 +4,7 @@ from typing import Tuple
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
import pytest
|
|
7
|
+
from dotenv import load_dotenv
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def _get_base_url():
|
|
@@ -11,6 +12,7 @@ def _get_base_url():
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def _read_token() -> str | None:
|
|
15
|
+
load_dotenv()
|
|
14
16
|
# Check PDFDANCER_API_TOKEN first (preferred), then PDFDANCER_TOKEN (legacy)
|
|
15
17
|
token = os.getenv("PDFDANCER_API_TOKEN") or os.getenv("PDFDANCER_TOKEN")
|
|
16
18
|
if token:
|
|
@@ -21,7 +21,7 @@ def test_find_lines_by_position():
|
|
|
21
21
|
|
|
22
22
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
23
23
|
lines = pdf.select_text_lines()
|
|
24
|
-
assert len(lines) ==
|
|
24
|
+
assert len(lines) == 36
|
|
25
25
|
|
|
26
26
|
first = lines[0]
|
|
27
27
|
assert first.position is not None
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_template_replace.py
RENAMED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""E2E tests for template replacement functionality."""
|
|
2
2
|
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
7
|
from pdfdancer import Color, Font, ReflowPreset, ValidationException
|
|
@@ -222,3 +224,53 @@ def test_reflow_preset_values():
|
|
|
222
224
|
assert ReflowPreset.BEST_EFFORT.value == "BEST_EFFORT"
|
|
223
225
|
assert ReflowPreset.FIT_OR_FAIL.value == "FIT_OR_FAIL"
|
|
224
226
|
assert ReflowPreset.NONE.value == "NONE"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_replace_template_with_image():
|
|
230
|
+
"""Test replacing a placeholder with an image file (natural size)."""
|
|
231
|
+
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
232
|
+
logo_path = Path(__file__).resolve().parent.parent / "fixtures" / "logo-80.png"
|
|
233
|
+
assert logo_path.exists(), "logo-80.png fixture not found"
|
|
234
|
+
|
|
235
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
236
|
+
# Count images before replacement
|
|
237
|
+
images_before = pdf.select_images()
|
|
238
|
+
count_before = len(images_before)
|
|
239
|
+
|
|
240
|
+
# Replace "Showcase" placeholder with an image
|
|
241
|
+
result = pdf.apply_replacements({
|
|
242
|
+
"Showcase": {"image": logo_path},
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
assert result is True
|
|
246
|
+
|
|
247
|
+
# The placeholder text should be gone
|
|
248
|
+
(
|
|
249
|
+
PDFAssertions(pdf)
|
|
250
|
+
.assert_textline_does_not_exist("Showcase")
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# There should be more images now
|
|
254
|
+
images_after = pdf.select_images()
|
|
255
|
+
assert len(images_after) > count_before
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_replace_template_with_image_explicit_size():
|
|
259
|
+
"""Test replacing a placeholder with an image file with explicit width/height."""
|
|
260
|
+
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
261
|
+
logo_path = Path(__file__).resolve().parent.parent / "fixtures" / "logo-80.png"
|
|
262
|
+
assert logo_path.exists(), "logo-80.png fixture not found"
|
|
263
|
+
|
|
264
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
265
|
+
# Replace "Showcase" placeholder with a sized image
|
|
266
|
+
result = pdf.apply_replacements({
|
|
267
|
+
"Showcase": {"image": logo_path, "width": 50, "height": 50},
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
assert result is True
|
|
271
|
+
|
|
272
|
+
# The placeholder text should be gone
|
|
273
|
+
(
|
|
274
|
+
PDFAssertions(pdf)
|
|
275
|
+
.assert_textline_does_not_exist("Showcase")
|
|
276
|
+
)
|
|
@@ -16,9 +16,13 @@ from pdfdancer import (
|
|
|
16
16
|
PositionMode,
|
|
17
17
|
ShapeType,
|
|
18
18
|
)
|
|
19
|
+
from pdfdancer.models import TemplateReplacement
|
|
19
20
|
|
|
20
21
|
# Import Point class for tests
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
21
24
|
from pdfdancer.models import Point
|
|
25
|
+
from pdfdancer.pdfdancer_v1 import _dict_to_replacements
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
class TestPosition:
|
|
@@ -274,3 +278,94 @@ class TestPoint:
|
|
|
274
278
|
|
|
275
279
|
assert point.x == 123.45
|
|
276
280
|
assert point.y == 678.90
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class TestTemplateReplacement:
|
|
284
|
+
"""Test TemplateReplacement serialization."""
|
|
285
|
+
|
|
286
|
+
def test_text_replacement_to_dict(self):
|
|
287
|
+
"""Test basic text replacement serializes correctly."""
|
|
288
|
+
r = TemplateReplacement(placeholder="{{NAME}}", text="John")
|
|
289
|
+
d = r.to_dict()
|
|
290
|
+
assert d == {"placeholder": "{{NAME}}", "text": "John"}
|
|
291
|
+
|
|
292
|
+
def test_text_none_with_image_to_dict(self):
|
|
293
|
+
"""Test image replacement has text=None in serialized output."""
|
|
294
|
+
img = Image(data=b"\x89PNG", format="PNG")
|
|
295
|
+
r = TemplateReplacement(placeholder="{{LOGO}}", text=None, image=img)
|
|
296
|
+
d = r.to_dict()
|
|
297
|
+
assert d["text"] is None
|
|
298
|
+
assert "image" in d
|
|
299
|
+
assert d["image"]["format"] == "PNG"
|
|
300
|
+
|
|
301
|
+
def test_image_replacement_base64_encoding(self):
|
|
302
|
+
"""Test image data is base64-encoded in serialized output."""
|
|
303
|
+
import base64
|
|
304
|
+
|
|
305
|
+
raw = b"\x89PNG\r\n\x1a\nfakedata"
|
|
306
|
+
img = Image(data=raw, format="PNG")
|
|
307
|
+
r = TemplateReplacement(placeholder="{{LOGO}}", image=img)
|
|
308
|
+
d = r.to_dict()
|
|
309
|
+
assert d["image"]["data"] == base64.b64encode(raw).decode("utf-8")
|
|
310
|
+
|
|
311
|
+
def test_image_replacement_with_size(self):
|
|
312
|
+
"""Test image replacement with explicit width/height includes size."""
|
|
313
|
+
img = Image(data=b"img", format="JPEG", width=50, height=30)
|
|
314
|
+
r = TemplateReplacement(placeholder="{{PIC}}", image=img)
|
|
315
|
+
d = r.to_dict()
|
|
316
|
+
assert d["image"]["size"] == {"width": 50, "height": 30}
|
|
317
|
+
|
|
318
|
+
def test_image_replacement_without_size(self):
|
|
319
|
+
"""Test image replacement without width/height omits size."""
|
|
320
|
+
img = Image(data=b"img", format="PNG")
|
|
321
|
+
r = TemplateReplacement(placeholder="{{PIC}}", image=img)
|
|
322
|
+
d = r.to_dict()
|
|
323
|
+
assert "size" not in d["image"]
|
|
324
|
+
|
|
325
|
+
def test_image_replacement_with_partial_size(self):
|
|
326
|
+
"""Test image replacement with only width includes partial size."""
|
|
327
|
+
img = Image(data=b"img", format="PNG", width=100)
|
|
328
|
+
r = TemplateReplacement(placeholder="{{PIC}}", image=img)
|
|
329
|
+
d = r.to_dict()
|
|
330
|
+
assert d["image"]["size"] == {"width": 100}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class TestDictToReplacements:
|
|
334
|
+
"""Test _dict_to_replacements helper."""
|
|
335
|
+
|
|
336
|
+
def test_string_value(self):
|
|
337
|
+
"""Test simple string values produce text replacements."""
|
|
338
|
+
result = _dict_to_replacements({"{{A}}": "hello"})
|
|
339
|
+
assert len(result) == 1
|
|
340
|
+
assert result[0].placeholder == "{{A}}"
|
|
341
|
+
assert result[0].text == "hello"
|
|
342
|
+
assert result[0].image is None
|
|
343
|
+
|
|
344
|
+
def test_dict_with_text(self):
|
|
345
|
+
"""Test dict values with 'text' key produce text replacements."""
|
|
346
|
+
result = _dict_to_replacements({"{{A}}": {"text": "hi", "font": Font("Arial", 12)}})
|
|
347
|
+
assert result[0].text == "hi"
|
|
348
|
+
assert result[0].font.name == "Arial"
|
|
349
|
+
|
|
350
|
+
def test_dict_with_image_path(self):
|
|
351
|
+
"""Test dict values with 'image' Path produce image replacements."""
|
|
352
|
+
logo = Path(__file__).parent / "fixtures" / "logo-80.png"
|
|
353
|
+
result = _dict_to_replacements({"{{LOGO}}": {"image": logo}})
|
|
354
|
+
assert len(result) == 1
|
|
355
|
+
assert result[0].text is None
|
|
356
|
+
assert result[0].image is not None
|
|
357
|
+
assert result[0].image.data == logo.read_bytes()
|
|
358
|
+
assert result[0].image.format == "PNG"
|
|
359
|
+
|
|
360
|
+
def test_dict_with_image_path_and_size(self):
|
|
361
|
+
"""Test dict values with 'image' Path and width/height."""
|
|
362
|
+
logo = Path(__file__).parent / "fixtures" / "logo-80.png"
|
|
363
|
+
result = _dict_to_replacements({"{{LOGO}}": {"image": logo, "width": 50, "height": 30}})
|
|
364
|
+
assert result[0].image.width == 50
|
|
365
|
+
assert result[0].image.height == 30
|
|
366
|
+
|
|
367
|
+
def test_dict_with_image_bytes(self):
|
|
368
|
+
"""Test dict values with 'image' as raw bytes."""
|
|
369
|
+
result = _dict_to_replacements({"{{LOGO}}": {"image": b"rawdata"}})
|
|
370
|
+
assert result[0].image.data == b"rawdata"
|
|
371
|
+
assert result[0].text is None
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.claude/commands/discuss.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/.github/workflows/daily-tests.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/media/logo-orange-512h.webp
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/media/logo-silver-512h.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/exceptions.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/fingerprint.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/image_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/page_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/paragraph_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/path_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/src/pdfdancer/text_line_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/pdf_assertions.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_bezier_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_context_manager.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_form_x_objects.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_image_transform.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_line_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_paragraph.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_path_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_pdfdancer.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_positioning.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_rectangle_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/e2e/test_text_line_edit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/Showcase.pdf
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/basic-paths.pdf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/fixtures/mixed-form-types.pdf
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_anonymous_token.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_openapi_compliance.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_pdf_object_equality.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.10}/tests/test_standard_fonts.py
RENAMED
|
File without changes
|
|
File without changes
|