Files
Latex-CV-Builder/attempt-2.py
T
2026-05-26 18:08:00 +01:00

799 lines
38 KiB
Python

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()