pdfdancer-client-python 3.0.1__tar.gz → 3.0.2__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 (43) hide show
  1. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/PKG-INFO +19 -5
  2. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/README.md +18 -4
  3. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/__init__.py +20 -0
  4. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/_version.py +2 -2
  5. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/models.py +113 -0
  6. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/pdfdancer_v2.py +88 -0
  7. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer_client_python.egg-info/PKG-INFO +19 -5
  8. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.autosnap.toml +0 -0
  9. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.flake8 +0 -0
  10. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.github/dependabot.yml +0 -0
  11. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.github/workflows/ci.yml +0 -0
  12. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.github/workflows/daily-tests.yml +0 -0
  13. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.github/workflows/release.yml +0 -0
  14. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.github/workflows/sdk-backward-compat.yml +0 -0
  15. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.gitignore +0 -0
  16. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/.gitmodules +0 -0
  17. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/LICENSE +0 -0
  18. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/MANIFEST.in +0 -0
  19. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/Makefile +0 -0
  20. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/NOTICE +0 -0
  21. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/TODO.md +0 -0
  22. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/check.py +0 -0
  23. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/docs/capabilities/.gitkeep +0 -0
  24. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/docs/capabilities/CLEAR_CLIPPING.md +0 -0
  25. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/media/logo-orange-512h.webp +0 -0
  26. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/media/logo-orange-60h.webp +0 -0
  27. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/media/logo-silver-512h.webp +0 -0
  28. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/media/logo-silver-60h.webp +0 -0
  29. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/pyproject.toml +0 -0
  30. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/setup.cfg +0 -0
  31. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/_runtime_version.py +0 -0
  32. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/exceptions.py +0 -0
  33. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/fingerprint.py +0 -0
  34. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/image_builder.py +0 -0
  35. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/page_builder.py +0 -0
  36. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/path_builder.py +0 -0
  37. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/text_editing.py +0 -0
  38. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer/types.py +0 -0
  39. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
  40. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  41. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  42. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  43. {pdfdancer_client_python-3.0.1 → pdfdancer_client_python-3.0.2}/test.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License-Expression: Apache-2.0
@@ -36,7 +36,7 @@ Dynamic: license-file
36
36
 
37
37
  # PDFDancer Python Client
38
38
 
39
- This README documents `pdfdancer-client-python` version `3.0.0`.
39
+ This README documents `pdfdancer-client-python` version `3.0.2`.
40
40
 
41
41
  ![PDFDancer logo](media/logo-silver-60h.webp)
42
42
 
@@ -69,7 +69,7 @@ pixel-perfect control from Python. The same API is also available for TypeScript
69
69
  ## Installation
70
70
 
71
71
  ```bash
72
- pip install pdfdancer-client-python==3.0.0
72
+ pip install pdfdancer-client-python==3.0.2
73
73
 
74
74
  # Editable install for local development
75
75
  pip install -e .
@@ -139,6 +139,20 @@ snapshot = first_page.get_snapshot()
139
139
 
140
140
  Page-scoped selectors, text editing, and builders automatically restrict the operation to that page.
141
141
 
142
+ ## Reading Units
143
+
144
+ Reading-unit analysis returns semantically classified text blocks with reading order, source provenance, bounds, and
145
+ relationships. Each call analyzes the current session state and makes a fresh request.
146
+
147
+ ```python
148
+ document_analysis = pdf.analyze_reading_units()
149
+ page_analysis = pdf.page(1).analyze_reading_units()
150
+
151
+ for page in document_analysis.pages:
152
+ for unit in page.units:
153
+ print(unit.role, unit.text)
154
+ ```
155
+
142
156
  ## Selection
143
157
 
144
158
  Document- and page-scoped selectors return typed objects for images, paths, form XObjects, and form fields. Position
@@ -412,8 +426,8 @@ Releases are published automatically to PyPI when a `v*` tag is pushed to GitHub
412
426
 
413
427
  ```bash
414
428
  # Create and push a release tag — GitHub Actions handles the rest
