pdfdancer-client-python 0.3.11__tar.gz → 0.3.13__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.11 → pdfdancer_client_python-0.3.13}/.github/workflows/ci.yml +74 -8
  2. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/.github/workflows/daily-tests.yml +2 -0
  3. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/.github/workflows/release.yml +6 -0
  4. pdfdancer_client_python-0.3.13/.github/workflows/sdk-backward-compat.yml +88 -0
  5. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/.gitignore +2 -0
  6. pdfdancer_client_python-0.3.13/.gitmodules +3 -0
  7. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/PKG-INFO +2 -2
  8. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/README.md +1 -1
  9. pdfdancer_client_python-0.3.13/docs/capabilities/.gitkeep +0 -0
  10. pdfdancer_client_python-0.3.13/docs/capabilities/CLEAR_CLIPPING.md +61 -0
  11. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/pyproject.toml +1 -1
  12. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/__init__.py +5 -1
  13. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/models.py +82 -0
  14. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/pdfdancer_v1.py +168 -23
  15. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/types.py +103 -7
  16. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/PKG-INFO +2 -2
  17. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/SOURCES.txt +8 -3
  18. pdfdancer_client_python-0.3.13/tests/e2e/pdf_assertions.py +1783 -0
  19. pdfdancer_client_python-0.3.13/tests/e2e/test_clipping.py +323 -0
  20. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_context_manager.py +6 -0
  21. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_paragraph.py +15 -3
  22. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path.py +48 -1
  23. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_redact.py +1 -0
  24. pdfdancer_client_python-0.3.13/tests/fixtures/Asimovian-Regular.ttf +0 -0
  25. pdfdancer_client_python-0.3.13/tests/fixtures/Roboto-Regular.ttf +0 -0
  26. pdfdancer_client_python-0.3.13/tests/fixtures/invisible-content-clipping-test.pdf +0 -0
  27. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_pdf_object_equality.py +19 -10
  28. pdfdancer_client_python-0.3.11/docs/api-schemas/v0.yml +0 -5300
  29. pdfdancer_client_python-0.3.11/docs/api-schemas/v1.yml +0 -5387
  30. pdfdancer_client_python-0.3.11/tests/e2e/pdf_assertions.py +0 -842
  31. pdfdancer_client_python-0.3.11/update-api-spec.sh +0 -5
  32. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/.claude/commands/discuss.md +0 -0
  33. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/.claude/commands/implement-new-api-features.md +0 -0
  34. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/.flake8 +0 -0
  35. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/CLAUDE.md +0 -0
  36. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/LICENSE +0 -0
  37. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/NOTICE +0 -0
  38. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/TODO.md +0 -0
  39. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/check.py +0 -0
  40. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/media/logo-orange-512h.webp +0 -0
  41. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/media/logo-orange-60h.webp +0 -0
  42. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/media/logo-silver-512h.webp +0 -0
  43. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/media/logo-silver-60h.webp +0 -0
  44. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/release.py +0 -0
  45. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/setup.cfg +0 -0
  46. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/exceptions.py +0 -0
  47. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/fingerprint.py +0 -0
  48. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/image_builder.py +0 -0
  49. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/page_builder.py +0 -0
  50. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/paragraph_builder.py +0 -0
  51. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/path_builder.py +0 -0
  52. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer/text_line_builder.py +0 -0
  53. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  54. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  55. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  56. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/test.sh +0 -0
  57. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/__init__.py +0 -0
  58. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/conftest.py +0 -0
  59. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/__init__.py +0 -0
  60. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_acroform.py +0 -0
  61. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_bezier_builder.py +0 -0
  62. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_form_x_objects.py +0 -0
  63. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_image.py +0 -0
  64. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_image_transform.py +0 -0
  65. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_line.py +0 -0
  66. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_line_builder.py +0 -0
  67. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_new_pdf.py +0 -0
  68. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_page.py +0 -0
  69. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_builder.py +0 -0
  70. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_builder_rectangle.py +0 -0
  71. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_comprehensive.py +0 -0
  72. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_group.py +0 -0
  73. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_pdfdancer.py +0 -0
  74. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_positioning.py +0 -0
  75. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_rectangle_builder.py +0 -0
  76. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_singular_selection.py +0 -0
  77. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_snapshot.py +0 -0
  78. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_template_replace.py +0 -0
  79. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_template_replace_linebreak.py +0 -0
  80. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/e2e/test_text_line_edit.py +0 -0
  81. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  82. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/Empty.pdf +0 -0
  83. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  84. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/Showcase.pdf +0 -0
  85. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/basic-paths.pdf +0 -0
  86. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/form-xobject-example.pdf +0 -0
  87. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/logo-80.png +0 -0
  88. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/fixtures/mixed-form-types.pdf +0 -0
  89. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_anonymous_token.py +0 -0
  90. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_fingerprint.py +0 -0
  91. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_models.py +0 -0
  92. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_openapi_compliance.py +0 -0
  93. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_path_models.py +0 -0
  94. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_rate_limit.py +0 -0
  95. {pdfdancer_client_python-0.3.11 → pdfdancer_client_python-0.3.13}/tests/test_standard_fonts.py +0 -0
