Auto_PPT_Generator / modules /ppx_builder.py
Corin1998's picture
Update modules/ppx_builder.py
8613cb4 verified
Raw History Blame Contribute Delete
6.61 kB
import io
from typing import List, Tuple, Dict, Any, Optional
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
from PIL import Image
import matplotlib.pyplot as plt
def _add_logo(prs: Presentation, slide, logo_bytes: Optional[bytes]):
if not logo_bytes:
return
img = Image.open(io.BytesIO(logo_bytes)).convert("RGBA")
# Resize to fit width<=2.0in, height<=1.0in
max_w, max_h = Inches(2.0), Inches(1.0)
w, h = img.size
ratio = min(max_w / max(w, 1), max_h / max(h, 1))
new_size = (max(1, int(w * ratio)), max(1, int(h * ratio)))
resized = img.resize(new_size)
b = io.BytesIO()
resized.save(b, format="PNG")
b.seek(0)
# Place top-right with small margin
left = prs.slide_width - max_w - Inches(0.5)
top = Inches(0.2)
slide.shapes.add_picture(b, left, top)
def _apply_theme_bg(slide, rgb):
fill = slide.background.fill
fill.solid()
fill.fore_color.rgb = RGBColor(*rgb)
def _title_slide(prs, title_text: str, theme_rgb, logo_bytes):
slide_layout = prs.slide_layouts[0] # Title slide
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]
title.text = title_text
subtitle.text = "自動生成プレゼンテーション"
# Accent band
_apply_theme_bg(slide, theme_rgb)
# White rounded rectangle for readability
left = Inches(0.6)
top = Inches(1.8)
width = prs.slide_width - Inches(1.2)
height = Inches(2.2)
box = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE, left, top, width, height)
box.fill.solid()
box.fill.fore_color.rgb = RGBColor(255, 255, 255)
box.line.color.rgb = RGBColor(0, 0, 0)
box.line.transparency = 0.8
# Reposition title
title.left = left + Inches(0.3)
title.top = top + Inches(0.3)
title.width = width - Inches(0.6)
title.height = Inches(1.4)
for p in title.text_frame.paragraphs:
p.font.size = Pt(40)
p.font.bold = True
# Subtitle
subtitle.left = left + Inches(0.3)
subtitle.top = top + Inches(1.6)
subtitle.width = width - Inches(0.6)
subtitle.height = Inches(0.8)
for p in subtitle.text_frame.paragraphs:
p.font.size = Pt(16)
p.font.bold = False
_add_logo(prs, slide, logo_bytes)
def _summary_slide(prs, summary: str):
if not summary:
return
slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title and Content
slide.shapes.title.text = "エグゼクティブサマリー"
tf = slide.placeholders[1].text_frame
tf.clear()
# Split summary into bullet-ish lines
lines = [ln.strip() for ln in summary.splitlines() if ln.strip()]
if not lines:
lines = [summary]
for i, ln in enumerate(lines):
p = tf.add_paragraph() if i > 0 else tf.paragraphs[0]
p.text = ln
p.level = 0
def _section_slide(prs, title: str, bullets: List[str]):
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = title[:90]
tf = slide.placeholders[1].text_frame
tf.clear()
if not bullets:
bullets = ["(要点なし)"]
for i, b in enumerate(bullets[:12]):
p = tf.add_paragraph() if i > 0 else tf.paragraphs[0]
p.text = b
p.level = 0
def _table_slide(prs, title: str, pairs: List[tuple]):
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.shapes.title.text = title
rows = len(pairs) + 1
cols = 2
left = Inches(0.5)
top = Inches(1.8)
width = prs.slide_width - Inches(1.0)
height = prs.slide_height - Inches(2.6)
table = slide.shapes.add_table(rows, cols, left, top, width, height).table
table.cell(0, 0).text = "項目"
table.cell(0, 1).text = "値"
for r, (k, v) in enumerate(pairs, start=1):
table.cell(r, 0).text = str(k)
table.cell(r, 1).text = str(v)
def _chart_slide(prs, title: str, series: List[tuple]):
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.shapes.title.text = title
# Build bar chart via matplotlib and embed as image
labels = [x[0] for x in series]
values = [x[1] for x in series]
fig = plt.figure(figsize=(8, 4.5))
plt.bar(range(len(values)), values)
plt.xticks(range(len(labels)), labels, rotation=20, ha='right')
plt.tight_layout()
buf = io.BytesIO()
fig.savefig(buf, format='png', dpi=200)
plt.close(fig)
buf.seek(0)
left = Inches(0.5)
top = Inches(1.6)
width = prs.slide_width - Inches(1.0)
height = prs.slide_height - Inches(2.2)
slide.shapes.add_picture(buf, left, top, width=width, height=height)
def _add_footer(prs, theme_rgb):
for idx, slide in enumerate(prs.slides, start=1):
left = Inches(0.3)
top = prs.slide_height - Inches(0.4)
width = prs.slide_width - Inches(0.6)
height = Inches(0.3)
shp = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left, top, width, height)
shp.fill.solid()
shp.fill.fore_color.rgb = RGBColor(*theme_rgb)
shp.line.fill.background()
# Slide number text box
tx = slide.shapes.add_textbox(prs.slide_width - Inches(1.0), top - Inches(0.05), Inches(0.8), Inches(0.3))
tf = tx.text_frame
p = tf.paragraphs[0]
p.text = f"{idx}"
p.font.size = Pt(10)
p.alignment = PP_ALIGN.RIGHT
def build_presentation(output_path: str,
title: str,
theme_rgb: tuple,
logo_bytes: Optional[bytes],
executive_summary: Optional[str],
sections: List[Tuple[str, str]],
bullets_by_section: Dict[int, List[str]],
tables: List[Dict[str, Any]],
charts: List[Dict[str, Any]]):
prs = Presentation()
# Title slide
_title_slide(prs, title, theme_rgb, logo_bytes)
# Summary
_summary_slide(prs, executive_summary)
# Sections
for idx, (sec_title, _body) in enumerate(sections):
bullets = bullets_by_section.get(idx, [])
_section_slide(prs, sec_title, bullets)
# Tables
for tbl in tables:
_table_slide(prs, tbl.get("title", "表"), tbl.get("pairs", []))
# Charts
for ch in charts:
_chart_slide(prs, ch.get("title", "チャート"), ch.get("series", []))
_add_footer(prs, theme_rgb)
prs.save(output_path)