awacke1 commited on
Commit
137bf65
ยท
verified ยท
1 Parent(s): 5ba8d4b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -34
app.py CHANGED
@@ -3,8 +3,11 @@ import os
3
  import re
4
  import glob
5
  import textwrap
 
 
6
  from datetime import datetime
7
  from pathlib import Path
 
8
 
9
  import streamlit as st
10
  import pandas as pd
@@ -12,6 +15,8 @@ from PIL import Image
12
  from reportlab.pdfgen import canvas
13
  from reportlab.lib.pagesizes import letter
14
  from reportlab.lib.utils import ImageReader
 
 
15
  import mistune
16
  from gtts import gTTS
17
 
@@ -40,7 +45,7 @@ def get_text_input(file_uploader_label, accepted_types, text_area_label):
40
  md_text = uploaded_file.getvalue().decode("utf-8")
41
  stem = Path(uploaded_file.name).stem
42
  else:
43
- md_text = st.text_area(text_area_label, height=200)
44
 
45
  # Convert markdown to plain text for processing
46
  renderer = mistune.HTMLRenderer()
@@ -75,15 +80,30 @@ def generate_pdf(text_content, images, pdf_params):
75
  Generates a PDF buffer from text and a list of images based on specified parameters.
76
  """
77
  buf = io.BytesIO()
 
 
 
 
 
 
 
 
 
78
  c = canvas.Canvas(buf)
79
  page_w, page_h = letter
80
  margin = 40
81
  gutter = 20
82
  col_w = (page_w - 2 * margin - (pdf_params['columns'] - 1) * gutter) / pdf_params['columns']
83
-
84
- c.setFont(pdf_params['font_family'], pdf_params['font_size'])
 
 
 
85
  line_height = pdf_params['font_size'] * 1.2
86
- wrap_width = int(col_w / (pdf_params['font_size'] * 0.6))
 
 
 
87
 
88
  y = page_h - margin
89
  col_idx = 0
@@ -96,7 +116,7 @@ def generate_pdf(text_content, images, pdf_params):
96
  col_idx += 1
97
  if col_idx >= pdf_params['columns']:
98
  c.showPage()
99
- c.setFont(pdf_params['font_family'], pdf_params['font_size'])
100
  col_idx = 0
101
  y = page_h - margin
102
 
@@ -108,13 +128,15 @@ def generate_pdf(text_content, images, pdf_params):
108
  # --- Render Images ---
109
  for img_file in images:
110
  try:
 
111
  img = Image.open(img_file)
112
  w, h = img.size
113
  c.showPage()
114
  c.setPageSize((w, h))
115
  c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=True, mask='auto')
116
  except Exception as e:
117
- st.warning(f"Could not process image {img_file.name}: {e}")
 
118
  continue
119
 
120
  c.save()
@@ -132,24 +154,73 @@ def show_asset_manager():
132
  return
133
 
134
  for asset_path in assets:
135
- ext = asset_path.split('.')[-1].lower()
 
 
 
 
136
  cols = st.columns([3, 1, 1])
137
- cols[0].write(asset_path)
138
 
139
  try:
140
  with open(asset_path, 'rb') as fp:
141
  file_bytes = fp.read()
142
 
143
- if ext == 'pdf':
144
  cols[1].download_button("๐Ÿ“ฅ", data=file_bytes, file_name=asset_path, mime="application/pdf", key=f"dl_{asset_path}")
145
- elif ext == 'mp3':
146
  cols[1].audio(file_bytes)
 
 
147
  except Exception as e:
148
- cols[1].error("Error reading file.")
149
 
150
  cols[2].button("๐Ÿ—‘๏ธ", key=f"del_{asset_path}", on_click=delete_asset, args=(asset_path,))
151
 
152
- # ๏ฟฝ Renders the entire UI and logic for the Python code interpreter.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  def render_code_interpreter():
154
  """Sets up the UI and execution logic for the code interpreter tab."""
155
  st.header("๐Ÿงช Python Code Executor & Demo")
@@ -161,18 +232,70 @@ def render_code_interpreter():
161
  def execute_code(code_str):
162
  output_buffer = io.StringIO()
163
  try:
 
 
 
 
 
 
 
 
 
164
  with redirect_stdout(output_buffer):
165
- exec(code_str, {})
166
  return output_buffer.getvalue(), None
167
  except Exception as e:
168
  return None, str(e)
169
 
170
  # --- Main Logic for the Tab ---
171
- DEFAULT_CODE = "import streamlit as st\n\nst.balloons()\nst.write('Hello, World!')"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  if 'code' not in st.session_state:
173
  st.session_state.code = DEFAULT_CODE
174
 
175
- uploaded_file = st.file_uploader("Upload .py or .md", type=['py', 'md'])
176
  if uploaded_file:
177
  file_content = uploaded_file.getvalue().decode()
178
  if uploaded_file.type == 'text/markdown':
@@ -180,19 +303,20 @@ def render_code_interpreter():
180
  st.session_state.code = codes[0] if codes else ''
181
  else:
182
  st.session_state.code = file_content
183
- st.code(st.session_state.code, language='python')
184
- else:
185
- st.session_state.code = st.text_area("๐Ÿ’ป Code Editor", value=st.session_state.code, height=300)
186
 
187
  c1, c2 = st.columns(2)
188
  if c1.button("โ–ถ๏ธ Run Code", use_container_width=True):
189
  output, err = execute_code(st.session_state.code)
 
190
  if err:
191
  st.error(err)
192
- elif output:
193
  st.code(output, language='text')
194
- else:
195
- st.success("Executed successfully with no output.")
 
196
  if c2.button("๐Ÿ—‘๏ธ Clear Code", use_container_width=True):
197
  st.session_state.code = ''
198
  st.rerun()
@@ -209,10 +333,18 @@ def main():
209
 
210
  # --- Sidebar Controls ---
211
  st.sidebar.title("PDF Settings")
 
 
 
 
 
 
 
212
  pdf_params = {
213
  'columns': st.sidebar.slider("Text columns", 1, 3, 1),
214
- 'font_family': st.sidebar.selectbox("Font", ["Helvetica", "Times-Roman", "Courier"]),
215
- 'font_size': st.sidebar.slider("Font size", 6, 24, 12),
 
216
  }
217
 
218
  # --- Main UI ---
@@ -238,28 +370,30 @@ def main():
238
  df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(uploaded_images)])
239
  edited_df = st.data_editor(df_imgs, use_container_width=True, key="img_order_editor")
240
 
241
- # Create a map for quick lookup
242
  image_map = {f.name: f for f in uploaded_images}
243
 
244
- # Sort and append images based on the edited order
245
  for _, row in edited_df.sort_values("order").iterrows():
246
  if row['name'] in image_map:
247
  ordered_images.append(image_map[row['name']])
248
 
249
  st.subheader("๐Ÿ–‹๏ธ PDF Generation")
250
- if st.button("Generate PDF with Markdown & Images"):
251
- pdf_buffer = generate_pdf(plain_text, ordered_images, pdf_params)
252
- st.download_button(
253
- "โฌ‡๏ธ Download PDF",
254
- data=pdf_buffer,
255
- file_name=f"{filename_stem}.pdf",
256
- mime="application/pdf"
257
- )
 
 
 
258
 
259
  show_asset_manager()
 
260
 
261
  with tab2:
262
  render_code_interpreter()
263
 
264
  if __name__ == "__main__":
265
- main()
 
3
  import re
4
  import glob
5
  import textwrap
6
+ import base64
7
+ import sys
8
  from datetime import datetime
9
  from pathlib import Path
10
+ from contextlib import redirect_stdout
11
 
12
  import streamlit as st
13
  import pandas as pd
 
15
  from reportlab.pdfgen import canvas
16
  from reportlab.lib.pagesizes import letter
17
  from reportlab.lib.utils import ImageReader
18
+ from reportlab.pdfbase import pdfmetrics
19
+ from reportlab.pdfbase.ttfonts import TTFont
20
  import mistune
21
  from gtts import gTTS
22
 
 
45
  md_text = uploaded_file.getvalue().decode("utf-8")
46
  stem = Path(uploaded_file.name).stem
47
  else:
48
+ md_text = st.text_area(text_area_label, height=200, value="## Your Markdown Here\n\nEnter your markdown text, or upload a file.")
49
 
50
  # Convert markdown to plain text for processing
51
  renderer = mistune.HTMLRenderer()
 
80
  Generates a PDF buffer from text and a list of images based on specified parameters.
81
  """
