Compare commits

..

14 commits

Author SHA1 Message Date
e415d0202a
README.md: stop referencing appveyor for installation 2024-08-07 07:42:21 +02:00
e84aa8d42e
release version 0.5.2 2024-01-12 09:47:42 +01:00
9475574481
plakativ.py: black 2024-01-12 09:45:41 +01:00
2aa1271aec
set Image.MAX_IMAGE_PIXELS=None when using img2pdf 2024-01-12 09:45:20 +01:00
fab6925674
use new snake_case naming for pymupdf 1.23.0 and later
closes: #6
2023-10-27 11:29:19 +02:00
d0e5c1e48d
add comment for loading error for images larger than 65536 pixel 2022-08-26 08:22:45 +02:00
d4ed39b7d2
replace the 'Open PDF' button text with 'Open PDF, JPG, PNG, TIF' if we have img2pdf 2022-08-26 08:11:09 +02:00
Johannes Schauer Marin Rodrigues
20928a8570 release version 0.5.1 2022-07-03 07:28:40 +02:00
d6d0a6ea06 use new snake_case naming for pymupdf 1.19.0 and later 2022-07-03 07:24:53 +02:00
a49d7b0a53 appveyor.yml: build for x64 as well as for x86 2022-07-03 07:24:53 +02:00
4ac61db781
release version 0.5 2021-10-11 10:23:23 +02:00
1e827c2186
reformat with black 2021-10-11 10:23:04 +02:00
d58d02092a
adjust the size of the right frame depending on the display dpi 2021-10-09 20:26:41 +02:00
6f8a42801f
update my name 2021-10-09 20:25:43 +02:00
7 changed files with 128 additions and 43 deletions

View file

@ -2,6 +2,21 @@
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)
----------------
- support for HiDPI displays
0.4 (2021-03-04) 0.4 (2021-03-04)
---------------- ----------------

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

@ -7,7 +7,7 @@
<metadata_license>FSFAP</metadata_license> <metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0</project_license> <project_license>GPL-3.0</project_license>
<developer_name>Johannes Schauer</developer_name> <developer_name>Johannes Schauer Marin Rodrigues</developer_name>
<description> <description>
<p> <p>

View file

@ -6,7 +6,7 @@
# pieces and putting each of them onto a paper size that can be printed # pieces and putting each of them onto a paper size that can be printed
# normally. The result can then be glued together into a bigger poster. # normally. The result can then be glued together into a bigger poster.
# #
# Copyright (C) 2019 Johannes 'josch' Schauer # Copyright (C) 2019 - 2021 Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
# #
# This program is free software: you can redistribute it and/or modify it under # This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 3 as published by the # the terms of the GNU General Public License version 3 as published by the
@ -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.4" 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),
@ -879,7 +942,7 @@ class VerticalScrolledFrame(tkinter.Frame):
borderwidth=0, borderwidth=0,
highlightthickness=0, highlightthickness=0,
yscrollcommand=vscrollbar.set, yscrollcommand=vscrollbar.set,
width=240, width=240 * parent.winfo_fpixels("1i") / 96.0,
) )
canvas.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.TRUE) canvas.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.TRUE)
vscrollbar.config(command=canvas.yview) vscrollbar.config(command=canvas.yview)
@ -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]):
@ -2202,7 +2270,7 @@ you can instruct plakativ to remove the alpha channel for you with the
$ plakativ --size A1 --output=poster.pdf --remove-alpha input.png $ plakativ --size A1 --output=poster.pdf --remove-alpha input.png
Written by Johannes 'josch' Schauer <josch@mister-muffin.de> Written by Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues Report bugs at https://gitlab.mister-muffin.de/josch/plakativ/issues
""" """

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,11 +1,11 @@
from setuptools import setup from setuptools import setup
VERSION = "0.4" VERSION = "0.5.2"
setup( setup(
name="plakativ", name="plakativ",
version=VERSION, version=VERSION,
author="Johannes 'josch' Schauer", author="Johannes Schauer Marin Rodrigues",
author_email="josch@mister-muffin.de", author_email="josch@mister-muffin.de",
description="Convert a PDF into a large poster that can be printed on multiple smaller pages.", description="Convert a PDF into a large poster that can be printed on multiple smaller pages.",
long_description="file: README.md, CHANGELOG.rst", long_description="file: README.md, CHANGELOG.rst",