import re from reportlab.lib.pagesizes import A4 from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.cidfonts import UnicodeCIDFont from config.settings import SERVER_HOST, REPORT_OUTPUT_DIR # 注册中文字体 pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light')) FONT_CN = 'STSong-Light' TABLE_BASE_STYLE = [ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('TOPPADDING', (0, 0), (-1, -1), 6), ('BOTTOMPADDING', (0, 0), (-1, -1), 6), ('LEFTPADDING', (0, 0), (-1, -1), 4), ('RIGHTPADDING', (0, 0), (-1, -1), 4), ('GRID', (0, 0), (-1, -1), 1, colors.black), ] def convert_bold(text: str) -> str: return re.sub(r'\*\*(.*?)\*\*', r'\1', text) def markdown_to_pdf(md_content: str, save_path: str) -> None: """Markdown转PDF,支持四级标题、加粗、表格""" doc = SimpleDocTemplate( save_path, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30 ) story = [] styles = getSampleStyleSheet() # 自定义样式 style_h1 = ParagraphStyle('CustomH1', parent=styles['Heading1'], fontSize=16, spaceAfter=12, fontName=FONT_CN) style_h2 = ParagraphStyle('CustomH2', parent=styles['Heading2'], fontSize=14, spaceAfter=10, fontName=FONT_CN) style_h3 = ParagraphStyle('CustomH3', parent=styles['Heading3'], fontSize=12, spaceAfter=8, fontName=FONT_CN) style_h4 = ParagraphStyle('CustomH4', parent=styles['Heading3'], fontSize=11, spaceAfter=6, fontName=FONT_CN) style_normal = ParagraphStyle('CustomNormal', parent=styles['Normal'], fontSize=11, leading=18, fontName=FONT_CN) table_text_style = ParagraphStyle('TableText', parent=styles['Normal'], fontSize=10, fontName=FONT_CN, leading=14) lines = md_content.splitlines() table_rows = [] in_table = False for raw_line in lines: line = raw_line.strip() if not line: continue # 四级标题 if line.startswith("#### "): txt = convert_bold(line.lstrip("#### ").strip()) story.append(Paragraph(txt, style_h4)) story.append(Spacer(1, 4)) continue elif line.startswith("### "): txt = convert_bold(line.lstrip("### ").strip()) story.append(Paragraph(txt, style_h3)) story.append(Spacer(1, 4)) continue elif line.startswith("## "): txt = convert_bold(line.lstrip("## ").strip()) story.append(Paragraph(txt, style_h2)) story.append(Spacer(1, 5)) continue elif line.startswith("# "): txt = convert_bold(line.lstrip("# ").strip()) story.append(Paragraph(txt, style_h1)) story.append(Spacer(1, 6)) continue # 表格分隔符 if all(c in "-| " for c in line): continue # 表格行 if line.startswith("|") and line.endswith("|"): in_table = True cells = [cell.strip() for cell in line.split("|")[1:-1]] paras = [Paragraph(convert_bold(c), table_text_style) for c in cells] table_rows.append(paras) continue # 结束表格渲染 if in_table: in_table = False if table_rows: col_cnt = len(table_rows[0]) col_w = [530 / col_cnt] * col_cnt tbl = Table(table_rows, colWidths=col_w, repeatRows=1) tbl.setStyle(TableStyle(TABLE_BASE_STYLE)) story.append(tbl) story.append(Spacer(1, 10)) table_rows = [] # 普通正文 fmt_line = convert_bold(line) story.append(Paragraph(fmt_line, style_normal)) story.append(Spacer(1, 3)) # 剩余表格 if in_table and table_rows: col_cnt = len(table_rows[0]) col_w = [530 / col_cnt] * col_cnt tbl = Table(table_rows, colWidths=col_w, repeatRows=1) tbl.setStyle(TableStyle(TABLE_BASE_STYLE)) story.append(tbl) story.append(Spacer(1, 10)) doc.build(story) def get_pdf_download_url(pdf_filename: str) -> str: return f"{SERVER_HOST}/static/report_pdf/{pdf_filename}"