pdfdancer-client-python 0.2.28__tar.gz → 0.3.1__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 (76) hide show
  1. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/.github/workflows/ci.yml +4 -3
  2. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/.gitignore +1 -0
  3. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/CLAUDE.md +2 -2
  4. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/PKG-INFO +4 -4
  5. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/README.md +3 -3
  6. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/pyproject.toml +1 -1
  7. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/image_builder.py +3 -3
  8. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/models.py +23 -23
  9. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/page_builder.py +37 -11
  10. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/paragraph_builder.py +12 -12
  11. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/path_builder.py +17 -17
  12. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/pdfdancer_v1.py +127 -93
  13. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/text_line_builder.py +9 -9
  14. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/types.py +63 -34
  15. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer_client_python.egg-info/PKG-INFO +4 -4
  16. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/pdf_assertions.py +42 -42
  17. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_acroform.py +6 -6
  18. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_bezier_builder.py +21 -21
  19. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_context_manager.py +26 -26
  20. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_form_x_objects.py +2 -2
  21. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_image.py +10 -10
  22. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_line.py +9 -9
  23. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_line_builder.py +27 -27
  24. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_new_pdf.py +5 -5
  25. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_page.py +15 -16
  26. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_paragraph.py +57 -56
  27. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_path.py +6 -6
  28. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_path_builder.py +48 -48
  29. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_path_builder_rectangle.py +63 -63
  30. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_path_comprehensive.py +18 -18
  31. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_rectangle_builder.py +49 -49
  32. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_singular_selection.py +38 -38
  33. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_snapshot.py +37 -35
  34. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_text_line_edit.py +76 -104
  35. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_models.py +10 -10
  36. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_openapi_compliance.py +4 -4
  37. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_path_models.py +4 -4
  38. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_pdf_object_equality.py +23 -23
  39. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/.claude/commands/discuss.md +0 -0
  40. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/.flake8 +0 -0
  41. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/.github/workflows/daily-tests.yml +0 -0
  42. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/LICENSE +0 -0
  43. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/NOTICE +0 -0
  44. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/TODO.md +0 -0
  45. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/check.py +0 -0
  46. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/docs/openapi.yml +0 -0
  47. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/media/logo-orange-512h.webp +0 -0
  48. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/media/logo-orange-60h.webp +0 -0
  49. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/release.py +0 -0
  50. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/setup.cfg +0 -0
  51. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/__init__.py +0 -0
  52. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/exceptions.py +0 -0
  53. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer/fingerprint.py +0 -0
  54. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
  55. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  56. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  57. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  58. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/test.sh +0 -0
  59. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/__init__.py +0 -0
  60. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/conftest.py +0 -0
  61. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/__init__.py +0 -0
  62. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_pdfdancer.py +0 -0
  63. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/e2e/test_positioning.py +0 -0
  64. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  65. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/Empty.pdf +0 -0
  66. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  67. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/Showcase.pdf +0 -0
  68. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/basic-paths.pdf +0 -0
  69. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/form-xobject-example.pdf +0 -0
  70. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/logo-80.png +0 -0
  71. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/fixtures/mixed-form-types.pdf +0 -0
  72. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_anonymous_token.py +0 -0
  73. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_fingerprint.py +0 -0
  74. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_rate_limit.py +0 -0
  75. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/tests/test_standard_fonts.py +0 -0
  76. {pdfdancer_client_python-0.2.28 → pdfdancer_client_python-0.3.1}/update-api-spec.sh +0 -0
@@ -2,9 +2,9 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ main, staging, develop, development, dev ]
5
+ branches: [ '**' ]
6
6
  pull_request:
7
- branches: [ main, staging, develop, development, dev ]
7
+ branches: [ '**' ]
8
8
  workflow_dispatch:
9
9
 
10
10
  jobs:
@@ -14,7 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  strategy:
16
16
  fail-fast: false
17
- max-parallel: 4
17
+ max-parallel: 2
18
18
  matrix:
19
19
  python-version: [ '3.12' ]
20
20
 
@@ -57,6 +57,7 @@ jobs:
57
57
  runs-on: ${{ matrix.os }}
