davidlms commited on
Commit
32e4589
·
verified ·
1 Parent(s): 0fed6c7

Use weasyprint instead of wkhtmltopdf.

Browse files
Files changed (1) hide show
  1. app.py +62 -97
app.py CHANGED
@@ -18,7 +18,7 @@ import requests
18
  import gradio as gr
19
  from openai import OpenAI
20
  import markdown
21
- import pdfkit
22
 
23
  from langchain_mcp_adapters.client import MultiServerMCPClient
24
 
@@ -195,21 +195,20 @@ def safe_json_dumps(obj, **kwargs):
195
  return json.dumps({"error": f"Error serializing: {str(e)}", "data": str(obj)}, **kwargs)
196
 
197
  def markdown_to_pdf(markdown_content: str, output_path: str) -> str:
198
- """Convert markdown to PDF using pdfkit."""
199
  try:
200
  # Convert markdown to HTML and add exercise break classes
201
  html_content = markdown.markdown(markdown_content, extensions=['tables', 'fenced_code'])
202
-
203
  # Add CSS classes for better page breaks
204
  import re
205
  # Add exercise-break class to h2 elements (exercises)
206
  html_content = re.sub(r'<h2>', r'<h2 class="exercise-break">', html_content)
207
-
208
  # Get the logo path
209
  logo_path = "https://huggingface.co/spaces/Agents-MCP-Hackathon/ipmentor-subnetting-exercises-generator/resolve/main/assets/logo.svg"
210
- logo_exists = True
211
-
212
- # Add CSS styling with IPMentor branding colors
213
  styled_html = f"""
214
  <!DOCTYPE html>
215
  <html>
@@ -218,28 +217,31 @@ def markdown_to_pdf(markdown_content: str, output_path: str) -> str:
218
  <style>
219
  @page {{
220
  margin: 1in 1in 100px 1in;
 
 
 
221
  }}
222
-
223
- body {{
224
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
225
- line-height: 1.6;
226
  margin: 0;
227
  padding: 20px;
228
  color: #333;
229
  background: #fefefe;
230
  }}
231
-
232
- h1 {{
233
- color: #FC8100;
234
  border-bottom: 4px solid #FED200;
235
  padding-bottom: 15px;
236
  margin-bottom: 30px;
237
  font-size: 2.2em;
238
  font-weight: bold;
239
  }}
240
-
241
- h2 {{
242
- color: #FC8100;
243
  border-bottom: 3px solid #FFCB00;
244
  padding-bottom: 8px;
245
  margin-top: 40px;
@@ -247,49 +249,49 @@ def markdown_to_pdf(markdown_content: str, output_path: str) -> str:
247
  font-size: 1.5em;
248
  page-break-after: avoid;
249
  }}
250
-
251
- h3 {{
252
- color: #FE8100;
253
  margin-top: 25px;
254
  margin-bottom: 15px;
255
  font-size: 1.2em;
256
  }}
257
-
258
  p {{
259
  margin-bottom: 15px;
260
  text-align: justify;
261
  }}
262
-
263
  em {{
264
  color: #F05600;
265
  font-style: italic;
266
  }}
267
-
268
  strong {{
269
  color: #FE8100;
270
  }}
271
-
272
  a {{
273
  color: #F05600;
274
  text-decoration: none;
275
  border-bottom: 1px dotted #F05600;
276
  }}
277
-
278
  a:hover {{
279
  border-bottom: 1px solid #F05600;
280
  }}
281
-
282
- img {{
283
- max-width: 85%;
284
  max-height: 450px;
285
- height: auto;
286
  display: block;
287
  margin: 25px auto;
288
  border: 2px solid #FED200;
289
  border-radius: 8px;
290
  box-shadow: 0 4px 8px rgba(254, 129, 0, 0.1);
291
  }}
292
-
293
  code {{
294
  background: #FFF4E6;
295
  color: #FC8100;
@@ -298,16 +300,16 @@ def markdown_to_pdf(markdown_content: str, output_path: str) -> str:
298
  font-family: 'Courier New', monospace;
299
  border: 1px solid #FED200;
300
  }}
301
-
302
- pre {{
303
- background: #FFF8F0;
304
- padding: 20px;
305
  border-radius: 8px;
306
  border-left: 6px solid #F05600;
307
  margin: 20px 0;
308
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
309
  }}
310
-
311
  hr {{
312
  border: none;
313
  height: 3px;
@@ -315,83 +317,46 @@ def markdown_to_pdf(markdown_content: str, output_path: str) -> str:
315
  margin: 30px 0;
316
  border-radius: 2px;
317
  }}
318
-
319
-
320
  .exercise-break {{
321
  page-break-before: always;
322
  margin-top: 0;
323
  }}
324
-
325
  .exercise-break:first-of-type {{
326
  page-break-before: avoid;
327
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  </style>
329
  </head>
330
  <body>
331
  {html_content}
332
-
 
 
 
333
  </body>
334
  </html>
335
  """
