diff --git a/main/test/make_ppt.py b/main/test/make_ppt.py new file mode 100644 index 0000000..b9adc35 --- /dev/null +++ b/main/test/make_ppt.py @@ -0,0 +1,517 @@ +from pptx import Presentation +from pptx.util import Inches, Pt, Cm +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN +from pptx.enum.text import MSO_ANCHOR # 导入正确的垂直对齐枚举 +from pptx.enum.shapes import MSO_SHAPE +from pptx.chart.data import ChartData +from pptx.enum.chart import XL_CHART_TYPE +from pptx.table import Table + +def apply_gradient_background(slide): + """应用统一的渐变背景""" + background = slide.background + fill = background.fill + fill.gradient() + fill.gradient_stops[0].color.rgb = RGBColor(0, 32, 96) # 深蓝 + fill.gradient_stops[1].color.rgb = RGBColor(0, 112, 192) # 蓝色 + +def create_content_slide(prs, title_text, subtitle_text=None, content_items=None): + """创建带二级标题的内容页""" + slide_layout = prs.slide_layouts[6] # 空白布局 + slide = prs.slides.add_slide(slide_layout) + apply_gradient_background(slide) + + # 添加主标题 + left = Cm(1.5) + top = Cm(1.5) + width = Cm(10) + height = Cm(2) + title_box = slide.shapes.add_textbox(left, top, width, height) + tf = title_box.text_frame + tf.clear() + + p = tf.add_paragraph() + p.text = title_text + p.font.size = Pt(36) + p.font.color.rgb = RGBColor(255, 255, 255) + p.font.bold = True + + # 添加二级标题(如果有) + if subtitle_text: + left = Cm(1.5) + top = Cm(3) + width = Cm(10) + height = Cm(1.5) + subtitle_box = slide.shapes.add_textbox(left, top, width, height) + tf = subtitle_box.text_frame + tf.clear() + + p = tf.add_paragraph() + p.text = subtitle_text + p.font.size = Pt(24) + p.font.color.rgb = RGBColor(200, 230, 255) # 浅蓝色 + p.font.italic = True + + # 添加内容区域 + if content_items: + # 添加内容文字 + left_text = Cm(2) + top_text = Cm(5.5 if subtitle_text else 4.5) + width_text = Cm(27.5) + height_text = Cm(8) + text_box = slide.shapes.add_textbox(left_text, top_text, width_text, height_text) + tf = text_box.text_frame + tf.clear() + + for item in content_items: + p = tf.add_paragraph() + p.text = item["text"] + p.font.size = Pt(item.get("size", 18)) + p.font.color.rgb = RGBColor(255, 255, 255) + p.space_after = Pt(item.get("space_after", 8)) + p.level = item.get("level", 0) + if item.get("bold", False): + p.font.bold = True + if item.get("bullet", False): + p.text = "• " + p.text + + return slide + +def create_table_slide(prs, title_text, subtitle_text, headers, data): + """创建带表格的数据页""" + slide = create_content_slide(prs, title_text, subtitle_text) + + # 计算表格位置和大小 + left = Cm(2) + top = Cm(5.5 if subtitle_text else 4.5) + width = Cm(26.5) + height = Cm(8) + + # 创建表格 (行数=数据行数+1,列数=标题数) + rows = len(data) + 1 + cols = len(headers) + table = slide.shapes.add_table(rows, cols, left, top, width, height).table + + # 设置表格样式 + table.first_row = True # 强调第一行 + table.horz_banding = True # 横向条纹 + + # 设置表头 + for col_idx, header in enumerate(headers): + cell = table.cell(0, col_idx) + cell.text = header + cell.fill.solid() + cell.fill.fore_color.rgb = RGBColor(0, 64, 128) # 深蓝色表头 + cell.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255) + cell.text_frame.paragraphs[0].font.bold = True + cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # 填充表格数据 + for row_idx, row_data in enumerate(data, start=1): + for col_idx, cell_data in enumerate(row_data): + cell = table.cell(row_idx, col_idx) + cell.text = str(cell_data) + cell.fill.solid() + cell.fill.fore_color.rgb = RGBColor(255, 255, 255) + cell.fill.fore_color.alpha = 0.2 # 半透明白色 + cell.text_frame.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0) # 黑色文字 + cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER + + # 设置表格边框 + for cell in table.iter_cells(): + cell.margin_left = Cm(0.1) + cell.margin_right = Cm(0.1) + cell.margin_top = Cm(0.05) + cell.margin_bottom = Cm(0.05) + cell.vertical_anchor = MSO_ANCHOR.MIDDLE + + return slide + + +def create_chart_slide(prs, title_text, subtitle_text, categories, data): + """创建图表的数据页""" + slide = create_content_slide(prs, title_text, subtitle_text) + + #半透明白色背景 + left_content = Cm(1.5) + top_content = Cm(5 if subtitle_text else 4) # 根据是否有二级标题调整位置 + width_content = Cm(28.5) + height_content = Cm(9) + content_box = slide.shapes.add_shape( + MSO_SHAPE.ROUNDED_RECTANGLE, left_content, top_content, width_content, height_content + ) + content_box.fill.solid() + content_box.fill.fore_color.rgb = RGBColor(255, 255, 255) + content_box.fill.fore_color.alpha = 0.2 # 20%透明度 + content_box.line.color.rgb = RGBColor(200, 230, 255) + + # 添加图表数据 + chart_data = ChartData() + chart_data.categories = categories + for lable_data in data: + chart_data.add_series(lable_data[0], lable_data[1]) + + # 添加图表 + left_chart = Cm(3) + top_chart = Cm(6) + width_chart = Cm(25) + height_chart = Cm(7) + chart = slide.shapes.add_chart( + XL_CHART_TYPE.LINE, left_chart, top_chart, width_chart, height_chart, chart_data + ) + + # 设置图表颜色以匹配主题 + chart.chart.plots[0].series[0].format.fill.solid() + chart.chart.plots[0].series[0].format.fill.fore_color.rgb = RGBColor(100, 180, 255) + chart.chart.plots[0].series[1].format.fill.solid() + chart.chart.plots[0].series[1].format.fill.fore_color.rgb = RGBColor(200, 230, 255) + + return slide + + + + +def create_image_layout_slide(prs, title_text, subtitle_text, images): + """创建图片布局页面""" + slide = create_content_slide(prs, title_text, subtitle_text) + + # 清除默认内容区域 + for shape in slide.shapes: + if shape.shape_type == MSO_SHAPE.ROUNDED_RECTANGLE: + sp = shape._element + sp.getparent().remove(sp) + + # 根据图片数量决定布局 + if len(images) == 1: + # 单张大图布局 + left = Cm(3) + top = Cm(5 if subtitle_text else 4) + width = Cm(25) + height = Cm(12) + pic = slide.shapes.add_picture(images[0]["path"], left, top, width, height) + + # 添加图片说明 + if "caption" in images[0]: + left_cap = Cm(3) + top_cap = Cm(17 if subtitle_text else 16) + width_cap = Cm(25) + height_cap = Cm(1) + cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) + tf = cap.text_frame + p = tf.add_paragraph() + p.text = images[0]["caption"] + p.font.size = Pt(14) + p.font.color.rgb = RGBColor(200, 230, 255) + p.alignment = PP_ALIGN.CENTER + + elif len(images) == 2: + # 两张图片并排布局 + # 第一张图片 + left1 = Cm(2) + top1 = Cm(5 if subtitle_text else 4) + width1 = Cm(13) + height1 = Cm(12) + pic1 = slide.shapes.add_picture(images[0]["path"], left1, top1, width1, height1) + + # 第二张图片 + left2 = Cm(16) + top2 = Cm(5 if subtitle_text else 4) + width2 = Cm(13) + height2 = Cm(12) + pic2 = slide.shapes.add_picture(images[1]["path"], left2, top2, width2, height2) + + # 添加图片说明 + for i, img in enumerate(images): + if "caption" in img: + left_cap = Cm(2 if i == 0 else 16) + top_cap = Cm(17 if subtitle_text else 16) + width_cap = Cm(13) + height_cap = Cm(1) + cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) + tf = cap.text_frame + p = tf.add_paragraph() + p.text = img["caption"] + p.font.size = Pt(12) + p.font.color.rgb = RGBColor(200, 230, 255) + p.alignment = PP_ALIGN.CENTER + + elif len(images) >= 3: + # 三张图片网格布局 + img_size = Cm(8) + gap = Cm(1) + + # 第一行 + for i in range(min(3, len(images))): + left = Cm(3) + (img_size + gap) * i + top = Cm(5 if subtitle_text else 4) + pic = slide.shapes.add_picture(images[i]["path"], left, top, img_size, img_size) + + if "caption" in images[i]: + left_cap = left + top_cap = top + img_size + Cm(0.5) + width_cap = img_size + height_cap = Cm(1) + cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) + tf = cap.text_frame + p = tf.add_paragraph() + p.text = images[i]["caption"] + p.font.size = Pt(10) + p.font.color.rgb = RGBColor(200, 230, 255) + p.alignment = PP_ALIGN.CENTER + + # 第二行(如果有4张以上图片) + img_size = Cm(4) + gap = Cm(1) + for i in range(3, len(images)): + left = Cm(3) + (img_size + gap) * (i - 3) + top = Cm(16 if subtitle_text else 15) + pic = slide.shapes.add_picture(images[i]["path"], left, top, img_size, img_size) + + if "caption" in images[i]: + left_cap = left + top_cap = top + img_size + Cm(0.5) + width_cap = img_size + height_cap = Cm(1) + cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) + tf = cap.text_frame + p = tf.add_paragraph() + p.text = images[i]["caption"] + p.font.size = Pt(10) + p.font.color.rgb = RGBColor(200, 230, 255) + p.alignment = PP_ALIGN.CENTER + + return slide + +def create_unified_ppt(output_filename): + # 创建一个16:9宽屏演示文稿对象 + prs = Presentation() + #prs.slide_width = Inches(13.333) # 16:9的宽度 + #prs.slide_height = Inches(7.5) # 16:9的高度 + + prs.slide_width = Inches(16) # 16:9的宽度 + prs.slide_height = Inches(9) # 16:9的高度 + + # ===== 封面幻灯片 ===== + slide_layout = prs.slide_layouts[6] # 空白布局 + slide = prs.slides.add_slide(slide_layout) + apply_gradient_background(slide) + + # 添加宽屏标题 + left = Cm(1.5) + top = Cm(4) + width = Cm(30) + height = Cm(4) + title_box = slide.shapes.add_textbox(left, top, width, height) + tf = title_box.text_frame + + p = tf.add_paragraph() + p.text = "专业商务演示" + p.font.size = Pt(48) + p.font.color.rgb = RGBColor(255, 255, 255) + p.font.bold = True + p.alignment = PP_ALIGN.LEFT + + # 添加副标题 + left = Cm(1.5) + top = Cm(8) + width = Cm(20) + height = Cm(2) + subtitle_box = slide.shapes.add_textbox(left, top, width, height) + tf = subtitle_box.text_frame + + p = tf.add_paragraph() + p.text = "包含表格和图片布局的演示文稿" + p.font.size = Pt(20) + p.font.color.rgb = RGBColor(200, 230, 255) + p.alignment = PP_ALIGN.LEFT + + # ===== 目录幻灯片 ===== + create_content_slide( + prs, + "内容目录", + content_items=[ + {"text": "1. 项目概述", "size": 24, "space_after": 12}, + {"text": "2. 市场分析", "size": 24, "space_after": 12}, + {"text": "3. 财务数据", "size": 24, "space_after": 12}, + {"text": "4. 产品展示", "size": 24, "space_after": 12}, + {"text": "5. 技术架构", "size": 24, "space_after": 12} + ] + ) + + # ===== 一级标题内容页 ===== + create_content_slide( + prs, + "项目概述", + content_items=[ + {"text": "项目背景", "size": 22, "bold": True, "space_after": 8}, + {"text": "随着数字化转型加速,企业需要更高效的解决方案来应对市场变化。", "bullet": True}, + {"text": "本项目旨在开发一套智能化管理系统,提升企业运营效率。", "bullet": True}, + {"text": "项目目标", "size": 22, "bold": True, "space_after": 8, "space_before": 12}, + {"text": "构建可扩展的技术架构,支持未来5年业务增长", "bullet": True}, + {"text": "实现关键业务流程自动化,减少人工干预", "bullet": True}, + {"text": "提供数据分析和决策支持功能", "bullet": True} + ] + ) + + # ===== 带二级标题的内容页 ===== + create_content_slide( + prs, + "技术架构", + "核心技术组件", # 二级标题 + content_items=[ + {"text": "前端技术", "size": 22, "bold": True, "space_after": 8}, + {"text": "采用React框架构建响应式用户界面", "bullet": True}, + {"text": "使用TypeScript提高代码质量", "bullet": True}, + {"text": "后端技术", "size": 22, "bold": True, "space_after": 8, "space_before": 12}, + {"text": "基于Spring Boot的微服务架构", "bullet": True}, + {"text": "使用Kubernetes进行容器编排", "bullet": True}, + {"text": "数据库技术", "size": 22, "bold": True, "space_after": 8, "space_before": 12}, + {"text": "主数据库: PostgreSQL 14", "bullet": True}, + {"text": "缓存层: Redis集群", "bullet": True}, + {"text": "数据分析: Elasticsearch", "bullet": True} + ] + ) + # ===== 表格数据页面 ===== + headers = ["季度", "营收(万元)", "同比增长", "利润率", "市场份额"] + data = [ + ["Q1 2023", 1250, "12.5%", "18.2%", "22.4%"], + ["Q2 2023", 1430, "15.8%", "19.1%", "23.7%"], + ["Q3 2023", 1580, "18.2%", "20.3%", "25.1%"], + ["Q4 2023", 1720, "20.1%", "21.5%", "26.8%"], + ["Q1 2024", 1850, "22.7%", "22.8%", "28.3%"] + ] + create_table_slide( + prs, + "财务数据", + "2023-2024季度表现", + headers, + data + ) + + # ===== 图片布局页面(单张大图)===== + create_image_layout_slide( + prs, + "产品展示", + "旗舰产品全景", + [{ + "path": "product.jpg", # 替换为实际图片路径 + "caption": "图1: 公司旗舰产品XYZ系列" + }] + ) + + # ===== 图片布局页面(两张并排)===== + create_image_layout_slide( + prs, + "技术对比", + "新旧技术方案比较", + [ + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "图2: 传统技术方案" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "图3: 创新技术方案" + } + ] + ) + + # ===== 图片布局页面(三栏)===== + create_image_layout_slide( + prs, + "应用场景", + "多领域解决方案", + [ + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "工业制造" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "金融服务" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "医疗健康" + }, + ] + ) + # ===== 图片布局页面(多栏)===== + create_image_layout_slide( + prs, + "应用场景", + "多领域解决方案", + [ + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "工业制造" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "金融服务" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "医疗健康" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "医疗健康" + }, + { + "path": "product.jpg", # 替换为实际图片路径 + "caption": "医疗健康" + }, + ] + ) + + # ===== 图表幻灯片 ===== + slide = create_chart_slide( + prs, + "市场分析", + "近五年增长趋势", # 二级标题 + categories = ['2019', '2020', '2021', '2022', '2023'], + data = [ + ['市场规模(亿元)', [45, 52, 61, 78, 92]], + ['年增长率(%)', [8.2, 15.6, 17.3, 27.9, 17.9]], + ] + ) + + # ===== 结束幻灯片 ===== + slide = create_content_slide( + prs, + "感谢聆听", + "期待与您合作" + ) + + # 在内容区域添加联系信息 + left = Cm(12) + top = Cm(12) + width = Cm(16) + height = Cm(3) + contact_box = slide.shapes.add_textbox(left, top, width, height) + tf = contact_box.text_frame + + p = tf.add_paragraph() + p.text = "联系方式" + p.font.size = Pt(24) + p.font.color.rgb = RGBColor(255, 255, 255) + p.font.bold = True + p.alignment = PP_ALIGN.CENTER + p.space_after = Pt(16) + + p = tf.add_paragraph() + p.text = "邮箱: contact@example.com\n电话: 123-456-7890\n网址: www.example.com" + p.font.size = Pt(18) + p.font.color.rgb = RGBColor(200, 230, 255) + p.alignment = PP_ALIGN.CENTER + p.space_before = Pt(8) + + # 保存演示文稿 + prs.save(output_filename) + +if __name__ == "__main__": + # 使用前请替换示例图片路径为实际图片路径 + create_unified_ppt("professional_presentation_with_tables_and_images.pptx") + print("带表格和图片布局的PPT生成完成!")