pdfdancer-client-python 0.2.19__py3-none-any.whl → 0.2.21__py3-none-any.whl

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.

Potentially problematic release.


This version of pdfdancer-client-python might be problematic. Click here for more details.

pdfdancer/models.py CHANGED
@@ -9,7 +9,36 @@ from typing import Optional, List, Any, Dict, Mapping, Tuple, ClassVar, Union
9
9
 
10
10
  @dataclass(frozen=True)
11
11
  class PageSize:
12
- """Represents a page size specification, covering both standard and custom dimensions."""
12
+ """Represents a page size specification, covering both standard and custom dimensions.
13
+
14
+ Parameters:
15
+ - name: Optional canonical name of the size (e.g. "A4", "LETTER"). Will be upper‑cased.
16
+ - width: Page width in PDF points (1/72 inch).
17
+ - height: Page height in PDF points (1/72 inch).
18
+
19
+ Notes:
20
+ - Use `PageSize.from_name()` or the convenience constants (e.g. `PageSize.A4`) for common sizes.
21
+ - `width` and `height` must be positive numbers and are validated in `__post_init__`.
22
+
23
+ Examples:
24
+ - From standard name:
25
+ ```python
26
+ size = PageSize.from_name("A4") # or PageSize.A4
27
+ ```
28
+ - From custom dimensions:
29
+ ```python
30
+ size = PageSize(name=None, width=500.0, height=700.0)
31
+ ```
32
+ - From dict (e.g. deserialized JSON):
33
+ ```python
34
+ size = PageSize.from_dict({"width": 612, "height": 792, "name": "letter"})
35
+ ```
36
+ - Coercion utility:
37
+ ```python
38
+ size = PageSize.coerce("A4")
39
+ size = PageSize.coerce({"width": 300, "height": 300})
40
+ ```
41
+ """
13
42
 
14
43
  name: Optional[str]
15
44
  width: float
@@ -154,6 +183,7 @@ class StandardFonts(Enum):
154
183
 
155
184
 
156
185
  class ObjectType(Enum):
186
+ """Server object type discriminator used in refs, requests, and snapshots."""
157
187
  FORM_FIELD = "FORM_FIELD"
158
188
  IMAGE = "IMAGE"
159
189
  FORM_X_OBJECT = "FORM_X_OBJECT"
@@ -166,6 +196,7 @@ class ObjectType(Enum):
166
196
  RADIO_BUTTON = "RADIO_BUTTON"
167
197
  BUTTON = "BUTTON"
168
198
  DROPDOWN = "DROPDOWN"
199
+ TEXT_ELEMENT = "TEXT_ELEMENT"
169
200
 
170
201
 
171
202
  class PositionMode(Enum):
@@ -215,7 +246,39 @@ class BoundingRect:
215
246
  @dataclass
216
247
  class Position:
217
248
  """
218
- Represents spatial positioning and location information for PDF objects.
249
+ Spatial locator used to find or place objects on a page.
250
+
251
+ Parameters:
252
+ - page_index: Zero-based page index this position refers to. Required for most operations
253
+ that place or search on a specific page; use `Position.at_page()` as a shortcut.
254
+ - shape: Optional geometric shape used when matching by area (`POINT`, `LINE`, `CIRCLE`, `RECT`).
255
+ - mode: How to match objects relative to the shape (`INTERSECT` or `CONTAINS`).
256
+ - bounding_rect: Rectangle describing the area or point (for `POINT`, width/height are 0).
257
+ - text_starts_with: Filter for text objects whose content starts with this string.
258
+ - text_pattern: Regex pattern to match text content.
259
+ - name: Named anchor or element name to target (e.g. form field name).
260
+
261
+ Builder helpers:
262
+ - `Position.at_page(page_index)` – target a whole page.
263
+ - `Position.at_page_coordinates(page_index, x, y)` – target a point on a page.
264
+ - `Position.by_name(name)` – target object(s) by name.
265
+ - `pos.at_coordinates(Point(x, y))` – switch to a point on the current page.
266
+ - `pos.move_x(dx)`, `pos.move_y(dy)` – offset the current coordinates.
267
+
268
+ Examples:
269
+ ```python
270
+ # A point on page 0
271
+ pos = Position.at_page_coordinates(0, x=72, y=720)
272
+
273
+ # Search by name (e.g. a form field) and then move down 12 points
274
+ pos = Position.by_name("Email").move_y(-12)
275
+
276
+ # Match anything intersecting a rectangular area on page 1
277
+ pos = Position.at_page(1)
278
+ pos.shape = ShapeType.RECT
279
+ pos.mode = PositionMode.INTERSECT
280
+ pos.bounding_rect = BoundingRect(x=100, y=100, width=200, height=50)
281
+ ```
219
282
  """
