Corin1998 commited on
Commit
8613cb4
·
verified ·
1 Parent(s): ff7d898

Update modules/ppx_builder.py

Browse files
Files changed (1) hide show
  1. modules/ppx_builder.py +67 -58
modules/ppx_builder.py CHANGED
@@ -5,52 +5,55 @@ from pptx.util import Inches, Pt
5
  from pptx.enum.text import PP_ALIGN
6
  from pptx.dml.color import RGBColor
7
  from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
8
- from pptx.enum.dml import MSO_THEME_COLOR
9
  from PIL import Image
10
  import matplotlib.pyplot as plt
11
 
12
- def _add_logo(slide, logo_bytes: Optional[bytes]):
 
13
  if not logo_bytes:
14
  return
15
  img = Image.open(io.BytesIO(logo_bytes)).convert("RGBA")
 
 
16
  w, h = img.size
17
- ratio = min(max_w/ w, max_h/h)
18
- new_size = (int(w * ratio), int(h * ratio))
19
  resized = img.resize(new_size)
20
  b = io.BytesIO()
21
  resized.save(b, format="PNG")
22
  b.seek(0)
23
  # Place top-right with small margin
24
- left = slide.width - Inches(0.5) - max_w
25
  top = Inches(0.2)
26
- slide.shapes.add_picture(b,left, top)
 
27
 
28
  def _apply_theme_bg(slide, rgb):
29
  fill = slide.background.fill
30
  fill.solid()
31
  fill.fore_color.rgb = RGBColor(*rgb)
32
 
33
- def _title_slide(prs, title_text: str, theme_bg, logo_bytes):
34
- slide_layout = prs.slide_layouts[0] # Title Slide layout
 
35
  slide = prs.slides.add_slide(slide_layout)
 
36
  subtitle = slide.placeholders[1]
37
  title.text = title_text
38
  subtitle.text = "自動生成プレゼンテーション"
39
  # Accent band
40
  _apply_theme_bg(slide, theme_rgb)
41
- # Put a white rouded rectangle for title readability
42
  left = Inches(0.6)
43
  top = Inches(1.8)
44
  width = prs.slide_width - Inches(1.2)
45
  height = Inches(2.2)
46
- box = slide.shapes.add_shape(
47
- MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE, left, top, width, height
48
- )
49
  box.fill.solid()
50
  box.fill.fore_color.rgb = RGBColor(255, 255, 255)
51
  box.line.color.rgb = RGBColor(0, 0, 0)
52
  box.line.transparency = 0.8
53
- # Reposition title inside box
54
  title.left = left + Inches(0.3)
55
  title.top = top + Inches(0.3)
56
  title.width = width - Inches(0.6)
@@ -65,10 +68,11 @@ def _title_slide(prs, title_text: str, theme_bg, logo_bytes):
65
  subtitle.height = Inches(0.8)
66
  for p in subtitle.text_frame.paragraphs:
67
  p.font.size = Pt(16)
68
- p.font.bild = False
69
- _add_logo(slide, logo_bytes)
70
 
71
- def _summary_slide(prs, summary: str)
 
72
  if not summary:
73
  return
74
  slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title and Content
@@ -79,13 +83,14 @@ def _summary_slide(prs, summary: str)
79
  lines = [ln.strip() for ln in summary.splitlines() if ln.strip()]
80
  if not lines:
81
  lines = [summary]
82
- for i, line in enumerate(lines):
83
  p = tf.add_paragraph() if i > 0 else tf.paragraphs[0]
84
  p.text = ln
85
  p.level = 0
86
 
 
87
  def _section_slide(prs, title: str, bullets: List[str]):
88
- slide = prs.slides.add_slide(prs.slide_layouts[1])
89
  slide.shapes.title.text = title[:90]
90
  tf = slide.placeholders[1].text_frame
91
  tf.clear()
@@ -96,15 +101,16 @@ def _section_slide(prs, title: str, bullets: List[str]):
96
  p.text = b
97
  p.level = 0
98
 
99
- def _table_slide(prs, titile: str, pairs: List[tuple]):
100
- slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title and Content
 
101
  slide.shapes.title.text = title
102
  rows = len(pairs) + 1
103
  cols = 2
104
  left = Inches(0.5)
105
  top = Inches(1.8)
106
  width = prs.slide_width - Inches(1.0)
107
- height = prs.slide_height - top - Inches(2.6)
108
  table = slide.shapes.add_table(rows, cols, left, top, width, height).table
109
  table.cell(0, 0).text = "項目"
110
  table.cell(0, 1).text = "値"
@@ -112,74 +118,77 @@ def _table_slide(prs, titile: str, pairs: List[tuple]):
112
  table.cell(r, 0).text = str(k)
113
  table.cell(r, 1).text = str(v)
114
 
 
115
  def _chart_slide(prs, title: str, series: List[tuple]):
116
  slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
117
  slide.shapes.title.text = title
118
  # Build bar chart via matplotlib and embed as image
119
  labels = [x[0] for x in series]
120
  values = [x[1] for x in series]
