lexicalspace commited on
Commit
569e7a5
·
verified ·
1 Parent(s): df96de6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -142
app.py CHANGED
@@ -32,19 +32,24 @@ from io import BytesIO
32
 
33
 
34
 
 
 
 
 
 
 
 
35
  def run_ultimate_pdf_converter():
36
  """
37
- The Ultimate Text-to-PDF Converter.
38
- Contains ~55 features grouped into:
39
- 1. Smart Typography (Symbols, Quotes)
40
- 2. Markdown Engine (Headers, Tables, Code Blocks, Lists)
41
- 3. Media Handler (Images, Links)
42
- 4. Layout Engine (Margins, Orientation, Fonts)
43
- 5. LMS Sanitizer (Cleaning junk text)
44
  """
45
 
46
- # --- CONSTANTS & CONFIG ---
47
- # Feature Group 1: Smart Symbol Map (20+ symbols)
48
  SMART_SYMBOLS = {
49
  r'<->': '↔', r'->': '→', r'<-': '←', r'=>': '⇒', r'<=': '≤', r'>=': '≥', r'!=': '≠',
50
  r'\.\.\.': '…', r'\(c\)': '©', r'\(r\)': '®', r'\(tm\)': '™',
@@ -54,124 +59,131 @@ def run_ultimate_pdf_converter():
54
  r'deg': '°', r'infinity': '∞', r'sqrt': '√'
55
  }
56
 
57
- # --- INTERNAL CLASS: PDF GENERATOR ---
58
  class UltimatePDF(FPDF):
59
- def __init__(self, orientation='P', unit='mm', format='A4', font_cache_dir="."):
60
  super().__init__(orientation=orientation, unit=unit, format=format)
61
- self.font_cache_dir = font_cache_dir
62
- self.ensure_fonts()
63
  self.set_auto_page_break(auto=True, margin=15)
 
 
64
 
65
  def ensure_fonts(self):
66
- # Feature: Auto-download Unicode Font
67
- font_path = os.path.join(self.font_cache_dir, "DejaVuSans.ttf")
68
  font_url = "https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans.ttf"
69
- if not os.path.exists(font_path):
70
- try:
71
- r = requests.get(font_url, timeout=10)
72
- with open(font_path, "wb") as f:
73
- f.write(r.content)
74
- except:
75
- pass # Fallback handled later
76
 
77
- if os.path.exists(font_path):
78
- self.add_font('DejaVu', '', font_path, uni=True)
79
- self.add_font('DejaVu', 'B', font_path, uni=True) # Bold attempt
80
- self.main_font = 'DejaVu'
81
- else:
82
- self.main_font = 'Arial'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  def header(self):
85
- # Feature: Automatic Header with Date/Page
86
  if getattr(self, 'show_header', False):
87
  self.set_font(self.main_font, '', 8)
88
  self.set_text_color(128)
89
- self.cell(0, 10, f'Generated by Ultimate PDF | {self.title_meta}', 0, 0, 'R')
90
  self.ln(10)
91
 
92
  def footer(self):
93
- # Feature: Page Numbering
94
  self.set_y(-15)
95
  self.set_font(self.main_font, '', 8)
96
  self.set_text_color(128)
97
  self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
98
 
 
99
  def add_markdown_header(self, text, level):
100
- # Feature: Dynamic Header Sizes (H1, H2, H3)
101
- sizes = {1: 24, 2: 18, 3: 14}
102
  self.set_font(self.main_font, '', sizes.get(level, 12))
103
- self.set_text_color(0, 50, 100) # Navy Blue
104
  self.cell(0, 10, text, ln=True)
105
- self.set_text_color(0) # Reset
106
  self.set_font(self.main_font, '', 12)
107
 
108
  def add_code_block(self, code_lines):
109
- # Feature: Code Block Formatting (Gray background, Monospace)
110
  self.set_font("Courier", size=10)