82
  buf = io.BytesIO()
83
+
84
+ # --- Register Custom Fonts ---
85
+ for font_path in pdf_params.get('ttf_files', []):
86
+ try:
87
+ font_name = Path(font_path).stem
88
+ pdfmetrics.registerFont(TTFont(font_name, font_path))
89
+ except Exception as e:
90
+ st.warning(f"Could not register font {font_path}: {e}")
91
+
92
  c = canvas.Canvas(buf)
93
  page_w, page_h = letter
94
  margin = 40
95
  gutter = 20
96
  col_w = (page_w - 2 * margin - (pdf_params['columns'] - 1) * gutter) / pdf_params['columns']
97
+
98
+ # Use registered font name, which is the stem of the file path
99
+ font_name_to_use = Path(pdf_params['font_family']).stem if ".ttf" in pdf_params['font_family'] else pdf_params['font_family']
100
+ c.setFont(font_name_to_use, pdf_params['font_size'])
101
+
102
  line_height = pdf_params['font_size'] * 1.2
103
+ # Estimate characters per line for wrapping
104
+ # 0.6 is a common factor for average character width vs font size
105
+ wrap_width = int(col_w / (pdf_params['font_size'] * 0.6)) if pdf_params['font_size'] > 0 else 50
106
+
107
 
