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
|