111
- self.set_fill_color(240, 240, 240) # Light Gray
112
  for line in code_lines:
113
- self.cell(0, 6, line, ln=True, fill=True, border=0)
114
- self.set_font(self.main_font, '', 12) # Reset
115
- self.ln(2)
 
 
116
 
117
  def add_table(self, table_lines):
118
- # Feature: ASCII Table Parsing (Lines with |)
119
  self.set_font(self.main_font, '', 10)
120
- cell_height = 8
121
  for row in table_lines:
122
  cols = [c.strip() for c in row.split('|') if c.strip()]
123
  if not cols: continue
124
-
125
- # Dynamic width calculation
126
- col_width = (self.w - 30) // len(cols)
127
  for col in cols:
128
- self.cell(col_width, cell_height, col, border=1)
129
  self.ln()
130
- self.set_font(self.main_font, '', 12) # Reset
131
  self.ln(5)
132
 
133
  def add_blockquote(self, text):
134
- # Feature: Blockquotes (Indented, Italic)
135
- self.set_font(self.main_font, '', 12)
136
- self.set_text_color(100)
137
- self.set_x(self.l_margin + 10) # Indent
138
- self.multi_cell(0, 8, f"“ {text} ”")
139
- self.set_x(self.l_margin) # Reset
140
  self.set_text_color(0)
141
  self.ln(2)
142
 
143
  def add_image_from_url(self, url):
144
- # Feature: Image Embedding
145
  try:
146
  r = requests.get(url, timeout=5)
147
  if r.status_code == 200:
148
  img_data = BytesIO(r.content)
149
- self.image(img_data, w=100) # Width 100mm
150
  self.ln(5)
151
- else:
152
- self.set_text_color(255, 0, 0)
153
- self.cell(0, 10, f"[Image Error: {url}]", ln=True)
154
  except:
155
- self.set_text_color(255, 0, 0)
156
- self.cell(0, 10, f"[Invalid URL]", ln=True)
157
- self.set_text_color(0)
158
 
159
- # --- LOGIC: TEXT PROCESSOR ---
160
  def clean_and_parse(raw_text, use_smart_symbols=True, clean_lms=True):
161
  processed_lines = []
162
 
163
- # 1. LMS CLEANING
164
  if clean_lms:
165
- # Feature: Remove ID tags [ID:123]
166
- raw_text = re.sub(r'\[ID:?\s*\w+\]', '', raw_text, flags=re.IGNORECASE)
167
- # Feature: Remove Point values (1 pts)
168
- raw_text = re.sub(r'\(\d+\s*pts?\)', '', raw_text, flags=re.IGNORECASE)
169
- # Feature: Remove "Select one:" instructions
170
- raw_text = raw_text.replace("Select one:", "")
171
- # Feature: Remove excessive newlines
172
- raw_text = re.sub(r'\n{3,}', '\n\n', raw_text)
173
-
174
- # 2. SMART SYMBOLS
 
 
 
175
  if use_smart_symbols:
176
  for pattern, symbol in SMART_SYMBOLS.items():
177
  if pattern.isalpha():
@@ -181,114 +193,109 @@ def run_ultimate_pdf_converter():
181
 
182
  lines = raw_text.split('\n')
183
 
184
- # 3. STRUCTURE PARSING (Block detection)
185
- buffer_type = None # 'code', 'table'
186
  buffer_content = []
187
 
188
  for line in lines:
189
  line_stripped = line.strip()
190
 
191
- # A. CODE BLOCKS
192
  if line_stripped.startswith('```'):
193
- if buffer_type == 'code': # End of code block
194
  processed_lines.append({'type': 'code', 'content': buffer_content})
195
  buffer_content = []
196
  buffer_type = None
197
- else: # Start of code block
198
- if buffer_type == 'table': # Flush table if open
199
  processed_lines.append({'type': 'table', 'content': buffer_content})
200
  buffer_content = []
201
  buffer_type = 'code'
202
  continue