336
-
337
- # Create temporary footer HTML file with logo
338
- footer_html_path = None
339
- if logo_exists:
340
- with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as footer_file:
341
- footer_html_path = footer_file.name
342
- footer_content = f"""
343
- <!DOCTYPE html>
344
- <html>
345
- <head>
346
- <meta charset="UTF-8">
347
- <style>
348
- body {{
349
- margin: 0;
350
- padding: 8px;
351
- text-align: center;
352
- }}
353
- .logo {{
354
- height: 38px;
355
- width: auto;
356
- }}
357
- </style>
358
- </head>
359
- <body>
360
- <img src="{logo_path}" alt="IPMentor" class="logo">
361
- </body>
362
- </html>
363
- """
364
- footer_file.write(footer_content)
365
-
366
- # Configure PDF options
367
- options = {
368
- 'page-size': 'A4',
369
- 'margin-top': '1in',
370
- 'margin-right': '1in',
371
- 'margin-bottom': '1in',
372
- 'margin-left': '1in',
373
- 'encoding': "UTF-8",
374
- 'no-outline': None,
375
- 'enable-local-file-access': None,
376
- 'print-media-type': None,
377
- 'disable-smart-shrinking': None
378
- }
379
-
380
- # Add footer if logo exists
381
- if footer_html_path:
382
- options['footer-html'] = footer_html_path
383
- options['footer-spacing'] = '5'
384
-
385
- # Generate PDF
386
- try:
387
- pdfkit.from_string(styled_html, output_path, options=options)
388
- finally:
389
- # Clean up temporary footer file
390
- if footer_html_path and os.path.exists(footer_html_path):
391
- os.remove(footer_html_path)
392
-
393
  return output_path
394
-
395
  except Exception as e:
396
  raise Exception(f"PDF generation failed: {str(e)}")
397
 
 
18
  import gradio as gr
19
  from openai import OpenAI
20
  import markdown
21
+ from weasyprint import HTML
22
 
23
  from langchain_mcp_adapters.client import MultiServerMCPClient
24
 
 
195
  return json.dumps({"error": f"Error serializing: {str(e)}", "data": str(obj)}, **kwargs)
196
 
197
  def markdown_to_pdf(markdown_content: str, output_path: str) -> str:
198
+ """Convert markdown to PDF using weasyprint."""
199
  try:
200
  # Convert markdown to HTML and add exercise break classes
201
  html_content = markdown.markdown(markdown_content, extensions=['tables', 'fenced_code'])
202
+
203
  # Add CSS classes for better page breaks
204
  import re
205
  # Add exercise-break class to h2 elements (exercises)
206
  html_content = re.sub(r'<h2>', r'<h2 class="exercise-break">', html_content)
207
+
208
  # Get the logo path
209
  logo_path = "https://huggingface.co/spaces/Agents-MCP-Hackathon/ipmentor-subnetting-exercises-generator/resolve/main/assets/logo.svg"