108
  y = page_h - margin
109
  col_idx = 0
 
116
  col_idx += 1
117
  if col_idx >= pdf_params['columns']:
118
  c.showPage()
119
+ c.setFont(font_name_to_use, pdf_params['font_size'])
120
  col_idx = 0
121
  y = page_h - margin
122
 
 
128
  # --- Render Images ---
129
  for img_file in images:
130
  try:
131
+ # Handle both file paths and uploaded file objects
132
  img = Image.open(img_file)
133
  w, h = img.size
134
  c.showPage()
135
  c.setPageSize((w, h))
136
  c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=True, mask='auto')
137
  except Exception as e:
138
+ img_name = img_file.name if hasattr(img_file, 'name') else img_file
139
+ st.warning(f"Could not process image {img_name}: {e}")
140
  continue
141
 
142
  c.save()
 
154
  return
155
 
156
  for asset_path in assets:
157
+ # Avoid showing the script itself
158
+ if asset_path.endswith('.py'):
159
+ continue
160
+
161
+ ext = Path(asset_path).suffix.lower()
162
  cols = st.columns([3, 1, 1])
163
+ cols[0].write(f"`{asset_path}`")
164
 
165
  try:
166
  with open(asset_path, 'rb') as fp:
167
  file_bytes = fp.read()
168
 
169
+ if ext == '.pdf':
170
  cols[1].download_button("๐Ÿ“ฅ", data=file_bytes, file_name=asset_path, mime="application/pdf", key=f"dl_{asset_path}")
171
+ elif ext == '.mp3':
172
  cols[1].audio(file_bytes)
173
+ elif ext in ['.png', '.jpg', '.jpeg']:
174
+ cols[1].image(file_bytes, width=60)
175
  except Exception as e:
176
+ cols[1].error("Error")
177
 
178
  cols[2].button("๐Ÿ—‘๏ธ", key=f"del_{asset_path}", on_click=delete_asset, args=(asset_path,))
179
 
