Create app.py.backupSunEve.py
Browse files- app.py.backupSunEve.py +368 -0
app.py.backupSunEve.py
ADDED
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
For this app take current version below and add the ability to generate it not only for each page layout but also every font to paragraph lead caharcter mappings with smart mappings to process markdown bold and markdown outlines and outline characters and alignement including tables and outlines to special font and layout instructions that mimic the markdown beauty with unicode reflect with maximally the right combinations of ofontsso first read the ttf file and determine which fonts then use those dynamically and have output sample of each ready for user: import streamlit as st
|
2 |
+
|
3 |
+
from pathlib import Path
|
4 |
+
|
5 |
+
import base64
|
6 |
+
|
7 |
+
import datetime
|
8 |
+
|
9 |
+
import re
|
10 |
+
|
11 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
|
12 |
+
|
13 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
14 |
+
|
15 |
+
from reportlab.lib.pagesizes import letter, A4, legal, landscape
|
16 |
+
|
17 |
+
from reportlab.lib.units import inch
|
18 |
+
|
19 |
+
from reportlab.lib import colors
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
# --- Configuration & Setup ---
|
24 |
+
|
25 |
+
|
26 |
+
|
27 |
+
# Define layouts using reportlab's pagesizes
|
28 |
+
|
29 |
+
# The 'size' key now holds a tuple (width, height)
|
30 |
+
|
31 |
+
LAYOUTS = {
|
32 |
+
|
33 |
+
"A4 Portrait": {"size": A4, "icon": "📄"},
|
34 |
+
|
35 |
+
"A4 Landscape": {"size": landscape(A4), "icon": "📄"},
|
36 |
+
|
37 |
+
"Letter Portrait": {"size": letter, "icon": "📄"},
|
38 |
+
|
39 |
+
"Letter Landscape": {"size": landscape(letter), "icon": "📄"},
|
40 |
+
|
41 |
+
"Legal Portrait": {"size": legal, "icon": "📄"},
|
42 |
+
|
43 |
+
"Legal Landscape": {"size": landscape(legal), "icon": "📄"},
|
44 |
+
|
45 |
+
}
|
46 |
+
|
47 |
+
|
48 |
+
|
49 |
+
# Directory to save the generated PDFs
|
50 |
+
|
51 |
+
OUTPUT_DIR = Path("generated_pdfs")
|
52 |
+
|
53 |
+
OUTPUT_DIR.mkdir(exist_ok=True)
|
54 |
+
|
55 |
+
|
56 |
+
|
57 |
+
# --- ReportLab PDF Generation ---
|
58 |
+
|
59 |
+
|
60 |
+
|
61 |
+
def markdown_to_story(markdown_text: str):
|
62 |
+
|
63 |
+
"""Converts a markdown string into a list of ReportLab Flowables (a 'story')."""
|
64 |
+
|
65 |
+
styles = getSampleStyleSheet()
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
# Define custom styles
|
70 |
+
|
71 |
+
style_normal = styles['BodyText']
|
72 |
+
|
73 |
+
style_h1 = styles['h1']
|
74 |
+
|
75 |
+
style_h2 = styles['h2']
|
76 |
+
|
77 |
+
style_h3 = styles['h3']
|
78 |
+
|
79 |
+
style_code = styles['Code']
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
# A simple regex-based parser for markdown
|
84 |
+
|
85 |
+
story = []
|
86 |
+
|
87 |
+
lines = markdown_text.split('\n')
|
88 |
+
|
89 |
+
|
90 |
+
|
91 |
+
in_code_block = False
|
92 |
+
|
93 |
+
code_block_text = ""
|
94 |
+
|
95 |
+
|
96 |
+
|
97 |
+
for line in lines:
|
98 |
+
|
99 |
+
if line.strip().startswith("```"):
|
100 |
+
|
101 |
+
if in_code_block:
|
102 |
+
|
103 |
+
story.append(Paragraph(code_block_text.replace('\n', '<br/>'), style_code))
|
104 |
+
|
105 |
+
in_code_block = False
|
106 |
+
|
107 |
+
code_block_text = ""
|
108 |
+
|
109 |
+
else:
|
110 |
+
|
111 |
+
in_code_block = True
|
112 |
+
|
113 |
+
continue
|
114 |
+
|
115 |
+
|
116 |
+
|
117 |
+
if in_code_block:
|
118 |
+
|
119 |
+
# Escape HTML tags for code blocks
|
120 |
+
|
121 |
+
escaped_line = line.replace('&', '&').replace('<', '<').replace('>', '>')
|
122 |
+
|
123 |
+
code_block_text += escaped_line + '\n'
|
124 |
+
|
125 |
+
continue
|
126 |
+
|
127 |
+
|
128 |
+
|
129 |
+
if line.startswith("# "):
|
130 |
+
|
131 |
+
story.append(Paragraph(line[2:], style_h1))
|
132 |
+
|
133 |
+
elif line.startswith("## "):
|
134 |
+
|
135 |
+
story.append(Paragraph(line[3:], style_h2))
|
136 |
+
|
137 |
+
elif line.startswith("### "):
|
138 |
+
|
139 |
+
story.append(Paragraph(line[4:], style_h3))
|
140 |
+
|
141 |
+
elif line.strip().startswith(("* ", "- ")):
|
142 |
+
|
143 |
+
# Handle bullet points
|
144 |
+
|
145 |
+
story.append(Paragraph(f"• {line.strip()[2:]}", style_normal, bulletText='•'))
|
146 |
+
|
147 |
+
elif re.match(r'^\d+\.\s', line.strip()):
|
148 |
+
|
149 |
+
# Handle numbered lists
|
150 |
+
|
151 |
+
story.append(Paragraph(line.strip(), style_normal))
|
152 |
+
|
153 |
+
elif line.strip() == "":
|
154 |
+
|
155 |
+
story.append(Spacer(1, 0.2 * inch))
|
156 |
+
|
157 |
+
else:
|
158 |
+
|
159 |
+
# Handle bold and italics
|
160 |
+
|
161 |
+
line = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line)
|
162 |
+
|
163 |
+
line = re.sub(r'_(.*?)_', r'<i>\1</i>', line)
|
164 |
+
|
165 |
+
story.append(Paragraph(line, style_normal))
|
166 |
+
|
167 |
+
|
168 |
+
|
169 |
+
return story
|
170 |
+
|
171 |
+
|
172 |
+
|
173 |
+
def create_pdf_with_reportlab(md_path: Path, layout_name: str, layout_properties: dict):
|
174 |
+
|
175 |
+
"""Creates a PDF for a given markdown file and layout."""
|
176 |
+
|
177 |
+
try:
|
178 |
+
|
179 |
+
md_content = md_path.read_text(encoding="utf-8")
|
180 |
+
|
181 |
+
|
182 |
+
|
183 |
+
date_str = datetime.datetime.now().strftime("%Y-%m-%d")
|
184 |
+
|
185 |
+
output_filename = f"{md_path.stem}_{layout_name.replace(' ', '-')}_{date_str}.pdf"
|
186 |
+
|
187 |
+
output_path = OUTPUT_DIR / output_filename
|
188 |
+
|
189 |
+
|
190 |
+
|
191 |
+
doc = SimpleDocTemplate(
|
192 |
+
|
193 |
+
str(output_path),
|
194 |
+
|
195 |
+
pagesize=layout_properties.get("size", A4),
|
196 |
+
|
197 |
+
rightMargin=inch,
|
198 |
+
|
199 |
+
leftMargin=inch,
|
200 |
+
|
201 |
+
topMargin=inch,
|
202 |
+
|
203 |
+
bottomMargin=inch
|
204 |
+
|
205 |
+
)
|
206 |
+
|
207 |
+
|
208 |
+
|
209 |
+
story = markdown_to_story(md_content)
|
210 |
+
|
211 |
+
|
212 |
+
|
213 |
+
doc.build(story)
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
except Exception as e:
|
218 |
+
|
219 |
+
�� st.error(f"Failed to process {md_path.name} with ReportLab: {e}")
|
220 |
+
|
221 |
+
|
222 |
+
|
223 |
+
|
224 |
+
|
225 |
+
# --- Streamlit UI and File Handling (Mostly Unchanged) ---
|
226 |
+
|
227 |
+
|
228 |
+
|
229 |
+
def get_file_download_link(file_path: Path) -> str:
|
230 |
+
|
231 |
+
"""Generates a base64-encoded download link for a file."""
|
232 |
+
|
233 |
+
with open(file_path, "rb") as f:
|
234 |
+
|
235 |
+
data = base64.b64encode(f.read()).decode()
|
236 |
+
|
237 |
+
return f'<a href="data:application/octet-stream;base64,{data}" download="{file_path.name}">Download</a>'
|
238 |
+
|
239 |
+
|
240 |
+
|
241 |
+
def display_file_explorer():
|
242 |
+
|
243 |
+
"""Renders a simple file explorer in the Streamlit app."""
|
244 |
+
|
245 |
+
st.header("📂 File Explorer")
|
246 |
+
|
247 |
+
|
248 |
+
|
249 |
+
st.subheader("Source Markdown Files (.md)")
|
250 |
+
|
251 |
+
md_files = list(Path(".").glob("*.md"))
|
252 |
+
|
253 |
+
if not md_files:
|
254 |
+
|
255 |
+
st.info("No Markdown files found. Create a `.md` file to begin.")
|
256 |
+
|
257 |
+
else:
|
258 |
+
|
259 |
+
for md_file in md_files:
|
260 |
+
|
261 |
+
col1, col2 = st.columns([0.8, 0.2])
|
262 |
+
|
263 |
+
with col1:
|
264 |
+
|
265 |
+
st.write(f"📝 `{md_file.name}`")
|
266 |
+
|
267 |
+
with col2:
|
268 |
+
|
269 |
+
st.markdown(get_file_download_link(md_file), unsafe_allow_html=True)
|
270 |
+
|
271 |
+
|
272 |
+
|
273 |
+
st.subheader("Generated PDF Files")
|
274 |
+
|
275 |
+
pdf_files = sorted(list(OUTPUT_DIR.glob("*.pdf")), key=lambda p: p.stat().st_mtime, reverse=True)
|
276 |
+
|
277 |
+
if not pdf_files:
|
278 |
+
|
279 |
+
st.info("No PDFs generated yet. Click the button above.")
|
280 |
+
|
281 |
+
else:
|
282 |
+
|
283 |
+
for pdf_file in pdf_files:
|
284 |
+
|
285 |
+
col1, col2 = st.columns([0.8, 0.2])
|
286 |
+
|
287 |
+
with col1:
|
288 |
+
|
289 |
+
st.write(f"📄 `{pdf_file.name}`")
|
290 |
+
|
291 |
+
with col2:
|
292 |
+
|
293 |
+
st.markdown(get_file_download_link(pdf_file), unsafe_allow_html=True)
|
294 |
+
|
295 |
+
|
296 |
+
|
297 |
+
|
298 |
+
|
299 |
+
# --- Main App ---
|
300 |
+
|
301 |
+
|
302 |
+
|
303 |
+
st.set_page_config(layout="wide", page_title="PDF Generator")
|
304 |
+
|
305 |
+
|
306 |
+
|
307 |
+
st.title("📄 Markdown to PDF Generator (ReportLab Engine)")
|
308 |
+
|
309 |
+
st.markdown("This tool finds all `.md` files in this directory, converts them to PDF in various layouts, and provides download links. It uses the `ReportLab` library and requires no external dependencies.")
|
310 |
+
|
311 |
+
|
312 |
+
|
313 |
+
if not list(Path(".").glob("*.md")):
|
314 |
+
|
315 |
+
with open("sample.md", "w", encoding="utf-8") as f:
|
316 |
+
|
317 |
+
f.write("# Sample Document\n\nThis is a sample markdown file. **ReportLab** is now creating the PDF.\n\n### Features\n- Item 1\n- Item 2\n\n1. Numbered item\n2. Another one\n\n```\ndef hello():\n print(\"Hello, PDF!\")\n```\n")
|
318 |
+
|
319 |
+
st.rerun()
|
320 |
+
|
321 |
+
|
322 |
+
|
323 |
+
if st.button("🚀 Generate PDFs from all Markdown Files", type="primary"):
|
324 |
+
|
325 |
+
markdown_files = list(Path(".").glob("*.md"))
|
326 |
+
|
327 |
+
|
328 |
+
|
329 |
+
if not markdown_files:
|
330 |
+
|
331 |
+
st.warning("No `.md` files found. Please add a markdown file to the directory.")
|
332 |
+
|
333 |
+
else:
|
334 |
+
|
335 |
+
total_pdfs = len(markdown_files) * len(LAYOUTS)
|
336 |
+
|
337 |
+
progress_bar = st.progress(0)
|
338 |
+
|
339 |
+
pdf_count = 0
|
340 |
+
|
341 |
+
|
342 |
+
|
343 |
+
with st.spinner("Generating PDFs using ReportLab..."):
|
344 |
+
|
345 |
+
for md_file in markdown_files:
|
346 |
+
|
347 |
+
st.info(f"Processing: **{md_file.name}**")
|
348 |
+
|
349 |
+
for name, properties in LAYOUTS.items():
|
350 |
+
|
351 |
+
st.write(f" - Generating `{name}` format...")
|
352 |
+
|
353 |
+
create_pdf_with_reportlab(md_file, name, properties)
|
354 |
+
|
355 |
+
pdf_count += 1
|
356 |
+
|
357 |
+
progress_bar.progress(pdf_count / total_pdfs)
|
358 |
+
|
359 |
+
|
360 |
+
|
361 |
+
st.success("✅ PDF generation complete!")
|
362 |
+
|
363 |
+
st.rerun()
|
364 |
+
|
365 |
+
|
366 |
+
|
367 |
+
display_file_explorer()
|
368 |
+
|