""" generate_pdfs.py ---------------- Generates realistic sample PDFs for adobe-to-docusign migration testing. Each PDF mirrors the form fields described in the matching *-formfields.json so that tab positions map to visible labels on the document. Adobe rect coordinates are top-left origin; DocuSign yPosition is bottom-left. Formula: docusign_y = PAGE_HEIGHT - adobe_top - adobe_height To place a *label* just above a field: label_y = PAGE_HEIGHT - adobe_top + 2 """ import base64 import json from pathlib import Path from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas W, H = letter # 612 × 792 pt SAMPLE_DIR = Path(__file__).parent.parent / "sample-templates" SAMPLE_DIR.mkdir(exist_ok=True) def save_b64(pdf_path: Path) -> Path: b64_path = pdf_path.with_suffix(".pdf.b64") b64_path.write_text(base64.b64encode(pdf_path.read_bytes()).decode()) return b64_path # --------------------------------------------------------------------------- # Helper: draw a labelled field box at an Adobe-style rect # --------------------------------------------------------------------------- def draw_field(c: canvas.Canvas, label: str, rect: dict, page_h: float = H, field_hint: str = ""): left = rect["left"] top = rect["top"] width = rect["width"] height = rect["height"] # Convert Adobe top-origin to ReportLab bottom-origin rl_y = page_h - top - height # Label just above the box c.setFont("Helvetica", 9) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawString(left, rl_y + height + 3, label + (" [" + field_hint + "]" if field_hint else ":")) # Field box c.setStrokeColorRGB(0.5, 0.5, 0.5) c.setFillColorRGB(0.97, 0.97, 1.0) c.rect(left, rl_y, width, height, fill=1, stroke=1) c.setFillColorRGB(0, 0, 0) def draw_radio_option(c: canvas.Canvas, value: str, rect: dict, page_h: float = H): left = rect["left"] top = rect["top"] size = min(rect["width"], rect["height"]) rl_y = page_h - top - size c.setStrokeColorRGB(0.3, 0.3, 0.3) c.setFillColorRGB(1, 1, 1) c.circle(left + size / 2, rl_y + size / 2, size / 2, fill=1, stroke=1) c.setFont("Helvetica", 9) c.setFillColorRGB(0, 0, 0) c.drawString(left + size + 3, rl_y + 1, value) def draw_checkbox(c: canvas.Canvas, label: str, rect: dict, page_h: float = H): left = rect["left"] top = rect["top"] size = min(rect["width"], rect["height"]) rl_y = page_h - top - size c.setStrokeColorRGB(0.3, 0.3, 0.3) c.setFillColorRGB(1, 1, 1) c.rect(left, rl_y, size, size, fill=1, stroke=1) c.setFont("Helvetica", 9) c.setFillColorRGB(0, 0, 0) c.drawString(left + size + 5, rl_y + 1, label) def draw_signature_block(c: canvas.Canvas, label: str, rect: dict, page_h: float = H): left = rect["left"] top = rect["top"] width = rect["width"] height = rect["height"] rl_y = page_h - top - height c.setFont("Helvetica", 9) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawString(left, rl_y + height + 3, label + ":") c.setStrokeColorRGB(0.2, 0.4, 0.8) c.setFillColorRGB(0.93, 0.96, 1.0) c.rect(left, rl_y, width, height, fill=1, stroke=1) c.setFillColorRGB(0.2, 0.4, 0.8) c.setFont("Helvetica-Oblique", 8) c.drawString(left + 4, rl_y + 4, "Sign here") c.setFillColorRGB(0, 0, 0) def section_header(c: canvas.Canvas, text: str, y: float): c.setFont("Helvetica-Bold", 11) c.setFillColorRGB(0.1, 0.2, 0.5) c.drawString(72, y, text) c.setStrokeColorRGB(0.1, 0.2, 0.5) c.line(72, y - 3, W - 72, y - 3) c.setFillColorRGB(0, 0, 0) # =========================================================================== # 1. Employee Onboarding Form (2 pages) # =========================================================================== def generate_onboarding(): path = SAMPLE_DIR / "onboarding-sample.pdf" c = canvas.Canvas(str(path), pagesize=letter) # ---- Page 1: Employee fills ---- c.setFont("Helvetica-Bold", 18) c.setFillColorRGB(0.1, 0.2, 0.5) c.drawCentredString(W / 2, H - 60, "Employee Onboarding Form") c.setFont("Helvetica", 10) c.setFillColorRGB(0.4, 0.4, 0.4) c.drawCentredString(W / 2, H - 78, "Please complete all required fields before your first day.") c.setFillColorRGB(0, 0, 0) section_header(c, "Personal Information", H - 110) fields_page1 = json.loads((SAMPLE_DIR / "onboarding-template-formfields.json").read_text()) for f in fields_page1: locs = f.get("locations", []) if not locs or locs[0]["pageNumber"] != 1: continue rect = locs[0]["rect"] ft = f["type"] label = f["fieldName"].replace("Employee", "Employee ").strip() if ft == "TEXT_FIELD": draw_field(c, label, rect) elif ft == "DATE": draw_field(c, label, rect, field_hint="MM/DD/YYYY") elif ft == "DROPDOWN": draw_field(c, label, rect, field_hint=", ".join(f.get("items", []))) elif ft == "CHECKBOX": draw_checkbox(c, "Health & Dental Benefits", rect) elif ft == "RADIO": # Draw label above first radio if locs: first_rect = locs[0]["rect"] rl_y = H - first_rect["top"] - first_rect["height"] c.setFont("Helvetica", 9) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawString(first_rect["left"], rl_y + first_rect["height"] + 3, "Commute Option:") c.setFillColorRGB(0, 0, 0) items = f.get("items", []) for i, loc in enumerate(locs): val = items[i] if i < len(items) else str(i) draw_radio_option(c, val, loc["rect"]) c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 1 of 2 — Employee Onboarding Form") c.showPage() # ---- Page 2: HR section + signatures ---- c.setFont("Helvetica-Bold", 14) c.setFillColorRGB(0.1, 0.2, 0.5) c.drawCentredString(W / 2, H - 50, "Employee Onboarding Form — Signatures") c.setFillColorRGB(0, 0, 0) section_header(c, "HR Notes (Internal)", H - 90) for f in fields_page1: locs = f.get("locations", []) if not locs or locs[0]["pageNumber"] != 2: continue rect = locs[0]["rect"] ft = f["type"] if ft == "TEXT_FIELD": draw_field(c, f["fieldName"], rect, field_hint="HR use only") elif ft == "SIGNATURE": draw_signature_block(c, f["fieldName"], rect) section_header(c, "Acknowledgement", H - 260) c.setFont("Helvetica", 9) c.setFillColorRGB(0.2, 0.2, 0.2) disclaimer = ( "By signing below, the employee confirms that all information provided is accurate " "and that they have read and understood the company policies. HR Representative " "countersigns to approve onboarding completion." ) text = c.beginText(72, H - 280) text.setFont("Helvetica", 9) text.setFillColor((0.2, 0.2, 0.2)) # Simple word wrap words = disclaimer.split() line, lines = [], [] for w in words: line.append(w) if len(" ".join(line)) > 72: lines.append(" ".join(line[:-1])) line = [w] if line: lines.append(" ".join(line)) for ln in lines: c.drawString(72, text.getY(), ln) text.moveCursor(0, 12) c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 2 of 2 — Employee Onboarding Form") c.save() b64 = save_b64(path) print(f"✅ Generated {path.name} + {b64.name}") # =========================================================================== # 2. NDA Template (2 pages) # =========================================================================== def generate_nda(): path = SAMPLE_DIR / "nda-sample.pdf" c = canvas.Canvas(str(path), pagesize=letter) # ---- Page 1: Agreement text ---- c.setFont("Helvetica-Bold", 18) c.setFillColorRGB(0.1, 0.2, 0.5) c.drawCentredString(W / 2, H - 60, "Non-Disclosure Agreement") c.setFont("Helvetica", 10) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawCentredString(W / 2, H - 80, "Confidential — Between Employee and Company") section_header(c, "Agreement Parties", H - 110) # EmployeeName field (page 1, top=220) fields = json.loads((SAMPLE_DIR / "nda-template-formfields.json").read_text()) for f in fields: locs = f.get("locations", []) if not locs or locs[0]["pageNumber"] != 1: continue rect = locs[0]["rect"] draw_field(c, "Employee Full Name", rect) # Boilerplate body text body = [ "This Non-Disclosure Agreement ('Agreement') is entered into as of the date signed", "below ('Effective Date') by and between the Employee identified above and Acme Corp", "('Company'), collectively referred to as the 'Parties'.", "", "1. CONFIDENTIAL INFORMATION", " The Employee agrees not to disclose, publish, or make available any Confidential", " Information (as defined herein) to any third party without the prior written", " consent of the Company.", "", "2. OBLIGATIONS", " The Employee shall use the Confidential Information solely for the purpose of", " performing their duties with the Company and shall protect the information with", " at least the same degree of care used for their own confidential information.", "", "3. TERM", " This Agreement shall remain in effect for a period of two (2) years from the", " Effective Date and shall survive termination of employment.", "", "4. GOVERNING LAW", " This Agreement shall be governed by the laws of the State of California.", ] y = H - 260 c.setFont("Helvetica", 9) c.setFillColorRGB(0.15, 0.15, 0.15) for line in body: c.drawString(72, y, line) y -= 13 c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 1 of 2 — Non-Disclosure Agreement") c.showPage() # ---- Page 2: Signature ---- c.setFont("Helvetica-Bold", 14) c.setFillColorRGB(0.1, 0.2, 0.5) c.drawCentredString(W / 2, H - 50, "Non-Disclosure Agreement — Execution Page") section_header(c, "Signatures", H - 100) c.setFont("Helvetica", 10) c.setFillColorRGB(0.2, 0.2, 0.2) c.drawString(72, H - 130, "By signing, the Employee acknowledges reading and agreeing to all terms above.") for f in fields: locs = f.get("locations", []) if not locs or locs[0]["pageNumber"] != 2: continue draw_signature_block(c, "Employee Signature", locs[0]["rect"]) # Date line c.setFont("Helvetica", 9) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawString(72, H - 500, "Date: _______________________________") c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 2 of 2 — Non-Disclosure Agreement") c.save() b64 = save_b64(path) print(f"✅ Generated {path.name} + {b64.name}") # =========================================================================== # 3. Sales Agreement (3 pages) # =========================================================================== def generate_sales_contract(): path = SAMPLE_DIR / "sales-contract-sample.pdf" c = canvas.Canvas(str(path), pagesize=letter) fields = json.loads((SAMPLE_DIR / "sales-contract-formfields.json").read_text()) # ---- Page 1: Terms + PurchasePrice field ---- c.setFont("Helvetica-Bold", 18) c.setFillColorRGB(0.1, 0.25, 0.1) c.drawCentredString(W / 2, H - 60, "Sales Agreement") c.setFont("Helvetica", 10) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawCentredString(W / 2, H - 78, "Buyer and Seller — Binding Contract") section_header(c, "Transaction Details", H - 110) for f in fields: locs = f.get("locations", []) if not locs or locs[0]["pageNumber"] != 1: continue rect = locs[0]["rect"] draw_field(c, "Purchase Price (USD)", rect, field_hint="e.g. 5000.00") body_p1 = [ "This Sales Agreement ('Agreement') is entered into as of the date of execution", "by and between the Buyer and the Seller identified on the signature page.", "", "1. SALE OF GOODS", " Seller agrees to sell and Buyer agrees to purchase the goods described in", " Schedule A (attached) for the Purchase Price stated above.", "", "2. PAYMENT TERMS", " Payment is due in full within 30 days of signing unless otherwise agreed", " in a separate written addendum signed by both parties.", "", "3. DELIVERY", " Seller shall deliver the goods within 14 days of receipt of payment.", " Risk of loss transfers to Buyer upon delivery.", "", "4. WARRANTIES", " Seller warrants that it has clear title to the goods and that the goods", " conform to the specifications in Schedule A.", "", "5. DISPUTE RESOLUTION", " Disputes shall be resolved by binding arbitration under AAA Commercial Rules.", ] y = H - 210 c.setFont("Helvetica", 9) c.setFillColorRGB(0.15, 0.15, 0.15) for line in body_p1: c.drawString(72, y, line) y -= 13 c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 1 of 3 — Sales Agreement") c.showPage() # ---- Page 2: Schedule A ---- c.setFont("Helvetica-Bold", 14) c.setFillColorRGB(0.1, 0.25, 0.1) c.drawCentredString(W / 2, H - 50, "Schedule A — Goods Description") section_header(c, "Item Detail", H - 90) body_p2 = [ "Item: [To be completed by Seller]", "Quantity: [To be completed by Seller]", "Description: [To be completed by Seller]", "Unit Price: [To be completed by Seller]", "", "Condition: New / Used / Refurbished (circle one)", "", "Special Terms:", " _________________________________________________________", " _________________________________________________________", " _________________________________________________________", ] y = H - 130 c.setFont("Helvetica", 10) c.setFillColorRGB(0.15, 0.15, 0.15) for line in body_p2: c.drawString(72, y, line) y -= 18 c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 2 of 3 — Sales Agreement") c.showPage() # ---- Page 3: Signatures ---- c.setFont("Helvetica-Bold", 14) c.setFillColorRGB(0.1, 0.25, 0.1) c.drawCentredString(W / 2, H - 50, "Sales Agreement — Execution Page") section_header(c, "Authorized Signatures", H - 90) c.setFont("Helvetica", 9) c.setFillColorRGB(0.2, 0.2, 0.2) c.drawString(72, H - 120, "Each party signing below agrees to be bound by all terms and conditions of this Agreement.") for f in fields: locs = f.get("locations", []) if not locs or locs[0]["pageNumber"] != 3: continue label = "Buyer Signature" if f["fieldName"] == "BuyerSign" else "Seller Signature" draw_signature_block(c, label, locs[0]["rect"]) # Printed name / date lines c.setFont("Helvetica", 9) c.setFillColorRGB(0.3, 0.3, 0.3) c.drawString(200, H - 440, "Printed Name: ________________________ Date: __________") c.drawString(420, H - 440, "Printed Name: ________________________ Date: __________") c.setFont("Helvetica", 8) c.setFillColorRGB(0.5, 0.5, 0.5) c.drawCentredString(W / 2, 30, "Page 3 of 3 — Sales Agreement") c.save() b64 = save_b64(path) print(f"✅ Generated {path.name} + {b64.name}") if __name__ == "__main__": generate_onboarding() generate_nda() generate_sales_contract() print("\nAll sample PDFs generated.")