58
58
  strategy:
59
59
  fail-fast: false
60
+ max-parallel: 2
60
61
  matrix:
61
62
  os: [ ubuntu-latest, windows-latest ]
62
63
  python-version: [ '3.10', '3.11', '3.12', '3.13' ]
@@ -17,3 +17,4 @@ __pycache__/
17
17
  *.so
18
18
  .Python
19
19
  /.run/
20
+ /build
@@ -83,7 +83,7 @@ pdf.save("output.pdf")
83
83
  - **Dual initialization**: `PDFDancer.open()` for existing PDFs, `PDFDancer.new()` for blank PDFs
84
84
  - **Session-based operations**: All constructors create server session automatically
85
85
  - **Object-oriented API**: Selected objects (paragraphs, images, etc.) have methods like `.delete()`, `.move()`
86
- - **Page-level operations**: `pdf.page(index)` provides page-scoped selections and operations
86
+ - **Page-level operations**: `pdf.page(number)` provides page-scoped selections and operations
87
87
  - **Builder pattern**: `new_paragraph()` and `new_image()` for fluent construction
88
88
  - **Strict validation**: All validation matches Java client exactly
89
89
  - **Exception handling**: FontNotFoundException, ValidationException, HttpClientException, etc.
@@ -188,7 +188,7 @@ page.delete()
188
188
  ### API Design
189
189
 
190
190
  - **Use object-oriented patterns**: Selected objects should have methods (`.delete()`, `.move()`, etc.)
191
- - **Provide page-level operations**: `pdf.page(index)` for page-scoped selections
191
+ - **Provide page-level operations**: `pdf.page(number)` for page-scoped selections
192
192
  - **Support fluent builders**: `new_paragraph()` and `new_image()` return builder instances
193
193
  - **Use snake_case for methods**: `select_paragraphs()`, `select_images_at()`, etc.
194
194
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.2.28
3
+ Version: 0.3.1
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -251,7 +251,7 @@ pixel-perfect control from Python. The same API is also available for TypeScript
251
251
 
252
252
  ## Highlights
253
253
 
254
- - Locate paragraphs, text lines, images, vector paths, form fields, and pages by index, coordinates, or text prefixes.
254
+ - Locate paragraphs, text lines, images, vector paths, form fields, and pages by page number, coordinates, or text prefixes.
255
255
  - Edit existing content in place with fluent editors and context managers that apply changes safely.
256
256
  - Programmatically control third-party PDFs—modify invoices, contracts, and reports you did not author.
257
257
  - Add content with precise XY positioning using paragraph and image builders, custom fonts, and color helpers.
