mirror of
https://github.com/mudabbir-ahmad/Latex-CV-Builder.git
synced 2026-10-07 18:10:20 +00:00
480 lines
19 KiB
Python
480 lines
19 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk, filedialog, messagebox
|
|
import os
|
|
|
|
|
|
class CVGeneratorApp:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.root.title("LaTeX CV Generator")
|
|
self.root.geometry("1200x800")
|
|
|
|
# Create notebook for tabs
|
|
self.notebook = ttk.Notebook(root)
|
|
self.notebook.pack(fill='both', expand=True, padx=5, pady=5)
|
|
|
|
# Create frames for each section
|
|
self.personal_frame = ttk.Frame(self.notebook)
|
|
self.education_frame = ttk.Frame(self.notebook)
|
|
self.experience_frame = ttk.Frame(self.notebook)
|
|
self.projects_frame = ttk.Frame(self.notebook)
|
|
self.skills_frame = ttk.Frame(self.notebook)
|
|
self.leadership_frame = ttk.Frame(self.notebook)
|
|
self.output_frame = ttk.Frame(self.notebook)
|
|
|
|
# Add tabs
|
|
self.notebook.add(self.personal_frame, text='Personal Info')
|
|
self.notebook.add(self.education_frame, text='Education')
|
|
self.notebook.add(self.experience_frame, text='Experience')
|
|
self.notebook.add(self.projects_frame, text='Projects')
|
|
self.notebook.add(self.skills_frame, text='Skills')
|
|
self.notebook.add(self.leadership_frame, text='Leadership')
|
|
self.notebook.add(self.output_frame, text='LaTeX Output')
|
|
|
|
self.setup_personal_info()
|
|
self.setup_education()
|
|
self.setup_experience()
|
|
self.setup_projects()
|
|
self.setup_skills()
|
|
self.setup_leadership()
|
|
self.setup_output()
|
|
|
|
# Button frame at bottom
|
|
button_frame = ttk.Frame(root)
|
|
button_frame.pack(fill='x', padx=5, pady=5)
|
|
|
|
ttk.Button(button_frame, text="Generate LaTeX", command=self.generate_latex).pack(side='left', padx=5)
|
|
ttk.Button(button_frame, text="Save to File", command=self.save_to_file).pack(side='left', padx=5)
|
|
ttk.Button(button_frame, text="Clear All", command=self.clear_all).pack(side='left', padx=5)
|
|
|
|
def setup_personal_info(self):
|
|
ttk.Label(self.personal_frame, text="Personal Information", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
form_frame = ttk.Frame(self.personal_frame)
|
|
form_frame.pack(fill='x', padx=20, pady=10)
|
|
|
|
fields = [
|
|
("First Name:", "first_name"),
|
|
("Last Name:", "last_name"),
|
|
("Address:", "address"),
|
|
("Phone:", "phone"),
|
|
("Email:", "email"),
|
|
("LinkedIn:", "linkedin"),
|
|
("GitHub:", "github"),
|
|
]
|
|
|
|
self.personal_vars = {}
|
|
for i, (label_text, var_name) in enumerate(fields):
|
|
ttk.Label(form_frame, text=label_text).grid(row=i, column=0, sticky='e', pady=5, padx=5)
|
|
self.personal_vars[var_name] = tk.StringVar()
|
|
entry = ttk.Entry(form_frame, textvariable=self.personal_vars[var_name], width=50)
|
|
entry.grid(row=i, column=1, sticky='w', pady=5, padx=5)
|
|
|
|
def setup_education(self):
|
|
ttk.Label(self.education_frame, text="Education", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
# Main education
|
|
edu_frame = ttk.LabelFrame(self.education_frame, text="Degree Information", padding=10)
|
|
edu_frame.pack(fill='x', padx=20, pady=10)
|
|
|
|
self.edu_vars = {
|
|
'university': tk.StringVar(),
|
|
'degree': tk.StringVar(),
|
|
'start_date': tk.StringVar(),
|
|
'end_date': tk.StringVar(),
|
|
'location': tk.StringVar(),
|
|
}
|
|
|
|
fields = [
|
|
("University:", 'university'),
|
|
("Degree:", 'degree'),
|
|
("Start Date:", 'start_date'),
|
|
("End Date:", 'end_date'),
|
|
("Location:", 'location'),
|
|
]
|
|
|
|
for i, (label, var_name) in enumerate(fields):
|
|
ttk.Label(edu_frame, text=label).grid(row=i, column=0, sticky='e', pady=3, padx=5)
|
|
ttk.Entry(edu_frame, textvariable=self.edu_vars[var_name], width=50).grid(row=i, column=1, sticky='w',
|
|
pady=3, padx=5)
|
|
|
|
# Coursework
|
|
course_frame = ttk.LabelFrame(self.education_frame, text="Relevant Coursework (comma-separated)", padding=10)
|
|
course_frame.pack(fill='x', padx=20, pady=10)
|
|
|
|
self.coursework_text = tk.Text(course_frame, height=4, width=80)
|
|
self.coursework_text.pack(pady=5)
|
|
|
|
def setup_experience(self):
|
|
ttk.Label(self.experience_frame, text="Work Experience", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
self.experience_entries = []
|
|
self.exp_container = ttk.Frame(self.experience_frame)
|
|
self.exp_container.pack(fill='both', expand=True, padx=20, pady=10)
|
|
|
|
ttk.Button(self.exp_container, text="Add Position", command=self.add_experience).pack(pady=5)
|
|
|
|
def add_experience(self):
|
|
exp_frame = ttk.LabelFrame(self.exp_container, text=f"Position {len(self.experience_entries) + 1}", padding=10)
|
|
exp_frame.pack(fill='x', pady=10)
|
|
|
|
vars_dict = {
|
|
'company': tk.StringVar(),
|
|
'title': tk.StringVar(),
|
|
'start_date': tk.StringVar(),
|
|
'end_date': tk.StringVar(),
|
|
'location': tk.StringVar(),
|
|
}
|
|
|
|
fields = [
|
|
("Company:", 'company'),
|
|
("Title:", 'title'),
|
|
("Start Date:", 'start_date'),
|
|
("End Date:", 'end_date'),
|
|
("Location:", 'location'),
|
|
]
|
|
|
|
for i, (label, var_name) in enumerate(fields):
|
|
ttk.Label(exp_frame, text=label).grid(row=i, column=0, sticky='e', pady=3, padx=5)
|
|
ttk.Entry(exp_frame, textvariable=vars_dict[var_name], width=50).grid(row=i, column=1, sticky='w', pady=3,
|
|
padx=5)
|
|
|
|
ttk.Label(exp_frame, text="Responsibilities (one per line):").grid(row=len(fields), column=0, columnspan=2,
|
|
sticky='w', pady=(10, 3))
|
|
responsibilities = tk.Text(exp_frame, height=5, width=80)
|
|
responsibilities.grid(row=len(fields) + 1, column=0, columnspan=2, pady=3)
|
|
|
|
self.experience_entries.append((vars_dict, responsibilities))
|
|
|
|
def setup_projects(self):
|
|
ttk.Label(self.projects_frame, text="Projects", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
self.project_entries = []
|
|
self.proj_container = ttk.Frame(self.projects_frame)
|
|
self.proj_container.pack(fill='both', expand=True, padx=20, pady=10)
|
|
|
|
ttk.Button(self.proj_container, text="Add Project", command=self.add_project).pack(pady=5)
|
|
|
|
def add_project(self):
|
|
proj_frame = ttk.LabelFrame(self.proj_container, text=f"Project {len(self.project_entries) + 1}", padding=10)
|
|
proj_frame.pack(fill='x', pady=10)
|
|
|
|
vars_dict = {
|
|
'name': tk.StringVar(),
|
|
'technologies': tk.StringVar(),
|
|
'date': tk.StringVar(),
|
|
}
|
|
|
|
fields = [
|
|
("Project Name:", 'name'),
|
|
("Technologies:", 'technologies'),
|
|
("Date:", 'date'),
|
|
]
|
|
|
|
for i, (label, var_name) in enumerate(fields):
|
|
ttk.Label(proj_frame, text=label).grid(row=i, column=0, sticky='e', pady=3, padx=5)
|
|
ttk.Entry(proj_frame, textvariable=vars_dict[var_name], width=50).grid(row=i, column=1, sticky='w', pady=3,
|
|
padx=5)
|
|
|
|
ttk.Label(proj_frame, text="Description (one per line):").grid(row=len(fields), column=0, columnspan=2,
|
|
sticky='w', pady=(10, 3))
|
|
description = tk.Text(proj_frame, height=4, width=80)
|
|
description.grid(row=len(fields) + 1, column=0, columnspan=2, pady=3)
|
|
|
|
self.project_entries.append((vars_dict, description))
|
|
|
|
def setup_skills(self):
|
|
ttk.Label(self.skills_frame, text="Technical Skills", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
skills_frame = ttk.Frame(self.skills_frame)
|
|
skills_frame.pack(fill='x', padx=20, pady=10)
|
|
|
|
self.skills_vars = {
|
|
'languages': tk.StringVar(),
|
|
'tools': tk.StringVar(),
|
|
'technologies': tk.StringVar(),
|
|
}
|
|
|
|
fields = [
|
|
("Languages:", 'languages'),
|
|
("Developer Tools:", 'tools'),
|
|
("Technologies/Frameworks:", 'technologies'),
|
|
]
|
|
|
|
for i, (label, var_name) in enumerate(fields):
|
|
ttk.Label(skills_frame, text=label).grid(row=i, column=0, sticky='ne', pady=5, padx=5)
|
|
ttk.Entry(skills_frame, textvariable=self.skills_vars[var_name], width=50).grid(row=i, column=1, sticky='w',
|
|
pady=5, padx=5)
|
|
|
|
def setup_leadership(self):
|
|
ttk.Label(self.leadership_frame, text="Leadership / Extracurricular", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
self.leadership_entries = []
|
|
self.lead_container = ttk.Frame(self.leadership_frame)
|
|
self.lead_container.pack(fill='both', expand=True, padx=20, pady=10)
|
|
|
|
ttk.Button(self.lead_container, text="Add Activity", command=self.add_leadership).pack(pady=5)
|
|
|
|
def add_leadership(self):
|
|
lead_frame = ttk.LabelFrame(self.lead_container, text=f"Activity {len(self.leadership_entries) + 1}",
|
|
padding=10)
|
|
lead_frame.pack(fill='x', pady=10)
|
|
|
|
vars_dict = {
|
|
'organization': tk.StringVar(),
|
|
'role': tk.StringVar(),
|
|
'date': tk.StringVar(),
|
|
'location': tk.StringVar(),
|
|
}
|
|
|
|
fields = [
|
|
("Organization:", 'organization'),
|
|
("Role:", 'role'),
|
|
("Date:", 'date'),
|
|
("Location:", 'location'),
|
|
]
|
|
|
|
for i, (label, var_name) in enumerate(fields):
|
|
ttk.Label(lead_frame, text=label).grid(row=i, column=0, sticky='e', pady=3, padx=5)
|
|
ttk.Entry(lead_frame, textvariable=vars_dict[var_name], width=50).grid(row=i, column=1, sticky='w', pady=3,
|
|
padx=5)
|
|
|
|
ttk.Label(lead_frame, text="Achievements (one per line):").grid(row=len(fields), column=0, columnspan=2,
|
|
sticky='w', pady=(10, 3))
|
|
achievements = tk.Text(lead_frame, height=4, width=80)
|
|
achievements.grid(row=len(fields) + 1, column=0, columnspan=2, pady=3)
|
|
|
|
self.leadership_entries.append((vars_dict, achievements))
|
|
|
|
def setup_output(self):
|
|
ttk.Label(self.output_frame, text="Generated LaTeX Code", font=('Arial', 14, 'bold')).pack(pady=10)
|
|
|
|
self.output_text = tk.Text(self.output_frame, wrap='word', width=120, height=40)
|
|
scrollbar = ttk.Scrollbar(self.output_text, command=self.output_text.yview)
|
|
self.output_text.configure(yscrollcommand=scrollbar.set)
|
|
|
|
self.output_text.pack(side='left', fill='both', expand=True, padx=20, pady=10)
|
|
scrollbar.pack(side='right', fill='y')
|
|
|
|
def generate_latex(self):
|
|
latex_code = self.build_latex_cv()
|
|
self.output_text.delete(1.0, tk.END)
|
|
self.output_text.insert(1.0, latex_code)
|
|
self.notebook.select(6) # Switch to output tab
|
|
|
|
def build_latex_cv(self):
|
|
# Document class and packages
|
|
latex = r"""\documentclass[11pt,a4paper]{article}
|
|
\usepackage[utf8]{inputenc}
|
|
\usepackage[T1]{fontenc}
|
|
\usepackage{geometry}
|
|
\usepackage{enumitem}
|
|
\usepackage{hyperref}
|
|
\usepackage{parskip}
|
|
|
|
\geometry{margin=1in}
|
|
\setlist[itemize]{leftmargin=*, nosep, topsep=2pt, itemsep=2pt}
|
|
\pagestyle{empty}
|
|
|
|
\begin{document}
|
|
|
|
"""
|
|
# Personal Info
|
|
first_name = self.personal_vars['first_name'].get()
|
|
last_name = self.personal_vars['last_name'].get()
|
|
latex += f"\\begin{{center}}\n"
|
|
latex += f"\\textbf{{\\LARGE {first_name} {last_name}}}\\\\\n"
|
|
|
|
address = self.personal_vars['address'].get()
|
|
phone = self.personal_vars['phone'].get()
|
|
email = self.personal_vars['email'].get()
|
|
linkedin = self.personal_vars['linkedin'].get()
|
|
github = self.personal_vars['github'].get()
|
|
|
|
contact_parts = []
|
|
if address: contact_parts.append(address)
|
|
if phone: contact_parts.append(f"\\href{{tel:{phone}}}{{{phone}}}")
|
|
if email: contact_parts.append(f"\\href{{mailto:{email}}}{{{email}}}")
|
|
if linkedin: contact_parts.append(
|
|
f"\\href{{https://{linkedin}}}{{linkedin.com/in/{linkedin.replace('https://', '').replace('www.', '')}}}")
|
|
if github: contact_parts.append(f"\\href{{https://github.com/{github}}}{{github.com/{github}}}")
|
|
|
|
latex += " | ".join(contact_parts) + "\n"
|
|
latex += f"\\end{{center}}\n\n"
|
|
|
|
# Education
|
|
latex += "\\section*{Education}\n"
|
|
university = self.edu_vars['university'].get()
|
|
degree = self.edu_vars['degree'].get()
|
|
start_date = self.edu_vars['start_date'].get()
|
|
end_date = self.edu_vars['end_date'].get()
|
|
location = self.edu_vars['location'].get()
|
|
|
|
if university:
|
|
date_str = f"{start_date} -- {end_date}" if start_date and end_date else ""
|
|
latex += f"\\noindent\\textbf{{{university}}}\\hfill {date_str}\\\\\n"
|
|
latex += f"\\textit{{{degree}}}\n"
|
|
if location:
|
|
latex += f"\\\\{location}"
|
|
latex += "\n\n"
|
|
|
|
# Coursework
|
|
coursework = self.coursework_text.get(1.0, tk.END).strip()
|
|
if coursework:
|
|
latex += "\\subsection*{Relevant Coursework}\n"
|
|
courses = [c.strip() for c in coursework.split(',') if c.strip()]
|
|
latex += "\\begin{itemize}\n"
|
|
for i in range(0, len(courses), 4):
|
|
row_courses = courses[i:i + 4]
|
|
latex += " \\item " + " \\quad $\\bullet$ ".join(row_courses) + "\n"
|
|
latex += "\\end{itemize}\n\n"
|
|
|
|
# Experience
|
|
latex += "\\section*{Experience}\n"
|
|
for vars_dict, resp_text in self.experience_entries:
|
|
company = vars_dict['company'].get()
|
|
title = vars_dict['title'].get()
|
|
start = vars_dict['start_date'].get()
|
|
end = vars_dict['end_date'].get()
|
|
location = vars_dict['location'].get()
|
|
|
|
if company:
|
|
date_str = f"{start} -- {end}" if start and end else ""
|
|
latex += f"\\noindent\\textbf{{{company}}}\\hfill {date_str}\\\\\n"
|
|
latex += f"\\textit{{{title}}}"
|
|
if location:
|
|
latex += f"\\\\{location}"
|
|
latex += "\n"
|
|
|
|
responsibilities = resp_text.get(1.0, tk.END).strip()
|
|
if responsibilities:
|
|
latex += "\\begin{itemize}\n"
|
|
for resp in responsibilities.split('\n'):
|
|
if resp.strip():
|
|
latex += f" \\item {resp.strip()}\n"
|
|
latex += "\\end{itemize}\n"
|
|
latex += "\n"
|
|
|
|
# Projects
|
|
latex += "\\section*{Projects}\n"
|
|
for vars_dict, desc_text in self.project_entries:
|
|
name = vars_dict['name'].get()
|
|
tech = vars_dict['technologies'].get()
|
|
date = vars_dict['date'].get()
|
|
|
|
if name:
|
|
tech_str = f" | {tech}" if tech else ""
|
|
latex += f"\\noindent\\textbf{{{name}}}\\textit{{{tech_str}}}\\hfill {date}\\\\\n"
|
|
|
|
description = desc_text.get(1.0, tk.END).strip()
|
|
if description:
|
|
latex += "\\begin{itemize}\n"
|
|
for desc in description.split('\n'):
|
|
if desc.strip():
|
|
latex += f" \\item {desc.strip()}\n"
|
|
latex += "\\end{itemize}\n"
|
|
latex += "\n"
|
|
|
|
# Skills
|
|
latex += "\\section*{Technical Skills}\n"
|
|
languages = self.skills_vars['languages'].get()
|
|
tools = self.skills_vars['tools'].get()
|
|
technologies = self.skills_vars['technologies'].get()
|
|
|
|
if languages or tools or technologies:
|
|
if languages:
|
|
latex += f"\\noindent\\textbf{{Languages:}} {languages}\\\\\n"
|
|
if tools:
|
|
latex += f"\\textbf{{Developer Tools:}} {tools}\\\\\n"
|
|
if technologies:
|
|
latex += f"\\textbf{{Technologies/Frameworks:}} {technologies}\n"
|
|
latex += "\n"
|
|
|
|
# Leadership
|
|
latex += "\\section*{Leadership / Extracurricular}\n"
|
|
for vars_dict, achieve_text in self.leadership_entries:
|
|
org = vars_dict['organization'].get()
|
|
role = vars_dict['role'].get()
|
|
date = vars_dict['date'].get()
|
|
location = vars_dict['location'].get()
|
|
|
|
if org:
|
|
latex += f"\\noindent\\textbf{{{org}}}\\hfill {date}\\\\\n"
|
|
latex += f"\\textit{{{role}}}"
|
|
if location:
|
|
latex += f"\\\\{location}"
|
|
latex += "\n"
|
|
|
|
achievements = achieve_text.get(1.0, tk.END).strip()
|
|
if achievements:
|
|
latex += "\\begin{itemize}\n"
|
|
for achieve in achievements.split('\n'):
|
|
if achieve.strip():
|
|
latex += f" \\item {achieve.strip()}\n"
|
|
latex += "\\end{itemize}\n"
|
|
latex += "\n"
|
|
|
|
latex += "\\end{document}"
|
|
return latex
|
|
|
|
def save_to_file(self):
|
|
latex_code = self.output_text.get(1.0, tk.END).strip()
|
|
if not latex_code:
|
|
messagebox.showwarning("Warning", "Please generate LaTeX code first!")
|
|
return
|
|
|
|
file_path = filedialog.asksaveasfilename(
|
|
defaultextension=".tex",
|
|
filetypes=[("LaTeX files", "*.tex"), ("All files", "*.*")]
|
|
)
|
|
|
|
if file_path:
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(latex_code)
|
|
messagebox.showinfo("Success", f"CV saved to {file_path}")
|
|
|
|
def clear_all(self):
|
|
if messagebox.askyesno("Confirm", "Are you sure you want to clear all fields?"):
|
|
# Clear personal info
|
|
for var in self.personal_vars.values():
|
|
var.set("")
|
|
|
|
# Clear education
|
|
for var in self.edu_vars.values():
|
|
var.set("")
|
|
self.coursework_text.delete(1.0, tk.END)
|
|
|
|
# Clear experience
|
|
for widget in self.exp_container.winfo_children():
|
|
widget.destroy()
|
|
self.experience_entries = []
|
|
ttk.Button(self.exp_container, text="Add Position", command=self.add_experience).pack(pady=5)
|
|
|
|
# Clear projects
|
|
for widget in self.proj_container.winfo_children():
|
|
widget.destroy()
|
|
self.project_entries = []
|
|
ttk.Button(self.proj_container, text="Add Project", command=self.add_project).pack(pady=5)
|
|
|
|
# Clear skills
|
|
for var in self.skills_vars.values():
|
|
var.set("")
|
|
|
|
# Clear leadership
|
|
for widget in self.lead_container.winfo_children():
|
|
widget.destroy()
|
|
self.leadership_entries = []
|
|
ttk.Button(self.lead_container, text="Add Activity", command=self.add_leadership).pack(pady=5)
|
|
|
|
# Clear output
|
|
self.output_text.delete(1.0, tk.END)
|
|
|
|
|
|
def main():
|
|
root = tk.Tk()
|
|
app = CVGeneratorApp(root)
|
|
root.mainloop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |