forked from josch/plakativ
add BorderWidget
This commit is contained in:
parent
d0f91abf6f
commit
9f369fd22a
2 changed files with 195 additions and 133 deletions
326
plakativ.py
326
plakativ.py
|
@ -79,14 +79,24 @@ class Plakativ:
|
|||
)
|
||||
return pix.getImageData("ppm")
|
||||
|
||||
def compute_layout(self, mode, size=None, mult=None, npages=None):
|
||||
self.layout = {"output_pagesize": PAGE_SIZES["A4 (21.0 cm × 29.7 cm)"]}
|
||||
def compute_layout(
|
||||
self, mode, size=None, mult=None, npages=None, border=(0, 0, 0, 0)
|
||||
):
|
||||
border_top, border_right, border_bottom, border_left = border
|
||||
|
||||
self.layout = {
|
||||
"output_pagesize": PAGE_SIZES["A4 (21.0 cm × 29.7 cm)"],
|
||||
"border_top": border_top,
|
||||
"border_right": border_right,
|
||||
"border_bottom": border_bottom,
|
||||
"border_left": border_left,
|
||||
}
|
||||
|
||||
printable_width = self.layout["output_pagesize"][0] - (
|
||||
self.border_left + self.border_right
|
||||
border_left + border_right
|
||||
)
|
||||
printable_height = self.layout["output_pagesize"][1] - (
|
||||
self.border_top + self.border_bottom
|
||||
border_top + border_bottom
|
||||
)
|
||||
inpage_width = pt_to_mm(self.doc[self.pagenr - 1].getDisplayList().rect.width)
|
||||
inpage_height = pt_to_mm(self.doc[self.pagenr - 1].getDisplayList().rect.height)
|
||||
|
@ -106,8 +116,6 @@ class Plakativ:
|
|||
else:
|
||||
raise Exception("unsupported mode: %s" % mode)
|
||||
|
||||
self.layout["postersize"] = poster_width, poster_height
|
||||
|
||||
pages_x_portrait = math.ceil(poster_width / printable_width)
|
||||
pages_y_portrait = math.ceil(poster_height / printable_height)
|
||||
|
||||
|
@ -124,17 +132,9 @@ class Plakativ:
|
|||
if portrait:
|
||||
pages_x = pages_x_portrait
|
||||
pages_y = pages_y_portrait
|
||||
self.layout["overallsize"] = (
|
||||
pages_x * printable_width + (self.border_right + self.border_left),
|
||||
pages_y * printable_height + (self.border_top + self.border_bottom),
|
||||
)
|
||||
else:
|
||||
pages_x = pages_x_landscape
|
||||
pages_y = pages_y_landscape
|
||||
self.layout["overallsize"] = (
|
||||
pages_x * printable_height + (self.border_top + self.border_bottom),
|
||||
pages_y * printable_width + (self.border_right + self.border_left),
|
||||
)
|
||||
elif mode == "npages":
|
||||
# stupid bruteforce algorithm to determine the largest printable
|
||||
# postersize with N pages
|
||||
|
@ -175,20 +175,37 @@ class Plakativ:
|
|||
best = (x, y, False, poster_width, poster_height)
|
||||
|
||||
pages_x, pages_y, portrait, poster_width, poster_height = best
|
||||
self.layout["postersize"] = poster_width, poster_height
|
||||
if portrait:
|
||||
self.layout["overallsize"] = (
|
||||
pages_x * printable_width + (self.border_right + self.border_left),
|
||||
pages_y * printable_height + (self.border_top + self.border_bottom),
|
||||
)
|
||||
else:
|
||||
self.layout["overallsize"] = (
|
||||
pages_x * printable_height + (self.border_top + self.border_bottom),
|
||||
pages_y * printable_width + (self.border_right + self.border_left),
|
||||
)
|
||||
else:
|
||||
raise Exception("unsupported mode: %s" % mode)
|
||||
|
||||
# size of the bounding box of all pages after they have been glued together
|
||||
if portrait:
|
||||
self.layout["overallsize"] = (
|
||||
pages_x * printable_width + (border_right + border_left),
|
||||
pages_y * printable_height + (border_top + border_bottom),
|
||||
)
|
||||
else:
|
||||
self.layout["overallsize"] = (
|
||||
pages_x * printable_height + (border_top + border_bottom),
|
||||
pages_y * printable_width + (border_right + border_left),
|
||||
)
|
||||
|
||||
# size of output poster is always proportional to size of input page
|
||||
self.layout["postersize"] = poster_width, poster_height
|
||||
|
||||
# position of the poster relative to upper left corner of layout["overallsize"]
|
||||
if portrait:
|
||||
self.layout["posterpos"] = (
|
||||
border_left + (pages_x * printable_width - poster_width) / 2,
|
||||
border_top + (pages_y * printable_height - poster_height) / 2,
|
||||
)
|
||||
else:
|
||||
self.layout["posterpos"] = (
|
||||
border_bottom + (pages_x * printable_height - poster_width) / 2,
|
||||
border_right + (pages_y * printable_width - poster_height) / 2,
|
||||
)
|
||||
|
||||
# positions are relative to upper left corner of poster
|
||||
self.layout["positions"] = []
|
||||
for y in range(pages_y):
|
||||
for x in range(pages_x):
|
||||
|
@ -233,12 +250,8 @@ class Plakativ:
|
|||
-1, width=page_width, height=page_height # insert after last page
|
||||
)
|
||||
|
||||
target_x = (
|
||||
x - self.layout["overallsize"][0] / 2 + self.layout["postersize"][0] / 2
|
||||
)
|
||||
target_y = (
|
||||
y - self.layout["overallsize"][1] / 2 + self.layout["postersize"][1] / 2
|
||||
)
|
||||
target_x = x - self.layout["posterpos"][0]
|
||||
target_y = y - self.layout["posterpos"][1]
|
||||
target_xoffset = 0
|
||||
target_yoffset = 0
|
||||
if portrait:
|
||||
|
@ -403,11 +416,6 @@ class Application(tkinter.Frame):
|
|||
if plakativ is not None:
|
||||
self.plakativ = plakativ
|
||||
|
||||
self.border_left = tkinter.DoubleVar(value=20.0)
|
||||
self.border_right = tkinter.DoubleVar(value=20.0)
|
||||
self.border_top = tkinter.DoubleVar(value=20.0)
|
||||
self.border_bottom = tkinter.DoubleVar(value=20.0)
|
||||
|
||||
self.canvas = tkinter.Canvas(self, bg="black")
|
||||
self.canvas.pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=tkinter.TRUE)
|
||||
self.canvas_size = self.canvas.winfo_width(), self.canvas.winfo_height()
|
||||
|
@ -506,71 +514,11 @@ class Application(tkinter.Frame):
|
|||
)
|
||||
label_pagesize_height_mm.grid(row=3, column=2, sticky=tkinter.W)
|
||||
|
||||
border_group = tkinter.LabelFrame(
|
||||
frame1.interior, text="Output Borders/Overlap"
|
||||
)
|
||||
border_group.pack(fill=tkinter.X)
|
||||
|
||||
label_left = tkinter.Label(border_group, text="Left:")
|
||||
label_left.grid(row=0, column=0, sticky=tkinter.W)
|
||||
border_left = tkinter.Spinbox(
|
||||
border_group,
|
||||
format="%.2f",
|
||||
increment=0.01,
|
||||
from_=0,
|
||||
to=100,
|
||||
width=5,
|
||||
textvariable=self.border_left,
|
||||
command=self.on_border,
|
||||
)
|
||||
border_left.grid(row=0, column=1)
|
||||
label_left_mm = tkinter.Label(border_group, text="mm")
|
||||
label_left_mm.grid(row=0, column=2)
|
||||
label_right = tkinter.Label(border_group, text="Right:")
|
||||
label_right.grid(row=1, column=0, sticky=tkinter.W)
|
||||
border_right = tkinter.Spinbox(
|
||||
border_group,
|
||||
format="%.2f",
|
||||
increment=0.01,
|
||||
from_=0,
|
||||
to=100,
|
||||
width=5,
|
||||
textvariable=self.border_right,
|
||||
command=self.on_border,
|
||||
)
|
||||
border_right.grid(row=1, column=1)
|
||||
label_right_mm = tkinter.Label(border_group, text="mm")
|
||||
label_right_mm.grid(row=1, column=2)
|
||||
label_top = tkinter.Label(border_group, text="Top:")
|
||||
label_top.grid(row=2, column=0, sticky=tkinter.W)
|
||||
border_top = tkinter.Spinbox(
|
||||
border_group,
|
||||
format="%.2f",
|
||||
increment=0.01,
|
||||
from_=0,
|
||||
to=100,
|
||||
width=5,
|
||||
textvariable=self.border_top,
|
||||
command=self.on_border,
|
||||
)
|
||||
border_top.grid(row=2, column=1)
|
||||
label_top_mm = tkinter.Label(border_group, text="mm")
|
||||
label_top_mm.grid(row=2, column=2)
|
||||
label_bottom = tkinter.Label(border_group, text="Bottom:")
|
||||
label_bottom.grid(row=3, column=0, sticky=tkinter.W)
|
||||
border_bottom = tkinter.Spinbox(
|
||||
border_group,
|
||||
format="%.2f",
|
||||
increment=0.01,
|
||||
from_=0,
|
||||
to=100,
|
||||
width=5,
|
||||
textvariable=self.border_bottom,
|
||||
command=self.on_border,
|
||||
)
|
||||
border_bottom.grid(row=3, column=1)
|
||||
label_bottom_mm = tkinter.Label(border_group, text="mm")
|
||||
label_bottom_mm.grid(row=3, column=2)
|
||||
self.border = BorderWidget(frame1.interior)
|
||||
self.border.pack(fill=tkinter.X)
|
||||
self.border.set(20.0, 20.0, 20.0, 20.0)
|
||||
if hasattr(self, "plakativ"):
|
||||
self.postersize.callback = self.on_border
|
||||
|
||||
self.postersize = PostersizeWidget(frame1.interior)
|
||||
self.postersize.pack(fill=tkinter.X)
|
||||
|
@ -632,16 +580,19 @@ class Application(tkinter.Frame):
|
|||
|
||||
def on_postersize(self, value):
|
||||
mode, (custom_size, size), mult, npages = value
|
||||
size, mult, npages = self.plakativ.compute_layout(mode, size, mult, npages)
|
||||
border = self.border.value
|
||||
size, mult, npages = self.plakativ.compute_layout(
|
||||
mode, size, mult, npages, border
|
||||
)
|
||||
self.draw()
|
||||
return (mode, (custom_size, size), mult, npages)
|
||||
|
||||
def on_border(self):
|
||||
self.posterizer.border_left = self.border_left.get()
|
||||
self.posterizer.border_right = self.border_right.get()
|
||||
self.posterizer.border_top = self.border_top.get()
|
||||
self.posterizer.border_bottom = self.border_bottom.get()
|
||||
self.posterizer.compute_layout()
|
||||
def on_border(self, value):
|
||||
mode, (custom_size, size), mult, npages = self.postersize.value
|
||||
size, mult, npages = self.plakativ.compute_layout(
|
||||
mode, size, mult, npages, value
|
||||
)
|
||||
self.postersize.set(mode, (custom_size, size), mult, npages)
|
||||
self.draw()
|
||||
|
||||
def on_select_pagesize(self, value):
|
||||
|
@ -664,6 +615,8 @@ class Application(tkinter.Frame):
|
|||
|
||||
width, height = self.plakativ.get_input_page_size()
|
||||
|
||||
# factor to convert from input page dimensions (given in pt) into
|
||||
# canvas dimensions (given in pixels)
|
||||
zoom_0 = min(
|
||||
self.canvas_size[0]
|
||||
/ width
|
||||
|
@ -675,6 +628,11 @@ class Application(tkinter.Frame):
|
|||
/ (self.plakativ.layout["overallsize"][1] + canvas_padding),
|
||||
)
|
||||
|
||||
img = self.plakativ.get_image(zoom_0)
|
||||
tkimg = tkinter.PhotoImage(data=img)
|
||||
|
||||
# factor to convert from output poster dimensions (given in mm) into
|
||||
# canvas dimensions (given in pixels)
|
||||
zoom_1 = min(
|
||||
self.canvas_size[0]
|
||||
/ (self.plakativ.layout["overallsize"][0] + canvas_padding),
|
||||
|
@ -682,13 +640,12 @@ class Application(tkinter.Frame):
|
|||
/ (self.plakativ.layout["overallsize"][1] + canvas_padding),
|
||||
)
|
||||
|
||||
img = self.plakativ.get_image(zoom_0)
|
||||
tkimg = tkinter.PhotoImage(data=img)
|
||||
|
||||
# draw image
|
||||
# draw image on canvas
|
||||
self.canvas.create_image(
|
||||
(self.canvas_size[0] - zoom_0 * width) / 2,
|
||||
(self.canvas_size[1] - zoom_0 * height) / 2,
|
||||
(self.canvas_size[0] - zoom_1 * self.plakativ.layout["overallsize"][0]) / 2
|
||||
+ zoom_1 * self.plakativ.layout["posterpos"][0],
|
||||
(self.canvas_size[1] - zoom_1 * self.plakativ.layout["overallsize"][1]) / 2
|
||||
+ zoom_1 * self.plakativ.layout["posterpos"][1],
|
||||
anchor=tkinter.NW,
|
||||
image=tkimg,
|
||||
)
|
||||
|
@ -698,13 +655,19 @@ class Application(tkinter.Frame):
|
|||
for (x, y, portrait) in self.plakativ.layout["positions"]:
|
||||
x0 = (
|
||||
x * zoom_1
|
||||
+ self.canvas_size[0] / 2
|
||||
- zoom_1 * self.plakativ.layout["overallsize"][0] / 2
|
||||
+ (
|
||||
self.canvas_size[0]
|
||||
- zoom_1 * self.plakativ.layout["overallsize"][0]
|
||||
)
|
||||
/ 2
|
||||
)
|
||||
y0 = (
|
||||
y * zoom_1
|
||||
+ self.canvas_size[1] / 2
|
||||
- zoom_1 * self.plakativ.layout["overallsize"][1] / 2
|
||||
+ (
|
||||
self.canvas_size[1]
|
||||
- zoom_1 * self.plakativ.layout["overallsize"][1]
|
||||
)
|
||||
/ 2
|
||||
)
|
||||
if portrait:
|
||||
x1 = x0 + self.plakativ.layout["output_pagesize"][0] * zoom_1
|
||||
|
@ -713,11 +676,21 @@ class Application(tkinter.Frame):
|
|||
x1 = x0 + self.plakativ.layout["output_pagesize"][1] * zoom_1
|
||||
y1 = y0 + self.plakativ.layout["output_pagesize"][0] * zoom_1
|
||||
self.canvas.create_rectangle(x0, y0, x1, y1, outline="red")
|
||||
if portrait:
|
||||
top = self.plakativ.layout["border_top"]
|
||||
right = self.plakativ.layout["border_right"]
|
||||
bottom = self.plakativ.layout["border_bottom"]
|
||||
left = self.plakativ.layout["border_left"]
|
||||
else:
|
||||
top = self.plakativ.layout["border_left"]
|
||||
right = self.plakativ.layout["border_top"]
|
||||
bottom = self.plakativ.layout["border_right"]
|
||||
left = self.plakativ.layout["border_bottom"]
|
||||
self.canvas.create_rectangle(
|
||||
x0 + zoom_1 * self.border_left.get(),
|
||||
y0 + zoom_1 * self.border_top.get(),
|
||||
x1 - zoom_1 * self.border_right.get(),
|
||||
y1 - zoom_1 * self.border_bottom.get(),
|
||||
x0 + zoom_1 * left,
|
||||
y0 + zoom_1 * top,
|
||||
x1 - zoom_1 * right,
|
||||
y1 - zoom_1 * bottom,
|
||||
outline="blue",
|
||||
)
|
||||
|
||||
|
@ -734,19 +707,20 @@ class Application(tkinter.Frame):
|
|||
self.filename = filename
|
||||
self.plakativ = Plakativ(self.filename)
|
||||
# compute the splitting with the current values
|
||||
mode, (_, size), mult, npages = self.postersize.value
|
||||
size, mult, npages = self.plakativ.compute_layout(mode, size, mult, npages)
|
||||
# copy computed values to postersize widget
|
||||
mode, (custom_size, _), _, _ = self.postersize.value
|
||||
self.postersize.value = (mode, (custom_size, size), mult, npages)
|
||||
mode, (custom_size, size), mult, npages = self.postersize.value
|
||||
border = self.border.value
|
||||
size, mult, npages = self.plakativ.compute_layout(
|
||||
mode, size, mult, npages, border
|
||||
)
|
||||
# update postersize widget
|
||||
self.postersize.set(*self.postersize.value)
|
||||
self.postersize.set(mode, (custom_size, size), mult, npages)
|
||||
# draw preview in canvas
|
||||
self.draw()
|
||||
# enable save button
|
||||
self.save_button.configure(state=tkinter.NORMAL)
|
||||
# set callback function
|
||||
self.postersize.callback = self.on_postersize
|
||||
self.border.callback = self.on_border
|
||||
|
||||
def on_save_button(self):
|
||||
base, ext = os.path.splitext(os.path.basename(self.filename))
|
||||
|
@ -763,6 +737,90 @@ class Application(tkinter.Frame):
|
|||
self.plakativ.render(filename)
|
||||
|
||||
|
||||
class BorderWidget(tkinter.LabelFrame):
|
||||
def __init__(self, parent, *args, **kw):
|
||||
tkinter.LabelFrame.__init__(
|
||||
self, parent, text="Output Borders/Overlap", *args, **kw
|
||||
)
|
||||
|
||||
self.callback = None
|
||||
|
||||
self.variables = dict()
|
||||
for i, (n, label) in enumerate(
|
||||
[
|
||||
("top", "Top:"),
|
||||
("right", "Right:"),
|
||||
("bottom", "Bottom:"),
|
||||
("left", "Left:"),
|
||||
]
|
||||
):
|
||||
self.variables[n] = tkinter.DoubleVar()
|
||||
# need to pass k and v as function arguments so that their value
|
||||
# does not get overwritten each loop iteration
|
||||
def callback(varname, idx, op, k_copy=n, v_copy=self.variables[n]):
|
||||
assert op == "w"
|
||||
getattr(self, "on_" + k_copy)(v_copy.get())
|
||||
|
||||
self.variables[n].trace("w", callback)
|
||||
|
||||
tkinter.Label(self, text=label).grid(row=i, column=0, sticky=tkinter.W)
|
||||
tkinter.Spinbox(
|
||||
self,
|
||||
format="%.2f",
|
||||
increment=1.0,
|
||||
from_=0,
|
||||
to=100,
|
||||
width=5,
|
||||
textvariable=self.variables[n],
|
||||
).grid(row=i, column=1)
|
||||
tkinter.Label(self, text="mm").grid(row=i, column=2)
|
||||
|
||||
def on_top(self, value):
|
||||
if getattr(self, "value", None) is None:
|
||||
return
|
||||
_, right, bottom, left = self.value
|
||||
self.set(value, right, bottom, left)
|
||||
|
||||
def on_right(self, value):
|
||||
if getattr(self, "value", None) is None:
|
||||
return
|
||||
top, _, bottom, left = self.value
|
||||
self.set(top, value, bottom, left)
|
||||
|
||||
def on_bottom(self, value):
|
||||
if getattr(self, "value", None) is None:
|
||||
return
|
||||
top, right, _, left = self.value
|
||||
self.set(top, right, value, left)
|
||||
|
||||
def on_left(self, value):
|
||||
if getattr(self, "value", None) is None:
|
||||
return
|
||||
top, right, bottom, _ = self.value
|
||||
self.set(top, right, bottom, value)
|
||||
|
||||
def set(self, top, right, bottom, left):
|
||||
# before setting self.value, check if the effective value is different
|
||||
# from before or otherwise we do not need to execute the callback in
|
||||
# the end
|
||||
state_changed = True
|
||||
if getattr(self, "value", None) is not None:
|
||||
state_changed = self.value != (top, right, bottom, left)
|
||||
# execute callback if necessary
|
||||
if state_changed and self.callback is not None:
|
||||
self.callback((top, right, bottom, left))
|
||||
self.value = top, right, bottom, left
|
||||
# only set variables that changed to not trigger multiple variable tracers
|
||||
if self.variables["top"].get() != top:
|
||||
self.variables["top"].set(top)
|
||||
if self.variables["right"].get() != right:
|
||||
self.variables["right"].set(right)
|
||||
if self.variables["bottom"].get() != bottom:
|
||||
self.variables["bottom"].set(bottom)
|
||||
if self.variables["left"].get() != left:
|
||||
self.variables["left"].set(left)
|
||||
|
||||
|
||||
class PostersizeWidget(tkinter.LabelFrame):
|
||||
def __init__(self, parent, *args, **kw):
|
||||
tkinter.LabelFrame.__init__(self, parent, text="Poster Size", *args, **kw)
|
||||
|
@ -983,9 +1041,11 @@ class PostersizeWidget(tkinter.LabelFrame):
|
|||
self.variables["pages"].set(npages)
|
||||
|
||||
|
||||
def compute_layout(infile, outfile, mode, size=None, mult=None, npages=None):
|
||||
def compute_layout(
|
||||
infile, outfile, mode, size=None, mult=None, npages=None, border=(0, 0, 0, 0)
|
||||
):
|
||||
plakativ = Plakativ(infile)
|
||||
plakativ.compute_layout(mode, size, mult, npages)
|
||||
plakativ.compute_layout(mode, size, mult, npages, border)
|
||||
plakativ.render(outfile)
|
||||
|
||||
|
||||
|
@ -1015,8 +1075,10 @@ def main():
|
|||
else:
|
||||
parser.set_defaults(gui=False)
|
||||
|
||||
parser.add_argument(
|
||||
"--mode", choices=["size", "mult", "npages"], help="select poster size"
|
||||
)
|
||||
parser.add_argument("-o", "--output", help="output file")
|
||||
|
||||
parser.add_argument("input", nargs="?", help="input file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
@ -1025,7 +1087,7 @@ def main():
|
|||
gui()
|
||||
exit(0)
|
||||
|
||||
compute_layout(args.input, args.output, mode="size", size=(297, 420))
|
||||
compute_layout(args.input, args.output, mode=args.mode, size=(297, 420))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
2
test.py
2
test.py
|
@ -140,7 +140,7 @@ def test_cases(postersize, input_pagesize, output_pagesize, expected):
|
|||
|
||||
fd, outfile = tempfile.mkstemp(prefix="plakativ")
|
||||
os.close(fd)
|
||||
plakativ.compute_layout(infile, outfile, mode="size", size=postersize)
|
||||
plakativ.compute_layout(infile, outfile, mode="size", size=postersize, border=(20, 20, 20, 20))
|
||||
os.unlink(infile)
|
||||
|
||||
reader = pdfrw.PdfReader(outfile)
|
||||
|
|
Loading…
Reference in a new issue