Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions src/pdfrest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
PdfAddTextPayload,
PdfBlankPayload,
PdfCompressPayload,
PdfConvertColorsPayload,
PdfDecryptPayload,
PdfEncryptPayload,
PdfFlattenAnnotationsPayload,
Expand Down Expand Up @@ -146,6 +147,7 @@
PdfPageOrientation,
PdfPageSelection,
PdfPageSize,
PdfPresetColorProfile,
PdfRedactionInstruction,
PdfRestriction,
PdfRGBColor,
Expand Down Expand Up @@ -3173,6 +3175,38 @@ def blank_pdf(
timeout=timeout,
)

def convert_colors(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
color_profile: PdfPresetColorProfile | PdfRestFile | Sequence[PdfRestFile],
preserve_black: bool = False,
Comment thread
datalogics-kam marked this conversation as resolved.
output: str | None = None,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> PdfRestFileBasedResponse:
"""Convert PDF colors using presets or a custom uploaded ICC profile."""

payload: dict[str, Any] = {
"files": file,
"color_profile": color_profile,
"preserve_black": preserve_black,
}
if output is not None:
payload["output"] = output

return self._post_file_operation(
endpoint="/pdf-with-converted-colors",
payload=payload,
payload_model=PdfConvertColorsPayload,
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)

def flatten_transparencies(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down Expand Up @@ -4876,6 +4910,38 @@ async def blank_pdf(
timeout=timeout,
)

async def convert_colors(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
color_profile: PdfPresetColorProfile | PdfRestFile | Sequence[PdfRestFile],
preserve_black: bool = False,
output: str | None = None,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> PdfRestFileBasedResponse:
"""Asynchronously convert PDF colors using presets or a custom ICC profile."""

payload: dict[str, Any] = {
"files": file,
"color_profile": color_profile,
"preserve_black": preserve_black,
}
if output is not None:
payload["output"] = output

return await self._post_file_operation(
endpoint="/pdf-with-converted-colors",
payload=payload,
payload_model=PdfConvertColorsPayload,
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)

async def flatten_transparencies(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down
107 changes: 107 additions & 0 deletions src/pdfrest/models/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
PdfInfoQuery,
PdfPageOrientation,
PdfPageSize,
PdfPresetColorProfile,
PdfRestriction,
PdfXType,
SummaryFormat,
Expand All @@ -43,6 +44,8 @@
)
from .public import PdfRestFile, PdfRestFileID

PdfConvertColorProfile = PdfPresetColorProfile | Literal["custom"]


def _ensure_list(value: Any) -> Any:
if value is None:
Expand All @@ -54,6 +57,14 @@ def _ensure_list(value: Any) -> Any:
return [value]


def _is_uploaded_file_value(value: Any) -> bool:
if isinstance(value, PdfRestFile):
return True
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
return all(isinstance(item, PdfRestFile) for item in value)
return False


def _list_of_strings(value: list[Any]) -> list[str]:
return [str(e) for e in value]

Expand Down Expand Up @@ -144,6 +155,12 @@ def _bool_to_on_off(value: Any) -> Any:
return value


def _bool_to_true_false(value: Any) -> Any:
if isinstance(value, bool):
return "true" if value else "false"
return value


def _serialize_page_ranges(value: list[str | int | tuple[str | int, ...]]) -> str:
def join_tuple(value: str | int | tuple[str | int, ...]) -> str:
if isinstance(value, tuple):
Expand Down Expand Up @@ -1780,6 +1797,96 @@ def _validate_page_configuration(self) -> PdfBlankPayload:
return self


class PdfConvertColorsPayload(BaseModel):
"""Adapt caller options into a pdfRest-ready convert-colors request payload."""

files: Annotated[
list[PdfRestFile],
Field(
min_length=1,
max_length=1,
validation_alias=AliasChoices("file", "files"),
serialization_alias="id",
),
BeforeValidator(_ensure_list),
AfterValidator(
_allowed_mime_types("application/pdf", error_msg="Must be a PDF file")
),
PlainSerializer(_serialize_as_first_file_id),
]
color_profile: Annotated[
PdfConvertColorProfile,
Field(serialization_alias="color_profile"),
]
custom_profile: Annotated[
list[PdfRestFile] | None,
Field(
default=None,
min_length=1,
max_length=1,
serialization_alias="profile_id",
),
BeforeValidator(_ensure_list),
AfterValidator(
_allowed_mime_types(
"application/vnd.iccprofile",
"application/octet-stream",
error_msg="Profile must be an ICC file",
)
),
PlainSerializer(_serialize_as_first_file_id),
] = None
preserve_black: Annotated[
Literal["true", "false"] | None,
Field(serialization_alias="preserve_black", default=None),
BeforeValidator(_bool_to_true_false),
] = None
output: Annotated[
str | None,
Field(serialization_alias="output", min_length=1, default=None),
AfterValidator(_validate_output_prefix),
] = None

@model_validator(mode="before")
@classmethod
def _normalize_color_profile(cls, value: Any) -> Any:
if not isinstance(value, dict):
return value

payload = cast(dict[str, Any], value).copy()
color_profile = payload.get("color_profile")
if not _is_uploaded_file_value(color_profile):
return payload

if payload.get("custom_profile") is not None:
msg = (
"Provide the custom profile file via color_profile only when "
"color_profile is a file."
)
raise ValueError(msg)

payload["color_profile"] = "custom"
payload["custom_profile"] = color_profile
return payload

@model_validator(mode="after")
Comment thread
datalogics-kam marked this conversation as resolved.
def _validate_profile_dependency(self) -> PdfConvertColorsPayload:
if self.color_profile == "custom":
if not self.custom_profile:
msg = (
"A custom color profile requires an uploaded ICC file passed "
"as color_profile."
)
raise ValueError(msg)
elif self.custom_profile:
msg = (
"A profile can only be provided by passing an uploaded ICC file "
"as color_profile."
)
raise ValueError(msg)
return self


class PdfRestrictPayload(BaseModel):
"""Adapt caller options into a pdfRest-ready restrict-PDF request payload."""

Expand Down
4 changes: 4 additions & 0 deletions src/pdfrest/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
PdfAddTextObject,
PdfAType,
PdfCmykColor,
PdfColorProfile,
PdfConversionCompression,
PdfConversionDownsample,
PdfConversionLocale,
Expand All @@ -28,6 +29,7 @@
PdfPageOrientation,
PdfPageSelection,
PdfPageSize,
PdfPresetColorProfile,
PdfRedactionInstruction,
PdfRedactionPreset,
PdfRedactionType,
Expand Down Expand Up @@ -60,6 +62,7 @@
"PdfAType",
"PdfAddTextObject",
"PdfCmykColor",
"PdfColorProfile",
"PdfConversionCompression",
"PdfConversionDownsample",
"PdfConversionLocale",
Expand All @@ -70,6 +73,7 @@
"PdfPageOrientation",
"PdfPageSelection",
"PdfPageSize",
"PdfPresetColorProfile",
"PdfRGBColor",
"PdfRedactionInstruction",
"PdfRedactionPreset",
Expand Down
20 changes: 20 additions & 0 deletions src/pdfrest/types/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"PdfAType",
"PdfAddTextObject",
"PdfCmykColor",
"PdfColorProfile",
"PdfConversionCompression",
"PdfConversionDownsample",
"PdfConversionLocale",
Expand All @@ -40,6 +41,7 @@
"PdfPageOrientation",
"PdfPageSelection",
"PdfPageSize",
"PdfPresetColorProfile",
"PdfRGBColor",
"PdfRedactionInstruction",
"PdfRedactionPreset",
Expand Down Expand Up @@ -219,3 +221,21 @@ class PdfMergeSource(TypedDict, total=False):
)
PdfPageSize = Literal["letter", "legal", "ledger", "A3", "A4", "A5"] | PdfCustomPageSize
PdfPageOrientation = Literal["portrait", "landscape"]
PdfPresetColorProfile = Literal[
"lab-d50",
"srgb",
"apple-rgb",
"color-match-rgb",
"gamma-18",
"gamma-22",
"dot-gain-10",
"dot-gain-15",
"dot-gain-20",
"dot-gain-25",
"dot-gain-30",
"monitor-rgb",
"acrobat5-cmyk",
"acrobat9-cmyk",
]

PdfColorProfile = PdfPresetColorProfile
Loading