203
 
204
  if buffer_type == 'code':
205
- buffer_content.append(line)
206
  continue
207
 
208
- # B. TABLES (Lines containing |)
209
  if '|' in line_stripped and len(line_stripped) > 3:
210
  if buffer_type != 'table':
211
  buffer_type = 'table'
212
  buffer_content.append(line_stripped)
213
  continue
214
- elif buffer_type == 'table': # End of table
215
  processed_lines.append({'type': 'table', 'content': buffer_content})
216
  buffer_content = []
217
  buffer_type = None
218
 
219
- # C. HEADERS
220
  if line_stripped.startswith('#'):
221
  level = line_stripped.count('#')
222
  text = line_stripped.replace('#', '').strip()
223
  processed_lines.append({'type': 'header', 'level': min(level, 3), 'content': text})
224
  continue
225
 
226
- # D. BLOCKQUOTES
227
  if line_stripped.startswith('> '):
228
  processed_lines.append({'type': 'quote', 'content': line_stripped[2:]})
229
  continue
230
 
231
- # E. IMAGES
232
  if line_stripped.startswith('http') and line_stripped.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
233
  processed_lines.append({'type': 'image', 'url': line_stripped})
234
  continue
235
 
236
- # F. LISTS
237
- if line_stripped.startswith('* ') or line_stripped.startswith('- '):
238
  processed_lines.append({'type': 'list', 'content': line_stripped[2:]})
239
  continue
240
 
241
- # G. HORIZONTAL RULE
242
  if line_stripped == '---':
243
  processed_lines.append({'type': 'hr'})
244
  continue
245
 
246
- # H. STANDARD TEXT
247
  if line_stripped:
248
  processed_lines.append({'type': 'text', 'content': line_stripped})
249
  else:
250
  processed_lines.append({'type': 'empty'})
251
 
252
- # Flush buffers
253
- if buffer_type == 'table': processed_lines.append({'type': 'table', 'content': buffer_content})
254
 
255
  return processed_lines
256
 
257
- # --- UI: STREAMLIT APP ---
258
  st.title("⚡ Ultimate PDF Engine")
259
- st.markdown("""
260
- <style>
261
- .reportview-container { background: #f0f2f6; }
262
- </style>
263
- """, unsafe_allow_html=True)
 
264
 
 
265
  with st.sidebar:
266
- st.header("⚙️ 55+ Features Control")
267
-
268
- # Group 1: Output Settings
269
- filename = st.text_input("Filename", "Ultimate_Notes.pdf")
270
- orientation = st.radio("Orientation", ["Portrait", "Landscape"], index=0)
271
-
272
- # Group 2: Features Toggle
273
- st.subheader("Processing")
274
- enable_lms = st.checkbox("LMS Cleaner (Regex)", True)
275
- enable_smart = st.checkbox("Smart Symbols (α, →)", True)
276
- enable_header = st.checkbox("Add Header/Footer", True)
277
-
278
- # Group 3: Style
279
- st.subheader("Styling")
280
- font_size = st.slider("Base Font Size", 8, 20, 12)
281
-
282
- # Main Input
283
- raw_input = st.text_area("Paste Content (Supports Markdown, Tables, Links, Images):", height=400)
284
-
285
  if st.button("🚀 Generate PDF", type="primary"):
286
- if not raw_input:
287
- st.error("Input is empty!")
288
  return
289
 
290
- with st.spinner("Engaging 55 features... Parsing blocks... Rendering..."):
291
- # 1. Init PDF
292
  orient_code = 'P' if orientation == "Portrait" else 'L'
293
  pdf = UltimatePDF(orientation=orient_code)
294
  pdf.title_meta = filename.replace('.pdf', '')
@@ -297,60 +304,49 @@ def run_ultimate_pdf_converter():
297
  pdf.add_page()
298
  pdf.set_font(pdf.main_font, '', font_size)
299
 
