Auto_PPT_Generator / modules /ppx_builder.py
Corin1998's picture
Update modules/ppx_builder.py
8613cb4 verified
raw
history blame
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)