# -*- coding: utf-8 -*- """ PDF工具类 - 用于生成和填充PDF文档 """ import io import os from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.colors import HexColor from pypdf import PdfReader, PdfWriter from app.core.logger import log class PDFUtil: """PDF工具类""" # 字体路径(需要中文字体支持) FONT_PATH = os.path.join(os.path.dirname(__file__), '..', '..', 'static', 'fonts') @classmethod def register_chinese_font(cls): """注册中文字体""" try: # 尝试注册思源黑体或其他中文字体 font_file = os.path.join(cls.FONT_PATH, 'SimHei.ttf') if os.path.exists(font_file): pdfmetrics.registerFont(TTFont('SimHei', font_file)) return 'SimHei' # 尝试系统字体 system_fonts = [ 'C:/Windows/Fonts/simhei.ttf', # Windows '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', # Linux '/System/Library/Fonts/PingFang.ttc', # macOS ] for font_path in system_fonts: if os.path.exists(font_path): pdfmetrics.registerFont(TTFont('ChineseFont', font_path)) return 'ChineseFont' log.warning("未找到中文字体,将使用默认字体") return 'Helvetica' except Exception as e: log.error(f"注册中文字体失败: {e}") return 'Helvetica' @classmethod def create_overlay_pdf(cls, data: dict, page_width: float, page_height: float) -> io.BytesIO: """ 创建覆盖层PDF(用于在模板上叠加文字) 参数: - data: 要填充的数据字典,格式: {字段名: (x, y, 值, 字体大小, 颜色)} - page_width: 页面宽度 - page_height: 页面高度 返回: - BytesIO: PDF字节流 """ buffer = io.BytesIO() c = canvas.Canvas(buffer, pagesize=(page_width, page_height)) font_name = cls.register_chinese_font() for field_name, field_config in data.items(): if isinstance(field_config, tuple) and len(field_config) >= 3: x, y, value = field_config[:3] font_size = field_config[3] if len(field_config) > 3 else 12 color = field_config[4] if len(field_config) > 4 else '#000000' c.setFont(font_name, font_size) c.setFillColor(HexColor(color)) c.drawString(x * mm, y * mm, str(value)) c.save() buffer.seek(0) return buffer @classmethod def fill_pdf_template(cls, template_path: str, data: dict, field_positions: dict) -> bytes: """ 填充PDF模板 参数: - template_path: 模板文件路径 - data: 数据字典 - field_positions: 字段位置配置,格式: {字段名: (x, y, 字体大小, 颜色)} 返回: - bytes: 填充后的PDF字节 """ # 读取模板 reader = PdfReader(template_path) writer = PdfWriter() # 获取第一页尺寸 first_page = reader.pages[0] page_width = float(first_page.mediabox.width) page_height = float(first_page.mediabox.height) # 构建覆盖数据 overlay_data = {} for field_name, position in field_positions.items(): if field_name in data and data[field_name] is not None: x, y = position[:2] font_size = position[2] if len(position) > 2 else 12 color = position[3] if len(position) > 3 else '#000000' overlay_data[field_name] = (x, y, data[field_name], font_size, color) # 创建覆盖层 overlay_buffer = cls.create_overlay_pdf(overlay_data, page_width, page_height) overlay_reader = PdfReader(overlay_buffer) # 合并页面 for i, page in enumerate(reader.pages): if i == 0 and len(overlay_reader.pages) > 0: page.merge_page(overlay_reader.pages[0]) writer.add_page(page) # 输出 output = io.BytesIO() writer.write(output) output.seek(0) return output.read()