300
- # 2. Parse Content
301
  blocks = clean_and_parse(raw_input, use_smart_symbols=enable_smart, clean_lms=enable_lms)
302
 
303
- # 3. Render Blocks
304
  for block in blocks:
305
  if block['type'] == 'header':
306
  pdf.add_markdown_header(block['content'], block['level'])
307
-
308
  elif block['type'] == 'code':
309
  pdf.add_code_block(block['content'])
310
-
311
  elif block['type'] == 'table':
312
  pdf.add_table(block['content'])
313
-
314
  elif block['type'] == 'quote':
315
  pdf.add_blockquote(block['content'])
316
-
317
  elif block['type'] == 'image':
318
  pdf.add_image_from_url(block['url'])
319
-
320
  elif block['type'] == 'list':
321
  pdf.set_x(pdf.l_margin + 5)
322
  pdf.write(8, f"• {block['content']}")
323
  pdf.ln()
324
  pdf.set_x(pdf.l_margin)
325
-
326
  elif block['type'] == 'hr':
327
- pdf.ln(5)
328
  pdf.line(pdf.l_margin, pdf.get_y(), pdf.w - pdf.r_margin, pdf.get_y())
329
  pdf.ln(5)
330
-
331
  elif block['type'] == 'text':
332
  pdf.write(8, block['content'])
333
  pdf.ln()
334
-
335
  elif block['type'] == 'empty':
336
  pdf.ln(4)
337
 
338
- # 4. Output
339
- # encode to latin-1 with 'ignore' is a fallback for st.download,
340
- # but FPDF2 'S' output is actually a string that needs encoding.
341
- # Better to use output(dest='S').encode('latin-1')
342
- pdf_bytes = pdf.output(dest='S').encode('latin-1', 'replace')
343
-
344
- col1, col2 = st.columns([3,1])
345
- with col1:
346
- st.success(f"Processed {len(blocks)} blocks successfully.")
347
- with col2:
348
  st.download_button(
349
- "⬇️ Download",
350
  data=pdf_bytes,
351
  file_name=filename if filename.endswith('.pdf') else f"{filename}.pdf",
352
  mime="application/pdf"
353
  )
 
 
 
354
 
355
 
356
 
 
32
 
33
 
34
 
35
+ import streamlit as st
36
+ from fpdf import FPDF
37
+ import requests
38
+ import re
39
+ import os
40
+ from io import BytesIO
41
+
42
  def run_ultimate_pdf_converter():
43
  """
44
+ The Ultimate Text-to-PDF Converter (Stable Version).
45
+ Features:
46
+ - Auto-Healing Font Loader (Fixes TTLibError)
47
+ - Smart Symbols & Typography
48
+ - Markdown Engine (Headers, Tables, Code Blocks)
49
+ - LMS Junk Cleaner
 
50
  """
51
 
52
+ # --- CONSTANTS ---
 
53
  SMART_SYMBOLS = {
54
  r'<->': '↔', r'->': '→', r'<-': '←', r'=>': '⇒', r'<=': '≤', r'>=': '≥', r'!=': '≠',
55
  r'\.\.\.': '…', r'\(c\)': '©', r'\(r\)': '®', r'\(tm\)': '™',
 
59
  r'deg': '°', r'infinity': '∞', r'sqrt': '√'
60
  }
61
 
62
+ # --- INTERNAL PDF CLASS ---
63
  class UltimatePDF(FPDF):
64
+ def __init__(self, orientation='P', unit='mm', format='A4'):
65
  super().__init__(orientation=orientation, unit=unit, format=format)
 
 
66
  self.set_auto_page_break(auto=True, margin=15)
67
+ self.main_font = 'Arial' # Default fallback
68
+ self.ensure_fonts()
69
 
70
  def ensure_fonts(self):
71
+ font_filename = "DejaVuSans.ttf"
 
72
  font_url = "https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans.ttf"
 
 
 
 
 
 
 
73
 
74
+ # 1. Check if file exists and is valid size (HTML error pages are small)
75
+ if os.path.exists(font_filename):
76
+ if os.path.getsize(font_filename) < 1000: # Less than 1KB is definitely garbage
77
+ os.remove(font_filename)
78
+
79
+ # 2. Download if missing
80
+ if not os.path.exists(font_filename):
81
+ try:
82
+ # Fake user-agent to avoid GitHub blocking scripts
83
+ headers = {'User-Agent': 'Mozilla/5.0'}
84
+ r = requests.get(font_url, headers=headers, timeout=10)
85
+ if r.status_code == 200:
86
+ with open(font_filename, "wb") as f:
87
+ f.write(r.content)
88
+ except Exception as e:
89
+ print(f"Font download failed: {e}")
90
+
91
+ # 3. Try Loading the Font
92
+ try:
93
+ if os.path.exists(font_filename):
94
+ self.add_font('DejaVu', '', font_filename, uni=True)
95
+ self.main_font = 'DejaVu'
96
+ except Exception:
97
+ # If loading fails (corrupt file), delete it to retry next time
98
+ try: os.remove(font_filename)
99
+ except: pass
100
+ self.main_font = 'Arial' # Fallback to standard
101
+ st.toast("⚠️ Font failed to load. Using standard font (some symbols may be missing).", icon="⚠️")
102
 
103
  def header(self):
 
104
  if getattr(self, 'show_header', False):
105
  self.set_font(self.main_font, '', 8)
106
  self.set_text_color(128)
107
+ self.cell(0, 10, f'Generated by Ultimate PDF | {getattr(self, "title_meta", "Doc")}', 0, 0, 'R')
108
  self.ln(10)
109
 
110
  def footer(self):
 
111
  self.set_y(-15)
112
  self.set_font(self.main_font, '', 8)
113
  self.set_text_color(128)
114
  self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
115
 
116
+ # --- MARKDOWN RENDERING HELPERS ---
117
  def add_markdown_header(self, text, level):
118
+ sizes = {1: 20, 2: 16, 3: 14}
 
119
  self.set_font(self.main_font, '', sizes.get(level, 12))
120
+ self.set_text_color(0, 50, 100)
121
  self.cell(0, 10, text, ln=True)
122
+ self.set_text_color(0)
123
  self.set_font(self.main_font, '', 12)
124
 
125
  def add_code_block(self, code_lines):
 
126
  self.set_font("Courier", size=10)
127
+ self.set_fill_color(245, 245, 245)
128
  for line in code_lines:
129
+ # Replace tabs with spaces to prevent alignment issues
130
+ safe_line = line.replace('\t', ' ')
131
+ self.cell(0, 5, safe_line, ln=True, fill=True, border=0)
132
+ self.set_font(self.main_font, '', 12)
133
+ self.ln(3)
134
 
135
  def add_table(self, table_lines):
 
136
  self.set_font(self.main_font, '', 10)
137
+ cell_h = 7
138
  for row in table_lines:
139
  cols = [c.strip() for c in row.split('|') if c.strip()]
140
  if not cols: continue
141
+ col_w = (self.w - 30) // len(cols)
 
 
142
  for col in cols:
143
+ self.cell(col_w, cell_h, col, border=1)
144
  self.ln()
145
+ self.set_font(self.main_font, '', 12)
146
  self.ln(5)
147
 
148
  def add_blockquote(self, text):
149
+ self.set_text_color(80)
150
+ self.set_x(self.l_margin + 8)
151
+ self.multi_cell(0, 6, f"“ {text}")
152
+ self.set_x(self.l_margin)
 
 
153
  self.set_text_color(0)
154
  self.ln(2)
155
 
156
  def add_image_from_url(self, url):
 
157
  try:
158
  r = requests.get(url, timeout=5)
159
  if r.status_code == 200:
160
  img_data = BytesIO(r.content)
