Files
2026-05-26 18:23:26 +01:00

1605 lines
74 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
# Comprehensive Month & Season List for High-Fidelity Academic Resumes
MONTHS_LIST = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November",
"December",
"Spring", "Summer", "Fall", "Winter"
]
YEARS_LIST = [str(y) for y in range(2030, 2010, -1)] + ["Present"]
SUGGESTED_LOCATIONS = [
"Berkeley, CA", "San Francisco, CA", "New York, NY", "Seattle, WA",
"Austin, TX", "Boston, MA", "Chicago, IL", "Los Angeles, CA",
"London, UK", "Toronto, ON", "Vancouver, BC", "Paris, France",
"Berlin, Germany", "Tokyo, Japan", "Sydney, Australia", "Singapore", "City, State", "University Name"
]
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)
# State management
self.nav_orientation = "horizontal" # Starts in modern horizontal tab layout
self.current_appearance_mode = "System Mode"
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 escape_latex(self, text):
"""Escapes raw text entries to ensure special characters won't crash pdflatex compilation"""
if not text:
return ""
# Handle backslash replacement first to avoid recursive escaping
text = text.replace('\\', '\\textbackslash{}')
# Escape characters that have special meaning inside LaTeX
text = text.replace('&', '\\&')
text = text.replace('%', '\\%')
text = text.replace('$', '\\$')
text = text.replace('_', '\\_')
text = text.replace('{', '\\{')
text = text.replace('}', '\\}')
text = text.replace('#', '\\#')
text = text.replace('~', '\\textasciitilde{}')
text = text.replace('^', '\\textasciicircum{}')
return text
def _build_ui(self):
"""Clears and builds the interface dynamically based on chosen orientation"""
if hasattr(self, "sidebar") and self.sidebar:
self.sidebar.grid_forget()
if hasattr(self, "top_nav") and self.top_nav:
self.top_nav.grid_forget()
if hasattr(self, "main_container") and self.main_container:
self.main_container.grid_forget()
if self.nav_orientation == "vertical":
self._build_vertical_sidebar()
self._build_main_area(column=1, row=0)
else:
self._build_horizontal_top_nav()
self._build_main_area(column=0, row=1)
self._navigate_to("personal")
def _build_horizontal_top_nav(self):
"""Builds a horizontal navigation bar with all options side-by-side"""
self.top_nav = ctk.CTkFrame(self, height=75, corner_radius=0, fg_color=COLOR_BG_SIDEBAR)
self.top_nav.grid(row=0, column=0, columnspan=2, sticky="ew")
# Branding (Left section)
brand_frame = ctk.CTkFrame(self.top_nav, fg_color="transparent")
brand_frame.pack(side="left", padx=20, pady=5)
ctk.CTkLabel(
brand_frame, text="LaTeX CV Builder",
font=ctk.CTkFont(family="Inter", size=15, weight="bold"),
text_color=COLOR_TEXT_PRIMARY
).pack(anchor="w")
selectors_container = ctk.CTkFrame(brand_frame, fg_color="transparent")
selectors_container.pack(anchor="w", pady=(2, 0))
self.orientation_selector = ctk.CTkComboBox(
selectors_container, values=["Horizontal Layout", "Vertical Layout"],
command=self._toggle_orientation_layout, height=24, width=130,
font=ctk.CTkFont(family="Inter", size=10)
)
self.orientation_selector.pack(side="left", padx=(0, 5))
self.orientation_selector.set("Horizontal Layout")
self.appearance_selector = ctk.CTkComboBox(
selectors_container, values=["System Mode", "Light Mode", "Dark Mode"],
command=self._update_appearance_mode, height=24, width=110,
font=ctk.CTkFont(family="Inter", size=10)
)
self.appearance_selector.pack(side="left")
self.appearance_selector.set(self.current_appearance_mode)
# Actions (Right section)
actions_frame = ctk.CTkFrame(self.top_nav, fg_color="transparent")
actions_frame.pack(side="right", padx=15, pady=10)
self.import_btn = ctk.CTkButton(
actions_frame, text="Import", command=self._import_latex,
fg_color=("#64748b", "#475569"), hover_color=("#475569", "#334155"),
text_color="white", width=80, height=34, corner_radius=6,
font=ctk.CTkFont(family="Inter", size=11, weight="bold")
)
self.import_btn.pack(side="left", padx=4)
self.generate_btn = ctk.CTkButton(
actions_frame, text="Generate", command=self._generate_latex,
fg_color=("#2563eb", "#3b82f6"), hover_color=("#1d4ed8", "#2563eb"),
text_color="white", width=90, height=34, corner_radius=6,
font=ctk.CTkFont(family="Inter", size=11, weight="bold")
)
self.generate_btn.pack(side="left", padx=4)
self.compile_btn = ctk.CTkButton(
actions_frame, text="Compile", command=self._compile_latex,
fg_color=("#16a34a", "#22c55e"), hover_color=("#15803d", "#16a34a"),
text_color="white", width=90, height=34, corner_radius=6,
font=ctk.CTkFont(family="Inter", size=11, weight="bold"), state="disabled"
)
self.compile_btn.pack(side="left", padx=4)
# Tab Selectors (Center section)
tabs_frame = ctk.CTkFrame(self.top_nav, fg_color="transparent")
tabs_frame.pack(side="left", fill="x", expand=True, padx=20)
nav_items = [
("personal", "Personal Info"),
("education", "Education"),
("coursework", "Coursework"),
("experience", "Experience"),
("projects", "Projects"),
("skills", "Skills"),
("leadership", "Leadership"),
("visual_editor", "Word Visual Editor"),
("output", "LaTeX & Live Preview")
]
self.nav_buttons = {}
for key, label in nav_items:
btn = ctk.CTkButton(
tabs_frame, text=label, command=lambda k=key: self._navigate_to(k),
fg_color="transparent", text_color=COLOR_TEXT_PRIMARY,
hover_color=("white", "gray10"), height=34, corner_radius=6,
font=ctk.CTkFont(family="Inter", size=12, weight="normal")
)
btn.pack(side="left", padx=2)
self.nav_buttons[key] = btn
def _build_vertical_sidebar(self):
"""Builds a classic side-aligned vertical sidebar layout"""
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(12, weight=1)
# Branding
brand_frame = ctk.CTkFrame(self.sidebar, fg_color="transparent")
brand_frame.grid(row=0, column=0, padx=20, pady=(25, 10), sticky="ew")
ctk.CTkLabel(
brand_frame, text="LaTeX CV Builder",
font=ctk.CTkFont(family="Inter", size=18, weight="bold"),
text_color=COLOR_TEXT_PRIMARY
).pack(anchor="w")
ctk.CTkLabel(
brand_frame, text="ATS & AI-Optimized Layouts",
font=ctk.CTkFont(family="Inter", size=11, weight="normal"),
text_color=COLOR_TEXT_MUTED
).pack(anchor="w", pady=(2, 0))
# Orientation Selector
self.orientation_selector = ctk.CTkComboBox(
self.sidebar, values=["Horizontal Layout", "Vertical Layout"],
command=self._toggle_orientation_layout, height=30,
font=ctk.CTkFont(family="Inter", size=12)
)
self.orientation_selector.grid(row=1, column=0, padx=12, pady=(0, 5), sticky="ew")
self.orientation_selector.set("Vertical Layout")
# Appearance Mode Selector
self.appearance_selector = ctk.CTkComboBox(
self.sidebar, values=["System Mode", "Light Mode", "Dark Mode"],
command=self._update_appearance_mode, height=30,
font=ctk.CTkFont(family="Inter", size=12)
)
self.appearance_selector.grid(row=2, column=0, padx=12, pady=(0, 15), sticky="ew")
self.appearance_selector.set(self.current_appearance_mode)
# Navigation
nav_items = [
("personal", "Personal Information"),
("education", "Academic Education"),
("coursework", "Relevant Coursework"),
("experience", "Work Experience"),
("projects", "Technical Projects"),
("skills", "Core Skills Matrix"),
("leadership", "Leadership & Activities"),
("visual_editor", "Word Visual Editor"),
("output", "LaTeX & Live Preview")
]
self.nav_buttons = {}
for idx, (key, label) in enumerate(nav_items, start=3):
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=36, corner_radius=6, anchor="w",
font=ctk.CTkFont(family="Inter", size=13, weight="normal")
)
btn.grid(row=idx, column=0, padx=12, pady=2, sticky="ew")
self.nav_buttons[key] = btn
# Actions
actions_frame = ctk.CTkFrame(self.sidebar, fg_color="transparent")
actions_frame.grid(row=12, 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 Label
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=13, column=0, padx=15, pady=(0, 20), sticky="w")
def _update_appearance_mode(self, selected_option):
"""Updates the visual appearance theme dynamically and syncs text formatting boxes"""
self.current_appearance_mode = selected_option
if "Light" in selected_option:
ctk.set_appearance_mode("light")
bg_color = COLOR_BG_CARD[0]
fg_color = COLOR_TEXT_PRIMARY[0]
caret_color = "black"
elif "Dark" in selected_option:
ctk.set_appearance_mode("dark")
bg_color = COLOR_BG_CARD[1]
fg_color = COLOR_TEXT_PRIMARY[1]
caret_color = "white"
else:
ctk.set_appearance_mode("system")
# Determine system theme
current_mode = ctk.get_appearance_mode()
bg_color = COLOR_BG_CARD[1] if current_mode == "Dark" else COLOR_BG_CARD[0]
fg_color = COLOR_TEXT_PRIMARY[1] if current_mode == "Dark" else COLOR_TEXT_PRIMARY[0]
caret_color = "white" if current_mode == "Dark" else "black"
if hasattr(self, "visual_text") and self.visual_text:
self.visual_text.configure(bg=bg_color, fg=fg_color, insertbackground=caret_color)
if hasattr(self, "appearance_selector") and self.appearance_selector:
self.appearance_selector.set(selected_option)
def _toggle_orientation_layout(self, selected_option):
"""Transitions between horizontal top ribbon tabs or vertical side tabs layout"""
new_mode = "horizontal" if "Horizontal" in selected_option else "vertical"
if new_mode != self.nav_orientation:
self.nav_orientation = new_mode
self._build_ui()
def _build_main_area(self, column=1, row=0):
"""Instantiates all views inside a unified structural container"""
self.main_container = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent")
self.main_container.grid(row=row, column=column, sticky="nsew", padx=30, pady=30)
self.main_container.grid_columnconfigure(0, weight=1)
self.main_container.grid_rowconfigure(0, weight=1)
# Setup correct responsive layout weights
if self.nav_orientation == "horizontal":
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=0)
else:
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=0)
self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=1)
# Instantiate all sections
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["visual_editor"] = self._create_visual_editor_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,
is_combobox=False, values=None):
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
)
elif is_combobox:
field = ctk.CTkComboBox(
field_container, height=height, corner_radius=6,
border_width=1, border_color=COLOR_BORDER, fg_color=COLOR_BG_MAIN,
values=values or []
)
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_date_selectors(self, parent, row, col, columnspan=1):
"""Structured side-by-side dropdown selectors for Month/Year"""
container = ctk.CTkFrame(parent, fg_color="transparent")
container.grid(row=row, column=col, columnspan=columnspan, sticky="ew", padx=10, pady=10)
container.grid_columnconfigure((0, 1, 2, 3), weight=1)
# Start Date
ctk.CTkLabel(container, text="Start Month", font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
text_color=COLOR_TEXT_MUTED).grid(row=0, column=0, sticky="w")
start_month = ctk.CTkComboBox(container, height=32, corner_radius=6, values=MONTHS_LIST, width=90)
start_month.grid(row=1, column=0, padx=(0, 5), sticky="ew")
ctk.CTkLabel(container, text="Start Year", font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
text_color=COLOR_TEXT_MUTED).grid(row=0, column=1, sticky="w")
start_year = ctk.CTkComboBox(container, height=32, corner_radius=6, values=YEARS_LIST[:-1], width=90)
start_year.grid(row=1, column=1, padx=(0, 10), sticky="ew")
# End Date
ctk.CTkLabel(container, text="End Month", font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
text_color=COLOR_TEXT_MUTED).grid(row=0, column=2, sticky="w")
end_month = ctk.CTkComboBox(container, height=32, corner_radius=6, values=["Present"] + MONTHS_LIST, width=90)
end_month.grid(row=1, column=2, padx=(0, 5), sticky="ew")
ctk.CTkLabel(container, text="End Year", font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
text_color=COLOR_TEXT_MUTED).grid(row=0, column=3, sticky="w")
end_year = ctk.CTkComboBox(container, height=32, corner_radius=6, values=YEARS_LIST, width=90)
end_year.grid(row=1, column=3, sticky="ew")
# Set clean defaults
start_month.set("Jan")
start_year.set("2018")
end_month.set("May")
end_year.set("2022")
def handle_present_select(val):
if val == "Present" or end_year.get() == "Present":
if val == "Present":
end_year.set("Present")
end_month.set("Present")
end_month.configure(command=handle_present_select)
end_year.configure(command=handle_present_select)
return {
"start_month": start_month,
"start_year": start_year,
"end_month": end_month,
"end_year": end_year
}
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_university = self._create_form_field(card, "University / College Name", 0, 0, columnspan=2)
self.edu_degree = self._create_form_field(card, "Degree Major / Qualification", 1, 0, columnspan=2)
self.edu_dates = self._create_date_selectors(card, 2, 0, columnspan=2)
self.edu_location = self._create_form_field(card, "Institution Location (Select or Type)", 3, 0, columnspan=2,
is_combobox=True, values=SUGGESTED_LOCATIONS)
self.edu_location.set("Berkeley, CA")
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")
company = self._create_form_field(card, "Company / Employer Name", 1, 0)
title = self._create_form_field(card, "Job Title / Designation", 1, 1)
dates = self._create_date_selectors(card, 2, 0, columnspan=2)
location = self._create_form_field(card, "Work Location (Select or Type)", 3, 0, columnspan=2, is_combobox=True,
values=SUGGESTED_LOCATIONS)
location.set("San Francisco, CA")
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(({
'company': company,
'title': title,
'dates': dates,
'location': location
}, 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")
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)
ctk.CTkLabel(card, text="Execution Date", font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
text_color=COLOR_TEXT_MUTED).grid(row=2, column=1, sticky="w", padx=10, pady=(10, 0))
proj_date_container = ctk.CTkFrame(card, fg_color="transparent")
proj_date_container.grid(row=3, column=1, sticky="ew", padx=10, pady=(0, 10))
proj_date_container.grid_columnconfigure((0, 1), weight=1)
proj_month = ctk.CTkComboBox(proj_date_container, height=32, corner_radius=6, values=MONTHS_LIST)
proj_month.grid(row=0, column=0, padx=(0, 5), sticky="ew")
proj_month.set("Jan")
proj_year = ctk.CTkComboBox(proj_date_container, height=32, corner_radius=6, values=YEARS_LIST[:-1])
proj_year.grid(row=0, column=1, sticky="ew")
proj_year.set("2021")
desc_text = self._create_form_field(card, "Project Deliverables / Features (One bullet point per line)", 4, 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=5, column=1, pady=(0, 15), padx=15, sticky="e")
self.proj_entries.append(({
'name': name,
'technologies': technologies,
'proj_month': proj_month,
'proj_year': proj_year
}, 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")
organization = self._create_form_field(card, "Organization / Project Initiative", 1, 0)
role = self._create_form_field(card, "Your Role / Position", 1, 1)
dates = self._create_date_selectors(card, 2, 0, columnspan=2)
location = self._create_form_field(card, "Location", 3, 0, columnspan=2, is_combobox=True,
values=SUGGESTED_LOCATIONS)
location.set("Berkeley, CA")
achieve_text = self._create_form_field(card, "Core Accomplishments / Impacts (One point per line)", 4, 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=5, column=1, pady=(0, 15), padx=15, sticky="e")
self.lead_entries.append(({
'organization': organization,
'role': role,
'dates': dates,
'location': location
}, achieve_text, card))
def _create_visual_editor_section(self):
"""Builds a Microsoft Word style rich WYSIWYG visual editor workspace"""
frame = ctk.CTkFrame(self.main_container, fg_color="transparent")
frame.grid_columnconfigure(0, weight=1)
frame.grid_rowconfigure(2, weight=1)
self._create_section_header(
frame, "Microsoft Word WYSIWYG Editor",
"Type, format, and structure with classic word-processor controls. Changes compile directly to clean LaTeX."
)
# Ribbon Style Toolbar
toolbar = ctk.CTkFrame(frame, corner_radius=8, border_width=1, border_color=COLOR_BORDER,
fg_color=COLOR_BG_SIDEBAR)
toolbar.grid(row=1, column=0, sticky="ew", padx=5, pady=(0, 10))
# Alignment / Style actions
btn_bold = ctk.CTkButton(
toolbar, text="B", width=36, height=28, corner_radius=4,
font=ctk.CTkFont(family="Inter", size=13, weight="bold"),
command=lambda: self.toggle_tag("bold")
)
btn_bold.pack(side="left", padx=4, pady=6)
btn_italic = ctk.CTkButton(
toolbar, text="I", width=36, height=28, corner_radius=4,
font=ctk.CTkFont(family="Inter", size=13, weight="normal"),
command=lambda: self.toggle_tag("italic")
)
btn_italic.pack(side="left", padx=4, pady=6)
btn_underline = ctk.CTkButton(
toolbar, text="U", width=36, height=28, corner_radius=4,
font=ctk.CTkFont(family="Inter", size=13, weight="normal"),
command=lambda: self.toggle_tag("underline")
)
btn_underline.pack(side="left", padx=4, pady=6)
# Style Ribbon Divider
divider1 = ctk.CTkFrame(toolbar, width=1, height=20, fg_color=COLOR_BORDER)
divider1.pack(side="left", padx=10, pady=6)
btn_h1 = ctk.CTkButton(
toolbar, text="Section Heading", width=120, height=28, corner_radius=4,
fg_color=("#cbd5e1", "#334155"), text_color=COLOR_TEXT_PRIMARY,
font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
command=lambda: self.toggle_tag("h1")
)
btn_h1.pack(side="left", padx=4, pady=6)
btn_h2 = ctk.CTkButton(
toolbar, text="Subsection", width=95, height=28, corner_radius=4,
fg_color=("#cbd5e1", "#334155"), text_color=COLOR_TEXT_PRIMARY,
font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
command=lambda: self.toggle_tag("h2")
)
btn_h2.pack(side="left", padx=4, pady=6)
divider2 = ctk.CTkFrame(toolbar, width=1, height=20, fg_color=COLOR_BORDER)
divider2.pack(side="left", padx=10, pady=6)
btn_bullet = ctk.CTkButton(
toolbar, text="• Bullet Point", width=100, height=28, corner_radius=4,
fg_color=("#cbd5e1", "#334155"), text_color=COLOR_TEXT_PRIMARY,
font=ctk.CTkFont(family="Inter", size=11),
command=lambda: self.toggle_tag("bullet")
)
btn_bullet.pack(side="left", padx=4, pady=6)
# Clear formats
btn_clear = ctk.CTkButton(
toolbar, text="Clear Formatting", width=110, height=28, corner_radius=4,
fg_color=("#fee2e2", "#7f1d1d"), hover_color=("#fecaca", "#991b1b"),
text_color=("#dc2626", "#fca5a5"),
font=ctk.CTkFont(family="Inter", size=11, weight="bold"),
command=self.clear_visual_formatting
)
btn_clear.pack(side="right", padx=10, pady=6)
# Editor Frame Wrapper
editor_wrapper = ctk.CTkFrame(frame, corner_radius=10, border_width=1, border_color=COLOR_BORDER,
fg_color=COLOR_BG_CARD)
editor_wrapper.grid(row=2, column=0, sticky="nsew", padx=5, pady=5)
editor_wrapper.grid_rowconfigure(0, weight=1)
editor_wrapper.grid_columnconfigure(0, weight=1)
scrollbar_y = ctk.CTkScrollbar(editor_wrapper)
scrollbar_y.grid(row=0, column=1, sticky="ns")
# Resolve correct starting colors for manual Tk Text integration
current_mode = ctk.get_appearance_mode()
bg_init = COLOR_BG_CARD[1] if current_mode == "Dark" else COLOR_BG_CARD[0]
fg_init = COLOR_TEXT_PRIMARY[1] if current_mode == "Dark" else COLOR_TEXT_PRIMARY[0]
caret_init = "white" if current_mode == "Dark" else "black"
self.visual_text = tk.Text(
editor_wrapper, wrap="word", undo=True, font=("Inter", 12),
bg=bg_init, fg=fg_init, insertbackground=caret_init,
relief="flat", bd=0, padx=25, pady=25,
yscrollcommand=scrollbar_y.set
)
self.visual_text.grid(row=0, column=0, sticky="nsew")
scrollbar_y.configure(command=self.visual_text.yview)
# Config standard WYSIWYG live rendering tags
self.visual_text.tag_config("bold", font=("Inter", 12, "bold"))
self.visual_text.tag_config("italic", font=("Inter", 12, "italic"))
self.visual_text.tag_config("underline", underline=True)
self.visual_text.tag_config("h1", font=("Inter", 18, "bold"), foreground="#2563eb")
self.visual_text.tag_config("h2", font=("Inter", 14, "bold"), foreground="#4b5563")
self.visual_text.tag_config("bullet", font=("Inter", 12), lmargin1=20, lmargin2=35)
self._load_visual_sample_data()
return frame
def toggle_tag(self, tag_name):
"""Applies or removes formatting blocks/inlines across active selections"""
try:
start = self.visual_text.index("sel.first")
end = self.visual_text.index("sel.last")
except tk.TclError:
return # Empty selection trigger block
# Inline vs block style tags check
ranges = self.visual_text.tag_ranges(tag_name)
has_tag = False
for i in range(0, len(ranges), 2):
if self.visual_text.compare(start, ">=", ranges[i]) and self.visual_text.compare(end, "<=", ranges[i + 1]):
has_tag = True
break
if has_tag:
self.visual_text.tag_remove(tag_name, start, end)
else:
self.visual_text.tag_add(tag_name, start, end)
def clear_visual_formatting(self):
"""Restores plain-text status across selected range block"""
try:
start = self.visual_text.index("sel.first")
end = self.visual_text.index("sel.last")
except tk.TclError:
return
for tag in ["bold", "italic", "underline", "h1", "h2", "bullet"]:
self.visual_text.tag_remove(tag, start, end)
def _load_visual_sample_data(self):
"""Hydrates the Word Visual Editor with styled CV presets matching template"""
self.visual_text.delete("1.0", "end")
sample_blocks = [
("FIRST LAST\n", ["bold", "h1"]),
("123 Street Name, Town, State 12345 | 123-456-7890 | email@gmail.com\n\n", ["italic"]),
("Education\n", ["h1"]),
("State University - Sep 2017 to May 2021\n", ["bold"]),
("Bachelor of Science in Computer Science - GPA: 3.8/4.0\n\n", []),
("Relevant Coursework\n", ["h1"]),
("• Data Structures, Algorithms Analysis, Artificial Intelligence\n", ["bullet"]),
("• Systems Programming, Software Methodology, Computer Architecture\n\n", ["bullet"]),
("Experience\n", ["h1"]),
("Electronics Company - May 2020 to Aug 2020\n", ["bold"]),
("Software Engineer Intern\n", ["italic"]),
("• Developed automated daily unit testing pipelines reducing debug loops.\n", ["bullet"]),
("• Built continuous integration automation systems using Python and PowerShell.\n\n", ["bullet"]),
("Technical Skills\n", ["h1"]),
("• Languages: Python, Java, C, HTML/CSS, JavaScript, SQL\n", ["bullet"]),
("• Tools: VS Code, Eclipse, Google Cloud Platform, Android Studio\n", ["bullet"])
]
for text, tags in sample_blocks:
start_pos = self.visual_text.index("insert")
self.visual_text.insert("insert", text)
end_pos = self.visual_text.index("insert -1c")
for tag in tags:
self.visual_text.tag_add(tag, start_pos, end_pos)
def _create_output_section(self):
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 = 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, (data, _, 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):
"""Triggers targeted compilations based on focused visual or form tabs"""
is_visual_editor_active = (self.current_section == self.section_frames["visual_editor"])
if is_visual_editor_active:
self.latex_text = self._parse_visual_to_latex()
else:
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 _parse_visual_to_latex(self):
"""Converts formatted editor text cleanly into fully custom LaTeX CV layout"""
latex = r"""\documentclass[letterpaper,11pt]{article}
\usepackage{latexsym}
\usepackage[empty]{fullpage}
\usepackage{titlesec}
\usepackage{marvosym}
\usepackage[usenames,dvipsnames]{color}
\usepackage{verbatim}
\usepackage{enumitem}
\usepackage[hidelinks]{hyperref}
\usepackage{fancyhdr}
\usepackage[english]{babel}
\usepackage{tabularx}
\input{glyphtounicode}
\pagestyle{fancy}
\fancyhf{}
\fancyfoot{}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\addtolength{\oddsidemargin}{-0.5in}
\addtolength{\evensidemargin}{-0.5in}
\addtolength{\textwidth}{1.0in}
\addtolength{\topmargin}{-0.5in}
\addtolength{\textheight}{1.0in}
\urlstyle{same}
\raggedbottom
\raggedright
\setlength{\tabcolsep}{0in}
\titleformat{\section}{
\vspace{-4pt}\scshape\raggedright\large
}{}{0em}{}[\color{black}\titrule \vspace{-5pt}]
\pdfgentounicode=1
\newcommand{\resumeItem}[1]{
\item\small{{#1 \vspace{-2pt}}}
}
\begin{document}
"""
lines = self.visual_text.get("1.0", "end-1c").split("\n")
in_list = False
is_first_header = True
for idx, line in enumerate(lines):
if not line.strip():
continue
line_start = f"{idx + 1}.0"
tags = self.visual_text.tag_names(line_start)
if "h1" in tags:
if in_list:
latex += "\\end{itemize}\n\n"
in_list = False
# Treat the absolute first h1 tag as Centered Name Header
if is_first_header:
latex += f"\\begin{{center}}\n \\textbf{{\\Huge \\scshape {self.escape_latex(line.strip())}}} \\\\\n"
is_first_header = False
else:
latex += f"\\section{{{self.escape_latex(line.strip())}}}\n"
elif "h2" in tags:
if in_list:
latex += "\\end{itemize}\n\n"
in_list = False
latex += f"\\subsection*{{{self.escape_latex(line.strip())}}}\n"
elif line.strip().startswith("•") or "bullet" in tags:
if not in_list:
latex += "\\begin{itemize}[leftmargin=0.15in, label={}]\n"
in_list = True
cleaned_item = line.replace("•", "").strip()
formatted_item = self._format_line_text(idx + 1, cleaned_item)
latex += f" \\resumeItem{{{formatted_item}}}\n"
else:
if in_list:
latex += "\\end{itemize}\n\n"
in_list = False
formatted_line = self._format_line_text(idx + 1, line)
if not is_first_header and idx == 1: # Address/Contact subtitle line centered
latex += f" \\small {formatted_line} \\\\\n\\end{{center}}\n\n"
else:
latex += f"{formatted_line} \\\\\n"
if in_list:
latex += "\\end{itemize}\n"
latex += "\\end{document}"
return latex
def _format_line_text(self, line_num, text_str):
"""Iterates char tags to produce clean nested LaTeX formatting tags with robust escaping"""
formatted = ""
active_tags = set()
for char_idx, char in enumerate(text_str):
char_pos = f"{line_num}.{char_idx}"
tags_at_char = set(self.visual_text.tag_names(char_pos))
tags_at_char = {t for t in tags_at_char if t in ("bold", "italic", "underline")}
# Orderly tag closing
if "bold" in active_tags and "bold" not in tags_at_char:
formatted += "}"
active_tags.remove("bold")
if "italic" in active_tags and "italic" not in tags_at_char:
formatted += "}"
active_tags.remove("italic")
if "underline" in active_tags and "underline" not in tags_at_char:
formatted += "}"
active_tags.remove("underline")
# Orderly tag opening
if "underline" in tags_at_char and "underline" not in active_tags:
formatted += "\\underline{"
active_tags.add("underline")
if "italic" in tags_at_char and "italic" not in active_tags:
formatted += "\\textit{"
active_tags.add("italic")
if "bold" in tags_at_char and "bold" not in active_tags:
formatted += "\\textbf{"
active_tags.add("bold")
# Escaping LaTeX syntax
if char == "&":
formatted += "\\&"
elif char == "%":
formatted += "\\%"
elif char == "$":
formatted += "\\$"
elif char == "_":
formatted += "\\_"
elif char == "#":
formatted += "\\#"
elif char == "{":
formatted += "\\{"
elif char == "}":
formatted += "\\}"
else:
formatted += char
for tag in list(active_tags):
formatted += "}"
return formatted
def _build_latex_cv(self):
"""Generates traditional high-fidelity structure matching Template CV exactly, escaping inputs"""
def get_date_range_str(d):
s_m, s_y, e_m, e_y = d['start_month'].get(), d['start_year'].get(), d['end_month'].get(), d[
'end_year'].get()
if e_m == "Present" or e_y == "Present":
return f"{s_m} {s_y} -- Present"
return f"{s_m} {s_y} -- {e_m} {e_y}"
latex = r"""\documentclass[letterpaper,11pt]{article}
\usepackage{latexsym}
\usepackage[empty]{fullpage}
\usepackage{titlesec}
\usepackage{marvosym}
\usepackage[usenames,dvipsnames]{color}
\usepackage{verbatim}
\usepackage{enumitem}
\usepackage[hidelinks]{hyperref}
\usepackage{fancyhdr}
\usepackage[english]{babel}
\usepackage{tabularx}
\input{glyphtounicode}
\pagestyle{fancy}
\fancyhf{}
\fancyfoot{}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\addtolength{\oddsidemargin}{-0.5in}
\addtolength{\evensidemargin}{-0.5in}
\addtolength{\textwidth}{1.0in}
\addtolength{\topmargin}{-0.5in}
\addtolength{\textheight}{1.0in}
\urlstyle{same}
\raggedbottom
\raggedright
\setlength{\tabcolsep}{0in}
% Sections formatting - Left-aligned with an elegant horizontal divider rule
\titleformat{\section}{
\vspace{-4pt}\scshape\raggedright\large
}{}{0em}{}[\color{black}\titrule \vspace{-5pt}]
\pdfgentounicode=1
\newcommand{\resumeItem}[1]{
\item\small{{#1 \vspace{-2pt}}}
}
\newcommand{\resumeSubheading}[4]{
\vspace{-1pt}\item
\begin{tabular*}{0.97\textwidth}[t]{l@{\extracolsep{\fill}}r}
\textbf{#1} & #2 \\
\textit{#3} & \textit{#4} \\
\end{tabular*}\vspace{-5pt}
}
\newcommand{\resumeProjectHeading}[2]{
\vspace{-1pt}\item
\begin{tabular*}{0.97\textwidth}{l@{\extracolsep{\fill}}r}
\textbf{#1} & #2 \\
\end{tabular*}\vspace{-5pt}
}
\renewcommand\labelitemii{$\vcenter{\hbox{\tiny$\bullet$}}$}
\newcommand{\resumeSubHeadingListStart}{\begin{itemize}[leftmargin=0.15in, label={}]}
\newcommand{\resumeSubHeadingListEnd}{\end{itemize}}
\newcommand{\resumeItemListStart}{\begin{itemize}}
\newcommand{\resumeItemListEnd}{\end{itemize}\vspace{-5pt}}
\begin{document}
"""
# Center aligned Name Header
first = self.escape_latex(self.first_name.get().strip().upper())
last = self.escape_latex(self.last_name.get().strip().upper())
latex += f"\\begin{{center}}\n"
latex += f" \\textbf{{\\Huge \\scshape {first} {last}}} \\\\\n"
address = self.escape_latex(self.personal_vars['address'].get().strip())
if address:
latex += f" \\small {address} \\\\\n"
contacts = []
if self.personal_vars['phone'].get().strip():
p = self.escape_latex(self.personal_vars['phone'].get().strip())
contacts.append(f"\\small {p}")
if self.personal_vars['email'].get().strip():
e = self.personal_vars['email'].get().strip()
# href paths should not contain escaped structures
contacts.append(f"\\href{{mailto:{e}}}{{{self.escape_latex(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/{self.escape_latex(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/{self.escape_latex(g)}}}")
latex += " \\ $|$ \\ ".join(contacts) + "\n\\end{center}\n\n"
# Education Section
if self.edu_university.get().strip():
latex += "\\section{Education}\n"
latex += "\\resumeSubHeadingListStart\n"
uni = self.escape_latex(self.edu_university.get().strip())
deg = self.escape_latex(self.edu_degree.get().strip())
loc = self.escape_latex(self.edu_location.get().strip())
date_str = self.escape_latex(get_date_range_str(self.edu_dates))
latex += f" \\resumeSubheading\n" \
f" {{{uni}}}{{{date_str}}}\n" \
f" {{{deg}}}{{{loc}}}\n"
latex += "\\resumeSubHeadingListEnd\n\n"
# Relevant Coursework Section (Configured with high-fidelity side-by-side spacing)
cw = self.coursework.get("1.0", tk.END).strip()
if cw:
latex += "\\section{Relevant Coursework}\n"
courses = [self.escape_latex(c.strip()) for c in cw.split(',') if c.strip()]
latex += "\\begin{itemize}[leftmargin=0.15in, label={}]\n"
latex += " \\item\\small{\n"
# Format clean multi-column layouts securely inside standard tables to prevent overflow
latex += " \\begin{tabular*}{0.97\\textwidth}[t]{l@{\\extracolsep{\\fill}}l@{\\extracolsep{\\fill}}l@{\\extracolsep{\\fill}}l}\n"
for i in range(0, len(courses), 4):
chunk = courses[i:i + 4]
while len(chunk) < 4:
chunk.append("")
row_str = " & ".join([f"$\\bullet$ {c}" if c else "" for c in chunk])
latex += f" {row_str} \\\\\n"
latex += " \\end{tabular*}\n"
latex += " }\n\\end{itemize}\n\n"
# Professional Experience Section
if self.exp_entries:
latex += "\\section{Experience}\n"
latex += "\\resumeSubHeadingListStart\n"
for v, t, _ in self.exp_entries:
comp = self.escape_latex(v['company'].get().strip())
if not comp: continue
title = self.escape_latex(v['title'].get().strip())
loc = self.escape_latex(v['location'].get().strip())
date_str = self.escape_latex(get_date_range_str(v['dates']))
latex += f" \\resumeSubheading\n" \
f" {{{comp}}}{{{date_str}}}\n" \
f" {{{title}}}{{{loc}}}\n"
resp = t.get("1.0", tk.END).strip()
if resp:
latex += " \\resumeItemListStart\n"
for r in resp.split('\n'):
if r.strip():
latex += f" \\resumeItem{{{self.escape_latex(r.strip())}}}\n"
latex += " \\resumeItemListEnd\n"
latex += "\\resumeSubHeadingListEnd\n\n"
# Technical Projects Section
if self.proj_entries:
latex += "\\section{Projects}\n"
latex += "\\resumeSubHeadingListStart\n"
for v, t, _ in self.proj_entries:
name = self.escape_latex(v['name'].get().strip())
if not name: continue
tech = self.escape_latex(v['technologies'].get().strip())
p_m = self.escape_latex(v['proj_month'].get())
p_y = self.escape_latex(v['proj_year'].get())
date_str = f"{p_m} {p_y}"
tech_str = f" \\ $|$ \\ \\textit{{{tech}}}" if tech else ""
latex += f" \\resumeProjectHeading\n" \
f" {{\\textbf{{{name}}}{tech_str}}}{{{date_str}}}\n"
desc = t.get("1.0", tk.END).strip()
if desc:
latex += " \\resumeItemListStart\n"
for d in desc.split('\n'):
if d.strip():
latex += f" \\resumeItem{{{self.escape_latex(d.strip())}}}\n"
latex += " \\resumeItemListEnd\n"
latex += "\\resumeSubHeadingListEnd\n\n"
# Technical Skills Matrix
has_skills = False
skills_latex = "\\section{Technical Skills}\n" \
"\\begin{itemize}[leftmargin=0.15in, label={}]\n" \
" \\small{\\item{\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" \\textbf{{{label}:}} {{{self.escape_latex(txt)}}} \\\\\n"
has_skills = True
skills_latex += " }}\n\\end{itemize}\n"
if has_skills:
latex += skills_latex + "\n"
# Leadership & Extracurricular Activities
if self.lead_entries:
latex += "\\section{Leadership / Extracurricular}\n"
latex += "\\resumeSubHeadingListStart\n"
for v, t, _ in self.lead_entries:
org = self.escape_latex(v['organization'].get().strip())
if not org: continue
role = self.escape_latex(v['role'].get().strip())
loc = self.escape_latex(v['location'].get().strip())
date_str = self.escape_latex(get_date_range_str(v['dates']))
latex += f" \\resumeSubheading\n" \
f" {{{org}}}{{{date_str}}}\n" \
f" {{{role}}}{{{loc}}}\n"
ach = t.get("1.0", tk.END).strip()
if ach:
latex += " \\resumeItemListStart\n"
for a in ach.split('\n'):
if a.strip():
latex += f" \\resumeItem{{{self.escape_latex(a.strip())}}}\n"
latex += " \\resumeItemListEnd\n"
latex += "\\resumeSubHeadingListEnd\n\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):
# Personal Name Mapping
name_match = re.search(r'\\textbf\{\\Huge \\scshape (.*?)\}', 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 "")
edu_match = re.search(r'\\resumeSubheading\s*\{(.*?)\}\s*\{(.*?)\}\s*\{(.*?)\}\s*\{(.*?)\}', latex_text)
if edu_match:
self.edu_university.delete(0, tk.END)
self.edu_university.insert(0, edu_match.group(1))
self.edu_degree.delete(0, tk.END)
self.edu_degree.insert(0, edu_match.group(3))
self.edu_location.set(edu_match.group(4))
cw_match = re.search(r'\\section\{Relevant Coursework\}.*?\\item\\small\{(.*?)\}', latex_text, re.DOTALL)
if cw_match:
self.coursework.delete("1.0", tk.END)
cleaned_cw = cw_match.group(1).replace('\\item', '').replace('\\quad $\\bullet$', ',').replace('$\\bullet$',
'').replace(
'\\\\', '').replace('\n', '').strip()
self.coursework.insert("1.0", cleaned_cw)
self._set_status("Asset import matching done. Review 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")
# Show useful debug log window to trace markup structural failures
log_window = tk.Toplevel(self)
log_window.title("Compilation Logs / Diagnostic Window")
log_window.geometry("700x500")
log_text = ctk.CTkTextbox(log_window, wrap="word")
log_text.pack(fill="both", expand=True, padx=10, pady=10)
log_text.insert("1.0", result.stdout + "\n" + result.stderr)
messagebox.showerror("Compilation Error",
"LaTeX subprocess run failed. Review Diagnostics logs opened on screen.")
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):
"""Populates form controls with the exact high-fidelity template values out of the box"""
self.first_name.insert(0, "First")
self.last_name.insert(0, "Last")
self.personal_vars['address'].insert(0, "123 Street Name, Town, State 12345")
self.personal_vars['phone'].insert(0, "123-456-7890")
self.personal_vars['email'].insert(0, "email@gmail.com")
self.personal_vars['linkedin'].insert(0, "username")
self.personal_vars['github'].insert(0, "username")
self.edu_university.insert(0, "State University")
self.edu_degree.insert(0, "Bachelor of Science in Computer Science")
self.edu_dates['start_month'].set("Sep")
self.edu_dates['start_year'].set("2017")
self.edu_dates['end_month'].set("May")
self.edu_dates['end_year'].set("2021")
self.edu_location.set("City, State")
self.coursework.insert("1.0",
"Data Structures, Algorithms Analysis, Artificial Intelligence, Systems Programming, Software Methodology, Database Management, Internet Technology, Computer Architecture")
# Programmatically populate Experience Card Nodes matching template CV
for _, _, card in self.exp_entries:
card.destroy()
self.exp_entries.clear()
# Job 1
self._add_experience_entry()
job1 = self.exp_entries[0]
job1[0]['company'].insert(0, "Electronics Company")
job1[0]['title'].insert(0, "Software Engineer Intern")
job1[0]['dates']['start_month'].set("May")
job1[0]['dates']['start_year'].set("2020")
job1[0]['dates']['end_month'].set("Aug")
job1[0]['dates']['end_year'].set("2020")
job1[0]['location'].set("City, State")
job1[1].insert("1.0",
"Developed a service to automatically perform a set of unit tests daily on a product in development in order to decrease time needed for team members to identify and fix bugs/issues.\n"
"Incorporated scripts using Python and PowerShell to aggregate XML test results into an organized format and to load the latest build code onto the hardware, so that daily testing can be performed.\n"
"Utilized Jenkins to provide a continuous integration service in order to automate the entire process of loading the latest build code and test files, running the tests, and generating a report of the results once per day.\n"
"Explored ways to visualize and send a daily report of test results to team members using HTML, Javascript, and CSS."
)
# Job 2
self._add_experience_entry()
job2 = self.exp_entries[1]
job2[0]['company'].insert(0, "Startup, Inc")
job2[0]['title'].insert(0, "Front End Developer Intern")
job2[0]['dates']['start_month'].set("May")
job2[0]['dates']['start_year'].set("2019")
job2[0]['dates']['end_month'].set("Aug")
job2[0]['dates']['end_year'].set("2019")
job2[0]['location'].set("City, State")
job2[1].insert("1.0",
"Assisted in development of the front end of a mobile application for iOS/Android using Dart and the Flutter framework.\n"
"Worked with Google Firebase to manage user inputted data across multiple platforms including web and mobile apps.\n"
"Collaborated with team members using version control systems such as Git to organize modifications and assign tasks.\n"
"Utilized Android Studio as a development environment in order to visualize the application in both iOS and Android."
)
# Clear and load 3 projects from template CV
for _, _, card in self.proj_entries:
card.destroy()
self.proj_entries.clear()
# Project 1
self._add_project_entry()
p1 = self.proj_entries[0]
p1[0]['name'].insert(0, "Gym Reservation Bot")
p1[0]['technologies'].insert(0, "Python, Selenium, Google Cloud Console")
p1[0]['proj_month'].set("Jan")
p1[0]['proj_year'].set("2021")
p1[1].insert("1.0",
"Developed an automatic bot using Python and Google Cloud Console to register myself for a timeslot at my school gym.\n"
"Implemented Selenium to create an instance of Chrome in order to interact with the correct elements of the web page.\n"
"Created a Linux virtual machine to run on Google Cloud so that the program is able to run everyday from the cloud.\n"
"Used Cron to schedule the program to execute automatically at 11 AM every morning so a reservation is made for me."
)
# Project 2
self._add_project_entry()
p2 = self.proj_entries[1]
p2[0]['name'].insert(0, "Ticket Price Calculator App")
p2[0]['technologies'].insert(0, "Java, Android Studio")
p2[0]['proj_month'].set("Nov")
p2[0]['proj_year'].set("2020")
p2[1].insert("1.0",
"Created an Android application using Java and Android Studio to calculate ticket prices for trips to museums in NYC.\n"
"Processed user inputted information in the back-end of the app to return a subtotal price based on the tickets selected.\n"
"Utilized the layout editor to create a UI for the application in order to allow different scenes to interact with each other."
)
# Project 3
self._add_project_entry()
p3 = self.proj_entries[2]
p3[0]['name'].insert(0, "Transaction Management GUI")
p3[0]['technologies'].insert(0, "Java, Eclipse, JavaFX")
p3[0]['proj_month'].set("Oct")
p3[0]['proj_year'].set("2020")
p3[1].insert("1.0",
"Designed a sample banking transaction system using Java to simulate the common functions of using a bank account.\n"
"Used JavaFX to create a GUI that supports actions such as creating an account, deposit, withdraw, list all accounts, etc.\n"
"Implemented object-oriented programming practices such as inheritance to create different account types and databases."
)
# Technical Skills
self.skills_vars['languages'].insert("1.0", "Python, Java, C, HTML/CSS, JavaScript, SQL")
self.skills_vars['tools'].insert("1.0", "VS Code, Eclipse, Google Cloud Platform, Android Studio")
self.skills_vars['technologies'].insert("1.0", "Linux, Jenkins, GitHub, JUnit, WordPress")
# Leadership / Extracurricular Node
for _, _, card in self.lead_entries:
card.destroy()
self.lead_entries.clear()
self._add_leadership_entry()
l1 = self.lead_entries[0]
l1[0]['organization'].insert(0, "Fraternity")
l1[0]['role'].insert(0, "President")
l1[0]['dates']['start_month'].set("Spring")
l1[0]['dates']['start_year'].set("2020")
l1[0]['dates']['end_month'].set("Present")
l1[0]['dates']['end_year'].set("Present")
l1[0]['location'].set("University Name")
l1[1].insert("1.0",
"Achieved a 4 star fraternity ranking by the Office of Fraternity and Sorority Affairs (highest possible ranking).\n"
"Managed executive board of 5 members and ran weekly meetings to oversee progress in essential parts of the chapter.\n"
"Led chapter of 30+ members to work towards goals that improve and promote community service, academics, and unity."
)
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()