220
283
  page_index: Optional[int] = None
221
284
  shape: Optional[ShapeType] = None
@@ -287,7 +350,24 @@ class Position:
287
350
  @dataclass
288
351
  class ObjectRef:
289
352
  """
290
- Lightweight reference to a PDF object providing identity and type information.
353
+ Reference to an object in a PDF document returned by the server.
354
+
355
+ Parameters:
356
+ - internal_id: Server-side identifier for the object.
357
+ - position: Position information describing where the object is.
358
+ - type: Object type (see `ObjectType`).
359
+
360
+ Usage:
361
+ - Instances are typically returned in snapshots or find results.
362
+ - Pass an `ObjectRef` to request objects such as `MoveRequest`, `DeleteRequest`,
363
+ `ModifyRequest`, or `ModifyTextRequest`.
364
+
365
+ Example:
366
+ ```python
367
+ # Move an object to a new position
368
+ new_pos = Position.at_page_coordinates(0, 120, 500)
369
+ payload = MoveRequest(object_ref=obj_ref, position=new_pos).to_dict()
370
+ ```
291
371
  """
292
372
  internal_id: str
293
373
  position: Position
@@ -325,7 +405,23 @@ class ObjectRef:
325
405
 
326
406
  @dataclass
327
407
  class Color:
328
- """Represents an RGB color with optional alpha channel, values from 0-255."""
408
+ """RGB color with optional alpha channel.
409
+
410
+ Parameters:
411
+ - r: Red component (0-255)
412
+ - g: Green component (0-255)
413
+ - b: Blue component (0-255)
414
+ - a: Alpha component (0-255), default 255 (opaque)
415
+
416
+ Raises:
417
+ - ValueError: If any component is outside 0-255.
418
+
419
+ Example:
420
+ ```python
421
+ red = Color(255, 0, 0)
422
+ semi_transparent_black = Color(0, 0, 0, a=128)
423
+ ```
424
+ """
329
425
  r: int
330
426
  g: int
331
427
  b: int
@@ -339,7 +435,23 @@ class Color:
339
435
 
340
436
  @dataclass
341
437
  class Font:
342
- """Represents a font with name and size."""
438
+ """Font face and size.
439
+
440
+ Parameters:
441
+ - name: Font family name. Can be one of `StandardFonts` values or any embedded font name.
442
+ - size: Font size in points (> 0).
443
+
444
+ Raises:
445
+ - ValueError: If `size` is not positive.
446
+
447
+ Example:
448
+ ```python
449
+ from pdfdancer.models import Font, StandardFonts
450
+
451
+ title_font = Font(name=StandardFonts.HELVETICA_BOLD.value, size=16)
452
+ body_font = Font(name="MyEmbeddedFont", size=10.5)
453
+ ```
454
+ """
343
455
  name: str
344
456
  size: float
345
457
 
@@ -351,10 +463,18 @@ class Font:
351
463
  @dataclass
352
464
  class PathSegment:
353
465
  """
354
- Abstract base class for individual path segments within vector paths.
355
- This class provides common properties for path elements including stroke and fill colors,
356
- line width, and positioning. Concrete subclasses implement specific geometric shapes
357
- like lines, curves, and bezier segments.
466
+ Base class for vector path segments.
467
+
468
+ Parameters:
469
+ - stroke_color: Outline color for the segment (`Color`).
470
+ - fill_color: Fill color for closed shapes when applicable (`Color`).
471
+ - stroke_width: Line width in points.
472
+ - dash_array: Dash pattern (e.g. `[3, 2]` for 3 on, 2 off). None or empty for solid.
473
+ - dash_phase: Offset into the dash pattern.
474
+
475
+ Notes:
476
+ - Concrete subclasses are `Line` and `Bezier`.
477
+ - Used inside `Path.path_segments` and serialized by `AddRequest`.
358
478
  """
359
479
  stroke_color: Optional[Color] = None
360
480
  fill_color: Optional[Color] = None
