barunsaha commited on
Commit
437156d
1 Parent(s): 372569c

Probabilistically add background & foreground images to some of the slides

Browse files
Files changed (2) hide show
  1. helpers/image_search.py +111 -0
  2. helpers/pptx_helper.py +320 -85
helpers/image_search.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Search photos using Pexels API.
3
+ """
4
+ import os
5
+ import random
6
+ from io import BytesIO
7
+ from typing import Union, Tuple, Optional
8
+ from urllib.parse import urlparse, parse_qs
9
+
10
+ import requests
11
+
12
+
13
+ REQUEST_TIMEOUT = 12
14
+ MAX_PHOTOS = 2
15
+
16
+
17
+ def search_pexels(query: str, size: Optional[str] = 'medium', per_page: int = MAX_PHOTOS):
18
+ """
19
+ Searches for images on Pexels using the provided query.
20
+
21
+ This function sends a GET request to the Pexels API with the specified search query
22
+ and authorization header containing the API key. It returns the JSON response from the API.
23
+
24
+ :param query: The search query for finding images.
25
+ :param size: The size of the images: small, medium, or large.
26
+ :param per_page: No. of results to be displayed per page.
27
+ :return: The JSON response from the Pexels API containing search results.
28
+ :raises requests.exceptions.RequestException: If the request to the Pexels API fails.
29
+ """
30
+
31
+ url = 'https://api.pexels.com/v1/search'
32
+ headers = {
33
+ 'Authorization': os.getenv('PEXEL_API_KEY')
34
+ }
35
+ params = {
36
+ 'query': query,
37
+ 'size': size,
38
+ 'page': 1,
39
+ 'per_page': per_page
40
+ }
41
+ response = requests.get(url, headers=headers, params=params, timeout=REQUEST_TIMEOUT)
42
+ response.raise_for_status() # Ensure the request was successful
43
+
44
+ return response.json()
45
+
46
+
47
+ def get_photo_url_from_api_response(
48
+ json_response: dict
49
+ ) -> Tuple[Union[str, None], Union[str, None]]:
50
+ """
51
+ Return a randomly chosen photo from a Pexels search API response. In addition, also return
52
+ the original URL of the page on Pexels.
53
+
54
+ :param json_response: The JSON response.
55
+ :return: The selected photo URL and page URL or `None`.
56
+ """
57
+
58
+ page_url = None
59
+ photo_url = None
60
+
61
+ if 'photos' in json_response:
62
+ photos = json_response['photos']
63
+
64
+ if photos:
65
+ photo_idx = random.choice(list(range(MAX_PHOTOS)))
66
+ photo = photos[photo_idx]
67
+
68
+ if 'url' in photo:
69
+ page_url = photo['url']
70
+
71
+ if 'src' in photo:
72
+ if 'large' in photo['src']:
73
+ photo_url = photo['src']['large']
74
+ elif 'original' in photo['src']:
75
+ photo_url = photo['src']['original']
76
+
77
+ return photo_url, page_url
78
+
79
+
80
+ def get_image_from_url(url: str) -> BytesIO:
81
+ """
82
+ Fetches an image from the specified URL and returns it as a BytesIO object.
83
+
84
+ This function sends a GET request to the provided URL, retrieves the image data,
85
+ and wraps it in a BytesIO object, which can be used like a file.
86
+
87
+ :param url: The URL of the image to be fetched.
88
+ :return: A BytesIO object containing the image data.
89
+ :raises requests.exceptions.RequestException: If the request to the URL fails.
90
+ """
91
+
92
+ response = requests.get(url, stream=True, timeout=REQUEST_TIMEOUT)
93
+ response.raise_for_status()
94
+ image_data = BytesIO(response.content)
95
+
96
+ return image_data
97
+
98
+
99
+ def extract_dimensions(url: str) -> Tuple[int, int]:
100
+ """
101
+ Extracts the height and width from the URL parameters.
102
+
103
+ :param url: The URL containing the image dimensions.
104
+ :return: A tuple containing the width and height as integers.
105
+ """
106
+ parsed_url = urlparse(url)
107
+ query_params = parse_qs(parsed_url.query)
108
+ width = int(query_params.get('w', [0])[0])
109
+ height = int(query_params.get('h', [0])[0])
110
+
111
+ return width, height
helpers/pptx_helper.py CHANGED
@@ -1,17 +1,30 @@
 
 
 
1
  import logging
2
  import pathlib
 
3
  import re
 
4
  import tempfile
5
-
6
- from typing import List, Tuple
7
 
8
  import json5
9
  import pptx
 
10
  from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
 
 
 
 
11
 
 
12
  from global_config import GlobalConfig
13
 
14
 
 
 
 
15
  # English Metric Unit (used by PowerPoint) to inches
16
  EMU_TO_INCH_SCALING_FACTOR = 1.0 / 914400
17
  INCHES_1_5 = pptx.util.Inches(1.5)
@@ -21,24 +34,10 @@ INCHES_0_4 = pptx.util.Inches(0.4)
21
  INCHES_0_3 = pptx.util.Inches(0.3)
22
 
23
  STEP_BY_STEP_PROCESS_MARKER = '>> '
 
 
 
24
 
25
- PATTERN = re.compile(r"^slide[ ]+\d+:", re.IGNORECASE)
26
- SAMPLE_JSON_FOR_PPTX = '''
27
- {
28
- "title": "Understanding AI",
29
- "slides": [
30
- {
31
- "heading": "Introduction",
32
- "bullet_points": [
33
- "Brief overview of AI",
34
- [
35
- "Importance of understanding AI"
36
- ]
37
- ]
38
- }
39
- ]
40
- }
41
- '''
42
 
43
  logger = logging.getLogger(__name__)
44
 
@@ -48,9 +47,10 @@ def remove_slide_number_from_heading(header: str) -> str:
48
  Remove the slide number from a given slide header.
49
 
50
  :param header: The header of a slide.
 
51
  """
52
 
53
- if PATTERN.match(header):
54
  idx = header.find(':')
55
  header = header[idx + 1:]
56
 
@@ -68,7 +68,7 @@ def generate_powerpoint_presentation(
68
  :param structured_data: The presentation contents as "JSON" (may contain trailing commas).
69
  :param slides_template: The PPTX template to use.
70
  :param output_file_path: The path of the PPTX file to save as.
71
- :return A list of presentation title and slides headers.
72
  """
73
 
74
  # The structured "JSON" might contain trailing commas, so using json5
@@ -166,6 +166,28 @@ def _handle_default_display(
166
  :param slide_height_inch: The height of the slide in inches.
167
  """
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  bullet_slide_layout = presentation.slide_layouts[1]
170
  slide = presentation.slides.add_slide(bullet_slide_layout)
171
 
@@ -197,6 +219,181 @@ def _handle_default_display(
197
  )
198
 
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  def _handle_double_col_layout(
201
  presentation: pptx.Presentation(),
202
  slide_json: dict,
@@ -417,112 +614,150 @@ def _get_slide_width_height_inches(presentation: pptx.Presentation) -> Tuple[flo
417
  return slide_width_inch, slide_height_inch
418
 
419
 
 
 
 
 
 
 
 
 
 
 
 
420
  if __name__ == '__main__':
421
  _JSON_DATA = '''
422
  {
423
- "title": "Understanding AI",
424
  "slides": [
425
  {
426
- "heading": "Introduction",
427
  "bullet_points": [
428
- "Brief overview of AI",
429
- [
430
- "Importance of understanding AI"
431
- ]
432
  ],
433
- "key_message": ""
 
434
  },
435
  {
436
- "heading": "What is AI?",
437
  "bullet_points": [
438
- "Definition of AI",
 
439
  [
440
- "Types of AI",
441
- [
442
- "Narrow or weak AI",
443
- "General or strong AI"
444
- ]
445
  ],
446
- "Differences between AI and machine learning"
 
447
  ],
448
- "key_message": ""
 
449
  },
450
  {
451
- "heading": "How AI Works",
452
  "bullet_points": [
453
- "Overview of AI algorithms",
454
- [
455
- "Types of AI algorithms",
456
- [
457
- "Rule-based systems",
458
- "Decision tree systems",
459
- "Neural networks"
 
 
 
 
 
 
 
 
 
 
460
  ]
461
- ],
462
- "How AI processes data"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  ],
464
- "key_message": ""
 
465
  },
466
  {
467
- "heading": "Building AI Models",
468
  "bullet_points": [
469
- ">> Collect data",
470
- ">> Select model or architecture to use",
471
- ">> Set appropriate parameters",
472
- ">> Train model with data",
473
- ">> Run inference",
474
  ],
475
- "key_message": ""
 
476
  },
477
  {
478
- "heading": "Pros and Cons: Deep Learning vs. Classical Machine Learning",
 
 
 
 
 
 
 
 
 
 
479
  "bullet_points": [
480
  {
481
- "heading": "Classical Machine Learning",
482
  "bullet_points": [
483
- "Interpretability: Easy to understand the model",
484
- "Faster Training: Quicker to train models",
485
- "Scalability: Can handle large datasets"
486
  ]
487
  },
488
  {
489
- "heading": "Deep Learning",
490
  "bullet_points": [
491
- "Handling Complex Data: Can learn from raw data",
492
- "Feature Extraction: Automatically learns features",
493
- "Improved Accuracy: Achieves higher accuracy"
494
  ]
495
  }
496
  ],
497
- "key_message": ""
498
- },
499
- {
500
- "heading": "Pros of AI",
501
- "bullet_points": [
502
- "Increased efficiency and productivity",
503
- "Improved accuracy and precision",
504
- "Enhanced decision-making capabilities",
505
- "Personalized experiences"
506
- ],
507
- "key_message": "AI can be used for many different purposes"
508
  },
509
  {
510
- "heading": "Cons of AI",
511
  "bullet_points": [
512
- "Job displacement and loss of employment",
513
- "Bias and discrimination",
514
- "Privacy and security concerns",
515
- "Dependence on technology"
516
  ],
517
- "key_message": ""
 
518
  },
519
  {
520
- "heading": "Future Prospects of AI",
521
  "bullet_points": [
522
- "Advancements in fields such as healthcare and finance",
523
- "Increased use"
 
524
  ],
525
- "key_message": ""
 
526
  }
527
  ]
528
  }'''
 
1
+ """
2
+ A set of functions to create a PowerPoint slide deck.
3
+ """
4
  import logging
5
  import pathlib
6
+ import random
7
  import re
8
+ import sys
9
  import tempfile
10
+ from typing import List, Tuple, Optional
 
11
 
12
  import json5
13
  import pptx
14
+ from dotenv import load_dotenv
15
  from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
16
+ from pptx.shapes.placeholder import PicturePlaceholder, SlidePlaceholder
17
+
18
+ sys.path.append('..')
19
+ sys.path.append('../..')
20
 
21
+ import helpers.image_search as ims
22
  from global_config import GlobalConfig
23
 
24
 
25
+ load_dotenv()
26
+
27
+
28
  # English Metric Unit (used by PowerPoint) to inches
29
  EMU_TO_INCH_SCALING_FACTOR = 1.0 / 914400
30
  INCHES_1_5 = pptx.util.Inches(1.5)
 
34
  INCHES_0_3 = pptx.util.Inches(0.3)
35
 
36
  STEP_BY_STEP_PROCESS_MARKER = '>> '
37
+ IMAGE_DISPLAY_PROBABILITY = 0.3
38
+ FOREGROUND_IMAGE_PROBABILITY = 0.75
39
+ SLIDE_NUMBER_REGEX = re.compile(r"^slide[ ]+\d+:", re.IGNORECASE)
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  logger = logging.getLogger(__name__)
43
 
 
47
  Remove the slide number from a given slide header.
48
 
49
  :param header: The header of a slide.
50
+ :return: The header without slide number.
51
  """
52
 
53
+ if SLIDE_NUMBER_REGEX.match(header):
54
  idx = header.find(':')
55
  header = header[idx + 1:]
56
 
 
68
  :param structured_data: The presentation contents as "JSON" (may contain trailing commas).
69
  :param slides_template: The PPTX template to use.
70
  :param output_file_path: The path of the PPTX file to save as.
71
+ :return: A list of presentation title and slides headers.
72
  """
73
 
74
  # The structured "JSON" might contain trailing commas, so using json5
 
166
  :param slide_height_inch: The height of the slide in inches.
167
  """
168
 
169
+ status = False
170
+
171
+ if random.random() < IMAGE_DISPLAY_PROBABILITY:
172
+ if random.random() < FOREGROUND_IMAGE_PROBABILITY:
173
+ status = _handle_display_image__in_foreground(
174
+ presentation,
175
+ slide_json,
176
+ slide_width_inch,
177
+ slide_height_inch
178
+ )
179
+ else:
180
+ status = _handle_display_image__in_background(
181
+ presentation,
182
+ slide_json,
183
+ slide_width_inch,
184
+ slide_height_inch
185
+ )
186
+
187
+ if status:
188
+ return
189
+
190
+ # Image display failed, so display only text
191
  bullet_slide_layout = presentation.slide_layouts[1]
192
  slide = presentation.slides.add_slide(bullet_slide_layout)
193
 
 
219
  )
220
 
221
 
222
+ def _handle_display_image__in_foreground(
223
+ presentation: pptx.Presentation(),
224
+ slide_json: dict,
225
+ slide_width_inch: float,
226
+ slide_height_inch: float
227
+ ) -> bool:
228
+ """
229
+ Create a slide with text and image using a picture placeholder layout.
230
+
231
+ :param presentation: The presentation object.
232
+ :param slide_json: The content of the slide as JSON data.
233
+ :param slide_width_inch: The width of the slide in inches.
234
+ :param slide_height_inch: The height of the slide in inches.
235
+ :return: True if the side has been processed.
236
+ """
237
+
238
+ img_keywords = slide_json['img_keywords'].strip()
239
+ slide = presentation.slide_layouts[8] # Picture with Caption
240
+ slide = presentation.slides.add_slide(slide)
241
+
242
+ title_placeholder = slide.shapes.title
243
+ title_placeholder.text = remove_slide_number_from_heading(slide_json['heading'])
244
+
245
+ pic_col: PicturePlaceholder = slide.shapes.placeholders[1]
246
+ text_col: SlidePlaceholder = slide.shapes.placeholders[2]
247
+ flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
248
+
249
+ for idx, an_item in enumerate(flat_items_list):
250
+ if idx == 0:
251
+ text_col.text_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
252
+ else:
253
+ paragraph = text_col.text_frame.add_paragraph()
254
+ paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
255
+ paragraph.level = an_item[1]
256
+
257
+ if not img_keywords:
258
+ # No keywords, so no image search and addition
259
+ return True
260
+
261
+ try:
262
+ photo_url, page_url = ims.get_photo_url_from_api_response(
263
+ ims.search_pexels(query=img_keywords, size='medium')
264
+ )
265
+
266
+ if photo_url:
267
+ pic_col.insert_picture(
268
+ ims.get_image_from_url(photo_url)
269
+ )
270
+
271
+ _add_text_at_bottom(
272
+ slide=slide,
273
+ slide_width_inch=slide_width_inch,
274
+ slide_height_inch=slide_height_inch,
275
+ text='Photo provided by Pexels',
276
+ hyperlink=page_url
277
+ )
278
+ except Exception as ex:
279
+ logger.error(
280
+ '*** Error occurred while running adding image to slide: %s',
281
+ str(ex)
282
+ )
283
+
284
+ return True
285
+
286
+
287
+ def _handle_display_image__in_background(
288
+ presentation: pptx.Presentation(),
289
+ slide_json: dict,
290
+ slide_width_inch: float,
291
+ slide_height_inch: float
292
+ ) -> bool:
293
+ """
294
+ Add a slide with text and an image in the background. It works just like
295
+ `_handle_default_display()` but with a background image added.
296
+
297
+ :param presentation: The presentation object.
298
+ :param slide_json: The content of the slide as JSON data.
299
+ :param slide_width_inch: The width of the slide in inches.
300
+ :param slide_height_inch: The height of the slide in inches.
301
+ :return: True if the slide has been processed.
302
+ """
303
+
304
+ img_keywords = slide_json['img_keywords'].strip()
305
+
306
+ # Add a photo in the background, text in the foreground
307
+ slide = presentation.slides.add_slide(presentation.slide_layouts[1])
308
+
309
+ title_shape = slide.shapes.title
310
+ body_shape = slide.shapes.placeholders[1]
311
+ title_shape.text = remove_slide_number_from_heading(slide_json['heading'])
312
+
313
+ flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
314
+
315
+ for idx, an_item in enumerate(flat_items_list):
316
+ if idx == 0:
317
+ body_shape.text_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
318
+ else:
319
+ paragraph = body_shape.text_frame.add_paragraph()
320
+ paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
321
+ paragraph.level = an_item[1]
322
+
323
+ if not img_keywords:
324
+ # No keywords, so no image search and addition
325
+ return True
326
+
327
+ try:
328
+ photo_url, page_url = ims.get_photo_url_from_api_response(
329
+ ims.search_pexels(query=img_keywords, size='large')
330
+ )
331
+
332
+ if photo_url:
333
+ picture = slide.shapes.add_picture(
334
+ image_file=ims.get_image_from_url(photo_url),
335
+ left=0,
336
+ top=0,
337
+ width=pptx.util.Inches(slide_width_inch),
338
+ )
339
+
340
+ _add_text_at_bottom(
341
+ slide=slide,
342
+ slide_width_inch=slide_width_inch,
343
+ slide_height_inch=slide_height_inch,
344
+ text='Photos provided by Pexels',
345
+ hyperlink=page_url
346
+ )
347
+
348
+ # Move picture to background
349
+ # https://github.com/scanny/python-pptx/issues/49#issuecomment-137172836
350
+ slide.shapes._spTree.remove(picture._element)
351
+ slide.shapes._spTree.insert(2, picture._element)
352
+ except Exception as ex:
353
+ logger.error(
354
+ '*** Error occurred while running adding image to the slide background: %s',
355
+ str(ex)
356
+ )
357
+
358
+ return True
359
+
360
+
361
+ def _add_text_at_bottom(
362
+ slide: pptx.slide.Slide,
363
+ slide_width_inch: float,
364
+ slide_height_inch: float,
365
+ text: str,
366
+ hyperlink: Optional[str] = None,
367
+ target_height: Optional[float] = 0.5
368
+ ):
369
+ """
370
+ Add arbitrary text to a textbox positioned near the lower left side of a slide.
371
+
372
+ :param slide: The slide.
373
+ :param slide_width_inch: The width of the slide.
374
+ :param slide_height_inch: The height of the slide.
375
+ :param target_height: the target height of the box in inches (optional).
376
+ :param text: The text to be added
377
+ :param hyperlink: The hyperlink to be added to the text (optional).
378
+ """
379
+
380
+ footer = slide.shapes.add_textbox(
381
+ left=INCHES_1,
382
+ top=pptx.util.Inches(slide_height_inch - target_height),
383
+ width=pptx.util.Inches(slide_width_inch),
384
+ height=pptx.util.Inches(target_height)
385
+ )
386
+
387
+ paragraph = footer.text_frame.paragraphs[0]
388
+ run = paragraph.add_run()
389
+ run.text = text
390
+ run.font.size = pptx.util.Pt(10)
391
+ run.font.underline = False
392
+
393
+ if hyperlink:
394
+ run.hyperlink.address = hyperlink
395
+
396
+
397
  def _handle_double_col_layout(
398
  presentation: pptx.Presentation(),
399
  slide_json: dict,
 
614
  return slide_width_inch, slide_height_inch
615
 
616
 
617
+ def print_placeholder_names(slide: pptx.slide.Slide):
618
+ """
619
+ Display the placeholder details of a given slide.
620
+
621
+ :param slide: The slide.
622
+ """
623
+
624
+ for shape in slide.placeholders:
625
+ print(f'{shape.placeholder_format.idx=}, {shape.name=}')
626
+
627
+
628
  if __name__ == '__main__':
629
  _JSON_DATA = '''
630
  {
631
+ "title": "Mastering PowerPoint Shapes",
632
  "slides": [
633
  {
634
+ "heading": "Introduction to PowerPoint Shapes",
635
  "bullet_points": [
636
+ "Shapes are fundamental elements in PowerPoint",
637
+ "Used to create diagrams, flowcharts, and visuals",
638
+ "Available in various types: lines, rectangles, circles, etc."
 
639
  ],
640
+ "key_message": "",
641
+ "img_keywords": "PowerPoint shapes, basic shapes"
642
  },
643
  {
644
+ "heading": "Types of Shapes in PowerPoint",
645
  "bullet_points": [
646
+ "Lines: Connect two points",
647
+ "Rectangles: Four-sided figures with right angles",
648
  [
649
+ "Squares: Special type of rectangle with equal sides",
650
+ "Rounded Rectangles: Rectangles with rounded corners"
 
 
 
651
  ],
652
+ "Circles: Round shapes with no corners",
653
+ "Ovals: Elliptical shapes"
654
  ],
655
+ "key_message": "",
656
+ "img_keywords": "PowerPoint shapes, types of shapes"
657
  },
658
  {
659
+ "heading": "Creating and Manipulating Shapes",
660
  "bullet_points": [
661
+ ">> Select the 'Home' tab and click on 'Shapes'",
662
+ ">> Choose the desired shape",
663
+ ">> Click and drag to create the shape",
664
+ ">> Resize, move, or rotate shapes using handles",
665
+ ">> Change shape color, fill, and outline"
666
+ ],
667
+ "key_message": "Demonstrates the process of creating and manipulating shapes",
668
+ "img_keywords": "PowerPoint shapes, creating shapes, manipulating shapes"
669
+ },
670
+ {
671
+ "heading": "Advanced Shape Manipulation",
672
+ "bullet_points": [
673
+ {
674
+ "heading": "Adding Text to Shapes",
675
+ "bullet_points": [
676
+ "Right-click on the shape and select 'Add Text'",
677
+ "Type or paste the desired text"
678
  ]
679
+ },
680
+ {
681
+ "heading": "Grouping and Ungrouping Shapes",
682
+ "bullet_points": [
683
+ "Select multiple shapes and press 'Ctrl + G' to group",
684
+ "Right-click and select 'Ungroup' to separate"
685
+ ]
686
+ }
687
+ ],
688
+ "key_message": "Explores advanced techniques for working with shapes",
689
+ "img_keywords": "PowerPoint shapes, advanced manipulation, grouping, text in shapes"
690
+ },
691
+ {
692
+ "heading": "Using the 'Format' Tab for Shapes",
693
+ "bullet_points": [
694
+ "Access advanced shape formatting options",
695
+ "Change shape fill, outline, and effects",
696
+ "Adjust shape size and position"
697
  ],
698
+ "key_message": "",
699
+ "img_keywords": "PowerPoint shapes, format tab, advanced formatting"
700
  },
701
  {
702
+ "heading": "Example: Creating a Simple Diagram",
703
  "bullet_points": [
704
+ "Use rectangles to represent blocks",
705
+ "Use lines to connect blocks",
706
+ "Add text to shapes to label elements"
 
 
707
  ],
708
+ "key_message": "Illustrates the use of shapes to create a simple diagram",
709
+ "img_keywords": "PowerPoint shapes, diagram example, simple diagram"
710
  },
711
  {
712
+ "heading": "Example: Creating a Flowchart",
713
+ "bullet_points": [
714
+ "Use different shapes to represent steps, decisions, and inputs/outputs",
715
+ "Use connectors to link shapes",
716
+ "Add text to shapes to describe each step"
717
+ ],
718
+ "key_message": "Demonstrates the use of shapes to create a flowchart",
719
+ "img_keywords": "PowerPoint shapes, flowchart example, creating flowchart"
720
+ },
721
+ {
722
+ "heading": "Double Column Layout: Shapes in Older vs. Newer PowerPoint Versions",
723
  "bullet_points": [
724
  {
725
+ "heading": "Older PowerPoint Versions",
726
  "bullet_points": [
727
+ "Limited shape types and formatting options",
728
+ "Less intuitive shape creation and manipulation"
 
729
  ]
730
  },
731
  {
732
+ "heading": "Newer PowerPoint Versions",
733
  "bullet_points": [
734
+ "Expanded shape library with more types and styles",
735
+ "Improved shape formatting and manipulation tools"
 
736
  ]
737
  }
738
  ],
739
+ "key_message": "Compares the use of shapes in older and newer PowerPoint versions",
740
+ "img_keywords": "PowerPoint shapes, older versions, newer versions, comparison"
 
 
 
 
 
 
 
 
 
741
  },
742
  {
743
+ "heading": "Tips for Effective Use of Shapes",
744
  "bullet_points": [
745
+ "Keep shapes simple and uncluttered",
746
+ "Use consistent colors and styles",
747
+ "Avoid overusing shapes, maintain balance with text and other elements"
 
748
  ],
749
+ "key_message": "Provides best practices for using shapes in presentations",
750
+ "img_keywords": "PowerPoint shapes, best practices, effective use"
751
  },
752
  {
753
+ "heading": "Conclusion",
754
  "bullet_points": [
755
+ "Shapes are versatile tools in PowerPoint",
756
+ "Mastering shapes enhances presentation visuals",
757
+ "Practice and experimentation are key to improving shape usage"
758
  ],
759
+ "key_message": "Summarizes the importance of shapes in PowerPoint and encourages practice",
760
+ "img_keywords": "PowerPoint shapes, conclusion, importance"
761
  }
762
  ]
763
  }'''