pdfdancer-client-python 0.2.17__py3-none-any.whl → 0.2.18__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/__init__.py +9 -1
- pdfdancer/exceptions.py +3 -3
- pdfdancer/fingerprint.py +121 -0
- pdfdancer/models.py +243 -27
- pdfdancer/path_builder.py +557 -0
- pdfdancer/pdfdancer_v1.py +865 -126
- {pdfdancer_client_python-0.2.17.dist-info → pdfdancer_client_python-0.2.18.dist-info}/METADATA +18 -18
- pdfdancer_client_python-0.2.18.dist-info/RECORD +15 -0
- pdfdancer_client_python-0.2.17.dist-info/RECORD +0 -13
- {pdfdancer_client_python-0.2.17.dist-info → pdfdancer_client_python-0.2.18.dist-info}/WHEEL +0 -0
- {pdfdancer_client_python-0.2.17.dist-info → pdfdancer_client_python-0.2.18.dist-info}/licenses/LICENSE +0 -0
- {pdfdancer_client_python-0.2.17.dist-info → pdfdancer_client_python-0.2.18.dist-info}/licenses/NOTICE +0 -0
- {pdfdancer_client_python-0.2.17.dist-info → pdfdancer_client_python-0.2.18.dist-info}/top_level.txt +0 -0
pdfdancer/__init__.py
CHANGED
|
@@ -13,14 +13,18 @@ from .exceptions import (
|
|
|
13
13
|
from .models import (
|
|
14
14
|
ObjectRef, Position, ObjectType, Font, Color, Image, BoundingRect, Paragraph, FormFieldRef, TextObjectRef,
|
|
15
15
|
PageRef, PositionMode, ShapeType, Point, StandardFonts, PageSize, Orientation, TextStatus, FontRecommendation,
|
|
16
|
-
FontType
|
|
16
|
+
FontType, PathSegment, Line, Bezier, Path
|
|
17
17
|
)
|
|
18
18
|
from .paragraph_builder import ParagraphBuilder
|
|
19
|
+
from .path_builder import PathBuilder, LineBuilder, BezierBuilder
|
|
19
20
|
|
|
20
21
|
__version__ = "1.0.0"
|
|
21
22
|
__all__ = [
|
|
22
23
|
"PDFDancer",
|
|
23
24
|
"ParagraphBuilder",
|
|
25
|
+
"PathBuilder",
|
|
26
|
+
"LineBuilder",
|
|
27
|
+
"BezierBuilder",
|
|
24
28
|
"ObjectRef",
|
|
25
29
|
"Position",
|
|
26
30
|
"ObjectType",
|
|
@@ -41,6 +45,10 @@ __all__ = [
|
|
|
41
45
|
"TextStatus",
|
|
42
46
|
"FontRecommendation",
|
|
43
47
|
"FontType",
|
|
48
|
+
"PathSegment",
|
|
49
|
+
"Line",
|
|
50
|
+
"Bezier",
|
|
51
|
+
"Path",
|
|
44
52
|
"PdfDancerException",
|
|
45
53
|
"FontNotFoundException",
|
|
46
54
|
"ValidationException",
|
pdfdancer/exceptions.py
CHANGED
|
@@ -5,7 +5,7 @@ Mirrors the Java client exception hierarchy.
|
|
|
5
5
|
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import httpx
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class PdfDancerException(Exception):
|
|
@@ -32,10 +32,10 @@ class FontNotFoundException(PdfDancerException):
|
|
|
32
32
|
class HttpClientException(PdfDancerException):
|
|
33
33
|
"""
|
|
34
34
|
Exception raised for HTTP client errors during API communication.
|
|
35
|
-
Wraps
|
|
35
|
+
Wraps httpx exceptions and HTTP errors from the API.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
|
-
def __init__(self, message: str, response: Optional[
|
|
38
|
+
def __init__(self, message: str, response: Optional[httpx.Response] = None, cause: Optional[Exception] = None):
|
|
39
39
|
super().__init__(message, cause)
|
|
40
40
|
self.response = response
|
|
41
41
|
self.status_code = response.status_code if response else None
|
pdfdancer/fingerprint.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import locale
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import socket
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Fingerprint:
|
|
12
|
+
"""Generates a fingerprint hash for API request identification."""
|
|
13
|
+
|
|
14
|
+
_SALT_FILE = Path.home() / ".pdfdancer" / "fingerprint.salt"
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def generate(cls) -> str:
|
|
18
|
+
"""Generate X-Fingerprint header value.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
SHA256 hash of fingerprint components
|
|
22
|
+
"""
|
|
23
|
+
ip_hash = cls._get_local_ip()
|
|
24
|
+
uid_hash = cls._get_uid()
|
|
25
|
+
os_type = platform.system()
|
|
26
|
+
sdk_language = "python"
|
|
27
|
+
timezone = cls._get_timezone()
|
|
28
|
+
locale_str = cls._get_locale()
|
|
29
|
+
domain_hash = cls._get_hostname()
|
|
30
|
+
install_salt = cls._get_or_create_salt()
|
|
31
|
+
|
|
32
|
+
fingerprint_data = (
|
|
33
|
+
ip_hash +
|
|
34
|
+
uid_hash +
|
|
35
|
+
os_type +
|
|
36
|
+
sdk_language +
|
|
37
|
+
timezone +
|
|
38
|
+
locale_str +
|
|
39
|
+
domain_hash +
|
|
40
|
+
install_salt
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return cls._hash(fingerprint_data)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _get_local_ip(cls) -> str:
|
|
47
|
+
"""Get local IP address."""
|
|
48
|
+
try:
|
|
49
|
+
return socket.gethostbyname(socket.gethostname())
|
|
50
|
+
except Exception:
|
|
51
|
+
return "unknown"
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _get_uid(cls) -> str:
|
|
55
|
+
"""Get user login name."""
|
|
56
|
+
try:
|
|
57
|
+
return os.getlogin()
|
|
58
|
+
except Exception:
|
|
59
|
+
return "unknown"
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def _get_timezone(cls) -> str:
|
|
63
|
+
"""Get timezone name."""
|
|
64
|
+
try:
|
|
65
|
+
tz = datetime.now().astimezone().tzinfo
|
|
66
|
+
timezone_name = getattr(tz, 'key', str(tz))
|
|
67
|
+
return timezone_name
|
|
68
|
+
except Exception:
|
|
69
|
+
return "unknown"
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def _get_locale(cls) -> str:
|
|
73
|
+
"""Get default locale."""
|
|
74
|
+
try:
|
|
75
|
+
loc = locale.getlocale()[0]
|
|
76
|
+
return loc if loc else "en_US"
|
|
77
|
+
except Exception:
|
|
78
|
+
return "unknown"
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def _get_hostname(cls) -> str:
|
|
82
|
+
"""Get local hostname."""
|
|
83
|
+
try:
|
|
84
|
+
return socket.gethostname()
|
|
85
|
+
except Exception:
|
|
86
|
+
return "unknown"
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def _get_or_create_salt(cls) -> str:
|
|
90
|
+
"""Get or create persistent install salt.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
UUID string used as install salt
|
|
94
|
+
"""
|
|
95
|
+
if cls._SALT_FILE.exists():
|
|
96
|
+
try:
|
|
97
|
+
return cls._SALT_FILE.read_text().strip()
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# Create salt file
|
|
102
|
+
salt = str(uuid.uuid4())
|
|
103
|
+
try:
|
|
104
|
+
cls._SALT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
cls._SALT_FILE.write_text(salt)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
return salt
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def _hash(cls, value: str) -> str:
|
|
113
|
+
"""Generate SHA256 hash of value.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
value: String to hash
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Hexadecimal SHA256 hash
|
|
120
|
+
"""
|
|
121
|
+
return hashlib.sha256(value.encode('utf-8')).hexdigest()
|
pdfdancer/models.py
CHANGED
|
@@ -164,6 +164,8 @@ class ObjectType(Enum):
|
|
|
164
164
|
TEXT_FIELD = "TEXT_FIELD"
|
|
165
165
|
CHECK_BOX = "CHECK_BOX"
|
|
166
166
|
RADIO_BUTTON = "RADIO_BUTTON"
|
|
167
|
+
BUTTON = "BUTTON"
|
|
168
|
+
DROPDOWN = "DROPDOWN"
|
|
167
169
|
|
|
168
170
|
|
|
169
171
|
class PositionMode(Enum):
|
|
@@ -309,10 +311,15 @@ class ObjectRef:
|
|
|
309
311
|
|
|
310
312
|
def to_dict(self) -> dict:
|
|
311
313
|
"""Convert to dictionary for JSON serialization."""
|
|
314
|
+
# Normalize type back to API format (API uses "CHECKBOX" not "CHECK_BOX")
|
|
315
|
+
type_value = self.type.value
|
|
316
|
+
if type_value == "CHECK_BOX":
|
|
317
|
+
type_value = "CHECKBOX"
|
|
318
|
+
|
|
312
319
|
return {
|
|
313
320
|
"internalId": self.internal_id,
|
|
314
321
|
"position": FindRequest._position_to_dict(self.position),
|
|
315
|
-
"type":
|
|
322
|
+
"type": type_value
|
|
316
323
|
}
|
|
317
324
|
|
|
318
325
|
|
|
@@ -341,6 +348,117 @@ class Font:
|
|
|
341
348
|
raise ValueError(f"Font size must be positive, got {self.size}")
|
|
342
349
|
|
|
343
350
|
|
|
351
|
+
@dataclass
|
|
352
|
+
class PathSegment:
|
|
353
|
+
"""
|
|
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.
|
|
358
|
+
"""
|
|
359
|
+
stroke_color: Optional[Color] = None
|
|
360
|
+
fill_color: Optional[Color] = None
|
|
361
|
+
stroke_width: Optional[float] = None
|
|
362
|
+
dash_array: Optional[List[float]] = None
|
|
363
|
+
dash_phase: Optional[float] = None
|
|
364
|
+
|
|
365
|
+
def get_stroke_color(self) -> Optional[Color]:
|
|
366
|
+
"""Color used for drawing the segment's outline or stroke."""
|
|
367
|
+
return self.stroke_color
|
|
368
|
+
|
|
369
|
+
def get_fill_color(self) -> Optional[Color]:
|
|
370
|
+
"""Color used for filling the segment's interior area (if applicable)."""
|
|
371
|
+
return self.fill_color
|
|
372
|
+
|
|
373
|
+
def get_stroke_width(self) -> Optional[float]:
|
|
374
|
+
"""Width of the stroke line in PDF coordinate units."""
|
|
375
|
+
return self.stroke_width
|
|
376
|
+
|
|
377
|
+
def get_dash_array(self) -> Optional[List[float]]:
|
|
378
|
+
"""Dash pattern for stroking the path segment. Null or empty means solid line."""
|
|
379
|
+
return self.dash_array
|
|
380
|
+
|
|
381
|
+
def get_dash_phase(self) -> Optional[float]:
|
|
382
|
+
"""Dash phase (offset) into the dash pattern in user space units."""
|
|
383
|
+
return self.dash_phase
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@dataclass
|
|
387
|
+
class Line(PathSegment):
|
|
388
|
+
"""
|
|
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.
|
|
392
|
+
"""
|
|
393
|
+
p0: Optional[Point] = None
|
|
394
|
+
p1: Optional[Point] = None
|
|
395
|
+
|
|
396
|
+
def get_p0(self) -> Optional[Point]:
|
|
397
|
+
"""Returns the starting point of this line segment."""
|
|
398
|
+
return self.p0
|
|
399
|
+
|
|
400
|
+
def get_p1(self) -> Optional[Point]:
|
|
401
|
+
"""Returns the ending point of this line segment."""
|
|
402
|
+
return self.p1
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass
|
|
406
|
+
class Bezier(PathSegment):
|
|
407
|
+
"""
|
|
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.
|
|
411
|
+
"""
|
|
412
|
+
p0: Optional[Point] = None
|
|
413
|
+
p1: Optional[Point] = None
|
|
414
|
+
p2: Optional[Point] = None
|
|
415
|
+
p3: Optional[Point] = None
|
|
416
|
+
|
|
417
|
+
def get_p0(self) -> Optional[Point]:
|
|
418
|
+
"""Returns the starting point p0 of this Bezier segment."""
|
|
419
|
+
return self.p0
|
|
420
|
+
|
|
421
|
+
def get_p1(self) -> Optional[Point]:
|
|
422
|
+
"""Returns the first control point p1 of this Bezier segment."""
|
|
423
|
+
return self.p1
|
|
424
|
+
|
|
425
|
+
def get_p2(self) -> Optional[Point]:
|
|
426
|
+
"""Returns the second control point p2 of this Bezier segment."""
|
|
427
|
+
return self.p2
|
|
428
|
+
|
|
429
|
+
def get_p3(self) -> Optional[Point]:
|
|
430
|
+
"""Returns the ending point p3 of this Bezier segment."""
|
|
431
|
+
return self.p3
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@dataclass
|
|
435
|
+
class Path:
|
|
436
|
+
"""
|
|
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.
|
|
440
|
+
"""
|
|
441
|
+
position: Optional[Position] = None
|
|
442
|
+
path_segments: Optional[List[PathSegment]] = None
|
|
443
|
+
even_odd_fill: Optional[bool] = None
|
|
444
|
+
|
|
445
|
+
def get_position(self) -> Optional[Position]:
|
|
446
|
+
"""Returns the position of this path."""
|
|
447
|
+
return self.position
|
|
448
|
+
|
|
449
|
+
def set_position(self, position: Position) -> None:
|
|
450
|
+
"""Sets the position of this path."""
|
|
451
|
+
self.position = position
|
|
452
|
+
|
|
453
|
+
def get_path_segments(self) -> Optional[List[PathSegment]]:
|
|
454
|
+
"""Returns the list of path segments that compose this path."""
|
|
455
|
+
return self.path_segments
|
|
456
|
+
|
|
457
|
+
def get_even_odd_fill(self) -> Optional[bool]:
|
|
458
|
+
"""Returns whether even-odd fill rule should be used (true) or nonzero (false)."""
|
|
459
|
+
return self.even_odd_fill
|
|
460
|
+
|
|
461
|
+
|
|
344
462
|
@dataclass
|
|
345
463
|
class Image:
|
|
346
464
|
"""
|
|
@@ -428,12 +546,9 @@ class DeleteRequest:
|
|
|
428
546
|
|
|
429
547
|
def to_dict(self) -> dict:
|
|
430
548
|
"""Convert to dictionary for JSON serialization."""
|
|
549
|
+
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
431
550
|
return {
|
|
432
|
-
"objectRef":
|
|
433
|
-
"internalId": self.object_ref.internal_id,
|
|
434
|
-
"position": FindRequest._position_to_dict(self.object_ref.position),
|
|
435
|
-
"type": self.object_ref.type.value
|
|
436
|
-
}
|
|
551
|
+
"objectRef": self.object_ref.to_dict()
|
|
437
552
|
}
|
|
438
553
|
|
|
439
554
|
|
|
@@ -446,12 +561,9 @@ class MoveRequest:
|
|
|
446
561
|
def to_dict(self) -> dict:
|
|
447
562
|
"""Convert to dictionary for JSON serialization."""
|
|
448
563
|
# Server API expects the new coordinates under 'newPosition'
|
|
564
|
+
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
449
565
|
return {
|
|
450
|
-
"objectRef":
|
|
451
|
-
"internalId": self.object_ref.internal_id,
|
|
452
|
-
"position": FindRequest._position_to_dict(self.object_ref.position),
|
|
453
|
-
"type": self.object_ref.type.value
|
|
454
|
-
},
|
|
566
|
+
"objectRef": self.object_ref.to_dict(),
|
|
455
567
|
"newPosition": FindRequest._position_to_dict(self.position)
|
|
456
568
|
}
|
|
457
569
|
|
|
@@ -487,7 +599,26 @@ class AddRequest:
|
|
|
487
599
|
def _object_to_dict(self, obj: Any) -> dict:
|
|
488
600
|
"""Convert PDF object to dictionary for JSON serialization."""
|
|
489
601
|
import base64
|
|
490
|
-
|
|
602
|
+
from .models import Path as PathModel, Line, Bezier, PathSegment
|
|
603
|
+
|
|
604
|
+
if isinstance(obj, PathModel):
|
|
605
|
+
# Serialize Path object
|
|
606
|
+
segments = []
|
|
607
|
+
if obj.path_segments:
|
|
608
|
+
for seg in obj.path_segments:
|
|
609
|
+
seg_dict = self._segment_to_dict(seg)
|
|
610
|
+
# Include per-segment position to satisfy backend validation (matches Java client)
|
|
611
|
+
if obj.position:
|
|
612
|
+
seg_dict["position"] = FindRequest._position_to_dict(obj.position)
|
|
613
|
+
segments.append(seg_dict)
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
"type": "PATH",
|
|
617
|
+
"position": FindRequest._position_to_dict(obj.position) if obj.position else None,
|
|
618
|
+
"pathSegments": segments if segments else None,
|
|
619
|
+
"evenOddFill": obj.even_odd_fill
|
|
620
|
+
}
|
|
621
|
+
elif isinstance(obj, Image):
|
|
491
622
|
size = None
|
|
492
623
|
if obj.width is not None and obj.height is not None:
|
|
493
624
|
size = {"width": obj.width, "height": obj.height}
|
|
@@ -538,6 +669,61 @@ class AddRequest:
|
|
|
538
669
|
else:
|
|
539
670
|
raise ValueError(f"Unsupported object type: {type(obj)}")
|
|
540
671
|
|
|
672
|
+
def _segment_to_dict(self, segment: 'PathSegment') -> dict:
|
|
673
|
+
"""Convert a PathSegment (Line or Bezier) to dictionary for JSON serialization."""
|
|
674
|
+
from .models import Line, Bezier
|
|
675
|
+
|
|
676
|
+
result = {}
|
|
677
|
+
|
|
678
|
+
# Add common PathSegment properties
|
|
679
|
+
if segment.stroke_color:
|
|
680
|
+
result["strokeColor"] = {
|
|
681
|
+
"red": segment.stroke_color.r,
|
|
682
|
+
"green": segment.stroke_color.g,
|
|
683
|
+
"blue": segment.stroke_color.b,
|
|
684
|
+
"alpha": segment.stroke_color.a
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if segment.fill_color:
|
|
688
|
+
result["fillColor"] = {
|
|
689
|
+
"red": segment.fill_color.r,
|
|
690
|
+
"green": segment.fill_color.g,
|
|
691
|
+
"blue": segment.fill_color.b,
|
|
692
|
+
"alpha": segment.fill_color.a
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if segment.stroke_width is not None:
|
|
696
|
+
result["strokeWidth"] = segment.stroke_width
|
|
697
|
+
|
|
698
|
+
if segment.dash_array:
|
|
699
|
+
result["dashArray"] = segment.dash_array
|
|
700
|
+
|
|
701
|
+
if segment.dash_phase is not None:
|
|
702
|
+
result["dashPhase"] = segment.dash_phase
|
|
703
|
+
|
|
704
|
+
# Add segment-specific properties
|
|
705
|
+
if isinstance(segment, Line):
|
|
706
|
+
result["type"] = "LINE"
|
|
707
|
+
result["segmentType"] = "LINE"
|
|
708
|
+
if segment.p0:
|
|
709
|
+
result["p0"] = {"x": segment.p0.x, "y": segment.p0.y}
|
|
710
|
+
if segment.p1:
|
|
711
|
+
result["p1"] = {"x": segment.p1.x, "y": segment.p1.y}
|
|
712
|
+
|
|
713
|
+
elif isinstance(segment, Bezier):
|
|
714
|
+
result["type"] = "BEZIER"
|
|
715
|
+
result["segmentType"] = "BEZIER"
|
|
716
|
+
if segment.p0:
|
|
717
|
+
result["p0"] = {"x": segment.p0.x, "y": segment.p0.y}
|
|
718
|
+
if segment.p1:
|
|
719
|
+
result["p1"] = {"x": segment.p1.x, "y": segment.p1.y}
|
|
720
|
+
if segment.p2:
|
|
721
|
+
result["p2"] = {"x": segment.p2.x, "y": segment.p2.y}
|
|
722
|
+
if segment.p3:
|
|
723
|
+
result["p3"] = {"x": segment.p3.x, "y": segment.p3.y}
|
|
724
|
+
|
|
725
|
+
return result
|
|
726
|
+
|
|
541
727
|
|
|
542
728
|
@dataclass
|
|
543
729
|
class ModifyRequest:
|
|
@@ -547,12 +733,9 @@ class ModifyRequest:
|
|
|
547
733
|
|
|
548
734
|
def to_dict(self) -> dict:
|
|
549
735
|
"""Convert to dictionary for JSON serialization."""
|
|
736
|
+
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
550
737
|
return {
|
|
551
|
-
"ref":
|
|
552
|
-
"internalId": self.object_ref.internal_id,
|
|
553
|
-
"position": FindRequest._position_to_dict(self.object_ref.position),
|
|
554
|
-
"type": self.object_ref.type.value
|
|
555
|
-
},
|
|
738
|
+
"ref": self.object_ref.to_dict(),
|
|
556
739
|
"newObject": AddRequest(None)._object_to_dict(self.new_object)
|
|
557
740
|
}
|
|
558
741
|
|
|
@@ -565,12 +748,9 @@ class ModifyTextRequest:
|
|
|
565
748
|
|
|
566
749
|
def to_dict(self) -> dict:
|
|
567
750
|
"""Convert to dictionary for JSON serialization."""
|
|
751
|
+
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
568
752
|
return {
|
|
569
|
-
"ref":
|
|
570
|
-
"internalId": self.object_ref.internal_id,
|
|
571
|
-
"position": FindRequest._position_to_dict(self.object_ref.position),
|
|
572
|
-
"type": self.object_ref.type.value
|
|
573
|
-
},
|
|
753
|
+
"ref": self.object_ref.to_dict(),
|
|
574
754
|
"newTextLine": self.new_text
|
|
575
755
|
}
|
|
576
756
|
|
|
@@ -582,12 +762,9 @@ class ChangeFormFieldRequest:
|
|
|
582
762
|
|
|
583
763
|
def to_dict(self) -> dict:
|
|
584
764
|
"""Convert to dictionary for JSON serialization."""
|
|
765
|
+
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
585
766
|
return {
|
|
586
|
-
"ref":
|
|
587
|
-
"internalId": self.object_ref.internal_id,
|
|
588
|
-
"position": FindRequest._position_to_dict(self.object_ref.position),
|
|
589
|
-
"type": self.object_ref.type.value
|
|
590
|
-
},
|
|
767
|
+
"ref": self.object_ref.to_dict(),
|
|
591
768
|
"value": self.value
|
|
592
769
|
}
|
|
593
770
|
|
|
@@ -753,3 +930,42 @@ class CommandResult:
|
|
|
753
930
|
@classmethod
|
|
754
931
|
def empty(cls, command_name: str, element_id: str | None) -> 'CommandResult':
|
|
755
932
|
return CommandResult(command_name=command_name, element_id=element_id, message=None, success=True, warning=None)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
@dataclass
|
|
936
|
+
class PageSnapshot:
|
|
937
|
+
"""
|
|
938
|
+
Snapshot of a single page containing all elements and page metadata.
|
|
939
|
+
"""
|
|
940
|
+
page_ref: PageRef
|
|
941
|
+
elements: List[ObjectRef]
|
|
942
|
+
|
|
943
|
+
def get_page_ref(self) -> PageRef:
|
|
944
|
+
"""Get the page reference."""
|
|
945
|
+
return self.page_ref
|
|
946
|
+
|
|
947
|
+
def get_elements(self) -> List[ObjectRef]:
|
|
948
|
+
"""Get the list of elements on this page."""
|
|
949
|
+
return self.elements
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
@dataclass
|
|
953
|
+
class DocumentSnapshot:
|
|
954
|
+
"""
|
|
955
|
+
Snapshot of the entire document containing all pages and font information.
|
|
956
|
+
"""
|
|
957
|
+
page_count: int
|
|
958
|
+
fonts: List[FontRecommendation]
|
|
959
|
+
pages: List[PageSnapshot]
|
|
960
|
+
|
|
961
|
+
def get_page_count(self) -> int:
|
|
962
|
+
"""Get the total number of pages."""
|
|
963
|
+
return self.page_count
|
|
964
|
+
|
|
965
|
+
def get_fonts(self) -> List[FontRecommendation]:
|
|
966
|
+
"""Get the list of fonts used in the document."""
|
|
967
|
+
return self.fonts
|
|
968
|
+
|
|
969
|
+
def get_pages(self) -> List[PageSnapshot]:
|
|
970
|
+
"""Get the list of page snapshots."""
|
|
971
|
+
return self.pages
|