161
+ self.image(img_data, w=100)
162
  self.ln(5)
 
 
 
163
  except:
164
+ self.set_text_color(200, 0, 0)
165
+ self.cell(0, 10, f"[Image load failed: {url}]", ln=True)
166
+ self.set_text_color(0)
167
 
168
+ # --- TEXT PROCESSOR ---
169
  def clean_and_parse(raw_text, use_smart_symbols=True, clean_lms=True):
170
  processed_lines = []
171
 
172
+ # 1. LMS Regex Cleaning
173
  if clean_lms:
174
+ # Common LMS patterns
175
+ patterns = [
176
+ r'\[ID:?\s*\w+\]', # [ID: 123]
177
+ r'Question\s+ID\s*[:\-]\s*\w+', # Question ID: 123
178
+ r'\(\d+\s*pts?\)', # (1 pts)
179
+ r'Select one:', # Moodle/Blackboard prompt
180
+ r'\[\d{1,2}:\d{2}\s*(AM|PM)?\]' # Timestamps
181
+ ]
182
+ for p in patterns:
183
+ raw_text = re.sub(p, '', raw_text, flags=re.IGNORECASE)
184
+ raw_text = re.sub(r'\n{3,}', '\n\n', raw_text) # Fix spacing
185
+
186
+ # 2. Smart Symbols
187
  if use_smart_symbols:
188
  for pattern, symbol in SMART_SYMBOLS.items():
189
  if pattern.isalpha():
 
193
 
194
  lines = raw_text.split('\n')
195
 
196
+ # 3. Block Parser
197
+ buffer_type = None
198
  buffer_content = []
199
 
200
  for line in lines:
201
  line_stripped = line.strip()
202
 
203
+ # Detect Code Block
204
  if line_stripped.startswith('```'):
205
+ if buffer_type == 'code': # Close code
206
  processed_lines.append({'type': 'code', 'content': buffer_content})
207
  buffer_content = []
208
  buffer_type = None
209
+ else: # Open code
210
+ if buffer_type == 'table': # Close table if open
211
  processed_lines.append({'type': 'table', 'content': buffer_content})
212
  buffer_content = []
213
  buffer_type = 'code'
214
  continue
215
 
216
  if buffer_type == 'code':
217
+ buffer_content.append(line) # Preserve whitespace in code
218
  continue
219
 
220
+ # Detect Table
221
  if '|' in line_stripped and len(line_stripped) > 3:
222
  if buffer_type != 'table':
223
  buffer_type = 'table'
224
  buffer_content.append(line_stripped)
225
  continue
226
+ elif buffer_type == 'table': # Close table
227
  processed_lines.append({'type': 'table', 'content': buffer_content})
228
  buffer_content = []
229
  buffer_type = None
230
 
231
+ # Detect Headers
232
  if line_stripped.startswith('#'):
233
  level = line_stripped.count('#')
234
  text = line_stripped.replace('#', '').strip()
235
  processed_lines.append({'type': 'header', 'level': min(level, 3), 'content': text})
236
  continue
237
 
238
+ # Detect Quotes
239
  if line_stripped.startswith('> '):
240
  processed_lines.append({'type': 'quote', 'content': line_stripped[2:]})
241
  continue
242
 
243
+ # Detect Images
244
  if line_stripped.startswith('http') and line_stripped.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
245
  processed_lines.append({'type': 'image', 'url': line_stripped})
246
  continue
247
 
248
+ # Detect Lists
249
+ if line_stripped.startswith(('* ', '- ')):
250
  processed_lines.append({'type': 'list', 'content': line_stripped[2:]})
251
  continue
252
 
253
+ # Detect HR
254
  if line_stripped == '---':
255
  processed_lines.append({'type': 'hr'})
256
  continue
257
 
 
258
  if line_stripped:
259
  processed_lines.append({'type': 'text', 'content': line_stripped})
260
  else:
261
  processed_lines.append({'type': 'empty'})
262
 
