Files
SporkCards/spork.py

225 lines
7.0 KiB
Python
Raw Permalink Normal View History

from reportlab.lib.units import inch
from reportlab.pdfgen import canvas
from PIL import Image, ImageDraw, ImageFont
import os
import tempfile
# Colors (R, G, B)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 128, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
GRAY = (128, 128, 128)
LIGHT_GRAY = (192, 192, 192)
DARK_GRAY = (64, 64, 64)
class LoadedImage:
def __init__(self, filepath):
if not os.path.exists(filepath):
raise FileNotFoundError(f"Image not found: {filepath}")
self.filepath = filepath
self.image = Image.open(filepath)
def CropPixelsL(self, pixels):
w, h = self.image.size
self.image = self.image.crop((pixels, 0, w, h))
return self
def CropPixelsR(self, pixels):
w, h = self.image.size
self.image = self.image.crop((0, 0, w - pixels, h))
return self
def CropPixelsT(self, pixels):
w, h = self.image.size
self.image = self.image.crop((0, pixels, w, h))
return self
def CropPixelsB(self, pixels):
w, h = self.image.size
self.image = self.image.crop((0, 0, w, h - pixels))
return self
def CropSquare(self):
w, h = self.image.size
if w > h:
excess = w - h
left = excess // 2
self.image = self.image.crop((left, 0, left + h, h))
elif h > w:
excess = h - w
top = excess // 2
self.image = self.image.crop((0, top, w, top + w))
return self
def KeepPixelsT(self, pixels):
w, h = self.image.size
self.image = self.image.crop((0, 0, w, pixels))
return self
def KeepPixelsB(self, pixels):
w, h = self.image.size
self.image = self.image.crop((0, h - pixels, w, h))
return self
def KeepPixelsL(self, pixels):
w, h = self.image.size
self.image = self.image.crop((0, 0, pixels, h))
return self
def KeepPixelsR(self, pixels):
w, h = self.image.size
self.image = self.image.crop((w - pixels, 0, w, h))
return self
def KeepSquare(self, pixels):
w, h = self.image.size
cx, cy = w // 2, h // 2
half = pixels // 2
self.image = self.image.crop((cx - half, cy - half, cx - half + pixels, cy - half + pixels))
return self
def ReadPNG(filepath):
return LoadedImage(filepath)
def ReadJPG(filepath):
return LoadedImage(filepath)
DPI = 300
# Alignment
LEFT = "left"
CENTER = "center"
RIGHT = "right"
def _render_text(page, text, font_path, size_pt, x, y, w, h, align, color):
size_px = int(size_pt * DPI / 72)
font = ImageFont.truetype(font_path, size_px)
draw = ImageDraw.Draw(page)
bbox = draw.textbbox((0, 0), text, font=font)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
xp = int(x * DPI)
yp = int(y * DPI)
wp = int(w * DPI)
hp = int(h * DPI)
if align == CENTER:
tx = xp + (wp - tw) // 2
elif align == RIGHT:
tx = xp + wp - tw
else:
tx = xp
ty = yp + (hp - th) // 2
draw.text((tx, ty - bbox[1]), text, font=font, fill=color + (255,))
class NewPDF:
def __init__(self, width_inches, height_inches):
self.width_inches = width_inches
self.height_inches = height_inches
self.width = width_inches * inch
self.height = height_inches * inch
self._ops = []
def layer(self, img, x_inches, y_inches, w_inches, h_inches):
self._ops.append(("layer", img, x_inches, y_inches, w_inches, h_inches))
def copy(self, x0, y0, x1, y1, x2, y2):
self._ops.append(("copy", x0, y0, x1, y1, x2, y2))
def fill(self, color):
self._ops.append(("fill", color))
def line(self, color, x0, y0, x1, y1, width=1):
self._ops.append(("line", color, x0, y0, x1, y1, width))
def text(self, string, font, size, x, y, w, h, align=LEFT, color=BLACK):
self._ops.append(("text", string, font, size, x, y, w, h, align, color))
def save(self, filename):
pw = int(self.width_inches * DPI)
ph = int(self.height_inches * DPI)
page = Image.new("RGBA", (pw, ph), (255, 255, 255, 255))
for op in self._ops:
if op[0] == "layer":
_, img, x, y, w, h = op
xp = int(x * DPI)
yp = int(y * DPI)
wp = int(w * DPI)
hp = int(h * DPI)
resized = img.image.resize((wp, hp), Image.LANCZOS)
if resized.mode == "RGBA":
page.paste(resized, (xp, yp), resized)
else:
page.paste(resized, (xp, yp))
elif op[0] == "copy":
_, x0, y0, x1, y1, x2, y2 = op
region = page.crop((
int(x0 * DPI), int(y0 * DPI),
int(x1 * DPI), int(y1 * DPI),
))
page.paste(region, (int(x2 * DPI), int(y2 * DPI)))
elif op[0] == "fill":
_, color = op
page.paste(color + (255,), (0, 0, pw, ph))
elif op[0] == "line":
_, color, x0, y0, x1, y1, width = op
draw = ImageDraw.Draw(page)
draw.line(
[(int(x0 * DPI), int(y0 * DPI)),
(int(x1 * DPI), int(y1 * DPI))],
fill=color + (255,), width=width,
)
elif op[0] == "text":
_, string, font, size, x, y, w, h, align, color = op
if size == 0:
wp = int(w * DPI)
hp = int(h * DPI)
lo, hi = 1, 1000
while lo < hi:
mid = (lo + hi + 1) // 2
size_px = int(mid * DPI / 72)
f = ImageFont.truetype(font, size_px)
bbox = ImageDraw.Draw(page).textbbox((0, 0), string, font=f)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
if tw <= wp and th <= hp:
lo = mid
else:
hi = mid - 1
size = lo
_render_text(page, string, font, size, x, y, w, h, align, color)
# Write composited image to PDF
rgb_page = page.convert("RGB")
c = canvas.Canvas(filename, pagesize=(self.width, self.height))
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
try:
rgb_page.save(tmp.name, "PNG")
c.drawImage(tmp.name, 0, 0, self.width, self.height)
finally:
os.unlink(tmp.name)
c.save()
print(f"Saved: {filename}")
# ── Example usage ──
if __name__ == "__main__":
outfile = NewPDF(8.5, 11)
a = ReadPNG("mushroom.png")
b = ReadPNG("PictureFrame.png")
outfile.layer(a, 0.5, 0.5, 3.5, 3.5)
outfile.layer(b, 0.5, 0.5, 3.5, 3.5)
outfile.save("output.pdf")