121
- fig = plt.figure(figsize=(6,4.5))
122
- plt.bar(range(len(values)), labels, rotation=20, ha='right')
 
123
  plt.tight_layout()
124
  buf = io.BytesIO()
125
  fig.savefig(buf, format='png', dpi=200)
126
  plt.close(fig)
127
  buf.seek(0)
128
- left = Inches(1.6)
 
129
  width = prs.slide_width - Inches(1.0)
130
  height = prs.slide_height - Inches(2.2)
131
  slide.shapes.add_picture(buf, left, top, width=width, height=height)
132
 
133
- def _add_footer(prs, theme_tgb):
134
- for slide in prs.slides:
 
135
  left = Inches(0.3)
136
  top = prs.slide_height - Inches(0.4)
137
  width = prs.slide_width - Inches(0.6)
138
  height = Inches(0.3)
139
- shp = slide.shapes.add_shape(
140
- MSO_AUTO_SHAPE_TYPE.RECTANGLE, left, top, width, height
141
- )
142
  shp.fill.solid()
143
  shp.fill.fore_color.rgb = RGBColor(*theme_rgb)
144
  shp.line.fill.background()
145
- # Slide number text boxz
146
- tx = slide.shapes.add_textbox(prs.slide_width - Inches(1.0), top, Inches(0.05), Inches(0.8), Inches(0.3))
147
  tf = tx.text_frame
148
  p = tf.paragraphs[0]
149
- p.text = f"Slide {slide.slide_id % 10000}"
150
  p.font.size = Pt(10)
151
  p.alignment = PP_ALIGN.RIGHT
152
 
153
- def build_presentation(out_path:str,
154
- title: str,
155
- theme_rgb: tuple
156
- logo_bytes: Optional[bytes],
157
- executive_summary: Optional[str],
158
- sections: List[Tuple[str, str]],
159
- bullets_by_section: Dict[str, List[str]],
160
- tables: List[Dict[str, Any]],
161
- charts: List[Dict[str, Any]]):
162
- prs = Presentation()
163
 
164
- # Title slide
165
- _title_slide(prs, title, theme_rgb, logo_bytes)
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- # Summary
168
- _summary_slide(prs, executive_summary)
169
 
170
- # Sections
171
- for idx,(sec_title, _body) in enumerate(sections):
172
- bullets = bullets_by_section.get(sec_title, [])
173
- _section_slide(prs, sec_title, bullets)
174
 
175
- # Tables
176
- for tbl in tables:
177
- _table_slide(prs, tbl.get("title", "表"), tbl.get("pairs", []))
178
 
179
- # Charts
180
- for ch in charts:
181
- _chart_slide(prs, ch.get("title", "チャート"), ch.get("series", []))
182
 
183
- _add_footer(prs, theme_rgb)
184
 
185
- prs.save(out_path)
 
5
  from pptx.enum.text import PP_ALIGN
6
  from pptx.dml.color import RGBColor
7
  from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
 
8
  from PIL import Image
9
  import matplotlib.pyplot as plt
10
 
11
+
12
+ def _add_logo(prs: Presentation, slide, logo_bytes: Optional[bytes]):
13
  if not logo_bytes:
14
  return
15
  img = Image.open(io.BytesIO(logo_bytes)).convert("RGBA")
16
+ # Resize to fit width<=2.0in, height<=1.0in
17
+ max_w, max_h = Inches(2.0), Inches(1.0)
18
  w, h = img.size
19
+ ratio = min(max_w / max(w, 1), max_h / max(h, 1))
20
+ new_size = (max(1, int(w * ratio)), max(1, int(h * ratio)))
21
  resized = img.resize(new_size)
22
  b = io.BytesIO()
23
  resized.save(b, format="PNG")
24
  b.seek(0)
25
  # Place top-right with small margin
26
+ left = prs.slide_width - max_w - Inches(0.5)
27
  top = Inches(0.2)
28
+ slide.shapes.add_picture(b, left, top)
29
+
30
 
31
  def _apply_theme_bg(slide, rgb):
32
  fill = slide.background.fill
33
  fill.solid()
34
  fill.fore_color.rgb = RGBColor(*rgb)
35
 
36
+
37
+ def _title_slide(prs, title_text: str, theme_rgb, logo_bytes):
38
+ slide_layout = prs.slide_layouts[0] # Title slide
39
  slide = prs.slides.add_slide(slide_layout)
40
+ title = slide.shapes.title
41
  subtitle = slide.placeholders[1]
42
  title.text = title_text
43
  subtitle.text = "自動生成プレゼンテーション"
44
  # Accent band
45
  _apply_theme_bg(slide, theme_rgb)
46
+ # White rounded rectangle for readability
47
  left = Inches(0.6)
48
  top = Inches(1.8)
49
  width = prs.slide_width - Inches(1.2)
50
  height = Inches(2.2)
51
+ box = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE, left, top, width, height)
 
 
52
  box.fill.solid()
53
  box.fill.fore_color.rgb = RGBColor(255, 255, 255)
54
  box.line.color.rgb = RGBColor(0, 0, 0)
55
  box.line.transparency = 0.8
