From 8bab9a8bc6400a9fdda94b217c211cf9b390ab71 Mon Sep 17 00:00:00 2001 From: Johannes 'josch' Schauer Date: Sat, 20 Jun 2020 01:44:43 +0200 Subject: [PATCH] add --cover-page, --cutting-guides, --page-numbers and --poster-border (closes: #2) --- README.md | 2 - plakativ.py | 256 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 237 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 65bce6b..16a9932 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,6 @@ TODO While basic functionality is implemented, lots of work remains to be done: - help button - - print poster borders - - print cutting guides - changing units - changing language - make PyMuPDF dependency optional diff --git a/plakativ.py b/plakativ.py index 74c319a..1897a3c 100755 --- a/plakativ.py +++ b/plakativ.py @@ -620,7 +620,7 @@ class Plakativ: 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"): raise LayoutNotComputedException() @@ -628,7 +628,85 @@ class Plakativ: 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: page_width = mm_to_pt(self.layout["output_pagesize"][0]) 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 ) + 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"): # outfile is an object with a write() method outfile.write(outdoc.write(garbage=4, deflate=True)) @@ -865,21 +1027,8 @@ class Application(tkinter.Frame): if hasattr(self, "plakativ"): self.layouter.callback = self.on_layouter - output_group = tkinter.LabelFrame(frame1.interior, text="Output options") - output_group.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) + self.outopts = OutOptsWidget(frame1.interior) + self.outopts.pack(fill=tkinter.X) option_group = tkinter.LabelFrame(frame1.interior, text="Program options") option_group.pack(fill=tkinter.X) @@ -1205,7 +1354,13 @@ class Application(tkinter.Frame): ) if filename == "": 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): @@ -1252,6 +1407,31 @@ class LayouterWidget(tkinter.LabelFrame): 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): def __init__(self, parent, *args, **kw): tkinter.LabelFrame.__init__(self, parent, text="Input properties", *args, **kw) @@ -1764,6 +1944,10 @@ def compute_layout( border=(0, 0, 0, 0), strategy="simple", remove_alpha=False, + cover=False, + guides=False, + numbers=False, + poster_border=False, ): doc = None if have_img2pdf: @@ -1803,7 +1987,7 @@ def compute_layout( doc = fitz.open(filename=infile) plakativ = Plakativ(doc, pagenr) 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): @@ -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 " "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() @@ -2142,6 +2356,10 @@ Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues strategy=args.layouter, **{mode: args.mode}, remove_alpha=args.remove_alpha, + cover=args.cover_page, + guides=args.cutting_guides, + numbers=args.page_numbers, + poster_border=args.poster_border, )