From a040efe56c5dcf541688d8c7c0f82bc8c1a8d891 Mon Sep 17 00:00:00 2001 From: mudabbir-ahmad Date: Tue, 26 May 2026 18:08:00 +0100 Subject: [PATCH] Attempt 2 --> Promising --- attempt-2.py | 799 +++++++++++++++++++++++++++++++++++++++++++ qwen-styler.py | 909 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1708 insertions(+) create mode 100644 attempt-2.py create mode 100644 qwen-styler.py diff --git a/attempt-2.py b/attempt-2.py new file mode 100644 index 0000000..cd6367e --- /dev/null +++ b/attempt-2.py @@ -0,0 +1,799 @@ +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() \ No newline at end of file diff --git a/qwen-styler.py b/qwen-styler.py new file mode 100644 index 0000000..47a4261 --- /dev/null +++ b/qwen-styler.py @@ -0,0 +1,909 @@ +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 sys + +try: + import fitz # PyMuPDF + from PIL import Image, ImageTk + PDF_AVAILABLE = True +except ImportError: + PDF_AVAILABLE = False + +# Modern SaaS theme +ctk.set_appearance_mode("light") +ctk.set_default_color_theme("blue") + +# Color palette (SaaS-inspired) +COLORS = { + "primary": "#6366f1", # Indigo + "primary_hover": "#4f46e5", + "accent": "#10b981", # Emerald + "danger": "#ef4444", + "surface": "#ffffff", + "surface_alt": "#f9fafb", + "border": "#e5e7eb", + "text": "#111827", + "text_muted": "#6b7280", + "bg": "#f3f4f6", +} + +class CVBuilderApp(ctk.CTk): + def __init__(self): + super().__init__() + self.title("CV Forge — Professional LaTeX Resume Builder") + self.geometry("1400x900") + self.minsize(1100, 750) + self.configure(fg_color=COLORS["bg"]) + + # State + self.current_tab = "personal" + self.section_frames = {} + self.nav_buttons = {} + self.latex_text = "" + self.pdf_image_refs = [] + self.temp_dir = None + self.exp_entries = [] + self.proj_entries = [] + self.lead_entries = [] + + self._build_header() + self._build_tab_bar() + self._build_content_area() + self._populate_sample_data() + + # ------------------------------------------------------------------ # + # HEADER BAR + # ------------------------------------------------------------------ # + def _build_header(self): + header = ctk.CTkFrame(self, fg_color=COLORS["surface"], height=70, corner_radius=0) + header.grid(row=0, column=0, sticky="ew") + header.grid_columnconfigure(2, weight=1) + header.grid_propagate(False) + + # Brand + brand = ctk.CTkFrame(header, fg_color="transparent") + brand.grid(row=0, column=0, padx=24, pady=0, sticky="w") + + logo = ctk.CTkLabel(brand, text="CV", font=ctk.CTkFont(size=18, weight="bold"), + text_color="white", width=36, height=36, corner_radius=8, + fg_color=COLORS["primary"]) + logo.pack(side="left", padx=(0, 12)) + + brand_text = ctk.CTkFrame(brand, fg_color="transparent") + brand_text.pack(side="left") + ctk.CTkLabel(brand_text, text="CV Forge", font=ctk.CTkFont(size=16, weight="bold"), + text_color=COLORS["text"]).pack(anchor="w") + ctk.CTkLabel(brand_text, text="Professional Resume Builder", + font=ctk.CTkFont(size=11), text_color=COLORS["text_muted"]).pack(anchor="w") + + # Divider + sep = ctk.CTkFrame(header, width=1, fg_color=COLORS["border"]) + sep.grid(row=0, column=1, sticky="ns", padx=24, pady=18) + + # Status indicator + self.status_pill = ctk.CTkLabel( + header, text=" Ready ", font=ctk.CTkFont(size=11, weight="bold"), + text_color=COLORS["accent"], fg_color="#d1fae5", + corner_radius=12, height=24 + ) + self.status_pill.grid(row=0, column=2, sticky="w", padx=12) + + # Actions (right side) + actions = ctk.CTkFrame(header, fg_color="transparent") + actions.grid(row=0, column=3, padx=24, sticky="e") + + self.import_btn = ctk.CTkButton( + actions, text="Import .tex", command=self._import_latex, + fg_color="transparent", border_width=1, border_color=COLORS["border"], + text_color=COLORS["text"], hover_color=COLORS["surface_alt"], + height=36, corner_radius=8, font=ctk.CTkFont(size=12, weight="bold") + ) + self.import_btn.pack(side="left", padx=6) + + self.generate_btn = ctk.CTkButton( + actions, text="Generate", command=self._generate_latex, + fg_color=COLORS["primary"], hover_color=COLORS["primary_hover"], + text_color="white", height=36, corner_radius=8, + font=ctk.CTkFont(size=12, weight="bold") + ) + self.generate_btn.pack(side="left", padx=6) + + self.compile_btn = ctk.CTkButton( + actions, text="Compile PDF", command=self._compile_latex, + fg_color=COLORS["accent"], hover_color="#059669", + text_color="white", height=36, corner_radius=8, + font=ctk.CTkFont(size=12, weight="bold"), state="disabled" + ) + self.compile_btn.pack(side="left", padx=(6, 0)) + + # ------------------------------------------------------------------ # + # HORIZONTAL TAB BAR + # ------------------------------------------------------------------ # + def _build_tab_bar(self): + tab_bar = ctk.CTkFrame(self, fg_color=COLORS["surface"], height=56, corner_radius=0) + tab_bar.grid(row=1, column=0, sticky="ew") + tab_bar.grid_propagate(False) + + tabs_container = ctk.CTkFrame(tab_bar, fg_color="transparent") + tabs_container.pack(side="left", padx=24, pady=10) + + tab_items = [ + ("personal", "Personal"), + ("education", "Education"), + ("coursework", "Coursework"), + ("experience", "Experience"), + ("projects", "Projects"), + ("skills", "Skills"), + ("leadership", "Leadership"), + ("output", "Preview"), + ] + + for key, label in tab_items: + btn = ctk.CTkButton( + tabs_container, text=label, + command=lambda k=key: self._navigate_to(k), + fg_color="transparent", text_color=COLORS["text_muted"], + hover_color=COLORS["surface_alt"], + height=34, corner_radius=8, + font=ctk.CTkFont(size=13, weight="bold") + ) + btn.pack(side="left", padx=2) + self.nav_buttons[key] = btn + + # Separator at bottom of tab bar + sep = ctk.CTkFrame(tab_bar, height=1, fg_color=COLORS["border"]) + sep.pack(side="bottom", fill="x") + + # ------------------------------------------------------------------ # + # MAIN CONTENT AREA + # ------------------------------------------------------------------ # + def _build_content_area(self): + self.content = ctk.CTkFrame(self, fg_color=COLORS["bg"], corner_radius=0) + self.content.grid(row=2, column=0, sticky="nsew") + self.content.grid_columnconfigure(0, weight=1) + self.content.grid_rowconfigure(0, weight=1) + self.grid_rowconfigure(2, weight=1) + + # Build all section frames + 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() + + self._navigate_to("personal") + + # Reusable components + def _make_card(self, parent, title=None): + card = ctk.CTkFrame(parent, fg_color=COLORS["surface"], corner_radius=12, border_width=1, border_color=COLORS["border"]) + if title: + ctk.CTkLabel(card, text=title, font=ctk.CTkFont(size=14, weight="bold"), + text_color=COLORS["text"]).grid(row=0, column=0, columnspan=2, + sticky="w", padx=20, pady=(16, 8)) + return card + + def _section_header(self, parent, title, subtitle): + wrap = ctk.CTkFrame(parent, fg_color="transparent") + wrap.grid(row=0, column=0, sticky="ew", pady=(0, 20)) + ctk.CTkLabel(wrap, text=title, font=ctk.CTkFont(size=24, weight="bold"), + text_color=COLORS["text"]).pack(anchor="w") + ctk.CTkLabel(wrap, text=subtitle, font=ctk.CTkFont(size=13), + text_color=COLORS["text_muted"]).pack(anchor="w", pady=(4, 0)) + return wrap + + def _form_row(self, parent, label, widget, row): + ctk.CTkLabel(parent, text=label, font=ctk.CTkFont(size=12, weight="bold"), + text_color=COLORS["text_muted"]).grid(row=row, column=0, sticky="w", + padx=20, pady=8) + widget.grid(row=row, column=1, sticky="ew", padx=20, pady=8) + + # ------------------------------------------------------------------ # + # PERSONAL + # ------------------------------------------------------------------ # + def _create_personal_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + + self._section_header(frame, "Personal Information", + "Tell employers how to reach you.") + + card = self._make_card(frame) + card.grid(row=1, column=0, sticky="ew") + card.grid_columnconfigure(1, weight=1) + + # Name row + ctk.CTkLabel(card, text="Full Name", font=ctk.CTkFont(size=12, weight="bold"), + text_color=COLORS["text_muted"]).grid(row=0, column=0, sticky="w", + padx=20, pady=(20, 8)) + + name_row = ctk.CTkFrame(card, fg_color="transparent") + name_row.grid(row=0, column=1, sticky="ew", padx=20, pady=(20, 8)) + name_row.grid_columnconfigure((0, 1), weight=1) + + self.first_name = ctk.CTkEntry(name_row, height=38, placeholder_text="First name", + border_color=COLORS["border"]) + self.first_name.grid(row=0, column=0, sticky="ew", padx=(0, 8)) + self.last_name = ctk.CTkEntry(name_row, height=38, placeholder_text="Last name", + border_color=COLORS["border"]) + self.last_name.grid(row=0, column=1, sticky="ew", padx=(8, 0)) + + # Contact fields + self.personal_vars = {} + fields = [ + ("Address", "address", "e.g. 123 Main St, New York, NY"), + ("Phone", "phone", "e.g. 555-123-4567"), + ("Email", "email", "you@example.com"), + ("LinkedIn", "linkedin", "username"), + ("GitHub", "github", "username"), + ] + for i, (label, key, ph) in enumerate(fields, start=1): + entry = ctk.CTkEntry(card, height=38, placeholder_text=ph, border_color=COLORS["border"]) + self._form_row(card, label, entry, i) + self.personal_vars[key] = entry + + # Spacer + ctk.CTkFrame(card, height=10, fg_color="transparent").grid(row=len(fields)+1, column=0) + + return frame + + # ------------------------------------------------------------------ # + # EDUCATION + # ------------------------------------------------------------------ # + def _create_education_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + self._section_header(frame, "Education", "Your academic background.") + + card = self._make_card(frame) + card.grid(row=1, column=0, sticky="ew") + card.grid_columnconfigure(1, weight=1) + + self.edu_vars = {} + fields = [ + ("University", "university", "e.g. Stanford University"), + ("Degree", "degree", "e.g. B.S. Computer Science"), + ("Start Date", "start_date", "e.g. Sep. 2020"), + ("End Date", "end_date", "e.g. May 2024"), + ("Location", "location", "e.g. Stanford, CA"), + ] + for i, (label, key, ph) in enumerate(fields): + entry = ctk.CTkEntry(card, height=38, placeholder_text=ph, border_color=COLORS["border"]) + self._form_row(card, label, entry, i) + self.edu_vars[key] = entry + + ctk.CTkFrame(card, height=10, fg_color="transparent").grid(row=len(fields), column=0) + return frame + + # ------------------------------------------------------------------ # + # COURSEWORK + # ------------------------------------------------------------------ # + def _create_coursework_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + self._section_header(frame, "Relevant Coursework", + "Highlight academic courses that match the role.") + + card = self._make_card(frame) + card.grid(row=1, column=0, sticky="ew") + card.grid_columnconfigure(0, weight=1) + + self.coursework = ctk.CTkTextbox(card, height=140, corner_radius=8, + border_width=1, border_color=COLORS["border"], + font=("Segoe UI", 13)) + self.coursework.grid(row=0, column=0, sticky="ew", padx=20, pady=20) + + ctk.CTkLabel(card, text="Separate courses with commas. They'll be auto-formatted.", + font=ctk.CTkFont(size=12), text_color=COLORS["text_muted"]).grid( + row=1, column=0, sticky="w", padx=20, pady=(0, 16)) + return frame + + # ------------------------------------------------------------------ # + # EXPERIENCE + # ------------------------------------------------------------------ # + def _create_experience_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + self._section_header(frame, "Professional Experience", + "Your work history — most recent first.") + + 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) + + ctk.CTkButton( + frame, text="+ Add Position", command=self._add_experience_entry, + fg_color=COLORS["primary"], hover_color=COLORS["primary_hover"], + text_color="white", height=38, corner_radius=8, + font=ctk.CTkFont(size=13, weight="bold") + ).grid(row=2, column=0, pady=20, sticky="w") + return frame + + def _add_experience_entry(self): + idx = len(self.exp_entries) + card = self._make_card(self.exp_container, title=f"Position {idx + 1}") + card.grid(row=idx, column=0, sticky="ew", pady=(0, 12)) + card.grid_columnconfigure(1, weight=1) + + vars_dict = {} + fields = [ + ("Company", "company", "Company name"), + ("Title", "title", "e.g. Software Engineer"), + ("Start Date", "start_date", "e.g. Jun. 2023"), + ("End Date", "end_date", "e.g. Present"), + ("Location", "location", "e.g. Remote"), + ] + for i, (label, key, ph) in enumerate(fields, start=1): + entry = ctk.CTkEntry(card, height=36, placeholder_text=ph, border_color=COLORS["border"]) + self._form_row(card, label, entry, i) + vars_dict[key] = entry + + ctk.CTkLabel(card, text="Responsibilities (one per line)", + font=ctk.CTkFont(size=12, weight="bold"), + text_color=COLORS["text_muted"]).grid( + row=len(fields)+1, column=0, sticky="w", padx=20, pady=(12, 6)) + resp = ctk.CTkTextbox(card, height=120, corner_radius=8, + border_width=1, border_color=COLORS["border"]) + resp.grid(row=len(fields)+2, column=0, columnspan=2, sticky="ew", padx=20) + + ctk.CTkButton( + card, text="Remove", command=lambda c=card: self._remove_entry(c, self.exp_entries), + fg_color="transparent", border_width=1, border_color=COLORS["danger"], + text_color=COLORS["danger"], hover_color="#fee2e2", + height=32, corner_radius=8, width=90 + ).grid(row=len(fields)+3, column=0, columnspan=2, pady=(12, 16), sticky="w", padx=20) + + self.exp_entries.append((vars_dict, resp, card)) + + # ------------------------------------------------------------------ # + # PROJECTS + # ------------------------------------------------------------------ # + def _create_projects_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + self._section_header(frame, "Projects", + "Showcase your side projects and personal work.") + + 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) + + ctk.CTkButton( + frame, text="+ Add Project", command=self._add_project_entry, + fg_color=COLORS["primary"], hover_color=COLORS["primary_hover"], + text_color="white", height=38, corner_radius=8, + font=ctk.CTkFont(size=13, weight="bold") + ).grid(row=2, column=0, pady=20, sticky="w") + return frame + + def _add_project_entry(self): + idx = len(self.proj_entries) + card = self._make_card(self.proj_container, title=f"Project {idx + 1}") + card.grid(row=idx, column=0, sticky="ew", pady=(0, 12)) + card.grid_columnconfigure(1, weight=1) + + vars_dict = {} + fields = [ + ("Project Name", "name", "e.g. Task Manager App"), + ("Technologies", "technologies", "e.g. React, Node.js, PostgreSQL"), + ("Date", "date", "e.g. Summer 2023"), + ] + for i, (label, key, ph) in enumerate(fields, start=1): + entry = ctk.CTkEntry(card, height=36, placeholder_text=ph, border_color=COLORS["border"]) + self._form_row(card, label, entry, i) + vars_dict[key] = entry + + ctk.CTkLabel(card, text="Description (one per line)", + font=ctk.CTkFont(size=12, weight="bold"), + text_color=COLORS["text_muted"]).grid( + row=len(fields)+1, column=0, sticky="w", padx=20, pady=(12, 6)) + desc = ctk.CTkTextbox(card, height=100, corner_radius=8, + border_width=1, border_color=COLORS["border"]) + desc.grid(row=len(fields)+2, column=0, columnspan=2, sticky="ew", padx=20) + + ctk.CTkButton( + card, text="Remove", command=lambda c=card: self._remove_entry(c, self.proj_entries), + fg_color="transparent", border_width=1, border_color=COLORS["danger"], + text_color=COLORS["danger"], hover_color="#fee2e2", + height=32, corner_radius=8, width=90 + ).grid(row=len(fields)+3, column=0, columnspan=2, pady=(12, 16), sticky="w", padx=20) + + self.proj_entries.append((vars_dict, desc, card)) + + # ------------------------------------------------------------------ # + # SKILLS + # ------------------------------------------------------------------ # + def _create_skills_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + self._section_header(frame, "Technical Skills", + "Languages, tools, and technologies you know.") + + card = self._make_card(frame) + card.grid(row=1, column=0, sticky="ew") + card.grid_columnconfigure(1, weight=1) + + self.skills_vars = {} + fields = [ + ("Languages", "languages", "e.g. Python, JavaScript, Go"), + ("Developer Tools", "tools", "e.g. Git, Docker, VS Code"), + ("Frameworks", "technologies", "e.g. React, Django, TensorFlow"), + ] + for i, (label, key, ph) in enumerate(fields): + entry = ctk.CTkEntry(card, height=38, placeholder_text=ph, border_color=COLORS["border"]) + self._form_row(card, label, entry, i) + self.skills_vars[key] = entry + + ctk.CTkFrame(card, height=10, fg_color="transparent").grid(row=len(fields), column=0) + return frame + + # ------------------------------------------------------------------ # + # LEADERSHIP + # ------------------------------------------------------------------ # + def _create_leadership_section(self): + frame = ctk.CTkScrollableFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + self._section_header(frame, "Leadership & Activities", + "Volunteer work, clubs, and community involvement.") + + 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) + + ctk.CTkButton( + frame, text="+ Add Activity", command=self._add_leadership_entry, + fg_color=COLORS["primary"], hover_color=COLORS["primary_hover"], + text_color="white", height=38, corner_radius=8, + font=ctk.CTkFont(size=13, weight="bold") + ).grid(row=2, column=0, pady=20, sticky="w") + return frame + + def _add_leadership_entry(self): + idx = len(self.lead_entries) + card = self._make_card(self.lead_container, title=f"Activity {idx + 1}") + card.grid(row=idx, column=0, sticky="ew", pady=(0, 12)) + card.grid_columnconfigure(1, weight=1) + + vars_dict = {} + fields = [ + ("Organization", "organization", "e.g. ACM Student Chapter"), + ("Role", "role", "e.g. President"), + ("Date", "date", "e.g. 2022 — 2023"), + ("Location", "location", "e.g. Campus"), + ] + for i, (label, key, ph) in enumerate(fields, start=1): + entry = ctk.CTkEntry(card, height=36, placeholder_text=ph, border_color=COLORS["border"]) + self._form_row(card, label, entry, i) + vars_dict[key] = entry + + ctk.CTkLabel(card, text="Achievements (one per line)", + font=ctk.CTkFont(size=12, weight="bold"), + text_color=COLORS["text_muted"]).grid( + row=len(fields)+1, column=0, sticky="w", padx=20, pady=(12, 6)) + ach = ctk.CTkTextbox(card, height=100, corner_radius=8, + border_width=1, border_color=COLORS["border"]) + ach.grid(row=len(fields)+2, column=0, columnspan=2, sticky="ew", padx=20) + + ctk.CTkButton( + card, text="Remove", command=lambda c=card: self._remove_entry(c, self.lead_entries), + fg_color="transparent", border_width=1, border_color=COLORS["danger"], + text_color=COLORS["danger"], hover_color="#fee2e2", + height=32, corner_radius=8, width=90 + ).grid(row=len(fields)+3, column=0, columnspan=2, pady=(12, 16), sticky="w", padx=20) + + self.lead_entries.append((vars_dict, ach, card)) + + # ------------------------------------------------------------------ # + # OUTPUT (Side-by-side editor + preview) + # ------------------------------------------------------------------ # + def _create_output_section(self): + frame = ctk.CTkFrame(self.content, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + frame.grid_rowconfigure(1, weight=1) + + self._section_header(frame, "Preview & Export", + "Your LaTeX source alongside the compiled PDF.") + + # Two-column layout + split = ctk.CTkFrame(frame, fg_color="transparent") + split.grid(row=1, column=0, sticky="nsew") + split.grid_columnconfigure((0, 1), weight=1) + split.grid_rowconfigure(0, weight=1) + + # LaTeX editor card + editor_card = self._make_card(split) + editor_card.grid(row=0, column=0, sticky="nsew", padx=(0, 8)) + editor_card.grid_columnconfigure(0, weight=1) + editor_card.grid_rowconfigure(1, weight=1) + + ed_head = ctk.CTkFrame(editor_card, fg_color="transparent") + ed_head.grid(row=0, column=0, sticky="ew", padx=20, pady=(16, 10)) + ed_head.grid_columnconfigure(0, weight=1) + ctk.CTkLabel(ed_head, text="LaTeX Source", font=ctk.CTkFont(size=14, weight="bold"), + text_color=COLORS["text"]).grid(row=0, column=0, sticky="w") + + ed_actions = ctk.CTkFrame(ed_head, fg_color="transparent") + ed_actions.grid(row=0, column=1, sticky="e") + ctk.CTkButton(ed_actions, text="Save", command=self._save_tex, + fg_color="transparent", border_width=1, border_color=COLORS["border"], + text_color=COLORS["text"], hover_color=COLORS["surface_alt"], + height=28, corner_radius=6, width=60, + font=ctk.CTkFont(size=11, weight="bold")).pack(side="left", padx=4) + ctk.CTkButton(ed_actions, text="Copy", command=self._copy_latex, + fg_color="transparent", border_width=1, border_color=COLORS["border"], + text_color=COLORS["text"], hover_color=COLORS["surface_alt"], + height=28, corner_radius=6, width=60, + font=ctk.CTkFont(size=11, weight="bold")).pack(side="left") + + self.latex_editor = ctk.CTkTextbox( + editor_card, corner_radius=8, font=("Consolas", 11), + border_width=1, border_color=COLORS["border"], fg_color="#fafafa" + ) + self.latex_editor.grid(row=1, column=0, sticky="nsew", padx=20, pady=(0, 20)) + + # Preview card + preview_card = self._make_card(split) + preview_card.grid(row=0, column=1, sticky="nsew", padx=(8, 0)) + preview_card.grid_columnconfigure(0, weight=1) + preview_card.grid_rowconfigure(1, weight=1) + + pv_head = ctk.CTkFrame(preview_card, fg_color="transparent") + pv_head.grid(row=0, column=0, sticky="ew", padx=20, pady=(16, 10)) + ctk.CTkLabel(pv_head, text="PDF Preview", font=ctk.CTkFont(size=14, weight="bold"), + text_color=COLORS["text"]).pack(side="left") + + self.pdf_viewer = ctk.CTkScrollableFrame(preview_card, corner_radius=8, + border_width=1, border_color=COLORS["border"], + fg_color=COLORS["surface_alt"]) + self.pdf_viewer.grid(row=1, column=0, sticky="nsew", padx=20, pady=(0, 20)) + + self.placeholder = ctk.CTkLabel( + self.pdf_viewer, + text="Click 'Generate' then 'Compile PDF'\nto preview your resume here.", + font=ctk.CTkFont(size=13), text_color=COLORS["text_muted"], + justify="center" + ) + self.placeholder.pack(expand=True, pady=60) + + return frame + + # ------------------------------------------------------------------ # + # SHARED UTILITIES + # ------------------------------------------------------------------ # + 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, key): + if self.current_tab and self.current_tab in self.section_frames: + self.section_frames[self.current_tab].grid_remove() + + self.current_tab = key + self.section_frames[key].grid(row=0, column=0, sticky="nsew") + + for k, btn in self.nav_buttons.items(): + if k == key: + btn.configure(fg_color=COLORS["primary"], text_color="white", + hover_color=COLORS["primary_hover"]) + else: + btn.configure(fg_color="transparent", text_color=COLORS["text_muted"], + hover_color=COLORS["surface_alt"]) + + def _set_status(self, text, color=None): + color = color or COLORS["accent"] + self.status_pill.configure(text=f" {text} ", text_color=color) + + # ------------------------------------------------------------------ # + # LATEX GENERATION + # ------------------------------------------------------------------ # + 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 generated") + 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=0.75in} +\setlist[itemize]{leftmargin=*, nosep, topsep=2pt, itemsep=2pt} +\pagestyle{empty} + +\definecolor{primary}{RGB}{99,102,241} +\definecolor{secondary}{RGB}{107,114,128} + +\hypersetup{colorlinks=true, linkcolor=primary, urlcolor=primary} + +\begin{document} +""" + first = self.first_name.get().strip() + last = self.last_name.get().strip() + latex += f"\n\\begin{{center}}\n\\textbf{{\\Huge \\textcolor{{primary}}{{{first} {last}}}}}\\\\[4pt]\n" + + contacts = [] + for key in ['address', 'phone', 'email', 'linkedin', 'github']: + v = self.personal_vars[key].get().strip() + if not v: continue + if key == 'phone': contacts.append(f"\\href{{tel:{v}}}{{{v}}}") + elif key == 'email': contacts.append(f"\\href{{mailto:{v}}}{{{v}}}") + elif key == 'linkedin': contacts.append(f"\\href{{https://linkedin.com/in/{v}}}{{linkedin.com/in/{v}}}") + elif key == 'github': contacts.append(f"\\href{{https://github.com/{v}}}{{github.com/{v}}}") + else: contacts.append(v) + latex += " \\textcolor{secondary}{$\\cdot$} ".join(contacts) + "\n\\end{center}\n\n\\vspace{4pt}\n\\hrule\n\\vspace{6pt}\n\n" + + if self.edu_vars['university'].get().strip(): + latex += "\\section*{\\textcolor{primary}{Education}}\n" + uni = self.edu_vars['university'].get().strip() + deg = self.edu_vars['degree'].get().strip() + s = self.edu_vars['start_date'].get().strip() + e = self.edu_vars['end_date'].get().strip() + loc = self.edu_vars['location'].get().strip() + 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 += "\\\\\n" + latex += f"\\textit{{{deg}}}" + if loc: latex += f" \\textcolor{{secondary}}{{$\\cdot$}} {loc}" + latex += "\n\n" + + cw = self.coursework.get("1.0", tk.END).strip() + if cw: + latex += "\\section*{\\textcolor{primary}{Relevant Coursework}}\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" + + if any(v['company'].get().strip() for v, _, _ in self.exp_entries): + latex += "\\section*{\\textcolor{primary}{Experience}}\n" + for v, t, _ in self.exp_entries: + comp = v['company'].get().strip() + if not comp: continue + title = v['title'].get().strip() + s = v['start_date'].get().strip() + e = v['end_date'].get().strip() + loc = v['location'].get().strip() + 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 += "\\\\\n" + latex += f"\\textit{{{title}}}" + if loc: latex += f" \\textcolor{{secondary}}{{$\\cdot$}} {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" + + if any(v['name'].get().strip() for v, _, _ in self.proj_entries): + latex += "\\section*{\\textcolor{primary}{Projects}}\n" + for v, t, _ in self.proj_entries: + name = v['name'].get().strip() + if not name: continue + tech = v['technologies'].get().strip() + date = v['date'].get().strip() + if tech: latex += f"\\noindent\\textbf{{{name}}} \\textcolor{{secondary}}{{|}} \\textit{{{tech}}}" + else: latex += f"\\noindent\\textbf{{{name}}}" + 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" + + has_skill = any(self.skills_vars[k].get().strip() for k in ['languages', 'tools', 'technologies']) + if has_skill: + latex += "\\section*{\\textcolor{primary}{Technical Skills}}\n" + for key, label in [('languages','Languages'), ('tools','Developer Tools'), ('technologies','Frameworks')]: + v = self.skills_vars[key].get().strip() + if v: latex += f"\\noindent\\textbf{{{label}:}} {v}\\\\\n" + latex += "\n" + + if any(v['organization'].get().strip() for v, _, _ in self.lead_entries): + latex += "\\section*{\\textcolor{primary}{Leadership \\& Activities}}\n" + for v, t, _ in self.lead_entries: + org = v['organization'].get().strip() + if not org: continue + role = v['role'].get().strip() + date = v['date'].get().strip() + loc = v['location'].get().strip() + latex += f"\\noindent\\textbf{{{org}}}" + if date: latex += f"\\hfill \\textcolor{{secondary}}{{{date}}}" + latex += "\\\\\n" + latex += f"\\textit{{{role}}}" + if loc: latex += f" \\textcolor{{secondary}}{{$\\cdot$}} {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 + + # ------------------------------------------------------------------ # + # IMPORT + # ------------------------------------------------------------------ # + def _import_latex(self): + path = filedialog.askopenfilename(title="Import LaTeX CV", + filetypes=[("LaTeX", "*.tex"), ("All", "*.*")]) + if not path: return + try: + with open(path, "r", encoding="utf-8") as f: + content = f.read() + self._parse_and_populate(content) + self._set_status("Imported from file") + except Exception as e: + messagebox.showerror("Import Error", f"Could not parse file:\n{e}") + + def _parse_and_populate(self, text): + name_m = re.search(r'\\textbf\{\\Huge \\textcolor\{primary\}\{(.*?)\}\}', text) + if name_m: + parts = name_m.group(1).split(None, 1) + self.first_name.delete(0, tk.END) + self.last_name.delete(0, tk.END) + self.first_name.insert(0, parts[0] if parts else "") + self.last_name.insert(0, parts[1] if len(parts) > 1 else "") + + contact_m = re.search(r'\\end\{center\}(.*?)\\hrule', text, re.DOTALL) + if contact_m: + block = contact_m.group(1) + email = re.search(r'([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)', block) + if email: + self.personal_vars['email'].delete(0, tk.END) + self.personal_vars['email'].insert(0, email.group(1)) + phone = re.search(r'(\d{3}[-.\s]?\d{3}[-.\s]?\d{4})', block) + if phone: + self.personal_vars['phone'].delete(0, tk.END) + self.personal_vars['phone'].insert(0, phone.group(1)) + + edu_m = re.search(r'\\section\*\{.*?Education\}.*?\\textbf\{(.*?)\}.*?\\textit\{(.*?)\}', text, re.DOTALL) + if edu_m: + self.edu_vars['university'].delete(0, tk.END) + self.edu_vars['university'].insert(0, edu_m.group(1)) + self.edu_vars['degree'].delete(0, tk.END) + self.edu_vars['degree'].insert(0, edu_m.group(2)) + + cw_m = re.search(r'\\section\*\{.*?Coursework\}.*?\\begin\{itemize\}(.*?)\\end\{itemize\}', text, re.DOTALL) + if cw_m: + self.coursework.delete("1.0", tk.END) + clean = cw_m.group(1).replace('\\item', '').replace('\\quad $\\bullet$', ',').replace('\n', '').strip() + self.coursework.insert("1.0", clean) + + # ------------------------------------------------------------------ # + # COMPILE & PDF + # ------------------------------------------------------------------ # + def _compile_latex(self): + if not shutil.which("pdflatex"): + messagebox.showerror("LaTeX Not Found", + "pdflatex is not on your PATH.\nInstall 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...", COLORS["primary"]) + 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("Compiled successfully") + self._render_pdf(pdf_path) + else: + self._set_status("Compilation failed", COLORS["danger"]) + messagebox.showerror("Compile Error", "Check your LaTeX syntax.") + except Exception as e: + self._set_status("Error", COLORS["danger"]) + messagebox.showerror("Error", str(e)) + finally: + self.compile_btn.configure(state="normal") + + def _render_pdf(self, pdf_path): + if not PDF_AVAILABLE: + ctk.CTkLabel(self.pdf_viewer, + text="Install PyMuPDF to enable PDF preview.", + text_color=COLORS["danger"]).pack(pady=30) + return + for w in self.pdf_viewer.winfo_children(): w.destroy() + self.pdf_image_refs.clear() + try: + doc = fitz.open(pdf_path) + for pg in range(len(doc)): + pix = doc.load_page(pg).get_pixmap(matrix=fitz.Matrix(1.8, 1.8)) + img = Image.open(io.BytesIO(pix.tobytes("ppm"))) + photo = ImageTk.PhotoImage(img) + self.pdf_image_refs.append(photo) + lbl = ctk.CTkLabel(self.pdf_viewer, image=photo, text="") + lbl.pack(pady=10) + shadow = ctk.CTkFrame(self.pdf_viewer, height=1, fg_color=COLORS["border"]) + shadow.pack(fill="x", padx=20) + doc.close() + except Exception as e: + ctk.CTkLabel(self.pdf_viewer, text=f"Render error: {e}", + text_color=COLORS["danger"]).pack(pady=20) + + # ------------------------------------------------------------------ # + # FILE OPERATIONS + # ------------------------------------------------------------------ # + def _save_tex(self): + path = filedialog.asksaveasfilename(defaultextension=".tex", + filetypes=[("LaTeX", "*.tex")], + initialfile="resume.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") + + # ------------------------------------------------------------------ # + # SAMPLE DATA + # ------------------------------------------------------------------ # + def _populate_sample_data(self): + self.first_name.insert(0, "Jane") + self.last_name.insert(0, "Doe") + self.personal_vars['address'].insert(0, "San Francisco, CA") + self.personal_vars['phone'].insert(0, "555-123-4567") + self.personal_vars['email'].insert(0, "jane.doe@example.com") + self.personal_vars['linkedin'].insert(0, "janedoe") + self.personal_vars['github'].insert(0, "janedoe") + + self.edu_vars['university'].insert(0, "Stanford University") + self.edu_vars['degree'].insert(0, "B.S. Computer Science, GPA 3.9") + self.edu_vars['start_date'].insert(0, "Sep. 2020") + self.edu_vars['end_date'].insert(0, "Jun. 2024") + self.edu_vars['location'].insert(0, "Stanford, CA") + + self.coursework.insert("1.0", + "Data Structures, Algorithms, Distributed Systems, " + "Machine Learning, Operating Systems, Computer Networks, " + "Database Systems, Software Engineering") + + +def main(): + app = CVBuilderApp() + app.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file