@@ -386,9 +506,19 @@ class PathSegment:
386
506
  @dataclass
387
507
  class Line(PathSegment):
388
508
  """
389
- Represents a straight line path segment between two points.
390
- This class defines a linear path element connecting two coordinate points,
391
- commonly used in vector graphics and geometric shapes within PDF documents.
509
+ Straight line segment between two points.
510
+
511
+ Parameters:
512
+ - p0: Start point.
513
+ - p1: End point.
514
+
515
+ Example:
516
+ ```python
517
+ from pdfdancer.models import Line, Point, Path
518
+
519
+ line = Line(p0=Point(10, 10), p1=Point(100, 10))
520
+ path = Path(path_segments=[line])
521
+ ```
392
522
  """
393
523
  p0: Optional[Point] = None
394
524
  p1: Optional[Point] = None
@@ -405,9 +535,20 @@ class Line(PathSegment):
405
535
  @dataclass
406
536
  class Bezier(PathSegment):
407
537
  """
408
- Represents a cubic Bezier curve path segment defined by four control points.
409
- This class implements a cubic Bezier curve with start point, two control points,
410
- and end point, providing smooth curved path segments for complex vector graphics.
538
+ Cubic Bezier curve segment defined by 4 points.
539
+
540
+ Parameters:
541
+ - p0: Start point.
542
+ - p1: First control point.
543
+ - p2: Second control point.
544
+ - p3: End point.
545
+
546
+ Example:
547
+ ```python
548
+ curve = Bezier(
549
+ p0=Point(10, 10), p1=Point(50, 80), p2=Point(80, 50), p3=Point(120, 10)
550
+ )
551
+ ```
411
552
  """
412
553
  p0: Optional[Point] = None
413
554
  p1: Optional[Point] = None
@@ -434,9 +575,28 @@ class Bezier(PathSegment):
434
575
  @dataclass
435
576
  class Path:
436
577
  """
437
- Represents a complex vector path consisting of multiple path segments.
438
- This class encapsulates vector graphics data within PDF documents, composed of
439
- various path elements like lines, curves, and shapes.
578
+ Vector path composed of one or more `PathSegment`s.
579
+
580
+ Parameters:
581
+ - position: Where to place the path on the page.
582
+ - path_segments: List of `Line` and/or `Bezier` segments.
583
+ - even_odd_fill: If True, use even-odd rule for fills; otherwise nonzero winding.
584
+
585
+ Example (adding a triangle to a page):
586
+ ```python
587
+ from pdfdancer.models import Path, Line, Point, Position, AddRequest
588
+
589
+ tri = Path(
590
+ position=Position.at_page_coordinates(0, 100, 100),
591
+ path_segments=[
592
+ Line(Point(0, 0), Point(50, 100)),
593
+ Line(Point(50, 100), Point(100, 0)),
594
+ Line(Point(100, 0), Point(0, 0)),
595
+ ],
596
+ even_odd_fill=True,
597
+ )
598
+ payload = AddRequest(tri).to_dict()
599
+ ```
440
600
  """
441
601
  position: Optional[Position] = None
442
602
  path_segments: Optional[List[PathSegment]] = None
@@ -462,7 +622,28 @@ class Path:
462
622
  @dataclass
463
623
  class Image:
464
624
  """
465
- Represents an image object in a PDF document.
625
+ Raster image to be placed on a page.
626
+
627
+ Parameters:
628
+ - position: Where to place the image. Use `Position.at_page_coordinates(page, x, y)`.
629
+ - format: Image format hint for the server (e.g. "PNG", "JPEG"). Optional.
630
+ - width: Target width in points. Optional; server may infer from data.
631
+ - height: Target height in points. Optional; server may infer from data.
632
+ - data: Raw image bytes. If provided, it will be base64-encoded in `AddRequest.to_dict()`.
633
+
634
+ Example:
635
+ ```python
636
+ from pdfdancer.models import Image, Position, AddRequest
637
+
638
+ img = Image(
639
+ position=Position.at_page_coordinates(0, 72, 600),
640
+ format="PNG",
641
+ width=128,
642
+ height=64,
643
+ data=open("/path/logo.png", "rb").read(),
644
+ )
645
+ payload = AddRequest(img).to_dict()
646
+ ```
466
647
  """
467
648
  position: Optional[Position] = None
468
649
  format: Optional[str] = None
@@ -479,16 +660,67 @@ class Image:
479
660
  self.position = position
480
661
 
481
662
 
663
+ @dataclass
664
+ class TextLine:
665
+ """
666
+ One line of text to add to a page.
667
+
668
+ Parameters:
669
+ - position: Anchor position where the first line begins.
670
+ - text: the text
671
+ provide separate entries for multiple lines.
672
+ - font: Font to use for all text elements unless overridden later.
673
+ - color: Text color.
674
+
675
+ """
676
+ position: Optional[Position] = None
677
+ font: Optional[Font] = None
678
+ color: Optional[Color] = None
679
+ line_spacing: float = 1.2
680
+ text: str = ""
681
+
682
+ def get_position(self) -> Optional[Position]:
683
+ """Returns the position of this paragraph."""
684
+ return self.position
685
+
686
+ def set_position(self, position: Position) -> None:
687
+ """Sets the position of this paragraph."""
688
+ self.position = position
689
+
690
+
482
691
  @dataclass
483
692
  class Paragraph:
484
693
  """
485
- Represents a paragraph of text in a PDF document.
694
+ Multi-line text paragraph to add to a page.
695
+
696
+ Parameters:
697
+ - position: Anchor position where the first line begins.
698
+ - text_lines: List of strings, one per line. Use `\n` within a string only if desired; normally
699
+ provide separate entries for multiple lines.
700
+ - font: Font to use for all text elements unless overridden later.
701
+ - color: Text color.
702
+ - line_spacing: Distance multiplier between lines. Server expects a list, handled for you by `AddRequest`.
703
+
704
+ Example:
705
+ ```python
706
+ from pdfdancer.models import Paragraph, Position, Font, Color, StandardFonts, AddRequest
707
+
708
+ para = Paragraph(
709
+ position=Position.at_page_coordinates(0, 72, 700),
710
+ text_lines=["Hello", "PDFDancer!"],
711
+ font=Font(StandardFonts.HELVETICA.value, 12),
712
+ color=Color(50, 50, 50),
713
+ line_spacing=1.4,
714
+ )
715
+ payload = AddRequest(para).to_dict()
716
+ ```
486
717
  """
487
718
  position: Optional[Position] = None
488
- text_lines: Optional[List[str]] = None
719
+ text_lines: Optional[List[TextLine]] = None
489
720
  font: Optional[Font] = None
490
721
  color: Optional[Color] = None
491
722
  line_spacing: float = 1.2
723
+ line_spacings: Optional[List[float]] = None
492
724
 
493
725
  def get_position(self) -> Optional[Position]:
494
726
  """Returns the position of this paragraph."""
@@ -498,11 +730,54 @@ class Paragraph:
498
730
  """Sets the position of this paragraph."""
499
731
  self.position = position
500
732
 
733
+ def clear_lines(self) -> None:
734
+ """Removes all text lines from this paragraph."""
735
+ self.text_lines = []
736
+
737
+ def add_line(self, text_line: TextLine) -> None:
738
+ """Appends a text line to this paragraph."""
739
+ if self.text_lines is None:
740
+ self.text_lines = []
741
+ self.text_lines.append(text_line)
742
+
743
+ def get_lines(self) -> List[TextLine]:
744
+ """Returns the list of text lines, defaulting to an empty list."""
745
+ if self.text_lines is None:
746
+ self.text_lines = []
747
+ return self.text_lines
748
+
749
+ def set_lines(self, lines: List[TextLine]) -> None:
750
+ """Replaces the current text lines with the provided list."""
751
+ self.text_lines = list(lines)
752
+
753
+ def set_line_spacings(self, spacings: Optional[List[float]]) -> None:
754
+ """Sets the per-line spacing factors for this paragraph."""
755
+ self.line_spacings = list(spacings) if spacings else None
756
+
757
+ def get_line_spacings(self) -> Optional[List[float]]:
758
+ """Returns the per-line spacing factors if present."""
759
+ return list(self.line_spacings) if self.line_spacings else None
760
+
501
761
 
502
762
  # Request classes for API communication
503
763
  @dataclass
504
764
  class FindRequest:
505
- """Request object for find operations."""
765
+ """Request for locating objects.
766
+
767
+ Parameters:
768
+ - object_type: Filter by `ObjectType` (optional). If None, all types may be returned.
769
+ - position: `Position` describing where/how to search.
770
+ - hint: Optional backend hint or free-form note to influence matching.
771
+
772
+ Usage:
773
+ ```python
774
+ req = FindRequest(
775
+ object_type=ObjectType.TEXT_LINE,
776
+ position=Position.at_page_coordinates(0, 72, 700).with_text_starts("Hello"),
777
+ )
778
+ payload = req.to_dict()
779
+ ```
780
+ """
506
781
  object_type: Optional[ObjectType]
507
782
  position: Optional[Position]
508
783
  hint: Optional[str] = None
@@ -541,7 +816,16 @@ class FindRequest:
541
816
 
542
817
  @dataclass
543
818
  class DeleteRequest:
544
- """Request object for delete operations."""
819
+ """Request to delete an existing object.
820
+
821
+ Parameters:
822
+ - object_ref: The object to delete.
823
+
824
+ Example:
825
+ ```python
826
+ payload = DeleteRequest(object_ref=obj_ref).to_dict()
827
+ ```
828
+ """
545
829
  object_ref: ObjectRef
546
830
 
547
831
  def to_dict(self) -> dict:
@@ -554,7 +838,19 @@ class DeleteRequest:
554
838
 
555
839
  @dataclass
556
840
  class MoveRequest:
557
- """Request object for move operations."""
841
+ """Request to move an existing object to a new position.
842
+
843
+ Parameters:
844
+ - object_ref: The object to move (obtained from a snapshot or find call).
845
+ - position: The new target `Position` (commonly a point created with `Position.at_page_coordinates`).
846
+
847
+ Example:
848
+ ```python
849
+ new_pos = Position.at_page_coordinates(0, 200, 500)
850
+ req = MoveRequest(object_ref=obj_ref, position=new_pos)
851
+ payload = req.to_dict()
852
+ ```
853
+ """
558
854
  object_ref: ObjectRef
559
855
  position: Position
560
856
 
@@ -570,7 +866,19 @@ class MoveRequest:
570
866
 
571
867
  @dataclass
572
868
  class PageMoveRequest:
573
- """Request object for moving pages within the document."""
869
+ """Request to reorder pages.
870
+
871
+ Parameters:
872
+ - from_page_index: Zero-based index of the page to move.
873
+ - to_page_index: Zero-based destination index.
874
+
875
+ Example:
876
+ ```python
877
+ # Move first page to the end
878
+ req = PageMoveRequest(from_page_index=0, to_page_index=doc_page_count - 1)
879
+ payload = req.to_dict()
880
+ ```
881
+ """
574
882
  from_page_index: int
575
883
  to_page_index: int
576
884
 
@@ -581,9 +889,60 @@ class PageMoveRequest:
581
889
  }
582
890
 
583
891
 
892
+ @dataclass
893
+ class AddPageRequest:
894
+ """Request to add a new page to the document.
895
+
896
+ Parameters:
897
+ - page_index: Optional zero-based index where the new page should be inserted.
898
+ - orientation: Optional page orientation (portrait or landscape).
899
+ - page_size: Optional size of the page.
900
+
901
+ Only populated fields are sent to the server to maintain backward compatibility
902
+ with default server behavior.
903
+ """
904
+ page_index: Optional[int] = None
905
+ orientation: Optional[Orientation] = None
906
+ page_size: Optional[PageSize] = None
907
+
908
+ def to_dict(self) -> dict:
909
+ payload: Dict[str, Any] = {}
910
+ if self.page_index is not None:
911
+ payload["pageIndex"] = int(self.page_index)
912
+ if self.orientation is not None:
913
+ orientation_value: Orientation
914
+ if isinstance(self.orientation, Orientation):
915
+ orientation_value = self.orientation
916
+ elif isinstance(self.orientation, str):
917
+ normalized = self.orientation.strip().upper()
918
+ orientation_value = Orientation(normalized)
919
+ else:
920
+ raise TypeError("Orientation must be an Orientation enum or string value")
921
+ payload["orientation"] = orientation_value.value
922
+ if self.page_size is not None:
923
+ page_size = PageSize.coerce(self.page_size)
924
+ payload["pageSize"] = page_size.to_dict()
925
+ return payload
926
+
927
+
584
928
  @dataclass
585
929
  class AddRequest:
