from io import BytesIO import os from pathlib import Path from shutil import copyfile from PIL import Image, ImageDraw, ImageFont from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas from reportlab.platypus import Table, TableStyle OUTPUT = Path(os.environ.get("PDF_STUDY_OUTPUT_DIR", Path(__file__).resolve().parents[2] / "public/research/pdf-extraction")) OUTPUT.mkdir(parents=True, exist_ok=True) WIDTH, HEIGHT = A4 FONT_PATH = Path(__file__).with_name("NotoSansKR-study-subset.ttf") pdfmetrics.registerFont(TTFont("StudyNotoSansKR", FONT_PATH)) def new_pdf(filename, title): pdf = canvas.Canvas(str(OUTPUT / filename), pagesize=A4, invariant=1) pdf.setTitle(title) return pdf def draw_lines(pdf, lines, x, y, leading=18, font="Helvetica", size=10): pdf.setFont(font, size) for line in lines: pdf.drawString(x, y, line) y -= leading def single_column(): pdf = new_pdf("synthetic-single-column.pdf", "Synthetic single-column resume") pdf.setFont("Helvetica-Bold", 16) pdf.drawString(54, HEIGHT - 60, "ALEX RIVERA - SYNTHETIC RESUME") draw_lines(pdf, ["single-contact synthetic@example.invalid", "SUMMARY", "single-summary Builds accessible data tools for small teams.", "EXPERIENCE", "single-role Product Engineer | Example Lab | 2023-2026", "single-impact Reduced report setup time from 40 to 12 minutes.", "EDUCATION", "single-school Example Institute | 2023", "SKILLS", "single-skills TypeScript, SQL, accessibility testing"], 54, HEIGHT - 90) pdf.save() def two_column(): pdf = new_pdf("synthetic-two-column.pdf", "Synthetic two-column resume") pdf.setFont("Helvetica-Bold", 16) pdf.drawString(54, HEIGHT - 60, "MORGAN LEE - SYNTHETIC TWO-COLUMN RESUME") draw_lines(pdf, ["LEFT-SUMMARY", "left-focus Operations analyst", "LEFT-EXPERIENCE", "left-role Analyst | Sample Works", "left-result Cut weekly review time by 25 percent."], 54, HEIGHT - 100) draw_lines(pdf, ["RIGHT-SKILLS", "right-skill SQL", "right-skill Forecasting", "RIGHT-EDUCATION", "right-school Demo University | 2022"], 330, HEIGHT - 100) pdf.save() def korean(): pdf = new_pdf("synthetic-korean.pdf", "Synthetic Korean resume") draw_lines(pdf, ["한글-이름 김새로", "한글-연락처 kim@example.invalid", "경력", "한글-직무 제품 엔지니어 | 예시 연구소 | 2023-2026", "한글-성과 보고서 작성 시간을 40분에서 12분으로 줄였습니다.", "기술", "한글-기술 타입스크립트, SQL, 접근성 테스트"], 54, HEIGHT - 70, 22, "StudyNotoSansKR", 11) pdf.save() def mixed_language(): pdf = new_pdf("synthetic-mixed-language.pdf", "Synthetic mixed-language resume") draw_lines(pdf, ["혼합-이름 JIHO PARK 박지호", "mixed-contact jiho@example.invalid", "EXPERIENCE 경력", "mixed-role Product Designer 제품 디자이너", "혼합-성과 Improved completion rate 완료율 18% 향상", "SKILLS 기술", "mixed-skills Figma, research, 사용자 조사"], 54, HEIGHT - 70, 22, "StudyNotoSansKR", 11) pdf.save() def table_and_links(): pdf = new_pdf("synthetic-table-links.pdf", "Synthetic table and links resume") pdf.setFont("Helvetica-Bold", 16) pdf.drawString(54, HEIGHT - 60, "TAYLOR CHEN - TABLE AND LINKS") table = Table([["table-header-year", "table-header-role"], ["table-year-2024", "table-role Research lead"], ["table-year-2022", "table-role Analyst"]], colWidths=[150, 300], rowHeights=28) table.setStyle(TableStyle([("FONT", (0, 0), (-1, -1), "Helvetica", 10), ("FONT", (0, 0), (-1, 0), "Helvetica-Bold", 10), ("GRID", (0, 0), (-1, -1), 0.5, colors.grey), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("LEFTPADDING", (0, 0), (-1, -1), 8)])) table.wrapOn(pdf, WIDTH, HEIGHT) table.drawOn(pdf, 54, HEIGHT - 190) pdf.setFont("Helvetica", 10) pdf.drawString(54, HEIGHT - 225, "link-visible Portfolio: https://example.invalid/portfolio") pdf.linkURL("https://example.invalid/portfolio", (148, HEIGHT - 229, 330, HEIGHT - 215), relative=0) pdf.drawString(54, HEIGHT - 250, "link-label Case study") pdf.linkURL("https://example.invalid/case-study", (104, HEIGHT - 254, 170, HEIGHT - 240), relative=0) pdf.save() def scanned_image(): scale = 2 image = Image.new("RGB", (int(WIDTH * scale), int(HEIGHT * scale)), "white") draw = ImageDraw.Draw(image) font = ImageFont.load_default(size=22) lines = ["CASEY PARK - SYNTHETIC SCANNED RESUME", "scan-contact scan@example.invalid", "EXPERIENCE", "scan-role Support Specialist | Fictional Studio", "scan-result Resolved 96 percent of requests within one day.", "SKILLS", "scan-skills Documentation, triage, customer research"] y = 120 for line in lines: draw.text((108, y), line, fill="black", font=font) y += 52 image_buffer = BytesIO() image.save(image_buffer, format="PNG") image_buffer.seek(0) pdf = new_pdf("synthetic-scanned-image.pdf", "Synthetic image-only resume") from reportlab.lib.utils import ImageReader pdf.drawImage(ImageReader(image_buffer), 0, 0, width=WIDTH, height=HEIGHT) pdf.save() single_column() two_column() korean() mixed_language() table_and_links() scanned_image() copyfile(Path(__file__), OUTPUT / "generate-fixtures.py.txt") copyfile(FONT_PATH, OUTPUT / "NotoSansKR-study-subset.ttf") copyfile(Path(__file__).with_name("OFL.txt"), OUTPUT / "NotoSansKR-OFL.txt") if Path(__file__).with_name("README.md").exists(): copyfile(Path(__file__).with_name("README.md"), OUTPUT / "reproduce.txt") print(f"Created 6 synthetic PDF fixtures in {OUTPUT}")