@@ -148,6 +148,11 @@ def _serialize_redactions(value: list[_PdfRedactionVariant]) -> str:
148148 return json .dumps (payload , separators = ("," , ":" ))
149149
150150
151+ def _serialize_text_objects (value : list [BaseModel ]) -> str :
152+ payload = [entry .model_dump (mode = "json" , exclude_none = True ) for entry in value ]
153+ return json .dumps (payload , separators = ("," , ":" ))
154+
155+
151156def _allowed_mime_types (
152157 allowed_mime_types : str , * more_allowed_mime_types : str , error_msg : str | None
153158) -> Callable [[Any ], Any ]:
@@ -563,6 +568,64 @@ class ExtractImagesPayload(BaseModel):
563568
564569
565570RgbChannel = Annotated [int , Field (ge = 0 , le = 255 )]
571+ CmykChannel = Annotated [int , Field (ge = 0 , le = 100 )]
572+
573+
574+ def _validate_rgb_values (value : list [Any ] | tuple [Any , ...] | None ) -> list [int ] | None :
575+ if value is None :
576+ return None
577+ if len (value ) != 3 :
578+ msg = "text_color_rgb must have exactly 3 values."
579+ raise ValueError (msg )
580+ channels : list [int ] = []
581+ for channel in value :
582+ try :
583+ numeric = int (channel )
584+ except (TypeError , ValueError ) as exc :
585+ msg = "text_color_rgb values must be integers."
586+ raise ValueError (msg ) from exc
587+ if not 0 <= numeric <= 255 :
588+ msg = "text_color_rgb values must be between 0 and 255."
589+ raise ValueError (msg )
590+ channels .append (numeric )
591+ return channels
592+
593+
594+ def _validate_cmyk_values (
595+ value : list [Any ] | tuple [Any , ...] | None ,
596+ ) -> list [int ] | None :
597+ if value is None :
598+ return None
599+ if len (value ) != 4 :
600+ msg = "text_color_cmyk must have exactly 4 values."
601+ raise ValueError (msg )
602+ channels : list [int ] = []
603+ for channel in value :
604+ try :
605+ numeric = int (channel )
606+ except (TypeError , ValueError ) as exc :
607+ msg = "text_color_cmyk values must be integers."
608+ raise ValueError (msg ) from exc
609+ if not 0 <= numeric <= 100 :
610+ msg = "text_color_cmyk values must be between 0 and 100."
611+ raise ValueError (msg )
612+ channels .append (numeric )
613+ return channels
614+
615+
616+ def _validate_add_text_page (value : str | int ) -> str | int :
617+ if isinstance (value , str ):
618+ if value .lower () == "all" :
619+ return "all"
620+ if value .isdigit ():
621+ numeric_page = int (value )
622+ if numeric_page >= 1 :
623+ return numeric_page
624+ else :
625+ if value >= 1 :
626+ return value
627+ msg = 'page must be a positive integer or "all".'
628+ raise ValueError (msg )
566629
567630
568631class PdfLiteralRedactionModel (BaseModel ):
@@ -1021,6 +1084,94 @@ def _validate_profile_dependency(self) -> PdfCompressPayload:
10211084 return self
10221085
10231086
1087+ class PdfAddTextObjectModel (BaseModel ):
1088+ """Adapt caller text options for insertion into a PDF."""
1089+
1090+ font : Annotated [str , Field (min_length = 1 , serialization_alias = "font" )]
1091+ max_width : Annotated [
1092+ float ,
1093+ Field (serialization_alias = "max_width" , gt = 0 ),
1094+ ]
1095+ opacity : Annotated [
1096+ float ,
1097+ Field (serialization_alias = "opacity" , ge = 0.0 , le = 1.0 ),
1098+ ]
1099+ page : Annotated [
1100+ str | int ,
1101+ Field (serialization_alias = "page" ),
1102+ AfterValidator (_validate_add_text_page ),
1103+ ]
1104+ rotation : Annotated [float , Field (serialization_alias = "rotation" )]
1105+ text : Annotated [str , Field (min_length = 1 , serialization_alias = "text" )]
1106+ text_color_rgb : Annotated [
1107+ list [int ] | tuple [int , ...] | None ,
1108+ Field (serialization_alias = "text_color_rgb" , default = None ),
1109+ BeforeValidator (_split_comma_string ),
1110+ AfterValidator (_validate_rgb_values ),
1111+ PlainSerializer (_serialize_as_comma_separated_string ),
1112+ ] = None
1113+ text_color_cmyk : Annotated [
1114+ list [int ] | tuple [int , ...] | None ,
1115+ Field (serialization_alias = "text_color_cmyk" , default = None ),
1116+ BeforeValidator (_split_comma_string ),
1117+ AfterValidator (_validate_cmyk_values ),
1118+ PlainSerializer (_serialize_as_comma_separated_string ),
1119+ ] = None
1120+ text_size : Annotated [
1121+ float ,
1122+ Field (serialization_alias = "text_size" , ge = 5 , le = 100 ),
1123+ ]
1124+ x : Annotated [float , Field (serialization_alias = "x" )]
1125+ y : Annotated [float , Field (serialization_alias = "y" )]
1126+ is_rtl : Annotated [
1127+ bool | None ,
1128+ Field (serialization_alias = "is_rtl" , default = None ),
1129+ ] = None
1130+
1131+ @model_validator (mode = "after" )
1132+ def _ensure_single_color_option (self ) -> PdfAddTextObjectModel :
1133+ if self .text_color_rgb is None and self .text_color_cmyk is None :
1134+ msg = "Either text_color_rgb or text_color_cmyk must be provided."
1135+ raise ValueError (msg )
1136+ if self .text_color_rgb is not None and self .text_color_cmyk is not None :
1137+ msg = "Provide only one of text_color_rgb or text_color_cmyk."
1138+ raise ValueError (msg )
1139+ return self
1140+
1141+
1142+ class PdfAddTextPayload (BaseModel ):
1143+ """Adapt caller options into a pdfRest-ready add-text request payload."""
1144+
1145+ files : Annotated [
1146+ list [PdfRestFile ],
1147+ Field (
1148+ min_length = 1 ,
1149+ max_length = 1 ,
1150+ validation_alias = AliasChoices ("file" , "files" ),
1151+ serialization_alias = "id" ,
1152+ ),
1153+ BeforeValidator (_ensure_list ),
1154+ AfterValidator (
1155+ _allowed_mime_types ("application/pdf" , error_msg = "Must be a PDF file" )
1156+ ),
1157+ PlainSerializer (_serialize_as_first_file_id ),
1158+ ]
1159+ text_objects : Annotated [
1160+ list [PdfAddTextObjectModel ],
1161+ Field (
1162+ serialization_alias = "text_objects" ,
1163+ min_length = 1 ,
1164+ ),
1165+ BeforeValidator (_ensure_list ),
1166+ PlainSerializer (_serialize_text_objects ),
1167+ ]
1168+ output : Annotated [
1169+ str | None ,
1170+ Field (serialization_alias = "output" , min_length = 1 , default = None ),
1171+ AfterValidator (_validate_output_prefix ),
1172+ ] = None
1173+
1174+
10241175class PdfAddImagePayload (BaseModel ):
10251176 """Adapt caller options into a pdfRest-ready add-image request payload."""
10261177
0 commit comments