from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.shapes import MSO_SHAPE OUT_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\songs\anytime-anywhere\AnytimeAnywhereFlowchart.pptx" SLIDE_W = Inches(13.333) SLIDE_H = Inches(7.5) TWILIGHT = RGBColor(0x1C, 0x2E, 0x4A) GOLD = RGBColor(0xE8, 0xC5, 0x6A) WHITE = RGBColor(0xFF, 0xFF, 0xFF) TITLE = "Anytime, Anywhere" # Flow: A -> R, then R <-> B loop (R -> B -> back to R) # The refrain box R appears once; both other lines feed into it. BOX_R = "Anytime,\nanywhere" # (verse label, box A text, box B text) VERSES = [ ("Verse 1", "I can pray to my\nHeav'nly Father", "For He always cares,\nand He hears my prayers"), ("Verse 2", "I can listen for\nheav'nly guidance", "The Spirit will guide\nas I seek His light"), ] def add_box(slide, left, top, w, h, text, is_refrain): box = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, w, h) box.fill.solid() box.fill.fore_color.rgb = GOLD if is_refrain else WHITE box.line.color.rgb = GOLD box.line.width = Pt(2.5) tf = box.text_frame tf.word_wrap = True tf.vertical_anchor = MSO_ANCHOR.MIDDLE for i, line in enumerate(text.split("\n")): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.alignment = PP_ALIGN.CENTER run = p.add_run() run.text = line run.font.size = Pt(28) run.font.bold = True run.font.color.rgb = TWILIGHT return box def add_arrow(slide, shape_type, left, top, w, h): arrow = slide.shapes.add_shape(shape_type, left, top, w, h) arrow.fill.solid() arrow.fill.fore_color.rgb = GOLD arrow.line.fill.background() return arrow def add_flow_slide(prs, verse_label, box_a, box_b): slide = prs.slides.add_slide(prs.slide_layouts[6]) fill = slide.background.fill fill.solid() fill.fore_color.rgb = TWILIGHT # Title title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.35), SLIDE_W - Inches(1.0), Inches(0.9)) p = title_box.text_frame.paragraphs[0] p.alignment = PP_ALIGN.CENTER run = p.add_run() run.text = TITLE run.font.size = Pt(40) run.font.color.rgb = GOLD run.font.bold = True # Verse label under the title label_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.05), SLIDE_W - Inches(1.0), Inches(0.45)) p2 = label_box.text_frame.paragraphs[0] p2.alignment = PP_ALIGN.CENTER run2 = p2.add_run() run2.text = verse_label run2.font.size = Pt(20) run2.font.color.rgb = WHITE run2.font.italic = True # Layout: A on top of the left column, R below it, B to the right of R. # A flows down into R; R and B loop (R -> B on top, B -> R underneath). box_w = Inches(4.8) gap_x = Inches(1.7) left_x = (SLIDE_W - (box_w * 2 + gap_x)) / 2 right_x = left_x + box_w + gap_x a_y = Inches(1.7) a_h = Inches(1.6) r_y = Inches(4.3) r_h = Inches(1.9) add_box(slide, left_x, a_y, box_w, a_h, box_a, False) add_box(slide, left_x, r_y, box_w, r_h, BOX_R, True) add_box(slide, right_x, r_y, box_w, r_h, box_b, False) # A -> R: down arrow arrow_w = Inches(0.7) arrow_l = Inches(0.85) gap_y = r_y - (a_y + a_h) add_arrow(slide, MSO_SHAPE.DOWN_ARROW, left_x + (box_w - arrow_w) / 2, a_y + a_h + (gap_y - arrow_l) / 2, arrow_w, arrow_l) # R <-> B loop: right arrow above, left arrow below loop_arrow_w = Inches(1.3) loop_x = left_x + box_w + (gap_x - loop_arrow_w) / 2 add_arrow(slide, MSO_SHAPE.RIGHT_ARROW, loop_x, r_y + r_h * 0.22 - arrow_w / 2, loop_arrow_w, arrow_w) add_arrow(slide, MSO_SHAPE.LEFT_ARROW, loop_x, r_y + r_h * 0.78 - arrow_w / 2, loop_arrow_w, arrow_w) print(f" Added: {verse_label}") def main(): prs = Presentation() prs.slide_width = SLIDE_W prs.slide_height = SLIDE_H for verse_label, box_a, box_b in VERSES: add_flow_slide(prs, verse_label, box_a, box_b) prs.save(OUT_PATH) print(f"\nSaved: {OUT_PATH} ({len(VERSES)} slides)") if __name__ == "__main__": main()