263
+ if buffer_type == 'table':
264
+ processed_lines.append({'type': 'table', 'content': buffer_content})
265
 
266
  return processed_lines
267
 
268
+ # --- UI RENDER ---
269
  st.title("⚡ Ultimate PDF Engine")
270
+
271
+ with st.expander("ℹ️ Help & Features", expanded=False):
272
+ st.write("- **Smart Symbols:** Writes 'alpha' as α, '->' as →")
273
+ st.write("- **Tables:** Use `| Name | Score |` format")
274
+ st.write("- **Code:** Use ` ``` ` for code blocks")
275
+ st.write("- **Images:** Paste URL on new line")
276
 
277
+ # Settings Sidebar
278
  with st.sidebar:
279
+ st.header("⚙️ PDF Config")
280
+ filename = st.text_input("Filename", "My_Notes.pdf")
281
+ orientation = st.radio("Orientation", ["Portrait", "Landscape"])
282
+ st.subheader("Filters")
283
+ enable_lms = st.checkbox("Clean LMS Junk", True)
284
+ enable_smart = st.checkbox("Smart Symbols", True)
285
+ enable_header = st.checkbox("Show Header", True)
286
+ font_size = st.slider("Font Size", 8, 24, 12)
287
+
288
+ # Input
289
+ raw_input = st.text_area("Paste text here...", height=350)
290
+
291
+ # Action
 
 
 
 
 
 
292
  if st.button("🚀 Generate PDF", type="primary"):
293
+ if not raw_input.strip():
294
+ st.warning("Input is empty.")
295
  return
296
 
297
+ with st.spinner("Processing..."):
298
+ # Setup PDF
299
  orient_code = 'P' if orientation == "Portrait" else 'L'
300
  pdf = UltimatePDF(orientation=orient_code)
301
  pdf.title_meta = filename.replace('.pdf', '')
 
304
  pdf.add_page()
305
  pdf.set_font(pdf.main_font, '', font_size)
306
 
307
+ # Process
308
  blocks = clean_and_parse(raw_input, use_smart_symbols=enable_smart, clean_lms=enable_lms)
309
 
310
+ # Render
311
  for block in blocks:
312
  if block['type'] == 'header':
313
  pdf.add_markdown_header(block['content'], block['level'])
 
314
  elif block['type'] == 'code':
315
  pdf.add_code_block(block['content'])
 
316
  elif block['type'] == 'table':
317
  pdf.add_table(block['content'])
 
318
  elif block['type'] == 'quote':
319
  pdf.add_blockquote(block['content'])
 
320
  elif block['type'] == 'image':
321
  pdf.add_image_from_url(block['url'])
 
322
  elif block['type'] == 'list':
323
  pdf.set_x(pdf.l_margin + 5)
324
  pdf.write(8, f"• {block['content']}")
325
  pdf.ln()
326
  pdf.set_x(pdf.l_margin)
 
327
  elif block['type'] == 'hr':
328
+ pdf.ln(2)
329
  pdf.line(pdf.l_margin, pdf.get_y(), pdf.w - pdf.r_margin, pdf.get_y())
330
  pdf.ln(5)
 
331
  elif block['type'] == 'text':
332
  pdf.write(8, block['content'])
333
  pdf.ln()
 
334
  elif block['type'] == 'empty':
335
  pdf.ln(4)
336
 
337
+ # Download
338
+ try:
339
+ pdf_bytes = pdf.output(dest='S').encode('latin-1', 'replace')
340
+ st.success("PDF Generated Successfully!")
 
 
 
 
 
 
341
  st.download_button(
342
+ "⬇️ Download PDF",
343
  data=pdf_bytes,
344
  file_name=filename if filename.endswith('.pdf') else f"{filename}.pdf",
345
  mime="application/pdf"
346
  )
347
+ except Exception as e:
348
+ st.error(f"Error creating PDF file: {e}")
349
+
350
 
351
 
352