awacke1 commited on
Commit
dc245b1
·
verified ·
1 Parent(s): 3c73f1c

Delete backup11.app.py

Browse files
Files changed (1) hide show
  1. backup11.app.py +0 -651
backup11.app.py DELETED
@@ -1,651 +0,0 @@
1
- import io
2
- import re
3
- import os
4
- import glob
5
- import asyncio
6
- import hashlib
7
- import unicodedata
8
- import streamlit as st
9
- from PIL import Image
10
- import fitz
11
- import edge_tts
12
- from reportlab.lib.pagesizes import A4
13
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
14
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
15
- from reportlab.lib import colors
16
- from reportlab.pdfbase import pdfmetrics
17
- from reportlab.pdfbase.ttfonts import TTFont
18
- from reportlab.pdfgen import canvas
19
- from datetime import datetime
20
- import pytz
21
- from pypdf import PdfReader, PdfWriter
22
- from pypdf.annotations import Link
23
- from pypdf.generic import Fit
24
-
25
- st.set_page_config(layout="wide", initial_sidebar_state="expanded")
26
-
27
- # Existing functions (unchanged)
28
- def get_timestamp_prefix():
29
- central = pytz.timezone("US/Central")
30
- now = datetime.now(central)
31
- return now.strftime("%a %m%d %I%M%p").upper()
32
-
33
- def clean_for_speech(text):
34
- text = text.replace("#", "")
35
- emoji_pattern = re.compile(
36
- r"[\U0001F300-\U0001F5FF"
37
- r"\U0001F600-\U0001F64F"
38
- r"\U0001F680-\U0001F6FF"
39
- r"\U0001F700-\U0001F77F"
40
- r"\U0001F780-\U0001F7FF"
41
- r"\U0001F800-\U0001F8FF"
42
- r"\U0001F900-\U0001F9FF"
43
- r"\U0001FA00-\U0001FA6F"
44
- r"\U0001FA70-\U0001FAFF"
45
- r"\u2600-\u26FF"
46
- r"\u2700-\u27BF]+", flags=re.UNICODE)
47
- text = emoji_pattern.sub('', text)
48
- return text
49
-
50
- def trim_emojis_except_numbered(markdown_text):
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"\U0001FAD0-\U0001FAD9"
60
- r"\U0001FA00-\U0001FA6F"
61
- r"\U0001FA70-\U0001FAFF"
62
- r"\u2600-\u26FF"
63
- r"\u2700-\u27BF]+"
64
- )
65
- number_pattern = re.compile(r'^\d+\.\s')
66
- lines = markdown_text.strip().split('\n')
67
- processed_lines = []
68
-
69
- for line in lines:
70
- if number_pattern.match(line):
71
- processed_lines.append(line)
72
- else:
73
- processed_lines.append(emoji_pattern.sub('', line))
74
-
75
- return '\n'.join(processed_lines)
76
-
77
- async def generate_audio(text, voice, filename):
78
- communicate = edge_tts.Communicate(text, voice)
79
- await communicate.save(filename)
80
- return filename
81
-
82
- def detect_and_convert_links(text):
83
- # Convert Markdown links [text](url) to HTML <a> tags
84
- md_link_pattern = re.compile(r'\[(.*?)\]\((https?://[^\s\[\]()<>{}]+)\)')
85
- text = md_link_pattern.sub(r'<a href="\2" color="blue">\1</a>', text)
86
-
87
- # Convert plain URLs to HTML <a> tags, avoiding already tagged links
88
- url_pattern = re.compile(
89
- r'(?<!href=")(https?://[^\s<>{}]+)',
90
- re.IGNORECASE
91
- )
92
- text = url_pattern.sub(r'<a href="\1" color="blue">\1</a>', text)
93
- return text
94
-
95
- def apply_emoji_font(text, emoji_font):
96
- # Protect existing tags
97
- tag_pattern = re.compile(r'(<[^>]+>)')
98
- segments = tag_pattern.split(text)
99
- result = []
100
-
101
- # Apply emoji font only to emojis, use DejaVuSans for other text
102
- emoji_pattern = re.compile(
103
- r"([\U0001F300-\U0001F5FF"
104
- r"\U0001F600-\U0001F64F"
105
- r"\U0001F680-\U0001F6FF"
106
- r"\U0001F700-\U0001F77F"
107
- r"\U0001F780-\U0001F7FF"
108
- r"\U0001F800-\U0001F8FF"
109
- r"\U0001F900-\U0001F9FF"
110
- r"\U0001FAD0-\U0001FAD9"
111
- r"\U0001FA00-\U0001FA6F"
112
- r"\U0001FA70-\U0001FAFF"
113
- r"\u2600-\u26FF"
114
- r"\u2700-\u27BF]+)"
115
- )
116
-
117
- def replace_emoji(match):
118
- emoji = match.group(1)
119
- emoji = unicodedata.normalize('NFC', emoji)
120
- return f'<font face="{emoji_font}">{emoji}</font>'
121
-
122
- for segment in segments:
123
- if tag_pattern.match(segment):
124
- # Keep tags unchanged
125
- result.append(segment)
126
- else:
127
- # Apply DejaVuSans to non-emoji text, emoji_font to emojis
128
- parts = []
129
- last_pos = 0
130
- for match in emoji_pattern.finditer(segment):
131
- start, end = match.span()
132
- if last_pos < start:
133
- parts.append(f'<font face="DejaVuSans">{segment[last_pos:start]}</font>')
134
- parts.append(replace_emoji(match))
135
- last_pos = end
136
- if last_pos < len(segment):
137
- parts.append(f'<font face="DejaVuSans">{segment[last_pos:]}</font>')
138
- result.append(''.join(parts))
139
-
140
- return ''.join(result)
141
-
142
- def markdown_to_pdf_content(markdown_text, add_space_before_numbered, headings_to_fonts):
143
- lines = markdown_text.strip().split('\n')
144
- pdf_content = []
145
- number_pattern = re.compile(r'^\d+(\.\d+)*\.\s')
146
- heading_pattern = re.compile(r'^(#{1,4})\s+(.+)$')
147
- first_numbered_seen = False
148
-
149
- for line in lines:
150
- line = line.strip()
151
- if not line:
152
- continue
153
-
154
- if headings_to_fonts and line.startswith('#'):
155
- heading_match = heading_pattern.match(line)
156
- if heading_match:
157
- level = len(heading_match.group(1))
158
- heading_text = heading_match.group(2).strip()
159
- formatted_heading = f"<h{level}>{heading_text}</h{level}>"
160
- pdf_content.append(formatted_heading)
161
- continue
162
-
163
- is_numbered_line = number_pattern.match(line) is not None
164
-
165
- if add_space_before_numbered and is_numbered_line:
166
- if first_numbered_seen and not line.startswith("1."):
167
- pdf_content.append("")
168
- if not first_numbered_seen:
169
- first_numbered_seen = True
170
-
171
- line = detect_and_convert_links(line)
172
-
173
- # Preserve bold and emphasis formatting
174
- line = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', line)
175
- line = re.sub(r'\*([^*]+?)\*', r'<b>\1</b>', line)
176
-
177
- pdf_content.append(line)
178
- total_lines = len(pdf_content)
179
- return pdf_content, total_lines
180
-
181
- def create_pdf(markdown_text, base_font_size, num_columns, add_space_before_numbered, headings_to_fonts, doc_title, longest_line_words, total_lines):
182
- buffer = io.BytesIO()
183
- page_width = A4[0] * 2
184
- page_height = A4[1]
185
- doc = SimpleDocTemplate(
186
- buffer,
187
- pagesize=(page_width, page_height),
188
- leftMargin=36,
189
- rightMargin=36,
190
- topMargin=36,
191
- bottomMargin=36,
192
- title=doc_title
193
- )
194
- styles = getSampleStyleSheet()
195
- spacer_height = 10
196
- pdf_content, total_lines = markdown_to_pdf_content(markdown_text, add_space_before_numbered, headings_to_fonts)
197
- try:
198
- available_font_files = glob.glob("*.ttf")
199
- if not available_font_files:
200
- st.error("No .ttf font files found.")
201
- return
202
- selected_font_path = next((f for f in available_font_files if "NotoEmoji-Bold" in f), None)
203
- if selected_font_path:
204
- pdfmetrics.registerFont(TTFont("NotoEmoji-Bold", selected_font_path))
205
- pdfmetrics.registerFont(TTFont("DejaVuSans", "DejaVuSans.ttf"))
206
- except Exception as e:
207
- st.error(f"Font registration error: {e}")
208
- return
209
- total_chars = sum(len(line) for line in pdf_content)
210
- hierarchy_weight = sum(1.5 if line.startswith("<b>") else 1 for line in pdf_content)
211
- content_density = total_lines * hierarchy_weight + total_chars / 50
212
- usable_height = page_height - 72 - spacer_height
213
- usable_width = page_width - 72
214
- avg_line_chars = total_chars / total_lines if total_lines > 0 else 50
215
- ideal_lines_per_col = 20
216
- suggested_columns = max(2, min(4, int(total_lines / ideal_lines_per_col) + 1))
217
- num_columns = num_columns if num_columns != 0 else suggested_columns
218
- col_width = usable_width / num_columns
219
- min_font_size = 6
220
- max_font_size = 16
221
- lines_per_col = total_lines / num_columns if num_columns > 0 else total_lines
222
- target_height_per_line = usable_height / lines_per_col if lines_per_col > 0 else usable_height
223
- estimated_font_size = int(target_height_per_line / 1.5)
224
- adjusted_font_size = max(min_font_size, min(max_font_size, estimated_font_size))
225
- if avg_line_chars > col_width / adjusted_font_size * 10:
226
- adjusted_font_size = int(col_width / (avg_line_chars / 10))
227
- adjusted_font_size = max(min_font_size, adjusted_font_size)
228
-
229
- # Adjust font size to fit one page based on longest line
230
- if longest_line_words > 17:
231
- font_scale = 17 / longest_line_words # Scale down from reference (17 words)
232
- adjusted_font_size = max(min_font_size, int(adjusted_font_size * font_scale))
233
-
234
- item_style = ParagraphStyle(
235
- 'ItemStyle', parent=styles['Normal'], fontName="DejaVuSans",
236
- fontSize=adjusted_font_size, leading=adjusted_font_size * 1.15, spaceAfter=1,
237
- linkUnderline=True
238
- )
239
- numbered_bold_style = ParagraphStyle(
240
- 'NumberedBoldStyle', parent=styles['Normal'], fontName="NotoEmoji-Bold",
241
- fontSize=adjusted_font_size, leading=adjusted_font_size * 1.15, spaceAfter=1,
242
- linkUnderline=True
243
- )
244
- section_style = ParagraphStyle(
245
- 'SectionStyle', parent=styles['Heading2'], fontName="DejaVuSans",
246
- textColor=colors.darkblue, fontSize=adjusted_font_size * 1.1, leading=adjusted_font_size * 1.32, spaceAfter=2,
247
- linkUnderline=True
248
- )
249
- columns = [[] for _ in range(num_columns)]
250
- lines_per_column = total_lines / num_columns if num_columns > 0 else total_lines
251
- current_line_count = 0
252
- current_column = 0
253
- number_pattern = re.compile(r'^\d+(\.\d+)*\.\s')
254
- for item in pdf_content:
255
- if current_line_count >= lines_per_column and current_column < num_columns - 1:
256
- current_column += 1
257
- current_line_count = 0
258
- columns[current_column].append(item)
259
- current_line_count += 1
260
- column_cells = [[] for _ in range(num_columns)]
261
- for col_idx, column in enumerate(columns):
262
- for item in column:
263
- if isinstance(item, str):
264
- heading_match = re.match(r'<h(\d)>(.*?)</h\1>', item) if headings_to_fonts else None
265
- if heading_match:
266
- level = int(heading_match.group(1))
267
- heading_text = heading_match.group(2)
268
- heading_style = ParagraphStyle(
269
- f'Heading{level}Style',
270
- parent=styles['Heading1'],
271
- fontName="DejaVuSans",
272
- textColor=colors.darkblue if level == 1 else (colors.black if level > 2 else colors.blue),
273
- fontSize=adjusted_font_size * (1.6 - (level-1)*0.15),
274
- leading=adjusted_font_size * (1.8 - (level-1)*0.15),
275
- spaceAfter=4 - (level-1),
276
- spaceBefore=6 - (level-1),
277
- linkUnderline=True
278
- )
279
- column_cells[col_idx].append(Paragraph(apply_emoji_font(heading_text, "NotoEmoji-Bold"), heading_style))
280
- elif item.startswith("<b>") and item.endswith("</b>"):
281
- content = item[3:-4].strip()
282
- if number_pattern.match(content):
283
- column_cells[col_idx].append(Paragraph(apply_emoji_font(content, "NotoEmoji-Bold"), numbered_bold_style))
284
- else:
285
- column_cells[col_idx].append(Paragraph(apply_emoji_font(content, "NotoEmoji-Bold"), section_style))
286
- else:
287
- column_cells[col_idx].append(Paragraph(apply_emoji_font(item, "NotoEmoji-Bold"), item_style))
288
- else:
289
- column_cells[col_idx].append(Paragraph(apply_emoji_font(str(item), "NotoEmoji-Bold"), item_style))
290
- max_cells = max(len(cells) for cells in column_cells) if column_cells else 0
291
- for cells in column_cells:
292
- cells.extend([Paragraph("", item_style)] * (max_cells - len(cells)))
293
- table_data = list(zip(*column_cells)) if column_cells else [[]]
294
- table = Table(table_data, colWidths=[col_width] * num_columns, hAlign='CENTER')
295
- table.setStyle(TableStyle([
296
- ('VALIGN', (0, 0), (-1, -1), 'TOP'),
297
- ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
298
- ('BACKGROUND', (0, 0), (-1, -1), colors.white),
299
- ('GRID', (0, 0), (-1, -1), 0, colors.white),
300
- ('LINEAFTER', (0, 0), (num_columns-1, -1), 0.5, colors.grey),
301
- ('LEFTPADDING', (0, 0), (-1, -1), 2),
302
- ('RIGHTPADDING', (0, 0), (-1, -1), 2),
303
- ('TOPPADDING', (0, 0), (-1, -1), 1),
304
- ('BOTTOMPADDING', (0, 0), (-1, -1), 1),
305
- ]))
306
- story = [Spacer(1, spacer_height), table]
307
- doc.build(story)
308
- buffer.seek(0)
309
- return buffer.getvalue()
310
-
311
- def pdf_to_image(pdf_bytes):
312
- try:
313
- doc = fitz.open(stream=pdf_bytes, filetype="pdf")
314
- images = []
315
- for page in doc:
316
- pix = page.get_pixmap(matrix=fitz.Matrix(2.0, 2.0))
317
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
318
- images.append(img)
319
- doc.close()
320
- return images
321
- except Exception as e:
322
- st.error(f"Failed to render PDF preview: {e}")
323
- return None
324
-
325
- # PDF creation and linking functions
326
- WORDS_12 = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"]
327
- WORDS_24 = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
328
- "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty", "twenty one", "twenty two", "twenty three", "twenty four"]
329
-
330
- def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pdf"):
331
- """Create two PDFs with cross-file linking."""
332
- def create_base_pdf(filename):
333
- buffer = io.BytesIO()
334
- c = canvas.Canvas(buffer)
335
- c.setFont("Helvetica", 12)
336
- for i, word in enumerate(WORDS_12, 1):
337
- y = 800 - (i * 20)
338
- c.drawString(50, y, f"{i}. {word}")
339
- c.showPage()
340
- c.save()
341
- buffer.seek(0)
342
- with open(filename, "wb") as f:
343
- f.write(buffer.getvalue())
344
- buffer.close()
345
-
346
- def add_bookmark_to_seven(pdf_file):
347
- reader = PdfReader(pdf_file)
348
- writer = PdfWriter()
349
- for page in reader.pages:
350
- writer.add_page(page)
351
- page = writer.pages[0]
352
- y_position = 800 - (7 * 20)
353
- fit = Fit(fit_type="/XYZ", fit_args=[50, y_position, 0])
354
- writer.add_outline_item("Seven Bookmark", 0, fit=fit)
355
- with open(pdf_file, "wb") as f:
356
- writer.write(f)
357
-
358
- def modify_source_pdf(source, target):
359
- reader = PdfReader(source)
360
- writer = PdfWriter()
361
- for page in reader.pages:
362
- writer.add_page(page)
363
- buffer = io.BytesIO()
364
- c = canvas.Canvas(buffer)
365
- c.setFont("Helvetica", 8)
366
- seven_y = 800 - (7 * 20)
367
- c.drawString(90, seven_y - 5, "link")
368
- c.showPage()
369
- c.save()
370
- buffer.seek(0)
371
- text_pdf = PdfReader(buffer)
372
- page = writer.pages[0]
373
- page.merge_page(text_pdf.pages[0])
374
- link = Link(
375
- rect=(90, seven_y - 10, 150, seven_y + 10),
376
- url=f"file://{os.path.abspath(target)}#page=1"
377
- )
378
- writer.add_annotation(page_number=0, annotation=link)
379
- with open(source, "wb") as f:
380
- writer.write(f)
381
- buffer.close()
382
-
383
- def add_internal_link(pdf_file):
384
- reader = PdfReader(pdf_file)
385
- writer = PdfWriter()
386
- for page in reader.pages:
387
- writer.add_page(page)
388
- one_y = 800 - (1 * 20)
389
- ten_y = 800 - (10 * 20)
390
- link = Link(
391
- rect=(50, one_y - 10, 100, one_y + 10),
392
- target_page_index=0,
393
- fit=Fit(fit_type="/XYZ", fit_args=[50, ten_y, 0])
394
- )
395
- writer.add_annotation(page_number=0, annotation=link)
396
- with open(pdf_file, "wb") as f:
397
- writer.write(f)
398
-
399
- create_base_pdf(source_pdf)
400
- create_base_pdf(target_pdf)
401
- add_bookmark_to_seven(target_pdf)
402
- modify_source_pdf(source, target)
403
- add_internal_link(source_pdf)
404
- add_internal_link(target_pdf)
405
- return source_pdf, target_pdf
406
-
407
- def create_selflinking_pdf(pdf_file="SelfLinking.pdf"):
408
- """Create a PDF with a TOC on page 1 linking to a 1-20 list starting on page 2."""
409
- buffer = io.BytesIO()
410
- c = canvas.Canvas(buffer)
411
-
412
- # Page 1: Table of Contents
413
- c.setFont("Helvetica", 14)
414
- c.drawString(50, 800, "Table of Contents")
415
- c.setFont("Helvetica", 12)
416
- toc_y_positions = []
417
- for i, word in enumerate(WORDS_12, 1):
418
- y = 760 - (i * 20)
419
- c.drawString(50, y, f"{word}")
420
- toc_y_positions.append(y)
421
- c.showPage()
422
-
423
- # Page 2: Numbered list 1-20
424
- c.setFont("Helvetica", 12)
425
- list_y_positions = []
426
- for i, word in enumerate(WORDS_24, 1):
427
- y = 800 - (i * 20)
428
- c.drawString(50, y, f"{i}. {word}")
429
- list_y_positions.append(y)
430
- c.showPage()
431
-
432
- # Save the initial PDF
433
- c.save()
434
- buffer.seek(0)
435
- with open(pdf_file, "wb") as f:
436
- f.write(buffer.getvalue())
437
- buffer.close()
438
-
439
- # Add outlines and links
440
- reader = PdfReader(pdf_file)
441
- writer = PdfWriter()
442
- for page in reader.pages:
443
- writer.add_page(page)
444
-
445
- # Add outline entries
446
- toc_page = writer.pages[0]
447
- list_page = writer.pages[1]
448
- writer.add_outline_item("Table of Contents", 0, fit=Fit(fit_type="/Fit"))
449
- for i, word in enumerate(WORDS_12, 1):
450
- y = list_y_positions[i-1]
451
- writer.add_outline_item(word, 1, fit=Fit(fit_type="/XYZ", fit_args=[50, y, 0]))
452
-
453
- # Add TOC links from page 1 to page 2
454
- for i, word in enumerate(WORDS_12):
455
- toc_y = toc_y_positions[i]
456
- list_y = list_y_positions[i]
457
- link = Link(
458
- rect=(50, toc_y - 10, 150, toc_y + 10),
459
- target_page_index=1,
460
- fit=Fit(fit_type="/XYZ", fit_args=[50, list_y, 0])
461
- )
462
- writer.add_annotation(page_number=0, annotation=link)
463
-
464
- # Save the modified PDF
465
- with open(pdf_file, "wb") as f:
466
- writer.write(f)
467
-
468
- return pdf_file
469
-
470
- # Streamlit UI
471
- md_files = [f for f in glob.glob("*.md") if os.path.basename(f) != "README.md"]
472
- md_options = [os.path.splitext(os.path.basename(f))[0] for f in md_files]
473
-
474
- with st.sidebar:
475
- st.markdown("### 📄 PDF Options")
476
- if md_options:
477
- selected_md = st.selectbox("Select Markdown File", options=md_options, index=0)
478
- with open(f"{selected_md}.md", "r", encoding="utf-8") as f:
479
- st.session_state.markdown_content = f.read()
480
- else:
481
- st.warning("No markdown file found. Please add one to your folder.")
482
- selected_md = None
483
- st.session_state.markdown_content = ""
484
-
485
- available_font_files = {os.path.splitext(os.path.basename(f))[0]: f for f in glob.glob("*.ttf")}
486
- selected_font_name = st.selectbox(
487
- "Select Emoji Font",
488
- options=list(available_font_files.keys()),
489
- index=list(available_font_files.keys()).index("NotoEmoji-Bold") if "NotoEmoji-Bold" in available_font_files else 0
490
- )
491
- base_font_size = st.slider("Font Size (points)", min_value=6, max_value=16, value=8, step=1)
492
-
493
- add_space_before_numbered = st.checkbox("Add Space Ahead of Numbered Lines", value=False)
494
- headings_to_fonts = st.checkbox(
495
- "Headings to Fonts",
496
- value=False,
497
- help="Convert Markdown headings (# Heading) to styled fonts"
498
- )
499
- auto_columns = st.checkbox("AutoColumns", value=True)
500
-
501
- # Calculate document stats
502
- longest_line_words = 0
503
- total_lines = 0
504
- adjusted_font_size_display = base_font_size
505
- if 'markdown_content' in st.session_state:
506
- current_markdown = st.session_state.markdown_content
507
- lines = current_markdown.strip().split('\n')
508
- total_lines = len([line for line in lines if line.strip()])
509
- for line in lines:
510
- if line.strip():
511
- word_count = len(line.split())
512
- longest_line_words = max(longest_line_words, word_count)
513
- if auto_columns:
514
- if longest_line_words > 38:
515
- recommended_columns = 2
516
- elif longest_line_words < 18 and total_lines < 20:
517
- recommended_columns = 4
518
- else:
519
- recommended_columns = 3
520
- else:
521
- recommended_columns = 3
522
- # Adjust font size for one-page fit
523
- if longest_line_words > 17:
524
- font_scale = 17 / longest_line_words
525
- adjusted_font_size_display = max(6, int(base_font_size * font_scale))
526
- st.markdown("**Document Stats**")
527
- st.write(f"- Longest Line: {longest_line_words} words")
528
- st.write(f"- Total Lines: {total_lines}")
529
- st.write(f"- Recommended Columns: {recommended_columns}")
530
- st.write(f"- Adjusted Font Size: {adjusted_font_size_display} points")
531
-
532
- column_options = [2, 3, 4]
533
- num_columns = st.selectbox(
534
- "Number of Columns",
535
- options=column_options,
536
- index=column_options.index(recommended_columns)
537
- )
538
- st.info("Font size and columns adjust to fit one page.")
539
-
540
- st.markdown("### ✍️ Edit Markdown")
541
- edited_markdown = st.text_area(
542
- "Input Markdown",
543
- value=st.session_state.markdown_content,
544
- height=200,
545
- key=f"markdown_{selected_md}_{selected_font_name}_{num_columns}"
546
- )
547
-
548
- st.markdown("### 💾 Actions")
549
- col1, col2 = st.columns(2)
550
- with col1:
551
- if st.button("🔄 Update PDF"):
552
- st.session_state.markdown_content = edited_markdown
553
- if selected_md:
554
- with open(f"{selected_md}.md", "w", encoding="utf-8") as f:
555
- f.write(edited_markdown)
556
- st.rerun()
557
-
558
- with col2:
559
- if st.button("✂️ Trim Emojis"):
560
- trimmed_content = trim_emojis_except_numbered(edited_markdown)
561
- st.session_state.markdown_content = trimmed_content
562
- if selected_md:
563
- with open(f"{selected_md}.md", "w", encoding="utf-8") as f:
564
- f.write(trimmed_content)
565
- st.rerun()
566
-
567
- prefix = get_timestamp_prefix()
568
- st.download_button(
569
- label="💾 Save Markdown",
570
- data=st.session_state.markdown_content,
571
- file_name=f"{prefix} {selected_md}.md" if selected_md else f"{prefix} default.md",
572
- mime="text/markdown"
573
- )
574
-
575
- st.markdown("### 🔊 Text-to-Speech")
576
- VOICES = ["en-US-AriaNeural", "en-US-JennyNeural", "en-GB-SoniaNeural", "en-US-GuyNeural", "en-US-AnaNeural"]
577
- selected_voice = st.selectbox("Select Voice for TTS", options=VOICES, index=0)
578
- if st.button("Generate Audio"):
579
- cleaned_text = clean_for_speech(st.session_state.markdown_content)
580
- audio_filename = f"{prefix} {selected_md} {selected_voice}.mp3" if selected_md else f"{prefix} default {selected_voice}.mp3"
581
- audio_file = asyncio.run(generate_audio(cleaned_text, selected_voice, audio_filename))
582
- st.audio(audio_file)
583
- with open(audio_file, "rb") as f:
584
- audio_bytes = f.read()
585
- st.download_button(
586
- label="💾 Save Audio",
587
- data=audio_bytes,
588
- file_name=audio_filename,
589
- mime="audio/mpeg"
590
- )
591
-
592
- if st.button("📑 Create CrossFile PDFs"):
593
- with st.spinner("Creating cross-file linked PDFs..."):
594
- source_pdf, target_pdf = create_crossfile_pdfs()
595
- st.success(f"Created {source_pdf} and {target_pdf}")
596
- for pdf_file in [source_pdf, target_pdf]:
597
- with open(pdf_file, "rb") as f:
598
- st.download_button(
599
- label=f"💾 Download {pdf_file}",
600
- data=f.read(),
601
- file_name=pdf_file,
602
- mime="application/pdf"
603
- )
604
-
605
- if st.button("🧪 Create SelfLinking PDF"):
606
- with st.spinner("Generating self-linking PDF with TOC..."):
607
- pdf_file = create_selflinking_pdf()
608
- st.success(f"Generated {pdf_file}")
609
- with open(pdf_file, "rb") as f:
610
- pdf_bytes = f.read()
611
- images = pdf_to_image(pdf_bytes)
612
- if images:
613
- st.subheader(f"Preview of {pdf_file}")
614
- for i, img in enumerate(images):
615
- st.image(img, caption=f"{pdf_file} Page {i+1}", use_container_width=True)
616
- with open(pdf_file, "rb") as f:
617
- st.download_button(
618
- label=f"💾 Download {pdf_file}",
619
- data=f.read(),
620
- file_name=pdf_file,
621
- mime="application/pdf"
622
- )
623
-
624
- with st.spinner("Generating PDF..."):
625
- pdf_bytes = create_pdf(
626
- st.session_state.markdown_content,
627
- base_font_size,
628
- num_columns,
629
- add_space_before_numbered,
630
- headings_to_fonts,
631
- doc_title=selected_md if selected_md else "Untitled",
632
- longest_line_words=longest_line_words,
633
- total_lines=total_lines
634
- )
635
-
636
- with st.container():
637
- st.markdown("### 📊 PDF Preview")
638
- pdf_images = pdf_to_image(pdf_bytes)
639
- if pdf_images:
640
- for img in pdf_images:
641
- st.image(img, use_container_width=True)
642
- else:
643
- st.info("Download the PDF to view it locally.")
644
-
645
- with st.sidebar:
646
- st.download_button(
647
- label="💾 Save PDF",
648
- data=pdf_bytes,
649
- file_name=f"{prefix} {selected_md}.pdf" if selected_md else f"{prefix} output.pdf",
650
- mime="application/pdf"
651
- )