awacke1 commited on
Commit
efca14d
ยท
verified ยท
1 Parent(s): f6e161c

Create backup13.app.py

Browse files
Files changed (1) hide show
  1. backup13.app.py +768 -0
backup13.app.py ADDED
@@ -0,0 +1,768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ )