zolicsaki commited on
Commit
282ed7c
·
verified ·
1 Parent(s): 993eb39

Update pptx_utils.py

Browse files
Files changed (1) hide show
  1. pptx_utils.py +695 -0
pptx_utils.py CHANGED
@@ -0,0 +1,695 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pptx import Presentation
2
+ from pptx.dml.color import RGBColor
3
+ from pptx.util import Inches
4
+ from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
5
+ from pptx.util import Pt
6
+ import string
7
+ from datetime import datetime
8
+ import os
9
+ import re
10
+
11
+ def clean_leading_numbering(text):
12
+ # Remove leading numbering like: "1. ", "1) ", "(1) ", "- 1. ", etc.
13
+ return re.sub(r'^[\s\(\-\.\d\)]*', '', text)
14
+
15
+ def is_logo_exist(file_path: str):
16
+ print(file_path)
17
+ if os.path.exists(file_path):
18
+ # print("File exists.")
19
+ return True
20
+ else:
21
+ print("File does not exist.")
22
+ return False
23
+
24
+ class Dict2PPT:
25
+ def __init__(self, logo_path: str = 'logo.png', title_size: int = 32, content_size: int=24) -> None:
26
+ self.title_font_size = Pt(title_size)
27
+ self.content_font_size = Pt(content_size)
28
+ self.logo_path = logo_path
29
+ self.prs = Presentation()
30
+
31
+ def _title_preprocess(self, title: str):
32
+ words = title.split()
33
+ capitalized_words = [word.capitalize() for word in words]
34
+ result = ' '.join(capitalized_words)
35
+ return result
36
+
37
+ def _add_time_footnote(self, slide):
38
+ # Get slide dimensions
39
+ slide_width = self.prs.slide_width
40
+ slide_height = self.prs.slide_height
41
+
42
+ # Prepare date text
43
+ date_str = datetime.today().strftime("%B %d, %Y") # e.g., March 26, 2025
44
+
45
+ # Set textbox size
46
+ textbox_width = Inches(3) # You can adjust this
47
+ textbox_height = Inches(0.3)
48
+ left = (slide_width - textbox_width) / 2 # Center horizontally
49
+ top = slide_height - Inches(0.5) # Near bottom
50
+
51
+ textbox = slide.shapes.add_textbox(left, top, textbox_width, textbox_height)
52
+ text_frame = textbox.text_frame
53
+ p = text_frame.paragraphs[0]
54
+ run = p.add_run()
55
+ run.text = date_str
56
+ run.font.size = Pt(12)
57
+ p.alignment = PP_ALIGN.CENTER # ✅ Center text horizontally
58
+
59
+ def _add_logo(self, slide):
60
+ # Define logo path and size
61
+ # logo_path = "logo.png" # Replace with your actual logo path
62
+
63
+ if not is_logo_exist(file_path=self.logo_path):
64
+ return
65
+ logo_width = Inches(1.0) # Resize logo as needed
66
+ logo_height = Inches(1.0)
67
+
68
+ # Calculate position for top-right corner
69
+ slide_width = self.prs.slide_width
70
+ right_margin = Inches(0.2) # Optional small margin from edge
71
+ top = Inches(0.2)
72
+
73
+ # Position: from right edge minus logo width
74
+ left = slide_width - logo_width - right_margin
75
+
76
+ # Add logo
77
+ slide.shapes.add_picture(self.logo_path, left, top, width=logo_width, height=logo_height)
78
+
79
+ def _set_background_color(self, slide):
80
+ fill = slide.background.fill
81
+ fill.solid() # Use solid color
82
+ fill.fore_color.rgb = RGBColor(240, 248, 255) # RGB for a light blue
83
+
84
+ def title_slide(self, title: str, authors: str):
85
+ title_slide_layout = self.prs.slide_layouts[0] # Title Slide
86
+ slide = self.prs.slides.add_slide(title_slide_layout)
87
+
88
+ self._set_background_color(slide=slide)
89
+ self._add_logo(slide=slide)
90
+
91
+ title_shape = slide.shapes.title
92
+ title_shape.text = title
93
+ title_paragraph = title_shape.text_frame.paragraphs[0]
94
+ for run in title_paragraph.runs:
95
+ run.font.bold = True
96
+ run.font.name = 'Times New Roman'
97
+ run.font.size = Pt(36) # e.g., 44 pt
98
+
99
+ author_shape = slide.placeholders[1]
100
+ today = datetime.today().strftime("%B %d, %Y") # e.g., March 25, 2025
101
+ # print('authors', authors)
102
+ # if authors:
103
+ # author_shape.text = '\n' + authors + '\n' + today
104
+ # else:
105
+ # author_shape.text = '\nAuthor Here\n' + today
106
+ author_shape.text = '\nAuthor Here\n' + today
107
+ # Set subtitle font size
108
+ author_paragraph = author_shape.text_frame.paragraphs[1] # 0 is blank line, 1 is actual text
109
+ for run in author_paragraph.runs:
110
+ run.font.name = 'Times New Roman'
111
+ run.font.size = Pt(24) # Set subtitle font size to 28 pt
112
+
113
+ def outline_slide(self, outline: dict):
114
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
115
+ slide = self.prs.slides.add_slide(content_slide_layout)
116
+ self._set_background_color(slide=slide)
117
+ self._add_logo(slide=slide)
118
+ title_shape = slide.shapes.title
119
+ title_shape.text = 'Outline'
120
+ title_paragraph = title_shape.text_frame.paragraphs[0]
121
+ title_paragraph.alignment = PP_ALIGN.LEFT
122
+ for run in title_paragraph.runs:
123
+ run.font.bold = True
124
+ run.font.name = 'Times New Roman'
125
+ run.font.size = Pt(36) # e.g., 36 pt
126
+
127
+ # Clear existing content
128
+ content_shape = slide.placeholders[1]
129
+ text_frame = content_shape.text_frame
130
+ text_frame.clear()
131
+
132
+ # Add topic
133
+ for topic, desc in outline.items():
134
+ p1 = text_frame.add_paragraph()
135
+ p1.text = topic
136
+ p1.level = 0
137
+ p1.font.size = Pt(20)
138
+ p1.font.name = 'Times New Roman'
139
+ p1.font.bold = True
140
+ p1.alignment = PP_ALIGN.LEFT
141
+
142
+ # Line 2: description (indented)
143
+ if len(desc) > 0:
144
+ p2 = text_frame.add_paragraph()
145
+ p2.text = desc
146
+ p2.level = 1 # Indented bullet
147
+ p2.font.size = Pt(12)
148
+ p2.font.name = 'Times New Roman'
149
+ p2.alignment = PP_ALIGN.LEFT
150
+
151
+ self._add_time_footnote(slide=slide)
152
+
153
+ def _outline_preprocess_(self, outline):
154
+ if isinstance(outline, dict):
155
+ clean_outline = {}
156
+ for topic, desc in outline.items():
157
+ topic = topic.strip().strip(string.punctuation).strip()
158
+ desc = desc.strip().strip(string.punctuation).strip()
159
+ clean_outline[topic] = desc
160
+ return clean_outline
161
+
162
+ elif isinstance(outline, str):
163
+ sentences = outline.split('\n')
164
+ sentences = [text.strip().strip(string.punctuation).strip() for text in sentences]
165
+ sent_dict = {}
166
+ for sent in sentences:
167
+ tokens = sent.split(':')
168
+ if len(tokens) == 1:
169
+ sent_dict[tokens[0]] = ''
170
+ else:
171
+ key = tokens[0].strip().strip(string.punctuation).strip()
172
+ value = ''.join(tokens[1:])
173
+ value = value.strip().strip(string.punctuation).strip()
174
+ sent_dict[key] = value
175
+ return sent_dict
176
+ else:
177
+ print('Wrong format')
178
+ return {}
179
+
180
+ def _background_preprocess(self, background: str):
181
+ background_array = []
182
+ sentences = background.strip().splitlines()
183
+ for sent in sentences:
184
+ sent = clean_leading_numbering(sent)
185
+ background_array.append(sent.strip().strip(string.punctuation).strip())
186
+ return background_array
187
+
188
+ def background_slide(self, background):
189
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
190
+ slide = self.prs.slides.add_slide(content_slide_layout)
191
+ self._set_background_color(slide=slide)
192
+ self._add_logo(slide=slide)
193
+ title_shape = slide.shapes.title
194
+ title_shape.text = 'Background'
195
+ title_paragraph = title_shape.text_frame.paragraphs[0]
196
+ title_paragraph.alignment = PP_ALIGN.LEFT
197
+ for run in title_paragraph.runs:
198
+ run.font.bold = True
199
+ run.font.name = 'Times New Roman'
200
+ run.font.size = Pt(36) # e.g., 36 pt
201
+
202
+ # Clear existing content
203
+ content_shape = slide.placeholders[1]
204
+ text_frame = content_shape.text_frame
205
+ text_frame.clear()
206
+ # ✅ Vertically center content inside the placeholder
207
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
208
+
209
+ # Add topic + indented description as two lines
210
+ background_item_num = len(background)
211
+ fontsize = 22
212
+ if background_item_num >= 4 and background_item_num <= 6:
213
+ fontsize = 20
214
+ elif background_item_num >7:
215
+ fontsize = 18
216
+ for idx, topic in enumerate(background, start=1):
217
+ p1 = text_frame.add_paragraph()
218
+ p1.text = f"{idx}. {topic}"
219
+ p1.level = 0
220
+ p1.font.size = Pt(fontsize)
221
+ p1.font.name = 'Times New Roman'
222
+ # p1.font.bold = True
223
+ p1.alignment = PP_ALIGN.LEFT
224
+
225
+ self._add_time_footnote(slide=slide)
226
+
227
+ def _problem_define_preprocess(self, problem_desc: str):
228
+ from collections import OrderedDict
229
+ def split_text_by_headers(text, headers):
230
+ sections = OrderedDict({header: [] for header in headers})
231
+ current = None
232
+ for line in text.strip().strip(string.punctuation).splitlines():
233
+ line_clean = line.strip().strip(string.punctuation).strip()
234
+ if len(line_clean) == 0:
235
+ continue
236
+ # Check if line matches any of the section headers
237
+ matched = [h for h in headers if h.lower() == line_clean.lower()]
238
+ if matched:
239
+ current = matched[0]
240
+ continue
241
+ if current:
242
+ cleaned_line = clean_leading_numbering(text=line_clean)
243
+ cleaned_line = cleaned_line.strip().strip(string.punctuation).strip()
244
+ sections[current].append(cleaned_line)
245
+
246
+ # Convert lists to joined text blocks
247
+ return {k: v for k, v in sections.items()}
248
+
249
+ sections = ["Scope", "Challenges", "Assumptions", "Relevance"]
250
+ problem_dict = {}
251
+ if any([_ in problem_desc for _ in sections]):
252
+ problem_dict = split_text_by_headers(text=problem_desc, headers=sections)
253
+
254
+ if all([len(v)==0 for k, v in problem_dict.items()]) or len(problem_dict) == 0:
255
+ problem_dict = {}
256
+ cleaned_sentences = []
257
+ sentences = problem_desc.strip().strip(string.punctuation).splitlines()
258
+ for sent in sentences:
259
+ cleaned_line = clean_leading_numbering(text=sent)
260
+ cleaned_line = cleaned_line.strip().strip(string.punctuation).strip()
261
+ cleaned_sentences.append(cleaned_line)
262
+ problem_dict['Scope'] = cleaned_sentences
263
+
264
+ return problem_dict
265
+
266
+ def problem_def_slide(self, problems):
267
+ sections = ["Scope", "Challenges", "Assumptions", "Relevance"]
268
+ scope = problems.get('Scope', [])
269
+ challenges = problems.get('Challenges', [])
270
+ assumptions = problems.get('Assumptions', [])
271
+ relevance = problems.get('Relevance', [])
272
+ for sect_name in sections:
273
+ section_contents = problems.get(sect_name, [])
274
+ if len(section_contents) == 0:
275
+ continue
276
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
277
+ slide = self.prs.slides.add_slide(content_slide_layout)
278
+ self._set_background_color(slide=slide)
279
+ self._add_logo(slide=slide)
280
+ title_shape = slide.shapes.title
281
+ if sect_name == 'Scope':
282
+ title_shape.text = 'Problem Definition'
283
+ elif sect_name in {'Challenges', 'Assumptions'}:
284
+ title_shape.text = 'Problem Definition - {}'.format(sect_name)
285
+ else:
286
+ title_shape.text = 'Interested Practitioners'
287
+ title_paragraph = title_shape.text_frame.paragraphs[0]
288
+ title_paragraph.alignment = PP_ALIGN.LEFT
289
+ for run in title_paragraph.runs:
290
+ run.font.bold = True
291
+ run.font.name = 'Times New Roman'
292
+ run.font.size = Pt(36) # e.g., 36 pt
293
+
294
+ # Clear existing content
295
+ content_shape = slide.placeholders[1]
296
+ text_frame = content_shape.text_frame
297
+ text_frame.clear()
298
+ # ✅ Vertically center content inside the placeholder
299
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
300
+ fontsize = 20
301
+ for idx, topic in enumerate(section_contents, start=1):
302
+ p1 = text_frame.add_paragraph()
303
+ p1.text = f"{idx}. {topic}"
304
+ p1.level = 0
305
+ p1.font.size = Pt(fontsize)
306
+ p1.font.name = 'Times New Roman'
307
+ # p1.font.bold = True
308
+ p1.alignment = PP_ALIGN.LEFT
309
+
310
+ self._add_time_footnote(slide=slide)
311
+
312
+ def _objective_preprocess(self, objective: str):
313
+ objective_array = []
314
+ sentences = objective.strip().splitlines()
315
+ for sent in sentences:
316
+ sent = clean_leading_numbering(text=sent)
317
+ objective_array.append(sent.strip().strip(string.punctuation).strip())
318
+ return objective_array
319
+
320
+ def objective_slide(self, objectives):
321
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
322
+ slide = self.prs.slides.add_slide(content_slide_layout)
323
+ self._set_background_color(slide=slide)
324
+ self._add_logo(slide=slide)
325
+ title_shape = slide.shapes.title
326
+ title_shape.text = 'Objectives & How'
327
+ title_paragraph = title_shape.text_frame.paragraphs[0]
328
+ title_paragraph.alignment = PP_ALIGN.LEFT
329
+ for run in title_paragraph.runs:
330
+ run.font.bold = True
331
+ run.font.name = 'Times New Roman'
332
+ run.font.size = Pt(36) # e.g., 36 pt
333
+
334
+ # Clear existing content
335
+ content_shape = slide.placeholders[1]
336
+ text_frame = content_shape.text_frame
337
+ text_frame.clear()
338
+ # ✅ Vertically center content inside the placeholder
339
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
340
+
341
+ objective_item_num = len(objectives)
342
+ fontsize = 24
343
+ if objective_item_num >= 4 and objective_item_num <= 6:
344
+ fontsize = 22
345
+ elif objective_item_num >7:
346
+ fontsize = 20
347
+ for idx, topic in enumerate(objectives, start=1):
348
+ p1 = text_frame.add_paragraph()
349
+ p1.text = f"{idx}. {topic}"
350
+ p1.level = 0
351
+ p1.font.size = Pt(fontsize)
352
+ p1.font.name = 'Times New Roman'
353
+ # p1.font.bold = True
354
+ p1.alignment = PP_ALIGN.LEFT
355
+
356
+ self._add_time_footnote(slide=slide)
357
+
358
+ def _method_preprocess(self, methodology: str):
359
+ method_array = []
360
+ sentences = methodology.strip().splitlines()
361
+ for sent in sentences:
362
+ sent_trim = clean_leading_numbering(text=sent)
363
+ sent_trim = sent_trim.strip().strip(string.punctuation).strip()
364
+ method_array.append(sent_trim)
365
+ return method_array
366
+
367
+ def method_slide(self, methods):
368
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
369
+ slide = self.prs.slides.add_slide(content_slide_layout)
370
+ self._set_background_color(slide=slide)
371
+ self._add_logo(slide=slide)
372
+
373
+ title_shape = slide.shapes.title
374
+ title_shape.text = 'Proposed Method'
375
+ title_paragraph = title_shape.text_frame.paragraphs[0]
376
+ title_paragraph.alignment = PP_ALIGN.LEFT
377
+ for run in title_paragraph.runs:
378
+ run.font.bold = True
379
+ run.font.name = 'Times New Roman'
380
+ run.font.size = Pt(36) # e.g., 36 pt
381
+
382
+ # Clear existing content
383
+ content_shape = slide.placeholders[1]
384
+ text_frame = content_shape.text_frame
385
+ text_frame.clear()
386
+ # ✅ Vertically center content inside the placeholder
387
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
388
+
389
+ fontsize = 20
390
+ for idx, step in enumerate(methods, start=1):
391
+ p = text_frame.add_paragraph()
392
+ run1 = p.add_run()
393
+ run1.text = "Step {}. ".format(idx)
394
+ run1.font.bold = True
395
+ run1.font.size = Pt(fontsize)
396
+
397
+ # Second run: normal text
398
+ run2 = p.add_run()
399
+ run2.text = step
400
+ run2.font.bold = False
401
+ run2.font.size = Pt(fontsize)
402
+ p.font.name = 'Times New Roman'
403
+ p.alignment = PP_ALIGN.LEFT
404
+
405
+ self._add_time_footnote(slide=slide)
406
+
407
+ def _experiment_preprocess(self, experiment: str):
408
+ def split_sections_by_keywords(text: str, keyword1: str, keyword2: str) -> dict:
409
+ lines = text.strip().splitlines()
410
+ part1_lines = []
411
+ part2_lines = []
412
+ current_section = None
413
+ for line in lines:
414
+ stripped = clean_leading_numbering(line)
415
+ stripped = stripped.strip().strip(string.punctuation).strip()
416
+ if len(stripped) == 0:
417
+ continue
418
+ if keyword1 in stripped:
419
+ current_section = keyword1
420
+ continue
421
+ elif keyword2 in stripped:
422
+ current_section = keyword2
423
+ continue
424
+
425
+ if current_section == keyword1:
426
+ tokens = stripped.split(':')
427
+ key = tokens[0].strip().strip(string.punctuation).strip()
428
+ if len(tokens) > 1:
429
+ parse_stripped = key + ": " + ':'.join(tokens[1:]).strip().strip(string.punctuation).strip()
430
+ else:
431
+ parse_stripped = key
432
+ part1_lines.append(parse_stripped)
433
+ elif current_section == keyword2:
434
+ tokens = stripped.split(':')
435
+ key = tokens[0].strip().strip(string.punctuation).strip()
436
+ if len(tokens) > 1:
437
+ parse_stripped = (key, ':'.join(tokens[1:]))
438
+ else:
439
+ parse_stripped = (key, '')
440
+ part2_lines.append(parse_stripped)
441
+ return {
442
+ keyword1: part1_lines,
443
+ keyword2: part2_lines
444
+ }
445
+
446
+ experiment_dict = {}
447
+ sentences = experiment.strip().splitlines()
448
+ evidence_keyword = 'Evidence Summary'
449
+ exp_summary_keyword = 'Experimental Summary'
450
+ if (evidence_keyword in experiment) and (exp_summary_keyword in experiment):
451
+ experiment_dict = split_sections_by_keywords(text=experiment, keyword1=evidence_keyword, keyword2=exp_summary_keyword)
452
+ else:
453
+ experiment_array = []
454
+ for sent in sentences:
455
+ sent = clean_leading_numbering(sent)
456
+ sent = sent.strip().strip(string.punctuation).strip()
457
+ experiment_array.append(sent)
458
+ experiment_dict[exp_summary_keyword] = experiment_array
459
+ return experiment_dict
460
+
461
+ def experiment_slide(self, experiments):
462
+ evidence_keyword = 'Evidence Summary'
463
+ exp_summary_keyword = 'Experimental Summary'
464
+ if len(experiments) == 1:
465
+ experiments_part1 = experiments[exp_summary_keyword]
466
+ experiments_part2 = []
467
+ else:
468
+ assert len(experiments) == 2
469
+ experiments_part1 = experiments[exp_summary_keyword]
470
+ experiments_part2 = experiments[evidence_keyword]
471
+
472
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
473
+ slide = self.prs.slides.add_slide(content_slide_layout)
474
+ self._set_background_color(slide=slide)
475
+ self._add_logo(slide=slide)
476
+ title_shape = slide.shapes.title
477
+ title_shape.text = 'Experimental Study'
478
+ title_paragraph = title_shape.text_frame.paragraphs[0]
479
+ title_paragraph.alignment = PP_ALIGN.LEFT
480
+ for run in title_paragraph.runs:
481
+ run.font.bold = True
482
+ run.font.name = 'Times New Roman'
483
+ run.font.size = Pt(36) # e.g., 36 pt
484
+
485
+ # Clear existing content
486
+ content_shape = slide.placeholders[1]
487
+ text_frame = content_shape.text_frame
488
+ text_frame.clear()
489
+ # ✅ Vertically center content inside the placeholder
490
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
491
+
492
+ fontsize = 20
493
+ if len(experiments_part2) == 0:
494
+ for idx, sent in enumerate(experiments_part1, start=1):
495
+ p1 = text_frame.add_paragraph()
496
+ p1.text = f"{idx}. {sent}"
497
+ p1.level = 0
498
+ p1.font.size = Pt(fontsize)
499
+ p1.font.name = 'Times New Roman'
500
+ p1.font.bold = True
501
+ p1.alignment = PP_ALIGN.LEFT
502
+ else:
503
+ for idx, step in enumerate(experiments_part1, start=1):
504
+ key, value = step
505
+ if len(value) == 0:
506
+ continue
507
+ p = text_frame.add_paragraph()
508
+ run1 = p.add_run()
509
+ run1.text = key
510
+ run1.font.bold = True
511
+ run1.font.size = Pt(fontsize)
512
+
513
+ # Second run: normal text
514
+ run2 = p.add_run()
515
+ run2.text = value
516
+ run2.font.bold = False
517
+ run2.font.size = Pt(fontsize)
518
+ p.font.name = 'Times New Roman'
519
+ p.alignment = PP_ALIGN.LEFT
520
+
521
+ self._add_time_footnote(slide=slide)
522
+
523
+ ###experimental study in multiple pages
524
+ if len(experiments_part2) > 0:
525
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
526
+ slide_2 = self.prs.slides.add_slide(content_slide_layout)
527
+ self._set_background_color(slide=slide_2)
528
+ self._add_logo(slide=slide_2)
529
+ title_shape = slide_2.shapes.title
530
+ title_shape.text = 'Experimental Study (Summary)'
531
+ title_paragraph = title_shape.text_frame.paragraphs[0]
532
+ title_paragraph.alignment = PP_ALIGN.LEFT
533
+ for run in title_paragraph.runs:
534
+ run.font.bold = True
535
+ run.font.name = 'Times New Roman'
536
+ run.font.size = Pt(36) # e.g., 36 pt
537
+
538
+ # Clear existing content
539
+ content_shape = slide_2.placeholders[1]
540
+ text_frame = content_shape.text_frame
541
+ text_frame.clear()
542
+ # ✅ Vertically center content inside the placeholder
543
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
544
+ self._add_time_footnote(slide=slide_2)
545
+ for idx, sent in enumerate(experiments_part2, start=1):
546
+ p1 = text_frame.add_paragraph()
547
+ p1.text = f"{idx}. {sent}"
548
+ p1.level = 0
549
+ p1.font.size = Pt(fontsize)
550
+ p1.font.name = 'Times New Roman'
551
+ p1.alignment = PP_ALIGN.LEFT
552
+
553
+ def _conclusion_preprocess(self, conclusion: str):
554
+ conclusion_dict = {}
555
+ sentences = conclusion.strip().splitlines()
556
+ for sent in sentences:
557
+ trim_sent = sent.strip().strip(string.punctuation).strip()
558
+ trim_sent = clean_leading_numbering(text=trim_sent)
559
+ if len(trim_sent) == 0 or trim_sent.lower().startswith('conclusion'):
560
+ continue
561
+ else:
562
+ tokens = trim_sent.split(':')
563
+ key = tokens[0].strip().strip(string.punctuation).strip()
564
+ if len(tokens) == 1:
565
+ conclusion_dict[key] = ''
566
+ else:
567
+ value = ':'.join(tokens[1:]).strip().strip(string.punctuation).strip()
568
+ conclusion_dict[key] = value
569
+ return conclusion_dict
570
+
571
+ def conclusion_slide(self, conclusion):
572
+ content_slide_layout = self.prs.slide_layouts[1] # title and Content Slide Layout
573
+ slide = self.prs.slides.add_slide(content_slide_layout)
574
+ self._set_background_color(slide=slide)
575
+ self._add_logo(slide=slide)
576
+ title_shape = slide.shapes.title
577
+ title_shape.text = 'Conclusions & Future Work'
578
+ title_paragraph = title_shape.text_frame.paragraphs[0]
579
+ title_paragraph.alignment = PP_ALIGN.LEFT
580
+ for run in title_paragraph.runs:
581
+ run.font.bold = True
582
+ run.font.name = 'Times New Roman'
583
+ run.font.size = Pt(36) # e.g., 36 pt
584
+
585
+ # Clear existing content
586
+ content_shape = slide.placeholders[1]
587
+ text_frame = content_shape.text_frame
588
+ text_frame.clear()
589
+ # ✅ Vertically center content inside the placeholder
590
+ text_frame.vertical_anchor = MSO_ANCHOR.MIDDLE
591
+
592
+ # Add topic
593
+ for topic, desc in conclusion.items():
594
+ if len(desc) == 0:
595
+ continue
596
+ p1 = text_frame.add_paragraph()
597
+ p1.text = topic
598
+ p1.level = 0
599
+ p1.font.size = Pt(20)
600
+ p1.font.name = 'Times New Roman'
601
+ p1.font.bold = True
602
+ p1.alignment = PP_ALIGN.LEFT
603
+
604
+ # Line 2: description (indented)
605
+ p2 = text_frame.add_paragraph()
606
+ p2.text = desc
607
+ p2.level = 1 # Indented bullet
608
+ p2.font.size = Pt(16)
609
+ p2.font.italic = True
610
+ p2.font.name = 'Times New Roman'
611
+ p2.alignment = PP_ALIGN.LEFT
612
+
613
+ self._add_time_footnote(slide=slide)
614
+
615
+ def build_slides(self, slide_dict: dict, authors: str = 'Author here'):
616
+ title = slide_dict.get('Title', '')
617
+ title = self._title_preprocess(title=title)
618
+ self.title_slide(title=title, authors=authors)
619
+
620
+ outline = slide_dict.get('Outline', {})
621
+ outline = self._outline_preprocess_(outline=outline)
622
+ assert len(outline) > 0, 'No outline detected!!!'
623
+ self.outline_slide(outline=outline)
624
+
625
+ background = slide_dict.get('Background', '')
626
+ if background:
627
+ background = self._background_preprocess(background=background)
628
+ self.background_slide(background=background)
629
+
630
+ problem_definition = slide_dict.get('Research problem', '')
631
+ # print('problem_definition', problem_definition)
632
+ if problem_definition:
633
+ problems = self._problem_define_preprocess(problem_desc=problem_definition)
634
+ # print('problems', problems)
635
+ self.problem_def_slide(problems=problems)
636
+
637
+ objectives = slide_dict.get('Objectives', '')
638
+ if objectives:
639
+ objectives = self._objective_preprocess(objective=objectives)
640
+ self.objective_slide(objectives=objectives)
641
+
642
+ methodology = slide_dict.get('Methodology', '')
643
+ if methodology:
644
+ methodology = self._method_preprocess(methodology=methodology)
645
+ # print('Method', methodology)
646
+ self.method_slide(methods=methodology)
647
+
648
+ experimental_study = slide_dict.get('Results', '')
649
+ if experimental_study:
650
+ experiments = self._experiment_preprocess(experiment=experimental_study)
651
+ # print('experiments', experiments)
652
+ self.experiment_slide(experiments=experiments)
653
+
654
+ conclusion = slide_dict.get('Conclusions', '')
655
+ if conclusion:
656
+ conclusion = self._conclusion_preprocess(conclusion=conclusion)
657
+ self.conclusion_slide(conclusion=conclusion)
658
+
659
+ self.qa_slides()
660
+ print('Done!!')
661
+
662
+ def qa_slides(self):
663
+ # Add a blank slide (usually layout 6 is blank)
664
+ blank_slide_layout = self.prs.slide_layouts[6]
665
+ slide = self.prs.slides.add_slide(blank_slide_layout)
666
+ self._set_background_color(slide=slide)
667
+ self._add_logo(slide=slide)
668
+
669
+ # Add a textbox in the center
670
+ left = Inches(2)
671
+ top = Inches(2.5)
672
+ width = Inches(6)
673
+ height = Inches(2)
674
+
675
+ textbox = slide.shapes.add_textbox(left, top, width, height)
676
+ text_frame = textbox.text_frame
677
+ text_frame.clear()
678
+
679
+ # Add "Thank you"
680
+ p1 = text_frame.add_paragraph()
681
+ p1.text = "Thank you!"
682
+ p1.font.size = Pt(44)
683
+ p1.font.bold = True
684
+ p1.alignment = PP_ALIGN.CENTER
685
+
686
+ # Add "Q & A"
687
+ p2 = text_frame.add_paragraph()
688
+ p2.text = "\nQ & A"
689
+ p2.font.size = Pt(36)
690
+ p2.alignment = PP_ALIGN.CENTER
691
+
692
+ self._add_time_footnote(slide=slide)
693
+
694
+ def save(self, file_name='slides.pptx'):
695
+ self.prs.save(file_name)