Skip to content

Instantly share code, notes, and snippets.

@mvolfik
Last active January 24, 2021 18:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvolfik/ce5445da6294f9374adfaccc56890ee0 to your computer and use it in GitHub Desktop.
Save mvolfik/ce5445da6294f9374adfaccc56890ee0 to your computer and use it in GitHub Desktop.
Reorder pages of a PDF to allow printing as a brochure
import sys, subprocess, tempfile, os.path, warnings
A4 = (595, 842)
source = sys.argv[1]
target = sys.argv[2]
pages = None
page_size = None
for l in subprocess.run(
["pdfinfo", source], stdout=subprocess.PIPE, text=True
).stdout.splitlines():
k, *v = map(str.strip, l.split(":"))
if k == "Pages":
pages = int(v[0])
elif k == "Page size":
s = v[0].split(" ")
if s[3] != "pts":
warnings.warn(
"Page size detected, but uses unknown units, falling back to A4"
)
page_size = A4
page_size = (float(s[0]), float(s[2]))
if pages is None:
raise RuntimeError("Couldn't detect number of pages")
if page_size is None:
warnings.warn("Page size detected, but uses unknown units, falling back to A4")
page_size = A4
tmpdir = tempfile.mkdtemp()
page_size_bytestring = " ".join(map(str, page_size)).encode()
with open(os.path.join(tmpdir, "empty.pdf"), "wb") as fh:
fh.write(
b"%PDF-1.7\n%\xc2\xa6\xc2\xa6\n1 0 obj\n<<\n/Type /Pages\n/Count 1\n/Kids [ 2 0 R ]\n>>\nendobj\n2 0 obj\n<<\n/Type /Page\n/Parent 1 0 R\n/MediaBox [ 0 0 "
+ page_size_bytestring
+ b" ]\n>>\nendobj\n3 0 obj\n<<\n/Type /Catalog\n/Pages 1 0 R\n>>\nendobj\nxref\n0 4\n0000000000 65535 f \n0000000015 00000 n \n0000000074 00000 n \n0000000147 00000 n \ntrailer\n<<\n/Size 4\n/Root 3 0 R\n/Info 1 0 R\n/ID <656d7074795f70616765>\n>>\nstartxref\n196\n%%EOF"
)
pages_multipleof_four = (pages - 1) if (m := pages % 4) == 0 else pages + 3 - m
page_format = (
f"%0{len(str(pages_multipleof_four))}d" if pages_multipleof_four > 9 else "%d"
)
split_proc = subprocess.run(["pdfseparate", source, os.path.join(tmpdir, page_format)])
if split_proc.returncode != 0:
raise RuntimeError("PDF separation failed")
def f(y):
x = 0
while x < y:
yield y
yield x
yield x + 1
yield y - 1
x += 2
y -= 2
page_order = [
page_format % (x + 1,) if x < pages else "empty.pdf"
for x in f(pages_multipleof_four)
]
subprocess.run(
[
"pdfunite",
*map(lambda x: os.path.join(tmpdir, x), page_order),
target,
]
)

python3 pdf_brochure.py source.pdf brochure.pdf

Tested with Python 3.8, will likely work with other Python 3.* versions.

Depends on https://repology.org/project/poppler (uses commands pdfinfo, pdfseparate and pdfunite)

Print the resulting file with these settings:

Setting Value Comment
Two-sided Short Edge (Flip) the less common two-sided print option
Pages per side 2 that's the brochure trick, ya know?
Page ordering Left to right
Page Scaling Fit to Printable Area autoscaling magic
Auto Rotate and Center off if some pages are landscape oriented, it will behave weirdly without this
Orientation portrait stack the pages horizontally, not vertically
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment