Abhishek Paliwal
HF_SPACE Updated on AP-MBP.local - Tue Nov 19 17:01:21 EET 2024
807454e
raw
history blame
12.3 kB
import os
import gradio as gr
import markdown
from jinja2 import Environment, FileSystemLoader
from weasyprint import HTML, CSS, urls
from markdown.extensions.meta import MetaExtension
from datetime import datetime
class CVGenerator:
def __init__(self):
self.template_dir = os.path.abspath('templates')
self.env = Environment(loader=FileSystemLoader(self.template_dir))
os.makedirs(self.template_dir, exist_ok=True)
self._create_template()
def _create_template(self):
template_path = os.path.join(self.template_dir, 'cv_template.html')
template_content = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1-Page-CV</title>
</head>
<body>
<div class="container">
<div class="left-column">
{% if profile_image %}
<div class="profile-image-container">
<img src="{{ profile_image }}" alt="Profile" class="profile-image">
</div>
{% endif %}
{{ left_column }}
</div>
<div class="right-column">
<h1>{{ name }}</h1>
<h2>{{ title }}</h2>
{{ right_column }}
</div>
</div>
</body>
</html>
"""
with open(template_path, 'w', encoding='utf-8') as f:
f.write(template_content)
def parse_markdown(self, md_content):
# Get first two lines
lines = md_content.split('\n')
name = lines[1].strip('# ').strip() # Remove potential markdown heading
title = lines[2].strip('# ').strip() if len(lines) > 1 else ""
md = markdown.Markdown(extensions=[MetaExtension()])
html = md.convert(md_content)
sections = html.split('<h3>')
left_content = []
right_content = []
for section in sections:
if not section.strip():
continue
section = '<h3>' + section
if any(keyword in section.lower() for keyword in ['contact', 'education', 'skills', 'languages']):
left_content.append(section)
elif 'name' not in section.lower():
right_content.append(section)
return {
'left_column': '\n'.join(left_content),
'right_column': '\n'.join(right_content),
'name': name,
'title': title
}
def generate_pdf(self, md_content, profile_image_path=None, main_color="#1B3A4B", secondary_color="rgb(239, 61, 55)", base_font_size="8pt"):
"""Generate PDF with customizable colors and font size"""
content = self.parse_markdown(md_content)
if profile_image_path:
abs_image_path = os.path.abspath(profile_image_path)
if os.path.exists(abs_image_path):
content['profile_image'] = urls.path2url(abs_image_path)
else:
print(f"Warning: Profile image not found at {abs_image_path}")
content['profile_image'] = None
else:
content['profile_image'] = None
template = self.env.get_template('cv_template.html')
rendered_html = template.render(**content)
# CSS with variable replacements
css = CSS(string=f'''
@page {{
size: A4;
margin: 0;
}}
body {{
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
font-size: {base_font_size};
line-height: 1.2;
letter-spacing: 0.03em;
}}
.container {{
display: flex;
height: 297mm;
width: 210mm;
}}
.left-column {{
background-color: {main_color};
color: white;
width: 70mm;
padding: 20px;
height: 100%;
box-sizing: border-box;
border-left: 5px solid {secondary_color};
}}
.profile-image-container {{
width: 100%;
text-align: center;
margin-bottom: 20px;
}}
.profile-image {{
width: 60mm;
height: 60mm;
border-radius: 5px;
object-fit: cover;
display: block;
margin: 0 auto;
}}
.right-column {{
flex: 1;
padding: 20px;
background-color: white;
box-sizing: border-box;
}}
h1 {{
font-size: calc({base_font_size} * 3);
margin: 0 0 5px 0;
color: {main_color};
font-family: 'Playfair Display', Garamond, Times, serif;
}}
h2 {{
font-size: calc({base_font_size} * 1.75);
margin: 0 0 20px 0;
color: {main_color};
font-family: 'Playfair Display', Garamond, Times, serif;
}}
h3 {{
font-size: calc({base_font_size} * 1.5);
margin: 15px 0 10px 0;
border-bottom: 2px solid currentColor;
padding-bottom: 5px;
}}
.left-column h3 {{
color: white;
}}
.right-column h3 {{
color: {main_color};
}}
ul {{
list-style-type: none;
padding-left: 0;
margin: 5px 0;
}}
.left-column ul li {{
margin-bottom: 5px;
padding-left: 15px;
position: relative;
}}
.left-column ul li::before {{
content: "•";
position: absolute;
left: 0;
color: white;
font-size: {base_font_size};
}}
.right-column ul li {{
margin-bottom: 5px;
padding-left: 15px;
position: relative;
}}
.right-column ul li::before {{
content: "•";
position: absolute;
left: 0;
color: {main_color};
font-size: calc({base_font_size} * 1.5);
}}
.contact-info {{
margin-bottom: 15px;
}}
.work-experience {{
margin-bottom: 15px;
}}
.work-experience h4 {{
font-size: calc({base_font_size} * 1.25);
margin: 10px 0 5px 0;
color: {main_color};
}}
.timeline-item {{
margin-bottom: 15px;
}}
.date {{
color: #666;
font-size: calc({base_font_size} * 1.125);
}}
.references {{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
font-size: calc({base_font_size} * 1.125);
}}
.reference {{
margin-bottom: 10px;
}}
p {{
margin: 5px 0;
}}
''')
current_datetime = datetime.now().strftime('%Y%m%d_%H%M')
output_filename = f"output_cv_{current_datetime}.pdf"
output_dir = os.path.abspath('outputs')
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, output_filename)
HTML(string=rendered_html, base_url=os.getcwd()).write_pdf(
output_path,
stylesheets=[css]
)
return output_path
def generate_cv(markdown_file, cover_image, main_color, secondary_color, base_font_size):
"""
Gradio interface function to generate CV
Args:
markdown_file (file): Markdown input file for CV content
cover_image (file): Cover image for the CV
main_color (str): Main color for the CV theme
secondary_color (str): Secondary color for the left border
base_font_size (str): Base font size for the CV
Returns:
str: Path to the generated PDF
"""
try:
size = base_font_size.replace('pt', '').strip()
float(size)
base_font_size = f"{size}pt"
except ValueError:
base_font_size = "8pt"
with open(markdown_file.name, 'r', encoding='utf-8') as file:
md_content = file.read()
generator = CVGenerator()
pdf_path = generator.generate_pdf(
md_content,
cover_image.name if cover_image else None,
main_color,
secondary_color,
base_font_size
)
return pdf_path
def create_gradio_app():
with gr.Blocks() as demo:
gr.Markdown("![App-Logo](https://www.abhishekpaliwal.fi/images/abhishek-logo-200px.png)")
gr.Markdown("# 1-Page PDF CV Generator (Create your designer CV today)")
gr.Markdown("**Step 1:** Download this markdown plain-text [CV template](https://huggingface.co/spaces/pandared/1-page-pdf-cv-maker/resolve/main/examples/example_cv.txt) and edit it.")
gr.Markdown("**Step 2:** Upload your edited markdown CV data (as .md or .txt) and cover image to generate a professional PDF.")
gr.Markdown("- If needed, create your circular profile image online: <https://crop-circle.imageonline.co/>.")
gr.Markdown("- SOME EXAMPLES are provided at the end of this page. Try them first.")
gr.Markdown("- FOR HELP, contact me from this [Google Form.](https://docs.google.com/forms/d/e/1FAIpQLSfEcSszdpyCiGa76K5wwvuPM5gblbdPlRzHOU0nRm73l3NtEw/viewform)")
with gr.Row():
markdown_input = gr.File(
file_types=['.md', '.txt'],
label="Upload Markdown CV Data"
)
cover_image = gr.File(
file_types=['image'],
label="Upload Cover Image"
)
with gr.Row():
main_color = gr.ColorPicker(
label="Main Color Theme",
value="#1B3A4B"
)
secondary_color = gr.ColorPicker(
label="Secondary Color (Left Border)",
value="#EF3D37"
)
base_font_size = gr.Textbox(
label="Base Font Size (in pt)",
value="8",
placeholder="Enter a number (e.g., 8)"
)
generate_btn = gr.Button("Generate CV PDF")
output_pdf = gr.File(label="Generated CV PDF")
# Add example inputs
example_markdown = "examples/example_cv.txt"
example_image_circle = "examples/example_image_circle.png"
example_image_square = "examples/example_image_square.png"
gr.Examples(
examples=[
[example_markdown, example_image_circle, "#108dc7", "#ef8e38", "8"],
[example_markdown, example_image_circle, "#FC5C7D", "#6A82FB", "8.5"],
[example_markdown, example_image_square, "#23074d", "#cc5333", "9"],
],
inputs=[markdown_input, cover_image, main_color, secondary_color, base_font_size],
label="Try These Examples"
)
generate_btn.click(
fn=generate_cv,
inputs=[markdown_input, cover_image, main_color, secondary_color, base_font_size],
outputs=[output_pdf]
)
return demo
if __name__ == "__main__":
app = create_gradio_app()
app.launch(server_name="0.0.0.0", server_port=7860)