Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import io
|
| 2 |
import re
|
| 3 |
import streamlit as st
|
|
|
|
|
|
|
| 4 |
from PIL import Image
|
| 5 |
import fitz
|
| 6 |
from reportlab.lib.pagesizes import A4
|
|
@@ -13,22 +15,22 @@ from reportlab.pdfbase.ttfonts import TTFont
|
|
| 13 |
st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
|
| 14 |
|
| 15 |
def create_pdf_tab(default_markdown):
|
| 16 |
-
#
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
"
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
}
|
| 25 |
|
| 26 |
# Sidebar configuration
|
| 27 |
with st.sidebar:
|
| 28 |
-
selected_font_name = st.selectbox("Select
|
| 29 |
selected_font_path = available_fonts[selected_font_name]
|
| 30 |
base_font_size = st.slider("Font Size (points)", min_value=6, max_value=16, value=9, step=1) # Default to 9
|
| 31 |
plain_text_mode = st.checkbox("Render as Plain Text (Preserve Bold Only)", value=False)
|
|
|
|
| 32 |
num_columns = st.selectbox("Number of Columns", options=[1, 2, 3, 4, 5, 6], index=3) # Default to 4
|
| 33 |
|
| 34 |
# Markdown editor and buttons
|
|
@@ -42,35 +44,47 @@ def create_pdf_tab(default_markdown):
|
|
| 42 |
|
| 43 |
st.download_button(label="Save Markdown", data=st.session_state.markdown_content, file_name="deities_guide.md", mime="text/markdown")
|
| 44 |
|
| 45 |
-
# Register font
|
|
|
|
| 46 |
pdfmetrics.registerFont(TTFont(selected_font_name, selected_font_path))
|
| 47 |
|
| 48 |
-
# Emoji font application
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
def apply_emoji_font(text, emoji_font):
|
| 50 |
emoji_pattern = re.compile(
|
| 51 |
-
r"([\U0001F300-\U0001F5FF"
|
| 52 |
-
r"\U0001F600-\U0001F64F"
|
| 53 |
-
r"\U0001F680-\U0001F6FF"
|
| 54 |
-
r"\U0001F700-\U0001F77F"
|
| 55 |
-
r"\U0001F780-\U0001F7FF"
|
| 56 |
-
r"\U0001F800-\U0001F8FF"
|
| 57 |
-
r"\U0001F900-\U0001F9FF"
|
| 58 |
-
r"\U0001FA00-\U0001FA6F"
|
| 59 |
-
r"\U0001FA70-\U0001FAFF"
|
| 60 |
-
r"\u2600-\u26FF"
|
| 61 |
-
r"\u2700-\u27BF]+)
|
|
|
|
| 62 |
)
|
| 63 |
def replace_emoji(match):
|
| 64 |
emoji = match.group(1)
|
|
|
|
|
|
|
| 65 |
if len(emoji) > 1:
|
| 66 |
emoji = emoji[0]
|
| 67 |
return f'<font face="{emoji_font}">{emoji}</font>'
|
| 68 |
return emoji_pattern.sub(replace_emoji, text)
|
| 69 |
|
| 70 |
-
#
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
lines = markdown_text.strip().split('\n')
|
| 73 |
pdf_content = []
|
|
|
|
| 74 |
|
| 75 |
if plain_text_mode:
|
| 76 |
for line in lines:
|
|
@@ -88,6 +102,8 @@ def create_pdf_tab(default_markdown):
|
|
| 88 |
if line.startswith('## ') or line.startswith('### '):
|
| 89 |
text = line.replace('## ', '').replace('### ', '').strip()
|
| 90 |
pdf_content.append(f"<b>{text}</b>")
|
|
|
|
|
|
|
| 91 |
else:
|
| 92 |
pdf_content.append(line.strip())
|
| 93 |
|
|
@@ -95,17 +111,20 @@ def create_pdf_tab(default_markdown):
|
|
| 95 |
return pdf_content, total_lines
|
| 96 |
|
| 97 |
# Create PDF
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
buffer = io.BytesIO()
|
| 100 |
-
# Double A4 page: A4 width * 2 (landscape)
|
| 101 |
page_width = A4[0] * 2
|
| 102 |
page_height = A4[1]
|
| 103 |
doc = SimpleDocTemplate(buffer, pagesize=(page_width, page_height), leftMargin=36, rightMargin=36, topMargin=36, bottomMargin=36)
|
| 104 |
styles = getSampleStyleSheet()
|
| 105 |
story = []
|
| 106 |
spacer_height = 10
|
| 107 |
-
section_spacer_height = 15
|
| 108 |
-
pdf_content, total_lines = markdown_to_pdf_content(markdown_text, plain_text_mode)
|
| 109 |
|
| 110 |
item_font_size = base_font_size
|
| 111 |
section_font_size = base_font_size * 1.1
|
|
@@ -125,12 +144,9 @@ def create_pdf_tab(default_markdown):
|
|
| 125 |
current_line_count = 0
|
| 126 |
current_column = 0
|
| 127 |
|
| 128 |
-
# Regex to detect numbered sections (e.g., "1. ", "2. ")
|
| 129 |
number_pattern = re.compile(r'^\d+\.\s')
|
| 130 |
-
|
| 131 |
for i, item in enumerate(pdf_content):
|
| 132 |
-
|
| 133 |
-
if i > 0 and number_pattern.match(item):
|
| 134 |
columns[current_column].append(Spacer(1, section_spacer_height))
|
| 135 |
|
| 136 |
if current_line_count >= lines_per_column and current_column < num_columns - 1:
|
|
@@ -170,7 +186,8 @@ def create_pdf_tab(default_markdown):
|
|
| 170 |
buffer.seek(0)
|
| 171 |
return buffer.getvalue()
|
| 172 |
|
| 173 |
-
# PDF to image
|
|
|
|
| 174 |
def pdf_to_image(pdf_bytes):
|
| 175 |
try:
|
| 176 |
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
|
@@ -185,9 +202,9 @@ def create_pdf_tab(default_markdown):
|
|
| 185 |
st.error(f"Failed to render PDF preview: {e}")
|
| 186 |
return None
|
| 187 |
|
| 188 |
-
# Main logic
|
| 189 |
with st.spinner("Generating PDF..."):
|
| 190 |
-
pdf_bytes = create_pdf(st.session_state.markdown_content, base_font_size, plain_text_mode, num_columns)
|
| 191 |
|
| 192 |
with st.container():
|
| 193 |
pdf_images = pdf_to_image(pdf_bytes)
|
|
@@ -197,7 +214,6 @@ def create_pdf_tab(default_markdown):
|
|
| 197 |
else:
|
| 198 |
st.info("Download the PDF to view it locally.")
|
| 199 |
|
| 200 |
-
# "Download PDF" in sidebar
|
| 201 |
with st.sidebar:
|
| 202 |
st.download_button(label="Download PDF", data=pdf_bytes, file_name="deities_guide.pdf", mime="application/pdf")
|
| 203 |
|
|
@@ -229,7 +245,7 @@ default_markdown = """# Deities Guide: Mythology and Moral Lessons π
|
|
| 229 |
|
| 230 |
5. π Ascension and Signs
|
| 231 |
- Paths: Birth, deeds, revelation, as with Jesus and Arjuna.
|
| 232 |
-
- Signs: Miracles and prophecies, like those in the Quran and Gita.
|
| 233 |
- Morals: Obedience and devotion shape destiny.
|
| 234 |
|
| 235 |
6. π² Storytelling and Games
|
|
|
|
| 1 |
import io
|
| 2 |
import re
|
| 3 |
import streamlit as st
|
| 4 |
+
import glob
|
| 5 |
+
import os
|
| 6 |
from PIL import Image
|
| 7 |
import fitz
|
| 8 |
from reportlab.lib.pagesizes import A4
|
|
|
|
| 15 |
st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
|
| 16 |
|
| 17 |
def create_pdf_tab(default_markdown):
|
| 18 |
+
# Dynamically load all .ttf fonts from the current directory
|
| 19 |
+
# Fonts are sourced from: https://fonts.google.com/download/next-steps
|
| 20 |
+
# We use glob to find all .ttf files, making the font list dynamic instead of hardcoded.
|
| 21 |
+
font_files = glob.glob("*.ttf")
|
| 22 |
+
if not font_files:
|
| 23 |
+
st.error("No .ttf font files found in the current directory. Please add some, e.g., NotoColorEmoji-Regular.ttf.")
|
| 24 |
+
return
|
| 25 |
+
available_fonts = {os.path.splitext(os.path.basename(f))[0]: f for f in font_files}
|
|
|
|
| 26 |
|
| 27 |
# Sidebar configuration
|
| 28 |
with st.sidebar:
|
| 29 |
+
selected_font_name = st.selectbox("Select Font", options=list(available_fonts.keys()), index=0 if "NotoColorEmoji-Regular" in available_fonts else 0)
|
| 30 |
selected_font_path = available_fonts[selected_font_name]
|
| 31 |
base_font_size = st.slider("Font Size (points)", min_value=6, max_value=16, value=9, step=1) # Default to 9
|
| 32 |
plain_text_mode = st.checkbox("Render as Plain Text (Preserve Bold Only)", value=False)
|
| 33 |
+
auto_bold_numbers = st.checkbox("Auto-Bold Numbered Lines", value=False)
|
| 34 |
num_columns = st.selectbox("Number of Columns", options=[1, 2, 3, 4, 5, 6], index=3) # Default to 4
|
| 35 |
|
| 36 |
# Markdown editor and buttons
|
|
|
|
| 44 |
|
| 45 |
st.download_button(label="Save Markdown", data=st.session_state.markdown_content, file_name="deities_guide.md", mime="text/markdown")
|
| 46 |
|
| 47 |
+
# Register the selected font with ReportLab
|
| 48 |
+
# Note: Fonts must be TrueType (.ttf) for ReportLab compatibility.
|
| 49 |
pdfmetrics.registerFont(TTFont(selected_font_name, selected_font_path))
|
| 50 |
|
| 51 |
+
# Emoji font application function
|
| 52 |
+
# This function handles Unicode emojis in the text, applying the selected font to them.
|
| 53 |
+
# We use a regex to match emoji ranges (Unicode blocks like Miscellaneous Symbols, Emoticons, etc.).
|
| 54 |
+
# To avoid multi-character emoji issues (e.g., base char + variation selector U+FE0F), we limit to the first character.
|
| 55 |
+
# Noto Color Emoji (NotoColorEmoji-Regular.ttf) is recommended as it supports a full range of modern emojis,
|
| 56 |
+
# including color rendering, though ReportLab renders in black-and-white by default.
|
| 57 |
def apply_emoji_font(text, emoji_font):
|
| 58 |
emoji_pattern = re.compile(
|
| 59 |
+
r"([\U0001F300-\U0001F5FF" # Miscellaneous Symbols and Pictographs
|
| 60 |
+
r"\U0001F600-\U0001F64F" # Emoticons
|
| 61 |
+
r"\U0001F680-\U0001F6FF" # Transport and Map Symbols
|
| 62 |
+
r"\U0001F700-\U0001F77F" # Alchemical Symbols
|
| 63 |
+
r"\U0001F780-\U0001F7FF" # Geometric Shapes Extended
|
| 64 |
+
r"\U0001F800-\U0001F8FF" # Supplemental Arrows-C
|
| 65 |
+
r"\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs
|
| 66 |
+
r"\U0001FA00-\U0001FA6F" # Chess Symbols
|
| 67 |
+
r"\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A
|
| 68 |
+
r"\u2600-\u26FF" # Miscellaneous Symbols (e.g., β‘ U+26A1)
|
| 69 |
+
r"\u2700-\u27BF]+" # Dingbats (e.g., β U+271D)
|
| 70 |
+
r")"
|
| 71 |
)
|
| 72 |
def replace_emoji(match):
|
| 73 |
emoji = match.group(1)
|
| 74 |
+
# Limit to first character to avoid rendering issues with multi-codepoint emojis
|
| 75 |
+
# Example: βοΈ (U+271D U+FE0F) becomes just β (U+271D), dropping the variation selector
|
| 76 |
if len(emoji) > 1:
|
| 77 |
emoji = emoji[0]
|
| 78 |
return f'<font face="{emoji_font}">{emoji}</font>'
|
| 79 |
return emoji_pattern.sub(replace_emoji, text)
|
| 80 |
|
| 81 |
+
# Convert markdown to PDF content
|
| 82 |
+
# This function processes markdown lines, filtering out headers and applying bolding rules.
|
| 83 |
+
# If auto_bold_numbers is True, lines starting with "number. " (e.g., "1. ") are bolded.
|
| 84 |
+
def markdown_to_pdf_content(markdown_text, plain_text_mode, auto_bold_numbers):
|
| 85 |
lines = markdown_text.strip().split('\n')
|
| 86 |
pdf_content = []
|
| 87 |
+
number_pattern = re.compile(r'^\d+\.\s')
|
| 88 |
|
| 89 |
if plain_text_mode:
|
| 90 |
for line in lines:
|
|
|
|
| 102 |
if line.startswith('## ') or line.startswith('### '):
|
| 103 |
text = line.replace('## ', '').replace('### ', '').strip()
|
| 104 |
pdf_content.append(f"<b>{text}</b>")
|
| 105 |
+
elif auto_bold_numbers and number_pattern.match(line):
|
| 106 |
+
pdf_content.append(f"<b>{line}</b>")
|
| 107 |
else:
|
| 108 |
pdf_content.append(line.strip())
|
| 109 |
|
|
|
|
| 111 |
return pdf_content, total_lines
|
| 112 |
|
| 113 |
# Create PDF
|
| 114 |
+
# This function builds a PDF using ReportLab, arranging content in columns.
|
| 115 |
+
# We use a double A4 landscape layout (A4 width * 2) for wide content.
|
| 116 |
+
# Spacers are added before numbered sections for visual separation.
|
| 117 |
+
# Paragraph styles define font sizes and bolding, with emojis rendered via the selected font.
|
| 118 |
+
def create_pdf(markdown_text, base_font_size, plain_text_mode, num_columns, auto_bold_numbers):
|
| 119 |
buffer = io.BytesIO()
|
|
|
|
| 120 |
page_width = A4[0] * 2
|
| 121 |
page_height = A4[1]
|
| 122 |
doc = SimpleDocTemplate(buffer, pagesize=(page_width, page_height), leftMargin=36, rightMargin=36, topMargin=36, bottomMargin=36)
|
| 123 |
styles = getSampleStyleSheet()
|
| 124 |
story = []
|
| 125 |
spacer_height = 10
|
| 126 |
+
section_spacer_height = 15
|
| 127 |
+
pdf_content, total_lines = markdown_to_pdf_content(markdown_text, plain_text_mode, auto_bold_numbers)
|
| 128 |
|
| 129 |
item_font_size = base_font_size
|
| 130 |
section_font_size = base_font_size * 1.1
|
|
|
|
| 144 |
current_line_count = 0
|
| 145 |
current_column = 0
|
| 146 |
|
|
|
|
| 147 |
number_pattern = re.compile(r'^\d+\.\s')
|
|
|
|
| 148 |
for i, item in enumerate(pdf_content):
|
| 149 |
+
if i > 0 and number_pattern.match(item.replace('<b>', '').replace('</b>', '')):
|
|
|
|
| 150 |
columns[current_column].append(Spacer(1, section_spacer_height))
|
| 151 |
|
| 152 |
if current_line_count >= lines_per_column and current_column < num_columns - 1:
|
|
|
|
| 186 |
buffer.seek(0)
|
| 187 |
return buffer.getvalue()
|
| 188 |
|
| 189 |
+
# Convert PDF to image for preview
|
| 190 |
+
# Uses PyMuPDF (fitz) to render PDF pages as images for Streamlit display.
|
| 191 |
def pdf_to_image(pdf_bytes):
|
| 192 |
try:
|
| 193 |
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
|
|
|
| 202 |
st.error(f"Failed to render PDF preview: {e}")
|
| 203 |
return None
|
| 204 |
|
| 205 |
+
# Main logic
|
| 206 |
with st.spinner("Generating PDF..."):
|
| 207 |
+
pdf_bytes = create_pdf(st.session_state.markdown_content, base_font_size, plain_text_mode, num_columns, auto_bold_numbers)
|
| 208 |
|
| 209 |
with st.container():
|
| 210 |
pdf_images = pdf_to_image(pdf_bytes)
|
|
|
|
| 214 |
else:
|
| 215 |
st.info("Download the PDF to view it locally.")
|
| 216 |
|
|
|
|
| 217 |
with st.sidebar:
|
| 218 |
st.download_button(label="Download PDF", data=pdf_bytes, file_name="deities_guide.pdf", mime="application/pdf")
|
| 219 |
|
|
|
|
| 245 |
|
| 246 |
5. π Ascension and Signs
|
| 247 |
- Paths: Birth, deeds, revelation, as with Jesus and Arjuna.
|
| 248 |
+
- Signs: Miracles and prophecies, like those in the Quran and Gita Kobe.
|
| 249 |
- Morals: Obedience and devotion shape destiny.
|
| 250 |
|
| 251 |
6. π² Storytelling and Games
|