586
- """Request object for add operations."""
930
+ """Request to add a new object to the document.
931
+
932
+ Parameters:
933
+ - pdf_object: The object to add (e.g. `Image`, `Paragraph`, or `Path`).
934
+
935
+ Usage:
936
+ ```python
937
+ para = Paragraph(position=Position.at_page_coordinates(0, 72, 700), text_lines=["Hello"])
938
+ req = AddRequest(para)
939
+ payload = req.to_dict() # ready to send to the server API
940
+ ```
941
+
942
+ Notes:
943
+ - Serialization details (like base64 for image `data`, or per-segment position for paths)
944
+ are handled for you in `to_dict()`.
945
+ """
587
946
  pdf_object: Any # Can be Image, Paragraph, etc.
588
947
 
589
948
  def to_dict(self) -> dict:
@@ -599,7 +958,7 @@ class AddRequest:
599
958
  def _object_to_dict(self, obj: Any) -> dict:
600
959
  """Convert PDF object to dictionary for JSON serialization."""
601
960
  import base64
602
- from .models import Path as PathModel, Line, Bezier, PathSegment
961
+ from .models import Path as PathModel
603
962
 
604
963
  if isinstance(obj, PathModel):
605
964
  # Serialize Path object
@@ -634,37 +993,55 @@ class AddRequest:
634
993
  "data": data_b64
635
994
  }
636
995
  elif isinstance(obj, Paragraph):
637
- # Build lines -> List<TextLine> with minimal structure required by server
638
- lines = []
996
+ def _font_to_dict(font: Optional[Font]) -> Optional[dict]:
997
+ if font:
998
+ return {"name": font.name, "size": font.size}
999
+ return None
1000
+
1001
+ def _color_to_dict(color: Optional[Color]) -> Optional[dict]:
1002
+ if color:
1003
+ return {"red": color.r, "green": color.g, "blue": color.b, "alpha": color.a}
1004
+ return None
1005
+
1006
+ lines_payload = []
639
1007
  if obj.text_lines:
640
1008
  for line in obj.text_lines:
1009
+ if isinstance(line, TextLine):
1010
+ line_text = line.text
1011
+ line_font = line.font or obj.font
1012
+ line_color = line.color or obj.color
1013
+ line_position = line.position or obj.position
1014
+ else:
1015
+ line_text = str(line)
1016
+ line_font = obj.font
1017
+ line_color = obj.color
1018
+ line_position = obj.position
1019
+
641
1020
  text_element = {
642
- "text": line,
643
- "font": {"name": obj.font.name, "size": obj.font.size} if obj.font else None,
644
- "color": {"red": obj.color.r, "green": obj.color.g, "blue": obj.color.b,
645
- "alpha": obj.color.a} if obj.color else None,
646
- "position": FindRequest._position_to_dict(obj.position) if obj.position else None
647
- }
648
- text_line = {
649
- "textElements": [text_element]
1021
+ "text": line_text,
1022
+ "font": _font_to_dict(line_font),
1023
+ "color": _color_to_dict(line_color),
1024
+ "position": FindRequest._position_to_dict(line_position) if line_position else None
650
1025
  }
651
- # TextLine has color and position
652
- if obj.color:
653
- text_line["color"] = {"red": obj.color.r, "green": obj.color.g, "blue": obj.color.b,
654
- "alpha": obj.color.a}
655
- if obj.position:
656
- text_line["position"] = FindRequest._position_to_dict(obj.position)
657
- lines.append(text_line)
1026
+ text_line = {"textElements": [text_element]}
1027
+ if line_color:
1028
+ text_line["color"] = _color_to_dict(line_color)
1029
+ if line_position:
1030
+ text_line["position"] = FindRequest._position_to_dict(line_position)
1031
+ lines_payload.append(text_line)
1032
+
658
1033
  line_spacings = None
659
- if hasattr(obj, "line_spacing") and obj.line_spacing is not None:
660
- # Server expects a list
1034
+ if getattr(obj, "line_spacings", None):
1035
+ line_spacings = list(obj.line_spacings)
1036
+ elif getattr(obj, "line_spacing", None) is not None:
661
1037
  line_spacings = [obj.line_spacing]
1038
+
662
1039
  return {
663
1040
  "type": "PARAGRAPH",
664
1041
  "position": FindRequest._position_to_dict(obj.position) if obj.position else None,
665
- "lines": lines,
1042
+ "lines": lines_payload if lines_payload else None,
666
1043
  "lineSpacings": line_spacings,
667
- "font": {"name": obj.font.name, "size": obj.font.size} if obj.font else None
1044
+ "font": _font_to_dict(obj.font)
668
1045
  }
