diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66d710e..cb962ea 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -9,6 +9,7 @@ history and [GitHubs 'Contributors' feature](https://github.com/py-pdf/pdfly/gra ## Contributors to the pdfly project * [Thoma, Martin](https://github.com/MartinThoma): Creator of pdfly. [LinkedIn](https://www.linkedin.com/in/martin-thoma/) | [StackOverflow](https://stackoverflow.com/users/562769/martin-thoma) | [Blog](https://martin-thoma.com/) +* [Ulrych, Stanislav](https://github.com/stanislavulrych) ## Adding a new contributor diff --git a/pdfly/cli.py b/pdfly/cli.py index 4b377f4..ba34110 100644 --- a/pdfly/cli.py +++ b/pdfly/cli.py @@ -17,6 +17,7 @@ import pdfly.pagemeta import pdfly.up2 import pdfly.x2pdf +import pdfly.tiles def version_callback(value: bool) -> None: @@ -218,6 +219,17 @@ def x2pdf( return pdfly.x2pdf.main(x, output) +@entry_point.command(name="tiles") # type: ignore[misc] +def tiles( + output: Path = typer.Option(..., "-o", "--output"), + xcount: int = typer.Option(2, "-x", "--xcount"), + ycount: int = typer.Option(2, "-y", "--ycount"), + files: List[str] = typer.Argument( # noqa + ..., help="filenames and/or page ranges" + ), +) -> None: + return pdfly.tiles.main(output, xcount, ycount, files,) + up2.__doc__ = pdfly.up2.__doc__ extract_images.__doc__ = pdfly.extract_images.__doc__ cat.__doc__ = pdfly.cat.__doc__ @@ -225,3 +237,4 @@ def x2pdf( pagemeta.__doc__ = pdfly.pagemeta.__doc__ compress.__doc__ = pdfly.compress.__doc__ x2pdf.__doc__ = pdfly.x2pdf.__doc__ +tiles.__doc__ = pdfly.tiles.__doc__ diff --git a/pdfly/tiles.py b/pdfly/tiles.py new file mode 100644 index 0000000..d15d005 --- /dev/null +++ b/pdfly/tiles.py @@ -0,0 +1,87 @@ +""" + Crope and merge PDF files into a single PDF as a layout of `xcount` x `ycount` tiles. + + Especially useful when layouting on A4 printable sticker paper. + + Currently only limited to A4 (portrait) input and output format. + + Use '-' to omit the file. + + `-x` and `-y` parameters specify the tiles size in relation to A4 format. + + A5: `-x 1 -y 2` (landscape) + A6: `-x 2 -y 2` (portrait) - the default + A7: `-x 2 -y 4` (landscape) + A8: `-x 4 -y 4` (portrait) + ... + + Examples + + pdfly tiles -o out.pdf -x 2 -y 2 A.pdf B.pdf C.pdf D.pdf + + Merge the top left (A6) corners of provided PDFs (A4) as A6 tiles into a single page (A4). + + A | B | C | D | A | B + --|-- --|-- --|-- --|-- ===> --|-- + | | | | C | D + + + pdfly tiles -o out.pdf - A.pdf B.pdf C.pdf + + Omitting one position by using `-` (also omitted -x 2 -y 2 as those are by default). + + A | B | C | | A + --|-- --|-- --|-- ===> --|-- + | | | B | C + +""" + + +import os +import sys +import traceback +from pathlib import Path +from typing import List + +from pypdf import PdfReader, PdfWriter, Transformation, PaperSize + + +def main( + output: Path, xcount: int, ycount: int, fn_pgrgs: List[str] +) -> None: + if output: + output_fh = open(output, "wb") + else: + sys.stdout.flush() + output_fh = os.fdopen(sys.stdout.fileno(), "wb") + + xsize = PaperSize.A4.width + ysize = PaperSize.A4.height + + writer = PdfWriter() + dest_page = writer.add_blank_page(width=xsize, height=ysize) + try: + for i in range(len(fn_pgrgs)): + f = fn_pgrgs[i] + if f == "-": + continue + + reader = PdfReader(f) + for p in reader.pages: + t = { + "tx": (i % xcount)*(xsize/xcount), + "ty": -(i // xcount)*(ysize/ycount) + } + p.add_transformation(Transformation().translate(**t)) + dest_page.merge_page(p) + + writer.write(output_fh) + except Exception: + print(traceback.format_exc(), file=sys.stderr) + print(f"Error while reading {f}", file=sys.stderr) + sys.exit(1) + finally: + output_fh.close() + + +