add --cover-page, --cutting-guides, --page-numbers and --poster-border (closes: #2)

This commit is contained in:
Johannes 'josch' Schauer 2020-06-20 01:44:43 +02:00
parent 3e18f1fc35
commit 8bab9a8bc6
Signed by: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 237 additions and 21 deletions

View file

@ -77,8 +77,6 @@ TODO
While basic functionality is implemented, lots of work remains to be done: While basic functionality is implemented, lots of work remains to be done:
- help button - help button
- print poster borders
- print cutting guides
- changing units - changing units
- changing language - changing language
- make PyMuPDF dependency optional - make PyMuPDF dependency optional

View file

@ -620,7 +620,7 @@ class Plakativ:
return postersize, mult, npages return postersize, mult, npages
def render(self, outfile): def render(self, outfile, cover=False, guides=False, numbers=False, border=False):
if not hasattr(self, "layout"): if not hasattr(self, "layout"):
raise LayoutNotComputedException() raise LayoutNotComputedException()
@ -628,7 +628,85 @@ class Plakativ:
outdoc = fitz.open() outdoc = fitz.open()
for (x, y, portrait) in self.layout["positions"]: if cover:
# factor to convert from output poster dimensions (given in mm) into
# pdf dimensions (given in pt)
zoom_1 = min(
mm_to_pt(
self.layout["output_pagesize"][0]
- 2 * max(self.layout["border_left"], self.layout["border_right"])
)
/ (self.layout["overallsize"][0]),
mm_to_pt(
self.layout["output_pagesize"][1]
- 2 * max(self.layout["border_top"], self.layout["border_bottom"])
)
/ (self.layout["overallsize"][1]),
)
page = outdoc.newPage(
-1, # insert after last page
width=mm_to_pt(self.layout["output_pagesize"][0]),
height=mm_to_pt(self.layout["output_pagesize"][1]),
)
for i, (x, y, portrait) in enumerate(self.layout["positions"]):
x0 = (x + self.layout["posterpos"][0]) * zoom_1 + (
mm_to_pt(self.layout["output_pagesize"][0])
- zoom_1 * self.layout["overallsize"][0]
) / 2
y0 = (y + self.layout["posterpos"][1]) * zoom_1 + (
mm_to_pt(self.layout["output_pagesize"][1])
- zoom_1 * self.layout["overallsize"][1]
) / 2
if portrait:
page_width = self.layout["output_pagesize"][0] * zoom_1
page_height = self.layout["output_pagesize"][1] * zoom_1
top = self.layout["border_top"] * zoom_1
right = self.layout["border_right"] * zoom_1
bottom = self.layout["border_bottom"] * zoom_1
left = self.layout["border_left"] * zoom_1
else:
# page is rotated 90 degrees clockwise
page_width = self.layout["output_pagesize"][1] * zoom_1
page_height = self.layout["output_pagesize"][0] * zoom_1
top = self.layout["border_left"] * zoom_1
right = self.layout["border_top"] * zoom_1
bottom = self.layout["border_right"] * zoom_1
left = self.layout["border_bottom"] * zoom_1
# inner rectangle
shape = page.newShape()
shape.drawRect(
fitz.Rect(
x0,
y0,
x0 + page_width - left - right,
y0 + page_height - top - bottom,
)
)
shape.finish(color=(0, 0, 1))
# outer rectangle
shape.drawRect(
fitz.Rect(
x0 - left,
y0 - top,
x0 - left + page_width,
y0 - top + page_height,
)
)
shape.finish(color=(1, 0, 0))
shape.insertTextbox(
fitz.Rect(
x0 + 5,
y0 + 5,
x0 + page_width - left - right - 5,
y0 + page_height - top - bottom - 5,
),
"%d" % (i + 1),
fontsize=20,
color=(0, 0, 0),
)
shape.commit()
for i, (x, y, portrait) in enumerate(self.layout["positions"]):
if portrait: if portrait:
page_width = mm_to_pt(self.layout["output_pagesize"][0]) page_width = mm_to_pt(self.layout["output_pagesize"][0])
page_height = mm_to_pt(self.layout["output_pagesize"][1]) page_height = mm_to_pt(self.layout["output_pagesize"][1])
@ -686,6 +764,90 @@ class Plakativ:
clip=sourcerect, # part of the input page to use clip=sourcerect, # part of the input page to use
) )
shape = page.newShape()
if guides:
if portrait:
shape.drawRect(
fitz.Rect(
mm_to_pt(self.layout["border_left"]),
mm_to_pt(self.layout["border_top"]),
page_width - mm_to_pt(self.layout["border_right"]),
page_height - mm_to_pt(self.layout["border_bottom"]),
)
)
else:
shape.drawRect(
fitz.Rect(
mm_to_pt(self.layout["border_bottom"]),
mm_to_pt(self.layout["border_left"]),
page_width - mm_to_pt(self.layout["border_top"]),
page_height - mm_to_pt(self.layout["border_right"]),
)
)
shape.finish(width=0.2, color=(0.5, 0.5, 0.5), dashes="[5 6 1 6]")
if numbers:
if portrait:
shape.insertTextbox(
fitz.Rect(
mm_to_pt(self.layout["border_left"]) + 5,
mm_to_pt(self.layout["border_top"]) + 5,
page_width - mm_to_pt(self.layout["border_right"]) - 5,
page_height - mm_to_pt(self.layout["border_bottom"]) - 5,
),
"%d" % (i + 1),
fontsize=8,
color=(0.5, 0.5, 0.5),
)
else:
shape.insertTextbox(
fitz.Rect(
mm_to_pt(self.layout["border_bottom"]) + 5,
mm_to_pt(self.layout["border_left"]) + 5,
page_width - mm_to_pt(self.layout["border_top"]) - 5,
page_height - mm_to_pt(self.layout["border_right"]) - 5,
),
"%d" % (i + 1),
fontsize=8,
color=(0.5, 0.5, 0.5),
)
if border:
if portrait:
shape.drawRect(
fitz.Rect(
mm_to_pt(self.layout["border_left"] - x),
mm_to_pt(self.layout["border_top"] - y),
mm_to_pt(
self.layout["border_left"]
- x
+ self.layout["postersize"][0]
),
mm_to_pt(
self.layout["border_top"]
- y
+ self.layout["postersize"][1]
),
)
)
else:
shape.drawRect(
fitz.Rect(
mm_to_pt(self.layout["border_bottom"] - x),
mm_to_pt(self.layout["border_left"] - y),
mm_to_pt(
self.layout["border_bottom"]
- x
+ self.layout["postersize"][0]
),
mm_to_pt(
self.layout["border_left"]
- y
+ self.layout["postersize"][1]
),
)
)
shape.finish(width=0.2, color=(0.5, 0.5, 0.5), dashes="[1 1]")
shape.commit()
if hasattr(outfile, "write"): if hasattr(outfile, "write"):
# outfile is an object with a write() method # outfile is an object with a write() method
outfile.write(outdoc.write(garbage=4, deflate=True)) outfile.write(outdoc.write(garbage=4, deflate=True))
@ -865,21 +1027,8 @@ class Application(tkinter.Frame):
if hasattr(self, "plakativ"): if hasattr(self, "plakativ"):
self.layouter.callback = self.on_layouter self.layouter.callback = self.on_layouter
output_group = tkinter.LabelFrame(frame1.interior, text="Output options") self.outopts = OutOptsWidget(frame1.interior)
output_group.pack(fill=tkinter.X) self.outopts.pack(fill=tkinter.X)
tkinter.Checkbutton(
output_group, text="Print cutting guides", state=tkinter.DISABLED
).pack(anchor=tkinter.W)
tkinter.Checkbutton(
output_group, text="Print poster border", state=tkinter.DISABLED
).pack(anchor=tkinter.W)
tkinter.Checkbutton(
output_group, text="Print page number", state=tkinter.DISABLED
).pack(anchor=tkinter.W)
tkinter.Checkbutton(
output_group, text="Print layout cover page", state=tkinter.DISABLED
).pack(anchor=tkinter.W)
option_group = tkinter.LabelFrame(frame1.interior, text="Program options") option_group = tkinter.LabelFrame(frame1.interior, text="Program options")
option_group.pack(fill=tkinter.X) option_group.pack(fill=tkinter.X)
@ -1205,7 +1354,13 @@ class Application(tkinter.Frame):
) )
if filename == "": if filename == "":
return return
self.plakativ.render(filename) self.plakativ.render(
filename,
cover=self.outopts.variables["cover"].get(),
guides=self.outopts.variables["guides"].get(),
numbers=self.outopts.variables["numbers"].get(),
border=self.outopts.variables["border"].get(),
)
class LayouterWidget(tkinter.LabelFrame): class LayouterWidget(tkinter.LabelFrame):
@ -1252,6 +1407,31 @@ class LayouterWidget(tkinter.LabelFrame):
self.variables["strategy"].set(strategy) self.variables["strategy"].set(strategy)
class OutOptsWidget(tkinter.LabelFrame):
def __init__(self, parent, *args, **kw):
tkinter.LabelFrame.__init__(self, parent, text="Output options", *args, **kw)
self.variables = {
"guides": tkinter.IntVar(),
"border": tkinter.IntVar(),
"numbers": tkinter.IntVar(),
"cover": tkinter.IntVar(),
}
tkinter.Checkbutton(
self, text="Print cutting guides", variable=self.variables["guides"]
).pack(anchor=tkinter.W)
tkinter.Checkbutton(
self, text="Print poster border", variable=self.variables["border"]
).pack(anchor=tkinter.W)
tkinter.Checkbutton(
self, text="Print page number", variable=self.variables["numbers"]
).pack(anchor=tkinter.W)
tkinter.Checkbutton(
self, text="Print layout cover page", variable=self.variables["cover"]
).pack(anchor=tkinter.W)
class InputWidget(tkinter.LabelFrame): class InputWidget(tkinter.LabelFrame):
def __init__(self, parent, *args, **kw): def __init__(self, parent, *args, **kw):
tkinter.LabelFrame.__init__(self, parent, text="Input properties", *args, **kw) tkinter.LabelFrame.__init__(self, parent, text="Input properties", *args, **kw)
@ -1764,6 +1944,10 @@ def compute_layout(
border=(0, 0, 0, 0), border=(0, 0, 0, 0),
strategy="simple", strategy="simple",
remove_alpha=False, remove_alpha=False,
cover=False,
guides=False,
numbers=False,
poster_border=False,
): ):
doc = None doc = None
if have_img2pdf: if have_img2pdf:
@ -1803,7 +1987,7 @@ def compute_layout(
doc = fitz.open(filename=infile) doc = fitz.open(filename=infile)
plakativ = Plakativ(doc, pagenr) plakativ = Plakativ(doc, pagenr)
plakativ.compute_layout(mode, size, mult, npages, pagesize, border, strategy) plakativ.compute_layout(mode, size, mult, npages, pagesize, border, strategy)
plakativ.render(outfile) plakativ.render(outfile, cover, guides, numbers, poster_border)
def gui(filename=None): def gui(filename=None):
@ -2105,6 +2289,36 @@ Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues
"plakativ can remove the alpha channel for you. The resulting PDF " "plakativ can remove the alpha channel for you. The resulting PDF "
"poster might not be lossless anymore.", "poster might not be lossless anymore.",
) )
parser.add_argument(
"--cover-page",
action="store_true",
help="Add a cover page as the first page which shows the resulting "
"layout. This is especially interesting for the complex layouter "
"unless you like puzzles.",
)
parser.add_argument(
"--cutting-guides",
action="store_true",
help="Print light-gray dashed lines that surround the visible part "
"of each page and can help with easier cutting and gluing of the "
"pages. This is generally only needed if the poster does not contain "
"enough detail for accurate gluing.",
)
parser.add_argument(
"--page-numbers",
action="store_true",
help="Print a small number of each page to uniquely identify each "
"sheet. This is especially useful in combination with --cover-page "
"because the numbers on the cover page correspond to the page numbers.",
)
parser.add_argument(
"--poster-border",
action="store_true",
help="If the poster itself has a white background and it is important "
"that the final result has precisely the desired poster size, then "
"this option will print a light-gray dashed border around the whole "
"poster, so that it can be accurately cut to the correct overall size.",
)
args = parser.parse_args() args = parser.parse_args()
@ -2142,6 +2356,10 @@ Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues
strategy=args.layouter, strategy=args.layouter,
**{mode: args.mode}, **{mode: args.mode},
remove_alpha=args.remove_alpha, remove_alpha=args.remove_alpha,
cover=args.cover_page,
guides=args.cutting_guides,
numbers=args.page_numbers,
poster_border=args.poster_border,
) )