56
+ # Reposition title
57
  title.left = left + Inches(0.3)
58
  title.top = top + Inches(0.3)
59
  title.width = width - Inches(0.6)
 
68
  subtitle.height = Inches(0.8)
69
  for p in subtitle.text_frame.paragraphs:
70
  p.font.size = Pt(16)
71
+ p.font.bold = False
72
+ _add_logo(prs, slide, logo_bytes)
73
 
74
+
75
+ def _summary_slide(prs, summary: str):
76
  if not summary:
77
  return
78
  slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title and Content
 
83
  lines = [ln.strip() for ln in summary.splitlines() if ln.strip()]
84
  if not lines:
85
  lines = [summary]
86
+ for i, ln in enumerate(lines):
87
  p = tf.add_paragraph() if i > 0 else tf.paragraphs[0]
88
  p.text = ln
89
  p.level = 0
90
 
91
+
92
  def _section_slide(prs, title: str, bullets: List[str]):
93
+ slide = prs.slides.add_slide(prs.slide_layouts[1])
94
  slide.shapes.title.text = title[:90]
95
  tf = slide.placeholders[1].text_frame
96
  tf.clear()
 
101
  p.text = b
102
  p.level = 0
103
 
104
+
105
+ def _table_slide(prs, title: str, pairs: List[tuple]):
106
+ slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
107
  slide.shapes.title.text = title
108
  rows = len(pairs) + 1
109
  cols = 2
110
  left = Inches(0.5)
111
  top = Inches(1.8)
112
  width = prs.slide_width - Inches(1.0)
113
+ height = prs.slide_height - Inches(2.6)
114
  table = slide.shapes.add_table(rows, cols, left, top, width, height).table
115
  table.cell(0, 0).text = "項目"
116
  table.cell(0, 1).text = "値"
 
118
  table.cell(r, 0).text = str(k)
119
  table.cell(r, 1).text = str(v)
120
 
121
+
122
  def _chart_slide(prs, title: str, series: List[tuple]):
123
  slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
124
  slide.shapes.title.text = title
125
  # Build bar chart via matplotlib and embed as image
126
  labels = [x[0] for x in series]
127
  values = [x[1] for x in series]
128
+ fig = plt.figure(figsize=(8, 4.5))
129
+ plt.bar(range(len(values)), values)
130
+ plt.xticks(range(len(labels)), labels, rotation=20, ha='right')
131
  plt.tight_layout()
132
  buf = io.BytesIO()
133
  fig.savefig(buf, format='png', dpi=200)
134
  plt.close(fig)
135
  buf.seek(0)
136
+ left = Inches(0.5)
137
+ top = Inches(1.6)
138
  width = prs.slide_width - Inches(1.0)
139
  height = prs.slide_height - Inches(2.2)
140
  slide.shapes.add_picture(buf, left, top, width=width, height=height)
141
 
142
+
143
+ def _add_footer(prs, theme_rgb):
144
+ for idx, slide in enumerate(prs.slides, start=1):
145
  left = Inches(0.3)
146
  top = prs.slide_height - Inches(0.4)
147
  width = prs.slide_width - Inches(0.6)
148
  height = Inches(0.3)
149
+ shp = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left, top, width, height)
 
 
150
  shp.fill.solid()
151
  shp.fill.fore_color.rgb = RGBColor(*theme_rgb)
152
  shp.line.fill.background()
153
+ # Slide number text box
154
+ tx = slide.shapes.add_textbox(prs.slide_width - Inches(1.0), top - Inches(0.05), Inches(0.8), Inches(0.3))
155
  tf = tx.text_frame
156
  p = tf.paragraphs[0]
157
+ p.text = f"{idx}"
158
  p.font.size = Pt(10)
159
  p.alignment = PP_ALIGN.RIGHT
160
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ def build_presentation(output_path: str,
163
+ title: str,
164
+ theme_rgb: tuple,
165
+ logo_bytes: Optional[bytes],
166
+ executive_summary: Optional[str],
167
+ sections: List[Tuple[str, str]],
168
+ bullets_by_section: Dict[int, List[str]],
169
+ tables: List[Dict[str, Any]],
170
+ charts: List[Dict[str, Any]]):
171
+ prs = Presentation()
172
+
173
+ # Title slide
174
+ _title_slide(prs, title, theme_rgb, logo_bytes)
175
 
176
+ # Summary
177
+ _summary_slide(prs, executive_summary)
178
 
179
+ # Sections
180
+ for idx, (sec_title, _body) in enumerate(sections):
181
+ bullets = bullets_by_section.get(idx, [])
182
+ _section_slide(prs, sec_title, bullets)
183
 
184
+ # Tables
185
+ for tbl in tables:
186
+ _table_slide(prs, tbl.get("title", "表"), tbl.get("pairs", []))
187
 
188
+ # Charts
189
+ for ch in charts:
190
+ _chart_slide(prs, ch.get("title", "チャート"), ch.get("series", []))
191
 
192
+ _add_footer(prs, theme_rgb)
193
 
194
+ prs.save(output_path)