669
1046
  else:
670
1047
  raise ValueError(f"Unsupported object type: {type(obj)}")
@@ -727,7 +1104,19 @@ class AddRequest:
727
1104
 
728
1105
  @dataclass
729
1106
  class ModifyRequest:
730
- """Request object for modify operations."""
1107
+ """Request to replace an object with a new one of possibly different type.
1108
+
1109
+ Parameters:
1110
+ - object_ref: The existing object to replace.
1111
+ - new_object: The replacement object (e.g. `Paragraph`, `Image`, or `Path`).
1112
+
1113
+ Example:
1114
+ ```python
1115
+ new_para = Paragraph(position=old.position, text_lines=["Updated text"])
1116
+ req = ModifyRequest(object_ref=old, new_object=new_para)
1117
+ payload = req.to_dict()
1118
+ ```
1119
+ """
731
1120
  object_ref: ObjectRef
732
1121
  new_object: Any
733
1122
 
@@ -742,7 +1131,18 @@ class ModifyRequest:
742
1131
 
743
1132
  @dataclass
744
1133
  class ModifyTextRequest:
745
- """Request object for text modification operations."""
1134
+ """Request to change the text content of a text object.
1135
+
1136
+ Parameters:
1137
+ - object_ref: The text object to modify (e.g. a `TextObjectRef`).
1138
+ - new_text: Replacement text content.
1139
+
1140
+ Example:
1141
+ ```python
1142
+ req = ModifyTextRequest(object_ref=text_ref, new_text="Hello world")
1143
+ payload = req.to_dict()
1144
+ ```
1145
+ """
746
1146
  object_ref: ObjectRef
747
1147
  new_text: str
748
1148
 
@@ -757,6 +1157,19 @@ class ModifyTextRequest:
757
1157
 
758
1158
  @dataclass
759
1159
  class ChangeFormFieldRequest:
1160
+ """Request to set a form field's value.
1161
+
1162
+ Parameters:
1163
+ - object_ref: A `FormFieldRef` (or generic `ObjectRef`) identifying the field.
1164
+ - value: The new value as a string. For checkboxes/radio buttons, use the
1165
+ appropriate on/off/selection string per the document's field options.
1166
+
1167
+ Example:
1168
+ ```python
1169
+ req = ChangeFormFieldRequest(object_ref=field_ref, value="Jane Doe")
1170
+ payload = req.to_dict()
1171
+ ```
1172
+ """
760
1173
  object_ref: ObjectRef
761
1174
  value: str
762
1175
 
@@ -772,8 +1185,20 @@ class ChangeFormFieldRequest:
772
1185
  @dataclass
773
1186
  class FormFieldRef(ObjectRef):
774
1187
  """
775
- Represents a form field reference with additional form-specific properties.
776
- Extends ObjectRef to include form field name and value.
1188
+ Reference to a form field object with name and value.
1189
+
1190
+ Parameters (usually provided by the server):
1191
+ - internal_id: Identifier of the form field object.
1192
+ - position: Position of the field.
1193
+ - type: One of `ObjectType.TEXT_FIELD`, `ObjectType.CHECK_BOX`, etc.
1194
+ - name: Field name (as defined inside the PDF).
1195
+ - value: Current field value (string representation).
1196
+
1197
+ Usage:
1198
+ - You can pass a `FormFieldRef` to `ChangeFormFieldRequest` to update its value.
1199
+ ```python
1200
+ payload = ChangeFormFieldRequest(object_ref=field_ref, value="john@doe.com").to_dict()
1201
+ ```
777
1202
  """
778
1203
  name: Optional[str] = None
779
1204
  value: Optional[str] = None
@@ -841,8 +1266,23 @@ class TextStatus:
841
1266
 
842
1267
  class TextObjectRef(ObjectRef):
