Spaces:
Sleeping
Sleeping
import streamlit as st | |
from pathlib import Path | |
import base64 | |
import datetime | |
import markdown2 | |
from weasyprint import HTML, CSS | |
# --- Configuration & Setup --- | |
# Define the layouts based on the specification. | |
# The 'size' key uses CSS-compatible dimensions. | |
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": "πΌοΈ"}, | |
} | |
# Directory to save the generated PDFs | |
OUTPUT_DIR = Path("generated_pdfs") | |
OUTPUT_DIR.mkdir(exist_ok=True) | |
# --- Helper Functions --- | |
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") | |
# Display Source Markdown files | |
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) | |
# Display Generated PDF files | |
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"]) | |
# Basic styling for the PDF | |
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: | |
# For aspect ratio, we fix width and calculate height. This is an approximation. | |
# A more robust solution might require more complex CSS. | |
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}") | |
# --- Streamlit App UI --- | |
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.") | |
# Create a sample markdown file if none exists | |
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!") | |
# Use st.rerun() to immediately refresh the file explorer | |
st.rerun() | |
# Display the file explorer section | |
display_file_explorer() | |