Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
@@ -108,15 +108,19 @@ def apply_emoji_font(text: str, emoji_font_name: str) -> str:
|
|
108 |
# --- PDF Generation & Handling ---
|
109 |
|
110 |
def markdown_to_story(markdown_text: str, font_name: str, emoji_font: str):
|
111 |
-
"""
|
|
|
|
|
|
|
112 |
styles = getSampleStyleSheet()
|
|
|
113 |
style_normal = ParagraphStyle('BodyText', fontName=font_name, spaceAfter=6, leading=14, fontSize=10)
|
114 |
style_h1 = ParagraphStyle('h1', parent=styles['h1'], fontName=font_name, spaceBefore=12, fontSize=24, leading=28, textColor=colors.darkblue)
|
115 |
style_h2 = ParagraphStyle('h2', parent=styles['h2'], fontName=font_name, fontSize=18, leading=22, spaceBefore=10)
|
116 |
style_h3 = ParagraphStyle('h3', parent=styles['h3'], fontName=font_name, fontSize=14, leading=18, spaceBefore=8)
|
117 |
style_code = ParagraphStyle('Code', fontName='Courier', backColor=colors.whitesmoke, textColor=colors.darkred, borderWidth=1, borderColor=colors.lightgrey, padding=8, leading=12, fontSize=9)
|
118 |
style_table_header = ParagraphStyle('TableHeader', parent=style_normal, fontName=font_name + "-Bold" if font_name != 'Helvetica' else 'Helvetica-Bold')
|
119 |
-
|
120 |
story = []
|
121 |
lines = markdown_text.split('\n')
|
122 |
|
@@ -125,51 +129,70 @@ def markdown_to_story(markdown_text: str, font_name: str, emoji_font: str):
|
|
125 |
first_heading = True
|
126 |
|
127 |
for line in lines:
|
128 |
-
|
|
|
|
|
129 |
if in_code_block:
|
130 |
story.append(Paragraph(code_block_text.replace('\n', '<br/>'), style_code)); story.append(Spacer(1, 0.1 * inch))
|
131 |
in_code_block = False; code_block_text = ""
|
132 |
-
else:
|
133 |
-
in_code_block = True
|
134 |
continue
|
135 |
if in_code_block:
|
136 |
code_block_text += line.replace('&', '&').replace('<', '<').replace('>', '>') + '\n'
|
137 |
continue
|
138 |
|
139 |
-
if
|
140 |
if not in_table: in_table = True
|
141 |
-
if all(c in '-|: ' for c in
|
142 |
-
cells = [
|
143 |
table_data.append(cells)
|
144 |
continue
|
145 |
if in_table:
|
146 |
in_table = False
|
147 |
if table_data:
|
148 |
-
|
149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
table = Table([header] + formatted_rows, hAlign='LEFT', repeatRows=1)
|
151 |
table.setStyle(TableStyle([('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), ('GRID', (0, 0), (-1, -1), 1, colors.darkgrey), ('VALIGN', (0,0), (-1,-1), 'MIDDLE')]))
|
152 |
story.append(table); story.append(Spacer(1, 0.2 * inch))
|
153 |
-
|
154 |
-
|
155 |
-
# Apply base formatting first
|
156 |
-
formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line))
|
157 |
-
# Then apply emoji font to the already formatted line
|
158 |
-
line_with_emoji = apply_emoji_font(formatted_line, emoji_font)
|
159 |
|
160 |
-
if
|
161 |
-
|
162 |
-
|
163 |
-
story.append(Paragraph(apply_emoji_font(re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line[2:]), emoji_font), style_h1)); first_heading = False
|
164 |
-
elif line.startswith("## "): story.append(Paragraph(apply_emoji_font(re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line[3:]), emoji_font), style_h2))
|
165 |
-
elif line.startswith("### "): story.append(Paragraph(apply_emoji_font(re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line[4:]), emoji_font), style_h3))
|
166 |
-
elif line.strip().startswith(("- ", "* ")): story.append(Paragraph(line_with_emoji.strip()[2:], style_normal, bulletText='β’'))
|
167 |
-
elif re.match(r'^\d+\.\s', line.strip()): story.append(Paragraph(line_with_emoji.strip(), style_normal))
|
168 |
-
elif line.strip() == "": story.append(Spacer(1, 0.1 * inch))
|
169 |
-
else: story.append(Paragraph(line_with_emoji, style_normal))
|
170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
return story
|
172 |
|
|
|
173 |
def create_pdf_preview(pdf_path: Path):
|
174 |
preview_path = PREVIEW_DIR / f"{pdf_path.stem}.png"
|
175 |
try:
|
@@ -247,27 +270,27 @@ SAMPLE_MARKDOWN = """# Deities Guide: Mythology and Moral Lessons
|
|
247 |
1. π **Introduction**
|
248 |
- **Purpose**: Explore deities, spirits, saints, and beings with their epic stories and morals!
|
249 |
- **Usage**: A guide for learning and storytelling across traditions. οΈ
|
250 |
-
- **Themes**: Justice
|
251 |
|
252 |
2. π οΈ **Core Concepts of Divinity**
|
253 |
-
- **Powers**: Creation
|
254 |
-
- **Life Cycle**: Mortality
|
255 |
-
- **Communication**: Omens
|
256 |
|
257 |
# βοΈ Arthurian Legends
|
258 |
-
- **Merlin, Morgan le Fay, Arthur**: Mentor
|
259 |
-
- **Relation**: Family tests loyalty
|
260 |
-
- **Lesson**: Honor
|
261 |
|
262 |
# ποΈ Greek Mythology
|
263 |
-
- **Zeus, Hera, Athena**: Father
|
264 |
-
- **Relation**: Family rules with tension
|
265 |
-
- **Lesson**: Hubris
|
266 |
|
267 |
# ποΈ Hindu Trimurti
|
268 |
-
- **Brahma, Vishnu, Shiva**: Creator
|
269 |
-
- **Relation**: Divine trio cycles existence
|
270 |
-
- **Lesson**: Balance
|
271 |
"""
|
272 |
with open(CWD / "sample.md", "w", encoding="utf-8") as f: f.write(SAMPLE_MARKDOWN)
|
273 |
|
|
|
108 |
# --- PDF Generation & Handling ---
|
109 |
|
110 |
def markdown_to_story(markdown_text: str, font_name: str, emoji_font: str):
|
111 |
+
"""
|
112 |
+
Converts markdown to a ReportLab story, with enhanced styling and page breaks.
|
113 |
+
This version correctly separates structural parsing from content formatting.
|
114 |
+
"""
|
115 |
styles = getSampleStyleSheet()
|
116 |
+
# Define styles for various markdown elements
|
117 |
style_normal = ParagraphStyle('BodyText', fontName=font_name, spaceAfter=6, leading=14, fontSize=10)
|
118 |
style_h1 = ParagraphStyle('h1', parent=styles['h1'], fontName=font_name, spaceBefore=12, fontSize=24, leading=28, textColor=colors.darkblue)
|
119 |
style_h2 = ParagraphStyle('h2', parent=styles['h2'], fontName=font_name, fontSize=18, leading=22, spaceBefore=10)
|
120 |
style_h3 = ParagraphStyle('h3', parent=styles['h3'], fontName=font_name, fontSize=14, leading=18, spaceBefore=8)
|
121 |
style_code = ParagraphStyle('Code', fontName='Courier', backColor=colors.whitesmoke, textColor=colors.darkred, borderWidth=1, borderColor=colors.lightgrey, padding=8, leading=12, fontSize=9)
|
122 |
style_table_header = ParagraphStyle('TableHeader', parent=style_normal, fontName=font_name + "-Bold" if font_name != 'Helvetica' else 'Helvetica-Bold')
|
123 |
+
|
124 |
story = []
|
125 |
lines = markdown_text.split('\n')
|
126 |
|
|
|
129 |
first_heading = True
|
130 |
|
131 |
for line in lines:
|
132 |
+
stripped_line = line.strip()
|
133 |
+
|
134 |
+
if stripped_line.startswith("```"):
|
135 |
if in_code_block:
|
136 |
story.append(Paragraph(code_block_text.replace('\n', '<br/>'), style_code)); story.append(Spacer(1, 0.1 * inch))
|
137 |
in_code_block = False; code_block_text = ""
|
138 |
+
else: in_code_block = True
|
|
|
139 |
continue
|
140 |
if in_code_block:
|
141 |
code_block_text += line.replace('&', '&').replace('<', '<').replace('>', '>') + '\n'
|
142 |
continue
|
143 |
|
144 |
+
if stripped_line.startswith('|'):
|
145 |
if not in_table: in_table = True
|
146 |
+
if all(c in '-|: ' for c in stripped_line): continue
|
147 |
+
cells = [cell.strip() for cell in stripped_line.strip('|').split('|')]
|
148 |
table_data.append(cells)
|
149 |
continue
|
150 |
if in_table:
|
151 |
in_table = False
|
152 |
if table_data:
|
153 |
+
header_content = [apply_emoji_font(re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', cell), emoji_font) for cell in table_data[0]]
|
154 |
+
header = [Paragraph(cell, style_table_header) for cell in header_content]
|
155 |
+
|
156 |
+
formatted_rows = []
|
157 |
+
for row in table_data[1:]:
|
158 |
+
formatted_cells = [apply_emoji_font(re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', cell), emoji_font) for cell in row]
|
159 |
+
formatted_rows.append([Paragraph(cell, style_normal) for cell in formatted_cells])
|
160 |
+
|
161 |
table = Table([header] + formatted_rows, hAlign='LEFT', repeatRows=1)
|
162 |
table.setStyle(TableStyle([('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), ('GRID', (0, 0), (-1, -1), 1, colors.darkgrey), ('VALIGN', (0,0), (-1,-1), 'MIDDLE')]))
|
163 |
story.append(table); story.append(Spacer(1, 0.2 * inch))
|
164 |
+
table_data = []
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
+
if not stripped_line:
|
167 |
+
story.append(Spacer(1, 0.1 * inch))
|
168 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
|
170 |
+
# Default content is the whole stripped line
|
171 |
+
content = stripped_line
|
172 |
+
style = style_normal
|
173 |
+
extra_args = {}
|
174 |
+
|
175 |
+
# Detect structural elements and extract the raw content
|
176 |
+
if stripped_line.startswith("# "):
|
177 |
+
if not first_heading: story.append(PageBreak())
|
178 |
+
content = stripped_line.lstrip('# '); style = style_h1; first_heading = False
|
179 |
+
elif stripped_line.startswith("## "):
|
180 |
+
content = stripped_line.lstrip('## '); style = style_h2
|
181 |
+
elif stripped_line.startswith("### "):
|
182 |
+
content = stripped_line.lstrip('### '); style = style_h3
|
183 |
+
elif stripped_line.startswith(("- ", "* ")):
|
184 |
+
content = stripped_line[2:]; extra_args['bulletText'] = 'β’'
|
185 |
+
|
186 |
+
# Now, format the extracted content
|
187 |
+
# Apply markdown formatting for bold/italic
|
188 |
+
formatted_content = re.sub(r'_(.*?)_', r'<i>\1</i>', re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', content))
|
189 |
+
# Then apply emoji font to the already formatted line
|
190 |
+
final_content = apply_emoji_font(formatted_content, emoji_font)
|
191 |
+
|
192 |
+
story.append(Paragraph(final_content, style, **extra_args))
|
193 |
return story
|
194 |
|
195 |
+
|
196 |
def create_pdf_preview(pdf_path: Path):
|
197 |
preview_path = PREVIEW_DIR / f"{pdf_path.stem}.png"
|
198 |
try:
|
|
|
270 |
1. π **Introduction**
|
271 |
- **Purpose**: Explore deities, spirits, saints, and beings with their epic stories and morals!
|
272 |
- **Usage**: A guide for learning and storytelling across traditions. οΈ
|
273 |
+
- **Themes**: Justice βοΈ, faith π, hubris ποΈ, redemption β¨, cosmic order π.
|
274 |
|
275 |
2. π οΈ **Core Concepts of Divinity**
|
276 |
+
- **Powers**: Creation π, omniscience ποΈβπ¨οΈ, shapeshifting π¦ across entities.
|
277 |
+
- **Life Cycle**: Mortality β³, immortality βΎοΈ, transitions like saints and avatars π.
|
278 |
+
- **Communication**: Omens ποΈ, visions ποΈ, miracles β¨ from gods and spirits.
|
279 |
|
280 |
# βοΈ Arthurian Legends
|
281 |
+
- **Merlin, Morgan le Fay, Arthur**: Mentor π§, rival π§ββοΈ, son π.
|
282 |
+
- **Relation**: Family tests loyalty π€.
|
283 |
+
- **Lesson**: Honor ποΈ vs. betrayal π‘οΈ.
|
284 |
|
285 |
# ποΈ Greek Mythology
|
286 |
+
- **Zeus, Hera, Athena**: Father β‘, mother π, daughter π¦.
|
287 |
+
- **Relation**: Family rules with tension π©οΈ.
|
288 |
+
- **Lesson**: Hubris ΰ€
ΰ€Ήΰ€ΰ€ΰ€Ύΰ€° meets wisdom π§ .
|
289 |
|
290 |
# ποΈ Hindu Trimurti
|
291 |
+
- **Brahma, Vishnu, Shiva**: Creator Brahma, preserver Vishnu, destroyer Shiva.
|
292 |
+
- **Relation**: Divine trio cycles existence π.
|
293 |
+
- **Lesson**: Balance βοΈ sustains life π.
|
294 |
"""
|
295 |
with open(CWD / "sample.md", "w", encoding="utf-8") as f: f.write(SAMPLE_MARKDOWN)
|
296 |
|