awacke1 commited on
Commit
1f34dc9
Β·
verified Β·
1 Parent(s): f258b94

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -56
app.py CHANGED
@@ -27,8 +27,8 @@ LAYOUTS = {
27
  OUTPUT_DIR = Path("generated_pdfs")
28
  OUTPUT_DIR.mkdir(exist_ok=True)
29
 
30
- # Path for the required emoji font file
31
- EMOJI_FONT_PATH = Path("NotoColorEmoji-Regular.ttf")
32
 
33
  # Regex to find and wrap emojis for ReportLab
34
  EMOJI_PATTERN = re.compile(
@@ -52,7 +52,6 @@ EMOJI_PATTERN = re.compile(
52
 
53
  class PDFGenerator:
54
  """
55
- An object-oriented approach to generating PDFs.
56
  Handles font registration, markdown parsing, and PDF creation.
57
  """
58
  def __init__(self, font_path: Path):
@@ -69,9 +68,8 @@ class PDFGenerator:
69
  Registers the TTF font file with ReportLab if the file exists.
70
  """
71
  if font_path.exists():
72
- pdfmetrics.registerFont(TTFont(self.emoji_font_name, font_path))
73
  else:
74
- # Provide a helpful error in the web app if the font is missing
75
  st.error(f"Emoji font not found at '{font_path}'. Emojis will not be rendered. Please download it.")
76
  self.emoji_font_name = "Helvetica" # Fallback to a standard font
77
 
@@ -80,11 +78,8 @@ class PDFGenerator:
80
  πŸ˜€ To make emojis appear so grand, wrap them with a font command.
81
  Finds all emojis and wraps them in ReportLab <font> tags.
82
  """
83
- # If the emoji font failed to register, don't try to use it.
84
  if self.emoji_font_name != "NotoEmoji":
85
  return text
86
-
87
- # The lambda function takes each matched emoji (m) and wraps it.
88
  return EMOJI_PATTERN.sub(lambda m: f'<font name="{self.emoji_font_name}">{m.group(0)}</font>', text)
89
 
90
  def _markdown_to_story(self, markdown_text: str) -> list:
@@ -93,13 +88,10 @@ class PDFGenerator:
93
  Converts a markdown string πŸ“ into a list of ReportLab Flowables (a 'story').
94
  """
95
  styles = getSampleStyleSheet()
96
-
97
- # Define custom styles for different markdown elements
98
  style_normal = styles['BodyText']
99
  style_h1 = styles['h1']
100
  style_h2 = styles['h2']
101
  style_h3 = styles['h3']
102
- # Use a monospaced font for code blocks
103
  style_code = ParagraphStyle('Code', parent=styles['Normal'], fontName='Courier', textColor=colors.darkred)
104
 
105
  story = []
@@ -108,7 +100,6 @@ class PDFGenerator:
108
  code_block_text = ""
109
 
110
  for line in lines:
111
- # Handle code blocks (```)
112
  if line.strip().startswith("```"):
113
  if in_code_block:
114
  story.append(Paragraph(code_block_text, style_code))
@@ -119,33 +110,35 @@ class PDFGenerator:
119
  continue
120
 
121
  if in_code_block:
122
- # Escape HTML-sensitive characters and preserve line breaks within code
123
  escaped_line = line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
124
  code_block_text += escaped_line + '<br/>'
125
  continue
126
-
127
- # This is where we process each line for emojis BEFORE creating a Paragraph
128
- processed_line = self._wrap_emojis_for_reportlab(line)
129
 
130
- # Handle markdown syntax
131
- if processed_line.startswith("# "):
132
- story.append(Paragraph(self._wrap_emojis_for_reportlab(processed_line[2:]), style_h1))
133
- elif processed_line.startswith("## "):
134
- story.append(Paragraph(self._wrap_emojis_for_reportlab(processed_line[3:]), style_h2))
135
- elif processed_line.startswith("### "):
136
- story.append(Paragraph(self._wrap_emojis_for_reportlab(processed_line[4:]), style_h3))
137
- elif processed_line.strip().startswith(("* ", "- ")):
138
- story.append(Paragraph(f"β€’ {self._wrap_emojis_for_reportlab(processed_line.strip()[2:])}", style_normal))
139
- elif re.match(r'^\d+\.\s', processed_line.strip()):
140
- story.append(Paragraph(processed_line.strip(), style_normal))
141
- elif processed_line.strip() == "":
 
 
 
 
 
142
  story.append(Spacer(1, 0.2 * inch))
143
  else:
144
- # Handle bold (**) and italics (_) using ReportLab's rich text tags
145
- formatted_line = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', processed_line)
146
  formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', formatted_line)
147
- story.append(Paragraph(formatted_line, style_normal))
148
-
 
149
  return story
150
 
151
  def create_pdf(self, md_asset: Path, layout_name: str, layout_properties: dict):
@@ -155,26 +148,18 @@ class PDFGenerator:
155
  """
156
  try:
157
  md_content = md_asset.read_text(encoding="utf-8")
158
-
159
  date_str = datetime.datetime.now().strftime("%Y-%m-%d")
160
  output_filename = f"{md_asset.stem}_{layout_name.replace(' ', '-')}_{date_str}.pdf"
161
  output_path = OUTPUT_DIR / output_filename
162
 
163
- # The SimpleDocTemplate handles the page creation and content flow
164
  doc = SimpleDocTemplate(
165
  str(output_path),
166
  pagesize=layout_properties.get("size", A4),
167
- rightMargin=inch,
168
- leftMargin=inch,
169
- topMargin=inch,
170
- bottomMargin=inch
171
  )
172
-
173
  story = self._markdown_to_story(md_content)
174
-
175
- # The .build() method takes the story and renders the PDF
176
  doc.build(story)
177
-
178
  except Exception as e:
179
  st.error(f"Failed to process {md_asset.name} with ReportLab: {e}")
180
 
@@ -184,7 +169,6 @@ class PDFGenerator:
184
  def get_file_download_link(file_path: Path) -> str:
185
  """
186
  πŸ”— To grab your file and not delay, a special link is paved today.
187
- Generates a base64-encoded download link for a file.
188
  """
189
  with open(file_path, "rb") as f:
190
  data = base64.b64encode(f.read()).decode()
@@ -193,10 +177,8 @@ def get_file_download_link(file_path: Path) -> str:
193
  def display_file_explorer():
194
  """
195
  πŸ“‚ To see your files, both old and new, this handy explorer gives a view.
196
- Renders a simple file explorer in the Streamlit app for MD and PDF files.
197
  """
198
  st.header("πŸ“‚ File Explorer")
199
-
200
  st.subheader("Source Markdown Files (.md)")
201
  md_files = list(Path(".").glob("*.md"))
202
  if not md_files:
@@ -210,7 +192,6 @@ def display_file_explorer():
210
  st.markdown(get_file_download_link(md_file), unsafe_allow_html=True)
211
 
212
  st.subheader("Generated PDF Files")
213
- # Sort PDFs by modification time to show the newest first
214
  pdf_files = sorted(list(OUTPUT_DIR.glob("*.pdf")), key=lambda p: p.stat().st_mtime, reverse=True)
215
  if not pdf_files:
216
  st.info("No PDFs generated yet. Click the button above to start.")
@@ -222,7 +203,6 @@ def display_file_explorer():
222
  with col2:
223
  st.markdown(get_file_download_link(pdf_file), unsafe_allow_html=True)
224
 
225
-
226
  # --- Main App Execution ---
227
 
228
  def main():
@@ -233,41 +213,33 @@ def main():
233
  st.title("πŸ“„ Markdown to PDF Generator")
234
  st.markdown("This tool converts all `.md` files in this directory to PDF. It now supports emojis! πŸ‘")
235
 
236
- # Create a sample markdown file if none exist, to help new users.
237
- if not list(Path(".").glob("*.md")):
238
  with open("sample.md", "w", encoding="utf-8") as f:
239
  f.write("# Sample Document πŸ‘\n\nThis is a sample markdown file. **ReportLab** is creating the PDF. Emojis like πŸš€ and πŸ’‘ should now appear correctly.\n\n### Features\n- Item 1\n- Item 2\n\n```\ndef hello_world():\n print(\"Hello, PDF! πŸ‘‹\")\n```\n")
240
  st.rerun()
241
 
242
- # Instantiate our generator. It will handle font setup on its own.
243
  pdf_generator = PDFGenerator(EMOJI_FONT_PATH)
244
 
245
  if st.button("πŸš€ Generate PDFs from all Markdown Files", type="primary"):
246
  markdown_files = list(Path(".").glob("*.md"))
247
-
248
  if not markdown_files:
249
  st.warning("No `.md` files found. Please add a markdown file to the directory.")
250
  else:
251
  total_pdfs = len(markdown_files) * len(LAYOUTS)
252
  progress_bar = st.progress(0, text="Starting PDF generation...")
253
  pdf_count = 0
254
-
255
  with st.spinner("Generating PDFs... Please wait."):
256
  for md_file in markdown_files:
257
  st.info(f"Processing: **{md_file.name}**")
258
  for name, properties in LAYOUTS.items():
259
- # Use the instance method to create the PDF
260
  pdf_generator.create_pdf(md_file, name, properties)
261
  pdf_count += 1
262
  progress_bar.progress(pdf_count / total_pdfs, f"Generated {pdf_count}/{total_pdfs} PDFs...")
263
-
264
  st.success("βœ… PDF generation complete!")
265
  st.balloons()
266
- # Rerun to refresh the file explorer immediately
267
  st.rerun()
268
 
269
  display_file_explorer()
270
 
271
-
272
  if __name__ == "__main__":
273
  main()
 
27
  OUTPUT_DIR = Path("generated_pdfs")
28
  OUTPUT_DIR.mkdir(exist_ok=True)
29
 
30
+ # ⚠️ UPDATED: Path for the required NON-COLOR emoji font file.
31
+ EMOJI_FONT_PATH = Path("NotoEmoji-Regular.ttf")
32
 
33
  # Regex to find and wrap emojis for ReportLab
34
  EMOJI_PATTERN = re.compile(
 
52
 
53
  class PDFGenerator:
54
  """
 
55
  Handles font registration, markdown parsing, and PDF creation.
56
  """
57
  def __init__(self, font_path: Path):
 
68
  Registers the TTF font file with ReportLab if the file exists.
69
  """
70
  if font_path.exists():
71
+ pdfmetrics.registerFont(TTFont(self.emoji_font_name, str(font_path)))
72
  else:
 
73
  st.error(f"Emoji font not found at '{font_path}'. Emojis will not be rendered. Please download it.")
74
  self.emoji_font_name = "Helvetica" # Fallback to a standard font
75
 
 
78
  πŸ˜€ To make emojis appear so grand, wrap them with a font command.
79
  Finds all emojis and wraps them in ReportLab <font> tags.
80
  """
 
81
  if self.emoji_font_name != "NotoEmoji":
82
  return text
 
 
83
  return EMOJI_PATTERN.sub(lambda m: f'<font name="{self.emoji_font_name}">{m.group(0)}</font>', text)
84
 
85
  def _markdown_to_story(self, markdown_text: str) -> list:
 
88
  Converts a markdown string πŸ“ into a list of ReportLab Flowables (a 'story').
89
  """
90
  styles = getSampleStyleSheet()
 
 
91
  style_normal = styles['BodyText']
92
  style_h1 = styles['h1']
93
  style_h2 = styles['h2']
94
  style_h3 = styles['h3']
 
95
  style_code = ParagraphStyle('Code', parent=styles['Normal'], fontName='Courier', textColor=colors.darkred)
96
 
97
  story = []
 
100
  code_block_text = ""
101
 
102
  for line in lines:
 
103
  if line.strip().startswith("```"):
104
  if in_code_block:
105
  story.append(Paragraph(code_block_text, style_code))
 
110
  continue
111
 
112
  if in_code_block:
 
113
  escaped_line = line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
114
  code_block_text += escaped_line + '<br/>'
115
  continue
 
 
 
116
 
117
+ # Process the line for markdown syntax first
118
+ if line.startswith("# "):
119
+ final_text = self._wrap_emojis_for_reportlab(line[2:])
120
+ story.append(Paragraph(final_text, style_h1))
121
+ elif line.startswith("## "):
122
+ final_text = self._wrap_emojis_for_reportlab(line[3:])
123
+ story.append(Paragraph(final_text, style_h2))
124
+ elif line.startswith("### "):
125
+ final_text = self._wrap_emojis_for_reportlab(line[4:])
126
+ story.append(Paragraph(final_text, style_h3))
127
+ elif line.strip().startswith(("* ", "- ")):
128
+ final_text = self._wrap_emojis_for_reportlab(line.strip()[2:])
129
+ story.append(Paragraph(f"β€’ {final_text}", style_normal))
130
+ elif re.match(r'^\d+\.\s', line.strip()):
131
+ final_text = self._wrap_emojis_for_reportlab(line.strip())
132
+ story.append(Paragraph(final_text, style_normal))
133
+ elif line.strip() == "":
134
  story.append(Spacer(1, 0.2 * inch))
135
  else:
136
+ # Handle bold/italics, then wrap emojis in the final string
137
+ formatted_line = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line)
138
  formatted_line = re.sub(r'_(.*?)_', r'<i>\1</i>', formatted_line)
139
+ final_text = self._wrap_emojis_for_reportlab(formatted_line)
140
+ story.append(Paragraph(final_text, style_normal))
141
+
142
  return story
143
 
144
  def create_pdf(self, md_asset: Path, layout_name: str, layout_properties: dict):
 
148
  """
149
  try:
150
  md_content = md_asset.read_text(encoding="utf-8")
 
151
  date_str = datetime.datetime.now().strftime("%Y-%m-%d")
152
  output_filename = f"{md_asset.stem}_{layout_name.replace(' ', '-')}_{date_str}.pdf"
153
  output_path = OUTPUT_DIR / output_filename
154
 
 
155
  doc = SimpleDocTemplate(
156
  str(output_path),
157
  pagesize=layout_properties.get("size", A4),
158
+ rightMargin=inch, leftMargin=inch,
159
+ topMargin=inch, bottomMargin=inch
 
 
160
  )
 
161
  story = self._markdown_to_story(md_content)
 
 
162
  doc.build(story)
 
163
  except Exception as e:
164
  st.error(f"Failed to process {md_asset.name} with ReportLab: {e}")
165
 
 
169
  def get_file_download_link(file_path: Path) -> str:
170
  """
171
  πŸ”— To grab your file and not delay, a special link is paved today.
 
172
  """
173
  with open(file_path, "rb") as f:
174
  data = base64.b64encode(f.read()).decode()
 
177
  def display_file_explorer():
178
  """
179
  πŸ“‚ To see your files, both old and new, this handy explorer gives a view.
 
180
  """
181
  st.header("πŸ“‚ File Explorer")
 
182
  st.subheader("Source Markdown Files (.md)")
183
  md_files = list(Path(".").glob("*.md"))
184
  if not md_files:
 
192
  st.markdown(get_file_download_link(md_file), unsafe_allow_html=True)
193
 
194
  st.subheader("Generated PDF Files")
 
195
  pdf_files = sorted(list(OUTPUT_DIR.glob("*.pdf")), key=lambda p: p.stat().st_mtime, reverse=True)
196
  if not pdf_files:
197
  st.info("No PDFs generated yet. Click the button above to start.")
 
203
  with col2:
204
  st.markdown(get_file_download_link(pdf_file), unsafe_allow_html=True)
205
 
 
206
  # --- Main App Execution ---
207
 
208
  def main():
 
213
  st.title("πŸ“„ Markdown to PDF Generator")
214
  st.markdown("This tool converts all `.md` files in this directory to PDF. It now supports emojis! πŸ‘")
215
 
216
+ if not any(Path(".").glob("*.md")):
 
217
  with open("sample.md", "w", encoding="utf-8") as f:
218
  f.write("# Sample Document πŸ‘\n\nThis is a sample markdown file. **ReportLab** is creating the PDF. Emojis like πŸš€ and πŸ’‘ should now appear correctly.\n\n### Features\n- Item 1\n- Item 2\n\n```\ndef hello_world():\n print(\"Hello, PDF! πŸ‘‹\")\n```\n")
219
  st.rerun()
220
 
 
221
  pdf_generator = PDFGenerator(EMOJI_FONT_PATH)
222
 
223
  if st.button("πŸš€ Generate PDFs from all Markdown Files", type="primary"):
224
  markdown_files = list(Path(".").glob("*.md"))
 
225
  if not markdown_files:
226
  st.warning("No `.md` files found. Please add a markdown file to the directory.")
227
  else:
228
  total_pdfs = len(markdown_files) * len(LAYOUTS)
229
  progress_bar = st.progress(0, text="Starting PDF generation...")
230
  pdf_count = 0
 
231
  with st.spinner("Generating PDFs... Please wait."):
232
  for md_file in markdown_files:
233
  st.info(f"Processing: **{md_file.name}**")
234
  for name, properties in LAYOUTS.items():
 
235
  pdf_generator.create_pdf(md_file, name, properties)
236
  pdf_count += 1
237
  progress_bar.progress(pdf_count / total_pdfs, f"Generated {pdf_count}/{total_pdfs} PDFs...")
 
238
  st.success("βœ… PDF generation complete!")
239
  st.balloons()
 
240
  st.rerun()
241
 
242
  display_file_explorer()
243
 
 
244
  if __name__ == "__main__":
245
  main()