Compare commits

..

No commits in common. "main" and "441ec3a6213233b714130f35ebc9ec77da68fcd5" have entirely different histories.

7 changed files with 43 additions and 128 deletions

View file

@ -2,21 +2,6 @@
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,8 +48,9 @@ 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. The resulting artifacts are attached to each release: appveyor. If you don't want to install Python before using plakativ you can
https://gitlab.mister-muffin.de/josch/plakativ/releases head to appveyor and click on "Artifacts" to download the latest version:
https://ci.appveyor.com/project/josch/plakativ
Complex Layouter Complex Layouter
================ ================

View file

@ -14,9 +14,6 @@ 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 Marin Rodrigues</developer_name> <developer_name>Johannes Schauer</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 - 2021 Johannes Schauer Marin Rodrigues <josch@mister-muffin.de> # Copyright (C) 2019 Johannes 'josch' Schauer
# #
# 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,11 +25,6 @@ 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
@ -51,7 +46,7 @@ except ImportError:
tkinter.Menubutton = dummy tkinter.Menubutton = dummy
tkinter.LabelFrame = dummy tkinter.LabelFrame = dummy
VERSION = "0.5.2" VERSION = "0.4"
PAGE_SIZES = OrderedDict( PAGE_SIZES = OrderedDict(
[ [
@ -272,7 +267,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,
@ -286,7 +281,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,
@ -331,30 +326,17 @@ class Plakativ:
return len(self.doc) return len(self.doc)
def get_input_page_size(self): def get_input_page_size(self):
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used width = self.doc[self.pagenr].getDisplayList().rect.width
if hasattr(self.doc[self.pagenr], "get_displaylist"): height = self.doc[self.pagenr].getDisplayList().rect.height
gdl = self.doc[self.pagenr].get_displaylist return (width, height)
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)
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used pix = (
if hasattr(self.doc[self.pagenr], "get_displaylist"): self.doc[self.pagenr].getDisplayList().getPixmap(matrix=mat_0, alpha=False)
gdl = self.doc[self.pagenr].get_displaylist() )
else: # the getImageData() function was only introduced in pymupdf 1.14.5
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()
@ -387,18 +369,8 @@ class Plakativ:
printable_height = self.layout["output_pagesize"][1] - ( printable_height = self.layout["output_pagesize"][1] - (
border_top + border_bottom border_top + border_bottom
) )
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used inpage_width = pt_to_mm(self.doc[self.pagenr].getDisplayList().rect.width)
if hasattr(self.doc[self.pagenr], "get_displaylist"): inpage_height = pt_to_mm(self.doc[self.pagenr].getDisplayList().rect.height)
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":
@ -598,7 +570,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:
@ -653,12 +625,7 @@ class Plakativ:
if not hasattr(self, "layout"): if not hasattr(self, "layout"):
raise LayoutNotComputedException() raise LayoutNotComputedException()
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used inpage_width = pt_to_mm(self.doc[self.pagenr].getDisplayList().rect.width)
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()
@ -677,13 +644,7 @@ class Plakativ:
) )
/ (self.layout["overallsize"][1]), / (self.layout["overallsize"][1]),
) )
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used page = outdoc.newPage(
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]),
@ -713,15 +674,8 @@ 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.newShape()
shape = page.new_shape() shape.drawRect(
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,
@ -731,7 +685,7 @@ class Plakativ:
) )
shape.finish(color=(0, 0, 1)) shape.finish(color=(0, 0, 1))
# outer rectangle # outer rectangle
dr( shape.drawRect(
fitz.Rect( fitz.Rect(
x0 - left, x0 - left,
y0 - top, y0 - top,
@ -760,12 +714,7 @@ 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])
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used page = outdoc.newPage(
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
) )
@ -809,29 +758,17 @@ class Plakativ:
mm_to_pt(factor * (target_y + target_height)), mm_to_pt(factor * (target_y + target_height)),
) )
# since pymupdf 1.19.0 a warning will be issued if the deprecated names are used page.showPDFpage(
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.newShape()
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:
dr( shape.drawRect(
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"]),
@ -840,7 +777,7 @@ class Plakativ:
) )
) )
else: else:
dr( shape.drawRect(
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"]),
@ -876,7 +813,7 @@ class Plakativ:
) )
if border: if border:
if portrait: if portrait:
dr( shape.drawRect(
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),
@ -893,7 +830,7 @@ class Plakativ:
) )
) )
else: else:
dr( shape.drawRect(
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),
@ -942,7 +879,7 @@ class VerticalScrolledFrame(tkinter.Frame):
borderwidth=0, borderwidth=0,
highlightthickness=0, highlightthickness=0,
yscrollcommand=vscrollbar.set, yscrollcommand=vscrollbar.set,
width=240 * parent.winfo_fpixels("1i") / 96.0, width=240,
) )
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)
@ -1051,10 +988,7 @@ 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)
button_text = "Open PDF" tkinter.Button(top_frame, text="Open PDF", command=self.on_open_button).pack(
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(
@ -1208,13 +1142,10 @@ 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 "%s" button in the upper right.' % button_text, text='Click on the "Open PDF" button in the upper right.',
fill="white", fill="white",
) )
return return
@ -1270,7 +1201,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
@ -1358,6 +1289,8 @@ 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)
@ -1714,7 +1647,6 @@ 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]):
@ -2270,7 +2202,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 Schauer Marin Rodrigues <josch@mister-muffin.de> Written by Johannes 'josch' Schauer <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.new_page(pno=-1, width=width, height=height) page = doc.newPage(pno=-1, width=width, height=height)
img = page.new_shape() img = page.newShape()
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.page_count), expected): for pnum, (bbox, matrix) in zip(range(doc.pageCount), 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.xref_object(xref).splitlines() for line in doc.xrefObject(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.5.2" VERSION = "0.4"
setup( setup(
name="plakativ", name="plakativ",
version=VERSION, version=VERSION,
author="Johannes Schauer Marin Rodrigues", author="Johannes 'josch' Schauer",
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",