pdf_builder.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import re
  2. from reportlab.lib.pagesizes import A4
  3. from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
  4. from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
  5. from reportlab.lib import colors
  6. from reportlab.pdfbase import pdfmetrics
  7. from reportlab.pdfbase.cidfonts import UnicodeCIDFont
  8. from config.settings import SERVER_HOST, REPORT_OUTPUT_DIR
  9. # 注册中文字体
  10. pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light'))
  11. FONT_CN = 'STSong-Light'
  12. TABLE_BASE_STYLE = [
  13. ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
  14. ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
  15. ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
  16. ('TOPPADDING', (0, 0), (-1, -1), 6),
  17. ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
  18. ('LEFTPADDING', (0, 0), (-1, -1), 4),
  19. ('RIGHTPADDING', (0, 0), (-1, -1), 4),
  20. ('GRID', (0, 0), (-1, -1), 1, colors.black),
  21. ]
  22. def convert_bold(text: str) -> str:
  23. return re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text)
  24. def markdown_to_pdf(md_content: str, save_path: str) -> None:
  25. """Markdown转PDF,支持四级标题、加粗、表格"""
  26. doc = SimpleDocTemplate(
  27. save_path,
  28. pagesize=A4,
  29. rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30
  30. )
  31. story = []
  32. styles = getSampleStyleSheet()
  33. # 自定义样式
  34. style_h1 = ParagraphStyle('CustomH1', parent=styles['Heading1'], fontSize=16, spaceAfter=12, fontName=FONT_CN)
  35. style_h2 = ParagraphStyle('CustomH2', parent=styles['Heading2'], fontSize=14, spaceAfter=10, fontName=FONT_CN)
  36. style_h3 = ParagraphStyle('CustomH3', parent=styles['Heading3'], fontSize=12, spaceAfter=8, fontName=FONT_CN)
  37. style_h4 = ParagraphStyle('CustomH4', parent=styles['Heading3'], fontSize=11, spaceAfter=6, fontName=FONT_CN)
  38. style_normal = ParagraphStyle('CustomNormal', parent=styles['Normal'], fontSize=11, leading=18, fontName=FONT_CN)
  39. table_text_style = ParagraphStyle('TableText', parent=styles['Normal'], fontSize=10, fontName=FONT_CN, leading=14)
  40. lines = md_content.splitlines()
  41. table_rows = []
  42. in_table = False
  43. for raw_line in lines:
  44. line = raw_line.strip()
  45. if not line:
  46. continue
  47. # 四级标题
  48. if line.startswith("#### "):
  49. txt = convert_bold(line.lstrip("#### ").strip())
  50. story.append(Paragraph(txt, style_h4))
  51. story.append(Spacer(1, 4))
  52. continue
  53. elif line.startswith("### "):
  54. txt = convert_bold(line.lstrip("### ").strip())
  55. story.append(Paragraph(txt, style_h3))
  56. story.append(Spacer(1, 4))
  57. continue
  58. elif line.startswith("## "):
  59. txt = convert_bold(line.lstrip("## ").strip())
  60. story.append(Paragraph(txt, style_h2))
  61. story.append(Spacer(1, 5))
  62. continue
  63. elif line.startswith("# "):
  64. txt = convert_bold(line.lstrip("# ").strip())
  65. story.append(Paragraph(txt, style_h1))
  66. story.append(Spacer(1, 6))
  67. continue
  68. # 表格分隔符
  69. if all(c in "-| " for c in line):
  70. continue
  71. # 表格行
  72. if line.startswith("|") and line.endswith("|"):
  73. in_table = True
  74. cells = [cell.strip() for cell in line.split("|")[1:-1]]
  75. paras = [Paragraph(convert_bold(c), table_text_style) for c in cells]
  76. table_rows.append(paras)
  77. continue
  78. # 结束表格渲染
  79. if in_table:
  80. in_table = False
  81. if table_rows:
  82. col_cnt = len(table_rows[0])
  83. col_w = [530 / col_cnt] * col_cnt
  84. tbl = Table(table_rows, colWidths=col_w, repeatRows=1)
  85. tbl.setStyle(TableStyle(TABLE_BASE_STYLE))
  86. story.append(tbl)
  87. story.append(Spacer(1, 10))
  88. table_rows = []
  89. # 普通正文
  90. fmt_line = convert_bold(line)
  91. story.append(Paragraph(fmt_line, style_normal))
  92. story.append(Spacer(1, 3))
  93. # 剩余表格
  94. if in_table and table_rows:
  95. col_cnt = len(table_rows[0])
  96. col_w = [530 / col_cnt] * col_cnt
  97. tbl = Table(table_rows, colWidths=col_w, repeatRows=1)
  98. tbl.setStyle(TableStyle(TABLE_BASE_STYLE))
  99. story.append(tbl)
  100. story.append(Spacer(1, 10))
  101. doc.build(story)
  102. def get_pdf_download_url(pdf_filename: str) -> str:
  103. return f"{SERVER_HOST}/static/report_pdf/{pdf_filename}"