# Lay the 2 in stickers out for Printo as a 10-page A4 PDF (one page per kiss-cut sheet), easy to cut apart by hand. # Each page is an even grid, so a few straight edge-to-edge scissor cuts give single stickers on their backing. # The five designs come in equal numbers over the 10 pages: each page holds every design as evenly as the grid allows, # and the leftover slots rotate from page to page. # Writes .pdf (art + scissor lines + cut line), -art.pdf, -cut.pdf, -pages/page-NN.svg, -preview.png. # usage: python sheet-layout.py import io, os, re, sys import numpy as np, cairosvg from PIL import Image from pypdf import PdfWriter, PdfReader SRC, OUT = sys.argv[1:3] COLS, ROWS = int(sys.argv[3]), int(sys.argv[4]) DPI, PAGE_W, PAGE_H, PAGES = 300, 2480, 3508, 10 DESIGNS = ["witch-hex-cellent", "bat-going-batty", "vampire-fang-cy", "ghost-boo-always-online", "ghost-no-ghost-customers"] # Scissor lines: sparse dashes, 3 mm on and 9 mm off, ~0.35 mm thick, in a mid green-grey that shows on white vinyl. GUIDE = '{}' def load(name): svg = open(f"{SRC}/{name}.svg").read() px = round(float(re.search(r'width="([\d.]+)in"', svg).group(1)) * DPI) b, c = svg.index('")] cut = cut[cut.index(">") + 1: cut.rindex("")] filled = f'{cut}' m = np.array(Image.open(io.BytesIO(cairosvg.svg2png(bytestring=filled.encode(), output_width=px * 2, output_height=px * 2))).convert("RGBA"))[:, :, 3] > 128 ys, xs = np.nonzero(m) return art, cut, (xs.min() / 2, ys.min() / 2, (xs.max() + 1) / 2, (ys.max() + 1) / 2) def page_order(p, slots): """Cycle through the designs row by row; with 3 columns no design sits beside or above itself.""" extra = slots % len(DESIGNS) shift = (p * extra) % len(DESIGNS) order = DESIGNS[shift:] + DESIGNS[:shift] return order * (slots // len(DESIGNS)) + order[:extra] stickers = {n: load(n) for n in DESIGNS} cw, ch = PAGE_W / COLS, PAGE_H / ROWS for n, (_, _, (x0, y0, x1, y1)) in stickers.items(): assert x1 - x0 < cw - 60 and y1 - y0 < ch - 60, f"{n} leaves under 2.5 mm to the scissor line" lines = "".join(f'' for i in range(1, COLS)) + "".join(f'' for j in range(1, ROWS)) head = f'' os.makedirs(f"{OUT}-pages", exist_ok=True) full, art_only, cut_only, thumbs, counts = PdfWriter(), PdfWriter(), PdfWriter(), [], {n: 0 for n in DESIGNS} for p in range(PAGES): art, cuts = [], [] for k, name in enumerate(page_order(p, COLS * ROWS)): counts[name] += 1 a, c, (bx0, by0, bx1, by1) = stickers[name] X = (k % COLS) * cw + (cw - (bx1 - bx0)) / 2 - bx0 Y = (k // COLS) * ch + (ch - (by1 - by0)) / 2 - by0 art.append(f'{a}') cuts.append(f'{c}') art_g = f'{"".join(art)}' guide_g = GUIDE.format(lines) cut_g = f'{"".join(cuts)}' title = f"MyBazaar Halloween stickers - A4 kiss-cut sheet {p + 1} of {PAGES}, {COLS}x{ROWS} grid of 2 in (AI-generated art)" svg = head + title + art_g + guide_g + cut_g + "" open(f"{OUT}-pages/page-{p + 1:02d}.svg", "w").write(svg) for writer, body in ((full, svg), (art_only, head + art_g + guide_g + ""), (cut_only, head + cut_g + "")): writer.add_page(PdfReader(io.BytesIO(cairosvg.svg2pdf(bytestring=body.encode()))).pages[0]) prev = head + f'' + art_g + guide_g + cut_g.replace('stroke-width="1"', 'stroke-width="5"') + "" thumbs.append(Image.open(io.BytesIO(cairosvg.svg2png(bytestring=prev.encode(), output_width=PAGE_W // 4, output_height=PAGE_H // 4)))) for writer, suffix in ((full, ""), (art_only, "-art"), (cut_only, "-cut")): with open(f"{OUT}{suffix}.pdf", "wb") as f: writer.write(f) tw, th = thumbs[0].size sheet = Image.new("RGB", (5 * tw + 6 * 24, 2 * th + 3 * 24), "#C9D3D1") for i, t in enumerate(thumbs): sheet.paste(t, (24 + (i % 5) * (tw + 24), 24 + (i // 5) * (th + 24))) sheet.save(f"{OUT}-preview.png") print(f"{COLS}x{ROWS}:", {n: v for n, v in counts.items()}, "total", sum(counts.values()))