Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
e415d0202a | |||
e84aa8d42e | |||
9475574481 | |||
2aa1271aec | |||
fab6925674 | |||
d0e5c1e48d | |||
d4ed39b7d2 | |||
|
20928a8570 | ||
d6d0a6ea06 | |||
a49d7b0a53 |
6 changed files with 118 additions and 38 deletions
|
@ -2,6 +2,16 @@
|
||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
0.5.2 (2024-01-12)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- support for pymupdf 1.23.0
|
||||||
|
|
||||||
|
0.5.1 (2022-07-23)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- support for pymupdf 1.20.0
|
||||||
|
|
||||||
0.5 (2021-10-11)
|
0.5 (2021-10-11)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,8 @@ Plakativ is available from pypi: https://pypi.org/project/plakativ/
|
||||||
Thus you can just run `pip install plakativ` on your platform of choice.
|
Thus you can just run `pip install plakativ` on your platform of choice.
|
||||||
|
|
||||||
For Microsoft Windows users, PyInstaller based .exe files are produced by
|
For Microsoft Windows users, PyInstaller based .exe files are produced by
|
||||||
appveyor. If you don't want to install Python before using plakativ you can
|
appveyor. The resulting artifacts are attached to each release:
|
||||||
head to appveyor and click on "Artifacts" to download the latest version:
|
https://gitlab.mister-muffin.de/josch/plakativ/releases
|
||||||
https://ci.appveyor.com/project/josch/plakativ
|
|
||||||
|
|
||||||
Complex Layouter
|
Complex Layouter
|
||||||
================
|
================
|
||||||
|
|
|
@ -14,6 +14,9 @@ environment:
|
||||||
# - PYTHON: "C:\\Python35-x64"
|
# - PYTHON: "C:\\Python35-x64"
|
||||||
# - PYTHON: "C:\\Python36-x64"
|
# - PYTHON: "C:\\Python36-x64"
|
||||||
- PYTHON: "C:\\Python37-x64"
|
- PYTHON: "C:\\Python37-x64"
|
||||||
|
platform: x64
|
||||||
|
- PYTHON: "C:\\Python37"
|
||||||
|
platform: x86
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- "%PYTHON%\\python.exe -m pip install wheel PyMuPDF pytest pyinstaller"
|
- "%PYTHON%\\python.exe -m pip install wheel PyMuPDF pytest pyinstaller"
|
||||||
|
|
128
plakativ.py
128
plakativ.py
|
@ -25,6 +25,11 @@ import logging
|
||||||
|
|
||||||
have_img2pdf = True
|
have_img2pdf = True
|
||||||
try:
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# ignore PIL limit because this software is meant to create posters which
|
||||||
|
# naturally can be very large in size
|
||||||
|
Image.MAX_IMAGE_PIXELS = None
|
||||||
import img2pdf
|
import img2pdf
|
||||||
except ImportError:
|
except ImportError:
|
||||||
have_img2pdf = False
|
have_img2pdf = False
|
||||||
|
@ -46,7 +51,7 @@ except ImportError:
|
||||||
tkinter.Menubutton = dummy
|
tkinter.Menubutton = dummy
|
||||||
tkinter.LabelFrame = dummy
|
tkinter.LabelFrame = dummy
|
||||||
|
|
||||||
VERSION = "0.5"
|
VERSION = "0.5.2"
|
||||||
|
|
||||||
PAGE_SIZES = OrderedDict(
|
PAGE_SIZES = OrderedDict(
|
||||||
[
|
[
|
||||||
|
@ -267,7 +272,7 @@ def complex_cover(n, m, x, y):
|
||||||
if X4 > 0 and Y4 > 0:
|
if X4 > 0 and Y4 > 0:
|
||||||
simple_config, (sx, sy) = simple_cover(X4, Y4, x, y)
|
simple_config, (sx, sy) = simple_cover(X4, Y4, x, y)
|
||||||
# shift the results such that they are in the center
|
# shift the results such that they are in the center
|
||||||
for (cx, cy, p) in simple_config:
|
for cx, cy, p in simple_config:
|
||||||
newconfig.append(
|
newconfig.append(
|
||||||
(
|
(
|
||||||
w0 * X(r, 0) + (X4 - sx) / 2 + cx,
|
w0 * X(r, 0) + (X4 - sx) / 2 + cx,
|
||||||
|
@ -281,7 +286,7 @@ def complex_cover(n, m, x, y):
|
||||||
if X4 > 0 and Y4 > 0:
|
if X4 > 0 and Y4 > 0:
|
||||||
simple_config, (sx, sy) = simple_cover(X4, Y4, x, y)
|
simple_config, (sx, sy) = simple_cover(X4, Y4, x, y)
|
||||||
# shift the results such that they are in the center
|
# shift the results such that they are in the center
|
||||||
for (cx, cy, p) in simple_config:
|
for cx, cy, p in simple_config:
|
||||||
newconfig.append(
|
newconfig.append(
|
||||||
(
|
(
|
||||||
w3 * X(r, 3) + (X4 - sx) / 2 + cx,
|
w3 * X(r, 3) + (X4 - sx) / 2 + cx,
|
||||||
|
@ -326,17 +331,30 @@ class Plakativ:
|
||||||
return len(self.doc)
|
return len(self.doc)
|
||||||
|
|
||||||
def get_input_page_size(self):
|
def get_input_page_size(self):
|
||||||
width = self.doc[self.pagenr].getDisplayList().rect.width
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
height = self.doc[self.pagenr].getDisplayList().rect.height
|
if hasattr(self.doc[self.pagenr], "get_displaylist"):
|
||||||
return (width, height)
|
gdl = self.doc[self.pagenr].get_displaylist
|
||||||
|
else:
|
||||||
|
gdl = self.doc[self.pagenr].getDisplayList
|
||||||
|
rect = gdl().rect
|
||||||
|
return (rect.width, rect.height)
|
||||||
|
|
||||||
def get_image(self, zoom):
|
def get_image(self, zoom):
|
||||||
mat_0 = fitz.Matrix(zoom, zoom)
|
mat_0 = fitz.Matrix(zoom, zoom)
|
||||||
pix = (
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
self.doc[self.pagenr].getDisplayList().getPixmap(matrix=mat_0, alpha=False)
|
if hasattr(self.doc[self.pagenr], "get_displaylist"):
|
||||||
)
|
gdl = self.doc[self.pagenr].get_displaylist()
|
||||||
# the getImageData() function was only introduced in pymupdf 1.14.5
|
else:
|
||||||
|
gdl = self.doc[self.pagenr].getDisplayList()
|
||||||
|
if hasattr(gdl, "get_pixmap"):
|
||||||
|
pix = gdl.get_pixmap(matrix=mat_0, alpha=False)
|
||||||
|
else:
|
||||||
|
pix = gdl.getPixmap(matrix=mat_0, alpha=False)
|
||||||
|
if hasattr(pix, "tobytes"):
|
||||||
|
# getImageData was deprecated in pymupdf 1.19.0
|
||||||
|
return pix.tobytes("ppm")
|
||||||
if hasattr(pix, "getImageData"):
|
if hasattr(pix, "getImageData"):
|
||||||
|
# the getImageData() function was only introduced in pymupdf 1.14.5
|
||||||
return pix.getImageData("ppm")
|
return pix.getImageData("ppm")
|
||||||
else:
|
else:
|
||||||
# this is essentially the same thing that the getImageData()
|
# this is essentially the same thing that the getImageData()
|
||||||
|
@ -369,8 +387,18 @@ class Plakativ:
|
||||||
printable_height = self.layout["output_pagesize"][1] - (
|
printable_height = self.layout["output_pagesize"][1] - (
|
||||||
border_top + border_bottom
|
border_top + border_bottom
|
||||||
)
|
)
|
||||||
inpage_width = pt_to_mm(self.doc[self.pagenr].getDisplayList().rect.width)
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
inpage_height = pt_to_mm(self.doc[self.pagenr].getDisplayList().rect.height)
|
if hasattr(self.doc[self.pagenr], "get_displaylist"):
|
||||||
|
gdl = self.doc[self.pagenr].get_displaylist
|
||||||
|
else:
|
||||||
|
gdl = self.doc[self.pagenr].getDisplayList
|
||||||
|
# this may fail with "RuntimeError: image is too wide"
|
||||||
|
# from pdf_load_image_imp() in pdf-image.c from mupdf for sizes larger
|
||||||
|
# than 1<<16 pixels:
|
||||||
|
# https://bugs.ghostscript.com/show_bug.cgi?id=703839
|
||||||
|
rect = gdl().rect
|
||||||
|
inpage_width = pt_to_mm(rect.width)
|
||||||
|
inpage_height = pt_to_mm(rect.height)
|
||||||
|
|
||||||
if mode in ["size", "mult"]:
|
if mode in ["size", "mult"]:
|
||||||
if mode == "size":
|
if mode == "size":
|
||||||
|
@ -570,7 +598,7 @@ class Plakativ:
|
||||||
# the computed positions and storing the largest border size in
|
# the computed positions and storing the largest border size in
|
||||||
# each dimension
|
# each dimension
|
||||||
poster_top = poster_right = poster_bottom = poster_left = 0
|
poster_top = poster_right = poster_bottom = poster_left = 0
|
||||||
for (posx, posy, p) in self.layout["positions"]:
|
for posx, posy, p in self.layout["positions"]:
|
||||||
if p:
|
if p:
|
||||||
top = posy - border_top
|
top = posy - border_top
|
||||||
if top < 0 and -top > poster_top:
|
if top < 0 and -top > poster_top:
|
||||||
|
@ -625,7 +653,12 @@ class Plakativ:
|
||||||
if not hasattr(self, "layout"):
|
if not hasattr(self, "layout"):
|
||||||
raise LayoutNotComputedException()
|
raise LayoutNotComputedException()
|
||||||
|
|
||||||
inpage_width = pt_to_mm(self.doc[self.pagenr].getDisplayList().rect.width)
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
|
if hasattr(self.doc[self.pagenr], "get_displaylist"):
|
||||||
|
gdl = self.doc[self.pagenr].get_displaylist
|
||||||
|
else:
|
||||||
|
gdl = self.doc[self.pagenr].getDisplayList
|
||||||
|
inpage_width = pt_to_mm(gdl().rect.width)
|
||||||
|
|
||||||
outdoc = fitz.open()
|
outdoc = fitz.open()
|
||||||
|
|
||||||
|
@ -644,7 +677,13 @@ class Plakativ:
|
||||||
)
|
)
|
||||||
/ (self.layout["overallsize"][1]),
|
/ (self.layout["overallsize"][1]),
|
||||||
)
|
)
|
||||||
page = outdoc.newPage(
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
|
if hasattr(outdoc, "new_page"):
|
||||||
|
np = outdoc.new_page
|
||||||
|
else:
|
||||||
|
np = outdoc.newPage
|
||||||
|
|
||||||
|
page = np(
|
||||||
-1, # insert after last page
|
-1, # insert after last page
|
||||||
width=mm_to_pt(self.layout["output_pagesize"][0]),
|
width=mm_to_pt(self.layout["output_pagesize"][0]),
|
||||||
height=mm_to_pt(self.layout["output_pagesize"][1]),
|
height=mm_to_pt(self.layout["output_pagesize"][1]),
|
||||||
|
@ -674,8 +713,15 @@ class Plakativ:
|
||||||
bottom = self.layout["border_right"] * zoom_1
|
bottom = self.layout["border_right"] * zoom_1
|
||||||
left = self.layout["border_bottom"] * zoom_1
|
left = self.layout["border_bottom"] * zoom_1
|
||||||
# inner rectangle
|
# inner rectangle
|
||||||
shape = page.newShape()
|
if hasattr(page, "new_shape"):
|
||||||
shape.drawRect(
|
shape = page.new_shape()
|
||||||
|
else:
|
||||||
|
shape = page.newShape()
|
||||||
|
if hasattr(shape, "draw_rect"):
|
||||||
|
dr = shape.draw_rect
|
||||||
|
else:
|
||||||
|
dr = shape.drawRect
|
||||||
|
dr(
|
||||||
fitz.Rect(
|
fitz.Rect(
|
||||||
x0,
|
x0,
|
||||||
y0,
|
y0,
|
||||||
|
@ -685,7 +731,7 @@ class Plakativ:
|
||||||
)
|
)
|
||||||
shape.finish(color=(0, 0, 1))
|
shape.finish(color=(0, 0, 1))
|
||||||
# outer rectangle
|
# outer rectangle
|
||||||
shape.drawRect(
|
dr(
|
||||||
fitz.Rect(
|
fitz.Rect(
|
||||||
x0 - left,
|
x0 - left,
|
||||||
y0 - top,
|
y0 - top,
|
||||||
|
@ -714,7 +760,12 @@ class Plakativ:
|
||||||
else:
|
else:
|
||||||
page_width = mm_to_pt(self.layout["output_pagesize"][1])
|
page_width = mm_to_pt(self.layout["output_pagesize"][1])
|
||||||
page_height = mm_to_pt(self.layout["output_pagesize"][0])
|
page_height = mm_to_pt(self.layout["output_pagesize"][0])
|
||||||
page = outdoc.newPage(
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
|
if hasattr(outdoc, "new_page"):
|
||||||
|
np = outdoc.new_page
|
||||||
|
else:
|
||||||
|
np = outdoc.newPage
|
||||||
|
page = np(
|
||||||
-1, width=page_width, height=page_height # insert after last page
|
-1, width=page_width, height=page_height # insert after last page
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -758,17 +809,29 @@ class Plakativ:
|
||||||
mm_to_pt(factor * (target_y + target_height)),
|
mm_to_pt(factor * (target_y + target_height)),
|
||||||
)
|
)
|
||||||
|
|
||||||
page.showPDFpage(
|
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used
|
||||||
|
if hasattr(page, "show_pdf_page"):
|
||||||
|
spp = page.show_pdf_page
|
||||||
|
else:
|
||||||
|
spp = page.showPDFpage
|
||||||
|
spp(
|
||||||
targetrect, # fill the whole page
|
targetrect, # fill the whole page
|
||||||
self.doc, # input document
|
self.doc, # input document
|
||||||
self.pagenr, # input page number
|
self.pagenr, # input page number
|
||||||
clip=sourcerect, # part of the input page to use
|
clip=sourcerect, # part of the input page to use
|
||||||
)
|
)
|
||||||
|
|
||||||
shape = page.newShape()
|
if hasattr(page, "new_shape"):
|
||||||
|
shape = page.new_shape()
|
||||||
|
else:
|
||||||
|
shape = page.newShape()
|
||||||
|
if hasattr(shape, "draw_rect"):
|
||||||
|
dr = shape.draw_rect
|
||||||
|
else:
|
||||||
|
dr = shape.drawRect
|
||||||
if guides:
|
if guides:
|
||||||
if portrait:
|
if portrait:
|
||||||
shape.drawRect(
|
dr(
|
||||||
fitz.Rect(
|
fitz.Rect(
|
||||||
mm_to_pt(self.layout["border_left"]),
|
mm_to_pt(self.layout["border_left"]),
|
||||||
mm_to_pt(self.layout["border_top"]),
|
mm_to_pt(self.layout["border_top"]),
|
||||||
|
@ -777,7 +840,7 @@ class Plakativ:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
shape.drawRect(
|
dr(
|
||||||
fitz.Rect(
|
fitz.Rect(
|
||||||
mm_to_pt(self.layout["border_bottom"]),
|
mm_to_pt(self.layout["border_bottom"]),
|
||||||
mm_to_pt(self.layout["border_left"]),
|
mm_to_pt(self.layout["border_left"]),
|
||||||
|
@ -813,7 +876,7 @@ class Plakativ:
|
||||||
)
|
)
|
||||||
if border:
|
if border:
|
||||||
if portrait:
|
if portrait:
|
||||||
shape.drawRect(
|
dr(
|
||||||
fitz.Rect(
|
fitz.Rect(
|
||||||
mm_to_pt(self.layout["border_left"] - x),
|
mm_to_pt(self.layout["border_left"] - x),
|
||||||
mm_to_pt(self.layout["border_top"] - y),
|
mm_to_pt(self.layout["border_top"] - y),
|
||||||
|
@ -830,7 +893,7 @@ class Plakativ:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
shape.drawRect(
|
dr(
|
||||||
fitz.Rect(
|
fitz.Rect(
|
||||||
mm_to_pt(self.layout["border_bottom"] - x),
|
mm_to_pt(self.layout["border_bottom"] - x),
|
||||||
mm_to_pt(self.layout["border_left"] - y),
|
mm_to_pt(self.layout["border_left"] - y),
|
||||||
|
@ -988,7 +1051,10 @@ class Application(tkinter.Frame):
|
||||||
top_frame = tkinter.Frame(frame_right)
|
top_frame = tkinter.Frame(frame_right)
|
||||||
top_frame.pack(fill=tkinter.X)
|
top_frame.pack(fill=tkinter.X)
|
||||||
|
|
||||||
tkinter.Button(top_frame, text="Open PDF", command=self.on_open_button).pack(
|
button_text = "Open PDF"
|
||||||
|
if have_img2pdf:
|
||||||
|
button_text = "Open PDF, JPG, PNG, TIF"
|
||||||
|
tkinter.Button(top_frame, text=button_text, command=self.on_open_button).pack(
|
||||||
side=tkinter.LEFT, expand=tkinter.TRUE, fill=tkinter.X
|
side=tkinter.LEFT, expand=tkinter.TRUE, fill=tkinter.X
|
||||||
)
|
)
|
||||||
tkinter.Button(top_frame, text="Help", state=tkinter.DISABLED).pack(
|
tkinter.Button(top_frame, text="Help", state=tkinter.DISABLED).pack(
|
||||||
|
@ -1142,10 +1208,13 @@ class Application(tkinter.Frame):
|
||||||
self.canvas.delete(tkinter.ALL)
|
self.canvas.delete(tkinter.ALL)
|
||||||
|
|
||||||
if not hasattr(self, "plakativ"):
|
if not hasattr(self, "plakativ"):
|
||||||
|
button_text = "Open PDF"
|
||||||
|
if have_img2pdf:
|
||||||
|
button_text = "Open PDF, JPG, PNG, TIF"
|
||||||
self.canvas.create_text(
|
self.canvas.create_text(
|
||||||
self.canvas_size[0] / 2,
|
self.canvas_size[0] / 2,
|
||||||
self.canvas_size[1] / 2,
|
self.canvas_size[1] / 2,
|
||||||
text='Click on the "Open PDF" button in the upper right.',
|
text='Click on the "%s" button in the upper right.' % button_text,
|
||||||
fill="white",
|
fill="white",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -1201,7 +1270,7 @@ class Application(tkinter.Frame):
|
||||||
|
|
||||||
# draw rectangles
|
# draw rectangles
|
||||||
# TODO: also draw numbers indicating the page number
|
# TODO: also draw numbers indicating the page number
|
||||||
for (x, y, portrait) in self.plakativ.layout["positions"]:
|
for x, y, portrait in self.plakativ.layout["positions"]:
|
||||||
x0 = (x + self.plakativ.layout["posterpos"][0]) * zoom_1 + (
|
x0 = (x + self.plakativ.layout["posterpos"][0]) * zoom_1 + (
|
||||||
self.canvas_size[0] - zoom_1 * self.plakativ.layout["overallsize"][0]
|
self.canvas_size[0] - zoom_1 * self.plakativ.layout["overallsize"][0]
|
||||||
) / 2
|
) / 2
|
||||||
|
@ -1289,8 +1358,6 @@ class Application(tkinter.Frame):
|
||||||
)
|
)
|
||||||
# remove alpha channel
|
# remove alpha channel
|
||||||
if remove_alpha:
|
if remove_alpha:
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
img = Image.open(self.filename).convert("RGBA")
|
img = Image.open(self.filename).convert("RGBA")
|
||||||
background = Image.new("RGBA", img.size, (255, 255, 255))
|
background = Image.new("RGBA", img.size, (255, 255, 255))
|
||||||
img = Image.alpha_composite(background, img)
|
img = Image.alpha_composite(background, img)
|
||||||
|
@ -1647,6 +1714,7 @@ class BorderSizeWidget(tkinter.LabelFrame):
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
self.variables[n] = tkinter.DoubleVar()
|
self.variables[n] = tkinter.DoubleVar()
|
||||||
|
|
||||||
# need to pass k and v as function arguments so that their value
|
# need to pass k and v as function arguments so that their value
|
||||||
# does not get overwritten each loop iteration
|
# does not get overwritten each loop iteration
|
||||||
def callback(varname, idx, op, k_copy=n, v_copy=self.variables[n]):
|
def callback(varname, idx, op, k_copy=n, v_copy=self.variables[n]):
|
||||||
|
|
|
@ -149,8 +149,8 @@ def test_cases(postersize, input_pagesize, output_pagesize, strategy, expected):
|
||||||
height = mm_to_pt(input_pagesize[1])
|
height = mm_to_pt(input_pagesize[1])
|
||||||
|
|
||||||
doc = fitz.open()
|
doc = fitz.open()
|
||||||
page = doc.newPage(pno=-1, width=width, height=height)
|
page = doc.new_page(pno=-1, width=width, height=height)
|
||||||
img = page.newShape()
|
img = page.new_shape()
|
||||||
|
|
||||||
red = fitz.utils.getColor("red")
|
red = fitz.utils.getColor("red")
|
||||||
green = fitz.utils.getColor("green")
|
green = fitz.utils.getColor("green")
|
||||||
|
@ -189,7 +189,7 @@ def test_cases(postersize, input_pagesize, output_pagesize, strategy, expected):
|
||||||
|
|
||||||
doc = fitz.open(outfile)
|
doc = fitz.open(outfile)
|
||||||
|
|
||||||
for pnum, (bbox, matrix) in zip(range(doc.pageCount), expected):
|
for pnum, (bbox, matrix) in zip(range(doc.page_count), expected):
|
||||||
xreflist = doc._getPageInfo(pnum, 3)
|
xreflist = doc._getPageInfo(pnum, 3)
|
||||||
assert len(xreflist) >= 1
|
assert len(xreflist) >= 1
|
||||||
xref, name, _, _ = xreflist[0]
|
xref, name, _, _ = xreflist[0]
|
||||||
|
@ -209,7 +209,7 @@ def test_cases(postersize, input_pagesize, output_pagesize, strategy, expected):
|
||||||
# >>
|
# >>
|
||||||
keyvals = dict(
|
keyvals = dict(
|
||||||
tuple(line.strip().split(maxsplit=1))
|
tuple(line.strip().split(maxsplit=1))
|
||||||
for line in doc.xrefObject(xref).splitlines()
|
for line in doc.xref_object(xref).splitlines()
|
||||||
if " " in line.strip()
|
if " " in line.strip()
|
||||||
)
|
)
|
||||||
assert "/BBox" in keyvals
|
assert "/BBox" in keyvals
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
VERSION = "0.5"
|
VERSION = "0.5.2"
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="plakativ",
|
name="plakativ",
|
||||||
|
|
Loading…
Reference in a new issue