Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add --format Option for Custom Page Sizes in x2pdf Command #65

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
10 changes: 7 additions & 3 deletions pdfly/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,7 @@ def compress(
) -> None:
pdfly.compress.main(pdf, output)


@entry_point.command(name="x2pdf", help=pdfly.x2pdf.__doc__) # type: ignore[misc]
@entry_point.command()
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
def x2pdf(
x: List[Path],
output: Annotated[
Expand All @@ -240,5 +239,10 @@ def x2pdf(
writable=True,
),
],
format: str = typer.Option(
None,
"--format",
help="Optional page format for output PDF: Letter, A4-portrait, A4-landscape, or custom dimensions (e.g., 210x297). If omitted, no format is enforced."
),
) -> int:
return pdfly.x2pdf.main(x, output)
return pdfly.x2pdf.main(x, output, format)
1 change: 1 addition & 0 deletions pdfly/up2.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ def main(pdf: Path, output: Path) -> None:
with open(output, "wb") as fp:
writer.write(fp)
print("done.")

63 changes: 41 additions & 22 deletions pdfly/x2pdf.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
"""Convert one or more files to PDF. Each file is a page."""

import re

from pathlib import Path
from typing import List

from fpdf import FPDF
from PIL import Image
from rich.console import Console

def get_page_size(format: str):
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
"""Get page dimensions based on format."""
sizes = {
"A4": (210, 297), "A3": (297, 420), "A2": (420, 594),
"A1": (594, 841), "A0": (841, 1189), "Letter": (215.9, 279.4),
"Legal": (215.9, 355.6)
}
match = re.match(r"(A\d|B\d|C\d|Letter|Legal)(-landscape)?$", format, re.IGNORECASE)
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
if match:
size_key = match.group(1).upper()
if size_key in sizes:
width, height = sizes[size_key]
return (height, width) if match.group(2) else (width, height)
raise ValueError(f"Invalid or unsupported page format provided: {format}")

def px_to_mm(px: float) -> float:
px_in_inch = 72
Expand All @@ -16,39 +32,42 @@ def px_to_mm(px: float) -> float:
return mm


def image_to_pdf(pdf: FPDF, x: Path) -> None:
def image_to_pdf(pdf: FPDF, x: Path, page_size: tuple) -> None:
cover = Image.open(x)
width: float
height: float
width, height = cover.size
cover.close()

# Convert dimensions to millimeters
width, height = px_to_mm(width), px_to_mm(height)
page_width, page_height = page_size

# Scale image to fit page size while maintaining aspect ratio
scale_factor = min(page_width / width, page_height / height)
scaled_width, scaled_height = width * scale_factor, height * scale_factor

pdf.add_page(format=(width, height))
pdf.image(x, x=0, y=0)
x_offset = (page_width - scaled_width) / 2
y_offset = (page_height - scaled_height) / 2

pdf.add_page(format=page_size)
pdf.image(str(x), x=x_offset, y=y_offset, w=scaled_width, h=scaled_height)


def main(xs: List[Path], output: Path) -> int:

def main(xs: List[Path], output: Path, format: str = None) -> int:
"""Main function to generate PDF with images fitted to specified page format."""
console = Console()
pdf = FPDF(unit="mm")
page_size = get_page_size(format) if format else None
for x in xs:
path_str = str(x).lower()
if path_str.endswith(("doc", "docx", "odt")):
console.print("[red]Error: Cannot convert Word documents to PDF")
return 1
if not x.exists():
console.print(f"[red]Error: File '{x}' does not exist.")
return 2
if output.exists():
console.print(f"[red]Error: Output file '{output}' exist.")
return 3
pdf = FPDF(
unit="mm",
)
for x in xs:
path_str = str(x).lower()
console.print(f"Skipping unsupported file format: {x}", style="yellow")
continue
try:
image_to_pdf(pdf, x)
except Exception:
console.print(f"[red]Error: Could not convert '{x}' to a PDF.")
image_to_pdf(pdf, x, page_size)
except Exception as e:
console.print(f"Error processing {x}: {e}", style="red")
return 1
pdf.output(str(output))
console.print(f"PDF created successfully at {output}", style="green")
return 0
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion tests/test_x2pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ def test_x2pdf(capsys, tmp_path: Path) -> None:
captured = capsys.readouterr()
assert exit_code == 0, captured
assert captured.out == ""
assert output.exists()
assert output.exists()
Loading