pdfdancer-client-python 0.2.14__tar.gz → 0.2.15__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.
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/PKG-INFO +1 -1
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/pyproject.toml +1 -1
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/__init__.py +5 -1
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/models.py +87 -14
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/pdfdancer_v1.py +33 -9
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/types.py +18 -5
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer_client_python.egg-info/PKG-INFO +1 -1
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/pdf_assertions.py +0 -2
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_form_x_objects.py +0 -2
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_line.py +56 -6
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_new_pdf.py +25 -3
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_paragraph.py +98 -12
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/.gitignore +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/README.md +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/docs/openapi.yml +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/release.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/setup.cfg +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/exceptions.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer_client_python.egg-info/SOURCES.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/__init__.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_acroform.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_image.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_path.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/ObviouslyAwesome.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_models.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_pdf_object_equality.py +0 -0
- {pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_standard_fonts.py +0 -0
|
@@ -12,7 +12,8 @@ from .exceptions import (
|
|
|
12
12
|
)
|
|
13
13
|
from .models import (
|
|
14
14
|
ObjectRef, Position, ObjectType, Font, Color, Image, BoundingRect, Paragraph, FormFieldRef, TextObjectRef,
|
|
15
|
-
PageRef, PositionMode, ShapeType, Point, StandardFonts, PageSize, Orientation
|
|
15
|
+
PageRef, PositionMode, ShapeType, Point, StandardFonts, PageSize, Orientation, TextStatus, FontRecommendation,
|
|
16
|
+
FontType
|
|
16
17
|
)
|
|
17
18
|
from .paragraph_builder import ParagraphBuilder
|
|
18
19
|
|
|
@@ -37,6 +38,9 @@ __all__ = [
|
|
|
37
38
|
"StandardFonts",
|
|
38
39
|
"PageSize",
|
|
39
40
|
"Orientation",
|
|
41
|
+
"TextStatus",
|
|
42
|
+
"FontRecommendation",
|
|
43
|
+
"FontType",
|
|
40
44
|
"PdfDancerException",
|
|
41
45
|
"FontNotFoundException",
|
|
42
46
|
"ValidationException",
|
|
@@ -154,7 +154,6 @@ class StandardFonts(Enum):
|
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
class ObjectType(Enum):
|
|
157
|
-
"""Object type enumeration matching the Java ObjectType."""
|
|
158
157
|
FORM_FIELD = "FORM_FIELD"
|
|
159
158
|
IMAGE = "IMAGE"
|
|
160
159
|
FORM_X_OBJECT = "FORM_X_OBJECT"
|
|
@@ -192,7 +191,6 @@ class Point:
|
|
|
192
191
|
class BoundingRect:
|
|
193
192
|
"""
|
|
194
193
|
Represents a bounding rectangle with position and dimensions.
|
|
195
|
-
Matches the Java BoundingRect class.
|
|
196
194
|
"""
|
|
197
195
|
x: float
|
|
198
196
|
y: float
|
|
@@ -216,7 +214,6 @@ class BoundingRect:
|
|
|
216
214
|
class Position:
|
|
217
215
|
"""
|
|
218
216
|
Represents spatial positioning and location information for PDF objects.
|
|
219
|
-
Closely mirrors the Java Position class with Python conventions.
|
|
220
217
|
"""
|
|
221
218
|
page_index: Optional[int] = None
|
|
222
219
|
shape: Optional[ShapeType] = None
|
|
@@ -230,7 +227,6 @@ class Position:
|
|
|
230
227
|
def at_page(page_index: int) -> 'Position':
|
|
231
228
|
"""
|
|
232
229
|
Creates a position specification for an entire page.
|
|
233
|
-
Equivalent to Position.fromPageIndex() in Java.
|
|
234
230
|
"""
|
|
235
231
|
return Position(page_index=page_index, mode=PositionMode.CONTAINS)
|
|
236
232
|
|
|
@@ -238,7 +234,6 @@ class Position:
|
|
|
238
234
|
def at_page_coordinates(page_index: int, x: float, y: float) -> 'Position':
|
|
239
235
|
"""
|
|
240
236
|
Creates a position specification for specific coordinates on a page.
|
|
241
|
-
Equivalent to Position.onPageCoordinates() in Java.
|
|
242
237
|
"""
|
|
243
238
|
position = Position.at_page(page_index)
|
|
244
239
|
position.at_coordinates(Point(x, y))
|
|
@@ -248,7 +243,6 @@ class Position:
|
|
|
248
243
|
def by_name(name: str) -> 'Position':
|
|
249
244
|
"""
|
|
250
245
|
Creates a position specification for finding objects by name.
|
|
251
|
-
Equivalent to Position.byName() in Java.
|
|
252
246
|
"""
|
|
253
247
|
position = Position()
|
|
254
248
|
position.name = name
|
|
@@ -257,7 +251,6 @@ class Position:
|
|
|
257
251
|
def at_coordinates(self, point: Point) -> 'Position':
|
|
258
252
|
"""
|
|
259
253
|
Sets the position to a specific point location.
|
|
260
|
-
Equivalent to Position.set() in Java.
|
|
261
254
|
"""
|
|
262
255
|
self.mode = PositionMode.CONTAINS
|
|
263
256
|
self.shape = ShapeType.POINT
|
|
@@ -293,7 +286,6 @@ class Position:
|
|
|
293
286
|
class ObjectRef:
|
|
294
287
|
"""
|
|
295
288
|
Lightweight reference to a PDF object providing identity and type information.
|
|
296
|
-
Mirrors the Java ObjectRef class exactly.
|
|
297
289
|
"""
|
|
298
290
|
internal_id: str
|
|
299
291
|
position: Position
|
|
@@ -333,7 +325,6 @@ class Color:
|
|
|
333
325
|
a: int = 255 # Alpha channel, default fully opaque
|
|
334
326
|
|
|
335
327
|
def __post_init__(self):
|
|
336
|
-
# Validation similar to Java client
|
|
337
328
|
for component in [self.r, self.g, self.b, self.a]:
|
|
338
329
|
if not 0 <= component <= 255:
|
|
339
330
|
raise ValueError(f"Color component must be between 0 and 255, got {component}")
|
|
@@ -354,7 +345,6 @@ class Font:
|
|
|
354
345
|
class Image:
|
|
355
346
|
"""
|
|
356
347
|
Represents an image object in a PDF document.
|
|
357
|
-
Matches the Java Image class structure.
|
|
358
348
|
"""
|
|
359
349
|
position: Optional[Position] = None
|
|
360
350
|
format: Optional[str] = None
|
|
@@ -375,7 +365,6 @@ class Image:
|
|
|
375
365
|
class Paragraph:
|
|
376
366
|
"""
|
|
377
367
|
Represents a paragraph of text in a PDF document.
|
|
378
|
-
Structure mirrors the Java Paragraph class.
|
|
379
368
|
"""
|
|
380
369
|
position: Optional[Position] = None
|
|
381
370
|
text_lines: Optional[List[str]] = None
|
|
@@ -456,7 +445,7 @@ class MoveRequest:
|
|
|
456
445
|
|
|
457
446
|
def to_dict(self) -> dict:
|
|
458
447
|
"""Convert to dictionary for JSON serialization."""
|
|
459
|
-
# Server API expects the new coordinates under 'newPosition'
|
|
448
|
+
# Server API expects the new coordinates under 'newPosition'
|
|
460
449
|
return {
|
|
461
450
|
"objectRef": {
|
|
462
451
|
"internalId": self.object_ref.internal_id,
|
|
@@ -488,7 +477,7 @@ class AddRequest:
|
|
|
488
477
|
def to_dict(self) -> dict:
|
|
489
478
|
"""Convert to dictionary for JSON serialization matching server API.
|
|
490
479
|
Server expects an AddRequest with a nested 'object' containing the PDFObject
|
|
491
|
-
(with a 'type' discriminator)
|
|
480
|
+
(with a 'type' discriminator).
|
|
492
481
|
"""
|
|
493
482
|
obj = self.pdf_object
|
|
494
483
|
return {
|
|
@@ -621,6 +610,58 @@ class FormFieldRef(ObjectRef):
|
|
|
621
610
|
return self.value
|
|
622
611
|
|
|
623
612
|
|
|
613
|
+
class FontType(Enum):
|
|
614
|
+
"""Font type classification from the PDF."""
|
|
615
|
+
SYSTEM = "SYSTEM"
|
|
616
|
+
STANDARD = "STANDARD"
|
|
617
|
+
EMBEDDED = "EMBEDDED"
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@dataclass
|
|
621
|
+
class FontRecommendation:
|
|
622
|
+
"""Represents a font recommendation with similarity score."""
|
|
623
|
+
font_name: str
|
|
624
|
+
font_type: 'FontType'
|
|
625
|
+
similarity_score: float
|
|
626
|
+
|
|
627
|
+
def get_font_name(self) -> str:
|
|
628
|
+
"""Get the recommended font name."""
|
|
629
|
+
return self.font_name
|
|
630
|
+
|
|
631
|
+
def get_font_type(self) -> 'FontType':
|
|
632
|
+
"""Get the recommended font type."""
|
|
633
|
+
return self.font_type
|
|
634
|
+
|
|
635
|
+
def get_similarity_score(self) -> float:
|
|
636
|
+
"""Get the similarity score."""
|
|
637
|
+
return self.similarity_score
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@dataclass
|
|
641
|
+
class TextStatus:
|
|
642
|
+
"""Status information for text objects."""
|
|
643
|
+
modified: bool
|
|
644
|
+
encodable: bool
|
|
645
|
+
font_type: FontType
|
|
646
|
+
font_recommendation: FontRecommendation
|
|
647
|
+
|
|
648
|
+
def is_modified(self) -> bool:
|
|
649
|
+
"""Check if the text has been modified."""
|
|
650
|
+
return self.modified
|
|
651
|
+
|
|
652
|
+
def is_encodable(self) -> bool:
|
|
653
|
+
"""Check if the text is encodable."""
|
|
654
|
+
return self.encodable
|
|
655
|
+
|
|
656
|
+
def get_font_type(self) -> FontType:
|
|
657
|
+
"""Get the font type."""
|
|
658
|
+
return self.font_type
|
|
659
|
+
|
|
660
|
+
def get_font_recommendation(self) -> FontRecommendation:
|
|
661
|
+
"""Get the font recommendation."""
|
|
662
|
+
return self.font_recommendation
|
|
663
|
+
|
|
664
|
+
|
|
624
665
|
class TextObjectRef(ObjectRef):
|
|
625
666
|
"""
|
|
626
667
|
Represents a text object reference with additional text-specific properties.
|
|
@@ -630,13 +671,14 @@ class TextObjectRef(ObjectRef):
|
|
|
630
671
|
def __init__(self, internal_id: str, position: Position, object_type: ObjectType,
|
|
631
672
|
text: Optional[str] = None, font_name: Optional[str] = None,
|
|
632
673
|
font_size: Optional[float] = None, line_spacings: Optional[List[float]] = None,
|
|
633
|
-
color: Optional[Color] = None):
|
|
674
|
+
color: Optional[Color] = None, status: Optional[TextStatus] = None):
|
|
634
675
|
super().__init__(internal_id, position, object_type)
|
|
635
676
|
self.text = text
|
|
636
677
|
self.font_name = font_name
|
|
637
678
|
self.font_size = font_size
|
|
638
679
|
self.line_spacings = line_spacings
|
|
639
680
|
self.color = color
|
|
681
|
+
self.status = status
|
|
640
682
|
self.children: List['TextObjectRef'] = []
|
|
641
683
|
|
|
642
684
|
def get_text(self) -> Optional[str]:
|
|
@@ -663,6 +705,10 @@ class TextObjectRef(ObjectRef):
|
|
|
663
705
|
"""Get the child text objects."""
|
|
664
706
|
return self.children
|
|
665
707
|
|
|
708
|
+
def get_status(self) -> Optional[TextStatus]:
|
|
709
|
+
"""Get the status information."""
|
|
710
|
+
return self.status
|
|
711
|
+
|
|
666
712
|
|
|
667
713
|
@dataclass
|
|
668
714
|
class PageRef(ObjectRef):
|
|
@@ -680,3 +726,30 @@ class PageRef(ObjectRef):
|
|
|
680
726
|
def get_orientation(self) -> Optional[Orientation]:
|
|
681
727
|
"""Get the page orientation."""
|
|
682
728
|
return self.orientation
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
@dataclass
|
|
732
|
+
class CommandResult:
|
|
733
|
+
"""
|
|
734
|
+
Result object returned by certain API endpoints indicating the outcome of an operation.
|
|
735
|
+
"""
|
|
736
|
+
command_name: str
|
|
737
|
+
element_id: str | None
|
|
738
|
+
message: str | None
|
|
739
|
+
success: bool
|
|
740
|
+
warning: str | None
|
|
741
|
+
|
|
742
|
+
@classmethod
|
|
743
|
+
def from_dict(cls, data: dict) -> 'CommandResult':
|
|
744
|
+
"""Create a CommandResult from a dictionary response."""
|
|
745
|
+
return cls(
|
|
746
|
+
command_name=data.get('commandName', ''),
|
|
747
|
+
element_id=data.get('elementId', ''),
|
|
748
|
+
message=data.get('message', ''),
|
|
749
|
+
success=data.get('success', False),
|
|
750
|
+
warning=data.get('warning', '')
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
@classmethod
|
|
754
|
+
def empty(cls, command_name: str, element_id: str | None) -> 'CommandResult':
|
|
755
|
+
return CommandResult(command_name=command_name, element_id=element_id, message=None, success=True, warning=None)
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
@@ -27,7 +27,7 @@ from .image_builder import ImageBuilder
|
|
|
27
27
|
from .models import (
|
|
28
28
|
ObjectRef, Position, ObjectType, Font, Image, Paragraph, FormFieldRef, TextObjectRef, PageRef,
|
|
29
29
|
FindRequest, DeleteRequest, MoveRequest, PageMoveRequest, AddRequest, ModifyRequest, ModifyTextRequest,
|
|
30
|
-
ChangeFormFieldRequest,
|
|
30
|
+
ChangeFormFieldRequest, CommandResult,
|
|
31
31
|
ShapeType, PositionMode, PageSize, Orientation
|
|
32
32
|
)
|
|
33
33
|
from .paragraph_builder import ParagraphPageBuilder
|
|
@@ -915,7 +915,7 @@ class PDFDancer:
|
|
|
915
915
|
return ImageBuilder(self)
|
|
916
916
|
|
|
917
917
|
# Modify Operations
|
|
918
|
-
def _modify_paragraph(self, object_ref: ObjectRef, new_paragraph: Union[Paragraph, str]) ->
|
|
918
|
+
def _modify_paragraph(self, object_ref: ObjectRef, new_paragraph: Union[Paragraph, str]) -> CommandResult:
|
|
919
919
|
"""
|
|
920
920
|
Modifies a paragraph object or its text content.
|
|
921
921
|
|
|
@@ -929,20 +929,20 @@ class PDFDancer:
|
|
|
929
929
|
if object_ref is None:
|
|
930
930
|
raise ValidationException("Object reference cannot be null")
|
|
931
931
|
if new_paragraph is None:
|
|
932
|
-
|
|
932
|
+
return CommandResult.empty("ModifyParagraph", object_ref.internal_id)
|
|
933
933
|
|
|
934
934
|
if isinstance(new_paragraph, str):
|
|
935
|
-
# Text modification
|
|
935
|
+
# Text modification - returns CommandResult
|
|
936
936
|
request_data = ModifyTextRequest(object_ref, new_paragraph).to_dict()
|
|
937
937
|
response = self._make_request('PUT', '/pdf/text/paragraph', data=request_data)
|
|
938
|
+
return CommandResult.from_dict(response.json())
|
|
938
939
|
else:
|
|
939
940
|
# Object modification
|
|
940
941
|
request_data = ModifyRequest(object_ref, new_paragraph).to_dict()
|
|
941
942
|
response = self._make_request('PUT', '/pdf/modify', data=request_data)
|
|
943
|
+
return CommandResult.from_dict(response.json())
|
|
942
944
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
def _modify_text_line(self, object_ref: ObjectRef, new_text: str) -> bool:
|
|
945
|
+
def _modify_text_line(self, object_ref: ObjectRef, new_text: str) -> CommandResult:
|
|
946
946
|
"""
|
|
947
947
|
Modifies a text line object.
|
|
948
948
|
|
|
@@ -960,7 +960,7 @@ class PDFDancer:
|
|
|
960
960
|
|
|
961
961
|
request_data = ModifyTextRequest(object_ref, new_text).to_dict()
|
|
962
962
|
response = self._make_request('PUT', '/pdf/text/line', data=request_data)
|
|
963
|
-
return response.json()
|
|
963
|
+
return CommandResult.from_dict(response.json())
|
|
964
964
|
|
|
965
965
|
# Font Operations
|
|
966
966
|
|
|
@@ -1171,6 +1171,29 @@ class PDFDancer:
|
|
|
1171
1171
|
if all(isinstance(v, int) for v in [red, green, blue]):
|
|
1172
1172
|
color = Color(red, green, blue, alpha)
|
|
1173
1173
|
|
|
1174
|
+
# Parse status if present
|
|
1175
|
+
status = None
|
|
1176
|
+
status_data = obj_data.get('status')
|
|
1177
|
+
if isinstance(status_data, dict):
|
|
1178
|
+
from .models import TextStatus, FontRecommendation, FontType
|
|
1179
|
+
|
|
1180
|
+
# Parse font recommendation
|
|
1181
|
+
font_rec_data = status_data.get('fontRecommendation')
|
|
1182
|
+
font_rec = None
|
|
1183
|
+
if isinstance(font_rec_data, dict):
|
|
1184
|
+
font_rec = FontRecommendation(
|
|
1185
|
+
font_name=font_rec_data.get('fontName', ''),
|
|
1186
|
+
font_type=FontType(font_rec_data.get('fontType', 'SYSTEM')),
|
|
1187
|
+
similarity_score=font_rec_data.get('similarityScore', 0.0)
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
status = TextStatus(
|
|
1191
|
+
modified=status_data.get('modified', False),
|
|
1192
|
+
encodable=status_data.get('encodable', True),
|
|
1193
|
+
font_type=FontType(status_data.get('fontType', 'UNKNOWN')),
|
|
1194
|
+
font_recommendation=font_rec
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1174
1197
|
text_object = TextObjectRef(
|
|
1175
1198
|
internal_id=internal_id,
|
|
1176
1199
|
position=position,
|
|
@@ -1179,7 +1202,8 @@ class PDFDancer:
|
|
|
1179
1202
|
font_name=obj_data.get('fontName') if isinstance(obj_data.get('fontName'), str) else None,
|
|
1180
1203
|
font_size=obj_data.get('fontSize') if isinstance(obj_data.get('fontSize'), (int, float)) else None,
|
|
1181
1204
|
line_spacings=line_spacings,
|
|
1182
|
-
color=color
|
|
1205
|
+
color=color,
|
|
1206
|
+
status=status
|
|
1183
1207
|
)
|
|
1184
1208
|
|
|
1185
1209
|
if isinstance(obj_data.get('children'), list) and len(obj_data['children']) > 0:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import statistics
|
|
4
|
+
import sys
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from typing import Optional, List
|
|
6
7
|
|
|
7
8
|
from . import ObjectType, Position, ObjectRef, Point, Paragraph, Font, Color, FormFieldRef, TextObjectRef
|
|
9
|
+
from .models import CommandResult
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
@dataclass
|
|
@@ -177,7 +179,7 @@ class BaseTextEdit:
|
|
|
177
179
|
|
|
178
180
|
|
|
179
181
|
class ParagraphEdit(BaseTextEdit):
|
|
180
|
-
def apply(self) ->
|
|
182
|
+
def apply(self) -> CommandResult:
|
|
181
183
|
if (
|
|
182
184
|
self._position is None
|
|
183
185
|
and self._line_spacing is None
|
|
@@ -186,7 +188,10 @@ class ParagraphEdit(BaseTextEdit):
|
|
|
186
188
|
and self._color is None
|
|
187
189
|
):
|
|
188
190
|
# noinspection PyProtectedMember
|
|
189
|
-
|
|
191
|
+
result = self._target_obj._client._modify_paragraph(self._object_ref, self._new_text)
|
|
192
|
+
if result.warning is not None:
|
|
193
|
+
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
194
|
+
return result
|
|
190
195
|
else:
|
|
191
196
|
new_paragraph = Paragraph(
|
|
192
197
|
position=self._position if self._position is not None else self._object_ref.position,
|
|
@@ -196,7 +201,10 @@ class ParagraphEdit(BaseTextEdit):
|
|
|
196
201
|
color=self._get_color(),
|
|
197
202
|
)
|
|
198
203
|
# noinspection PyProtectedMember
|
|
199
|
-
|
|
204
|
+
result = self._target_obj._client._modify_paragraph(self._object_ref, new_paragraph)
|
|
205
|
+
if result.warning is not None:
|
|
206
|
+
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
207
|
+
return result
|
|
200
208
|
|
|
201
209
|
def _get_line_spacing(self) -> float:
|
|
202
210
|
if self._line_spacing is not None:
|
|
@@ -241,9 +249,14 @@ class TextLineEdit(BaseTextEdit):
|
|
|
241
249
|
and self._color is None
|
|
242
250
|
):
|
|
243
251
|
# noinspection PyProtectedMember
|
|
244
|
-
|
|
252
|
+
result = self._target_obj._client._modify_text_line(self._object_ref, self._new_text)
|
|
253
|
+
if result.warning is not None:
|
|
254
|
+
print(f"WARNING: {result.warning}", file=sys.stderr)
|
|
255
|
+
return result
|
|
245
256
|
else:
|
|
246
|
-
|
|
257
|
+
# noinspection PyProtectedMember
|
|
258
|
+
# return self._target_obj._client._modify_text_line(self._object_ref, new_textline)
|
|
259
|
+
raise UnsupportedOperation("Full TextLineEdit not implemented - TODO")
|
|
247
260
|
|
|
248
261
|
|
|
249
262
|
class ParagraphObject(PDFObjectBase):
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/pdf_assertions.py
RENAMED
|
@@ -169,8 +169,6 @@ class PDFAssertions(object):
|
|
|
169
169
|
if page_index is None:
|
|
170
170
|
for page in self.pdf.pages():
|
|
171
171
|
total = total + len(page.select_elements())
|
|
172
|
-
for e in page.select_elements():
|
|
173
|
-
print(e)
|
|
174
172
|
else:
|
|
175
173
|
total = len(self.pdf.page(page_index).select_elements())
|
|
176
174
|
assert total == nr_of_elements, f"Total number of elements differ, actual {total} != expected {nr_of_elements}"
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
+
from pdfdancer import FontType
|
|
3
4
|
from pdfdancer.pdfdancer_v1 import PDFDancer
|
|
4
5
|
from tests.e2e import _require_env_and_fixture
|
|
5
6
|
from tests.e2e.pdf_assertions import PDFAssertions
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
def test_find_lines_by_position_multi():
|
|
10
|
+
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
11
|
+
|
|
12
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
13
|
+
for i in range(0, 10):
|
|
14
|
+
for line in pdf.select_text_lines():
|
|
15
|
+
assert line.object_ref().status is not None
|
|
16
|
+
assert not line.object_ref().status.is_modified()
|
|
17
|
+
assert line.object_ref().status.is_encodable()
|
|
18
|
+
|
|
19
|
+
|
|
8
20
|
def test_find_lines_by_position():
|
|
9
21
|
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
10
22
|
|
|
@@ -13,16 +25,22 @@ def test_find_lines_by_position():
|
|
|
13
25
|
assert len(lines) == 340
|
|
14
26
|
|
|
15
27
|
first = lines[0]
|
|
16
|
-
assert first.internal_id == "
|
|
28
|
+
assert first.internal_id == "TEXTLINE_000001"
|
|
17
29
|
assert first.position is not None
|
|
18
30
|
assert pytest.approx(first.position.x(), rel=0, abs=1) == 326
|
|
19
31
|
assert pytest.approx(first.position.y(), rel=0, abs=1) == 706
|
|
32
|
+
assert first.object_ref().status is not None
|
|
33
|
+
assert not first.object_ref().status.is_modified()
|
|
34
|
+
assert first.object_ref().status.is_encodable()
|
|
20
35
|
|
|
21
36
|
last = lines[-1]
|
|
22
|
-
assert last.internal_id == "
|
|
37
|
+
assert last.internal_id == "TEXTLINE_000340"
|
|
23
38
|
assert last.position is not None
|
|
24
39
|
assert pytest.approx(last.position.x(), rel=0, abs=2) == 548
|
|
25
40
|
assert pytest.approx(last.position.y(), rel=0, abs=2) == 35
|
|
41
|
+
assert last.object_ref().status is not None
|
|
42
|
+
assert not last.object_ref().status.is_modified()
|
|
43
|
+
assert last.object_ref().status.is_encodable()
|
|
26
44
|
|
|
27
45
|
|
|
28
46
|
def test_find_lines_by_text():
|
|
@@ -33,7 +51,7 @@ def test_find_lines_by_text():
|
|
|
33
51
|
assert len(lines) == 1
|
|
34
52
|
|
|
35
53
|
line = lines[0]
|
|
36
|
-
assert line.internal_id == "
|
|
54
|
+
assert line.internal_id == "TEXTLINE_000002"
|
|
37
55
|
assert pytest.approx(line.position.x(), rel=0, abs=1) == 54
|
|
38
56
|
assert pytest.approx(line.position.y(), rel=0, abs=2) == 606
|
|
39
57
|
|
|
@@ -66,6 +84,10 @@ def test_move_line():
|
|
|
66
84
|
|
|
67
85
|
moved_para = pdf.page(0).select_paragraphs_at(new_x, new_y)[0]
|
|
68
86
|
assert moved_para is not None
|
|
87
|
+
assert moved_para.object_ref().status is not None
|
|
88
|
+
assert moved_para.object_ref().status.is_encodable()
|
|
89
|
+
assert moved_para.object_ref().status.font_type == FontType.EMBEDDED
|
|
90
|
+
assert not moved_para.object_ref().status.is_modified()
|
|
69
91
|
|
|
70
92
|
(
|
|
71
93
|
PDFAssertions(pdf)
|
|
@@ -78,16 +100,44 @@ def test_modify_line():
|
|
|
78
100
|
|
|
79
101
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
80
102
|
line = pdf.page(0).select_text_lines_starting_with("The Complete")[0]
|
|
81
|
-
line.edit().replace(" replaced ").apply()
|
|
103
|
+
result = line.edit().replace(" replaced ").apply()
|
|
104
|
+
|
|
105
|
+
# this should issue a warning about an modified text with an embedded font
|
|
106
|
+
# the information is right now only available when selecting the paragraph again, that's bad
|
|
107
|
+
assert result.warning is not None
|
|
108
|
+
assert "You are using an embedded font and modified the text." in result.warning
|
|
82
109
|
|
|
83
110
|
# Validate replacements
|
|
84
111
|
assert pdf.page(0).select_text_lines_starting_with("The Complete") == []
|
|
85
|
-
|
|
112
|
+
lines = pdf.page(0).select_text_lines_starting_with(" replaced ")
|
|
113
|
+
assert lines != []
|
|
86
114
|
assert pdf.page(0).select_paragraphs_starting_with(" replaced ") != []
|
|
87
|
-
|
|
115
|
+
assert lines[0] is not None
|
|
116
|
+
assert lines[0].object_ref().status is not None
|
|
117
|
+
assert lines[0].object_ref().status.is_encodable
|
|
118
|
+
assert lines[0].object_ref().status.font_type == FontType.EMBEDDED
|
|
119
|
+
assert lines[0].object_ref().status.is_modified
|
|
88
120
|
(
|
|
89
121
|
PDFAssertions(pdf)
|
|
90
122
|
.assert_textline_does_not_exist("The Complete")
|
|
91
123
|
.assert_textline_exists(" replaced ")
|
|
92
124
|
.assert_paragraph_exists(" replaced ")
|
|
93
125
|
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_modify_line_multi():
|
|
129
|
+
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
130
|
+
|
|
131
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
132
|
+
line_text = "The Complete"
|
|
133
|
+
for i in range(0, 10):
|
|
134
|
+
line = pdf.page(0).select_text_lines_starting_with(line_text)[0]
|
|
135
|
+
line_text = f"{i} The Complete C"
|
|
136
|
+
# line.edit().replace(line_text).color(Color(255, 0, 0)).apply()
|
|
137
|
+
assert line.edit().replace(line_text).apply()
|
|
138
|
+
pdf.save("/tmp/test_modify_line_multi.pdf")
|
|
139
|
+
|
|
140
|
+
(
|
|
141
|
+
PDFAssertions(pdf)
|
|
142
|
+
.assert_textline_exists("9 The Complete C")
|
|
143
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from pdfdancer import PDFDancer, PageSize, Orientation, StandardFonts, Color
|
|
3
|
+
from pdfdancer import PDFDancer, PageSize, Orientation, StandardFonts, Color, ValidationException
|
|
4
4
|
from tests.e2e import _require_env
|
|
5
5
|
from tests.e2e.pdf_assertions import PDFAssertions
|
|
6
6
|
|
|
@@ -89,6 +89,30 @@ def test_create_blank_pdf_add_content():
|
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def test_create_blank_pdf_add_and_modify_content():
|
|
93
|
+
"""Test creating a blank PDF and adding content"""
|
|
94
|
+
base_url, token = _require_env()
|
|
95
|
+
|
|
96
|
+
with PDFDancer.new(token=token, base_url=base_url) as pdf:
|
|
97
|
+
(
|
|
98
|
+
pdf.new_paragraph()
|
|
99
|
+
.text("Hello from blank PDF")
|
|
100
|
+
.font(StandardFonts.COURIER_BOLD_OBLIQUE, 9)
|
|
101
|
+
.color(Color(128, 56, 127))
|
|
102
|
+
.at(0, 100, 201.5)
|
|
103
|
+
.add()
|
|
104
|
+
)
|
|
105
|
+
assert pdf.page(0).select_text_lines()[0].internal_id
|
|
106
|
+
pdf.save("/tmp/test_create_blank_pdf_add_and_modify_content.pdf")
|
|
107
|
+
|
|
108
|
+
with PDFDancer.open("/tmp/test_create_blank_pdf_add_and_modify_content.pdf", token=token,
|
|
109
|
+
base_url=base_url) as pdf2:
|
|
110
|
+
for i in range(0, 10):
|
|
111
|
+
line = pdf2.page(0).select_text_lines()[0]
|
|
112
|
+
assert line.edit().replace(f"hello {i}").apply()
|
|
113
|
+
pdf2.save("/tmp/test_create_blank_pdf_add_and_modify_content2.pdf")
|
|
114
|
+
|
|
115
|
+
|
|
92
116
|
def test_create_blank_pdf_add_page():
|
|
93
117
|
base_url, token = _require_env()
|
|
94
118
|
|
|
@@ -109,8 +133,6 @@ def test_create_blank_pdf_invalid_page_count():
|
|
|
109
133
|
"""Test that invalid page count raises validation error"""
|
|
110
134
|
base_url, token = _require_env()
|
|
111
135
|
|
|
112
|
-
from pdfdancer import ValidationException
|
|
113
|
-
|
|
114
136
|
with pytest.raises(ValidationException) as exc_info:
|
|
115
137
|
PDFDancer.new(
|
|
116
138
|
token=token,
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_paragraph.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from pdfdancer import Color, StandardFonts
|
|
3
|
+
from pdfdancer import Color, StandardFonts, FontType
|
|
4
4
|
from pdfdancer.pdfdancer_v1 import PDFDancer
|
|
5
5
|
from tests.e2e import _require_env_and_fixture
|
|
6
6
|
from tests.e2e.pdf_assertions import PDFAssertions
|
|
@@ -28,6 +28,11 @@ def test_find_paragraphs_by_position():
|
|
|
28
28
|
assert pytest.approx(last.position.x(), rel=0, abs=1) == 54
|
|
29
29
|
assert pytest.approx(last.position.y(), rel=0, abs=2) == 496
|
|
30
30
|
|
|
31
|
+
assert last.object_ref().status is not None
|
|
32
|
+
assert last.object_ref().status.is_encodable()
|
|
33
|
+
assert last.object_ref().status.font_type == FontType.EMBEDDED
|
|
34
|
+
assert not last.object_ref().status.is_modified()
|
|
35
|
+
|
|
31
36
|
|
|
32
37
|
def test_find_paragraphs_by_text():
|
|
33
38
|
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
@@ -60,6 +65,11 @@ def test_move_paragraph():
|
|
|
60
65
|
moved = pdf.page(0).select_paragraphs_at(0.1, 300)[0]
|
|
61
66
|
assert moved is not None
|
|
62
67
|
|
|
68
|
+
assert moved.object_ref().status is not None
|
|
69
|
+
assert moved.object_ref().status.is_encodable()
|
|
70
|
+
assert moved.object_ref().status.font_type == FontType.EMBEDDED
|
|
71
|
+
assert not moved.object_ref().status.is_modified()
|
|
72
|
+
|
|
63
73
|
|
|
64
74
|
def test_modify_paragraph():
|
|
65
75
|
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
@@ -76,6 +86,12 @@ def test_modify_paragraph():
|
|
|
76
86
|
.apply()
|
|
77
87
|
)
|
|
78
88
|
|
|
89
|
+
moved = pdf.page(0).select_paragraphs_at(300.1, 500)[0]
|
|
90
|
+
assert moved.object_ref().status is not None
|
|
91
|
+
assert moved.object_ref().status.is_encodable()
|
|
92
|
+
assert moved.object_ref().status.font_type == FontType.STANDARD
|
|
93
|
+
assert moved.object_ref().status.is_modified()
|
|
94
|
+
|
|
79
95
|
(
|
|
80
96
|
PDFAssertions(pdf)
|
|
81
97
|
.assert_textline_has_font("Awesomely", "Helvetica", 12)
|
|
@@ -136,17 +152,75 @@ def test_modify_paragraph_without_position_and_spacing():
|
|
|
136
152
|
)
|
|
137
153
|
|
|
138
154
|
|
|
139
|
-
def
|
|
155
|
+
def test_modify_paragraph_noop():
|
|
140
156
|
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
141
157
|
|
|
142
158
|
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
143
159
|
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
160
|
+
(
|
|
161
|
+
paragraph.edit()
|
|
162
|
+
.apply()
|
|
163
|
+
)
|
|
164
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
165
|
+
assert paragraph.object_ref().status is not None
|
|
166
|
+
assert paragraph.object_ref().status.is_encodable()
|
|
167
|
+
assert paragraph.object_ref().status.font_type == FontType.EMBEDDED
|
|
168
|
+
assert not paragraph.object_ref().status.is_modified()
|
|
169
|
+
|
|
144
170
|
(
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
.
|
|
171
|
+
PDFAssertions(pdf)
|
|
172
|
+
.assert_textline_has_font("The Complete", "IXKSWR+Poppins-Bold", 1)
|
|
173
|
+
.assert_textline_has_color("The Complete", Color(255, 255, 255))
|
|
148
174
|
)
|
|
149
175
|
|
|
176
|
+
|
|
177
|
+
def test_modify_paragraph_only_text():
|
|
178
|
+
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
179
|
+
|
|
180
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
181
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
182
|
+
result = (
|
|
183
|
+
paragraph.edit()
|
|
184
|
+
.replace("lorem\nipsum\nCaesar")
|
|
185
|
+
.apply()
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# this should issue a warning about an modified text with an embedded font
|
|
189
|
+
# the information is right now only available when selecting the paragraph again, that's bad
|
|
190
|
+
assert result.warning is not None
|
|
191
|
+
assert "You are using an embedded font and modified the text." in result.warning
|
|
192
|
+
|
|
193
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("lorem")[0]
|
|
194
|
+
assert paragraph.object_ref().status is not None
|
|
195
|
+
assert paragraph.object_ref().status.is_encodable()
|
|
196
|
+
assert paragraph.object_ref().status.font_type == FontType.EMBEDDED
|
|
197
|
+
assert paragraph.object_ref().status.is_modified()
|
|
198
|
+
|
|
199
|
+
(
|
|
200
|
+
PDFAssertions(pdf)
|
|
201
|
+
.assert_textline_does_not_exist("The Complete")
|
|
202
|
+
.assert_textline_has_color("lorem", Color(255, 255, 255))
|
|
203
|
+
.assert_textline_has_color("ipsum", Color(255, 255, 255))
|
|
204
|
+
.assert_textline_has_color("Caesar", Color(255, 255, 255))
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_modify_paragraph_only_font():
|
|
209
|
+
base_url, token, pdf_path = _require_env_and_fixture("ObviouslyAwesome.pdf")
|
|
210
|
+
|
|
211
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
212
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
213
|
+
(
|
|
214
|
+
paragraph.edit()
|
|
215
|
+
.font("Helvetica", 28)
|
|
216
|
+
.apply()
|
|
217
|
+
)
|
|
218
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
219
|
+
assert paragraph.object_ref().status is not None
|
|
220
|
+
assert paragraph.object_ref().status.is_encodable()
|
|
221
|
+
assert paragraph.object_ref().status.font_type == FontType.STANDARD
|
|
222
|
+
assert paragraph.object_ref().status.is_modified()
|
|
223
|
+
|
|
150
224
|
# TODO does not preserve color and fucks up line spacings
|
|
151
225
|
(
|
|
152
226
|
PDFAssertions(pdf)
|
|
@@ -167,6 +241,12 @@ def test_modify_paragraph_only_move():
|
|
|
167
241
|
.apply()
|
|
168
242
|
)
|
|
169
243
|
|
|
244
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
245
|
+
assert paragraph.object_ref().status is not None
|
|
246
|
+
assert paragraph.object_ref().status.is_encodable()
|
|
247
|
+
assert paragraph.object_ref().status.font_type == FontType.EMBEDDED
|
|
248
|
+
assert paragraph.object_ref().status.is_modified() # This should actually not be marked as 'modified' but since we are using a ModifyObject operation we are not (yet) able to detect this
|
|
249
|
+
|
|
170
250
|
(
|
|
171
251
|
PDFAssertions(pdf)
|
|
172
252
|
.assert_textline_has_font("The Complete", "IXKSWR+Poppins-Bold", 1)
|
|
@@ -182,13 +262,19 @@ def test_modify_paragraph_simple():
|
|
|
182
262
|
paragraph = pdf.page(0).select_paragraphs_starting_with("The Complete")[0]
|
|
183
263
|
paragraph.edit().replace("Awesomely\nObvious!").apply()
|
|
184
264
|
|
|
185
|
-
(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
265
|
+
paragraph = pdf.page(0).select_paragraphs_starting_with("Awesomely")[0]
|
|
266
|
+
assert paragraph.object_ref().status is not None
|
|
267
|
+
assert paragraph.object_ref().status.is_encodable()
|
|
268
|
+
assert paragraph.object_ref().status.font_type == FontType.EMBEDDED
|
|
269
|
+
assert paragraph.object_ref().status.is_modified()
|
|
270
|
+
|
|
271
|
+
(
|
|
272
|
+
PDFAssertions(pdf)
|
|
273
|
+
.assert_textline_has_font("Awesomely", "IXKSWR+Poppins-Bold", 1)
|
|
274
|
+
.assert_textline_has_font("Obvious!", "IXKSWR+Poppins-Bold", 1)
|
|
275
|
+
.assert_textline_has_color("Awesomely", Color(255, 255, 255))
|
|
276
|
+
.assert_textline_has_color("Obvious!", Color(255, 255, 255))
|
|
277
|
+
)
|
|
192
278
|
|
|
193
279
|
|
|
194
280
|
def test_add_paragraph_with_custom_font1_expect_not_found():
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/.claude/commands/discuss.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/exceptions.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/image_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/src/pdfdancer/paragraph_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_acroform.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_pdfdancer.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/e2e/test_positioning.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/basic-paths.pdf
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/fixtures/logo-80.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_openapi_compliance.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_pdf_object_equality.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.14 → pdfdancer_client_python-0.2.15}/tests/test_standard_fonts.py
RENAMED
|
File without changes
|