415
- git tag v2.0.0
416
- git push origin v2.0.0
429
+ git tag v3.0.2
430
+ git push origin v3.0.2
417
431
  ```
418
432
 
419
433
  #### Code Quality
@@ -1,6 +1,6 @@
1
1
  # PDFDancer Python Client
2
2
 
3
- This README documents `pdfdancer-client-python` version `3.0.0`.
3
+ This README documents `pdfdancer-client-python` version `3.0.2`.
4
4
 
5
5
  ![PDFDancer logo](media/logo-silver-60h.webp)
6
6
 
@@ -33,7 +33,7 @@ pixel-perfect control from Python. The same API is also available for TypeScript
33
33
  ## Installation
34
34
 
35
35
  ```bash
36
- pip install pdfdancer-client-python==3.0.0
36
+ pip install pdfdancer-client-python==3.0.2
37
37
 
38
38
  # Editable install for local development
39
39
  pip install -e .
@@ -103,6 +103,20 @@ snapshot = first_page.get_snapshot()
103
103
 
104
104
  Page-scoped selectors, text editing, and builders automatically restrict the operation to that page.
105
105
 
106
+ ## Reading Units
107
+
108
+ Reading-unit analysis returns semantically classified text blocks with reading order, source provenance, bounds, and
109
+ relationships. Each call analyzes the current session state and makes a fresh request.
110
+
111
+ ```python
112
+ document_analysis = pdf.analyze_reading_units()
113
+ page_analysis = pdf.page(1).analyze_reading_units()
114
+
115
+ for page in document_analysis.pages:
116
+ for unit in page.units:
117
+ print(unit.role, unit.text)
118
+ ```
119
+
106
120
  ## Selection
107
121
 
108
122
  Document- and page-scoped selectors return typed objects for images, paths, form XObjects, and form fields. Position
@@ -376,8 +390,8 @@ Releases are published automatically to PyPI when a `v*` tag is pushed to GitHub
376
390
 
377
391
  ```bash
378
392
  # Create and push a release tag — GitHub Actions handles the rest
379
- git tag v2.0.0
380
- git push origin v2.0.0
393
+ git tag v3.0.2
394
+ git push origin v3.0.2
381
395
  ```
382
396
 
383
397
  #### Code Quality