@@ -300,7 +300,7 @@ with PDFDancer.open(
300
300
  .font(StandardFonts.HELVETICA, 12) \
301
301
  .color(Color(70, 70, 70)) \
302
302
  .line_spacing(1.4) \
303
- .at(page_index=0, x=72, y=520) \
303
+ .at(page_number=0, x=72, y=520) \
304
304
  .add()
305
305
 
306
306
  # Persist the modified document
@@ -321,7 +321,7 @@ with PDFDancer.new(token="your-api-token") as pdf:
321
321
  .font(StandardFonts.TIMES_BOLD, 18) \
322
322
  .color(Color(10, 10, 80)) \
323
323
  .line_spacing(1.2) \
324
- .at(page_index=0, x=72, y=730) \
324
+ .at(page_number=0, x=72, y=730) \
325
325
  .add()
326
326
 
327
327
  pdf.new_image() \
@@ -12,7 +12,7 @@ pixel-perfect control from Python. The same API is also available for TypeScript
12
12
 
13
13
  ## Highlights
14
14
 
15
- - Locate paragraphs, text lines, images, vector paths, form fields, and pages by index, coordinates, or text prefixes.
15
+ - Locate paragraphs, text lines, images, vector paths, form fields, and pages by page number, coordinates, or text prefixes.
16
16
  - Edit existing content in place with fluent editors and context managers that apply changes safely.
17
17
  - Programmatically control third-party PDFs—modify invoices, contracts, and reports you did not author.
18
18
  - Add content with precise XY positioning using paragraph and image builders, custom fonts, and color helpers.
@@ -61,7 +61,7 @@ with PDFDancer.open(
61
61
  .font(StandardFonts.HELVETICA, 12) \
62
62
  .color(Color(70, 70, 70)) \
63
63
  .line_spacing(1.4) \
64
- .at(page_index=0, x=72, y=520) \
64
+ .at(page_number=0, x=72, y=520) \
65
65
  .add()
66
66
 
67
67
  # Persist the modified document
@@ -82,7 +82,7 @@ with PDFDancer.new(token="your-api-token") as pdf:
82
82
  .font(StandardFonts.TIMES_BOLD, 18) \
83
83
  .color(Color(10, 10, 80)) \
84
84
  .line_spacing(1.2) \
85
- .at(page_index=0, x=72, y=730) \
85
+ .at(page_number=0, x=72, y=730) \
86
86
  .add()
87
87
 
88
88
  pdf.new_image() \
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdfdancer-client-python"
7
- version = "0.2.28"
7
+ version = "0.3.1"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -39,7 +39,7 @@ class ImageBuilder:
39
39
 
40
40
  class ImageOnPageBuilder:
41
41
 
42
- def __init__(self, client: "PDFDancer", page_index: int):
42
+ def __init__(self, client: "PDFDancer", page_number: int):
43
43
  """
44
44
  Initialize the image builder with a client reference.
45
45
 
@@ -51,14 +51,14 @@ class ImageOnPageBuilder:
51
51
 
52
52
  self._client = client
53
53
  self._image = Image()
54
- self._page_index = page_index
54
+ self._page_number = page_number
55
55
 
56
56
  def from_file(self, img_path: Path) -> "ImageOnPageBuilder":
57
57
  self._image.data = img_path.read_bytes()
58
58
  return self
59
59
 
60
60
  def at(self, x, y) -> "ImageOnPageBuilder":
61
- self._image.position = Position.at_page_coordinates(self._page_index, x, y)
61
+ self._image.position = Position.at_page_coordinates(self._page_number, x, y)
62
62
  return self
63
63
 
64
64
  def add(self) -> bool:
@@ -262,7 +262,7 @@ class Position:
262
262
  Spatial locator used to find or place objects on a page.
263
263
 
264
264
  Parameters:
265
- - page_index: Zero-based page index this position refers to. Required for most operations
265
+ - page_number: One-based page number this position refers to. Required for most operations
266
266
  that place or search on a specific page; use `Position.at_page()` as a shortcut.
267
267
  - shape: Optional geometric shape used when matching by area (`POINT`, `LINE`, `CIRCLE`, `RECT`).
268
268
  - mode: How to match objects relative to the shape (`INTERSECT` or `CONTAINS`).
@@ -272,8 +272,8 @@ class Position:
272
272
  - name: Named anchor or element name to target (e.g. form field name).
273
273
 
274
274
  Builder helpers:
275
- - `Position.at_page(page_index)` – target a whole page.
276
- - `Position.at_page_coordinates(page_index, x, y)` – target a point on a page.
275
+ - `Position.at_page(page_number)` – target a whole page.
276
+ - `Position.at_page_coordinates(page_number, x, y)` – target a point on a page.
277
277
  - `Position.by_name(name)` – target object(s) by name.
278
278
  - `pos.at_coordinates(Point(x, y))` – switch to a point on the current page.
279
279
  - `pos.move_x(dx)`, `pos.move_y(dy)` – offset the current coordinates.
@@ -294,7 +294,7 @@ class Position:
294
294
  ```
295
295
  """
296
296
 
297
- page_index: Optional[int] = None
297
+ page_number: Optional[int] = None
298
298
  shape: Optional[ShapeType] = None
299
299
  mode: Optional[PositionMode] = None
300
300
  bounding_rect: Optional[BoundingRect] = None
@@ -303,18 +303,18 @@ class Position:
303
303
  name: Optional[str] = None
304
304
 
305
305
  @staticmethod
306
- def at_page(page_index: int) -> "Position":
306
+ def at_page(page_number: int) -> "Position":
307
307
  """
308
308
  Creates a position specification for an entire page.
309
309
  """
310
- return Position(page_index=page_index, mode=PositionMode.CONTAINS)
310
+ return Position(page_number=page_number, mode=PositionMode.CONTAINS)
311
311
 
312
312
  @staticmethod
313
- def at_page_coordinates(page_index: int, x: float, y: float) -> "Position":
313
+ def at_page_coordinates(page_number: int, x: float, y: float) -> "Position":
314
314
  """
315
315
  Creates a position specification for specific coordinates on a page.
316
316
  """
317
- position = Position.at_page(page_index)
317
+ position = Position.at_page(page_number)
318
318
  position.at_coordinates(Point(x, y))
319
319
  return position
320
320
 
@@ -823,7 +823,7 @@ class FindRequest:
823
823
  def _position_to_dict(position: Position) -> dict:
824
824
  """Convert Position to dictionary for JSON serialization."""
825
825
  result = {
826
- "pageIndex": position.page_index,
826
+ "pageNumber": position.page_number,
827
827
  "textStartsWith": position.text_starts_with,
828
828
  "textPattern": position.text_pattern,
829
829
  }
@@ -898,24 +898,24 @@ class PageMoveRequest:
898
898
  """Request to reorder pages.
899
899
 
900
900
  Parameters:
901
- - from_page_index: Zero-based index of the page to move.
902
- - to_page_index: Zero-based destination index.
901
+ - from_page: 1-based page number of the page to move.
902
+ - to_page: 1-based destination page number.
903
903
 
904
904
  Example:
905
905
  ```python
906
906
  # Move first page to the end
907
- req = PageMoveRequest(from_page_index=0, to_page_index=doc_page_count - 1)
907
+ req = PageMoveRequest(from_page=1, to_page=doc_page_count)
908
908
  payload = req.to_dict()
909
909
  ```
910
910
  """
911
911
 
912
- from_page_index: int
913
- to_page_index: int
912
+ from_page: int
913
+ to_page: int
914
914
 
915
915
  def to_dict(self) -> dict:
916
916
  return {
917
- "fromPageIndex": self.from_page_index,
918
- "toPageIndex": self.to_page_index,
917
+ "fromPage": self.from_page,
918
+ "toPage": self.to_page,
919
919
  }
920
920
 
921
921
 
@@ -924,7 +924,7 @@ class AddPageRequest:
924
924
  """Request to add a new page to the document.
925
925
 
926
926
  Parameters:
927
- - page_index: Optional zero-based index where the new page should be inserted.
927
+ - page_number: Optional 1-based page number where the new page should be inserted.
928
928
  - orientation: Optional page orientation (portrait or landscape).
929
929
  - page_size: Optional size of the page.
930
930
 
@@ -932,14 +932,14 @@ class AddPageRequest:
932
932
  with default server behavior.
933
933
  """
934
934
 
935
- page_index: Optional[int] = None
935
+ page_number: Optional[int] = None
936
936
  orientation: Optional[Orientation] = None
937
937
  page_size: Optional[PageSize] = None
938
938
 
939
939
  def to_dict(self) -> dict:
940
940
  payload: Dict[str, Any] = {}
941
- if self.page_index is not None:
942
- payload["pageIndex"] = int(self.page_index)
941
+ if self.page_number is not None:
942
+ payload["pageNumber"] = int(self.page_number)
943
943
  if self.orientation is not None:
944
944
  orientation_value: Orientation
945
945
  if isinstance(self.orientation, Orientation):
@@ -1449,14 +1449,14 @@ class PageRef(ObjectRef):
1449
1449
 
1450
1450
  Parameters (usually provided by the server):
1451
1451
  - internal_id: Identifier of the page object.
1452
- - position: Position referencing the page (often via `Position.at_page(page_index)`).
1452
+ - position: Position referencing the page (often via `Position.at_page(page_number)`).
1453
1453
  - type: Should be `ObjectType.PAGE`.
1454
1454
  - page_size: `PageSize` of the page.
1455
1455
  - orientation: `Orientation.PORTRAIT` or `Orientation.LANDSCAPE`.
1456
1456
 
1457
1457
  Usage:
1458
1458
  - Returned inside `PageSnapshot` objects. You can inspect page size/orientation
1459
- and use the page index for subsequent operations.
1459
+ and use the page number for subsequent operations.
1460
1460
  """
1461
1461
 
1462
1462
  page_size: Optional[PageSize]
@@ -1531,7 +1531,7 @@ class PageSnapshot:
1531
1531
 
1532
1532
  Usage:
1533
1533
  - Iterate over `elements` to find items to modify or move.
1534
- - Use `page_ref.position.page_index` as the page index for follow-up operations.
1534
+ - Use `page_ref.position.page_number` as the page number for follow-up operations.
1535
1535
  """
1536
1536
 
1537
1537
  page_ref: PageRef
@@ -11,10 +11,10 @@ if TYPE_CHECKING:
11
11
 
12
12
  class PageBuilder:
13
13
  """
14
- Fluent builder for adding pages with optional orientation, size, and index.
14
+ Fluent builder for adding pages with optional orientation, size, and page number.
15
15
 
16
16
  Usage:
17
- pdf.new_page().at_index(1).landscape().a4().add()
17
+ pdf.new_page().at_page(1).landscape().a4().add()
18
18
  """
19
19
 
20
20
  def __init__(self, client: "PDFDancer") -> None:
@@ -22,18 +22,44 @@ class PageBuilder:
22
22
  raise ValidationException("Client cannot be null")
23
23
 
24
24
  self._client = client
25
- self._page_index: Optional[int] = None
25
+ self._page_number: Optional[int] = None
26
26
  self._orientation: Optional[Orientation] = None
27
27
  self._page_size: Optional[PageSize] = None
28
28
 
29
- def at_index(self, page_index: int) -> "PageBuilder":
30
- if page_index is None:
31
- raise ValidationException("Page index cannot be null")
32
- if page_index < 0:
33
- raise ValidationException("Page index must be greater than or equal to 0")
34
- self._page_index = int(page_index)
29
+ def at_page(self, page_number: int) -> "PageBuilder":
30
+ """
31
+ Sets the page number where the new page should be inserted (1-based).
32
+ Page 1 is the first page.
33
+
34
+ Args:
35
+ page_number: The 1-based page number (must be >= 1)
36
+
37
+ Returns:
38
+ This builder
39
+
40
+ Raises:
41
+ ValidationException: If page_number is None or less than 1
42
+ """
43
+ if page_number is None:
44
+ raise ValidationException("Page number cannot be null")
45
+ if page_number < 1:
46
+ raise ValidationException("Page number must be >= 1 (1-based indexing)")
47
+ self._page_number = int(page_number)
35
48
  return self
36
49
 
50
+ def at_index(self, page_number: int) -> "PageBuilder":
51
+ """
52
+ Deprecated: Use at_page() instead. This method will be removed in a future release.
53
+ """
54
+ import warnings
55
+
56
+ warnings.warn(
57
+ "at_index() is deprecated, use at_page() instead",
58
+ DeprecationWarning,
59
+ stacklevel=2,
60
+ )
61
+ return self.at_page(page_number + 1)
62
+
37
63
  def orientation(self, orientation: Orientation) -> "PageBuilder":
38
64
  if orientation is None:
39
65
  raise ValidationException("Orientation cannot be null")
@@ -87,13 +113,13 @@ class PageBuilder:
87
113
 
88
114
  def _build_request(self) -> Optional[AddPageRequest]:
89
115
  if (
90
- self._page_index is None
116
+ self._page_number is None
91
117
  and self._orientation is None
92
118
  and self._page_size is None
93
119
  ):
94
120
  return None
95
121
  return AddPageRequest(
96
- page_index=self._page_index,
122
+ page_number=self._page_number,
97
123
  orientation=self._orientation,
98
124
  page_size=self._page_size,
99
125
  )
@@ -169,14 +169,14 @@ class ParagraphBuilder:
169
169
  "Cannot move paragraph without an existing position"
170
170
  )
171
171
 
172
- page_index = position.page_index
173
- if page_index is None:
172
+ page_number = position.page_number
173
+ if page_number is None:
174
174
  raise ValidationException(
175
- "Paragraph position must include a page index to move"
175
+ "Paragraph position must include a page number to move"
176
176
  )
177
177
 
178
178
  self._position_changed = True
179
- return self.at(page_index, x, y)
179
+ return self.at(page_number, x, y)
180
180
 
181
181
  def at_position(self, position: Position) -> "ParagraphBuilder":
182
182
  if position is None:
@@ -186,8 +186,8 @@ class ParagraphBuilder:
186
186
  self._position_changed = True
187
187
  return self
188
188
 
189
- def at(self, page_index: int, x: float, y: float) -> "ParagraphBuilder":
190
- return self.at_position(Position.at_page_coordinates(page_index, x, y))
189
+ def at(self, page_number: int, x: float, y: float) -> "ParagraphBuilder":
190
+ return self.at_position(Position.at_page_coordinates(page_number, x, y))
191
191
 
192
192
  def add_text_line(
193
193
  self, text_line: Union[TextLine, TextObjectRef, str]
@@ -470,14 +470,14 @@ class ParagraphBuilder:
470
470
  if paragraph_position is None:
471
471
  return None
472
472
 
473
- page_index = paragraph_position.page_index
473
+ page_number = paragraph_position.page_number
474
474
  base_x = paragraph_position.x()
475
475
  base_y = paragraph_position.y()
476
- if page_index is None or base_x is None or base_y is None:
476
+ if page_number is None or base_x is None or base_y is None:
477
477
  return None
478
478
 
479
479
  offset = line_index * self._calculate_baseline_distance(spacing_factor)
480
- return Position.at_page_coordinates(page_index, base_x, base_y + offset)
480
+ return Position.at_page_coordinates(page_number, base_x, base_y + offset)
481
481
 
482
482
  def _calculate_baseline_distance(self, spacing_factor: float) -> float:
483
483
  factor = spacing_factor if spacing_factor > 0 else DEFAULT_LINE_SPACING_FACTOR
@@ -545,10 +545,10 @@ class ParagraphBuilder:
545
545
 
546
546
  class ParagraphPageBuilder(ParagraphBuilder):
547
547
 
548
- def __init__(self, client: "PDFDancer", page_index: int):
548
+ def __init__(self, client: "PDFDancer", page_number: int):
549
549
  super().__init__(client)
550
- self._page_index: Optional[int] = page_index
550
+ self._page_number: Optional[int] = page_number
551
551
 
552
552
  # noinspection PyMethodOverriding
553
553
  def at(self, x: float, y: float) -> "ParagraphBuilder":
554
- return super().at(self._page_index, x, y)
554
+ return super().at(self._page_number, x, y)
@@ -21,19 +21,19 @@ class PathBuilder:
21
21
  All coordinates are absolute page coordinates.
22
22
  """
23
23
 
24
- def __init__(self, client: "PDFDancer", page_index: int):
24
+ def __init__(self, client: "PDFDancer", page_number: int):
25
25
  """
26
- Initialize the path builder with a client reference and page index.
26
+ Initialize the path builder with a client reference and page number.
27
27
 
28
28
  Args:
29
29
  client: The PDFDancer instance for adding the path
30
- page_index: The page number (0-indexed)
30
+ page_number: The page number (1-indexed)
31
31
  """
32
32
  if client is None:
33
33
  raise ValidationException("Client cannot be null")
34
34
 
35
35
  self._client = client
36
- self._page_index = page_index
36
+ self._page_number = page_number
37
37
  self._segments: List[PathSegment] = []
38
38
  self._even_odd_fill: bool = False
39
39
  self._current_stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
@@ -222,7 +222,7 @@ class PathBuilder:
222
222
  raise ValidationException("Path must have at least one segment")
223
223
 
224
224
  # Create position with only page index set
225
- position = Position.at_page_coordinates(self._page_index, 0, 0)
225
+ position = Position.at_page_coordinates(self._page_number, 0, 0)
226
226
 
227
227
  # Build the Path object
228
228
  path = Path(
@@ -242,19 +242,19 @@ class LineBuilder:
242
242
  Mirrors the Java client LineBuilder API.
243
243
  """
244
244
 
245
- def __init__(self, client: "PDFDancer", page_index: int):
245
+ def __init__(self, client: "PDFDancer", page_number: int):
246
246
  """
247
247
  Initialize the line builder.
248
248
 
249
249
  Args:
250
250
  client: The PDFDancer instance for adding the line
251
- page_index: The page number (0-indexed)
251
+ page_number: The page number (1-indexed)
252
252
  """
253
253
  if client is None:
254
254
  raise ValidationException("Client cannot be null")
255
255
 
256
256
  self._client = client
257
- self._page_index = page_index
257
+ self._page_number = page_number
258
258
  self._p0: Optional[Point] = None
259
259
  self._p1: Optional[Point] = None
260
260
  self._stroke_color: Optional[Color] = Color(0, 0, 0) # Black default
@@ -387,7 +387,7 @@ class LineBuilder:
387
387
  )
388
388
 
389
389
  # Create position with only page index set
390
- position = Position.at_page_coordinates(self._page_index, 0, 0)
390
+ position = Position.at_page_coordinates(self._page_number, 0, 0)
391
391
 
392
392
  # Wrap in Path with single segment
393
393
  path = Path(position=position, path_segments=[line], even_odd_fill=False)
@@ -403,19 +403,19 @@ class BezierBuilder:
403
403
  Mirrors the Java client BezierBuilder API.
404
404
  """
405
405
 
406
- def __init__(self, client: "PDFDancer", page_index: int):
406
+ def __init__(self, client: "PDFDancer", page_number: int):
407
407
  """
408
408
  Initialize the bezier builder.
409
409
 
410
410
  Args:
411
411
  client: The PDFDancer instance for adding the bezier
412
- page_index: The page number (0-indexed)
412
+ page_number: The page number (1-indexed)
413
413
  """
414
414
  if client is None:
415
415
  raise ValidationException("Client cannot be null")
416
416
 
417
417
  self._client = client
418
- self._page_index = page_index
418
+ self._page_number = page_number
419
419
  self._p0: Optional[Point] = None
420
420
  self._p1: Optional[Point] = None
421
421
  self._p2: Optional[Point] = None
@@ -590,7 +590,7 @@ class BezierBuilder:
590
590
  )
591
591
 
592
592
  # Create position with only page index set
593
- position = Position.at_page_coordinates(self._page_index, 0, 0)
593
+ position = Position.at_page_coordinates(self._page_number, 0, 0)
594
594
 
595
595
  # Wrap in Path with single segment
596
596
  path = Path(position=position, path_segments=[bezier], even_odd_fill=False)
@@ -606,19 +606,19 @@ class RectangleBuilder:
606
606
  Provides a convenient way to create a rectangle path with a single builder.
607
607
  """
608
608
 
609
- def __init__(self, client: "PDFDancer", page_index: int):
609
+ def __init__(self, client: "PDFDancer", page_number: int):
610
610
  """
611
611
  Initialize the rectangle builder.
612
612
 
613
613
  Args:
614
614
  client: The PDFDancer instance for adding the rectangle
615
- page_index: The page number (0-indexed)
615
+ page_number: The page number (1-indexed)
616
616
  """
617
617
  if client is None:
618
618
  raise ValidationException("Client cannot be null")
619
619
 
620
620
  self._client = client
621
- self._page_index = page_index
621
+ self._page_number = page_number
622
622
  self._x: Optional[float] = None
623
623
  self._y: Optional[float] = None
624
624
  self._width: Optional[float] = None
@@ -812,7 +812,7 @@ class RectangleBuilder:
812
812
  ]
813
813
 
814
814
  # Create position with only page index set
815
- position = Position.at_page_coordinates(self._page_index, 0, 0)
815
+ position = Position.at_page_coordinates(self._page_number, 0, 0)
816
816
 
817
817
  # Wrap in Path with four line segments
818
818
  path = Path(