from reportlab.pdfbase import pdfmetrics # 注册字体 from reportlab.pdfbase.ttfonts import TTFont # 字体类 from reportlab.platypus import Table, SimpleDocTemplate, Paragraph, Image, Frame, PageTemplate, Spacer # 报告内容相关类 from reportlab.lib.pagesizes import A4 # 页面的标志尺寸(8.5*inch, 11*inch) from reportlab.lib.styles import getSampleStyleSheet # 文本样式 from reportlab.lib import colors # 颜色模块 from reportlab.graphics.charts.barcharts import VerticalBarChart # 图表类 from reportlab.graphics.charts.legends import Legend # 图例类 from reportlab.graphics.shapes import Drawing # 绘图工具 from reportlab.lib.units import cm # 单位:cm import argparse import re pdfmetrics.registerFont(TTFont('SimSun', './py/SimSun.ttf')) class Graphs: # 绘制标题 @staticmethod def draw_title(title: str, linespace=50, fontsize=18): # 获取所有样式表 style = getSampleStyleSheet() # 拿到标题样式 ct = style['Heading2'] # 单独设置样式相关属性 ct.fontName = 'SimSun' # 字体名 ct.fontSize = fontsize # 字体大小 ct.leading = linespace # 行间距 ct.textColor = colors.green # 字体颜色 ct.alignment = 1 # 居中 ct.bold = True # 创建标题对应的段落,并且返回 return Paragraph(title, ct) # 绘制小标题 @staticmethod def draw_little_title(title: str): # 获取所有样式表 style = getSampleStyleSheet() # 拿到标题样式 ct = style['Normal'] # 单独设置样式相关属性 ct.fontName = 'SimSun' # 字体名 ct.fontSize = 15 # 字体大小 ct.leading = 30 # 行间距 ct.textColor = colors.red # 字体颜色 # 创建标题对应的段落,并且返回 return Paragraph(title, ct) # 绘制普通段落内容 @staticmethod def draw_text(text: str,alignment=0,firstLineIndent=32): # 获取所有样式表 style = getSampleStyleSheet() # 获取普通样式 ct = style['Normal'] ct.fontName = 'SimSun' ct.fontSize = 12 ct.wordWrap = 'CJK' # 设置自动换行 ct.alignment = alignment # 左对齐 ct.firstLineIndent = firstLineIndent # 第一行开头空格 ct.leading = 25 return Paragraph(text, ct) # 绘制表格 @staticmethod def draw_info_table(*args, col_width=120): # 列宽度 col_width = col_width style = [ ('FONTNAME', (0, 0), (-1, -1), 'SimSun'), # 字体 ('FONTSIZE', (0, 0), (-1, 0), 12), # 第一行的字体大小 ('FONTSIZE', (0, 1), (-1, -1), 12), # 第二行到最后一行的字体大小 ('BACKGROUND', (0, 0), (-1, 0), '#d5dae6'), # 设置第一行背景颜色 ('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 第一行水平居中 ('ALIGN', (0, 1), (-1, -1), 'CENTER'), # 第二行到最后一行左右左对齐 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 所有表格上下居中对齐 ('TEXTCOLOR', (0, 0), (-1, -1), colors.darkslategray), # 设置表格内文字颜色 ('GRID', (0, 0), (-1, -1), 0.5, colors.black), # 设置表格框线为grey色,线宽为0.5 # ('SPAN', (0, 1), (0, 2)), # 合并第一列二三行 # ('SPAN', (0, 3), (0, 4)), # 合并第一列三四行 # ('SPAN', (0, 5), (0, 6)), # 合并第一列五六行 # ('SPAN', (0, 7), (0, 8)), # 合并第一列五六行 ] table = Table(args, colWidths=col_width, rowHeights=(1*cm, 1*cm),style=style) return table # 绘制表格 @staticmethod def draw_result_table(*args, col_width=120): # 列宽度 col_width = col_width style = [ ('FONTNAME', (0, 0), (-1, -1), 'SimSun'), # 字体 ('FONTSIZE', (0, 0), (-1, 0), 12), # 第一行的字体大小 ('FONTSIZE', (0, 1), (-1, -1), 12), # 第二行到最后一行的字体大小 ('BACKGROUND', (0, 0), (-1, 0), '#d5dae6'), # 设置第一行背景颜色 ('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 第一行水平居中 ('ALIGN', (0, 1), (-1, -1), 'CENTER'), # 第二行到最后一行左右左对齐 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 所有表格上下居中对齐 ('TEXTCOLOR', (0, 0), (-1, -1), colors.darkslategray), # 设置表格内文字颜色 ('GRID', (0, 0), (-1, -1), 0.5, colors.black), # 设置表格框线为grey色,线宽为0.5 # ('SPAN', (0, 1), (0, 2)), # 合并第一列二三行 # ('SPAN', (0, 3), (0, 4)), # 合并第一列三四行 # ('SPAN', (0, 5), (0, 6)), # 合并第一列五六行 # ('SPAN', (0, 7), (0, 8)), # 合并第一列五六行 ] table = Table(args, colWidths=col_width, style=style) return table @staticmethod def draw_explanation_table(*args, col_width=120): # 列宽度 col_width = col_width style = [ ('FONTNAME', (0, 0), (-1, -1), 'SimSun'), # 字体 ('FONTSIZE', (0, 0), (-1, 0), 12), # 第一行的字体大小 ('FONTSIZE', (0, 1), (-1, -1), 12), # 第二行到最后一行的字体大小 ('BACKGROUND', (0, 0), (-1, 0), '#d5dae6'), # 设置第一行背景颜色 ('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 第一行水平居中 ('ALIGN', (0, 1), (-1, -1), 'CENTER'), # 第二行到最后一行左右左对齐 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 所有表格上下居中对齐 ('TEXTCOLOR', (0, 0), (-1, -1), colors.darkslategray), # 设置表格内文字颜色 ('GRID', (0, 0), (-1, -1), 0.5, colors.black), # 设置表格框线为grey色,线宽为0.5 # ('SPAN', (0, 1), (0, 2)), # 合并第一列二三行 # ('SPAN', (0, 3), (0, 4)), # 合并第一列三四行 # ('SPAN', (0, 5), (0, 6)), # 合并第一列五六行 # ('SPAN', (0, 7), (0, 8)), # 合并第一列五六行 ] table = Table(args, colWidths=(100,350), style=style) return table # 创建图表 @staticmethod def draw_bar(bar_data: list, ax: list, items: list): drawing = Drawing(500, 250) bc = VerticalBarChart() bc.x = 45 # 整个图表的x坐标 bc.y = 45 # 整个图表的y坐标 bc.height = 200 # 图表的高度 bc.width = 350 # 图表的宽度 bc.data = bar_data bc.strokeColor = colors.black # 顶部和右边轴线的颜色 bc.valueAxis.valueMin = 5000 # 设置y坐标的最小值 bc.valueAxis.valueMax = 26000 # 设置y坐标的最大值 bc.valueAxis.valueStep = 2000 # 设置y坐标的步长 bc.categoryAxis.labels.dx = 2 bc.categoryAxis.labels.dy = -8 bc.categoryAxis.labels.angle = 20 bc.categoryAxis.categoryNames = ax # 图示 leg = Legend() leg.fontName = 'SimSun' leg.alignment = 'right' leg.boxAnchor = 'ne' leg.x = 475 # 图例的x坐标 leg.y = 240 leg.dxTextSpace = 10 leg.columnMaximum = 3 leg.colorNamePairs = items drawing.add(leg) drawing.add(bc) return drawing # 绘制图片 @staticmethod def draw_img(path): img = Image(path) # 读取指定路径下的图片 img.drawWidth = 12*cm # 设置图片的宽度 img.drawHeight = 6*cm # 设置图片的高度 return img def footer(canvas, doc): canvas.saveState() canvas.setFont('SimSun', 12) canvas.line(cm, cm * 1.2, A4[0] - cm, cm * 1.2) canvas.drawCentredString(A4[0]/2, cm * 0.7, "本报告仅供临床参考,不做诊断证明之用") canvas.restoreState() def header(canvas, doc): canvas.saveState() canvas.drawImage(logo, 50, doc.height + 50,2*cm,2*cm) canvas.setFont('SimSun', 12) canvas.line(cm, cm * 1.2, A4[0] - cm, cm * 1.2) canvas.drawCentredString(A4[0]/2, cm * 0.7, "本报告仅供临床参考,不做诊断证明之用") canvas.restoreState() if __name__ == '__main__': parse = argparse.ArgumentParser() parse.add_argument('filename', type=str) parse.add_argument('logo', type=str) parse.add_argument('output', type=str) args = parse.parse_args() with open(args.filename, 'rb') as fp: data = fp.readlines() d = eval(data[0]) global logo, output_path logo = args.logo output_path = args.output gender = ['男','女'] # 创建内容对应的空列表 content = list() # 添加标题 content.append(Graphs.draw_title(d['userEntity']['institutionName'],linespace=20,fontsize=14)) content.append(Graphs.draw_title(d['name'])) # 基本信息 content.append(Graphs.draw_text('【基本信息】',firstLineIndent=0)) data = [ ('姓 名', '性 别', '出生日期'), (d['userEntity']['petName'],gender[int(d['userEntity']['gender'])],d['userEntity']['birthday']) ] content.append(Graphs.draw_info_table(*data, col_width=150)) # 添加段落文字 s = Spacer(width=0, height=1*cm) content.append(s) content.append(Graphs.draw_text('【测试结果】',firstLineIndent=0)) # 添加图片 # content.append(Graphs.draw_img('1.png')) data = [('项目', '结果','常模参考值')] for i in d['result']: data.append((i['name'],i['score'], i['reference'])) content.append(Graphs.draw_result_table(*data,col_width=150)) s = Spacer(width=0, height=1*cm) content.append(s) content.append(Graphs.draw_text('【结果说明】',firstLineIndent=0)) # 添加表格 styles = getSampleStyleSheet() styleC = styles["BodyText"] styleC.fontName = 'SimSun' styleC.fontSize = 12 # 字体大小 styleC.alignment = 1 # 居中 styleC.textColor = colors.darkslategray styles = getSampleStyleSheet() styleL = styles["BodyText"] styleL.fontName = 'SimSun' styleL.fontSize = 12 # 字体大小 styleL.alignment = 0 # 左对齐 styleL.textColor = colors.darkslategray for i in d['result']: if len(i['nameExplain'])>1: explanation = Paragraph(i['nameExplain'], styleL) else: explanation = Paragraph(i['nameExplain'], styleC) if len(i['suggestion'])>1: suggestion = Paragraph(i['suggestion'], styleL) else: suggestion = Paragraph(i['suggestion'], styleC) data = [ ('维度名称', i['name']), ('得 分', i['score']), ('结 论', i['symptom']), ('维度说明', explanation), ('建 议',suggestion) ] content.append(Graphs.draw_explanation_table(*data)) s = Spacer(width=0, height=2*cm) content.append(s) content.append(Graphs.draw_text(f'医生签名:__________________ 测评日期:{d["reportTime"]}',2)) # 生成pdf文件 output_name = f"{output_path}" doc = SimpleDocTemplate(output_name, pagesize=A4) frame = Frame(36, 36, A4[0] - 72, A4[1] - 72) header_frame = Frame(36, 36, A4[0] - 72, A4[1] - 72) footer_template = PageTemplate(id='footer', frames=[frame], onPage=footer) header_template = PageTemplate(id='header', frames=[header_frame], onPage=header) doc.addPageTemplates([header_template, footer_template]) doc.build(content,onFirstPage=footer, onLaterPages=footer)