Compare commits

..

10 commits
0.5 ... main

6 changed files with 118 additions and 38 deletions

View file

@ -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)
---------------- ----------------

View file

@ -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
================ ================

View file

@ -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"

View file

@ -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
if hasattr(page, "new_shape"):
shape = page.new_shape()
else:
shape = page.newShape() shape = page.newShape()
shape.drawRect( 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
) )
if hasattr(page, "new_shape"):
shape = page.new_shape()
else:
shape = page.newShape() 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]):

View file

@ -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

View file

@ -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",