import csv import os import re from io import BytesIO from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from pypdf import PdfReader, PdfWriter # ------------------------------------------------------------------ # 1. Load a Chinese font # ------------------------------------------------------------------ def load_chinese_font(): """Register SimSun from simsun.ttc (sub-font 0) or fall back to SimHei.""" if os.path.isfile("simsun.ttc"): pdfmetrics.registerFont(TTFont("SimSun", "simsun.ttc", subfontIndex=0)) print("? Using SimSun (simsun.ttc, subfont 0)") return "SimSun" elif os.path.isfile("simhei.ttf"): pdfmetrics.registerFont(TTFont("SimHei", "simhei.ttf")) print("? Using SimHei (simhei.ttf)") return "SimHei" else: raise RuntimeError("? Please place simsun.ttc or simhei.ttf in this folder.") FONT_NAME = load_chinese_font() FONT_SIZE = 20 # ------------------------------------------------------------------ # 2. Paths & layout constants # ------------------------------------------------------------------ TEMPLATE_PATH = "certificate.pdf" CSV_PATH = "recipients.csv" OUT_DIR = "kl" PAGE_WIDTH, PAGE_HEIGHT = A4 Y_NAME = 450 Y_IC = 420 os.makedirs(OUT_DIR, exist_ok=True) # ------------------------------------------------------------------ # 3. Helper: compute X for Name (shift right on long names) # ------------------------------------------------------------------ def compute_name_x(name: str) -> float: """ X-coordinate logic: • =10 chars ? centred • 11-20 chars ? +2 pt right per char beyond 10 • >20 chars ? +2 pt/char for chars 11-20 plus +4 pt/char beyond 20 cap shift at 100 pt """ name_width = pdfmetrics.stringWidth(name, FONT_NAME, FONT_SIZE) x_center = (PAGE_WIDTH - name_width) / 2.0 length = len(name) shift = 0 if length > 10: tier1 = min(length, 20) - 10 # 11-20 shift += tier1 * 2 if length > 20: tier2 = length - 20 # 21+ shift += tier2 * 4 return x_center + min(shift, 100) # ------------------------------------------------------------------ # 4. Generate certificates # ------------------------------------------------------------------ generated_names = [] # store names successfully generated with open(CSV_PATH, newline="", encoding="utf-8-sig") as f: reader = csv.DictReader(f) for row in reader: name = (row.get("Name") or "").strip() ic = (row.get("IC") or "").strip() if not name: print("?? Skipped row (no name found).") continue # ---- Create overlay ---- buf = BytesIO() c = canvas.Canvas(buf, pagesize=A4) c.setFont(FONT_NAME, FONT_SIZE) # Name (shifted if long) x_name = compute_name_x(name) c.drawString(x_name, Y_NAME, name) # IC (only if present, centred) if ic: ic_width = pdfmetrics.stringWidth(ic, FONT_NAME, FONT_SIZE) x_ic = (PAGE_WIDTH - ic_width) / 2 c.drawString(x_ic, Y_IC, ic) c.save() buf.seek(0) overlay_page = PdfReader(buf).pages[0] # Fresh template template_page = PdfReader(TEMPLATE_PATH).pages[0] if hasattr(template_page, "merge_page"): # pypdf =3 template_page.merge_page(overlay_page) else: template_page.mergePage(overlay_page) # PyPDF2 # Clean Name for filename name_clean = re.sub(r"[^\w\-?-?]", "_", name) out_main = os.path.join(OUT_DIR, f"{name_clean}.pdf") # Save PDF writer = PdfWriter() writer.add_page(template_page) with open(out_main, "wb") as f_out: writer.write(f_out) print(f"? Created {out_main}") generated_names.append(name_clean) # ------------------------------------------------------------------ # 5. Summary # ------------------------------------------------------------------ print("\n?? Certificate generation completed.") print(f"? Total generated: {len(generated_names)}")