pdfdancer-client-python 0.1.1__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 +40 -0
- pdfdancer/client_v1.py +675 -0
- pdfdancer/exceptions.py +57 -0
- pdfdancer/models.py +417 -0
- pdfdancer/paragraph_builder.py +267 -0
- pdfdancer_client_python-0.1.1.dist-info/METADATA +308 -0
- pdfdancer_client_python-0.1.1.dist-info/RECORD +9 -0
- pdfdancer_client_python-0.1.1.dist-info/WHEEL +5 -0
- pdfdancer_client_python-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ParagraphBuilder for the PDFDancer Python client.
|
|
3
|
+
Closely mirrors the Java ParagraphBuilder class with Python conventions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Union, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from .exceptions import ValidationException
|
|
10
|
+
from .models import Paragraph, Font, Color, Position
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .client_v1 import ClientV1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ParagraphBuilder:
|
|
17
|
+
"""
|
|
18
|
+
Builder class for constructing Paragraph objects with fluent interface.
|
|
19
|
+
Mirrors the Java ParagraphBuilder class exactly.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, client: 'ClientV1'):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the paragraph builder with a client reference.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
client: The ClientV1 instance for font registration
|
|
28
|
+
"""
|
|
29
|
+
if client is None:
|
|
30
|
+
raise ValidationException("Client cannot be null")
|
|
31
|
+
|
|
32
|
+
self._client = client
|
|
33
|
+
self._paragraph = Paragraph()
|
|
34
|
+
self._line_spacing = 1.2
|
|
35
|
+
self._text_color = Color(0, 0, 0) # Black by default
|
|
36
|
+
self._text: Optional[str] = None
|
|
37
|
+
self._ttf_file: Optional[Path] = None
|
|
38
|
+
self._font: Optional[Font] = None
|
|
39
|
+
|
|
40
|
+
def from_string(self, text: str, color: Optional[Color] = None) -> 'ParagraphBuilder':
|
|
41
|
+
"""
|
|
42
|
+
Set the text content for the paragraph.
|
|
43
|
+
Equivalent to fromString() methods in Java ParagraphBuilder.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
text: The text content for the paragraph
|
|
47
|
+
color: Optional text color (uses default if not provided)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Self for method chaining
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValidationException: If text is None or empty
|
|
54
|
+
"""
|
|
55
|
+
if text is None:
|
|
56
|
+
raise ValidationException("Text cannot be null")
|
|
57
|
+
if not text.strip():
|
|
58
|
+
raise ValidationException("Text cannot be empty")
|
|
59
|
+
|
|
60
|
+
self._text = text
|
|
61
|
+
if color is not None:
|
|
62
|
+
self._text_color = color
|
|
63
|
+
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def with_font(self, font: Font) -> 'ParagraphBuilder':
|
|
67
|
+
"""
|
|
68
|
+
Set the font for the paragraph using an existing Font object.
|
|
69
|
+
Equivalent to withFont(Font) in Java ParagraphBuilder.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
font: The Font object to use
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Self for method chaining
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValidationException: If font is None
|
|
79
|
+
"""
|
|
80
|
+
if font is None:
|
|
81
|
+
raise ValidationException("Font cannot be null")
|
|
82
|
+
|
|
83
|
+
self._font = font
|
|
84
|
+
self._ttf_file = None # Clear TTF file when using existing font
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def with_font_file(self, ttf_file: Union[Path, str], font_size: float) -> 'ParagraphBuilder':
|
|
88
|
+
"""
|
|
89
|
+
Set the font for the paragraph using a TTF file.
|
|
90
|
+
Equivalent to withFont(File, double) in Java ParagraphBuilder.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
ttf_file: Path to the TTF font file
|
|
94
|
+
font_size: Size of the font
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Self for method chaining
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValidationException: If TTF file is invalid or font size is not positive
|
|
101
|
+
"""
|
|
102
|
+
if ttf_file is None:
|
|
103
|
+
raise ValidationException("TTF file cannot be null")
|
|
104
|
+
if font_size <= 0:
|
|
105
|
+
raise ValidationException(f"Font size must be positive, got {font_size}")
|
|
106
|
+
|
|
107
|
+
ttf_path = Path(ttf_file)
|
|
108
|
+
|
|
109
|
+
# Strict validation like Java client
|
|
110
|
+
if not ttf_path.exists():
|
|
111
|
+
raise ValidationException(f"TTF file does not exist: {ttf_path}")
|
|
112
|
+
if not ttf_path.is_file():
|
|
113
|
+
raise ValidationException(f"TTF file is not a file: {ttf_path}")
|
|
114
|
+
if not ttf_path.stat().st_size > 0:
|
|
115
|
+
raise ValidationException(f"TTF file is empty: {ttf_path}")
|
|
116
|
+
|
|
117
|
+
# Check file permissions
|
|
118
|
+
try:
|
|
119
|
+
with open(ttf_path, 'rb') as f:
|
|
120
|
+
f.read(1) # Try to read one byte to check readability
|
|
121
|
+
except (IOError, OSError):
|
|
122
|
+
raise ValidationException(f"TTF file is not readable: {ttf_path}")
|
|
123
|
+
|
|
124
|
+
self._ttf_file = ttf_path
|
|
125
|
+
self._font = self._register_ttf(ttf_path, font_size)
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def with_line_spacing(self, spacing: float) -> 'ParagraphBuilder':
|
|
129
|
+
"""
|
|
130
|
+
Set the line spacing for the paragraph.
|
|
131
|
+
Equivalent to withLineSpacing() in Java ParagraphBuilder.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
spacing: Line spacing value (typically 1.0 to 2.0)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Self for method chaining
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ValidationException: If spacing is not positive
|
|
141
|
+
"""
|
|
142
|
+
if spacing <= 0:
|
|
143
|
+
raise ValidationException(f"Line spacing must be positive, got {spacing}")
|
|
144
|
+
|
|
145
|
+
self._line_spacing = spacing
|
|
146
|
+
return self
|
|
147
|
+
|
|
148
|
+
def with_color(self, color: Color) -> 'ParagraphBuilder':
|
|
149
|
+
"""
|
|
150
|
+
Set the text color for the paragraph.
|
|
151
|
+
Equivalent to withColor() in Java ParagraphBuilder.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
color: The Color object for the text
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Self for method chaining
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ValidationException: If color is None
|
|
161
|
+
"""
|
|
162
|
+
if color is None:
|
|
163
|
+
raise ValidationException("Color cannot be null")
|
|
164
|
+
|
|
165
|
+
self._text_color = color
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def with_position(self, position: Position) -> 'ParagraphBuilder':
|
|
169
|
+
"""
|
|
170
|
+
Set the position for the paragraph.
|
|
171
|
+
Equivalent to withPosition() in Java ParagraphBuilder.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
position: The Position object for the paragraph
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Self for method chaining
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValidationException: If position is None
|
|
181
|
+
"""
|
|
182
|
+
if position is None:
|
|
183
|
+
raise ValidationException("Position cannot be null")
|
|
184
|
+
|
|
185
|
+
self._paragraph.set_position(position)
|
|
186
|
+
return self
|
|
187
|
+
|
|
188
|
+
def build(self) -> Paragraph:
|
|
189
|
+
"""
|
|
190
|
+
Build and return the final Paragraph object.
|
|
191
|
+
Equivalent to build() in Java ParagraphBuilder.
|
|
192
|
+
|
|
193
|
+
This method validates all required fields and constructs the final paragraph
|
|
194
|
+
with text processing similar to ParagraphUtil.finalizeText() in Java.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
The constructed Paragraph object
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
ValidationException: If required fields are missing or invalid
|
|
201
|
+
"""
|
|
202
|
+
# Validate required fields
|
|
203
|
+
if self._text is None:
|
|
204
|
+
raise ValidationException("Text must be set before building paragraph")
|
|
205
|
+
if self._font is None:
|
|
206
|
+
raise ValidationException("Font must be set before building paragraph")
|
|
207
|
+
if self._paragraph.get_position() is None:
|
|
208
|
+
raise ValidationException("Position must be set before building paragraph")
|
|
209
|
+
|
|
210
|
+
# Set paragraph properties
|
|
211
|
+
self._paragraph.font = self._font
|
|
212
|
+
self._paragraph.color = self._text_color
|
|
213
|
+
self._paragraph.line_spacing = self._line_spacing
|
|
214
|
+
|
|
215
|
+
# Process text into lines (simplified version of ParagraphUtil.finalizeText)
|
|
216
|
+
# In the full implementation, this would handle text wrapping, line breaks, etc.
|
|
217
|
+
self._paragraph.text_lines = self._process_text_lines(self._text)
|
|
218
|
+
|
|
219
|
+
return self._paragraph
|
|
220
|
+
|
|
221
|
+
def _register_ttf(self, ttf_file: Path, font_size: float) -> Font:
|
|
222
|
+
"""
|
|
223
|
+
Register a TTF font with the client and return a Font object.
|
|
224
|
+
Equivalent to registerTTF() private method in Java ParagraphBuilder.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
ttf_file: Path to the TTF font file
|
|
228
|
+
font_size: Size of the font
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Font object with the registered font name and size
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
font_name = self._client.register_font(ttf_file)
|
|
235
|
+
return Font(font_name, font_size)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
raise ValidationException(f"Failed to register font file {ttf_file}: {e}")
|
|
238
|
+
|
|
239
|
+
def _process_text_lines(self, text: str) -> list[str]:
|
|
240
|
+
"""
|
|
241
|
+
Process text into lines for the paragraph.
|
|
242
|
+
This is a simplified version - the full implementation would handle
|
|
243
|
+
word wrapping, line breaks, and other text formatting based on the font
|
|
244
|
+
and paragraph width.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
text: The input text to process
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of text lines for the paragraph
|
|
251
|
+
"""
|
|
252
|
+
# Handle escaped newlines (\\n) as actual newlines
|
|
253
|
+
processed_text = text.replace('\\n', '\n')
|
|
254
|
+
|
|
255
|
+
# Simple implementation - split on newlines
|
|
256
|
+
# In the full version, this would implement proper text layout
|
|
257
|
+
lines = processed_text.split('\n')
|
|
258
|
+
|
|
259
|
+
# Remove empty lines at the end but preserve intentional line breaks
|
|
260
|
+
while lines and not lines[-1].strip():
|
|
261
|
+
lines.pop()
|
|
262
|
+
|
|
263
|
+
# Ensure at least one line
|
|
264
|
+
if not lines:
|
|
265
|
+
lines = ['']
|
|
266
|
+
|
|
267
|
+
return lines
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pdfdancer-client-python
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python client for PDFDancer API
|
|
5
|
+
Author-email: TFC <info@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tfc/pdfdancer-python
|
|
8
|
+
Project-URL: Repository, https://github.com/tfc/pdfdancer-python.git
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
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
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
24
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
25
|
+
Requires-Dist: flake8>=5.0; extra == "dev"
|
|
26
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# PDFDancer Python Client
|
|
29
|
+
|
|
30
|
+
A Python client library for the PDFDancer PDF manipulation API that closely mirrors the Java client structure and functionality.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **100% Manual Implementation** - Pure Python, no code generation
|
|
35
|
+
- **Java Client Compatibility** - Same methods, validation, and patterns
|
|
36
|
+
- **Session-based Operations** - Automatic session management
|
|
37
|
+
- **Builder Pattern** - Fluent ParagraphBuilder interface
|
|
38
|
+
- **Strict Validation** - Matches Java client validation exactly
|
|
39
|
+
- **Python Enhancements** - Type hints, context managers, Pathlib support
|
|
40
|
+
- **Comprehensive Testing** - 77 tests covering all functionality
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install pdfdancer-client-python
|
|
46
|
+
# Or for development:
|
|
47
|
+
pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from pdfdancer import ClientV1, Position, Font, Color
|
|
54
|
+
|
|
55
|
+
# Create client (mirrors Java: new Client(token, pdfFile))
|
|
56
|
+
client = ClientV1(token="your-jwt-token", pdf_data="document.pdf")
|
|
57
|
+
|
|
58
|
+
# Find operations (mirrors Java client methods)
|
|
59
|
+
paragraphs = client.find_paragraphs(None)
|
|
60
|
+
images = client.find_images(Position.from_page_index(0))
|
|
61
|
+
|
|
62
|
+
# Manipulation operations (mirrors Java client methods)
|
|
63
|
+
client.delete(paragraphs[0])
|
|
64
|
+
client.move(images[0], Position.on_page_coordinates(0, 100, 200))
|
|
65
|
+
|
|
66
|
+
# Builder pattern (mirrors Java ParagraphBuilder)
|
|
67
|
+
paragraph = (client.paragraph_builder()
|
|
68
|
+
.from_string("Hello World")
|
|
69
|
+
.with_font(Font("Arial", 12))
|
|
70
|
+
.with_color(Color(255, 0, 0))
|
|
71
|
+
.with_position(Position.from_page_index(0))
|
|
72
|
+
.build())
|
|
73
|
+
|
|
74
|
+
client.add_paragraph(paragraph)
|
|
75
|
+
|
|
76
|
+
# Save result (mirrors Java savePDF)
|
|
77
|
+
client.save_pdf("output.pdf")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Context Manager (Python Enhancement)
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from pdfdancer import ClientV1
|
|
84
|
+
|
|
85
|
+
# Automatic resource management
|
|
86
|
+
with ClientV1(token="jwt-token", pdf_data="input.pdf") as client:
|
|
87
|
+
paragraphs = client.find_paragraphs(None)
|
|
88
|
+
client.delete(paragraphs[0])
|
|
89
|
+
client.save_pdf("output.pdf")
|
|
90
|
+
# Session automatically cleaned up
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Methods
|
|
94
|
+
|
|
95
|
+
### Constructor Patterns
|
|
96
|
+
```python
|
|
97
|
+
# File path (Java: new Client(token, new File("pdf")))
|
|
98
|
+
client = ClientV1(token="jwt-token", pdf_data="document.pdf")
|
|
99
|
+
|
|
100
|
+
# Bytes (Java: new Client(token, pdfBytes, httpClient))
|
|
101
|
+
client = ClientV1(token="jwt-token", pdf_data=pdf_bytes)
|
|
102
|
+
|
|
103
|
+
# Custom server
|
|
104
|
+
client = ClientV1(token="jwt-token", pdf_data=pdf_file, base_url="https://api.server")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Find Operations
|
|
108
|
+
```python
|
|
109
|
+
# Generic find (Java: client.find())
|
|
110
|
+
objects = client.find(ObjectType.PARAGRAPH, position)
|
|
111
|
+
|
|
112
|
+
# Specific finders (Java: client.findParagraphs(), etc.)
|
|
113
|
+
paragraphs = client.find_paragraphs(position)
|
|
114
|
+
images = client.find_images(position)
|
|
115
|
+
forms = client.find_forms(position)
|
|
116
|
+
paths = client.find_paths(position)
|
|
117
|
+
text_lines = client.find_text_lines(position)
|
|
118
|
+
|
|
119
|
+
# Page operations (Java: client.getPages(), client.getPage())
|
|
120
|
+
pages = client.get_pages()
|
|
121
|
+
page = client.get_page(1) # 1-based indexing
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Manipulation Operations
|
|
125
|
+
```python
|
|
126
|
+
# Delete (Java: client.delete(), client.deletePage())
|
|
127
|
+
result = client.delete(object_ref)
|
|
128
|
+
result = client.delete_page(page_ref)
|
|
129
|
+
|
|
130
|
+
# Move (Java: client.move())
|
|
131
|
+
result = client.move(object_ref, new_position)
|
|
132
|
+
|
|
133
|
+
# Add (Java: client.addImage(), client.addParagraph())
|
|
134
|
+
result = client.add_image(image, position)
|
|
135
|
+
result = client.add_paragraph(paragraph)
|
|
136
|
+
|
|
137
|
+
# Modify (Java: client.modifyParagraph(), client.modifyTextLine())
|
|
138
|
+
result = client.modify_paragraph(ref, new_paragraph)
|
|
139
|
+
result = client.modify_text_line(ref, "new text")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Builder Pattern
|
|
143
|
+
```python
|
|
144
|
+
# Java: client.paragraphBuilder()
|
|
145
|
+
builder = client.paragraph_builder()
|
|
146
|
+
|
|
147
|
+
# Fluent interface (mirrors Java ParagraphBuilder)
|
|
148
|
+
paragraph = (builder
|
|
149
|
+
.from_string("Text content") # Java: fromString()
|
|
150
|
+
.with_font(Font("Arial", 12)) # Java: withFont()
|
|
151
|
+
.with_color(Color(255, 0, 0)) # Java: withColor()
|
|
152
|
+
.with_line_spacing(1.5) # Java: withLineSpacing()
|
|
153
|
+
.with_position(position) # Java: withPosition()
|
|
154
|
+
.build()) # Java: build()
|
|
155
|
+
|
|
156
|
+
# Font file registration (Java: withFont(File, double))
|
|
157
|
+
paragraph = (builder
|
|
158
|
+
.with_font_file("custom.ttf", 14.0) # Java: withFont(File, double)
|
|
159
|
+
.from_string("Custom font text")
|
|
160
|
+
.with_position(position)
|
|
161
|
+
.build())
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Position API
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from pdfdancer import Position
|
|
168
|
+
|
|
169
|
+
# Factory methods (Java: Position.fromPageNumber(), Position.onPageCoordinates())
|
|
170
|
+
position = Position.from_page_index(0)
|
|
171
|
+
position = Position.on_page_coordinates(0, 100, 200)
|
|
172
|
+
|
|
173
|
+
# Coordinate access (Java: position.getX(), position.getY())
|
|
174
|
+
x = position.get_x()
|
|
175
|
+
y = position.get_y()
|
|
176
|
+
|
|
177
|
+
# Movement (Java: position.moveX(), position.moveY())
|
|
178
|
+
position.move_x(50.0)
|
|
179
|
+
position.move_y(-25.0)
|
|
180
|
+
|
|
181
|
+
# Copy (Java: position.copy())
|
|
182
|
+
position_copy = position.copy()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Font Operations
|
|
186
|
+
```python
|
|
187
|
+
# Find fonts (Java: client.findFonts())
|
|
188
|
+
fonts = client.find_fonts("Arial", 12)
|
|
189
|
+
|
|
190
|
+
# Register custom font (Java: client.registerFont())
|
|
191
|
+
font_name = client.register_font("custom.ttf")
|
|
192
|
+
font_name = client.register_font(Path("font.ttf"))
|
|
193
|
+
font_name = client.register_font(font_bytes)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Document Operations
|
|
197
|
+
```python
|
|
198
|
+
# Get PDF content (Java: client.getPDFFile())
|
|
199
|
+
pdf_bytes = client.get_pdf_file()
|
|
200
|
+
|
|
201
|
+
# Save PDF (Java: client.savePDF())
|
|
202
|
+
client.save_pdf("output.pdf")
|
|
203
|
+
client.save_pdf(Path("output.pdf"))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Exception Handling
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from pdfdancer import (
|
|
210
|
+
PdfDancerException, ValidationException,
|
|
211
|
+
FontNotFoundException, HttpClientException
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
client = ClientV1(token="", pdf_data=b"pdf")
|
|
216
|
+
except ValidationException as e: # Java: IllegalArgumentException
|
|
217
|
+
print(f"Validation error: {e}")
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
fonts = client.find_fonts("NonExistentFont", 12)
|
|
221
|
+
except FontNotFoundException as e: # Java: FontNotFoundException
|
|
222
|
+
print(f"Font not found: {e}")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Data Models
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from pdfdancer import ObjectRef, Position, Font, Color, ObjectType
|
|
229
|
+
|
|
230
|
+
# Object reference (Java: ObjectRef)
|
|
231
|
+
obj_ref = ObjectRef(internal_id="obj-123", position=position, type=ObjectType.PARAGRAPH)
|
|
232
|
+
|
|
233
|
+
# Font (Java: Font)
|
|
234
|
+
font = Font(name="Arial", size=12.0)
|
|
235
|
+
|
|
236
|
+
# Color (Java: Color) - RGB values 0-255
|
|
237
|
+
color = Color(r=255, g=128, b=0)
|
|
238
|
+
|
|
239
|
+
# Position with bounding rectangle (Java: Position, BoundingRect)
|
|
240
|
+
position = Position.on_page_coordinates(page=0, x=100.0, y=200.0)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Development
|
|
244
|
+
|
|
245
|
+
### Setup
|
|
246
|
+
```bash
|
|
247
|
+
python -m venv venv
|
|
248
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
249
|
+
pip install -e .
|
|
250
|
+
pip install -r requirements-dev.txt
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Testing
|
|
254
|
+
```bash
|
|
255
|
+
# Run all tests (77 tests)
|
|
256
|
+
python -m pytest tests/ -v
|
|
257
|
+
|
|
258
|
+
# Run specific test files
|
|
259
|
+
python -m pytest tests/test_client_v1.py -v
|
|
260
|
+
python -m pytest tests/test_paragraph_builder.py -v
|
|
261
|
+
python -m pytest tests/test_models.py -v
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Run Demo
|
|
265
|
+
```bash
|
|
266
|
+
python demo.py
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Build Package
|
|
270
|
+
```bash
|
|
271
|
+
python -m build
|
|
272
|
+
python -m twine check dist/*
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Java Client Mapping
|
|
276
|
+
|
|
277
|
+
| Java Method | Python Method | Description |
|
|
278
|
+
|-------------|---------------|-------------|
|
|
279
|
+
| `new Client(token, file)` | `ClientV1(token="", pdf_data="")` | Constructor |
|
|
280
|
+
| `findParagraphs(position)` | `find_paragraphs(position)` | Find paragraphs |
|
|
281
|
+
| `findImages(position)` | `find_images(position)` | Find images |
|
|
282
|
+
| `delete(objectRef)` | `delete(object_ref)` | Delete object |
|
|
283
|
+
| `move(objectRef, position)` | `move(object_ref, position)` | Move object |
|
|
284
|
+
| `addParagraph(paragraph)` | `add_paragraph(paragraph)` | Add paragraph |
|
|
285
|
+
| `getPDFFile()` | `get_pdf_file()` | Get PDF bytes |
|
|
286
|
+
| `savePDF(path)` | `save_pdf(path)` | Save to file |
|
|
287
|
+
| `paragraphBuilder()` | `paragraph_builder()` | Create builder |
|
|
288
|
+
| `findFonts(name, size)` | `find_fonts(name, size)` | Find fonts |
|
|
289
|
+
| `registerFont(ttfFile)` | `register_font(ttf_file)` | Register font |
|
|
290
|
+
|
|
291
|
+
## Architecture
|
|
292
|
+
|
|
293
|
+
- **Pure Manual Implementation** - No code generation, uses `requests` for HTTP
|
|
294
|
+
- **Session-based** - Constructor creates session, all operations use session ID
|
|
295
|
+
- **Strict Validation** - Matches Java client validation exactly
|
|
296
|
+
- **Type Safety** - Full type hints throughout
|
|
297
|
+
- **Error Handling** - Complete exception hierarchy
|
|
298
|
+
- **Python Conventions** - snake_case methods, context managers, Pathlib support
|
|
299
|
+
|
|
300
|
+
## Requirements
|
|
301
|
+
|
|
302
|
+
- Python 3.8+
|
|
303
|
+
- `requests` library for HTTP communication
|
|
304
|
+
- `pathlib` for file handling (built-in)
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
[Add your license information here]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pdfdancer/__init__.py,sha256=uSIbj9rI81_o_asKd6Er15rI_Fa-TcMl1N7BEq7P_Gc,964
|
|
2
|
+
pdfdancer/client_v1.py,sha256=ZjJkNprzenwUmRJDRwbnXrl93lZBnLok26ytwAxFDWQ,25103
|
|
3
|
+
pdfdancer/exceptions.py,sha256=Y5zwNVZprsv2hvKX304cXWobJt11nrEhCzLklu2wiO8,1567
|
|
4
|
+
pdfdancer/models.py,sha256=-uuN3CY9HWvWom3sQC7vDD_KGrntB59W92vnacn3duc,13906
|
|
5
|
+
pdfdancer/paragraph_builder.py,sha256=uBMSNhL3b5DgbCJWf5VFWxgm3RpsQyQukk67FDd86Bs,8727
|
|
6
|
+
pdfdancer_client_python-0.1.1.dist-info/METADATA,sha256=Qm5zqznfrZ8tcctp--w7cXrpGWElmD9H-8K5Oa3kK_s,9313
|
|
7
|
+
pdfdancer_client_python-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
pdfdancer_client_python-0.1.1.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
|
|
9
|
+
pdfdancer_client_python-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pdfdancer
|