@@ -42,6 +42,16 @@ from .models import (
42
42
  Point,
43
43
  Position,
44
44
  PositionMode,
45
+ ReadingUnit,
46
+ ReadingUnitBounds,
47
+ ReadingUnitDocumentAnalysis,
48
+ ReadingUnitMode,
49
+ ReadingUnitPageAnalysis,
50
+ ReadingUnitProvenance,
51
+ ReadingUnitRelationship,
52
+ ReadingUnitRelationshipType,
53
+ ReadingUnitRole,
54
+ ReadingUnitStreamMembership,
45
55
  ShapeType,
46
56
  Size,
47
57
  StandardFonts,
@@ -116,6 +126,16 @@ __all__ = [
116
126
  "Point",
117
127
  "StandardFonts",
118
128
  "PageSize",
129
+ "ReadingUnit",
130
+ "ReadingUnitBounds",
131
+ "ReadingUnitDocumentAnalysis",
132
+ "ReadingUnitMode",
133
+ "ReadingUnitPageAnalysis",
134
+ "ReadingUnitProvenance",
135
+ "ReadingUnitRelationship",
136
+ "ReadingUnitRelationshipType",
137
+ "ReadingUnitRole",
138
+ "ReadingUnitStreamMembership",
119
139
  "Orientation",
120
140
  "FontRecommendation",
121
141
  "FontType",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.0.1'
22
- __version_tuple__ = version_tuple = (3, 0, 1)
21
+ __version__ = version = '3.0.2'
22
+ __version_tuple__ = version_tuple = (3, 0, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -268,6 +268,119 @@ class ShapeType(Enum):
268
268
  RECT = "RECT" # Rectangular area with width and height
269
269
 
270
270
 
271
+ class ReadingUnitRole(Enum):
272
+ HEADING = "HEADING"
273
+ PARAGRAPH = "PARAGRAPH"
274
+ LIST = "LIST"
275
+ TABLE = "TABLE"
276
+ CAPTION = "CAPTION"
277
+ CALLOUT = "CALLOUT"
278
+ PREFORMATTED_TEXT = "PREFORMATTED_TEXT"
279
+ PAGE_HEADER = "PAGE_HEADER"
280
+ PAGE_FOOTER = "PAGE_FOOTER"
281
+ PAGE_NUMBER = "PAGE_NUMBER"
282
+ FOOTNOTE = "FOOTNOTE"
283
+ WATERMARK = "WATERMARK"
284
+ UNKNOWN = "UNKNOWN"
285
+
286
+ @classmethod
287
+ def from_value(cls, value: Any) -> tuple["ReadingUnitRole", str]:
288
+ raw = value if isinstance(value, str) else "UNKNOWN"
289
+ try:
290
+ return cls(raw), raw
291
+ except ValueError:
292
+ return cls.UNKNOWN, raw
293
+
294
+
295
+ class ReadingUnitRelationshipType(Enum):
296
+ CAPTION_FOR = "CAPTION_FOR"
297
+ HEADING_PARENT_OF = "HEADING_PARENT_OF"
298
+ UNKNOWN = "UNKNOWN"
299
+
300
+ @classmethod
301
+ def from_value(cls, value: Any) -> tuple["ReadingUnitRelationshipType", str]:
302
+ raw = value if isinstance(value, str) else "UNKNOWN"
303
+ try:
304
+ return cls(raw), raw
305
+ except ValueError:
306
+ return cls.UNKNOWN, raw
307
+
308
+
309
+ class ReadingUnitMode(Enum):
310
+ PRIMARY = "PRIMARY"
311
+ UNKNOWN = "UNKNOWN"
312
+
313
+ @classmethod
314
+ def from_value(cls, value: Any) -> tuple["ReadingUnitMode", str]:
315
+ raw = value if isinstance(value, str) else "UNKNOWN"
316
+ try:
317
+ return cls(raw), raw
318
+ except ValueError:
319
+ return cls.UNKNOWN, raw
320
+
321
+
322
+ @dataclass(frozen=True)
323
+ class ReadingUnitBounds:
324
+ x: float
325
+ y: float
326
+ width: float
327
+ height: float
328
+
329
+
330
+ @dataclass(frozen=True)
331
+ class ReadingUnitProvenance:
332
+ page_number: int
333
+ source_element_ids: List[str]
334
+ bounds: ReadingUnitBounds
335
+
336
+
337
+ @dataclass(frozen=True)
338
+ class ReadingUnitStreamMembership:
339
+ included: bool
340
+ order: Optional[int]
341
+
342
+
343
+ @dataclass(frozen=True)
344
+ class ReadingUnitRelationship:
345
+ type: ReadingUnitRelationshipType
346
+ raw_type: str
347
+ target_unit_id: str
348
+
349
+ @classmethod
350
+ def from_dict(cls, data: Mapping[str, Any]) -> "ReadingUnitRelationship":
351
+ relationship_type, raw_type = ReadingUnitRelationshipType.from_value(
352
+ data.get("type")
353
+ )
354
+ return cls(relationship_type, raw_type, str(data.get("targetUnitId", "")))
355
+
356
+
357
+ @dataclass(frozen=True)
358
+ class ReadingUnit:
359
+ id: str
360
+ role: ReadingUnitRole
361
+ raw_role: str
362
+ text: str
363
+ stream: Mapping[str, ReadingUnitStreamMembership]
364
+ provenance: ReadingUnitProvenance
365
+ relationships: List[ReadingUnitRelationship]
366
+
367
+
368
+ @dataclass(frozen=True)
369
+ class ReadingUnitPageAnalysis:
370
+ page_number: int
371
+ mode: ReadingUnitMode
372
+ raw_mode: str
373
+ units: List[ReadingUnit]
374
+
375
+
376
+ @dataclass(frozen=True)
377
+ class ReadingUnitDocumentAnalysis:
378
+ page_count: int
379
+ mode: ReadingUnitMode
380
+ raw_mode: str
381
+ pages: List[ReadingUnitPageAnalysis]
382
+
383
+
271
384
  @dataclass
272
385
  class Point:
273
386
  """Represents a 2D point with x and y coordinates."""
@@ -74,6 +74,15 @@ from .models import (
74
74
  PathObjectRef,
75
75
  Position,
76
76
  PositionMode,
77
+ ReadingUnit,
78
+ ReadingUnitBounds,
79
+ ReadingUnitDocumentAnalysis,
80
+ ReadingUnitMode,
81
+ ReadingUnitPageAnalysis,
82
+ ReadingUnitProvenance,
83
+ ReadingUnitRelationship,
84
+ ReadingUnitRole,
85
+ ReadingUnitStreamMembership,
77
86
  ShapeType,
78
87
  TextObjectRef,
79
88
  )
@@ -721,6 +730,11 @@ class PageClient:
721
730
  def get_snapshot(self, types: Optional[str] = None) -> PageSnapshot:
722
731
  return self.root.get_page_snapshot(self.page_number, types)
723
732
 
733
+ def analyze_reading_units(self) -> ReadingUnitPageAnalysis:
734
+ return cast(
735
+ ReadingUnitPageAnalysis, self.root.analyze_reading_units(self.page_number)
736
+ )
737
+
724
738
  @property
725
739
  def size(self) -> Optional[PageSize]:
726
740
  """Property alias for page size."""
@@ -2480,6 +2494,22 @@ class PDFDancer:
2480
2494
 
2481
2495
  return self._parse_document_snapshot(data)
2482
2496
 
2497
+ def analyze_reading_units(
2498
+ self, page_number: Optional[int] = None
2499
+ ) -> Union[ReadingUnitDocumentAnalysis, ReadingUnitPageAnalysis]:
2500
+ """Analyze the current session PDF, or one one-based page, into reading units."""
2501
+ if page_number is not None:
2502
+ if page_number < 1:
2503
+ raise ValidationException(
2504
+ f"Page number must be >= 1 (1-based indexing), got {page_number}"
2505
+ )
2506
+ response = self._make_request(
2507
+ "GET", f"/pdf/page/{page_number}/reading-units"
2508
+ )
2509
+ return self._parse_reading_unit_page_analysis(response.json())
2510
+ response = self._make_request("GET", "/pdf/document/reading-units")
2511
+ return self._parse_reading_unit_document_analysis(response.json())
2512
+
2483
2513
  def get_page_snapshot(
2484
2514
  self, page_number: int, types: Optional[str] = None
2485
2515
  ) -> PageSnapshot:
@@ -3129,6 +3159,64 @@ class PDFDancer:
3129
3159
 
3130
3160
  return DocumentSnapshot(page_count=page_count, fonts=fonts, pages=pages)
3131
3161
 
3162
+ def _parse_reading_unit_page_analysis(
3163
+ self, data: Mapping[str, Any]
3164
+ ) -> ReadingUnitPageAnalysis:
3165
+ mode, raw_mode = ReadingUnitMode.from_value(data.get("mode"))
3166
+ return ReadingUnitPageAnalysis(
3167
+ page_number=int(data.get("pageNumber", 0)),
3168
+ mode=mode,
3169
+ raw_mode=raw_mode,
3170
+ units=[self._parse_reading_unit(unit) for unit in data.get("units", [])],
3171
+ )
3172
+
3173
+ def _parse_reading_unit_document_analysis(
3174
+ self, data: Mapping[str, Any]
3175
+ ) -> ReadingUnitDocumentAnalysis:
3176
+ mode, raw_mode = ReadingUnitMode.from_value(data.get("mode"))
3177
+ return ReadingUnitDocumentAnalysis(
3178
+ page_count=int(data.get("pageCount", 0)),
3179
+ mode=mode,
3180
+ raw_mode=raw_mode,
3181
+ pages=[
3182
+ self._parse_reading_unit_page_analysis(page)
3183
+ for page in data.get("pages", [])
3184
+ ],
3185
+ )
3186
+
3187
+ def _parse_reading_unit(self, data: Mapping[str, Any]) -> ReadingUnit:
3188
+ role, raw_role = ReadingUnitRole.from_value(data.get("role"))
3189
+ provenance_data = data.get("provenance") or {}
3190
+ bounds_data = provenance_data.get("bounds") or {}
3191
+ stream = {
3192
+ str(mode): ReadingUnitStreamMembership(
3193
+ bool(membership.get("included", False)), membership.get("order")
3194
+ )
3195
+ for mode, membership in (data.get("stream") or {}).items()
3196
+ }
3197
+ provenance = ReadingUnitProvenance(
3198
+ page_number=int(provenance_data.get("pageNumber", 0)),
3199
+ source_element_ids=list(provenance_data.get("sourceElementIds") or []),
3200
+ bounds=ReadingUnitBounds(
3201
+ float(bounds_data.get("x", 0)),
3202
+ float(bounds_data.get("y", 0)),
3203
+ float(bounds_data.get("width", 0)),
3204
+ float(bounds_data.get("height", 0)),
3205
+ ),
3206
+ )
3207
+ return ReadingUnit(
3208
+ id=str(data.get("id", "")),
3209
+ role=role,
3210
+ raw_role=raw_role,
3211
+ text=str(data.get("text", "")),
3212
+ stream=stream,
3213
+ provenance=provenance,
3214
+ relationships=[
3215
+ ReadingUnitRelationship.from_dict(item)
3216
+ for item in data.get("relationships", [])
3217
+ ],
3218
+ )
3219
+
3132
3220
  # Context Manager Support (Python enhancement)
3133
3221
  def __enter__(self) -> "PDFDancer":
3134
3222
  """Context manager entry."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License-Expression: Apache-2.0
@@ -36,7 +36,7 @@ Dynamic: license-file
36
36
 
37
37
  # PDFDancer Python Client
38
38
 
39
- This README documents `pdfdancer-client-python` version `3.0.0`.
39
+ This README documents `pdfdancer-client-python` version `3.0.2`.
40
40
 
41
41
  ![PDFDancer logo](media/logo-silver-60h.webp)
42
42
 
@@ -69,7 +69,7 @@ pixel-perfect control from Python. The same API is also available for TypeScript
69
69
  ## Installation
70
70
 
71
71
  ```bash
72
- pip install pdfdancer-client-python==3.0.0
72
+ pip install pdfdancer-client-python==3.0.2
73
73
 
74
74
  # Editable install for local development
75
75
  pip install -e .
@@ -139,6 +139,20 @@ snapshot = first_page.get_snapshot()
139
139
 
140
140
  Page-scoped selectors, text editing, and builders automatically restrict the operation to that page.
141
141
 
142
+ ## Reading Units
143
+
144
+ Reading-unit analysis returns semantically classified text blocks with reading order, source provenance, bounds, and
145
+ relationships. Each call analyzes the current session state and makes a fresh request.
146
+
147
+ ```python
148
+ document_analysis = pdf.analyze_reading_units()
149
+ page_analysis = pdf.page(1).analyze_reading_units()
150
+
151
+ for page in document_analysis.pages:
152
+ for unit in page.units:
153
+ print(unit.role, unit.text)
154
+ ```
155
+
142
156
  ## Selection
143
157
 
144
158
  Document- and page-scoped selectors return typed objects for images, paths, form XObjects, and form fields. Position
@@ -412,8 +426,8 @@ Releases are published automatically to PyPI when a `v*` tag is pushed to GitHub
412
426
 
413
427
  ```bash
414
428
  # Create and push a release tag — GitHub Actions handles the rest
415
- git tag v2.0.0
416
- git push origin v2.0.0
429
+ git tag v3.0.2
430
+ git push origin v3.0.2
417
431
  ```
418
432
 
419
433
  #### Code Quality