843
1268
  """
844
- Represents a text object reference with additional text-specific properties.
845
- Extends ObjectRef to include text content, font information, and hierarchical structure.
1269
+ Represents a text object with additional properties and optional hierarchy.
1270
+
1271
+ Parameters (typically provided by the server):
1272
+ - internal_id: Identifier of the text object.
1273
+ - position: Position of the text object.
1274
+ - object_type: `ObjectType.TEXT_LINE` or another text-related type.
1275
+ - text: Text content, when available.
1276
+ - font_name: Name of the font used for the text.
1277
+ - font_size: Size of the font in points.
1278
+ - line_spacings: Optional list of line spacing values for multi-line objects.
1279
+ - color: Text color.
1280
+ - status: `TextStatus` providing modification/encoding info.
1281
+
1282
+ Usage:
1283
+ - Instances are returned by find/snapshot APIs. You generally should not instantiate
1284
+ them manually, but you may read their properties or pass their `ObjectRef`-like
1285
+ identity to modification requests (e.g., `ModifyTextRequest`).
846
1286
  """
847
1287
 
848
1288
  def __init__(self, internal_id: str, position: Position, object_type: ObjectType,
@@ -890,8 +1330,18 @@ class TextObjectRef(ObjectRef):
890
1330
  @dataclass
891
1331
  class PageRef(ObjectRef):
892
1332
  """
893
- Represents a page reference with additional page-specific properties.
894
- Extends ObjectRef to include page size and orientation.
1333
+ Reference to a page with size and orientation metadata.
1334
+
1335
+ Parameters (usually provided by the server):
1336
+ - internal_id: Identifier of the page object.
1337
+ - position: Position referencing the page (often via `Position.at_page(page_index)`).
1338
+ - type: Should be `ObjectType.PAGE`.
1339
+ - page_size: `PageSize` of the page.
1340
+ - orientation: `Orientation.PORTRAIT` or `Orientation.LANDSCAPE`.
1341
+
1342
+ Usage:
1343
+ - Returned inside `PageSnapshot` objects. You can inspect page size/orientation
1344
+ and use the page index for subsequent operations.
895
1345
  """
896
1346
  page_size: Optional[PageSize]
897
1347
  orientation: Optional[Orientation]
@@ -908,7 +1358,22 @@ class PageRef(ObjectRef):
908
1358
  @dataclass
909
1359
  class CommandResult:
910
1360
  """
911
- Result object returned by certain API endpoints indicating the outcome of an operation.
1361
+ Outcome returned by certain API endpoints.
1362
+
1363
+ Parameters:
1364
+ - command_name: Name of the executed command on the server.
1365
+ - element_id: Optional related element ID (when applicable).
1366
+ - message: Informational message or error description.
1367
+ - success: Whether the command succeeded.
1368
+ - warning: Optional warning details.
1369
+
1370
+ Example:
1371
+ ```python
1372
+ # Parse from a server JSON response dict
1373
+ result = CommandResult.from_dict(resp_json)
1374
+ if not result.success:
1375
+ print("Operation failed:", result.message)
1376
+ ```
912
1377
  """
913
1378
  command_name: str
914
1379
  element_id: str | None
@@ -936,6 +1401,14 @@ class CommandResult:
936
1401
  class PageSnapshot:
937
1402
  """
938
1403
  Snapshot of a single page containing all elements and page metadata.
1404
+
1405
+ Parameters (provided by the server):
1406
+ - page_ref: `PageRef` describing the page (size, orientation, etc.).
1407
+ - elements: List of `ObjectRef` (and subclasses) present on the page.
1408
+
1409
+ Usage:
1410
+ - Iterate over `elements` to find items to modify or move.
1411
+ - Use `page_ref.position.page_index` as the page index for follow-up operations.
939
1412
  """
940
1413
  page_ref: PageRef
941
1414
  elements: List[ObjectRef]
@@ -952,7 +1425,21 @@ class PageSnapshot:
952
1425
  @dataclass
953
1426
  class DocumentSnapshot:
954
1427
  """
955
- Snapshot of the entire document containing all pages and font information.
1428
+ Snapshot of a document including pages and fonts used.
1429
+
1430
+ Parameters (provided by the server):
1431
+ - page_count: Number of pages in the document.
1432
+ - fonts: List of `FontRecommendation` entries summarizing fonts in the document.
1433
+ - pages: Ordered list of `PageSnapshot` objects, one per page.
1434
+
1435
+ Usage:
1436
+ ```python
1437
+ # Iterate pages and elements
1438
+ for page in snapshot.pages:
1439
+ for el in page.elements:
1440
+ if isinstance(el, TextObjectRef) and el.get_text():
1441
+ print(el.get_text())
1442
+ ```
956
1443
  """
957
1444
  page_count: int
958
1445
  fonts: List[FontRecommendation]