import customtkinter as ctk import tkinter as tk from tkinter import filedialog, messagebox import subprocess import tempfile import os import shutil import io import re import fitz # PyMuPDF from PIL import Image, ImageTk import sys # Professional theme configuration ctk.set_appearance_mode("system") # Automatically matches system light/dark mode ctk.set_default_color_theme("blue") # Design System Color Palette (Light, Dark) COLOR_BG_MAIN = ("#f8fafc", "#0f172a") # Slate 50, Slate 900 COLOR_BG_SIDEBAR = ("#f1f5f9", "#1e293b") # Slate 100, Slate 800 COLOR_BG_CARD = ("#ffffff", "#0f172a") # White, Slate 900 COLOR_BORDER = ("#cbd5e1", "#334155") # Slate 300, Slate 700 COLOR_TEXT_PRIMARY = ("#0f172a", "#f8fafc") # Slate 900, Slate 50 COLOR_TEXT_MUTED = ("#64748b", "#94a3b8") # Slate 500, Slate 400 class CVBuilderApp(ctk.CTk): def __init__(self): super().__init__() self.title("Professional CV Builder (LaTeX Core)") self.geometry("1400x900") self.minsize(1200, 800) self.configure(fg_color=COLOR_BG_MAIN) # Layout configuration self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) # State management self.current_section = None self.section_frames = {} self.latex_text = "" self.pdf_image_refs = [] self.temp_dir = None self._build_ui() self._load_sample_data() def _build_ui(self): self._build_sidebar() self._build_main_area() self._navigate_to("personal") def _build_sidebar(self): """Clean navigation sidebar with modern hierarchy""" self.sidebar = ctk.CTkFrame(self, width=260, corner_radius=0, fg_color=COLOR_BG_SIDEBAR) self.sidebar.grid(row=0, column=0, sticky="nsew") self.sidebar.grid_rowconfigure(9, weight=1) # Space out navigation from action buttons # Branding brand_frame = ctk.CTkFrame(self.sidebar, fg_color="transparent") brand_frame.grid(row=0, column=0, padx=20, pady=(30, 20), sticky="ew") ctk.CTkLabel( brand_frame, text="LaTeX CV Builder", font=ctk.CTkFont(family="Inter", size=20, weight="bold"), text_color=COLOR_TEXT_PRIMARY ).pack(anchor="w") ctk.CTkLabel( brand_frame, text="High-Fidelity Document Engine", font=ctk.CTkFont(family="Inter", size=11, weight="normal"), text_color=COLOR_TEXT_MUTED ).pack(anchor="w", pady=(2, 0)) # Navigation Menu Items nav_items = [ ("personal", "Personal Information"), ("education", "Academic Education"), ("coursework", "Relevant Coursework"), ("experience", "Work Experience"), ("projects", "Technical Projects"), ("skills", "Core Skills Matrix"), ("leadership", "Leadership & Activities"), ("output", "LaTeX & Live Preview") ] self.nav_buttons = {} for idx, (key, label) in enumerate(nav_items, start=1): btn = ctk.CTkButton( self.sidebar, text=label, command=lambda k=key: self._navigate_to(k), fg_color="transparent", text_color=COLOR_TEXT_PRIMARY, hover_color=("white", "gray10"), height=38, corner_radius=6, anchor="w", font=ctk.CTkFont(family="Inter", size=13, weight="normal") ) btn.grid(row=idx, column=0, padx=12, pady=3, sticky="ew") self.nav_buttons[key] = btn # Actions block at bottom of sidebar actions_frame = ctk.CTkFrame(self.sidebar, fg_color="transparent") actions_frame.grid(row=9, column=0, padx=12, pady=(15, 5), sticky="ew") actions_frame.grid_columnconfigure(0, weight=1) self.import_btn = ctk.CTkButton( actions_frame, text="Import LaTeX File", command=self._import_latex, fg_color=("#64748b", "#475569"), hover_color=("#475569", "#334155"), text_color="white", height=36, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ) self.import_btn.grid(row=0, column=0, pady=4, sticky="ew") self.generate_btn = ctk.CTkButton( actions_frame, text="Generate LaTeX Source", command=self._generate_latex, fg_color=("#2563eb", "#3b82f6"), hover_color=("#1d4ed8", "#2563eb"), text_color="white", height=38, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ) self.generate_btn.grid(row=1, column=0, pady=4, sticky="ew") self.compile_btn = ctk.CTkButton( actions_frame, text="Compile Live PDF", command=self._compile_latex, fg_color=("#16a34a", "#22c55e"), hover_color=("#15803d", "#16a34a"), text_color="white", height=38, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold"), state="disabled" ) self.compile_btn.grid(row=2, column=0, pady=(4, 15), sticky="ew") # Status Bar Indicator self.status_label = ctk.CTkLabel( self.sidebar, text="Ready", text_color=COLOR_TEXT_MUTED, font=ctk.CTkFont(family="Inter", size=11, weight="normal") ) self.status_label.grid(row=10, column=0, padx=15, pady=(0, 20), sticky="w") def _build_main_area(self): """Content area panel containing form card views""" self.main_container = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") self.main_container.grid(row=0, column=1, sticky="nsew", padx=30, pady=30) self.main_container.grid_columnconfigure(0, weight=1) self.main_container.grid_rowconfigure(0, weight=1) self.section_frames["personal"] = self._create_personal_section() self.section_frames["education"] = self._create_education_section() self.section_frames["coursework"] = self._create_coursework_section() self.section_frames["experience"] = self._create_experience_section() self.section_frames["projects"] = self._create_projects_section() self.section_frames["skills"] = self._create_skills_section() self.section_frames["leadership"] = self._create_leadership_section() self.section_frames["output"] = self._create_output_section() def _create_section_header(self, parent, title, subtitle=None): header = ctk.CTkFrame(parent, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) ctk.CTkLabel( header, text=title, font=ctk.CTkFont(family="Inter", size=24, weight="bold"), text_color=COLOR_TEXT_PRIMARY ).pack(anchor="w") if subtitle: ctk.CTkLabel( header, text=subtitle, font=ctk.CTkFont(family="Inter", size=12, weight="normal"), text_color=COLOR_TEXT_MUTED ).pack(anchor="w", pady=(2, 0)) return header def _create_form_field(self, parent, label_text, row, col, columnspan=1, height=36, is_textbox=False): """Helper function to keep form structures consistent, elegant, and perfectly structured""" field_container = ctk.CTkFrame(parent, fg_color="transparent") field_container.grid(row=row, column=col, columnspan=columnspan, sticky="ew", padx=10, pady=10) field_container.grid_columnconfigure(0, weight=1) ctk.CTkLabel( field_container, text=label_text, font=ctk.CTkFont(family="Inter", size=12, weight="bold"), text_color=COLOR_TEXT_PRIMARY ).grid(row=0, column=0, sticky="w", pady=(0, 5)) if is_textbox: field = ctk.CTkTextbox( field_container, height=height, corner_radius=6, border_width=1, border_color=COLOR_BORDER, fg_color=COLOR_BG_MAIN ) else: field = ctk.CTkEntry( field_container, height=height, corner_radius=6, border_width=1, border_color=COLOR_BORDER, fg_color=COLOR_BG_MAIN ) field.grid(row=1, column=0, sticky="ew") return field def _create_personal_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Personal Information", "Define your key contact details and online portfolios.") card = ctk.CTkFrame(frame, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=1, column=0, sticky="ew", padx=5, pady=5) card.grid_columnconfigure((0, 1), weight=1) self.first_name = self._create_form_field(card, "First Name", 0, 0) self.last_name = self._create_form_field(card, "Last Name", 0, 1) self.personal_vars = { 'address': self._create_form_field(card, "Mailing / Primary Address", 1, 0, columnspan=2), 'phone': self._create_form_field(card, "Phone Number", 2, 0), 'email': self._create_form_field(card, "Email Address", 2, 1), 'linkedin': self._create_form_field(card, "LinkedIn Username", 3, 0), 'github': self._create_form_field(card, "GitHub Username", 3, 1) } return frame def _create_education_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Education", "Enter your higher education details, locations, and degree pathways.") card = ctk.CTkFrame(frame, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=1, column=0, sticky="ew", padx=5, pady=5) card.grid_columnconfigure((0, 1), weight=1) self.edu_vars = { 'university': self._create_form_field(card, "University / College Name", 0, 0, columnspan=2), 'degree': self._create_form_field(card, "Degree Major / Qualification", 1, 0, columnspan=2), 'start_date': self._create_form_field(card, "Start Date (e.g. Sep. 2017)", 2, 0), 'end_date': self._create_form_field(card, "Graduation Date (e.g. May 2021)", 2, 1), 'location': self._create_form_field(card, "Institution Location (City, State)", 3, 0, columnspan=2) } return frame def _create_coursework_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Relevant Coursework", "Highlight relevant classes, certificates, and specialized course nodes.") card = ctk.CTkFrame(frame, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=1, column=0, sticky="ew", padx=5, pady=5) card.grid_columnconfigure(0, weight=1) self.coursework = self._create_form_field(card, "Enter Courses (Separated strictly by commas)", 0, 0, is_textbox=True, height=180) return frame def _create_experience_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Professional Experience", "Outline your historical career track, positions, and achievements.") self.exp_container = ctk.CTkFrame(frame, fg_color="transparent") self.exp_container.grid(row=1, column=0, sticky="ew") self.exp_container.grid_columnconfigure(0, weight=1) self.exp_entries = [] ctk.CTkButton( frame, text="+ Add Position Card", command=self._add_experience_entry, fg_color=("#2563eb", "#3b82f6"), hover_color=("#1d4ed8", "#2563eb"), height=36, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).grid(row=2, column=0, pady=15, sticky="w") return frame def _add_experience_entry(self): idx = len(self.exp_entries) card = ctk.CTkFrame(self.exp_container, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=idx, column=0, sticky="ew", pady=10, padx=5) card.grid_columnconfigure((0, 1), weight=1) ctk.CTkLabel( card, text=f"Work Experience Block #{idx + 1}", font=ctk.CTkFont(family="Inter", size=14, weight="bold"), text_color=COLOR_TEXT_PRIMARY ).grid(row=0, column=0, columnspan=2, padx=15, pady=(15, 5), sticky="w") vars_dict = { 'company': self._create_form_field(card, "Company / Employer Name", 1, 0), 'title': self._create_form_field(card, "Job Title / Designation", 1, 1), 'start_date': self._create_form_field(card, "Start Date (e.g., Jun. 2019)", 2, 0), 'end_date': self._create_form_field(card, "End Date (or 'Present')", 2, 1), 'location': self._create_form_field(card, "Work Location", 3, 0, columnspan=2) } resp_text = self._create_form_field(card, "Job Responsibilities / Impacts (One point per line)", 4, 0, columnspan=2, is_textbox=True, height=120) ctk.CTkButton( card, text="Delete Position", command=lambda c=card: self._remove_entry(c, self.exp_entries), fg_color=("#ef4444", "#dc2626"), hover_color=("#dc2626", "#b91c1c"), width=120, height=32, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).grid(row=5, column=1, pady=(0, 15), padx=15, sticky="e") self.exp_entries.append((vars_dict, resp_text, card)) def _create_projects_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Technical Projects", "Detail non-trivial software engineering or personal dev projects.") self.proj_container = ctk.CTkFrame(frame, fg_color="transparent") self.proj_container.grid(row=1, column=0, sticky="ew") self.proj_container.grid_columnconfigure(0, weight=1) self.proj_entries = [] ctk.CTkButton( frame, text="+ Add Project Node", command=self._add_project_entry, fg_color=("#2563eb", "#3b82f6"), hover_color=("#1d4ed8", "#2563eb"), height=36, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).grid(row=2, column=0, pady=15, sticky="w") return frame def _add_project_entry(self): idx = len(self.proj_entries) card = ctk.CTkFrame(self.proj_container, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=idx, column=0, sticky="ew", pady=10, padx=5) card.grid_columnconfigure((0, 1), weight=1) ctk.CTkLabel( card, text=f"Project Workspace #{idx + 1}", font=ctk.CTkFont(family="Inter", size=14, weight="bold"), text_color=COLOR_TEXT_PRIMARY ).grid(row=0, column=0, columnspan=2, padx=15, pady=(15, 5), sticky="w") vars_dict = { 'name': self._create_form_field(card, "Project Name", 1, 0, columnspan=2), 'technologies': self._create_form_field(card, "Core Tech Stack Used (e.g. React, C++, Docker)", 2, 0), 'date': self._create_form_field(card, "Timeline Date (e.g., Fall 2021)", 2, 1) } desc_text = self._create_form_field(card, "Project Deliverables / Features (One bullet point per line)", 3, 0, columnspan=2, is_textbox=True, height=120) ctk.CTkButton( card, text="Delete Project", command=lambda c=card: self._remove_entry(c, self.proj_entries), fg_color=("#ef4444", "#dc2626"), hover_color=("#dc2626", "#b91c1c"), width=120, height=32, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).grid(row=4, column=1, pady=(0, 15), padx=15, sticky="e") self.proj_entries.append((vars_dict, desc_text, card)) def _create_skills_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Core Skills Matrix", "Index your technical qualifications so they are easily scanable.") card = ctk.CTkFrame(frame, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=1, column=0, sticky="ew", padx=5, pady=5) card.grid_columnconfigure(0, weight=1) self.skills_vars = { 'languages': self._create_form_field(card, "Coding / Query Languages (e.g., Python, SQL, C++)", 0, 0, is_textbox=True, height=70), 'tools': self._create_form_field(card, "Developer Platforms & DevOps (e.g., Git, Docker, AWS)", 1, 0, is_textbox=True, height=70), 'technologies': self._create_form_field(card, "Libraries / Architecture (e.g., React.js, PyTorch, Node.js)", 2, 0, is_textbox=True, height=70) } return frame def _create_leadership_section(self): frame = ctk.CTkScrollableFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) self._create_section_header(frame, "Leadership & Extracurriculars", "List university clubs, hackathons organized, or non-profit volunteer work.") self.lead_container = ctk.CTkFrame(frame, fg_color="transparent") self.lead_container.grid(row=1, column=0, sticky="ew") self.lead_container.grid_columnconfigure(0, weight=1) self.lead_entries = [] ctk.CTkButton( frame, text="+ Add Activity Block", command=self._add_leadership_entry, fg_color=("#2563eb", "#3b82f6"), hover_color=("#1d4ed8", "#2563eb"), height=36, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).grid(row=2, column=0, pady=15, sticky="w") return frame def _add_leadership_entry(self): idx = len(self.lead_entries) card = ctk.CTkFrame(self.lead_container, corner_radius=10, fg_color=COLOR_BG_CARD, border_width=1, border_color=COLOR_BORDER) card.grid(row=idx, column=0, sticky="ew", pady=10, padx=5) card.grid_columnconfigure((0, 1), weight=1) ctk.CTkLabel( card, text=f"Activity Workspace #{idx + 1}", font=ctk.CTkFont(family="Inter", size=14, weight="bold"), text_color=COLOR_TEXT_PRIMARY ).grid(row=0, column=0, columnspan=2, padx=15, pady=(15, 5), sticky="w") vars_dict = { 'organization': self._create_form_field(card, "Organization / Project Initiative", 1, 0), 'role': self._create_form_field(card, "Your Role / Position", 1, 1), 'date': self._create_form_field(card, "Timeline Date", 2, 0), 'location': self._create_form_field(card, "Location", 2, 1) } achieve_text = self._create_form_field(card, "Core Accomplishments / Impacts (One point per line)", 3, 0, columnspan=2, is_textbox=True, height=120) ctk.CTkButton( card, text="Delete Activity", command=lambda c=card: self._remove_entry(c, self.lead_entries), fg_color=("#ef4444", "#dc2626"), hover_color=("#dc2626", "#b91c1c"), width=120, height=32, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).grid(row=4, column=1, pady=(0, 15), padx=15, sticky="e") self.lead_entries.append((vars_dict, achieve_text, card)) def _create_output_section(self): """High-fidelity dual column workspace design (IDE source layout and live PDF rendering view)""" frame = ctk.CTkFrame(self.main_container, corner_radius=12, fg_color="transparent") frame.grid_columnconfigure(0, weight=1) frame.grid_rowconfigure(1, weight=1) self._create_section_header(frame, "Compilation Workspace", "Review raw macro-injected LaTeX outputs and track compiled system states.") # Paned Window layout splits IDE view cleanly paned = tk.PanedWindow(frame, orient=tk.HORIZONTAL, bg="#1e1e24", sashrelief="flat", sashwidth=6) paned.grid(row=1, column=0, sticky="nsew", padx=5, pady=5) # Code block terminal style editor (Left) code_frame = ctk.CTkFrame(paned, corner_radius=8, fg_color=COLOR_BG_SIDEBAR) ctk.CTkLabel( code_frame, text="LaTeX Source Engine Output", font=ctk.CTkFont(family="Inter", size=13, weight="bold"), text_color=("#2563eb", "#3b82f6") ).pack(padx=15, pady=(12, 6), anchor="w") self.latex_editor = ctk.CTkTextbox( code_frame, corner_radius=6, font=("Consolas", 12), fg_color=COLOR_BG_MAIN, border_width=1, border_color=COLOR_BORDER ) self.latex_editor.pack(fill="both", expand=True, padx=12, pady=(0, 12)) paned.add(code_frame, width=500) # Live vector asset viewport (Right) viewer_frame = ctk.CTkFrame(paned, corner_radius=8, fg_color=COLOR_BG_SIDEBAR) ctk.CTkLabel( viewer_frame, text="Live Compiled PDF Display", font=ctk.CTkFont(family="Inter", size=13, weight="bold"), text_color=("#16a34a", "#22c55e") ).pack(padx=15, pady=(12, 6), anchor="w") self.pdf_canvas_frame = ctk.CTkScrollableFrame(viewer_frame, corner_radius=6, fg_color=COLOR_BG_MAIN) self.pdf_canvas_frame.pack(fill="both", expand=True, padx=12, pady=(0, 12)) self.placeholder_label = ctk.CTkLabel( self.pdf_canvas_frame, text="Generate source and run 'Compile PDF' execution pipeline.", font=ctk.CTkFont(family="Inter", size=13, weight="normal"), text_color=COLOR_TEXT_MUTED ) self.placeholder_label.pack(expand=True, pady=100) paned.add(viewer_frame) # File export actions panel btn_frame = ctk.CTkFrame(frame, fg_color="transparent", height=45) btn_frame.grid(row=2, column=0, sticky="ew", pady=(15, 0)) ctk.CTkButton( btn_frame, text="Export Source (.tex)", command=self._save_tex, fg_color=("#64748b", "#475569"), hover_color=("#475569", "#334155"), width=160, height=36, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).pack(side="left", padx=5) ctk.CTkButton( btn_frame, text="Copy to Clipboard", command=self._copy_latex, fg_color=("#64748b", "#475569"), hover_color=("#475569", "#334155"), width=160, height=36, corner_radius=6, font=ctk.CTkFont(family="Inter", size=12, weight="bold") ).pack(side="left", padx=5) return frame def _remove_entry(self, card, entry_list): for i, (_, _, c) in enumerate(entry_list): if c == card: card.destroy() entry_list.pop(i) break def _navigate_to(self, section_key): if self.current_section: self.current_section.grid_remove() self.current_section = self.section_frames[section_key] self.current_section.grid(row=0, column=0, sticky="nsew") for key, btn in self.nav_buttons.items(): if key == section_key: btn.configure(fg_color=("#2563eb", "#3b82f6"), text_color="white", hover_color=("#1d4ed8", "#2563eb")) else: btn.configure(fg_color="transparent", text_color=COLOR_TEXT_PRIMARY, hover_color=("white", "gray10")) def _generate_latex(self): self.latex_text = self._build_latex_cv() self.latex_editor.delete("1.0", tk.END) self.latex_editor.insert("1.0", self.latex_text) self.compile_btn.configure(state="normal") self._set_status("LaTeX runtime compilation target tree verified.") self._navigate_to("output") def _build_latex_cv(self): latex = r"""\documentclass[11pt,a4paper]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{geometry} \usepackage{enumitem} \usepackage{hyperref} \usepackage{parskip} \usepackage{xcolor} \geometry{margin=1in} \setlist[itemize]{leftmargin=*, nosep, topsep=3pt, itemsep=3pt} \pagestyle{empty} \definecolor{primary}{RGB}{0,85,170} \definecolor{secondary}{RGB}{80,80,80} \hypersetup{colorlinks=true, linkcolor=primary, urlcolor=primary} \begin{document} """ # Header first = self.first_name.get().strip() last = self.last_name.get().strip() latex += f"\\begin{{center}}\n\\textbf{{\\LARGE \\textcolor{{primary}}{{{first} {last}}}}}\\\\\n" contacts = [] if self.personal_vars['address'].get().strip(): contacts.append(self.personal_vars['address'].get().strip()) if self.personal_vars['phone'].get().strip(): p = self.personal_vars['phone'].get().strip(); contacts.append( f"\\href{{tel:{p}}}{{{p}}}") if self.personal_vars['email'].get().strip(): e = self.personal_vars['email'].get().strip(); contacts.append( f"\\href{{mailto:{e}}}{{{e}}}") if self.personal_vars['linkedin'].get().strip(): l = self.personal_vars[ 'linkedin'].get().strip(); contacts.append(f"\\href{{https://linkedin.com/in/{l}}}{{linkedin.com/in/{l}}}") if self.personal_vars['github'].get().strip(): g = self.personal_vars['github'].get().strip(); contacts.append( f"\\href{{https://github.com/{g}}}{{github.com/{g}}}") latex += " \\textcolor{secondary}{|} ".join(contacts) + "\n\\end{center}\n\n\\hrule\n\n" # Education if self.edu_vars['university'].get().strip(): latex += "\\section*{\\textcolor{primary}{Education}}\n\\addvspace{5pt}\n" uni, deg, s, e, loc = [v.get().strip() for v in self.edu_vars.values()] date_str = f"{s} -- {e}" if s and e else "" latex += f"\\noindent\\textbf{{{uni}}}" if date_str: latex += f"\\hfill \\textcolor{{secondary}}{{{date_str}}}" latex += f"\\\\\n\\textit{{{deg}}}" if loc: latex += f" \\textcolor{{secondary}}{{|}} {loc}" latex += "\n\n" # Coursework cw = self.coursework.get("1.0", tk.END).strip() if cw: latex += "\\section*{\\textcolor{primary}{Relevant Coursework}}\n\\addvspace{5pt}\n" courses = [c.strip() for c in cw.split(',') if c.strip()] latex += "\\begin{itemize}\n" for i in range(0, len(courses), 4): latex += " \\item " + " \\quad $\\bullet$ ".join(courses[i:i + 4]) + "\n" latex += "\\end{itemize}\n\n" # Experience if self.exp_entries: latex += "\\section*{\\textcolor{primary}{Experience}}\n\\addvspace{5pt}\n" for v, t, _ in self.exp_entries: comp = v['company'].get().strip() if not comp: continue title, s, e, loc = [v[k].get().strip() for k in ['title', 'start_date', 'end_date', 'location']] date_str = f"{s} -- {e}" if s and e else "" latex += f"\\noindent\\textbf{{{comp}}}" if date_str: latex += f"\\hfill \\textcolor{{secondary}}{{{date_str}}}" latex += f"\\\\\n\\textit{{{title}}}" if loc: latex += f" \\textcolor{{secondary}}{{|}} {loc}" latex += "\n" resp = t.get("1.0", tk.END).strip() if resp: latex += "\\begin{itemize}\n" for r in resp.split('\n'): if r.strip(): latex += f" \\item {r.strip()}\n" latex += "\\end{itemize}\n" latex += "\n" # Projects if self.proj_entries: latex += "\\section*{\\textcolor{primary}{Projects}}\n\\addvspace{5pt}\n" for v, t, _ in self.proj_entries: name = v['name'].get().strip() if not name: continue tech, date = [v[k].get().strip() for k in ['technologies', 'date']] tech_str = f" \\textcolor{{secondary}}{{|}} {tech}" if tech else "" latex += f"\\noindent\\textbf{{{name}}}{tech_str}" if date: latex += f"\\hfill \\textcolor{{secondary}}{{{date}}}" latex += "\n" desc = t.get("1.0", tk.END).strip() if desc: latex += "\\begin{itemize}\n" for d in desc.split('\n'): if d.strip(): latex += f" \\item {d.strip()}\n" latex += "\\end{itemize}\n" latex += "\n" # Skills has_skills = False skills_latex = "\\section*{\\textcolor{primary}{Technical Skills}}\n\\addvspace{5pt}\n" for key, label in [('languages', 'Languages'), ('tools', 'Developer Tools'), ('technologies', 'Technologies/Frameworks')]: txt = self.skills_vars[key].get("1.0", tk.END).strip() if txt: skills_latex += f"\\noindent\\textbf{{{label}:}} {txt}\\\\\n" has_skills = True if has_skills: latex += skills_latex + "\n" # Leadership if self.lead_entries: latex += "\\section*{\\textcolor{primary}{Leadership / Extracurricular}}\n\\addvspace{5pt}\n" for v, t, _ in self.lead_entries: org = v['organization'].get().strip() if not org: continue role, date, loc = [v[k].get().strip() for k in ['role', 'date', 'location']] latex += f"\\noindent\\textbf{{{org}}}" if date: latex += f"\\hfill \\textcolor{{secondary}}{{{date}}}" latex += f"\\\\\n\\textit{{{role}}}" if loc: latex += f" \\textcolor{{secondary}}{{|}} {loc}" latex += "\n" ach = t.get("1.0", tk.END).strip() if ach: latex += "\\begin{itemize}\n" for a in ach.split('\n'): if a.strip(): latex += f" \\item {a.strip()}\n" latex += "\\end{itemize}\n" latex += "\n" latex += "\\end{document}" return latex def _import_latex(self): file_path = filedialog.askopenfilename(title="Import LaTeX CV Target", filetypes=[("LaTeX", "*.tex"), ("All Profiles", "*.*")]) if not file_path: return try: with open(file_path, "r", encoding="utf-8") as f: content = f.read() self._parse_and_populate(content) self._set_status("Input profile mapping synchronized successfully.") except Exception as e: messagebox.showerror("Import Error", f"Failed to parse LaTeX structure:\n{e}") def _parse_and_populate(self, latex_text): """Standard regex parser to map elements cleanly back to CTK states""" # Personal Name Mapping name_match = re.search(r'\\textbf\{\\LARGE \\textcolor\{primary\}\{(.*?)\}\}', latex_text) if name_match: names = name_match.group(1).split() self.first_name.delete(0, tk.END) self.first_name.insert(0, names[0] if names else "") self.last_name.delete(0, tk.END) self.last_name.insert(0, " ".join(names[1:]) if len(names) > 1 else "") # Contact Node Mapping contact_line = re.search(r'\\end\{center\}\s*\n(.*?)\\hrule', latex_text, re.DOTALL) if contact_line: text = contact_line.group(1).replace('\\textcolor{secondary}{|}', '|').replace('\\href{tel:', '').replace( '}', '') phone_pattern = r'(\d{3}-\d{3}-\d{4})' self.personal_vars['phone'].delete(0, tk.END) self.personal_vars['phone'].insert(0, re.search(phone_pattern, text).group(1) if re.search(phone_pattern, text) else "") email_pattern = r'([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)' self.personal_vars['email'].delete(0, tk.END) self.personal_vars['email'].insert(0, re.search(email_pattern, text).group(1) if re.search(email_pattern, text) else "") # Academic Parsing edu_match = re.search( r'\\section\*\{\\textcolor\{primary\}\{Education\}\}.*?\\textbf\{(.*?)\}.*?\\textit\{(.*?)\}', latex_text, re.DOTALL) if edu_match: self.edu_vars['university'].delete(0, tk.END) self.edu_vars['university'].insert(0, edu_match.group(1)) self.edu_vars['degree'].delete(0, tk.END) self.edu_vars['degree'].insert(0, edu_match.group(2)) # Coursework parsing cw_match = re.search( r'\\section\*\{\\textcolor\{primary\}\{Relevant Coursework\}\}.*?\\begin\{itemize\}(.*?)\\end\{itemize\}', latex_text, re.DOTALL) if cw_match: self.coursework.delete("1.0", tk.END) self.coursework.insert("1.0", cw_match.group(1).replace('\\item', '').replace('\\quad $\\bullet$', ',').replace( '\n', '').strip()) self._set_status("Asset import matching done. Review structural fields.") def _compile_latex(self): if not shutil.which("pdflatex"): messagebox.showerror("Toolchain Missing", "pdflatex binaries were not found in standard system PATH environments. Install TeX Live or MiKTeX.") return self.temp_dir = tempfile.mkdtemp() tex_path = os.path.join(self.temp_dir, "cv.tex") try: with open(tex_path, "w", encoding="utf-8") as f: f.write(self.latex_text) self._set_status("Compiling with pdflatex...") self.compile_btn.configure(state="disabled") self.update() result = subprocess.run(["pdflatex", "-interaction=nonstopmode", "cv.tex"], cwd=self.temp_dir, capture_output=True, text=True) pdf_path = os.path.join(self.temp_dir, "cv.pdf") if result.returncode == 0 and os.path.exists(pdf_path): self._set_status("Compilation successful") self._render_pdf(pdf_path) else: self._set_status("Compilation failed") messagebox.showerror("Compilation Error", "LaTeX subprocess run failed. Review code markup.") except Exception as e: self._set_status("Pipeline Error") messagebox.showerror("Runtime Trace", str(e)) finally: self.compile_btn.configure(state="normal") def _render_pdf(self, pdf_path): """Direct vector rendering pipeline showing beautiful visual proofs on high-DPI scaling""" for widget in self.pdf_canvas_frame.winfo_children(): widget.destroy() self.pdf_image_refs.clear() self.placeholder_label.pack_forget() try: doc = fitz.open(pdf_path) for page_num in range(len(doc)): page = doc.load_page(page_num) pix = page.get_pixmap(matrix=fitz.Matrix(2.0, 2.0)) img = Image.open(io.BytesIO(pix.tobytes("ppm"))) photo = ImageTk.PhotoImage(img) self.pdf_image_refs.append(photo) # Render page inside frame with beautiful spacing page_label = ctk.CTkLabel(self.pdf_canvas_frame, image=photo, text="") page_label.pack(pady=10, padx=10) doc.close() except Exception as e: ctk.CTkLabel(self.pdf_canvas_frame, text=f"Render pipeline failed: {e}", text_color="#ef4444").pack(pady=20) def _save_tex(self): path = filedialog.asksaveasfilename(defaultextension=".tex", filetypes=[("LaTeX", "*.tex")], initialfile="cv.tex") if path: with open(path, "w", encoding="utf-8") as f: f.write(self.latex_text) self._set_status(f"Saved: {os.path.basename(path)}") def _copy_latex(self): self.clipboard_clear() self.clipboard_append(self.latex_text) self._set_status("Copied to clipboard") def _set_status(self, msg): self.status_label.configure(text=msg) def _load_sample_data(self): """Pre-fill high quality engineering data fields for clear demonstration""" self.first_name.insert(0, "Alex") self.last_name.insert(0, "Morgan") self.personal_vars['address'].insert(0, "789 Tech Drive, San Francisco, CA 94107") self.personal_vars['phone'].insert(0, "415-555-0199") self.personal_vars['email'].insert(0, "alex.morgan@domain.com") self.personal_vars['linkedin'].insert(0, "alexmorgan") self.personal_vars['github'].insert(0, "alexmorgan-dev") self.edu_vars['university'].insert(0, "University of California, Berkeley") self.edu_vars['degree'].insert(0, "Bachelor of Science in Computer Science and Engineering") self.edu_vars['start_date'].insert(0, "Aug. 2018") self.edu_vars['end_date'].insert(0, "May 2022") self.edu_vars['location'].insert(0, "Berkeley, CA") self.coursework.insert("1.0", "Data Structures, Efficient Algorithms, Distributed Systems, Software Engineering, Database Implementations, Computer Security, Artificial Intelligence") def main(): try: app = CVBuilderApp() app.mainloop() except Exception as e: messagebox.showerror("Startup Error", f"Failed to launch:\n{e}") sys.exit(1) if __name__ == "__main__": main()