pdfdancer-client-python 0.2.14__tar.gz → 0.2.16__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.
Potentially problematic release.
This version of pdfdancer-client-python might be problematic. Click here for more details.
- pdfdancer_client_python-0.2.16/PKG-INFO +190 -0
- pdfdancer_client_python-0.2.16/README.md +158 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/pyproject.toml +1 -1
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/__init__.py +5 -1
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/models.py +87 -14
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/pdfdancer_v1.py +33 -9
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/types.py +18 -5
- pdfdancer_client_python-0.2.16/src/pdfdancer_client_python.egg-info/PKG-INFO +190 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/pdf_assertions.py +0 -2
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_form_x_objects.py +0 -2
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_line.py +56 -6
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_new_pdf.py +25 -3
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_paragraph.py +98 -12
- pdfdancer_client_python-0.2.14/PKG-INFO +0 -200
- pdfdancer_client_python-0.2.14/README.md +0 -168
- pdfdancer_client_python-0.2.14/src/pdfdancer_client_python.egg-info/PKG-INFO +0 -200
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/.gitignore +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/docs/openapi.yml +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/release.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/setup.cfg +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/exceptions.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/__init__.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_acroform.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_image.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_path.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/ObviouslyAwesome.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_models.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_pdf_object_equality.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_standard_fonts.py +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pdfdancer-client-python
|
|
3
|
+
Version: 0.2.16
|
|
4
|
+
Summary: Python client for PDFDancer API
|
|
5
|
+
Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.pdfdancer.com/
|
|
8
|
+
Project-URL: Repository, https://github.com/MenschMachine/pdfdancer-client-python
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.25.0
|
|
19
|
+
Requires-Dist: pydantic>=1.8.0
|
|
20
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
21
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
27
|
+
Requires-Dist: flake8>=5.0; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
29
|
+
Requires-Dist: isort>=5.10.0; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=0.8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
32
|
+
|
|
33
|
+
# PDFDancer Python Client
|
|
34
|
+
|
|
35
|
+
**Getting Started with PDFDancer**
|
|
36
|
+
|
|
37
|
+
PDFDancer gives you pixel-perfect programmatic control over any PDF document from Python. Locate existing elements by
|
|
38
|
+
coordinates or text, adjust them precisely, add brand-new content, and ship the modified PDF in memory or on disk. The
|
|
39
|
+
same API is also available for TypeScript and Java, so teams can orchestrate identical PDF workflows across stacks.
|
|
40
|
+
|
|
41
|
+
> Need the raw API schema? The latest OpenAPI description lives in `docs/openapi.yml` and is published at
|
|
42
|
+
> https://bucket.pdfdancer.com/api-doc/development-0.0.yml.
|
|
43
|
+
|
|
44
|
+
## Highlights
|
|
45
|
+
|
|
46
|
+
- Locate paragraphs, text lines, images, vector paths, form fields, and pages by index, coordinates, or text prefixes.
|
|
47
|
+
- Edit existing content in place with fluent editors and context managers that apply changes safely.
|
|
48
|
+
- Programmatically control third-party PDFs—modify invoices, contracts, and reports you did not author.
|
|
49
|
+
- Add content with precise XY positioning using paragraph and image builders, custom fonts, and color helpers.
|
|
50
|
+
- Export results as bytes for downstream processing or save directly to disk with one call.
|
|
51
|
+
|
|
52
|
+
## What Makes PDFDancer Different
|
|
53
|
+
|
|
54
|
+
- **Edit any PDF**: Work with documents from customers, governments, or vendors—not just ones you generated.
|
|
55
|
+
- **Pixel-perfect positioning**: Move or add elements at exact coordinates and keep the original layout intact.
|
|
56
|
+
- **Surgical text replacement**: Swap or rewrite paragraphs without reflowing the rest of the page.
|
|
57
|
+
- **Form manipulation**: Inspect, fill, and update AcroForm fields programmatically.
|
|
58
|
+
- **Coordinate-based selection**: Select objects by position, bounding box, or text patterns.
|
|
59
|
+
- **Real PDF editing**: Modify the underlying PDF structure instead of merely stamping overlays.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install pdfdancer-client-python
|
|
65
|
+
|
|
66
|
+
# Editable install for local development
|
|
67
|
+
pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Requires Python 3.10+ and a PDFDancer API token.
|
|
71
|
+
|
|
72
|
+
## Quick Start — Edit an Existing PDF
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from pathlib import Path
|
|
76
|
+
from pdfdancer import Color, PDFDancer, StandardFonts
|
|
77
|
+
|
|
78
|
+
with PDFDancer.open(
|
|
79
|
+
pdf_data=Path("input.pdf"),
|
|
80
|
+
token="your-api-token", # optional when PDFDANCER_TOKEN is set
|
|
81
|
+
base_url="https://api.pdfdancer.com",
|
|
82
|
+
) as pdf:
|
|
83
|
+
# Locate and update an existing paragraph
|
|
84
|
+
heading = pdf.page(0).select_paragraphs_starting_with("Executive Summary")[0]
|
|
85
|
+
heading.move_to(72, 680)
|
|
86
|
+
with heading.edit() as editor:
|
|
87
|
+
editor.replace("Overview")
|
|
88
|
+
|
|
89
|
+
# Add a new paragraph with precise placement
|
|
90
|
+
pdf.new_paragraph() \
|
|
91
|
+
.text("Generated with PDFDancer") \
|
|
92
|
+
.font(StandardFonts.HELVETICA, 12) \
|
|
93
|
+
.color(Color(70, 70, 70)) \
|
|
94
|
+
.line_spacing(1.4) \
|
|
95
|
+
.at(page_index=0, x=72, y=520) \
|
|
96
|
+
.add()
|
|
97
|
+
|
|
98
|
+
# Persist the modified document
|
|
99
|
+
pdf.save("output.pdf")
|
|
100
|
+
# or keep it in memory
|
|
101
|
+
pdf_bytes = pdf.get_bytes()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Create a Blank PDF
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from pathlib import Path
|
|
108
|
+
from pdfdancer import Color, PDFDancer, StandardFonts
|
|
109
|
+
|
|
110
|
+
with PDFDancer.new(token="your-api-token") as pdf:
|
|
111
|
+
pdf.new_paragraph() \
|
|
112
|
+
.text("Quarterly Summary") \
|
|
113
|
+
.font(StandardFonts.TIMES_BOLD, 18) \
|
|
114
|
+
.color(Color(10, 10, 80)) \
|
|
115
|
+
.line_spacing(1.2) \
|
|
116
|
+
.at(page_index=0, x=72, y=730) \
|
|
117
|
+
.add()
|
|
118
|
+
|
|
119
|
+
pdf.new_image() \
|
|
120
|
+
.from_file(Path("logo.png")) \
|
|
121
|
+
.at(page=0, x=420, y=710) \
|
|
122
|
+
.add()
|
|
123
|
+
|
|
124
|
+
pdf.save("summary.pdf")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Work with Forms and Layout
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from pdfdancer import PDFDancer
|
|
131
|
+
|
|
132
|
+
with PDFDancer.open("contract.pdf") as pdf:
|
|
133
|
+
# Inspect global document structure
|
|
134
|
+
pages = pdf.pages()
|
|
135
|
+
print("Total pages:", len(pages))
|
|
136
|
+
|
|
137
|
+
# Update form fields
|
|
138
|
+
signature = pdf.select_form_fields_by_name("signature")[0]
|
|
139
|
+
signature.edit().value("Signed by Jane Doe").apply()
|
|
140
|
+
|
|
141
|
+
# Trim or move content at specific coordinates
|
|
142
|
+
images = pdf.page(1).select_images()
|
|
143
|
+
for image in images:
|
|
144
|
+
x = image.position.x()
|
|
145
|
+
if x is not None and x < 100:
|
|
146
|
+
image.delete()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
|
|
150
|
+
with helpers such as `delete()`, `move_to(x, y)`, or `edit()` depending on the object type.
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
- Set `PDFDANCER_TOKEN` for authentication (preferred for local development and CI).
|
|
155
|
+
- Override the API host with `PDFDANCER_BASE_URL` (e.g., sandbox environments).
|
|
156
|
+
- Tune HTTP read timeouts via the `timeout` argument on `PDFDancer.open()` and `PDFDancer.new()`.
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
Operations raise subclasses of `PdfDancerException`:
|
|
161
|
+
|
|
162
|
+
- `ValidationException`: input validation problems (missing token, invalid coordinates, etc.).
|
|
163
|
+
- `FontNotFoundException`: requested font unavailable on the service.
|
|
164
|
+
- `HttpClientException`: transport or server errors with detailed context.
|
|
165
|
+
- `SessionException`: session creation and lifecycle failures.
|
|
166
|
+
|
|
167
|
+
Wrap automated workflows in `try/except` blocks to surface actionable errors to your users.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
python -m venv venv
|
|
173
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
174
|
+
pip install -e ".[dev]"
|
|
175
|
+
|
|
176
|
+
pytest -q # unit suite
|
|
177
|
+
pytest tests/e2e # integration tests (requires live API + fixtures)
|
|
178
|
+
python -m build # produce distribution artifacts
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Releases are published with `python release.py`. Contributions are welcome via pull request.
|
|
182
|
+
|
|
183
|
+
## Related SDKs
|
|
184
|
+
|
|
185
|
+
- TypeScript client: https://github.com/MenschMachine/pdfdancer-client-js
|
|
186
|
+
- Java client: https://github.com/MenschMachine/pdfdancer-client-java
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT © The Famous Cat Ltd.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# PDFDancer Python Client
|
|
2
|
+
|
|
3
|
+
**Getting Started with PDFDancer**
|
|
4
|
+
|
|
5
|
+
PDFDancer gives you pixel-perfect programmatic control over any PDF document from Python. Locate existing elements by
|
|
6
|
+
coordinates or text, adjust them precisely, add brand-new content, and ship the modified PDF in memory or on disk. The
|
|
7
|
+
same API is also available for TypeScript and Java, so teams can orchestrate identical PDF workflows across stacks.
|
|
8
|
+
|
|
9
|
+
> Need the raw API schema? The latest OpenAPI description lives in `docs/openapi.yml` and is published at
|
|
10
|
+
> https://bucket.pdfdancer.com/api-doc/development-0.0.yml.
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- Locate paragraphs, text lines, images, vector paths, form fields, and pages by index, coordinates, or text prefixes.
|
|
15
|
+
- Edit existing content in place with fluent editors and context managers that apply changes safely.
|
|
16
|
+
- Programmatically control third-party PDFs—modify invoices, contracts, and reports you did not author.
|
|
17
|
+
- Add content with precise XY positioning using paragraph and image builders, custom fonts, and color helpers.
|
|
18
|
+
- Export results as bytes for downstream processing or save directly to disk with one call.
|
|
19
|
+
|
|
20
|
+
## What Makes PDFDancer Different
|
|
21
|
+
|
|
22
|
+
- **Edit any PDF**: Work with documents from customers, governments, or vendors—not just ones you generated.
|
|
23
|
+
- **Pixel-perfect positioning**: Move or add elements at exact coordinates and keep the original layout intact.
|
|
24
|
+
- **Surgical text replacement**: Swap or rewrite paragraphs without reflowing the rest of the page.
|
|
25
|
+
- **Form manipulation**: Inspect, fill, and update AcroForm fields programmatically.
|
|
26
|
+
- **Coordinate-based selection**: Select objects by position, bounding box, or text patterns.
|
|
27
|
+
- **Real PDF editing**: Modify the underlying PDF structure instead of merely stamping overlays.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pdfdancer-client-python
|
|
33
|
+
|
|
34
|
+
# Editable install for local development
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Python 3.10+ and a PDFDancer API token.
|
|
39
|
+
|
|
40
|
+
## Quick Start — Edit an Existing PDF
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from pdfdancer import Color, PDFDancer, StandardFonts
|
|
45
|
+
|
|
46
|
+
with PDFDancer.open(
|
|
47
|
+
pdf_data=Path("input.pdf"),
|
|
48
|
+
token="your-api-token", # optional when PDFDANCER_TOKEN is set
|
|
49
|
+
base_url="https://api.pdfdancer.com",
|
|
50
|
+
) as pdf:
|
|
51
|
+
# Locate and update an existing paragraph
|
|
52
|
+
heading = pdf.page(0).select_paragraphs_starting_with("Executive Summary")[0]
|
|
53
|
+
heading.move_to(72, 680)
|
|
54
|
+
with heading.edit() as editor:
|
|
55
|
+
editor.replace("Overview")
|
|
56
|
+
|
|
57
|
+
# Add a new paragraph with precise placement
|
|
58
|
+
pdf.new_paragraph() \
|
|
59
|
+
.text("Generated with PDFDancer") \
|
|
60
|
+
.font(StandardFonts.HELVETICA, 12) \
|
|
61
|
+
.color(Color(70, 70, 70)) \
|
|
62
|
+
.line_spacing(1.4) \
|
|
63
|
+
.at(page_index=0, x=72, y=520) \
|
|
64
|
+
.add()
|
|
65
|
+
|
|
66
|
+
# Persist the modified document
|
|
67
|
+
pdf.save("output.pdf")
|
|
68
|
+
# or keep it in memory
|
|
69
|
+
pdf_bytes = pdf.get_bytes()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Create a Blank PDF
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from pathlib import Path
|
|
76
|
+
from pdfdancer import Color, PDFDancer, StandardFonts
|
|
77
|
+
|
|
78
|
+
with PDFDancer.new(token="your-api-token") as pdf:
|
|
79
|
+
pdf.new_paragraph() \
|
|
80
|
+
.text("Quarterly Summary") \
|
|
81
|
+
.font(StandardFonts.TIMES_BOLD, 18) \
|
|
82
|
+
.color(Color(10, 10, 80)) \
|
|
83
|
+
.line_spacing(1.2) \
|
|
84
|
+
.at(page_index=0, x=72, y=730) \
|
|
85
|
+
.add()
|
|
86
|
+
|
|
87
|
+
pdf.new_image() \
|
|
88
|
+
.from_file(Path("logo.png")) \
|
|
89
|
+
.at(page=0, x=420, y=710) \
|
|
90
|
+
.add()
|
|
91
|
+
|
|
92
|
+
pdf.save("summary.pdf")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Work with Forms and Layout
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from pdfdancer import PDFDancer
|
|
99
|
+
|
|
100
|
+
with PDFDancer.open("contract.pdf") as pdf:
|
|
101
|
+
# Inspect global document structure
|
|
102
|
+
pages = pdf.pages()
|
|
103
|
+
print("Total pages:", len(pages))
|
|
104
|
+
|
|
105
|
+
# Update form fields
|
|
106
|
+
signature = pdf.select_form_fields_by_name("signature")[0]
|
|
107
|
+
signature.edit().value("Signed by Jane Doe").apply()
|
|
108
|
+
|
|
109
|
+
# Trim or move content at specific coordinates
|
|
110
|
+
images = pdf.page(1).select_images()
|
|
111
|
+
for image in images:
|
|
112
|
+
x = image.position.x()
|
|
113
|
+
if x is not None and x < 100:
|
|
114
|
+
image.delete()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
|
|
118
|
+
with helpers such as `delete()`, `move_to(x, y)`, or `edit()` depending on the object type.
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
- Set `PDFDANCER_TOKEN` for authentication (preferred for local development and CI).
|
|
123
|
+
- Override the API host with `PDFDANCER_BASE_URL` (e.g., sandbox environments).
|
|
124
|
+
- Tune HTTP read timeouts via the `timeout` argument on `PDFDancer.open()` and `PDFDancer.new()`.
|
|
125
|
+
|
|
126
|
+
## Error Handling
|
|
127
|
+
|
|
128
|
+
Operations raise subclasses of `PdfDancerException`:
|
|
129
|
+
|
|
130
|
+
- `ValidationException`: input validation problems (missing token, invalid coordinates, etc.).
|
|
131
|
+
- `FontNotFoundException`: requested font unavailable on the service.
|
|
132
|
+
- `HttpClientException`: transport or server errors with detailed context.
|
|
133
|
+
- `SessionException`: session creation and lifecycle failures.
|
|
134
|
+
|
|
135
|
+
Wrap automated workflows in `try/except` blocks to surface actionable errors to your users.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python -m venv venv
|
|
141
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
142
|
+
pip install -e ".[dev]"
|
|
143
|
+
|
|
144
|
+
pytest -q # unit suite
|
|
145
|
+
pytest tests/e2e # integration tests (requires live API + fixtures)
|
|
146
|
+
python -m build # produce distribution artifacts
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Releases are published with `python release.py`. Contributions are welcome via pull request.
|
|
150
|
+
|
|
151
|
+
## Related SDKs
|
|
152
|
+
|
|
153
|
+
- TypeScript client: https://github.com/MenschMachine/pdfdancer-client-js
|
|
154
|
+
- Java client: https://github.com/MenschMachine/pdfdancer-client-java
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT © The Famous Cat Ltd.
|
|
@@ -12,7 +12,8 @@ from .exceptions import (
|
|
|
12
12
|
)
|
|
13
13
|
from .models import (
|
|
14
14
|
ObjectRef, Position, ObjectType, Font, Color, Image, BoundingRect, Paragraph, FormFieldRef, TextObjectRef,
|
|
15
|
-
PageRef, PositionMode, ShapeType, Point, StandardFonts, PageSize, Orientation
|
|
15
|
+
PageRef, PositionMode, ShapeType, Point, StandardFonts, PageSize, Orientation, TextStatus, FontRecommendation,
|
|
16
|
+
FontType
|
|
16
17
|
)
|
|
17
18
|
from .paragraph_builder import ParagraphBuilder
|
|
18
19
|
|
|
@@ -37,6 +38,9 @@ __all__ = [
|
|
|
37
38
|
"StandardFonts",
|
|
38
39
|
"PageSize",
|
|
39
40
|
"Orientation",
|
|
41
|
+
"TextStatus",
|
|
42
|
+
"FontRecommendation",
|
|
43
|
+
"FontType",
|
|
40
44
|
"PdfDancerException",
|
|
41
45
|
"FontNotFoundException",
|
|
42
46
|
"ValidationException",
|
|
@@ -154,7 +154,6 @@ class StandardFonts(Enum):
|
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
class ObjectType(Enum):
|
|
157
|
-
"""Object type enumeration matching the Java ObjectType."""
|
|
158
157
|
FORM_FIELD = "FORM_FIELD"
|
|
159
158
|
IMAGE = "IMAGE"
|
|
160
159
|
FORM_X_OBJECT = "FORM_X_OBJECT"
|
|
@@ -192,7 +191,6 @@ class Point:
|
|
|
192
191
|
class BoundingRect:
|
|
193
192
|
"""
|
|
194
193
|
Represents a bounding rectangle with position and dimensions.
|
|
195
|
-
Matches the Java BoundingRect class.
|
|
196
194
|
"""
|
|
197
195
|
x: float
|
|
198
196
|
y: float
|
|
@@ -216,7 +214,6 @@ class BoundingRect:
|
|
|
216
214
|
class Position:
|
|
217
215
|
"""
|
|
218
216
|
Represents spatial positioning and location information for PDF objects.
|
|
219
|
-
Closely mirrors the Java Position class with Python conventions.
|
|
220
217
|
"""
|
|
221
218
|
page_index: Optional[int] = None
|
|
222
219
|
shape: Optional[ShapeType] = None
|
|
@@ -230,7 +227,6 @@ class Position:
|
|
|
230
227
|
def at_page(page_index: int) -> 'Position':
|
|
231
228
|
"""
|
|
232
229
|
Creates a position specification for an entire page.
|
|
233
|
-
Equivalent to Position.fromPageIndex() in Java.
|
|
234
230
|
"""
|
|
235
231
|
return Position(page_index=page_index, mode=PositionMode.CONTAINS)
|
|
236
232
|
|
|
@@ -238,7 +234,6 @@ class Position:
|
|
|
238
234
|
def at_page_coordinates(page_index: int, x: float, y: float) -> 'Position':
|
|
239
235
|
"""
|
|
240
236
|
Creates a position specification for specific coordinates on a page.
|
|
241
|
-
Equivalent to Position.onPageCoordinates() in Java.
|
|
242
237
|
"""
|
|
243
238
|
position = Position.at_page(page_index)
|
|
244
239
|
position.at_coordinates(Point(x, y))
|
|
@@ -248,7 +243,6 @@ class Position:
|
|
|
248
243
|
def by_name(name: str) -> 'Position':
|
|
249
244
|
"""
|
|
250
245
|
Creates a position specification for finding objects by name.
|
|
251
|
-
Equivalent to Position.byName() in Java.
|
|
252
246
|
"""
|
|
253
247
|
position = Position()
|
|
254
248
|
position.name = name
|
|
@@ -257,7 +251,6 @@ class Position:
|
|
|
257
251
|
def at_coordinates(self, point: Point) -> 'Position':
|
|
258
252
|
"""
|
|
259
253
|
Sets the position to a specific point location.
|
|
260
|
-
Equivalent to Position.set() in Java.
|
|
261
254
|
"""
|
|
262
255
|
self.mode = PositionMode.CONTAINS
|
|
263
256
|
self.shape = ShapeType.POINT
|
|
@@ -293,7 +286,6 @@ class Position:
|
|
|
293
286
|
class ObjectRef:
|
|
294
287
|
"""
|
|
295
288
|
Lightweight reference to a PDF object providing identity and type information.
|
|
296
|
-
Mirrors the Java ObjectRef class exactly.
|
|
297
289
|
"""
|
|
298
290
|
internal_id: str
|
|
299
291
|
position: Position
|
|
@@ -333,7 +325,6 @@ class Color:
|
|
|
333
325
|
a: int = 255 # Alpha channel, default fully opaque
|
|
334
326
|
|
|
335
327
|
def __post_init__(self):
|
|
336
|
-
# Validation similar to Java client
|
|
337
328
|
for component in [self.r, self.g, self.b, self.a]:
|
|
338
329
|
if not 0 <= component <= 255:
|
|
339
330
|
raise ValueError(f"Color component must be between 0 and 255, got {component}")
|
|
@@ -354,7 +345,6 @@ class Font:
|
|
|
354
345
|
class Image:
|
|
355
346
|
"""
|
|
356
347
|
Represents an image object in a PDF document.
|
|
357
|
-
Matches the Java Image class structure.
|
|
358
348
|
"""
|
|
359
349
|
position: Optional[Position] = None
|
|
360
350
|
format: Optional[str] = None
|
|
@@ -375,7 +365,6 @@ class Image:
|
|
|
375
365
|
class Paragraph:
|
|
376
366
|
"""
|
|
377
367
|
Represents a paragraph of text in a PDF document.
|
|
378
|
-
Structure mirrors the Java Paragraph class.
|
|
379
368
|
"""
|
|
380
369
|
position: Optional[Position] = None
|
|
381
370
|
text_lines: Optional[List[str]] = None
|
|
@@ -456,7 +445,7 @@ class MoveRequest:
|
|
|
456
445
|
|
|
457
446
|
def to_dict(self) -> dict:
|
|
458
447
|
"""Convert to dictionary for JSON serialization."""
|
|
459
|
-
# Server API expects the new coordinates under 'newPosition'
|
|
448
|
+
# Server API expects the new coordinates under 'newPosition'
|
|
460
449
|
return {
|
|
461
450
|
"objectRef": {
|
|
462
451
|
"internalId": self.object_ref.internal_id,
|
|
@@ -488,7 +477,7 @@ class AddRequest:
|
|
|
488
477
|
def to_dict(self) -> dict:
|
|
489
478
|
"""Convert to dictionary for JSON serialization matching server API.
|
|
490
479
|
Server expects an AddRequest with a nested 'object' containing the PDFObject
|
|
491
|
-
(with a 'type' discriminator)
|
|
480
|
+
(with a 'type' discriminator).
|
|
492
481
|
"""
|
|
493
482
|
obj = self.pdf_object
|
|
494
483
|
return {
|
|
@@ -621,6 +610,58 @@ class FormFieldRef(ObjectRef):
|
|
|
621
610
|
return self.value
|
|
622
611
|
|
|
623
612
|
|
|
613
|
+
class FontType(Enum):
|
|
614
|
+
"""Font type classification from the PDF."""
|
|
615
|
+
SYSTEM = "SYSTEM"
|
|
616
|
+
STANDARD = "STANDARD"
|
|
617
|
+
EMBEDDED = "EMBEDDED"
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@dataclass
|
|
621
|
+
class FontRecommendation:
|
|
622
|
+
"""Represents a font recommendation with similarity score."""
|
|
623
|
+
font_name: str
|
|
624
|
+
font_type: 'FontType'
|
|
625
|
+
similarity_score: float
|
|
626
|
+
|
|
627
|
+
def get_font_name(self) -> str:
|
|
628
|
+
"""Get the recommended font name."""
|
|
629
|
+
return self.font_name
|
|
630
|
+
|
|
631
|
+
def get_font_type(self) -> 'FontType':
|
|
632
|
+
"""Get the recommended font type."""
|
|
633
|
+
return self.font_type
|
|
634
|
+
|
|
635
|
+
def get_similarity_score(self) -> float:
|
|
636
|
+
"""Get the similarity score."""
|
|
637
|
+
return self.similarity_score
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@dataclass
|
|
641
|
+
class TextStatus:
|
|
642
|
+
"""Status information for text objects."""
|
|
643
|
+
modified: bool
|
|
644
|
+
encodable: bool
|
|
645
|
+
font_type: FontType
|
|
646
|
+
font_recommendation: FontRecommendation
|
|
647
|
+
|
|
648
|
+
def is_modified(self) -> bool:
|
|
649
|
+
"""Check if the text has been modified."""
|
|
650
|
+
return self.modified
|
|
651
|
+
|
|
652
|
+
def is_encodable(self) -> bool:
|
|
653
|
+
"""Check if the text is encodable."""
|
|
654
|
+
return self.encodable
|
|
655
|
+
|
|
656
|
+
def get_font_type(self) -> FontType:
|
|
657
|
+
"""Get the font type."""
|
|
658
|
+
return self.font_type
|
|
659
|
+
|
|
660
|
+
def get_font_recommendation(self) -> FontRecommendation:
|
|
661
|
+
"""Get the font recommendation."""
|
|
662
|
+
return self.font_recommendation
|
|
663
|
+
|
|
664
|
+
|
|
624
665
|
class TextObjectRef(ObjectRef):
|
|
625
666
|
"""
|
|
626
667
|
Represents a text object reference with additional text-specific properties.
|
|
@@ -630,13 +671,14 @@ class TextObjectRef(ObjectRef):
|
|
|
630
671
|
def __init__(self, internal_id: str, position: Position, object_type: ObjectType,
|
|
631
672
|
text: Optional[str] = None, font_name: Optional[str] = None,
|
|
632
673
|
font_size: Optional[float] = None, line_spacings: Optional[List[float]] = None,
|
|
633
|
-
color: Optional[Color] = None):
|
|
674
|
+
color: Optional[Color] = None, status: Optional[TextStatus] = None):
|
|
634
675
|
super().__init__(internal_id, position, object_type)
|
|
635
676
|
self.text = text
|
|
636
677
|
self.font_name = font_name
|
|
637
678
|
self.font_size = font_size
|
|
638
679
|
self.line_spacings = line_spacings
|
|
639
680
|
self.color = color
|
|
681
|
+
self.status = status
|
|
640
682
|
self.children: List['TextObjectRef'] = []
|
|
641
683
|
|
|
642
684
|
def get_text(self) -> Optional[str]:
|
|
@@ -663,6 +705,10 @@ class TextObjectRef(ObjectRef):
|
|
|
663
705
|
"""Get the child text objects."""
|
|
664
706
|
return self.children
|
|
665
707
|
|
|
708
|
+
def get_status(self) -> Optional[TextStatus]:
|
|
709
|
+
"""Get the status information."""
|
|
710
|
+
return self.status
|
|
711
|
+
|
|
666
712
|
|
|
667
713
|
@dataclass
|
|
668
714
|
class PageRef(ObjectRef):
|
|
@@ -680,3 +726,30 @@ class PageRef(ObjectRef):
|
|
|
680
726
|
def get_orientation(self) -> Optional[Orientation]:
|
|
681
727
|
"""Get the page orientation."""
|
|
682
728
|
return self.orientation
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
@dataclass
|
|
732
|
+
class CommandResult:
|
|
733
|
+
"""
|
|
734
|
+
Result object returned by certain API endpoints indicating the outcome of an operation.
|
|
735
|
+
"""
|
|
736
|
+
command_name: str
|
|
737
|
+
element_id: str | None
|
|
738
|
+
message: str | None
|
|
739
|
+
success: bool
|
|
740
|
+
warning: str | None
|
|
741
|
+
|
|
742
|
+
@classmethod
|
|
743
|
+
def from_dict(cls, data: dict) -> 'CommandResult':
|
|
744
|
+
"""Create a CommandResult from a dictionary response."""
|
|
745
|
+
return cls(
|
|
746
|
+
command_name=data.get('commandName', ''),
|
|
747
|
+
element_id=data.get('elementId', ''),
|
|
748
|
+
message=data.get('message', ''),
|
|
749
|
+
success=data.get('success', False),
|
|
750
|
+
warning=data.get('warning', '')
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
@classmethod
|
|
754
|
+
def empty(cls, command_name: str, element_id: str | None) -> 'CommandResult':
|
|
755
|
+
return CommandResult(command_name=command_name, element_id=element_id, message=None, success=True, warning=None)
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
@@ -27,7 +27,7 @@ from .image_builder import ImageBuilder
|
|
|
27
27
|
from .models import (
|
|
28
28
|
ObjectRef, Position, ObjectType, Font, Image, Paragraph, FormFieldRef, TextObjectRef, PageRef,
|
|
29
29
|
FindRequest, DeleteRequest, MoveRequest, PageMoveRequest, AddRequest, ModifyRequest, ModifyTextRequest,
|
|
30
|
-
ChangeFormFieldRequest,
|
|
30
|
+
ChangeFormFieldRequest, CommandResult,
|
|
31
31
|
ShapeType, PositionMode, PageSize, Orientation
|
|
32
32
|
)
|
|
33
33
|
from .paragraph_builder import ParagraphPageBuilder
|
|
@@ -915,7 +915,7 @@ class PDFDancer:
|
|
|
915
915
|
return ImageBuilder(self)
|
|
916
916
|
|
|
917
917
|
# Modify Operations
|
|
918
|
-
def _modify_paragraph(self, object_ref: ObjectRef, new_paragraph: Union[Paragraph, str]) ->
|
|
918
|
+
def _modify_paragraph(self, object_ref: ObjectRef, new_paragraph: Union[Paragraph, str]) -> CommandResult:
|
|
919
919
|
"""
|
|
920
920
|
Modifies a paragraph object or its text content.
|
|
921
921
|
|
|
@@ -929,20 +929,20 @@ class PDFDancer:
|
|
|
929
929
|
if object_ref is None:
|
|
930
930
|
raise ValidationException("Object reference cannot be null")
|
|
931
931
|
if new_paragraph is None:
|
|
932
|
-
|
|
932
|
+
return CommandResult.empty("ModifyParagraph", object_ref.internal_id)
|
|
933
933
|
|
|
934
934
|
if isinstance(new_paragraph, str):
|
|
935
|
-
# Text modification
|
|
935
|
+
# Text modification - returns CommandResult
|
|
936
936
|
request_data = ModifyTextRequest(object_ref, new_paragraph).to_dict()
|
|
937
937
|
response = self._make_request('PUT', '/pdf/text/paragraph', data=request_data)
|
|
938
|
+
return CommandResult.from_dict(response.json())
|
|
938
939
|
else:
|
|
939
940
|
# Object modification
|
|
940
941
|
request_data = ModifyRequest(object_ref, new_paragraph).to_dict()
|
|
941
942
|
response = self._make_request('PUT', '/pdf/modify', data=request_data)
|
|
943
|
+
return CommandResult.from_dict(response.json())
|
|
942
944
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
def _modify_text_line(self, object_ref: ObjectRef, new_text: str) -> bool:
|
|
945
|
+
def _modify_text_line(self, object_ref: ObjectRef, new_text: str) -> CommandResult:
|
|
946
946
|
"""
|
|
947
947
|
Modifies a text line object.
|
|
948
948
|
|
|
@@ -960,7 +960,7 @@ class PDFDancer:
|
|
|
960
960
|
|
|
961
961
|
request_data = ModifyTextRequest(object_ref, new_text).to_dict()
|
|
962
962
|
response = self._make_request('PUT', '/pdf/text/line', data=request_data)
|
|
963
|
-
return response.json()
|
|
963
|
+
return CommandResult.from_dict(response.json())
|
|
964
964
|
|
|
965
965
|
# Font Operations
|
|
966
966
|
|
|
@@ -1171,6 +1171,29 @@ class PDFDancer:
|
|
|
1171
1171
|
if all(isinstance(v, int) for v in [red, green, blue]):
|
|
1172
1172
|
color = Color(red, green, blue, alpha)
|
|
1173
1173
|
|
|
1174
|
+
# Parse status if present
|
|
1175
|
+
status = None
|
|
1176
|
+
status_data = obj_data.get('status')
|
|
1177
|
+
if isinstance(status_data, dict):
|
|
1178
|
+
from .models import TextStatus, FontRecommendation, FontType
|
|
1179
|
+
|
|
1180
|
+
# Parse font recommendation
|
|
1181
|
+
font_rec_data = status_data.get('fontRecommendation')
|
|
1182
|
+
font_rec = None
|
|
1183
|
+
if isinstance(font_rec_data, dict):
|
|
1184
|
+
font_rec = FontRecommendation(
|
|
1185
|
+
font_name=font_rec_data.get('fontName', ''),
|
|
1186
|
+
font_type=FontType(font_rec_data.get('fontType', 'SYSTEM')),
|
|
1187
|
+
similarity_score=font_rec_data.get('similarityScore', 0.0)
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
status = TextStatus(
|
|
1191
|
+
modified=status_data.get('modified', False),
|
|
1192
|
+
encodable=status_data.get('encodable', True),
|
|
1193
|
+
font_type=FontType(status_data.get('fontType', 'UNKNOWN')),
|
|
1194
|
+
font_recommendation=font_rec
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1174
1197
|
text_object = TextObjectRef(
|
|
1175
1198
|
internal_id=internal_id,
|
|
1176
1199
|
position=position,
|
|
@@ -1179,7 +1202,8 @@ class PDFDancer:
|
|
|
1179
1202
|
font_name=obj_data.get('fontName') if isinstance(obj_data.get('fontName'), str) else None,
|
|
1180
1203
|
font_size=obj_data.get('fontSize') if isinstance(obj_data.get('fontSize'), (int, float)) else None,
|
|
1181
1204
|
line_spacings=line_spacings,
|
|
1182
|
-
color=color
|
|
1205
|
+
color=color,
|
|
1206
|
+
status=status
|
|
1183
1207
|
)
|
|
1184
1208
|
|
|
1185
1209
|
if isinstance(obj_data.get('children'), list) and len(obj_data['children']) > 0:
|