awacke1 commited on
Commit
c902df7
·
verified ·
1 Parent(s): ec4d47e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -297
app.py CHANGED
@@ -1,13 +1,10 @@
1
- import os
 
2
  import io
3
  import re
4
  import streamlit as st
5
-
6
- st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
7
-
8
  from PIL import Image
9
  import fitz
10
-
11
  from reportlab.lib.pagesizes import A4
12
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
13
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@@ -15,58 +12,190 @@ from reportlab.lib import colors
15
  from reportlab.pdfbase import pdfmetrics
16
  from reportlab.pdfbase.ttfonts import TTFont
17
 
18
- available_fonts = {
19
- "NotoEmoji Variable": "NotoEmoji-VariableFont_wght.ttf",
20
- "NotoEmoji Bold": "NotoEmoji-Bold.ttf",
21
- "NotoEmoji Light": "NotoEmoji-Light.ttf",
22
- "NotoEmoji Medium": "NotoEmoji-Medium.ttf",
23
- "NotoEmoji Regular": "NotoEmoji-Regular.ttf",
24
- "NotoEmoji SemiBold": "NotoEmoji-SemiBold.ttf"
25
- }
26
-
27
- with st.sidebar:
28
- selected_font_name = st.selectbox(
29
- "Select NotoEmoji Font",
30
- options=list(available_fonts.keys())
31
- )
32
- selected_font_path = available_fonts[selected_font_name]
33
-
34
- auto_size = st.checkbox("Auto-size text", value=True)
35
- base_font_size = st.slider("Base Font Size (points)", min_value=6, max_value=16, value=10, step=1)
36
- if auto_size:
37
- st.info("Font size will adjust between 6-16 points based on content length, starting from your base size.")
38
-
39
- show_subitems = st.checkbox("Show Sub-items (e.g., 1.1, 2.1.3)", value=True)
40
- plain_text_mode = st.checkbox("Render as Plain Text (Preserve Bold Only)", value=False)
41
-
42
- num_columns = st.radio(
43
- "Number of Columns",
44
- options=[1, 2, 3, 4, 5, 6],
45
- index=3
46
- )
47
-
48
- pdfmetrics.registerFont(TTFont(selected_font_name, selected_font_path))
49
-
50
- def apply_emoji_font(text, emoji_font):
51
- emoji_pattern = re.compile(
52
- r"([\U0001F300-\U0001F5FF"
53
- r"\U0001F600-\U0001F64F"
54
- r"\U0001F680-\U0001F6FF"
55
- r"\U0001F700-\U0001F77F"
56
- r"\U0001F780-\U0001F7FF"
57
- r"\U0001F800-\U0001F8FF"
58
- r"\U0001F900-\U0001F9FF"
59
- r"\U0001FA00-\U0001FA6F"
60
- r"\U0001FA70-\U0001FAFF"
61
- r"\u2600-\u26FF"
62
- r"\u2700-\u27BF]+)"
63
- )
64
- def replace_emoji(match):
65
- emoji = match.group(1)
66
- if len(emoji) > 1:
67
- emoji = emoji[0]
68
- return f'<font face="{emoji_font}">{emoji}</font>'
69
- return emoji_pattern.sub(replace_emoji, text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  default_markdown = """# 🌟 Deities Guide: Mythology and Moral Lessons 🌟
72
 
@@ -226,243 +355,5 @@ default_markdown = """# 🌟 Deities Guide: Mythology and Moral Lessons 🌟
226
  3. Saints/Prophets: Virtues (e.g., justice, prophecy).
227
  """
228
 
229
- def markdown_to_pdf_content(markdown_text, show_subitems=True, plain_text_mode=False, font_size=10, num_columns=4):
230
- lines = markdown_text.strip().split('\n')
231
- pdf_content = []
232
-
233
- if plain_text_mode:
234
- for line in lines:
235
- line = line.strip()
236
- if not line:
237
- continue
238
- if line.startswith('# '):
239
- continue
240
- bold_pattern = re.compile(r'\*\*(.*?)\*\*')
241
- line = bold_pattern.sub(r'<b>\1</b>', line)
242
- pdf_content.append(line)
243
- else:
244
- in_list = False
245
- current_item = None
246
- sub_items = []
247
-
248
- for line in lines:
249
- line = line.strip()
250
- if not line:
251
- continue
252
-
253
- if line.startswith('# '):
254
- continue
255
- elif line.startswith('## '):
256
- if current_item and sub_items:
257
- pdf_content.append([current_item, sub_items])
258
- sub_items = []
259
- current_item = None
260
- section = line.replace('## ', '').strip()
261
- pdf_content.append(f"<b>{section}</b>")
262
- in_list = False
263
- elif line.startswith('### '):
264
- if current_item and sub_items:
265
- pdf_content.append([current_item, sub_items])
266
- sub_items = []
267
- current_item = None
268
- if show_subitems:
269
- subsection = line.replace('### ', '').strip()
270
- pdf_content.append(f"<b>{subsection}</b>")
271
- in_list = False
272
- elif re.match(r'^\d+\.', line):
273
- if current_item and sub_items:
274
- pdf_content.append([current_item, sub_items])
275
- sub_items = []
276
- current_item = line.strip()
277
- in_list = True
278
- elif re.match(r'^\d+\.\d+\.', line):
279
- if show_subitems:
280
- sub_items.append(line.strip())
281
- elif line.startswith('*') and not in_list:
282
- pdf_content.append(line.strip())
283
- else:
284
- if in_list:
285
- sub_items.append(line.strip())
286
- else:
287
- pdf_content.append(line.strip())
288
-
289
- if current_item and sub_items:
290
- pdf_content.append([current_item, sub_items])
291
-
292
- total_lines = sum(1 + len(subs) if isinstance(item, list) else 1 for item in pdf_content)
293
- total_chars = sum(len(item) if isinstance(item, str) else len(item[0]) + sum(len(sub) for sub in item[1]) for item in pdf_content)
294
- avg_line_length = total_chars / total_lines if total_lines > 0 else 1
295
-
296
- columns = [[] for _ in range(num_columns)]
297
- lines_per_column = total_lines / num_columns
298
- current_line_count = 0
299
- current_column = 0
300
-
301
- for item in pdf_content:
302
- item_lines = 1 + (len(item[1]) if isinstance(item, list) else 0)
303
- if current_line_count >= lines_per_column and current_column < num_columns - 1:
304
- current_column += 1
305
- current_line_count = 0
306
- columns[current_column].append(item)
307
- current_line_count += item_lines
308
-
309
- return columns, total_lines, num_columns
310
-
311
- def create_main_pdf(markdown_text, base_font_size=10, auto_size=False, show_subitems=True, plain_text_mode=False, num_columns=4):
312
- buffer = io.BytesIO()
313
- doc = SimpleDocTemplate(
314
- buffer,
315
- pagesize=(A4[1] * 2, A4[0]),
316
- leftMargin=36,
317
- rightMargin=36,
318
- topMargin=36,
319
- bottomMargin=36
320
- )
321
-
322
- styles = getSampleStyleSheet()
323
- story = []
324
- spacer_height = 10
325
- columns, total_lines, num_columns = markdown_to_pdf_content(markdown_text, show_subitems, plain_text_mode, base_font_size, num_columns)
326
-
327
- if auto_size:
328
- base_font_size = max(6, min(16, base_font_size * 300 / total_lines))
329
-
330
- item_font_size = base_font_size
331
- subitem_font_size = base_font_size * 0.85
332
- section_font_size = base_font_size * 1.1
333
- title_font_size = min(20, base_font_size * 1.4)
334
-
335
- title_style = ParagraphStyle(
336
- 'Heading1',
337
- parent=styles['Heading1'],
338
- fontName="Helvetica-Bold",
339
- textColor=colors.darkblue,
340
- alignment=1,
341
- fontSize=title_font_size,
342
- leading=title_font_size * 1.2
343
- )
344
-
345
- section_style = ParagraphStyle(
346
- 'SectionStyle',
347
- parent=styles['Heading2'],
348
- fontName="Helvetica-Bold",
349
- textColor=colors.darkblue,
350
- fontSize=section_font_size,
351
- leading=section_font_size * 1.2,
352
- spaceAfter=2
353
- )
354
-
355
- item_style = ParagraphStyle(
356
- 'ItemStyle',
357
- parent=styles['Normal'],
358
- fontName="Helvetica",
359
- fontSize=item_font_size,
360
- leading=item_font_size * 1.15,
361
- spaceAfter=1
362
- )
363
-
364
- subitem_style = ParagraphStyle(
365
- 'SubItemStyle',
366
- parent=styles['Normal'],
367
- fontName="Helvetica",
368
- fontSize=subitem_font_size,
369
- leading=subitem_font_size * 1.15,
370
- leftIndent=8,
371
- spaceAfter=1
372
- )
373
-
374
- story.append(Paragraph(apply_emoji_font("Deities Guide: Mythology and Moral Lessons", selected_font_name), title_style))
375
- story.append(Spacer(1, spacer_height))
376
-
377
- column_cells = [[] for _ in range(num_columns)]
378
- for col_idx, column in enumerate(columns):
379
- for item in column:
380
- if plain_text_mode:
381
- column_cells[col_idx].append(Paragraph(apply_emoji_font(item, selected_font_name), item_style))
382
- else:
383
- if isinstance(item, str) and item.startswith('<b>'):
384
- text = item.replace('<b>', '').replace('</b>', '')
385
- column_cells[col_idx].append(Paragraph(apply_emoji_font(text, selected_font_name), section_style))
386
- elif isinstance(item, list):
387
- main_item, sub_items = item
388
- column_cells[col_idx].append(Paragraph(apply_emoji_font(main_item, selected_font_name), item_style))
389
- for sub_item in sub_items:
390
- column_cells[col_idx].append(Paragraph(apply_emoji_font(sub_item, selected_font_name), subitem_style))
391
- else:
392
- column_cells[col_idx].append(Paragraph(apply_emoji_font(item, selected_font_name), item_style))
393
-
394
- max_cells = max(len(cells) for cells in column_cells)
395
- for cells in column_cells:
396
- cells.extend([Paragraph("", item_style)] * (max_cells - len(cells)))
397
-
398
- col_width = (A4[1] * 2 - 72) / num_columns
399
- table_data = list(zip(*column_cells))
400
- table = Table(table_data, colWidths=[col_width] * num_columns, hAlign='CENTER')
401
- table.setStyle(TableStyle([
402
- ('VALIGN', (0, 0), (-1, -1), 'TOP'),
403
- ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
404
- ('BACKGROUND', (0, 0), (-1, -1), colors.white),
405
- ('GRID', (0, 0), (-1, -1), 0, colors.white),
406
- ('LINEAFTER', (0, 0), (num_columns-1, -1), 0.5, colors.grey),
407
- ('LEFTPADDING', (0, 0), (-1, -1), 2),
408
- ('RIGHTPADDING', (0, 0), (-1, -1), 2),
409
- ('TOPPADDING', (0, 0), (-1, -1), 1),
410
- ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
411
- ]))
412
-
413
- story.append(table)
414
- doc.build(story)
415
- buffer.seek(0)
416
- return buffer.getvalue()
417
-
418
- def pdf_to_image(pdf_bytes):
419
- try:
420
- doc = fitz.open(stream=pdf_bytes, filetype="pdf")
421
- images = []
422
- for page in doc:
423
- pix = page.get_pixmap(matrix=fitz.Matrix(2.0, 2.0))
424
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
425
- images.append(img)
426
- doc.close()
427
- return images
428
- except Exception as e:
429
- st.error(f"Failed to render PDF preview: {e}")
430
- return None
431
-
432
- if 'markdown_content' not in st.session_state:
433
- st.session_state.markdown_content = default_markdown
434
-
435
- with st.spinner("Generating PDF..."):
436
- pdf_bytes = create_main_pdf(st.session_state.markdown_content, base_font_size, auto_size, show_subitems, plain_text_mode, num_columns)
437
-
438
- with st.container():
439
- pdf_images = pdf_to_image(pdf_bytes)
440
- if pdf_images:
441
- for i, img in enumerate(pdf_images):
442
- st.image(img, caption=f"Multi-Column Spread", use_container_width=True)
443
- else:
444
- st.info("Download the PDF to view it locally.")
445
-
446
- st.download_button(
447
- label="Download PDF",
448
- data=pdf_bytes,
449
- file_name="deities_guide_multi_column.pdf",
450
- mime="application/pdf"
451
- )
452
-
453
- edited_markdown = st.text_area(
454
- "Modify the markdown content below:",
455
- value=st.session_state.markdown_content,
456
- height=300
457
- )
458
-
459
- if st.button("Update PDF"):
460
- st.session_state.markdown_content = edited_markdown
461
- st.rerun()
462
-
463
- st.download_button(
464
- label="Save Markdown",
465
- data=st.session_state.markdown_content,
466
- file_name="deities_guide.md",
467
- mime="text/markdown"
468
- )
 
1
+
2
+
3
  import io
4
  import re
5
  import streamlit as st
 
 
 
6
  from PIL import Image
7
  import fitz
 
8
  from reportlab.lib.pagesizes import A4
9
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
10
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
 
12
  from reportlab.pdfbase import pdfmetrics
13
  from reportlab.pdfbase.ttfonts import TTFont
14
 
15
+ st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
16
+
17
+ def create_pdf_tab(default_markdown):
18
+ # Font setup
19
+ available_fonts = {
20
+ "NotoEmoji Variable": "NotoEmoji-VariableFont_wght.ttf",
21
+ "NotoEmoji Bold": "NotoEmoji-Bold.ttf",
22
+ "NotoEmoji Light": "NotoEmoji-Light.ttf",
23
+ "NotoEmoji Medium": "NotoEmoji-Medium.ttf",
24
+ "NotoEmoji Regular": "NotoEmoji-Regular.ttf",
25
+ "NotoEmoji SemiBold": "NotoEmoji-SemiBold.ttf"
26
+ }
27
+
28
+ # Sidebar configuration
29
+ with st.sidebar:
30
+ selected_font_name = st.selectbox("Select NotoEmoji Font", options=list(available_fonts.keys()))
31
+ selected_font_path = available_fonts[selected_font_name]
32
+ auto_size = st.checkbox("Auto-size text", value=True)
33
+ base_font_size = st.slider("Base Font Size (points)", min_value=6, max_value=16, value=10, step=1)
34
+ if auto_size:
35
+ st.info("Font size will adjust between 6-16 points based on content length, starting from your base size.")
36
+ plain_text_mode = st.checkbox("Render as Plain Text (Preserve Bold Only)", value=False)
37
+ num_columns = st.selectbox("Number of Columns", options=[1, 2, 3, 4, 5, 6], index=0) # Default to 1 column
38
+
39
+ # Register font
40
+ pdfmetrics.registerFont(TTFont(selected_font_name, selected_font_path))
41
+
42
+ # Emoji font application
43
+ def apply_emoji_font(text, emoji_font):
44
+ emoji_pattern = re.compile(
45
+ r"([\U0001F300-\U0001F5FF"
46
+ r"\U0001F600-\U0001F64F"
47
+ r"\U0001F680-\U0001F6FF"
48
+ r"\U0001F700-\U0001F77F"
49
+ r"\U0001F780-\U0001F7FF"
50
+ r"\U0001F800-\U0001F8FF"
51
+ r"\U0001F900-\U0001F9FF"
52
+ r"\U0001FA00-\U0001FA6F"
53
+ r"\U0001FA70-\U0001FAFF"
54
+ r"\u2600-\u26FF"
55
+ r"\u2700-\u27BF]+)"
56
+ )
57
+ def replace_emoji(match):
58
+ emoji = match.group(1)
59
+ if len(emoji) > 1:
60
+ emoji = emoji[0]
61
+ return f'<font face="{emoji_font}">{emoji}</font>'
62
+ return emoji_pattern.sub(replace_emoji, text)
63
+
64
+ # Markdown to PDF content
65
+ def markdown_to_pdf_content(markdown_text, plain_text_mode, font_size, num_columns):
66
+ lines = markdown_text.strip().split('\n')
67
+ pdf_content = []
68
+
69
+ if plain_text_mode:
70
+ for line in lines:
71
+ line = line.strip()
72
+ if not line or line.startswith('# '):
73
+ continue
74
+ bold_pattern = re.compile(r'\*\*(.*?)\*\*')
75
+ line = bold_pattern.sub(r'<b>\1</b>', line)
76
+ pdf_content.append(line)
77
+ else:
78
+ for line in lines:
79
+ line = line.strip()
80
+ if not line or line.startswith('# '):
81
+ continue
82
+ if line.startswith('## ') or line.startswith('### '):
83
+ text = line.replace('## ', '').replace('### ', '').strip()
84
+ pdf_content.append(f"<b>{text}</b>")
85
+ else:
86
+ pdf_content.append(line.strip())
87
+
88
+ total_lines = len(pdf_content)
89
+ columns = [[] for _ in range(num_columns)]
90
+ lines_per_column = total_lines / num_columns if num_columns > 0 else total_lines
91
+ current_line_count = 0
92
+ current_column = 0
93
+
94
+ for item in pdf_content:
95
+ if current_line_count >= lines_per_column and current_column < num_columns - 1:
96
+ current_column += 1
97
+ current_line_count = 0
98
+ columns[current_column].append(item)
99
+ current_line_count += 1
100
+
101
+ return columns, total_lines
102
+
103
+ # Create PDF
104
+ def create_pdf(markdown_text, base_font_size, auto_size, plain_text_mode, num_columns):
105
+ buffer = io.BytesIO()
106
+ doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=36, rightMargin=36, topMargin=36, bottomMargin=36)
107
+ styles = getSampleStyleSheet()
108
+ story = []
109
+ spacer_height = 10
110
+ columns, total_lines = markdown_to_pdf_content(markdown_text, plain_text_mode, base_font_size, num_columns)
111
+
112
+ if auto_size and total_lines > 0:
113
+ base_font_size = max(6, min(16, base_font_size * 300 / total_lines))
114
+
115
+ item_font_size = base_font_size
116
+ section_font_size = base_font_size * 1.1
117
+
118
+ section_style = ParagraphStyle(
119
+ 'SectionStyle', parent=styles['Heading2'], fontName="Helvetica-Bold",
120
+ textColor=colors.darkblue, fontSize=section_font_size, leading=section_font_size * 1.2, spaceAfter=2
121
+ )
122
+ item_style = ParagraphStyle(
123
+ 'ItemStyle', parent=styles['Normal'], fontName="Helvetica",
124
+ fontSize=item_font_size, leading=item_font_size * 1.15, spaceAfter=1
125
+ )
126
+
127
+ story.append(Spacer(1, spacer_height))
128
+ column_cells = [[] for _ in range(num_columns)]
129
+ for col_idx, column in enumerate(columns):
130
+ for item in column:
131
+ if isinstance(item, str) and item.startswith('<b>'):
132
+ text = item.replace('<b>', '').replace('</b>', '')
133
+ column_cells[col_idx].append(Paragraph(apply_emoji_font(text, selected_font_name), section_style))
134
+ else:
135
+ column_cells[col_idx].append(Paragraph(apply_emoji_font(item, selected_font_name), item_style))
136
+
137
+ max_cells = max(len(cells) for cells in column_cells) if column_cells else 0
138
+ for cells in column_cells:
139
+ cells.extend([Paragraph("", item_style)] * (max_cells - len(cells)))
140
+
141
+ col_width = (A4[0] - 72) / num_columns if num_columns > 0 else A4[0] - 72
142
+ table_data = list(zip(*column_cells)) if column_cells else [[]]
143
+ table = Table(table_data, colWidths=[col_width] * num_columns, hAlign='CENTER')
144
+ table.setStyle(TableStyle([
145
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
146
+ ('BACKGROUND', (0, 0), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 0, colors.white),
147
+ ('LINEAFTER', (0, 0), (num_columns-1, -1), 0.5, colors.grey),
148
+ ('LEFTPADDING', (0, 0), (-1, -1), 2), ('RIGHTPADDING', (0, 0), (-1, -1), 2),
149
+ ('TOPPADDING', (0, 0), (-1, -1), 1), ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
150
+ ]))
151
+
152
+ story.append(table)
153
+ doc.build(story)
154
+ buffer.seek(0)
155
+ return buffer.getvalue()
156
+
157
+ # PDF to image
158
+ def pdf_to_image(pdf_bytes):
159
+ try:
160
+ doc = fitz.open(stream=pdf_bytes, filetype="pdf")
161
+ images = []
162
+ for page in doc:
163
+ pix = page.get_pixmap(matrix=fitz.Matrix(2.0, 2.0))
164
+ img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
165
+ images.append(img)
166
+ doc.close()
167
+ return images
168
+ except Exception as e:
169
+ st.error(f"Failed to render PDF preview: {e}")
170
+ return None
171
+
172
+ # Main logic
173
+ if 'markdown_content' not in st.session_state:
174
+ st.session_state.markdown_content = default_markdown
175
+
176
+ with st.spinner("Generating PDF..."):
177
+ pdf_bytes = create_pdf(st.session_state.markdown_content, base_font_size, auto_size, plain_text_mode, num_columns)
178
+
179
+ with st.container():
180
+ pdf_images = pdf_to_image(pdf_bytes)
181
+ if pdf_images:
182
+ for img in pdf_images:
183
+ st.image(img, use_container_width=True)
184
+ else:
185
+ st.info("Download the PDF to view it locally.")
186
+
187
+ st.download_button(label="Download PDF", data=pdf_bytes, file_name="deities_guide.pdf", mime="application/pdf")
188
+
189
+ edited_markdown = st.text_area("Modify the markdown content below:", value=st.session_state.markdown_content, height=300)
190
+ if st.button("Update PDF"):
191
+ st.session_state.markdown_content = edited_markdown
192
+ st.rerun()
193
+
194
+ st.download_button(label="Save Markdown", data=st.session_state.markdown_content, file_name="deities_guide.md", mime="text/markdown")
195
+
196
+
197
+
198
+
199
 
200
  default_markdown = """# 🌟 Deities Guide: Mythology and Moral Lessons 🌟
201
 
 
355
  3. Saints/Prophets: Virtues (e.g., justice, prophecy).
356
  """
357
 
358
+
359
+ create_pdf_tab(default_markdown)