pdfdancer-client-python 0.2.27__tar.gz → 0.2.29__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/PKG-INFO +1 -1
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/pyproject.toml +1 -1
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/types.py +52 -13
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer_client_python.egg-info/PKG-INFO +1 -1
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_context_manager.py +1 -1
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_page.py +6 -4
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_paragraph.py +2 -2
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_snapshot.py +18 -18
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_text_line_edit.py +75 -79
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.flake8 +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.github/workflows/daily-tests.yml +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.gitignore +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/LICENSE +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/NOTICE +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/README.md +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/TODO.md +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/check.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/docs/openapi.yml +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/media/logo-orange-512h.webp +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/media/logo-orange-60h.webp +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/release.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/setup.cfg +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/__init__.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/exceptions.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/fingerprint.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/models.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/page_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/path_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/pdfdancer_v1.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/text_line_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/test.sh +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/__init__.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/pdf_assertions.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_acroform.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_bezier_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_form_x_objects.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_image.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_line.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_line_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_new_pdf.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_path.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_path_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_path_builder_rectangle.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_path_comprehensive.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_rectangle_builder.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_singular_selection.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/Showcase.pdf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_anonymous_token.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_fingerprint.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_models.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_path_models.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_pdf_object_equality.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_rate_limit.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_standard_fonts.py +0 -0
- {pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/update-api-spec.sh +0 -0
|
@@ -160,14 +160,14 @@ class BaseTextEdit:
|
|
|
160
160
|
|
|
161
161
|
class TextLineEdit(BaseTextEdit):
|
|
162
162
|
def apply(self) -> bool:
|
|
163
|
-
#
|
|
163
|
+
# Line spacing is NOT supported for text lines - fail hard
|
|
164
164
|
if self._line_spacing is not None:
|
|
165
|
-
|
|
166
|
-
"
|
|
167
|
-
|
|
165
|
+
raise UnsupportedOperation(
|
|
166
|
+
"Line spacing changes are not supported for individual text lines. "
|
|
167
|
+
"Line spacing can only be modified on paragraphs, not individual text lines."
|
|
168
168
|
)
|
|
169
169
|
|
|
170
|
-
#
|
|
170
|
+
# If only text changed (no font, color, or position), use simple text modification
|
|
171
171
|
only_text_changed = (
|
|
172
172
|
self._new_text is not None
|
|
173
173
|
and self._font_name is None
|
|
@@ -185,7 +185,7 @@ class TextLineEdit(BaseTextEdit):
|
|
|
185
185
|
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
186
186
|
return result
|
|
187
187
|
|
|
188
|
-
#
|
|
188
|
+
# If only position changed (move operation)
|
|
189
189
|
only_move = (
|
|
190
190
|
self._position is not None
|
|
191
191
|
and self._new_text is None
|
|
@@ -216,15 +216,54 @@ class TextLineEdit(BaseTextEdit):
|
|
|
216
216
|
result = self._target_obj._client._move(self._object_ref, position)
|
|
217
217
|
return result
|
|
218
218
|
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"To modify text styling, please modify the parent paragraph instead."
|
|
219
|
+
# For font/color changes or combined operations, use TextLineBuilder
|
|
220
|
+
# This ensures proper handling of font/color fallbacks just like ParagraphEditSession
|
|
221
|
+
from .text_line_builder import TextLineBuilder
|
|
222
|
+
|
|
223
|
+
builder = TextLineBuilder.from_object_ref(
|
|
224
|
+
self._target_obj._client, self._object_ref
|
|
226
225
|
)
|
|
227
226
|
|
|
227
|
+
# Apply modifications to builder
|
|
228
|
+
# IMPORTANT: Always explicitly set text to ensure it's preserved
|
|
229
|
+
if self._new_text is not None:
|
|
230
|
+
builder.text(self._new_text)
|
|
231
|
+
elif hasattr(self._object_ref, "text") and self._object_ref.text:
|
|
232
|
+
# Preserve original text when only changing font/color/position
|
|
233
|
+
builder.text(self._object_ref.text)
|
|
234
|
+
|
|
235
|
+
# IMPORTANT: Always explicitly set font to ensure it's preserved
|
|
236
|
+
if self._font_name is not None and self._font_size is not None:
|
|
237
|
+
builder.font(self._font_name, self._font_size)
|
|
238
|
+
elif hasattr(self._object_ref, "font_name") and hasattr(self._object_ref, "font_size"):
|
|
239
|
+
if self._object_ref.font_name and self._object_ref.font_size:
|
|
240
|
+
# Preserve original font when only changing color/position
|
|
241
|
+
builder.font(self._object_ref.font_name, self._object_ref.font_size)
|
|
242
|
+
|
|
243
|
+
if self._color is not None:
|
|
244
|
+
builder.color(self._color)
|
|
245
|
+
if self._position is not None:
|
|
246
|
+
x = self._position.x()
|
|
247
|
+
y = self._position.y()
|
|
248
|
+
if x is None or y is None:
|
|
249
|
+
raise ValidationException("Position must have x and y coordinates")
|
|
250
|
+
page_index = (
|
|
251
|
+
self._object_ref.position.page_index
|
|
252
|
+
if self._object_ref.position
|
|
253
|
+
else None
|
|
254
|
+
)
|
|
255
|
+
if page_index is None:
|
|
256
|
+
raise ValidationException(
|
|
257
|
+
"Text line position must include a page index"
|
|
258
|
+
)
|
|
259
|
+
builder.at(page_index, x, y)
|
|
260
|
+
|
|
261
|
+
# Use builder's modify method which handles all the complexity
|
|
262
|
+
result = builder.modify(self._object_ref)
|
|
263
|
+
if result.warning:
|
|
264
|
+
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
265
|
+
return result
|
|
266
|
+
|
|
228
267
|
|
|
229
268
|
class ParagraphObject(PDFObjectBase):
|
|
230
269
|
"""Represents a paragraph text block inside a PDF page."""
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_context_manager.py
RENAMED
|
@@ -12,7 +12,7 @@ def test_context_manager_basic_usage():
|
|
|
12
12
|
|
|
13
13
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
14
14
|
paragraphs = pdf.select_paragraphs()
|
|
15
|
-
assert len(paragraphs)
|
|
15
|
+
assert 20 <= len(paragraphs) <= 22 # strange, but differs on linux
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def test_context_manager_edit_text_only():
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pdfdancer import ObjectType, Orientation, PageSize, PDFDancer
|
|
2
|
+
|
|
2
3
|
from tests.e2e import _require_env_and_fixture
|
|
3
4
|
from tests.e2e.pdf_assertions import PDFAssertions
|
|
4
5
|
|
|
@@ -7,14 +8,15 @@ def test_get_all_elements():
|
|
|
7
8
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
8
9
|
|
|
9
10
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
10
|
-
expected_total = 95
|
|
11
11
|
assert (
|
|
12
|
-
|
|
13
|
-
), f"{len(pdf.select_elements())} elements found but
|
|
12
|
+
95 <= len(pdf.select_elements()) <= 97
|
|
13
|
+
), f"{len(pdf.select_elements())} elements found but 95-97 elements expected"
|
|
14
14
|
actual_total = 0
|
|
15
15
|
for page in pdf.pages():
|
|
16
16
|
actual_total += len(page.select_elements())
|
|
17
|
-
assert
|
|
17
|
+
assert (
|
|
18
|
+
95 <= actual_total <= 97
|
|
19
|
+
), f"{actual_total} elements found but 95-97 elements expected"
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def test_get_pages():
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_paragraph.py
RENAMED
|
@@ -10,8 +10,8 @@ def test_find_paragraphs_by_position():
|
|
|
10
10
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
11
11
|
|
|
12
12
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
13
|
-
|
|
14
|
-
assert len(
|
|
13
|
+
paragraphs = pdf.select_paragraphs()
|
|
14
|
+
assert 20 <= len(paragraphs) <= 22 # strange, but differs on linux
|
|
15
15
|
|
|
16
16
|
paras_page0 = pdf.page(0).select_paragraphs()
|
|
17
17
|
assert len(paras_page0) == 3
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_snapshot.py
RENAMED
|
@@ -4,8 +4,8 @@ Validates that snapshot data matches select_* method results before, during, and
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
-
|
|
8
7
|
from pdfdancer import ObjectType, PDFDancer
|
|
8
|
+
|
|
9
9
|
from tests.e2e import _require_env_and_fixture
|
|
10
10
|
|
|
11
11
|
|
|
@@ -34,7 +34,7 @@ def test_page_snapshot_matches_select_paragraphs():
|
|
|
34
34
|
selected_ids = {p.internal_id for p in selected_paragraphs}
|
|
35
35
|
|
|
36
36
|
assert (
|
|
37
|
-
|
|
37
|
+
selected_ids == snapshot_ids
|
|
38
38
|
), "Snapshot and select_paragraphs() should return identical paragraph IDs"
|
|
39
39
|
|
|
40
40
|
|
|
@@ -59,7 +59,7 @@ def test_page_snapshot_matches_select_images():
|
|
|
59
59
|
selected_ids = {img.internal_id for img in selected_images}
|
|
60
60
|
|
|
61
61
|
assert (
|
|
62
|
-
|
|
62
|
+
selected_ids == snapshot_ids
|
|
63
63
|
), "Snapshot and select_images() should return identical image IDs"
|
|
64
64
|
|
|
65
65
|
|
|
@@ -86,7 +86,7 @@ def test_page_snapshot_matches_select_forms():
|
|
|
86
86
|
selected_ids = {form.internal_id for form in selected_forms}
|
|
87
87
|
|
|
88
88
|
assert (
|
|
89
|
-
|
|
89
|
+
selected_ids == snapshot_ids
|
|
90
90
|
), "Snapshot and select_forms() should return identical form IDs"
|
|
91
91
|
|
|
92
92
|
|
|
@@ -102,12 +102,12 @@ def test_page_snapshot_matches_select_form_fields():
|
|
|
102
102
|
e
|
|
103
103
|
for e in snapshot.elements
|
|
104
104
|
if e.type
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
in (
|
|
106
|
+
ObjectType.FORM_FIELD,
|
|
107
|
+
ObjectType.TEXT_FIELD,
|
|
108
|
+
ObjectType.CHECK_BOX,
|
|
109
|
+
ObjectType.RADIO_BUTTON,
|
|
110
|
+
)
|
|
111
111
|
]
|
|
112
112
|
|
|
113
113
|
selected_form_fields = page.select_form_fields()
|
|
@@ -121,7 +121,7 @@ def test_page_snapshot_matches_select_form_fields():
|
|
|
121
121
|
selected_ids = {field.internal_id for field in selected_form_fields}
|
|
122
122
|
|
|
123
123
|
assert (
|
|
124
|
-
|
|
124
|
+
selected_ids == snapshot_ids
|
|
125
125
|
), "Snapshot and select_form_fields() should return identical form field IDs"
|
|
126
126
|
|
|
127
127
|
|
|
@@ -143,7 +143,7 @@ def test_page_snapshot_contains_all_element_types():
|
|
|
143
143
|
|
|
144
144
|
# Verify we have at least some text elements
|
|
145
145
|
assert (
|
|
146
|
-
|
|
146
|
+
paragraph_count > 0 or text_line_count > 0
|
|
147
147
|
), "Page should have at least some text elements"
|
|
148
148
|
|
|
149
149
|
# Verify all elements have required fields
|
|
@@ -173,7 +173,7 @@ def test_document_snapshot_matches_all_pages():
|
|
|
173
173
|
individual_page_ids = {e.internal_id for e in individual_page_snap.elements}
|
|
174
174
|
|
|
175
175
|
assert (
|
|
176
|
-
|
|
176
|
+
individual_page_ids == doc_page_ids
|
|
177
177
|
), f"Page {i} should have identical elements in document and individual snapshots"
|
|
178
178
|
|
|
179
179
|
|
|
@@ -201,7 +201,7 @@ def test_type_filter_matches_select_method():
|
|
|
201
201
|
selected_ids = {p.internal_id for p in selected_paragraphs}
|
|
202
202
|
|
|
203
203
|
assert (
|
|
204
|
-
|
|
204
|
+
selected_ids == snapshot_ids
|
|
205
205
|
), "Filtered snapshot and select_paragraphs() should return identical IDs"
|
|
206
206
|
|
|
207
207
|
|
|
@@ -239,7 +239,7 @@ def test_total_element_count_matches_expected():
|
|
|
239
239
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
240
240
|
# Showcase.pdf - Python API filters certain types (638)
|
|
241
241
|
all_elements = pdf.select_elements()
|
|
242
|
-
assert len(all_elements)
|
|
242
|
+
assert 95 <= len(all_elements) <= 97, "Showcase.pdf should have 95 total elements"
|
|
243
243
|
|
|
244
244
|
doc_snapshot = pdf.get_document_snapshot()
|
|
245
245
|
snapshot_total = sum(len(p.elements) for p in doc_snapshot.pages)
|
|
@@ -266,7 +266,7 @@ def test_snapshot_consistency_across_multiple_pages():
|
|
|
266
266
|
page_snap = pdf.get_page_snapshot(i)
|
|
267
267
|
assert page_snap is not None, f"Page {i} snapshot should not be None"
|
|
268
268
|
assert (
|
|
269
|
-
|
|
269
|
+
page_snap.page_ref.position.page_index == i
|
|
270
270
|
), "Page snapshot should have correct page index"
|
|
271
271
|
|
|
272
272
|
|
|
@@ -280,7 +280,7 @@ def test_document_snapshot_contains_fonts():
|
|
|
280
280
|
|
|
281
281
|
# Should have fonts
|
|
282
282
|
assert (
|
|
283
|
-
|
|
283
|
+
doc_snapshot.fonts is not None
|
|
284
284
|
), "Document snapshot should have fonts list"
|
|
285
285
|
assert len(doc_snapshot.fonts) > 0, "Document should have at least one font"
|
|
286
286
|
|
|
@@ -289,5 +289,5 @@ def test_document_snapshot_contains_fonts():
|
|
|
289
289
|
assert font.font_name is not None, "Font should have a name"
|
|
290
290
|
assert font.font_type is not None, "Font should have a type"
|
|
291
291
|
assert (
|
|
292
|
-
|
|
292
|
+
font.similarity_score is not None
|
|
293
293
|
), "Font should have similarity score"
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_text_line_edit.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
|
|
3
2
|
from pdfdancer import Color
|
|
4
3
|
from pdfdancer.pdfdancer_v1 import PDFDancer
|
|
4
|
+
|
|
5
5
|
from tests.e2e import _require_env_and_fixture
|
|
6
6
|
from tests.e2e.pdf_assertions import PDFAssertions
|
|
7
7
|
|
|
@@ -15,8 +15,8 @@ def test_text_line_edit_text_only():
|
|
|
15
15
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
16
16
|
"This is regular Sans text showing alignment and styles."
|
|
17
17
|
)
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
|
|
19
|
+
assert len(text_lines) >= 1
|
|
20
20
|
|
|
21
21
|
text_line = text_lines[0]
|
|
22
22
|
|
|
@@ -34,49 +34,45 @@ def test_text_line_edit_text_only():
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_text_line_edit_font_only():
|
|
37
|
-
"""Test
|
|
37
|
+
"""Test text line font-only changes"""
|
|
38
38
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
39
39
|
|
|
40
40
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
41
41
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
42
42
|
"This is regular Sans text showing alignment and styles."
|
|
43
43
|
)
|
|
44
|
-
|
|
45
|
-
pytest.skip("Required text line not found in test PDF")
|
|
44
|
+
assert len(text_lines) >= 1
|
|
46
45
|
|
|
47
46
|
text_line = text_lines[0]
|
|
47
|
+
original_text = text_line.text
|
|
48
48
|
|
|
49
|
-
# Font changes on text lines should
|
|
50
|
-
|
|
49
|
+
# Font changes on text lines should work
|
|
50
|
+
with text_line.edit() as editor:
|
|
51
|
+
editor.font("Helvetica", 28)
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
):
|
|
55
|
-
with text_line.edit() as editor:
|
|
56
|
-
editor.font("Helvetica", 28)
|
|
53
|
+
# Verify the text still exists (font changed but text preserved)
|
|
54
|
+
PDFAssertions(pdf).assert_textline_exists(original_text)
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
def test_text_line_edit_color_only():
|
|
60
|
-
"""Test
|
|
58
|
+
"""Test text line color-only changes"""
|
|
61
59
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
62
60
|
|
|
63
61
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
64
62
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
65
63
|
"This is regular Sans text showing alignment and styles."
|
|
66
64
|
)
|
|
67
|
-
|
|
68
|
-
pytest.skip("Required text line not found in test PDF")
|
|
65
|
+
assert len(text_lines) >= 1
|
|
69
66
|
|
|
70
67
|
text_line = text_lines[0]
|
|
68
|
+
original_text = text_line.text
|
|
71
69
|
|
|
72
|
-
# Color changes on text lines should
|
|
73
|
-
|
|
70
|
+
# Color changes on text lines should work
|
|
71
|
+
with text_line.edit() as editor:
|
|
72
|
+
editor.color(Color(0, 255, 0))
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
):
|
|
78
|
-
with text_line.edit() as editor:
|
|
79
|
-
editor.color(Color(0, 255, 0))
|
|
74
|
+
# Verify the text still exists (color changed but text preserved)
|
|
75
|
+
PDFAssertions(pdf).assert_textline_exists(original_text)
|
|
80
76
|
|
|
81
77
|
|
|
82
78
|
def test_text_line_edit_move_only():
|
|
@@ -87,8 +83,7 @@ def test_text_line_edit_move_only():
|
|
|
87
83
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
88
84
|
"This is regular Sans text showing alignment and styles."
|
|
89
85
|
)
|
|
90
|
-
|
|
91
|
-
pytest.skip("Required text line not found in test PDF")
|
|
86
|
+
assert len(text_lines) >= 1
|
|
92
87
|
|
|
93
88
|
text_line = text_lines[0]
|
|
94
89
|
|
|
@@ -108,100 +103,105 @@ def test_text_line_edit_move_only():
|
|
|
108
103
|
|
|
109
104
|
|
|
110
105
|
def test_text_line_edit_text_and_font():
|
|
111
|
-
"""Test
|
|
106
|
+
"""Test text+font changes work together"""
|
|
112
107
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
113
108
|
|
|
114
109
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
115
110
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
116
111
|
"This is regular Sans text showing alignment and styles."
|
|
117
112
|
)
|
|
118
|
-
|
|
119
|
-
pytest.skip("Required text line not found in test PDF")
|
|
113
|
+
assert len(text_lines) >= 1
|
|
120
114
|
|
|
121
115
|
text_line = text_lines[0]
|
|
122
116
|
|
|
123
|
-
# Text + Font changes should
|
|
124
|
-
|
|
117
|
+
# Text + Font changes should work
|
|
118
|
+
with text_line.edit() as editor:
|
|
119
|
+
editor.replace("New Text Here")
|
|
120
|
+
editor.font("Helvetica", 16)
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
# Verify the new text exists
|
|
123
|
+
(
|
|
124
|
+
PDFAssertions(pdf)
|
|
125
|
+
.assert_textline_exists("New Text Here")
|
|
126
|
+
.assert_textline_does_not_exist(
|
|
127
|
+
"This is regular Sans text showing alignment and styles."
|
|
128
|
+
)
|
|
129
|
+
)
|
|
132
130
|
|
|
133
131
|
|
|
134
132
|
def test_text_line_edit_all_properties():
|
|
135
|
-
"""Test that combined property changes
|
|
133
|
+
"""Test that combined property changes work (except line spacing)"""
|
|
136
134
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
137
135
|
|
|
138
136
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
139
137
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
140
138
|
"This is regular Sans text showing alignment and styles."
|
|
141
139
|
)
|
|
142
|
-
|
|
143
|
-
pytest.skip("Required text line not found in test PDF")
|
|
140
|
+
assert len(text_lines) >= 1
|
|
144
141
|
|
|
145
142
|
text_line = text_lines[0]
|
|
146
143
|
|
|
147
|
-
# Combined changes including font/color should
|
|
148
|
-
|
|
144
|
+
# Combined changes including font/color/position should work
|
|
145
|
+
with text_line.edit() as editor:
|
|
146
|
+
editor.replace("Fully Modified")
|
|
147
|
+
editor.font("Helvetica", 18)
|
|
148
|
+
editor.color(Color(255, 0, 0))
|
|
149
|
+
editor.move_to(100, 200)
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
):
|
|
153
|
-
with text_line.edit() as editor:
|
|
154
|
-
editor.replace("Fully Modified")
|
|
155
|
-
editor.font("Helvetica", 18)
|
|
156
|
-
editor.color(Color(255, 0, 0))
|
|
157
|
-
editor.move_to(100, 200)
|
|
151
|
+
# Verify the modified text exists
|
|
152
|
+
PDFAssertions(pdf).assert_textline_exists("Fully Modified")
|
|
158
153
|
|
|
159
154
|
|
|
160
|
-
def
|
|
161
|
-
"""Test that line spacing
|
|
155
|
+
def test_text_line_edit_line_spacing_fails():
|
|
156
|
+
"""Test that line spacing changes fail hard for text lines"""
|
|
162
157
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
163
158
|
|
|
164
159
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
165
160
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
166
161
|
"This is regular Sans text showing alignment and styles."
|
|
167
162
|
)
|
|
168
|
-
|
|
169
|
-
pytest.skip("Required text line not found in test PDF")
|
|
163
|
+
assert len(text_lines) >= 1
|
|
170
164
|
|
|
171
165
|
text_line = text_lines[0]
|
|
172
166
|
|
|
173
|
-
# Line spacing should
|
|
174
|
-
|
|
175
|
-
editor.line_spacing(2.0) # This should be ignored
|
|
176
|
-
editor.replace("Text with ignored spacing")
|
|
167
|
+
# Line spacing should raise UnsupportedOperation
|
|
168
|
+
from pdfdancer.types import UnsupportedOperation
|
|
177
169
|
|
|
178
|
-
|
|
179
|
-
|
|
170
|
+
with pytest.raises(
|
|
171
|
+
UnsupportedOperation,
|
|
172
|
+
match="Line spacing changes are not supported for individual text lines",
|
|
173
|
+
):
|
|
174
|
+
with text_line.edit() as editor:
|
|
175
|
+
editor.line_spacing(2.0)
|
|
176
|
+
editor.replace("Text with spacing")
|
|
180
177
|
|
|
181
178
|
|
|
182
179
|
def test_text_line_edit_chaining():
|
|
183
|
-
"""Test that chained font/color changes
|
|
180
|
+
"""Test that chained font/color changes work"""
|
|
184
181
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
185
182
|
|
|
186
183
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
|
|
187
184
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
188
185
|
"This is regular Sans text showing alignment and styles."
|
|
189
186
|
)
|
|
190
|
-
|
|
191
|
-
pytest.skip("Required text line not found in test PDF")
|
|
187
|
+
assert len(text_lines) >= 1
|
|
192
188
|
|
|
193
189
|
text_line = text_lines[0]
|
|
194
190
|
|
|
195
|
-
# Chained font/color changes should
|
|
196
|
-
|
|
191
|
+
# Chained font/color changes should work
|
|
192
|
+
with text_line.edit() as editor:
|
|
193
|
+
editor.replace("Chained Edits").font("Helvetica", 15).color(
|
|
194
|
+
Color(128, 128, 128)
|
|
195
|
+
)
|
|
197
196
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
# Verify the new text exists
|
|
198
|
+
(
|
|
199
|
+
PDFAssertions(pdf)
|
|
200
|
+
.assert_textline_exists("Chained Edits")
|
|
201
|
+
.assert_textline_does_not_exist(
|
|
202
|
+
"This is regular Sans text showing alignment and styles."
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
205
|
|
|
206
206
|
|
|
207
207
|
def test_text_line_edit_with_exception_no_apply():
|
|
@@ -212,8 +212,7 @@ def test_text_line_edit_with_exception_no_apply():
|
|
|
212
212
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
213
213
|
"This is regular Sans text showing alignment and styles."
|
|
214
214
|
)
|
|
215
|
-
|
|
216
|
-
pytest.skip("Required text line not found in test PDF")
|
|
215
|
+
assert len(text_lines) >= 1
|
|
217
216
|
|
|
218
217
|
text_line = text_lines[0]
|
|
219
218
|
|
|
@@ -238,8 +237,7 @@ def test_text_line_edit_multiple_sequential():
|
|
|
238
237
|
text_lines = pdf.page(0).select_text_lines_starting_with(
|
|
239
238
|
"This is regular Sans text showing alignment and styles."
|
|
240
239
|
)
|
|
241
|
-
|
|
242
|
-
pytest.skip("Required text line not found in test PDF")
|
|
240
|
+
assert len(text_lines) >= 1
|
|
243
241
|
|
|
244
242
|
text_line = text_lines[0]
|
|
245
243
|
with text_line.edit() as editor:
|
|
@@ -270,8 +268,7 @@ def test_text_line_edit_vs_manual_apply():
|
|
|
270
268
|
text_lines = pdf1.page(0).select_text_lines_starting_with(
|
|
271
269
|
"This is regular Sans text showing alignment and styles."
|
|
272
270
|
)
|
|
273
|
-
|
|
274
|
-
pytest.skip("Required text line not found in test PDF")
|
|
271
|
+
assert len(text_lines) >= 1
|
|
275
272
|
|
|
276
273
|
text_line = text_lines[0]
|
|
277
274
|
|
|
@@ -285,8 +282,7 @@ def test_text_line_edit_vs_manual_apply():
|
|
|
285
282
|
text_lines = pdf2.page(0).select_text_lines_starting_with(
|
|
286
283
|
"This is regular Sans text showing alignment and styles."
|
|
287
284
|
)
|
|
288
|
-
|
|
289
|
-
pytest.skip("Required text line not found in test PDF")
|
|
285
|
+
assert len(text_lines) >= 1
|
|
290
286
|
|
|
291
287
|
text_line = text_lines[0]
|
|
292
288
|
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.claude/commands/discuss.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/.github/workflows/daily-tests.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/media/logo-orange-512h.webp
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/media/logo-orange-60h.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/exceptions.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/fingerprint.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/image_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/page_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/paragraph_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/path_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/src/pdfdancer/text_line_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/pdf_assertions.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_acroform.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_bezier_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_form_x_objects.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_line_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_path_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_pdfdancer.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/e2e/test_positioning.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/Showcase.pdf
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/basic-paths.pdf
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/fixtures/logo-80.png
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_anonymous_token.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_openapi_compliance.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_pdf_object_equality.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.27 → pdfdancer_client_python-0.2.29}/tests/test_standard_fonts.py
RENAMED
|
File without changes
|
|
File without changes
|