awacke1 commited on
Commit
5c61536
·
verified ·
1 Parent(s): 00ff426

Delete backup13.app.py

Browse files
Files changed (1) hide show
  1. backup13.app.py +0 -768
backup13.app.py DELETED
@@ -1,768 +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
- # 🌟 1. Set the stage for a wide, welcoming app - wisdom: A broad canvas invites creativity, like a galaxy awaiting stars!
26
- st.set_page_config(layout="wide", initial_sidebar_state="expanded")
27
-
28
- # Functions
29
- # ⏰ 2. Timestamp generator - wisdom: Time stamps your work like a cosmic signature, grounding chaos in order!
30
- def get_timestamp_prefix():
31
- central = pytz.timezone("US/Central")
32
- now = datetime.now(central)
33
- return now.strftime("%a %m%d %I%M%p").upper()
34
-
35
- # 🧹 3. Text cleaner for speech - wisdom: Strip away emojis to let words sing clear, like a bard sans distractions!
36
- def clean_for_speech(text):
37
- text = text.replace("#", "")
38
- emoji_pattern = re.compile(
39
- r"[\U0001F300-\U0001F5FF"
40
- r"\U0001F600-\U0001F64F"
41
- r"\U0001F680-\U0001F6FF"
42
- r"\U0001F700-\U0001F77F"
43
- r"\U0001F780-\U0001F7FF"
44
- r"\U0001F800-\U0001F8FF"
45
- r"\U0001F900-\U0001F9FF"
46
- r"\U0001FA00-\U0001FA6F"
47
- r"\U0001FA70-\U0001FAFF"
48
- r"\u2600-\u26FF"
49
- r"\u2700-\u27BF]+", flags=re.UNICODE)
50
- text = emoji_pattern.sub('', text)
51
- return text
52
-
53
- # ✂️ 4. Emoji trimmer - wisdom: Keep numbered lines sacred; prune emojis elsewhere to focus the tale!
54
- def trim_emojis_except_numbered(markdown_text):
55
- emoji_pattern = re.compile(
56
- r"[\U0001F300-\U0001F5FF"
57
- r"\U0001F600-\U0001F64F"
58
- r"\U0001F680-\U0001F6FF"
59
- r"\U0001F700-\U0001F77F"
60
- r"\U0001F780-\U0001F7FF"
61
- r"\U0001F800-\U0001F8FF"
62
- r"\U0001F900-\U0001F9FF"
63
- r"\U0001FAD0-\U0001FAD9"
64
- r"\U0001FA00-\U0001FA6F"
65
- r"\U0001FA70-\U0001FAFF"
66
- r"\u2600-\u26FF"
67
- r"\u2700-\u27BF]+"
68
- )
69
- number_pattern = re.compile(r'^\d+\.\s')
70
- lines = markdown_text.strip().split('\n')
71
- processed_lines = []
72
-
73
- for line in lines:
74
- if number_pattern.match(line):
75
- processed_lines.append(line)
76
- else:
77
- processed_lines.append(emoji_pattern.sub('', line))
78
-
79
- return '\n'.join(processed_lines)
80
-
81
- # 🎙️ 5. Audio generator - wisdom: Give voice to text, like a storyteller breathing life into ancient scrolls!
82
- async def generate_audio(text, voice, filename):
83
- communicate = edge_tts.Communicate(text, voice)
84
- await communicate.save(filename)
85
- return filename
86
-
87
- # 🔗 6. Link converter - wisdom: Transform raw URLs into clickable paths, guiding readers like stars in the night!
88
- def detect_and_convert_links(text):
89
- md_link_pattern = re.compile(r'\[(.*?)\]\((https?://[^\s\[\]()<>{}]+)\)')
90
- text = md_link_pattern.sub(r'<a href="\2" color="blue">\1</a>', text)
91
- url_pattern = re.compile(
92
- r'(?<!href=")(https?://[^\s<>{}]+)',
93
- re.IGNORECASE
94
- )
95
- text = url_pattern.sub(r'<a href="\1" color="blue">\1</a>', text)
96
- return text
97
-
98
- # 😊 7. Emoji font applicator - wisdom: Dress emojis in bold fonts, letting them shine like jewels in a crown!
99
- def apply_emoji_font(text, emoji_font):
100
- tag_pattern = re.compile(r'(<[^>]+>)')
101
- segments = tag_pattern.split(text)
102
- result = []
103
- emoji_pattern = re.compile(
104
- r"([\U0001F300-\U0001F5FF"
105
- r"\U0001F600-\U0001F64F"
106
- r"\U0001F680-\U0001F6FF"
107
- r"\U0001F700-\U0001F77F"
108
- r"\U0001F780-\U0001F7FF"
109
- r"\U0001F800-\U0001F8FF"
110
- r"\U0001F900-\U0001F9FF"
111
- r"\U0001FAD0-\U0001FAD9"
112
- r"\U0001FA00-\U0001FA6F"
113
- r"\U0001FA70-\U0001FAFF"
114
- r"\u2600-\u26FF"
115
- r"\u2700-\u27BF]+)"
116
- )
117
-
118
- def replace_emoji(match):
119
- emoji = match.group(1)
120
- emoji = unicodedata.normalize('NFC', emoji)
121
- return f'<font face="{emoji_font}">{emoji}</font>'
122
-
123
- for segment in segments:
124
- if tag_pattern.match(segment):
125
- result.append(segment)
126
- else:
127
- parts = []
128
- last_pos = 0
129
- for match in emoji_pattern.finditer(segment):
130
- start, end = match.span()
131
- if last_pos < start:
132
- parts.append(f'<font face="DejaVuSans">{segment[last_pos:start]}</font>')
133
- parts.append(replace_emoji(match))
134
- last_pos = end
135
- if last_pos < len(segment):
136
- parts.append(f'<font face="DejaVuSans">{segment[last_pos:]}</font>')
137
- result.append(''.join(parts))
138
-
139
- return ''.join(result)
140
-
141
- # 📝 8. Markdown to PDF content - wisdom: Parse markdown like a sage, crafting content that flows like a river!
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
- line = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', line)
173
- line = re.sub(r'\*([^*]+?)\*', r'<b>\1</b>', line)
174
-
175
- pdf_content.append(line)
176
- total_lines = len(pdf_content)
177
- return pdf_content, total_lines
178
-
179
- # 📄 9. PDF creator - wisdom: Weave text into PDFs, like an alchemist turning words into eternal pages!
180
- def create_pdf(markdown_text, base_font_size, num_columns, add_space_before_numbered, headings_to_fonts, doc_title, longest_line_words, total_lines):
181
- if not markdown_text.strip():
182
- return None
183
- buffer = io.BytesIO()
184
- page_width = A4[0] * 2
185
- page_height = A4[1]
186
- doc = SimpleDocTemplate(
187
- buffer,
188
- pagesize=(page_width, page_height),
189
- leftMargin=36,
190
- rightMargin=36,
191
- topMargin=36,
192
- bottomMargin=36,
193
- title=doc_title
194
- )
195
- styles = getSampleStyleSheet()
196
- spacer_height = 10
197
- pdf_content, total_lines = markdown_to_pdf_content(markdown_text, add_space_before_numbered, headings_to_fonts)
198
- try:
199
- available_font_files = glob.glob("*.ttf")
200
- if not available_font_files:
201
- st.error("No .ttf font files found.")
202
- return None
203
- selected_font_path = next((f for f in available_font_files if "NotoEmoji-Bold" in f), None)
204
- if selected_font_path:
205
- pdfmetrics.registerFont(TTFont("NotoEmoji-Bold", selected_font_path))
206
- pdfmetrics.registerFont(TTFont("DejaVuSans", "DejaVuSans.ttf"))
207
- except Exception as e:
208
- st.error(f"Font registration error: {e}")
209
- return None
210
- total_chars = sum(len(line) for line in pdf_content)
211
- hierarchy_weight = sum(1.5 if line.startswith("<b>") else 1 for line in pdf_content)
212
- content_density = total_lines * hierarchy_weight + total_chars / 50
213
- usable_height = page_height - 72 - spacer_height
214
- usable_width = page_width - 72
215
- avg_line_chars = total_chars / total_lines if total_lines > 0 else 50
216
- ideal_lines_per_col = 20
217
- suggested_columns = max(2, min(4, int(total_lines / ideal_lines_per_col) + 1))
218
- num_columns = num_columns if num_columns != 0 else suggested_columns
219
- col_width = usable_width / num_columns
220
- min_font_size = 5
221
- max_font_size = 16
222
- lines_per_col = total_lines / num_columns if num_columns > 0 else total_lines
223
- target_height_per_line = usable_height / lines_per_col if lines_per_col > 0 else usable_height
224
- estimated_font_size = int(target_height_per_line / 1.5)
225
- adjusted_font_size = max(min_font_size, min(max_font_size, estimated_font_size))
226
- if avg_line_chars > col_width / adjusted_font_size * 10:
227
- adjusted_font_size = int(col_width / (avg_line_chars / 10))
228
- adjusted_font_size = max(min_font_size, adjusted_font_size)
229
-
230
- if longest_line_words > 17 or lines_per_col > 20:
231
- font_scale = min(17 / max(longest_line_words, 17), 60 / max(lines_per_col, 20))
232
- adjusted_font_size = max(min_font_size, int(base_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
- # 🖼️ 10. PDF to image converter - wisdom: Turn PDFs into images, like painting a story for eager eyes!
312
- def pdf_to_image(pdf_bytes):
313
- if pdf_bytes is None:
314
- return None
315
- try:
316
- doc = fitz.open(stream=pdf_bytes, filetype="pdf")
317
- images = []
318
- for page in doc:
319
- pix = page.get_pixmap(matrix=fitz.Matrix(2.0, 2.0))
320
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
321
- images.append(img)
322
- doc.close()
323
- return images
324
- except Exception as e:
325
- st.error(f"Failed to render PDF preview: {e}")
326
- return None
327
-
328
- # PDF creation and linking functions
329
- WORDS_12 = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"]
330
- WORDS_24 = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
331
- "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
332
- "twenty-one", "twenty-two", "twenty-three", "twenty-four"]
333
-
334
- # 🔗 11. Cross-file PDF linker - wisdom: Connect PDFs like bridges between islands, uniting stories!
335
- def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pdf"):
336
- """Create two PDFs with cross-file linking."""
337
- # 📜 11.1 Base PDF creator - wisdom: Lay the foundation with words, like carving runes on stone!
338
- def create_base_pdf(filename):
339
- buffer = io.BytesIO()
340
- c = canvas.Canvas(buffer)
341
- c.setFont("Helvetica", 12)
342
- for i, word in enumerate(WORDS_12, 1):
343
- y = 800 - (i * 20)
344
- c.drawString(50, y, f"{i}. {word}")
345
- c.showPage()
346
- c.save()
347
- buffer.seek(0)
348
- with open(filename, "wb") as f:
349
- f.write(buffer.getvalue())
350
- buffer.close()
351
-
352
- # 📑 11.2 Bookmark adder - wisdom: Mark the path to seven, like a beacon in the fog!
353
- def add_bookmark_to_seven(pdf_file):
354
- reader = PdfReader(pdf_file)
355
- writer = PdfWriter()
356
- for page in reader.pages:
357
- writer.add_page(page)
358
- page = writer.pages[0]
359
- y_position = 800 - (7 * 20)
360
- fit = Fit(fit_type="/XYZ", fit_args=[50, y_position, 0])
361
- writer.add_outline_item("Seven Bookmark", 0, fit=fit)
362
- with open(pdf_file, "wb") as f:
363
- writer.write(f)
364
-
365
- # 🌉 11.3 Source PDF modifier - wisdom: Forge links between files, like threads in a tapestry!
366
- def modify_source_pdf(source, target):
367
- reader = PdfReader(source)
368
- writer = PdfWriter()
369
- for page in reader.pages:
370
- writer.add_page(page)
371
- buffer = io.BytesIO()
372
- c = canvas.Canvas(buffer)
373
- c.setFont("Helvetica", 8)
374
- seven_y = 800 - (7 * 20)
375
- c.drawString(90, seven_y - 5, "link")
376
- c.showPage()
377
- c.save()
378
- buffer.seek(0)
379
- text_pdf = PdfReader(buffer)
380
- page = writer.pages[0]
381
- page.merge_page(text_pdf.pages[0])
382
- link = Link(
383
- rect=(90, seven_y - 10, 150, seven_y + 10),
384
- url=f"file://{os.path.abspath(target)}#page=1"
385
- )
386
- writer.add_annotation(page_number=0, annotation=link)
387
- with open(source, "wb") as f:
388
- writer.write(f)
389
- buffer.close()
390
-
391
- # 🛤️ 11.4 Internal link adder - wisdom: Guide readers within, like a map to hidden treasure!
392
- def add_internal_link(pdf_file):
393
- reader = PdfReader(pdf_file)
394
- writer = PdfWriter()
395
- for page in reader.pages:
396
- writer.add_page(page)
397
- one_y = 800 - (1 * 20)
398
- ten_y = 800 - (10 * 20)
399
- link = Link(
400
- rect=(50, one_y - 10, 100, one_y + 10),
401
- target_page_index=0,
402
- fit=Fit(fit_type="/XYZ", fit_args=[50, ten_y, 0])
403
- )
404
- writer.add_annotation(page_number=0, annotation=link)
405
- with open(pdf_file, "wb") as f:
406
- writer.write(f)
407
-
408
- create_base_pdf(source_pdf)
409
- create_base_pdf(target_pdf)
410
- add_bookmark_to_seven(target_pdf)
411
- modify_source_pdf(source, target)
412
- add_internal_link(source_pdf)
413
- add_internal_link(target_pdf)
414
- return source_pdf, target_pdf
415
-
416
- # 📘 12. Self-linking PDF creator - wisdom: Build a PDF that guides itself, like a book with its own compass!
417
- def create_selflinking_pdf(pdf_file="SelfLinking.pdf"):
418
- """Create a PDF with a TOC on page 1 linking to a 1-24 list starting on page 2."""
419
- buffer = io.BytesIO()
420
- c = canvas.Canvas(buffer)
421
- c.setFont("Helvetica", 14)
422
- c.drawString(50, 800, "Table of Contents")
423
- c.setFont("Helvetica", 12)
424
- toc_y_positions = []
425
- for i, word in enumerate(WORDS_12, 1):
426
- y = 760 - (i * 20)
427
- c.drawString(50, y, f"{word}")
428
- toc_y_positions.append(y)
429
- c.showPage()
430
- c.setFont("Helvetica", 12)
431
- list_y_positions = []
432
- for i, word in enumerate(WORDS_24, 1):
433
- y = 800 - (i * 20)
434
- c.drawString(50, y, f"{i}. {word}")
435
- list_y_positions.append(y)
436
- c.showPage()
437
- c.save()
438
- buffer.seek(0)
439
- with open(pdf_file, "wb") as f:
440
- f.write(buffer.getvalue())
441
- buffer.close()
442
- reader = PdfReader(pdf_file)
443
- writer = PdfWriter()
444
- for page in reader.pages:
445
- writer.add_page(page)
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
- for i, word in enumerate(WORDS_12):
453
- toc_y = toc_y_positions[i]
454
- list_y = list_y_positions[i]
455
- link = Link(
456
- rect=(50, toc_y - 10, 150, toc_y + 10),
457
- target_page_index=1,
458
- fit=Fit(fit_type="/XYZ", fit_args=[50, list_y, 0])
459
- )
460
- writer.add_annotation(page_number=0, annotation=link)
461
- with open(pdf_file, "wb") as f:
462
- writer.write(f)
463
- return pdf_file
464
-
465
- # 🖼️ 13. Image-linked PDF creator - wisdom: Link images to text, like windows opening to new worlds!
466
- def create_pdf_with_images(source_pdf_bytes, output_pdf="ImageLinked.pdf"):
467
- """Create a PDF with links on numbered headings to new pages with images."""
468
- image_files = sorted(glob.glob("*.png"))
469
- if not source_pdf_bytes:
470
- st.error("No source PDF provided.")
471
- return None
472
- if not image_files:
473
- st.error("No PNG images found in the directory.")
474
- return source_pdf_bytes
475
-
476
- reader = PdfReader(io.BytesIO(source_pdf_bytes))
477
- writer = PdfWriter()
478
-
479
- # Copy all pages from source PDF
480
- original_page_count = len(reader.pages)
481
- for page in reader.pages:
482
- writer.add_page(page)
483
-
484
- # Add image pages
485
- image_page_indices = []
486
- for image_file in image_files[:12]: # Limit to 12 images
487
- buffer = io.BytesIO()
488
- c = canvas.Canvas(buffer, pagesize=A4)
489
- try:
490
- img = Image.open(image_file)
491
- img_width, img_height = img.size
492
- page_width, page_height = A4
493
- scale = min((page_width - 40) / img_width, (page_height - 40) / img_height)
494
- new_width = img_width * scale
495
- new_height = img_height * scale
496
- x = (page_width - new_width) / 2
497
- y = (page_height - new_height) / 2
498
- c.drawImage(image_file, x, y, new_width, new_height)
499
- c.showPage()
500
- c.save()
501
- buffer.seek(0)
502
- img_pdf = PdfReader(buffer)
503
- writer.add_page(img_pdf.pages[0])
504
- image_page_indices.append(original_page_count + len(image_page_indices))
505
- buffer.close()
506
- except Exception as e:
507
- st.error(f"Failed to process image {image_file}: {e}")
508
- buffer.close()
509
- continue
510
-
511
- # Add links to numbered headings on first page
512
- if image_page_indices:
513
- page = writer.pages[0]
514
- y_positions = []
515
- for i in range(1, 13):
516
- y = 800 - (i * 20) # Matches layout from create_pdf
517
- y_positions.append(y)
518
-
519
- for idx, (y, target_page_idx) in enumerate(zip(y_positions, image_page_indices)):
520
- # Add "link" text
521
- buffer = io.BytesIO()
522
- c = canvas.Canvas(buffer)
523
- c.setFont("Helvetica", 8)
524
- c.drawString(90, y - 5, "link")
525
- c.showPage()
526
- c.save()
527
- buffer.seek(0)
528
- text_pdf = PdfReader(buffer)
529
- page.merge_page(text_pdf.pages[0])
530
-
531
- # Add link annotation
532
- link = Link(
533
- rect=(90, y - 10, 150, y + 10),
534
- target_page_index=target_page_idx,
535
- fit=Fit(fit_type="/Fit")
536
- )
537
- writer.add_annotation(page_number=0, annotation=link)
538
- buffer.close()
539
-
540
- output_buffer = io.BytesIO()
541
- writer.write(output_buffer)
542
- output_buffer.seek(0)
543
- with open(output_pdf, "wb") as f:
544
- f.write(output_buffer.getvalue())
545
- return output_buffer.getvalue()
546
-
547
- # 🎨 14. Streamlit UI - wisdom: Craft an interface like a garden, where users bloom with possibilities!
548
- md_files = [f for f in glob.glob("*.md") if os.path.basename(f) != "README.md"]
549
- md_options = [os.path.splitext(os.path.basename(f))[0] for f in md_files]
550
-
551
- with st.sidebar:
552
- # 📚 14.1 Markdown selector - wisdom: Offer choices like a librarian, guiding users to their story!
553
- st.markdown("### 📄 PDF Options")
554
- if md_options:
555
- selected_md = st.selectbox("Select Markdown File", options=md_options, index=0, key="markdown_select")
556
- if selected_md != st.session_state.get('last_selected_md'):
557
- with open(f"{selected_md}.md", "r", encoding="utf-8") as f:
558
- st.session_state.markdown_content = f.read()
559
- st.session_state.last_selected_md = selected_md
560
- else:
561
- st.warning("No markdown file found. Please add one to your folder.")
562
- selected_md = None
563
- st.session_state.markdown_content = ""
564
-
565
- # 🔠 14.2 Font selector - wisdom: Choose fonts like a painter picks colors, shaping the mood!
566
- available_font_files = {os.path.splitext(os.path.basename(f))[0]: f for f in glob.glob("*.ttf")}
567
- selected_font_name = st.selectbox(
568
- "Select Emoji Font",
569
- options=list(available_font_files.keys()),
570
- index=list(available_font_files.keys()).index("NotoEmoji-Bold") if "NotoEmoji-Bold" in available_font_files else 0
571
- )
572
- base_font_size = st.slider("Font Size (points)", min_value=6, max_value=16, value=8, step=1)
573
-
574
- # 📏 14.3 Layout options - wisdom: Space and style are the frame of your masterpiece!
575
- add_space_before_numbered = st.checkbox("Add Space Ahead of Numbered Lines", value=True)
576
- headings_to_fonts = st.checkbox(
577
- "Headings to Fonts",
578
- value=True,
579
- help="Convert Markdown headings (# Heading) to styled fonts"
580
- )
581
- auto_columns = st.checkbox("AutoColumns", value=True)
582
-
583
- # 📊 14.4 Document stats - wisdom: Know your text’s pulse, like a doctor checking its heartbeat!
584
- column_options = [2, 3, 4]
585
- num_columns = 3
586
- recommended_columns = 3
587
- longest_line_words = 0
588
- total_lines = 0
589
- adjusted_font_size_display = base_font_size
590
- if 'markdown_content' in st.session_state and st.session_state.markdown_content.strip():
591
- current_markdown = st.session_state.markdown_content
592
- lines = current_markdown.strip().split('\n')
593
- total_lines = len([line for line in lines if line.strip()])
594
- for line in lines:
595
- if line.strip():
596
- word_count = len(line.split())
597
- longest_line_words = max(longest_line_words, word_count)
598
- if auto_columns:
599
- if longest_line_words > 38:
600
- recommended_columns = 2
601
- elif longest_line_words < 18 and total_lines < 20:
602
- recommended_columns = 4
603
- else:
604
- recommended_columns = 3
605
- if longest_line_words > 17 or total_lines / max(recommended_columns, 1) > 20:
606
- font_scale = min(17 / max(longest_line_words, 17), 60 / max(total_lines / max(recommended_columns, 1), 20))
607
- adjusted_font_size_display = max(5, int(base_font_size * font_scale))
608
- st.markdown("**Document Stats**")
609
- st.write(f"- Longest Line: {longest_line_words} words")
610
- st.write(f"- Total Lines: {total_lines}")
611
- st.write(f"- Recommended Columns: {recommended_columns}")
612
- st.write(f"- Adjusted Font Size: {adjusted_font_size_display} points")
613
- else:
614
- st.markdown("**Document Stats**")
615
- st.write("- Longest Line: 0 words")
616
- st.write("- Total Lines: 0")
617
- st.write("- Recommended Columns: 3")
618
- st.write(f"- Adjusted Font Size: {base_font_size} points")
619
-
620
- # 🔢 14.5 Column selector - wisdom: Columns organize chaos, like shelves in a wizard’s library!
621
- num_columns = st.selectbox(
622
- "Number of Columns",
623
- options=column_options,
624
- index=column_options.index(recommended_columns) if recommended_columns in column_options else 0
625
- )
626
- st.info("Font size and columns adjust to fit one page.")
627
-
628
- # ✍️ 14.6 Markdown editor - wisdom: Let users scribe their saga, shaping worlds with words!
629
- st.markdown("### ✍️ Edit Markdown")
630
- edited_markdown = st.text_area(
631
- "Input Markdown",
632
- value=st.session_state.markdown_content,
633
- height=200,
634
- key=f"markdown_{selected_md}_{selected_font_name}_{num_columns}"
635
- )
636
-
637
- # 💾 14.7 Action buttons - wisdom: Actions spark progress, like flint igniting a fire!
638
- st.markdown("### 💾 Actions")
639
- col1, col2 = st.columns(2)
640
- with col1:
641
- if st.button("🔄 Update PDF"):
642
- st.session_state.markdown_content = edited_markdown
643
- if selected_md:
644
- with open(f"{selected_md}.md", "w", encoding="utf-8") as f:
645
- f.write(edited_markdown)
646
- st.rerun()
647
-
648
- with col2:
649
- if st.button("✂️ Trim Emojis"):
650
- trimmed_content = trim_emojis_except_numbered(edited_markdown)
651
- st.session_state.markdown_content = trimmed_content
652
- if selected_md:
653
- with open(f"{selected_md}.md", "w", encoding="utf-8") as f:
654
- f.write(trimmed_content)
655
- st.rerun()
656
-
657
- # 📥 14.8 Markdown saver - wisdom: Save your work, like bottling a potion for later!
658
- prefix = get_timestamp_prefix()
659
- st.download_button(
660
- label="💾 Save Markdown",
661
- data=st.session_state.markdown_content,
662
- file_name=f"{prefix} {selected_md}.md" if selected_md else f"{prefix} default.md",
663
- mime="text/markdown"
664
- )
665
-
666
- # 🔊 14.9 Text-to-speech - wisdom: Let words echo aloud, like a bard’s tale in the hall!
667
- st.markdown("### 🔊 Text-to-Speech")
668
- VOICES = ["en-US-AriaNeural", "en-US-JennyNeural", "en-GB-SoniaNeural", "en-US-GuyNeural", "en-US-AnaNeural"]
669
- selected_voice = st.selectbox("Select Voice for TTS", options=VOICES, index=0)
670
- if st.button("Generate Audio"):
671
- cleaned_text = clean_for_speech(st.session_state.markdown_content)
672
- audio_filename = f"{prefix} {selected_md} {selected_voice}.mp3" if selected_md else f"{prefix} default {selected_voice}.mp3"
673
- audio_file = asyncio.run(generate_audio(cleaned_text, selected_voice, audio_filename))
674
- st.audio(audio_file)
675
- with open(audio_file, "rb") as f:
676
- audio_bytes = f.read()
677
- st.download_button(
678
- label="💾 Save Audio",
679
- data=audio_bytes,
680
- file_name=audio_filename,
681
- mime="audio/mpeg"
682
- )
683
-
684
- # 📑 14.10 PDF action buttons - wisdom: Create and link PDFs, like crafting portals to knowledge!
685
- col1, col2 = st.columns(2)
686
- with col1:
687
- if st.button("📑 Create CrossFile PDFs"):
688
- with st.spinner("Creating cross-file linked PDFs..."):
689
- source_pdf, target_pdf = create_crossfile_pdfs()
690
- st.success(f"Created {source_pdf} and {target_pdf}")
691
- for pdf_file in [source_pdf, target_pdf]:
692
- with open(pdf_file, "rb") as f:
693
- st.download_button(
694
- label=f"💾 Download {pdf_file}",
695
- data=f.read(),
696
- file_name=pdf_file,
697
- mime="application/pdf"
698
- )
699
-
700
- with col2:
701
- if st.button("🧪 Create SelfLinking PDF"):
702
- with st.spinner("Generating self-linking PDF with TOC..."):
703
- pdf_file = create_selflinking_pdf()
704
- st.success(f"Generated {pdf_file}")
705
- with open(pdf_file, "rb") as f:
706
- self_linked_pdf_bytes = f.read()
707
- images = pdf_to_image(self_linked_pdf_bytes)
708
- if images:
709
- st.subheader(f"Preview of {pdf_file}")
710
- for i, img in enumerate(images):
711
- st.image(img, caption=f"{pdf_file} Page {i+1}", use_container_width=True)
712
- with open(pdf_file, "rb") as f:
713
- st.download_button(
714
- label=f"💾 Download {pdf_file}",
715
- data=f.read(),
716
- file_name=pdf_file,
717
- mime="application/pdf"
718
- )
719
-
720
- # 🖥️ 15. Main PDF generation - wisdom: Spin up the PDF like a weaver at the loom, crafting beauty!
721
- with st.spinner("Generating PDF..."):
722
- pdf_bytes = create_pdf(
723
- st.session_state.get('markdown_content', ''),
724
- base_font_size,
725
- num_columns,
726
- add_space_before_numbered,
727
- headings_to_fonts,
728
- doc_title=selected_md if selected_md else "Untitled",
729
- longest_line_words=longest_line_words,
730
- total_lines=total_lines
731
- )
732
-
733
- # 🖼️ 16. PDF preview - wisdom: Show the masterpiece before it’s framed, delighting the creator!
734
- with st.container():
735
- st.markdown("### 📊 PDF Preview")
736
- pdf_images = pdf_to_image(pdf_bytes)
737
- if pdf_images:
738
- for img in pdf_images:
739
- st.image(img, use_container_width=True)
740
- else:
741
- st.info("Download the PDF to view it locally.")
742
-
743
- with st.sidebar:
744
- # 💾 17. PDF saver - wisdom: Offer the final scroll, ready to be shared like wisdom across ages!
745
- st.download_button(
746
- label="💾 Save PDF",
747
- data=pdf_bytes if pdf_bytes else "",
748
- file_name=f"{prefix} {selected_md}.pdf" if selected_md else f"{prefix} output.pdf",
749
- mime="application/pdf",
750
- disabled=pdf_bytes is None
751
- )
752
-
753
- if st.button("🖼️ Generate PDF With Images"):
754
- with st.spinner("Generating PDF with image links..."):
755
- linked_pdf_bytes = create_pdf_with_images(pdf_bytes)
756
- if linked_pdf_bytes and linked_pdf_bytes != pdf_bytes:
757
- st.success("Generated PDF with image links")
758
- images = pdf_to_image(linked_pdf_bytes)
759
- if images:
760
- st.subheader("Preview of Image-Linked PDF")
761
- for i, img in enumerate(images):
762
- st.image(img, caption=f"Image-Linked PDF Page {i+1}", use_container_width=True)
763
- st.download_button(
764
- label="💾 Download Image-Linked PDF",
765
- data=linked_pdf_bytes,
766
- file_name=f"{prefix} {selected_md}_image_linked.pdf" if selected_md else f"{prefix} image_linked.pdf",
767
- mime="application/pdf"
768
- )