180
+ # ๐Ÿงฉ Shows a demo of how to use the functions with file lists
181
+ def show_batch_processing_demo(pdf_params):
182
+ """Finds local files and shows how to process them."""
183
+ st.markdown("---")
184
+ st.subheader("๐Ÿงฉ Batch Processing Demo")
185
+ st.info("This section demonstrates how you could call the PDF generation function programmatically with lists of files.")
186
+
187
+ md_files = glob.glob("*.md")
188
+ img_files = glob.glob("*.png") + glob.glob("*.jpg")
189
+
190
+ if not md_files or not img_files:
191
+ st.warning("To run the demo, please ensure there is at least one `.md` file and one image (`.png`/`.jpg`) in the directory.")
192
+ return
193
+
194
+ st.write("Found the following files to use for the demo:")
195
+ st.write(f"**Markdown file:** `{md_files[0]}`")
196
+ st.write(f"**Image files:** `{', '.join(img_files)}`")
197
+
198
+ if st.button("๐Ÿงช Run Demo with Above Files"):
199
+ md_file_str = md_files[0]
200
+ img_files_str = ",".join(img_files)
201
+
202
+ # --- Example of programmatic execution ---
203
+ # 1. Read the markdown file
204
+ with open(md_file_str, 'r') as f:
205
+ text_content = f.read()
206
+
207
+ # 2. Open the image files (generate_pdf expects file-like objects or paths)
208
+ image_objects = img_files # Pass paths directly
209
+
210
+ # 3. Call the generator function
211
+ pdf_buffer = generate_pdf(text_content, image_objects, pdf_params)
212
+
213
+ # 4. Provide for download
214
+ st.download_button(
215
+ "โฌ‡๏ธ Download Batch Demo PDF",
216
+ data=pdf_buffer,
217
+ file_name="batch_demo_output.pdf",
218
+ mime="application/pdf"
219
+ )
220
+ st.success("Batch demo PDF generated!")
221
+
222
+
223
+ # ๐Ÿ Renders the entire UI and logic for the Python code interpreter.
224
  def render_code_interpreter():
225
  """Sets up the UI and execution logic for the code interpreter tab."""
226
  st.header("๐Ÿงช Python Code Executor & Demo")
 
232
  def execute_code(code_str):
233
  output_buffer = io.StringIO()
234
  try:
235
+ # The exec function will have access to globally imported libraries
236
+ exec_globals = {
237
+ "st": st,
238
+ "glob": glob,
239
+ "base64": base64,
240
+ "io": io,
241
+ "canvas": canvas,
242
+ "letter": letter
243
+ }
244
  with redirect_stdout(output_buffer):
245
+ exec(code_str, exec_globals)
246
  return output_buffer.getvalue(), None
247
  except Exception as e:
248
  return None, str(e)
249
 
250
  # --- Main Logic for the Tab ---
251
+ DEFAULT_CODE = """
252
+ import streamlit as st
253
+ import glob
254
+ import base64
255
+ import io
256
+ from reportlab.pdfgen import canvas
257
+ from reportlab.lib.pagesizes import letter
258
+
259
+ st.title("๐Ÿ“Š Enhanced Demo App")
260
+ st.markdown("This demo shows file galleries and base64 PDF downloads.")
261
+
262
+ # --- Image Gallery ---
263
+ with st.expander("๐Ÿ–ผ๏ธ Show Image Files in Directory"):
264
+ image_files = glob.glob("*.png") + glob.glob("*.jpg")
265
+ if not image_files:
266
+ st.write("No image files found.")
267
+ else:
268
+ st.image(image_files)
269
+
270
+ # --- PDF Gallery ---
271
+ with st.expander("๐Ÿ“„ Show PDF Files in Directory"):
272
+ pdf_files = glob.glob("*.pdf")
273
+ if not pdf_files:
274
+ st.write("No PDF files found.")
275
+ else:
276
+ st.write(pdf_files)
277
+
278
+ # --- PDF Generation and Download ---
279
+ if st.button("Generate Demo PDF & Download Link"):
280
+ # 1. Create PDF in memory
281
+ buffer = io.BytesIO()
282
+ p = canvas.Canvas(buffer, pagesize=letter)
283
+ p.drawString(100, 750, "This is a demo PDF generated from the code interpreter.")
284
+ p.showPage()
285
+ p.save()
286
+
287
+ # 2. Encode PDF to Base64
288
+ b64 = base64.b64encode(buffer.getvalue()).decode()
289
+
290
+ # 3. Create download link
291
+ href = f'<a href="data:application/pdf;base64,{b64}" download="demo_from_code.pdf">Download Generated PDF</a>'
292
+ st.markdown(href, unsafe_allow_html=True)
293
+ st.success("PDF generated! Click the link above to download.")
294
+ """
295
  if 'code' not in st.session_state:
296
  st.session_state.code = DEFAULT_CODE
297
 
298
+ uploaded_file = st.file_uploader("Upload .py or .md", type=['py', 'md'], key="code_uploader")
299
  if uploaded_file:
300
  file_content = uploaded_file.getvalue().decode()
301
  if uploaded_file.type == 'text/markdown':
 
303
  st.session_state.code = codes[0] if codes else ''
304
  else:
305
  st.session_state.code = file_content
306
+
307
+ st.session_state.code = st.text_area("๐Ÿ’ป Code Editor", value=st.session_state.code, height=400)
 
308
 
309
  c1, c2 = st.columns(2)
310
  if c1.button("โ–ถ๏ธ Run Code", use_container_width=True):
311
  output, err = execute_code(st.session_state.code)
312
+ st.subheader("Output")
313
  if err:
314
  st.error(err)
315
+ if output: # Show output even if empty, to confirm it ran
316
  st.code(output, language='text')
317
+ if not err:
318
+ st.success("Executed successfully.")
319
+
320
  if c2.button("๐Ÿ—‘๏ธ Clear Code", use_container_width=True):
321
  st.session_state.code = ''
322
  st.rerun()
 
333
 
334
  # --- Sidebar Controls ---
335
  st.sidebar.title("PDF Settings")
336
+
337
+ # --- Dynamic Font Loading ---
338
+ ttf_files = glob.glob("*.ttf")
339
+ standard_fonts = ["Helvetica", "Times-Roman", "Courier"]
340
+ available_fonts = ttf_files + standard_fonts
341
+ default_font_index = 0 if ttf_files else 0 # Default to first ttf or Helvetica
342
+
343
  pdf_params = {
344
  'columns': st.sidebar.slider("Text columns", 1, 3, 1),
345
+ 'font_family': st.sidebar.selectbox("Font", available_fonts, index=default_font_index),
346
+ 'font_size': st.sidebar.slider("Font size", 6, 48, 12),
347
+ 'ttf_files': ttf_files
348
  }
349
 
350
  # --- Main UI ---
 
370
  df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(uploaded_images)])
371
  edited_df = st.data_editor(df_imgs, use_container_width=True, key="img_order_editor")
372
 
 
373
  image_map = {f.name: f for f in uploaded_images}
374
 
 
375
  for _, row in edited_df.sort_values("order").iterrows():
376
  if row['name'] in image_map:
377
  ordered_images.append(image_map[row['name']])
378
 
379
  st.subheader("๐Ÿ–‹๏ธ PDF Generation")
380
+ if st.button("Generate PDF from UI"):
381
+ if not plain_text.strip() and not ordered_images:
382
+ st.warning("Please provide some text or images to generate a PDF.")
383
+ else:
384
+ pdf_buffer = generate_pdf(plain_text, ordered_images, pdf_params)
385
+ st.download_button(
386
+ "โฌ‡๏ธ Download PDF",
387
+ data=pdf_buffer,
388
+ file_name=f"{filename_stem}.pdf",
389
+ mime="application/pdf"
390
+ )
391
 
392
  show_asset_manager()
393
+ show_batch_processing_demo(pdf_params)
394
 
395
  with tab2:
396
  render_code_interpreter()
397
 
398
  if __name__ == "__main__":
399
+ main()