Skip to content
Draft
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
4 changes: 4 additions & 0 deletions docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ Use this group for password protection, permissions, signatures, and standards.

Use this group to improve compatibility, performance, or file size.

- Page geometry:
[set_pdf_page_boxes][pdfrest.PdfRestClient.set_pdf_page_boxes] updates the
MediaBox, CropBox, BleedBox, TrimBox, or ArtBox on selected pages

- Size and optimization:
[compress_pdf][pdfrest.PdfRestClient.compress_pdf],
[linearize_pdf][pdfrest.PdfRestClient.linearize_pdf]
Expand Down
1 change: 1 addition & 0 deletions docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ to the API guide and API reference for exact method names and options.
- Passwords and permissions
- Encryption/decryption/restrictions
- URL-based text-and-data-mining reservation metadata
- Typed MediaBox, CropBox, BleedBox, TrimBox, and ArtBox updates
- Watermarking (text/image)
- Digital signing

Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ supported interpreter matrix.

- `examples/apply_tdm_reservation/apply_tdm_reservation_example.py` – embed a
hosted TDM policy URL in an uploaded PDF.
- `examples/set_pdf_page_boxes/set_pdf_page_boxes_example.py` – enlarge a PDF's
MediaBox and apply a CropBox inset with typed page selections and margins.
- `examples/add_shapes/add_shapes_to_pdf_example.py` – add a styled rectangle
and divider line to a PDF with accessibility tagging enabled.
- `examples/convert_structured_documents/convert_structured_documents_to_pdf_example.py`
Expand Down
85 changes: 85 additions & 0 deletions examples/set_pdf_page_boxes/set_pdf_page_boxes_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Update the MediaBox and CropBox of an uploaded PDF.

This sample demonstrates how to:

1. Load ``PDFREST_API_KEY`` from the environment or a local ``.env`` file.
2. Upload the checked-in ``examples/resources/report.pdf`` input.
3. Enlarge its MediaBox and apply a positive CropBox inset to every page.
4. Print the generated PDF metadata.

Set ``PDFREST_BASE_URL`` only for a self-hosted deployment. Run from the
repository root with
``uv run examples/set_pdf_page_boxes/set_pdf_page_boxes_example.py``.
"""

from __future__ import annotations

import os
from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient
from pdfrest.types import (
PdfInsetPageBoxDefinition,
PdfInsetPageBoxPage,
PdfMediaBoxDefinition,
PdfMediaBoxPage,
PdfPageBoxDefinition,
)

RESOURCE_DIRECTORY = Path(__file__).resolve().parents[1] / "resources"
INPUT_PATH = RESOURCE_DIRECTORY / "report.pdf"


def set_pdf_page_boxes() -> None:
"""Upload a PDF, update two page boxes, and print the output metadata."""
load_dotenv()
base_url = os.getenv("PDFREST_BASE_URL")
boxes: list[PdfPageBoxDefinition] = [
PdfMediaBoxDefinition(
box="media",
pages=[
PdfMediaBoxPage(
range="all",
top=-18,
bottom=-18,
left=-18,
right=-18,
)
],
),
PdfInsetPageBoxDefinition(
box="crop",
pages=[
PdfInsetPageBoxPage(
range="all",
top=36,
bottom=36,
left=36,
right=36,
)
],
),
]

with PdfRestClient(base_url=base_url) as client:
uploaded_pdf = client.files.create_from_paths(INPUT_PATH)[0]
response = client.set_pdf_page_boxes(
uploaded_pdf,
boxes=boxes,
output="report-with-page-boxes",
)

print(f"Input ID: {response.input_id}")
print(f"Output file: {response.output_file.name}")
print(f"Output MIME type: {response.output_file.type}")
print(f"Output URL: {response.output_file.url}")


if __name__ == "__main__": # pragma: no cover - manual example
set_pdf_page_boxes()
95 changes: 95 additions & 0 deletions src/pdfrest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
PdfRedactionPreviewPayload,
PdfRestRawFileResponse,
PdfRestrictPayload,
PdfSetPageBoxesPayload,
PdfSignPayload,
PdfSplitPayload,
PdfTdmReservationPayload,
Expand Down Expand Up @@ -163,6 +164,7 @@
PdfConversionLocale,
PdfInfoQuery,
PdfMergeInput,
PdfPageBoxDefinition,
PdfPageOrientation,
PdfPageSelection,
PdfPageSize,
Expand Down Expand Up @@ -3585,6 +3587,53 @@ def add_image_to_pdf(
timeout=timeout,
)

def set_pdf_page_boxes(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
boxes: PdfPageBoxDefinition | Sequence[PdfPageBoxDefinition],
output: str | None = None,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> PdfRestFileBasedResponse:
"""Set one or more page boundary boxes on a PDF.

Margins use PDF units, where 72 units equal one inch, and are relative
to the current MediaBox. MediaBox margins may be negative to enlarge a
page; CropBox, BleedBox, TrimBox, and ArtBox margins must be positive.

Args:
file: Uploaded PDF resource to update.
boxes: One or more typed MediaBox or inset page-box definitions.
output: Generated output filename without its extension.
extra_query: Additional query parameters merged into the request.
extra_headers: Additional HTTP headers merged into the request.
extra_body: Additional request body fields merged into the payload.
timeout: Request timeout override for this call.

Returns:
Validated file-based response containing the updated PDF.

Raises:
PdfRestError: If request execution fails at the client or API layer.
ValidationError: If local payload validation fails before sending.
"""
payload: dict[str, Any] = {"files": file, "boxes": boxes}
if output is not None:
payload["output"] = output

return self._post_file_operation(
endpoint="/pdf-with-page-boxes-set",
payload=payload,
payload_model=PdfSetPageBoxesPayload,
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)

def split_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down Expand Up @@ -7205,6 +7254,52 @@ async def add_image_to_pdf(
timeout=timeout,
)

async def set_pdf_page_boxes(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
boxes: PdfPageBoxDefinition | Sequence[PdfPageBoxDefinition],
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 set one or more page boundary boxes on a PDF.

This is the asynchronous variant of
[PdfRestClient.set_pdf_page_boxes][pdfrest.PdfRestClient.set_pdf_page_boxes].

Args:
file: Uploaded PDF resource to update.
boxes: One or more typed MediaBox or inset page-box definitions.
output: Generated output filename without its extension.
extra_query: Additional query parameters merged into the request.
extra_headers: Additional HTTP headers merged into the request.
extra_body: Additional request body fields merged into the payload.
timeout: Request timeout override for this call.

Returns:
Validated file-based response containing the updated PDF.

Raises:
PdfRestError: If request execution fails at the client or API layer.
ValidationError: If local payload validation fails before sending.
"""
payload: dict[str, Any] = {"files": file, "boxes": boxes}
if output is not None:
payload["output"] = output

return await self._post_file_operation(
endpoint="/pdf-with-page-boxes-set",
payload=payload,
payload_model=PdfSetPageBoxesPayload,
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)

async def up(
self,
*,
Expand Down
Loading
Loading