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.

Files changed (51) hide show
  1. pdfdancer_client_python-0.2.16/PKG-INFO +190 -0
  2. pdfdancer_client_python-0.2.16/README.md +158 -0
  3. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/pyproject.toml +1 -1
  4. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/__init__.py +5 -1
  5. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/models.py +87 -14
  6. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/pdfdancer_v1.py +33 -9
  7. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/types.py +18 -5
  8. pdfdancer_client_python-0.2.16/src/pdfdancer_client_python.egg-info/PKG-INFO +190 -0
  9. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/pdf_assertions.py +0 -2
  10. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_form_x_objects.py +0 -2
  11. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_line.py +56 -6
  12. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_new_pdf.py +25 -3
  13. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_paragraph.py +98 -12
  14. pdfdancer_client_python-0.2.14/PKG-INFO +0 -200
  15. pdfdancer_client_python-0.2.14/README.md +0 -168
  16. pdfdancer_client_python-0.2.14/src/pdfdancer_client_python.egg-info/PKG-INFO +0 -200
  17. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/.claude/commands/discuss.md +0 -0
  18. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/.github/workflows/ci.yml +0 -0
  19. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/.gitignore +0 -0
  20. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/CLAUDE.md +0 -0
  21. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/docs/openapi.yml +0 -0
  22. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/release.py +0 -0
  23. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/setup.cfg +0 -0
  24. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/exceptions.py +0 -0
  25. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/image_builder.py +0 -0
  26. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer/paragraph_builder.py +0 -0
  27. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
  28. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  29. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  30. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  31. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/__init__.py +0 -0
  32. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/conftest.py +0 -0
  33. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/__init__.py +0 -0
  34. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_acroform.py +0 -0
  35. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_image.py +0 -0
  36. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_page.py +0 -0
  37. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_path.py +0 -0
  38. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_pdfdancer.py +0 -0
  39. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/e2e/test_positioning.py +0 -0
  40. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  41. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/Empty.pdf +0 -0
  42. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  43. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/ObviouslyAwesome.pdf +0 -0
  44. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/basic-paths.pdf +0 -0
  45. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/form-xobject-example.pdf +0 -0
  46. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/logo-80.png +0 -0
  47. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/fixtures/mixed-form-types.pdf +0 -0
  48. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_models.py +0 -0
  49. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_openapi_compliance.py +0 -0
  50. {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.16}/tests/test_pdf_object_equality.py +0 -0
  51. {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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdfdancer-client-python"
7
- version = "0.2.14"
7
+ version = "0.2.16"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -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' (see Java MoveRequest)
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), mirroring Java AddRequest(PDFObject object).
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)
@@ -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]) -> bool:
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
- raise ValidationException("New paragraph cannot be null")
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
- return response.json()
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: