pdfdancer-client-python 0.3.11__tar.gz → 0.3.12__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.
Files changed (95) hide show
  1. pdfdancer_client_python-0.3.12/.api-url +1 -0
  2. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.github/workflows/ci.yml +31 -4
  3. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.github/workflows/daily-tests.yml +2 -0
  4. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.github/workflows/release.yml +6 -0
  5. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.gitignore +2 -0
  6. pdfdancer_client_python-0.3.12/.gitmodules +3 -0
  7. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/PKG-INFO +2 -2
  8. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/README.md +1 -1
  9. pdfdancer_client_python-0.3.12/docs/capabilities/.gitkeep +0 -0
  10. pdfdancer_client_python-0.3.12/docs/capabilities/CLEAR_CLIPPING.md +61 -0
  11. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/pyproject.toml +1 -1
  12. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/__init__.py +1 -1
  13. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/pdfdancer_v1.py +93 -20
  14. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/types.py +11 -6
  15. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/PKG-INFO +2 -2
  16. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/SOURCES.txt +8 -3
  17. pdfdancer_client_python-0.3.12/tests/e2e/pdf_assertions.py +1783 -0
  18. pdfdancer_client_python-0.3.12/tests/e2e/test_clipping.py +323 -0
  19. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_paragraph.py +15 -3
  20. pdfdancer_client_python-0.3.12/tests/fixtures/Asimovian-Regular.ttf +0 -0
  21. pdfdancer_client_python-0.3.12/tests/fixtures/Roboto-Regular.ttf +0 -0
  22. pdfdancer_client_python-0.3.12/tests/fixtures/invisible-content-clipping-test.pdf +0 -0
  23. pdfdancer_client_python-0.3.11/docs/api-schemas/v0.yml +0 -5300
  24. pdfdancer_client_python-0.3.11/docs/api-schemas/v1.yml +0 -5387
  25. pdfdancer_client_python-0.3.11/tests/e2e/pdf_assertions.py +0 -842
  26. pdfdancer_client_python-0.3.11/update-api-spec.sh +0 -5
  27. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.claude/commands/discuss.md +0 -0
  28. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.claude/commands/implement-new-api-features.md +0 -0
  29. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/.flake8 +0 -0
  30. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/CLAUDE.md +0 -0
  31. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/LICENSE +0 -0
  32. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/NOTICE +0 -0
  33. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/TODO.md +0 -0
  34. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/check.py +0 -0
  35. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/media/logo-orange-512h.webp +0 -0
  36. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/media/logo-orange-60h.webp +0 -0
  37. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/media/logo-silver-512h.webp +0 -0
  38. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/media/logo-silver-60h.webp +0 -0
  39. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/release.py +0 -0
  40. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/setup.cfg +0 -0
  41. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/exceptions.py +0 -0
  42. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/fingerprint.py +0 -0
  43. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/image_builder.py +0 -0
  44. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/models.py +0 -0
  45. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/page_builder.py +0 -0
  46. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/paragraph_builder.py +0 -0
  47. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/path_builder.py +0 -0
  48. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer/text_line_builder.py +0 -0
  49. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  50. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  51. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  52. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/test.sh +0 -0
  53. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/__init__.py +0 -0
  54. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/conftest.py +0 -0
  55. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/__init__.py +0 -0
  56. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_acroform.py +0 -0
  57. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_bezier_builder.py +0 -0
  58. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_context_manager.py +0 -0
  59. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_form_x_objects.py +0 -0
  60. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_image.py +0 -0
  61. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_image_transform.py +0 -0
  62. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_line.py +0 -0
  63. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_line_builder.py +0 -0
  64. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_new_pdf.py +0 -0
  65. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_page.py +0 -0
  66. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path.py +0 -0
  67. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_builder.py +0 -0
  68. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_builder_rectangle.py +0 -0
  69. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_comprehensive.py +0 -0
  70. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_group.py +0 -0
  71. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_pdfdancer.py +0 -0
  72. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_positioning.py +0 -0
  73. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_rectangle_builder.py +0 -0
  74. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_redact.py +0 -0
  75. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_singular_selection.py +0 -0
  76. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_snapshot.py +0 -0
  77. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_template_replace.py +0 -0
  78. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_template_replace_linebreak.py +0 -0
  79. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/e2e/test_text_line_edit.py +0 -0
  80. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  81. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/Empty.pdf +0 -0
  82. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  83. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/Showcase.pdf +0 -0
  84. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/basic-paths.pdf +0 -0
  85. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/form-xobject-example.pdf +0 -0
  86. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/logo-80.png +0 -0
  87. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/fixtures/mixed-form-types.pdf +0 -0
  88. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_anonymous_token.py +0 -0
  89. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_fingerprint.py +0 -0
  90. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_models.py +0 -0
  91. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_openapi_compliance.py +0 -0
  92. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_path_models.py +0 -0
  93. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_pdf_object_equality.py +0 -0
  94. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_rate_limit.py +0 -0
  95. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.12}/tests/test_standard_fonts.py +0 -0
@@ -0,0 +1 @@
1
+ http://46.225.120.69:8080
@@ -2,7 +2,7 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ '**' ]
5
+ branches: [ 'main' ]
6
6
  pull_request:
7
7
  branches: [ '**' ]
8
8
  workflow_dispatch:
@@ -20,6 +20,19 @@ jobs:
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v4
23
+ with:
24
+ submodules: true
25
+
26
+ - name: Detect API URL override
27
+ if: github.event_name == 'pull_request'
28
+ id: api-url
29
+ run: |
30
+ if [ -f .api-url ]; then
31
+ url=$(cat .api-url | tr -d '[:space:]')
32
+ if [ -n "$url" ]; then
33
+ echo "base_url=$url" >> "$GITHUB_OUTPUT"
34
+ fi
35
+ fi
23
36
 
24
37
  - name: Set up Python ${{ matrix.python-version }}
25
38
  uses: actions/setup-python@v5
@@ -40,7 +53,7 @@ jobs:
40
53
 
41
54
  - name: Run tests
42
55
  run: |
43
- PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
56
+ PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url || 'https://api-staging.pdfdancer.com' }} \
44
57
  PDFDANCER_TOKEN=42 \
45
58
  venv/bin/python -m pytest tests/ -v --maxfail=3
46
59
 
@@ -64,6 +77,20 @@ jobs:
64
77
 
65
78
  steps:
66
79
  - uses: actions/checkout@v4
80
+ with:
81
+ submodules: true
82
+
83
+ - name: Detect API URL override
84
+ if: github.event_name == 'pull_request'
85
+ id: api-url
86
+ shell: bash
87
+ run: |
88
+ if [ -f .api-url ]; then
89
+ url=$(cat .api-url | tr -d '[:space:]')
90
+ if [ -n "$url" ]; then
91
+ echo "base_url=$url" >> "$GITHUB_OUTPUT"
92
+ fi
93
+ fi
67
94
 
68
95
  - name: Set up Python ${{ matrix.python-version }}
69
96
  uses: actions/setup-python@v5
@@ -88,7 +115,7 @@ jobs:
88
115
  - name: Run tests (Unix)
89
116
  if: runner.os != 'Windows'
90
117
  run: |
91
- PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
118
+ PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url || 'https://api-staging.pdfdancer.com' }} \
92
119
  PDFDANCER_TOKEN=42 \
93
120
  venv/bin/python -m pytest tests/ -v --maxfail=3
94
121
 
@@ -119,7 +146,7 @@ jobs:
119
146
  if: runner.os == 'Windows'
120
147
  shell: cmd
121
148
  run: |
122
- set PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com
149
+ set PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url || 'https://api-staging.pdfdancer.com' }}
123
150
  set PDFDANCER_TOKEN=42
124
151
  venv\Scripts\python -m pytest tests/ -v --maxfail=3
125
152
 
@@ -18,6 +18,8 @@ jobs:
18
18
 
19
19
  steps:
20
20
  - uses: actions/checkout@v4
21
+ with:
22
+ submodules: true
21
23
 
22
24
  - name: Set up Python ${{ matrix.python-version }}
23
25
  uses: actions/setup-python@v5
@@ -9,6 +9,8 @@ jobs:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
11
  - uses: actions/checkout@v4
12
+ with:
13
+ submodules: true
12
14
 
13
15
  - name: Set up Python 3.12
14
16
  uses: actions/setup-python@v5
@@ -39,6 +41,8 @@ jobs:
39
41
  id-token: write
40
42
  steps:
41
43
  - uses: actions/checkout@v4
44
+ with:
45
+ submodules: true
42
46
 
43
47
  - name: Set up Python 3.12
44
48
  uses: actions/setup-python@v5
@@ -71,6 +75,8 @@ jobs:
71
75
  contents: write
72
76
  steps:
73
77
  - uses: actions/checkout@v4
78
+ with:
79
+ submodules: true
74
80
 
75
81
  - name: Create GitHub Release
76
82
  run: gh release create "$GITHUB_REF_NAME" --generate-notes
@@ -19,3 +19,5 @@ __pycache__/
19
19
  /.run/
20
20
  /build
21
21
  .env
22
+ .propagate*md
23
+ /.last-message.txt
@@ -0,0 +1,3 @@
1
+ [submodule "tests/fixtures/examples"]
2
+ path = tests/fixtures/examples
3
+ url = https://github.com/MenschMachine/pdfdancer-examples.git
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.3.11
3
+ Version: 0.3.12
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -359,7 +359,7 @@ with PDFDancer.open("contract.pdf") as pdf:
359
359
  ```
360
360
 
361
361
  Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
362
- with helpers such as `delete()`, `move_to(x, y)`, `redact()`, or `edit()` depending on the object type.
362
+ with helpers such as `delete()`, `move_to(x, y)`, `clear_clipping()`, `redact()`, or `edit()` depending on the object type.
363
363
 
364
364
  **Singular selection methods** return the first match (or `None`) for convenience:
365
365
 
@@ -120,7 +120,7 @@ with PDFDancer.open("contract.pdf") as pdf:
120
120
  ```
121
121
 
122
122
  Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
123
- with helpers such as `delete()`, `move_to(x, y)`, `redact()`, or `edit()` depending on the object type.
123
+ with helpers such as `delete()`, `move_to(x, y)`, `clear_clipping()`, `redact()`, or `edit()` depending on the object type.
124
124
 
125
125
  **Singular selection methods** return the first match (or `None`) for convenience:
126
126
 
@@ -0,0 +1,61 @@
1
+ # Clear Clipping Capability
2
+
3
+ Most of the PDF model classes including container classes like PDFParagraph/PDFTextLine/PDFPathGroup now implement ClippingDetachable with the method clearClipping().
4
+ This removes any clipping path which was active for this element.
5
+
6
+ This is useful in case clients want to move an element but the new position is hidden by the clipping path. clearing it makes the element visible again.
7
+
8
+ The new backend is version 1.8.6-rc2, available in the local m2 repository
9
+
10
+ ## Test PDFs to use
11
+
12
+ examples/clipping/invisible-content-clipping-test.pdf
13
+
14
+ - A PDF where content is present but not visible due to clipping paths that exclude the content areas. Contains an image clipped away by one clipping path and vector paths clipped away by another clipping path.
15
+
16
+
17
+ ## Api Docs
18
+
19
+ Explain for all elements and under the clipping-section.
20
+
21
+ ## Website and Marketing
22
+
23
+ Not to mention there.
24
+
25
+ ## What's new Newsletter
26
+
27
+ include
28
+
29
+ ## Changelog
30
+
31
+ include
32
+
33
+ ## Implementation in pdfdancer-api
34
+
35
+ - Updated backend dependency to `com.tfc.pdf:pdfdancer-backend:1.8.6-rc2` in `build.gradle.kts` to use the clipping-detach support.
36
+ - Added client-facing helpers:
37
+ - `BaseReference.clearClipping()` for any object reference implementing clipping detach semantics via the API.
38
+ - `PathGroupReference.clearClipping()` for grouped vector paths.
39
+ - `PDFDancer.clearClipping(ObjectRef)` calling `PUT /pdf/clipping/clear`.
40
+ - `PDFDancer.clearPathGroupClipping(pageIndex, groupId)` calling `PUT /pdf/path-group/clipping/clear`.
41
+ - Both client calls invalidate local snapshot caches after mutation.
42
+ - Added server endpoints in both controllers:
43
+ - `PDFController` and `PDFControllerV1` expose `PUT /pdf/clipping/clear` and `PUT /pdf/path-group/clipping/clear`.
44
+ - V1 uses `ClearClippingRequestV1` and `ClearPathGroupClippingRequestV1`, converting both to internal requests via `toInternal()`.
45
+ - Added controller orchestration in `ControllerOps`:
46
+ - `clearClipping(...)` validates `objectRef`, executes `ClearObjectClippingCommand`, and publishes `PDF_OBJECT_MODIFIED` metric with operation `clear_clipping`.
47
+ - `clearPathGroupClipping(...)` validates `groupId` and `pageIndex`, executes `ClearPathGroupClippingCommand`, and publishes `VECTOR_MANIPULATION` metric with operation `clear_path_group_clipping`.
48
+ - Wired session and replay support:
49
+ - `Session.clearClipping(...)` and `Session.clearPathGroupClipping(...)` execute commands inside `SessionContext` and record commands for session history.
50
+ - `CommandDeserializer` now reconstructs `ClearObjectClippingCommand` and `ClearPathGroupClippingCommand` for debug archive replay.
51
+ - Added tests and assertions:
52
+ - `ClippingTest` verifies clearing clipping on `PathReference`, `PathGroupReference`, and `TextLineReference`.
53
+ - `DirectPDFAssertions`/`PDFAssertions` gained helpers to detect clipped paths and assert clipping present/removed.
54
+
55
+ ## Implementation in pdfdancer-client-python
56
+
57
+ - Added top-level client methods in `src/pdfdancer/pdfdancer_v1.py`: `PDFDancer.clear_clipping(object_ref)` calls `PUT /pdf/clipping/clear`, and `PDFDancer.clear_path_group_clipping(page_number, group_id)` calls `PUT /pdf/path-group/clipping/clear`.
58
+ - The Python client validates required inputs before sending the request: `object_ref` must be present, `page_number` is a 1-based integer for path groups, and `group_id` must be non-empty. Both methods coerce the API response to `bool` and invalidate cached snapshots after a successful mutation.
59
+ - Added object-level convenience methods in `src/pdfdancer/types.py`. `PDFObjectBase.clear_clipping()` makes the capability available on typed selections that expose `object_ref()` such as paths, images, and text lines, while `PathGroupObject.clear_clipping()` forwards to the path-group API using `self._page_index + 1` to convert the internal 0-based index to the public 1-based page number.
60
+ - Updated `README.md` to list `clear_clipping()` alongside other typed-object helper methods so the feature is discoverable from the main usage guide.
61
+ - Added end-to-end coverage in `tests/e2e/test_clipping.py` for direct object calls and top-level client calls on paths, grouped paths, images, and clipped text lines, including a case where clipping spans multiple content streams. `tests/e2e/pdf_assertions.py` was extended with PDF draw-event inspection helpers that assert whether clipping is present or removed.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdfdancer-client-python"
7
- version = "0.3.11"
7
+ version = "0.3.12"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -55,7 +55,7 @@ from .paragraph_builder import ParagraphBuilder
55
55
  from .path_builder import BezierBuilder, LineBuilder, PathBuilder
56
56
  from .text_line_builder import TextLineBuilder
57
57
 
58
- __version__ = "0.3.11"
58
+ __version__ = "0.3.12"
59
59
  __all__ = [
60
60
  "PDFDancer",
61
61
  "ParagraphBuilder",
@@ -134,7 +134,7 @@ DEFAULT_RETRY_BACKOFF_FACTOR = float(
134
134
 
135
135
 
136
136
  def _dict_to_replacements(
137
- replacements: Dict[str, Union[str, dict]]
137
+ replacements: Dict[str, Union[str, dict]],
138
138
  ) -> List[TemplateReplacement]:
139
139
  """Convert dict-based replacements to TemplateReplacement list."""
140
140
  result = []
@@ -2292,6 +2292,37 @@ class PDFDancer:
2292
2292
  targets = [RedactTarget(obj.internal_id, replacement) for obj in objects]
2293
2293
  return self._redact(targets, replacement, placeholder_color)
2294
2294
 
2295
+ def clear_clipping(self, object_ref: ObjectRef) -> bool:
2296
+ """
2297
+ Clear clipping on a single PDF object.
2298
+
2299
+ Args:
2300
+ object_ref: Reference to the object whose clipping should be removed
2301
+
2302
+ Returns:
2303
+ True when the server cleared clipping successfully
2304
+ """
2305
+ return self._clear_clipping(object_ref)
2306
+
2307
+ def _clear_clipping(self, object_ref: ObjectRef) -> bool:
2308
+ """
2309
+ Internal helper to clear clipping from a single object.
2310
+ """
2311
+ if object_ref is None:
2312
+ raise ValidationException("Object reference cannot be null")
2313
+
2314
+ response = self._make_request(
2315
+ "PUT",
2316
+ "/pdf/clipping/clear",
2317
+ data={"objectRef": object_ref.to_dict()},
2318
+ )
2319
+ result = bool(response.json())
2320
+
2321
+ if result:
2322
+ self._invalidate_snapshots()
2323
+
2324
+ return result
2325
+
2295
2326
  # Template Replacement Operations
2296
2327
 
2297
2328
  def _apply_replacements(
@@ -2506,19 +2537,21 @@ class PDFDancer:
2506
2537
  data["pathIds"] = path_ids
2507
2538
  if region is not None:
2508
2539
  data["region"] = {
2509
- "x": region.x, "y": region.y,
2510
- "width": region.width, "height": region.height,
2540
+ "x": region.x,
2541
+ "y": region.y,
2542
+ "width": region.width,
2543
+ "height": region.height,
2511
2544
  }
2512
- response = self._make_request(
2513
- "POST", "/pdf/path-group/create", data=data
2514
- )
2545
+ response = self._make_request("POST", "/pdf/path-group/create", data=data)
2515
2546
  self._invalidate_snapshots()
2516
2547
  return PathGroupInfo.from_dict(response.json())
2517
2548
 
2518
2549
  def _move_path_group(self, page_index, group_id, x, y):
2519
2550
  data = {
2520
- "pageIndex": page_index, "groupId": group_id,
2521
- "x": x, "y": y,
2551
+ "pageIndex": page_index,
2552
+ "groupId": group_id,
2553
+ "x": x,
2554
+ "y": y,
2522
2555
  }
2523
2556
  response = self._make_request("PUT", "/pdf/path-group/move", data=data)
2524
2557
  self._invalidate_snapshots()
@@ -2526,13 +2559,12 @@ class PDFDancer:
2526
2559
 
2527
2560
  def _transform_path_group(self, page_index, group_id, transform_type, **kwargs):
2528
2561
  data = {
2529
- "pageIndex": page_index, "groupId": group_id,
2562
+ "pageIndex": page_index,
2563
+ "groupId": group_id,
2530
2564
  "transformType": transform_type,
2531
2565
  }
2532
2566
  data.update({k: v for k, v in kwargs.items() if v is not None})
2533
- response = self._make_request(
2534
- "PUT", "/pdf/path-group/transform", data=data
2535
- )
2567
+ response = self._make_request("PUT", "/pdf/path-group/transform", data=data)
2536
2568
  self._invalidate_snapshots()
2537
2569
  return response.json()
2538
2570
 
@@ -2552,15 +2584,16 @@ class PDFDancer:
2552
2584
  if width <= 0 or height <= 0:
2553
2585
  raise ValidationException("Width and height must be positive")
2554
2586
  return self._transform_path_group(
2555
- page_index, group_id, "RESIZE",
2556
- targetWidth=width, targetHeight=height,
2587
+ page_index,
2588
+ group_id,
2589
+ "RESIZE",
2590
+ targetWidth=width,
2591
+ targetHeight=height,
2557
2592
  )
2558
2593
 
2559
2594
  def _remove_path_group(self, page_index, group_id):
2560
2595
  data = {"pageIndex": page_index, "groupId": group_id}
2561
- response = self._make_request(
2562
- "DELETE", "/pdf/path-group/remove", data=data
2563
- )
2596
+ response = self._make_request("DELETE", "/pdf/path-group/remove", data=data)
2564
2597
  self._invalidate_snapshots()
2565
2598
  return response.json()
2566
2599
 
@@ -2568,12 +2601,52 @@ class PDFDancer:
2568
2601
  from .models import PathGroupInfo
2569
2602
  from .types import PathGroupObject
2570
2603
 
2571
- response = self._make_request(
2572
- "GET", f"/pdf/page/{page_index}/path-groups"
2573
- )
2604
+ response = self._make_request("GET", f"/pdf/page/{page_index}/path-groups")
2574
2605
  infos = [PathGroupInfo.from_dict(d) for d in response.json()]
2575
2606
  return [PathGroupObject(self, page_index, info) for info in infos]
2576
2607
 
2608
+ def clear_path_group_clipping(self, page_number: int, group_id: str) -> bool:
2609
+ """
2610
+ Clear clipping for a grouped set of paths.
2611
+
2612
+ Args:
2613
+ page_number: 1-based page number containing the path group
2614
+ group_id: Identifier returned by `group_paths(...)`
2615
+
2616
+ Returns:
2617
+ True when the server cleared clipping successfully
2618
+ """
2619
+ return self._clear_path_group_clipping(page_number, group_id)
2620
+
2621
+ def _clear_path_group_clipping(self, page_number: int, group_id: str) -> bool:
2622
+ """
2623
+ Internal helper to clear clipping from a path group using the v1 API.
2624
+ """
2625
+ if page_number is None:
2626
+ raise ValidationException("page_number cannot be null")
2627
+ if not isinstance(page_number, int):
2628
+ raise ValidationException(
2629
+ f"page_number must be an integer, got {type(page_number)}"
2630
+ )
2631
+ if page_number < 1:
2632
+ raise ValidationException(
2633
+ f"page_number must be >= 1 (1-based indexing), got {page_number}"
2634
+ )
2635
+ if group_id is None or not str(group_id).strip():
2636
+ raise ValidationException("group_id cannot be null or empty")
2637
+
2638
+ response = self._make_request(
2639
+ "PUT",
2640
+ "/pdf/path-group/clipping/clear",
2641
+ data={"pageNumber": page_number, "groupId": str(group_id).strip()},
2642
+ )
2643
+ result = bool(response.json())
2644
+
2645
+ if result:
2646
+ self._invalidate_snapshots()
2647
+
2648
+ return result
2649
+
2577
2650
  def new_paragraph(self) -> ParagraphBuilder:
2578
2651
  return ParagraphBuilder(self)
2579
2652
 
@@ -65,6 +65,10 @@ class PDFObjectBase:
65
65
  Position.at_page_coordinates(self.position.page_number, x, y),
66
66
  )
67
67
 
68
+ def clear_clipping(self) -> bool:
69
+ """Detach any active clipping path from this object."""
70
+ return self._client.clear_clipping(self.object_ref())
71
+
68
72
  def redact(self, replacement: str = "[REDACTED]") -> bool:
69
73
  """Redact this object from the PDF document."""
70
74
  from .models import RedactTarget
@@ -329,21 +333,22 @@ class PathGroupObject:
329
333
  return True
330
334
 
331
335
  def rotate(self, degrees: float) -> bool:
332
- self._client._rotate_path_group(
333
- self._page_index, self.group_id, degrees
334
- )
336
+ self._client._rotate_path_group(self._page_index, self.group_id, degrees)
335
337
  return True
336
338
 
337
339
  def resize(self, width: float, height: float) -> bool:
338
- self._client._resize_path_group(
339
- self._page_index, self.group_id, width, height
340
- )
340
+ self._client._resize_path_group(self._page_index, self.group_id, width, height)
341
341
  return True
342
342
 
343
343
  def remove(self) -> bool:
344
344
  self._client._remove_path_group(self._page_index, self.group_id)
345
345
  return True
346
346
 
347
+ def clear_clipping(self) -> bool:
348
+ return self._client.clear_path_group_clipping(
349
+ self._page_index + 1, self.group_id
350
+ )
351
+
347
352
  def __repr__(self):
348
353
  return f"PathGroupObject(group_id={self.group_id!r}, path_count={self.path_count}, page_index={self._page_index})"
349
354
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.3.11
3
+ Version: 0.3.12
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -359,7 +359,7 @@ with PDFDancer.open("contract.pdf") as pdf:
359
359
  ```
360
360
 
361
361
  Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
362
- with helpers such as `delete()`, `move_to(x, y)`, `redact()`, or `edit()` depending on the object type.
362
+ with helpers such as `delete()`, `move_to(x, y)`, `clear_clipping()`, `redact()`, or `edit()` depending on the object type.
363
363
 
364
364
  **Singular selection methods** return the first match (or `None`) for convenience:
365
365
 
@@ -1,5 +1,7 @@
1
+ .api-url
1
2
  .flake8
2
3
  .gitignore
4
+ .gitmodules
3
5
  CLAUDE.md
4
6
  LICENSE
5
7
  NOTICE
@@ -9,14 +11,13 @@ check.py
9
11
  pyproject.toml
10
12
  release.py
11
13
  test.sh
12
- update-api-spec.sh
13
14
  .claude/commands/discuss.md
14
15
  .claude/commands/implement-new-api-features.md
15
16
  .github/workflows/ci.yml
16
17
  .github/workflows/daily-tests.yml
17
18
  .github/workflows/release.yml
18
- docs/api-schemas/v0.yml
19
- docs/api-schemas/v1.yml
19
+ docs/capabilities/.gitkeep
20
+ docs/capabilities/CLEAR_CLIPPING.md
20
21
  media/logo-orange-512h.webp
21
22
  media/logo-orange-60h.webp
22
23
  media/logo-silver-512h.webp
@@ -51,6 +52,7 @@ tests/e2e/__init__.py
51
52
  tests/e2e/pdf_assertions.py
52
53
  tests/e2e/test_acroform.py
53
54
  tests/e2e/test_bezier_builder.py
55
+ tests/e2e/test_clipping.py
54
56
  tests/e2e/test_context_manager.py
55
57
  tests/e2e/test_form_x_objects.py
56
58
  tests/e2e/test_image.py
@@ -74,11 +76,14 @@ tests/e2e/test_snapshot.py
74
76
  tests/e2e/test_template_replace.py
75
77
  tests/e2e/test_template_replace_linebreak.py
76
78
  tests/e2e/test_text_line_edit.py
79
+ tests/fixtures/Asimovian-Regular.ttf
77
80
  tests/fixtures/DancingScript-Regular.ttf
78
81
  tests/fixtures/Empty.pdf
79
82
  tests/fixtures/JetBrainsMono-Regular.ttf
83
+ tests/fixtures/Roboto-Regular.ttf
80
84
  tests/fixtures/Showcase.pdf
81
85
  tests/fixtures/basic-paths.pdf
82
86
  tests/fixtures/form-xobject-example.pdf
87
+ tests/fixtures/invisible-content-clipping-test.pdf
83
88
  tests/fixtures/logo-80.png
84
89
  tests/fixtures/mixed-form-types.pdf