210
+
211
+ # Add CSS styling with IPMentor branding colors and footer with logo
 
212
  styled_html = f"""
213
  <!DOCTYPE html>
214
  <html>
 
217
  <style>
218
  @page {{
219
  margin: 1in 1in 100px 1in;
220
+ @bottom-center {{
221
+ content: element(footer);
222
+ }}
223
  }}
224
+
225
+ body {{
226
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
227
+ line-height: 1.6;
228
  margin: 0;
229
  padding: 20px;
230
  color: #333;
231
  background: #fefefe;
232
  }}
233
+
234
+ h1 {{
235
+ color: #FC8100;
236
  border-bottom: 4px solid #FED200;
237
  padding-bottom: 15px;
238
  margin-bottom: 30px;
239
  font-size: 2.2em;
240
  font-weight: bold;
241
  }}
242
+
243
+ h2 {{
244
+ color: #FC8100;
245
  border-bottom: 3px solid #FFCB00;
246
  padding-bottom: 8px;
247
  margin-top: 40px;
 
249
  font-size: 1.5em;
250
  page-break-after: avoid;
251
  }}
252
+
253
+ h3 {{
254
+ color: #FE8100;
255
  margin-top: 25px;
256
  margin-bottom: 15px;
257
  font-size: 1.2em;
258
  }}
259
+
260
  p {{
261
  margin-bottom: 15px;
262
  text-align: justify;
263
  }}
264
+
265
  em {{
266
  color: #F05600;
267
  font-style: italic;
268
  }}
269
+
270
  strong {{
271
  color: #FE8100;
272
  }}
273
+
274
  a {{
275
  color: #F05600;
276
  text-decoration: none;
277
  border-bottom: 1px dotted #F05600;
278
  }}
279
+
280
  a:hover {{
281
  border-bottom: 1px solid #F05600;
282
  }}
283
+
284
+ img {{
285
+ max-width: 85%;
286
  max-height: 450px;
287
+ height: auto;
288
  display: block;
289
  margin: 25px auto;
290
  border: 2px solid #FED200;
291
  border-radius: 8px;
292
  box-shadow: 0 4px 8px rgba(254, 129, 0, 0.1);
293
  }}
294
+
295
  code {{
296
  background: #FFF4E6;
297
  color: #FC8100;
 
300
  font-family: 'Courier New', monospace;
301
  border: 1px solid #FED200;
302
  }}
303
+
304
+ pre {{
305
+ background: #FFF8F0;
306
+ padding: 20px;
307
  border-radius: 8px;
308
  border-left: 6px solid #F05600;
309
  margin: 20px 0;
310
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
311
  }}
312
+
313
  hr {{
314
  border: none;
315
  height: 3px;
 
317
  margin: 30px 0;
318
  border-radius: 2px;
319
  }}
320
+
 
321
  .exercise-break {{
322
  page-break-before: always;
323
  margin-top: 0;
324
  }}
325
+
326
  .exercise-break:first-of-type {{
327
  page-break-before: avoid;
328
  }}
329
+
330
+ .footer {{
331
+ position: running(footer);
332
+ text-align: center;
333
+ padding: 8px;
334
+ }}
335
+
336
+ .footer img {{
337
+ height: 38px;
338
+ width: auto;
339
+ margin: 0 auto;
340
+ border: none;
341
+ box-shadow: none;
342
+ }}
343
  </style>
344
  </head>
345
  <body>
346
  {html_content}
347
+
348
+ <div class="footer">
349
+ <img src="{logo_path}" alt="IPMentor">
350
+ </div>
351
  </body>
352
  </html>
353
  """
354
+
355
+ # Generate PDF using WeasyPrint
356
+ HTML(string=styled_html).write_pdf(output_path)
357
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  return output_path
359
+
360
  except Exception as e:
361
  raise Exception(f"PDF generation failed: {str(e)}")
362