awacke1 commited on
Commit
c57a732
ยท
verified ยท
1 Parent(s): 0b373d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -58
app.py CHANGED
@@ -22,14 +22,17 @@ 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(
@@ -47,6 +50,7 @@ def clean_for_speech(text):
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"
@@ -74,17 +78,16 @@ def trim_emojis_except_numbered(markdown_text):
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
@@ -92,13 +95,11 @@ def detect_and_convert_links(text):
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"
@@ -121,10 +122,8 @@ def apply_emoji_font(text, 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):
@@ -139,6 +138,7 @@ def apply_emoji_font(text, emoji_font):
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 = []
@@ -169,8 +169,6 @@ def markdown_to_pdf_content(markdown_text, add_space_before_numbered, headings_t
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
 
@@ -178,9 +176,10 @@ def markdown_to_pdf_content(markdown_text, add_space_before_numbered, headings_t
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
  if not markdown_text.strip():
183
- return None # Handle empty markdown gracefully
184
  buffer = io.BytesIO()
185
  page_width = A4[0] * 2
186
  page_height = A4[1]
@@ -218,7 +217,7 @@ def create_pdf(markdown_text, base_font_size, num_columns, add_space_before_numb
218
  suggested_columns = max(2, min(4, int(total_lines / ideal_lines_per_col) + 1))
219
  num_columns = num_columns if num_columns != 0 else suggested_columns
220
  col_width = usable_width / num_columns
221
- min_font_size = 5 # Reduced to allow tighter fit
222
  max_font_size = 16
223
  lines_per_col = total_lines / num_columns if num_columns > 0 else total_lines
224
  target_height_per_line = usable_height / lines_per_col if lines_per_col > 0 else usable_height
@@ -228,7 +227,6 @@ def create_pdf(markdown_text, base_font_size, num_columns, add_space_before_numb
228
  adjusted_font_size = int(col_width / (avg_line_chars / 10))
229
  adjusted_font_size = max(min_font_size, adjusted_font_size)
230
 
231
- # Enhanced font size scaling for one-page fit
232
  if longest_line_words > 17 or lines_per_col > 20:
233
  font_scale = min(17 / max(longest_line_words, 17), 60 / max(lines_per_col, 20))
234
  adjusted_font_size = max(min_font_size, int(base_font_size * font_scale))
@@ -310,6 +308,7 @@ def create_pdf(markdown_text, base_font_size, num_columns, add_space_before_numb
310
  buffer.seek(0)
311
  return buffer.getvalue()
312
 
 
313
  def pdf_to_image(pdf_bytes):
314
  if pdf_bytes is None:
315
  return None
@@ -332,8 +331,10 @@ WORDS_24 = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nin
332
  "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
333
  "twenty-one", "twenty-two", "twenty-three", "twenty-four"]
334
 
 
335
  def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pdf"):
336
  """Create two PDFs with cross-file linking."""
 
337
  def create_base_pdf(filename):
338
  buffer = io.BytesIO()
339
  c = canvas.Canvas(buffer)
@@ -348,6 +349,7 @@ def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pd
348
  f.write(buffer.getvalue())
349
  buffer.close()
350
 
 
351
  def add_bookmark_to_seven(pdf_file):
352
  reader = PdfReader(pdf_file)
353
  writer = PdfWriter()
@@ -360,6 +362,7 @@ def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pd
360
  with open(pdf_file, "wb") as f:
361
  writer.write(f)
362
 
 
363
  def modify_source_pdf(source, target):
364
  reader = PdfReader(source)
365
  writer = PdfWriter()
@@ -385,6 +388,7 @@ def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pd
385
  writer.write(f)
386
  buffer.close()
387
 
 
388
  def add_internal_link(pdf_file):
389
  reader = PdfReader(pdf_file)
390
  writer = PdfWriter()
@@ -409,12 +413,11 @@ def create_crossfile_pdfs(source_pdf="TestSource.pdf", target_pdf="TestTarget.pd
409
  add_internal_link(target_pdf)
410
  return source_pdf, target_pdf
411
 
 
412
  def create_selflinking_pdf(pdf_file="SelfLinking.pdf"):
413
  """Create a PDF with a TOC on page 1 linking to a 1-24 list starting on page 2."""
414
  buffer = io.BytesIO()
415
  c = canvas.Canvas(buffer)
416
-
417
- # Page 1: Table of Contents
418
  c.setFont("Helvetica", 14)
419
  c.drawString(50, 800, "Table of Contents")
420
  c.setFont("Helvetica", 12)
@@ -424,8 +427,6 @@ def create_selflinking_pdf(pdf_file="SelfLinking.pdf"):
424
  c.drawString(50, y, f"{word}")
425
  toc_y_positions.append(y)
426
  c.showPage()
427
-
428
- # Page 2: Numbered list 1-24
429
  c.setFont("Helvetica", 12)
430
  list_y_positions = []
431
  for i, word in enumerate(WORDS_24, 1):
@@ -433,29 +434,21 @@ def create_selflinking_pdf(pdf_file="SelfLinking.pdf"):
433
  c.drawString(50, y, f"{i}. {word}")
434
  list_y_positions.append(y)
435
  c.showPage()
436
-
437
- # Save the initial PDF
438
  c.save()
439
  buffer.seek(0)
440
  with open(pdf_file, "wb") as f:
441
  f.write(buffer.getvalue())
442
  buffer.close()
443
-
444
- # Add outlines and links
445
  reader = PdfReader(pdf_file)
446
  writer = PdfWriter()
447
  for page in reader.pages:
448
  writer.add_page(page)
449
-
450
- # Add outline entries
451
  toc_page = writer.pages[0]
452
  list_page = writer.pages[1]
453
  writer.add_outline_item("Table of Contents", 0, fit=Fit(fit_type="/Fit"))
454
  for i, word in enumerate(WORDS_12, 1):
455
  y = list_y_positions[i-1]
456
  writer.add_outline_item(word, 1, fit=Fit(fit_type="/XYZ", fit_args=[50, y, 0]))
457
-
458
- # Add TOC links from page 1 to page 2
459
  for i, word in enumerate(WORDS_12):
460
  toc_y = toc_y_positions[i]
461
  list_y = list_y_positions[i]
@@ -465,45 +458,29 @@ def create_selflinking_pdf(pdf_file="SelfLinking.pdf"):
465
  fit=Fit(fit_type="/XYZ", fit_args=[50, list_y, 0])
466
  )
467
  writer.add_annotation(page_number=0, annotation=link)
468
-
469
- # Save the modified PDF
470
  with open(pdf_file, "wb") as f:
471
  writer.write(f)
472
-
473
  return pdf_file
474
 
 
475
  def create_pdf_with_images(source_pdf_bytes, output_pdf="ImageLinked.pdf"):
476
  """Create a PDF with links to images on numbered items 1-12."""
477
- # Get all PNG files in the project directory, sorted by name
478
  image_files = sorted(glob.glob("*.png"))
479
-
480
  if not source_pdf_bytes:
481
  st.error("No source PDF provided.")
482
  return None
483
-
484
- # Read the source PDF
485
  reader = PdfReader(io.BytesIO(source_pdf_bytes))
486
  writer = PdfWriter()
487
-
488
- # Copy all pages from source PDF
489
  for page in reader.pages:
490
  writer.add_page(page)
491
-
492
- # Only process the first page for links
493
  page = writer.pages[0]
494
  number_pattern = re.compile(r'^\d+\.\s')
495
-
496
- # Find numbered items 1-12 on the page
497
  y_positions = []
498
- # Assuming the PDF was generated with numbered items at predictable y-coordinates
499
- for i in range(1, 13): # 1 to 12
500
- y = 800 - (i * 20) # Adjust based on create_pdf layout
501
  y_positions.append(y)
502
-
503
- # Add links to images
504
  for idx, (y, image_file) in enumerate(zip(y_positions, image_files[:12])):
505
  if os.path.exists(image_file):
506
- # Add "link" text next to numbered item
507
  buffer = io.BytesIO()
508
  c = canvas.Canvas(buffer)
509
  c.setFont("Helvetica", 8)
@@ -513,29 +490,25 @@ def create_pdf_with_images(source_pdf_bytes, output_pdf="ImageLinked.pdf"):
513
  buffer.seek(0)
514
  text_pdf = PdfReader(buffer)
515
  page.merge_page(text_pdf.pages[0])
516
-
517
- # Add link annotation
518
  link = Link(
519
  rect=(90, y - 10, 150, y + 10),
520
  url=f"file://{os.path.abspath(image_file)}"
521
  )
522
  writer.add_annotation(page_number=0, annotation=link)
523
  buffer.close()
524
-
525
- # Save the modified PDF
526
  output_buffer = io.BytesIO()
527
  writer.write(output_buffer)
528
  output_buffer.seek(0)
529
  with open(output_pdf, "wb") as f:
530
  f.write(output_buffer.getvalue())
531
-
532
  return output_buffer.getvalue()
533
 
534
- # Streamlit UI
535
  md_files = [f for f in glob.glob("*.md") if os.path.basename(f) != "README.md"]
536
  md_options = [os.path.splitext(os.path.basename(f))[0] for f in md_files]
537
 
538
  with st.sidebar:
 
539
  st.markdown("### ๐Ÿ“„ PDF Options")
540
  if md_options:
541
  selected_md = st.selectbox("Select Markdown File", options=md_options, index=0)
@@ -546,6 +519,7 @@ with st.sidebar:
546
  selected_md = None
547
  st.session_state.markdown_content = ""
548
 
 
549
  available_font_files = {os.path.splitext(os.path.basename(f))[0]: f for f in glob.glob("*.ttf")}
550
  selected_font_name = st.selectbox(
551
  "Select Emoji Font",
@@ -554,6 +528,7 @@ with st.sidebar:
554
  )
555
  base_font_size = st.slider("Font Size (points)", min_value=6, max_value=16, value=8, step=1)
556
 
 
557
  add_space_before_numbered = st.checkbox("Add Space Ahead of Numbered Lines", value=True)
558
  headings_to_fonts = st.checkbox(
559
  "Headings to Fonts",
@@ -562,7 +537,10 @@ with st.sidebar:
562
  )
563
  auto_columns = st.checkbox("AutoColumns", value=True)
564
 
565
- # Calculate document stats
 
 
 
566
  longest_line_words = 0
567
  total_lines = 0
568
  adjusted_font_size_display = base_font_size
@@ -581,11 +559,8 @@ with st.sidebar:
581
  recommended_columns = 4
582
  else:
583
  recommended_columns = 3
584
- else:
585
- recommended_columns = 3
586
- # Adjust font size for one-page fit
587
- if longest_line_words > 17 or total_lines / max(num_columns, 1) > 20:
588
- font_scale = min(17 / max(longest_line_words, 17), 60 / max(total_lines / max(num_columns, 1), 20))
589
  adjusted_font_size_display = max(5, int(base_font_size * font_scale))
590
  st.markdown("**Document Stats**")
591
  st.write(f"- Longest Line: {longest_line_words} words")
@@ -599,7 +574,7 @@ with st.sidebar:
599
  st.write("- Recommended Columns: 3")
600
  st.write(f"- Adjusted Font Size: {base_font_size} points")
601
 
602
- column_options = [2, 3, 4]
603
  num_columns = st.selectbox(
604
  "Number of Columns",
605
  options=column_options,
@@ -607,6 +582,7 @@ with st.sidebar:
607
  )
608
  st.info("Font size and columns adjust to fit one page.")
609
 
 
610
  st.markdown("### โœ๏ธ Edit Markdown")
611
  edited_markdown = st.text_area(
612
  "Input Markdown",
@@ -615,6 +591,7 @@ with st.sidebar:
615
  key=f"markdown_{selected_md}_{selected_font_name}_{num_columns}"
616
  )
617
 
 
618
  st.markdown("### ๐Ÿ’พ Actions")
619
  col1, col2 = st.columns(2)
620
  with col1:
@@ -634,6 +611,7 @@ with st.sidebar:
634
  f.write(trimmed_content)
635
  st.rerun()
636
 
 
637
  prefix = get_timestamp_prefix()
638
  st.download_button(
639
  label="๐Ÿ’พ Save Markdown",
@@ -642,6 +620,7 @@ with st.sidebar:
642
  mime="text/markdown"
643
  )
644
 
 
645
  st.markdown("### ๐Ÿ”Š Text-to-Speech")
646
  VOICES = ["en-US-AriaNeural", "en-US-JennyNeural", "en-GB-SoniaNeural", "en-US-GuyNeural", "en-US-AnaNeural"]
647
  selected_voice = st.selectbox("Select Voice for TTS", options=VOICES, index=0)
@@ -659,6 +638,7 @@ with st.sidebar:
659
  mime="audio/mpeg"
660
  )
661
 
 
662
  col1, col2 = st.columns(2)
663
  with col1:
664
  if st.button("๐Ÿ“‘ Create CrossFile PDFs"):
@@ -711,6 +691,7 @@ with st.sidebar:
711
  mime="application/pdf"
712
  )
713
 
 
714
  with st.spinner("Generating PDF..."):
715
  pdf_bytes = create_pdf(
716
  st.session_state.markdown_content,
@@ -723,6 +704,7 @@ with st.spinner("Generating PDF..."):
723
  total_lines=total_lines
724
  )
725
 
 
726
  with st.container():
727
  st.markdown("### ๐Ÿ“Š PDF Preview")
728
  pdf_images = pdf_to_image(pdf_bytes)
@@ -732,6 +714,7 @@ with st.container():
732
  else:
733
  st.info("Download the PDF to view it locally.")
734
 
 
735
  with st.sidebar:
736
  st.download_button(
737
  label="๐Ÿ’พ Save PDF",
 
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(
 
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"
 
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
 
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"
 
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):
 
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 = []
 
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
 
 
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]
 
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
 
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))
 
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
 
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)
 
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()
 
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()
 
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()
 
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)
 
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):
 
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]
 
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 to images on numbered items 1-12."""
 
468
  image_files = sorted(glob.glob("*.png"))
 
469
  if not source_pdf_bytes:
470
  st.error("No source PDF provided.")
471
  return None
 
 
472
  reader = PdfReader(io.BytesIO(source_pdf_bytes))
473
  writer = PdfWriter()
 
 
474
  for page in reader.pages:
475
  writer.add_page(page)
 
 
476
  page = writer.pages[0]
477
  number_pattern = re.compile(r'^\d+\.\s')
 
 
478
  y_positions = []
479
+ for i in range(1, 13):
480
+ y = 800 - (i * 20)
 
481
  y_positions.append(y)
 
 
482
  for idx, (y, image_file) in enumerate(zip(y_positions, image_files[:12])):
483
  if os.path.exists(image_file):
 
484
  buffer = io.BytesIO()
485
  c = canvas.Canvas(buffer)
486
  c.setFont("Helvetica", 8)
 
490
  buffer.seek(0)
491
  text_pdf = PdfReader(buffer)
492
  page.merge_page(text_pdf.pages[0])
 
 
493
  link = Link(
494
  rect=(90, y - 10, 150, y + 10),
495
  url=f"file://{os.path.abspath(image_file)}"
496
  )
497
  writer.add_annotation(page_number=0, annotation=link)
498
  buffer.close()
 
 
499
  output_buffer = io.BytesIO()
500
  writer.write(output_buffer)
501
  output_buffer.seek(0)
502
  with open(output_pdf, "wb") as f:
503
  f.write(output_buffer.getvalue())
 
504
  return output_buffer.getvalue()
505
 
506
+ # ๐ŸŽจ 14. Streamlit UI - wisdom: Craft an interface like a garden, where users bloom with possibilities!
507
  md_files = [f for f in glob.glob("*.md") if os.path.basename(f) != "README.md"]
508
  md_options = [os.path.splitext(os.path.basename(f))[0] for f in md_files]
509
 
510
  with st.sidebar:
511
+ # ๐Ÿ“š 14.1 Markdown selector - wisdom: Offer choices like a librarian, guiding users to their story!
512
  st.markdown("### ๐Ÿ“„ PDF Options")
513
  if md_options:
514
  selected_md = st.selectbox("Select Markdown File", options=md_options, index=0)
 
519
  selected_md = None
520
  st.session_state.markdown_content = ""
521
 
522
+ # ๐Ÿ”  14.2 Font selector - wisdom: Choose fonts like a painter picks colors, shaping the mood!
523
  available_font_files = {os.path.splitext(os.path.basename(f))[0]: f for f in glob.glob("*.ttf")}
524
  selected_font_name = st.selectbox(
525
  "Select Emoji Font",
 
528
  )
529
  base_font_size = st.slider("Font Size (points)", min_value=6, max_value=16, value=8, step=1)
530
 
531
+ # ๐Ÿ“ 14.3 Layout options - wisdom: Space and style are the frame of your masterpiece!
532
  add_space_before_numbered = st.checkbox("Add Space Ahead of Numbered Lines", value=True)
533
  headings_to_fonts = st.checkbox(
534
  "Headings to Fonts",
 
537
  )
538
  auto_columns = st.checkbox("AutoColumns", value=True)
539
 
540
+ # ๐Ÿ“Š 14.4 Document stats - wisdom: Know your textโ€™s pulse, like a doctor checking its heartbeat!
541
+ column_options = [2, 3, 4]
542
+ num_columns = 3
543
+ recommended_columns = 3
544
  longest_line_words = 0
545
  total_lines = 0
546
  adjusted_font_size_display = base_font_size
 
559
  recommended_columns = 4
560
  else:
561
  recommended_columns = 3
562
+ if longest_line_words > 17 or total_lines / max(recommended_columns, 1) > 20:
563
+ font_scale = min(17 / max(longest_line_words, 17), 60 / max(total_lines / max(recommended_columns, 1), 20))
 
 
 
564
  adjusted_font_size_display = max(5, int(base_font_size * font_scale))
565
  st.markdown("**Document Stats**")
566
  st.write(f"- Longest Line: {longest_line_words} words")
 
574
  st.write("- Recommended Columns: 3")
575
  st.write(f"- Adjusted Font Size: {base_font_size} points")
576
 
577
+ # ๐Ÿ”ข 14.5 Column selector - wisdom: Columns organize chaos, like shelves in a wizardโ€™s library!
578
  num_columns = st.selectbox(
579
  "Number of Columns",
580
  options=column_options,
 
582
  )
583
  st.info("Font size and columns adjust to fit one page.")
584
 
585
+ # โœ๏ธ 14.6 Markdown editor - wisdom: Let users scribe their saga, shaping worlds with words!
586
  st.markdown("### โœ๏ธ Edit Markdown")
587
  edited_markdown = st.text_area(
588
  "Input Markdown",
 
591
  key=f"markdown_{selected_md}_{selected_font_name}_{num_columns}"
592
  )
593
 
594
+ # ๐Ÿ’พ 14.7 Action buttons - wisdom: Actions spark progress, like flint igniting a fire!
595
  st.markdown("### ๐Ÿ’พ Actions")
596
  col1, col2 = st.columns(2)
597
  with col1:
 
611
  f.write(trimmed_content)
612
  st.rerun()
613
 
614
+ # ๐Ÿ“ฅ 14.8 Markdown saver - wisdom: Save your work, like bottling a potion for later!
615
  prefix = get_timestamp_prefix()
616
  st.download_button(
617
  label="๐Ÿ’พ Save Markdown",
 
620
  mime="text/markdown"
621
  )
622
 
623
+ # ๐Ÿ”Š 14.9 Text-to-speech - wisdom: Let words echo aloud, like a bardโ€™s tale in the hall!
624
  st.markdown("### ๐Ÿ”Š Text-to-Speech")
625
  VOICES = ["en-US-AriaNeural", "en-US-JennyNeural", "en-GB-SoniaNeural", "en-US-GuyNeural", "en-US-AnaNeural"]
626
  selected_voice = st.selectbox("Select Voice for TTS", options=VOICES, index=0)
 
638
  mime="audio/mpeg"
639
  )
640
 
641
+ # ๐Ÿ“‘ 14.10 PDF action buttons - wisdom: Create and link PDFs, like crafting portals to knowledge!
642
  col1, col2 = st.columns(2)
643
  with col1:
644
  if st.button("๐Ÿ“‘ Create CrossFile PDFs"):
 
691
  mime="application/pdf"
692
  )
693
 
694
+ # ๐Ÿ–ฅ๏ธ 15. Main PDF generation - wisdom: Spin up the PDF like a weaver at the loom, crafting beauty!
695
  with st.spinner("Generating PDF..."):
696
  pdf_bytes = create_pdf(
697
  st.session_state.markdown_content,
 
704
  total_lines=total_lines
705
  )
706
 
707
+ # ๐Ÿ–ผ๏ธ 16. PDF preview - wisdom: Show the masterpiece before itโ€™s framed, delighting the creator!
708
  with st.container():
709
  st.markdown("### ๐Ÿ“Š PDF Preview")
710
  pdf_images = pdf_to_image(pdf_bytes)
 
714
  else:
715
  st.info("Download the PDF to view it locally.")
716
 
717
+ # ๐Ÿ’พ 17. PDF saver - wisdom: Offer the final scroll, ready to be shared like wisdom across ages!
718
  with st.sidebar:
719
  st.download_button(
720
  label="๐Ÿ’พ Save PDF",