@@ -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:
@@ -12,6 +12,10 @@ jobs:
12
12
  # Quick minimal test on non-main branches
13
13
  if: github.ref != 'refs/heads/main'
14
14
  runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: read
17
+ issues: read
18
+ pull-requests: read
15
19
  strategy:
16
20
  fail-fast: false
17
21
  max-parallel: 2
@@ -20,6 +24,54 @@ jobs:
20
24
 
21
25
  steps:
22
26
  - uses: actions/checkout@v4
27
+ with:
28
+ submodules: true
29
+
30
+ - name: Resolve API base URL
31
+ id: resolve-api-base-url
32
+ uses: actions/github-script@v7
33
+ with:
34
+ github-token: ${{ secrets.PROPAGATE_TOKEN }}
35
+ script: |
36
+ const fallbackUrl = 'https://api-staging.pdfdancer.com';
37
+ const pr = context.payload.pull_request;
38
+ if (!pr) {
39
+ core.setOutput('base_url', fallbackUrl);
40
+ return;
41
+ }
42
+
43
+ const body = pr.body || '';
44
+ const apiPrMatch = body.match(/## Source API PR[\s\S]*?^- Repository:\s*MenschMachine\/pdfdancer-api\s*$[\s\S]*?^- PR:\s*#?(\d+)\s*$/im);
45
+ if (!apiPrMatch) {
46
+ core.info('No Source API PR block found in the PR body; using staging API.');
47
+ core.setOutput('base_url', fallbackUrl);
48
+ return;
49
+ }
50
+
51
+ const apiPrNumber = Number(apiPrMatch[1]);
52
+ const comments = await github.paginate(github.rest.issues.listComments, {
53
+ owner: 'MenschMachine',
54
+ repo: 'pdfdancer-api',
55
+ issue_number: apiPrNumber,
56
+ per_page: 100,
57
+ });
58
+ const previewComments = comments.filter((comment) =>
59
+ typeof comment.body === 'string' && comment.body.includes('### Preview Environment')
60
+ );
61
+ if (previewComments.length === 0) {
62
+ core.setFailed(`Source API PR #${apiPrNumber} is referenced by this PR, but no preview comment was found.`);
63
+ return;
64
+ }
65
+
66
+ const previewComment = previewComments[previewComments.length - 1];
67
+ const urlMatch = previewComment.body.match(/\*\*API URL:\*\*\s*(https?:\/\/\S+)/);
68
+ if (!urlMatch) {
69
+ core.setFailed(`Preview comment on source API PR #${apiPrNumber} does not contain a parseable API URL.`);
70
+ return;
71
+ }
72
+
73
+ core.info(`Using preview API URL from source API PR #${apiPrNumber}: ${urlMatch[1]}`);
74
+ core.setOutput('base_url', urlMatch[1]);
23
75
 
24
76
  - name: Set up Python ${{ matrix.python-version }}
25
77
  uses: actions/setup-python@v5
@@ -40,8 +92,8 @@ jobs:
40
92
 
41
93
  - name: Run tests
42
94
  run: |
43
- PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
44
- PDFDANCER_TOKEN=42 \
95
+ PDFDANCER_BASE_URL=${{ steps.resolve-api-base-url.outputs.base_url }} \
96
+ PDFDANCER_API_TOKEN=42 \
45
97
  venv/bin/python -m pytest tests/ -v --maxfail=3
46
98
 
47
99
  - name: Build distribution packages
@@ -55,6 +107,10 @@ jobs:
55
107
  # Full matrix only on main
56
108
  if: github.ref == 'refs/heads/main'
57
109
  runs-on: ${{ matrix.os }}
110
+ permissions:
111
+ contents: read
112
+ issues: read
113
+ pull-requests: read
58
114
  strategy:
59
115
  fail-fast: false
60
116
  max-parallel: 2
@@ -64,6 +120,16 @@ jobs:
64
120
 
65
121
  steps:
66
122
  - uses: actions/checkout@v4
123
+ with:
124
+ submodules: true
125
+
126
+ - name: Resolve API base URL
127
+ id: resolve-api-base-url
128
+ uses: actions/github-script@v7
129
+ with:
130
+ github-token: ${{ secrets.PROPAGATE_TOKEN }}
131
+ script: |
132
+ core.setOutput('base_url', 'https://api-staging.pdfdancer.com');
67
133
 
68
134
  - name: Set up Python ${{ matrix.python-version }}
69
135
  uses: actions/setup-python@v5
@@ -88,8 +154,8 @@ jobs:
88
154
  - name: Run tests (Unix)
89
155
  if: runner.os != 'Windows'
90
156
  run: |
91
- PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
92
- PDFDANCER_TOKEN=42 \
157
+ PDFDANCER_BASE_URL=${{ steps.resolve-api-base-url.outputs.base_url }} \
158
+ PDFDANCER_API_TOKEN=42 \
93
159
  venv/bin/python -m pytest tests/ -v --maxfail=3
94
160
 
95
161
  - name: Build & Validate (Unix)
@@ -119,8 +185,8 @@ jobs:
119
185
  if: runner.os == 'Windows'
120
186
  shell: cmd
121
187
  run: |
122
- set PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com
123
- set PDFDANCER_TOKEN=42
188
+ set PDFDANCER_BASE_URL=${{ steps.resolve-api-base-url.outputs.base_url }}
189
+ set PDFDANCER_API_TOKEN=42
124
190
  venv\Scripts\python -m pytest tests/ -v --maxfail=3
125
191
 
126
192
  - name: Build & Validate (Windows)
@@ -128,4 +194,4 @@ jobs:
128
194
  shell: cmd
129
195
  run: |
130
196
  venv\Scripts\python -m build
131
- venv\Scripts\python -m twine check dist/*
197
+ venv\Scripts\python -m twine check dist/*
@@ -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
@@ -0,0 +1,88 @@
1
+ name: SDK Backward Compatibility Tests
2
+
3
+ on:
4
+ repository_dispatch:
5
+ types: [api-preview-deployed]
6
+
7
+ workflow_dispatch:
8
+ inputs:
9
+ api_url:
10
+ description: 'API base URL to test against'
11
+ required: true
12
+ type: string
13
+ sdk_token:
14
+ description: 'API token for testing'
15
+ required: true
16
+ type: string
17
+ default: '42'
18
+
19
+ jobs:
20
+ get-tags:
21
+ runs-on: ubuntu-latest
22
+ outputs:
23
+ tags: ${{ steps.tags.outputs.tags }}
24
+ steps:
25
+ - name: Fetch tags
26
+ id: tags
27
+ run: |
28
+ TAGS=$(git ls-remote --tags https://github.com/MenschMachine/pdfdancer-client-python.git \
29
+ | grep -v '\^{}' \
30
+ | awk -F'/' '{print $NF}' \
31
+ | grep '^v' \
32
+ | sort -V \
33
+ | tail -3 \
34
+ | jq -R -s -c 'split("\n") | map(select(length > 0))')
35
+ echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
36
+ echo "Last 3 SDK tags: $TAGS"
37
+
38
+ test-compat:
39
+ needs: get-tags
40
+ runs-on: ubuntu-latest
41
+ timeout-minutes: 60
42
+ strategy:
43
+ fail-fast: false
44
+ max-parallel: 1
45
+ matrix:
46
+ tag: ${{ fromJSON(needs.get-tags.outputs.tags) }}
47
+
48
+ steps:
49
+ - name: Checkout SDK at ${{ matrix.tag }}
50
+ uses: actions/checkout@v4
51
+ with:
52
+ repository: MenschMachine/pdfdancer-client-python
53
+ ref: ${{ matrix.tag }}
54
+ path: sdk
55
+
56
+ - name: Set up Python
57
+ uses: actions/setup-python@v5
58
+ with:
59
+ python-version: '3.12'
60
+
61
+ - name: Create virtual environment
62
+ run: python -m venv venv
63
+
64
+ - name: Install SDK at ${{ matrix.tag }}
65
+ run: |
66
+ venv/bin/pip install --upgrade pip
67
+ venv/bin/pip install -e "./sdk[dev]"
68
+
69
+ - name: Run e2e tests
70
+ working-directory: sdk
71
+ run: |
72
+ PDFDANCER_BASE_URL=${{ github.event.client_payload.api_url || inputs.api_url }} \
73
+ PDFDANCER_API_TOKEN=${{ github.event.client_payload.sdk_token || inputs.sdk_token }} \
74
+ ../venv/bin/python -m pytest -v -x \
75
+ -k "not test_redact_multiple_paths and not test_context_manager_vs_manual_apply" \
76
+ --junitxml=test-results.xml
77
+ env:
78
+ PYTHONWARNINGS: "ignore:Unverified HTTPS request"
79
+
80
+ - name: Upload test results
81
+ if: failure()
82
+ uses: actions/upload-artifact@v4
83
+ with:
84
+ name: test-results-${{ matrix.tag }}
85
+ path: |
86
+ sdk/test-results.xml
87
+ sdk/.pytest_cache/
88
+ retention-days: 7
@@ -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.13
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.13"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -28,6 +28,7 @@ from .models import (
28
28
  ImageTransformRequest,
29
29
  ImageTransformType,
30
30
  Line,
31
+ ModifyPathRequest,
31
32
  ObjectRef,
32
33
  ObjectType,
33
34
  Orientation,
@@ -36,6 +37,7 @@ from .models import (
36
37
  Paragraph,
37
38
  Path,
38
39
  PathGroupInfo,
40
+ PathObjectRef,
39
41
  PathSegment,
40
42
  Point,
41
43
  Position,
@@ -55,7 +57,7 @@ from .paragraph_builder import ParagraphBuilder
55
57
  from .path_builder import BezierBuilder, LineBuilder, PathBuilder
56
58
  from .text_line_builder import TextLineBuilder
57
59
 
58
- __version__ = "0.3.11"
60
+ __version__ = "0.3.13"
59
61
  __all__ = [
60
62
  "PDFDancer",
61
63
  "ParagraphBuilder",
@@ -65,6 +67,8 @@ __all__ = [
65
67
  "LineBuilder",
66
68
  "BezierBuilder",
67
69
  "ObjectRef",
70
+ "PathObjectRef",
71
+ "ModifyPathRequest",
68
72
  "Position",
69
73
  "ObjectType",
70
74
  "Font",
@@ -1895,3 +1895,85 @@ class ImageTransformRequest:
1895
1895
  result["fillColor"] = self.fill_color
1896
1896
 
1897
1897
  return result
1898
+
1899
+
1900
+ @dataclass
1901
+ class ModifyPathRequest:
1902
+ """Request to modify path stroke and fill colors.
1903
+
1904
+ Parameters:
1905
+ - object_ref: Reference to the path to modify.
1906
+ - stroke_color: New stroke color (optional - null means don't change).
1907
+ - fill_color: New fill color (optional - null means don't change).
1908
+
1909
+ Example:
1910
+ ```python
1911
+ req = ModifyPathRequest(object_ref=path_ref, stroke_color=Color(255, 0, 0), fill_color=None)
1912
+ payload = req.to_dict()
1913
+ ```
1914
+ """
1915
+
1916
+ object_ref: ObjectRef
1917
+ stroke_color: Optional[Color] = None
1918
+ fill_color: Optional[Color] = None
1919
+
1920
+ def to_dict(self) -> dict:
1921
+ """Convert to dictionary for JSON serialization."""
1922
+ result: Dict[str, Any] = {
1923
+ "ref": self.object_ref.to_dict(),
1924
+ }
1925
+
1926
+ if self.stroke_color is not None:
1927
+ result["strokeColor"] = {
1928
+ "red": self.stroke_color.r,
1929
+ "green": self.stroke_color.g,
1930
+ "blue": self.stroke_color.b,
1931
+ "alpha": self.stroke_color.a,
1932
+ }
1933
+
1934
+ if self.fill_color is not None:
1935
+ result["fillColor"] = {
1936
+ "red": self.fill_color.r,
1937
+ "green": self.fill_color.g,
1938
+ "blue": self.fill_color.b,
1939
+ "alpha": self.fill_color.a,
1940
+ }
1941
+
1942
+ return result
1943
+
1944
+
1945
+ class PathObjectRef(ObjectRef):
1946
+ """
1947
+ Reference to a path object with stroke and fill color information.
1948
+
1949
+ Parameters (typically provided by the server):
1950
+ - internal_id: Identifier of the path object.
1951
+ - position: Position of the path.
1952
+ - object_type: Should be ObjectType.PATH.
1953
+ - stroke_color: Stroke/outline color of the path (optional).
1954
+ - fill_color: Fill color of the path (optional).
1955
+
1956
+ Usage:
1957
+ - Returned by find/snapshot APIs when querying paths.
1958
+ - Pass to ModifyPathRequest to update path colors.
1959
+ """
1960
+
1961
+ def __init__(
1962
+ self,
1963
+ internal_id: str,
1964
+ position: Position,
1965
+ object_type: ObjectType,
1966
+ stroke_color: Optional[Color] = None,
1967
+ fill_color: Optional[Color] = None,
1968
+ ):
1969
+ super().__init__(internal_id, position, object_type)
1970
+ self.stroke_color = stroke_color
1971
+ self.fill_color = fill_color
1972
+
1973
+ def get_stroke_color(self) -> Optional[Color]:
1974
+ """Get the stroke/outline color of the path."""
1975
+ return self.stroke_color
1976
+
1977
+ def get_fill_color(self) -> Optional[Color]:
1978
+ """Get the fill color of the path."""
1979
+ return self.fill_color