from reportlab.lib.units import inch from reportlab.pdfgen import canvas from PIL import Image, ImageDraw 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 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 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, ) # 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")