
| Current Path : /bin/X11/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : //bin/X11/pdfbook2 |
#!/usr/bin/env python3
""" pdfbook2 - transform pdf files to booklets
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import shutil
import subprocess
import sys
from optparse import HelpFormatter, OptionGroup, OptionParser
# ===============================================================================
# Create booklet for file $name
# ===============================================================================
def booklify(name, opts):
# ------------------------------------------------------ Check if file exists
print("\nProcessing", name)
if not os.path.isfile(name):
print("SKIP: file not found.")
return
print("Getting bounds...", end=" ")
sys.stdout.flush()
# ---------------------------------------------------------- useful constants
bboxName = b"%%HiResBoundingBox:"
tmpFile = ".crop-tmp.pdf"
# ------------------------------------------------- find min/max bounding box
if opts.crop:
p = subprocess.Popen(
["pdfcrop", "--verbose", "--resolution", repr(opts.resolution), name, tmpFile],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = p.communicate()
if len(err) != 0:
print(err)
print("\n\nABORT: Problem getting bounds")
sys.exit(1)
lines = out.splitlines()
bboxes = [s[len(bboxName) + 1 :] for s in lines if s.startswith(bboxName)]
bounds = [[float(x) for x in bbox.split()] for bbox in bboxes]
minLOdd = min([bound[0] for bound in bounds[::2]])
maxROdd = max([bound[2] for bound in bounds[::2]])
if len(bboxes) > 1:
minLEven = min([bound[0] for bound in bounds[1::2]])
maxREven = max([bound[2] for bound in bounds[1::2]])
else:
minLEven = minLOdd
maxREven = maxROdd
minT = min([bound[1] for bound in bounds])
maxB = max([bound[3] for bound in bounds])
widthOdd = maxROdd - minLOdd
widthEven = maxREven - minLEven
maxWidth = max(widthOdd, widthEven)
minLOdd -= maxWidth - widthOdd
maxREven += maxWidth - widthEven
print("done")
sys.stdout.flush()
# --------------------------------------------- crop file to area of interest
print("cropping...", end=" ")
sys.stdout.flush()
p = subprocess.Popen(
[
"pdfcrop",
"--bbox-odd",
"{L} {T} {R} {B}".format(
L=minLOdd - opts.innerMargin / 2,
T=minT - opts.topMargin,
R=maxROdd + opts.outerMargin,
B=maxB + opts.outerMargin,
),
"--bbox-even",
"{L} {T} {R} {B}".format(
L=minLEven - opts.outerMargin,
T=minT - opts.topMargin,
R=maxREven + opts.innerMargin / 2,
B=maxB + opts.outerMargin,
),
"--resolution",
repr(opts.resolution),
name,
tmpFile,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = p.communicate()
if len(err) != 0:
print(err)
print("\n\nABORT: Problem with cropping")
sys.exit(1)
print("done")
sys.stdout.flush()
else:
shutil.copy(name, tmpFile)
# -------------------------------------------------------- create the booklet
print("create booklet...", end=" ")
sys.stdout.flush()
pdfJamCallList = [
"pdfjam",
"--landscape",
"--suffix",
"book",
tmpFile,
]
# add option signature if it is defined else booklet
if opts.signature != 0:
pdfJamCallList.append("--signature")
pdfJamCallList.append(repr(opts.signature))
else:
pdfJamCallList.append("--booklet")
pdfJamCallList.append("true")
# add option --paper to call
if opts.paper is not None:
pdfJamCallList.append("--paper")
pdfJamCallList.append(opts.paper)
# add option --short-edge to call
if opts.shortedge:
# check if everyshi.sty exists as texlive recommends
p = subprocess.Popen(
["kpsewhich", "everyshi.sty"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
out, err = p.communicate()
if len(out) == 0:
print("\n\nABORT: The everyshi.sty latex package is needed for short-edge.")
sys.exit(1)
else:
pdfJamCallList.append("--preamble")
pdfJamCallList.append(
r"\usepackage{everyshi}\makeatletter\EveryShipout{\ifodd\c@page\pdfpageattr{/Rotate 180}\fi}\makeatother"
)
# run call to pdfJam to make booklet
p = subprocess.Popen(pdfJamCallList, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
# -------------------------------------------- move file and remove temp file
os.rename(tmpFile[:-4] + "-book.pdf", name[:-4] + "-book.pdf")
os.remove(tmpFile)
print("done")
sys.stdout.flush()
# ===============================================================================
# Help formatter
# ===============================================================================
class MyHelpFormatter(HelpFormatter):
"""Format help with indented section bodies.
"""
def __init__(self, indent_increment=4, max_help_position=16, width=None, short_first=0):
HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
def format_usage(self, usage):
return ("USAGE\n\n%*s%s\n") % (self.indent_increment, "", usage)
def format_heading(self, heading):
return "%*s%s\n\n" % (self.current_indent, "", heading.upper())
# ===============================================================================
# main programm
# ===============================================================================
if __name__ == "__main__":
# ------------------------------------------------------------ useful strings
usageString = "Usage: %prog [options] file1 [file2 ...]"
versionString = """
%prog v1.4 (https://github.com/jenom/pdfbook2)
(c) 2015 - 2020 Johannes Neumann (http://www.neumannjo.de)
licensed under GPLv3 (http://www.gnu.org/licenses/gpl-3.0)
based on pdfbook by David Firth with help from Marco Pessotto\n"""
defaultString = " (default: %default)"
# ------------------------------------------------- create commandline parser
parser = OptionParser(
usage=usageString, version=versionString, formatter=MyHelpFormatter(indent_increment=4)
)
generalGroup = OptionGroup(parser, "General")
generalGroup.add_option(
"-p",
"--paper",
dest="paper",
type="str",
action="store",
metavar="STR",
help="Format of the output paper dimensions as latex keyword (e.g. a4paper, letterpaper, legalpaper, ...)",
)
generalGroup.add_option(
"-s",
"--short-edge",
dest="shortedge",
action="store_true",
help="Format the booklet for short-edge double-sided printing",
default=False,
)
generalGroup.add_option(
"-n",
"--no-crop",
dest="crop",
action="store_false",
help="Prevent the cropping to the content area",
default=True,
)
parser.add_option_group(generalGroup)
marginGroup = OptionGroup(parser, "Margins")
marginGroup.add_option(
"-o",
"--outer-margin",
type="int",
default=40,
dest="outerMargin",
action="store",
metavar="INT",
help="Defines the outer margin in the booklet" + defaultString,
)
marginGroup.add_option(
"-i",
"--inner-margin",
type="int",
default=150,
dest="innerMargin",
action="store",
metavar="INT",
help="Defines the inner margin between the pages in the booklet" + defaultString,
)
marginGroup.add_option(
"-t",
"--top-margin",
type="int",
default=30,
dest="topMargin",
action="store",
metavar="INT",
help="Defines the top margin in the booklet" + defaultString,
)
marginGroup.add_option(
"-b",
"--bottom-margin",
type="int",
default=30,
metavar="INT",
dest="bottomMargin",
action="store",
help="Defines the bottom margin in the booklet" + defaultString,
)
parser.add_option_group(marginGroup)
advancedGroup = OptionGroup(parser, "Advanced")
advancedGroup.add_option(
"--signature",
dest="signature",
action="store",
type="int",
help="Define the signature for the booklet handed to pdfjam, needs to be multiple of 4"
+ defaultString,
default=0,
metavar="INT",
)
advancedGroup.add_option(
"--signature*",
dest="signature",
action="store",
type="int",
help="Same as --signature",
metavar="INT",
)
advancedGroup.add_option(
"--resolution",
dest="resolution",
action="store",
type="int",
help="Resolution used by ghostscript in bp" + defaultString,
metavar="INT",
default=72,
)
parser.add_option_group(advancedGroup)
opts, args = parser.parse_args()
# ------------------------------------ show help if started without arguments
if len(args) == 0:
parser.print_version()
parser.print_help()
print("")
sys.exit(2)
# ------------------------------------------- run for each provided file name
parser.print_version()
for arg in args:
booklify(arg, opts)