|
import streamlit as st |
|
from pathlib import Path |
|
import base64 |
|
import datetime |
|
import markdown2 |
|
from weasyprint import HTML, CSS |
|
|
|
|
|
|
|
|
|
|
|
LAYOUTS = { |
|
"A4 Portrait": {"size": "210mm 297mm", "icon": "π"}, |
|
"A4 Landscape": {"size": "297mm 210mm", "icon": "π"}, |
|
"Letter Portrait": {"size": "8.5in 11in", "icon": "π"}, |
|
"Letter Landscape": {"size": "11in 8.5in", "icon": "π"}, |
|
"Wide 16:9": {"aspect_ratio": "16/9", "icon": "πΊ"}, |
|
"Vertical 9:16": {"aspect_ratio": "9/16", "icon": "π±"}, |
|
"Square 1:1": {"aspect_ratio": "1/1", "icon": "πΌοΈ"}, |
|
} |
|
|
|
|
|
OUTPUT_DIR = Path("generated_pdfs") |
|
OUTPUT_DIR.mkdir(exist_ok=True) |
|
|
|
|
|
|
|
def get_file_download_link(file_path: Path) -> str: |
|
"""Generates a base64-encoded download link for a file.""" |
|
with open(file_path, "rb") as f: |
|
data = base64.b64encode(f.read()).decode() |
|
return f'<a href="data:application/octet-stream;base64,{data}" download="{file_path.name}">Download</a>' |
|
|
|
def display_file_explorer(): |
|
"""Renders a simple file explorer in the Streamlit app.""" |
|
st.header("π File Explorer") |
|
|
|
|
|
st.subheader("Source Markdown Files (.md)") |
|
md_files = list(Path(".").glob("*.md")) |
|
if not md_files: |
|
st.info("No Markdown files found in the current directory. Create a `.md` file to begin.") |
|
else: |
|
for md_file in md_files: |
|
col1, col2 = st.columns([0.8, 0.2]) |
|
with col1: |
|
st.write(f"π `{md_file.name}`") |
|
with col2: |
|
st.markdown(get_file_download_link(md_file), unsafe_allow_html=True) |
|
|
|
|
|
st.subheader("Generated PDF Files") |
|
pdf_files = sorted(list(OUTPUT_DIR.glob("*.pdf")), reverse=True) |
|
if not pdf_files: |
|
st.info("No PDFs generated yet. Click the button above.") |
|
else: |
|
for pdf_file in pdf_files: |
|
col1, col2 = st.columns([0.8, 0.2]) |
|
with col1: |
|
st.write(f"π `{pdf_file.name}`") |
|
with col2: |
|
st.markdown(get_file_download_link(pdf_file), unsafe_allow_html=True) |
|
|
|
def generate_pdf_from_markdown(md_path: Path): |
|
""" |
|
Reads a markdown file and generates PDFs for all defined layouts. |
|
""" |
|
try: |
|
md_content = md_path.read_text(encoding="utf-8") |
|
html_content = markdown2.markdown(md_content, extras=["tables", "fenced-code-blocks", "cuddled-lists"]) |
|
|
|
|
|
base_css = """ |
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'); |
|
body { font-family: 'Inter', sans-serif; line-height: 1.6; } |
|
h1, h2, h3 { font-weight: 700; } |
|
code { |
|
background-color: #f0f0f0; |
|
padding: 2px 4px; |
|
border-radius: 3px; |
|
font-family: monospace; |
|
} |
|
pre { background-color: #f0f0f0; padding: 1em; border-radius: 5px; overflow: auto; } |
|
table { border-collapse: collapse; width: 100%; } |
|
th, td { border: 1px solid #ddd; padding: 8px; } |
|
th { background-color: #f2f2f2; } |
|
""" |
|
|
|
date_str = datetime.datetime.now().strftime("%Y-%m-%d") |
|
|
|
for name, properties in LAYOUTS.items(): |
|
st.write(f" - Generating `{name}` format...") |
|
|
|
page_css = f"@page {{ size: {properties.get('size', 'A4')}; margin: 2cm; }}" |
|
if 'aspect_ratio' in properties: |
|
|
|
|
|
page_css = f"@page {{ size: 210mm calc(210mm * {properties['aspect_ratio']}); margin: 1cm; }}" |
|
|
|
final_css = CSS(string=base_css + page_css) |
|
|
|
output_filename = f"{md_path.stem}_{name.replace(' ', '-')}_{date_str}.pdf" |
|
output_path = OUTPUT_DIR / output_filename |
|
|
|
HTML(string=html_content).write_pdf(output_path, stylesheets=[final_css]) |
|
|
|
except Exception as e: |
|
st.error(f"Failed to process {md_path.name}: {e}") |
|
|
|
|
|
|
|
|
|
st.set_page_config(layout="wide", page_title="PDF Generator") |
|
|
|
st.title("π Markdown to PDF Generator") |
|
st.markdown("This tool finds all `.md` files in this directory, converts them to PDF in various layouts, and provides download links.") |
|
|
|
|
|
if not list(Path(".").glob("*.md")): |
|
with open("sample.md", "w", encoding="utf-8") as f: |
|
f.write("# Sample Document\n\n") |
|
f.write("This is a sample markdown file created for you. You can edit this file or add your own `.md` files to this directory.\n\n") |
|
f.write("- Item 1\n- Item 2\n\n") |
|
f.write("`code snippet`\n\n") |
|
f.write("Click the button below to start the PDF generation process.") |
|
st.rerun() |
|
|
|
|
|
if st.button("π Generate PDFs from all Markdown Files", type="primary"): |
|
markdown_files = list(Path(".").glob("*.md")) |
|
|
|
if not markdown_files: |
|
st.warning("No `.md` files found. Please add a markdown file to the directory.") |
|
else: |
|
with st.spinner("Generating PDFs... This may take a moment."): |
|
progress_bar = st.progress(0) |
|
total_steps = len(markdown_files) |
|
|
|
for i, md_file in enumerate(markdown_files): |
|
st.info(f"Processing: **{md_file.name}**") |
|
generate_pdf_from_markdown(md_file) |
|
progress_bar.progress((i + 1) / total_steps) |
|
|
|
st.success("β
PDF generation complete!") |
|
|
|
st.rerun() |
|
|
|
|
|
display_file_explorer() |
|
|