python-pptx2 2.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,189 @@
1
+ # Animations (Phase 5)
2
+
3
+ `pptx2.animation` ships a preset-only API that maps directly onto
4
+ PowerPoint's built-in animation library. All generated XML is valid
5
+ OOXML and round-trips through PowerPoint without loss.
6
+
7
+ ## Imports
8
+
9
+ ```python
10
+ from pptx2.animation import Entrance, Exit, Emphasis, MotionPath, Trigger
11
+ from pptx2.util import Inches, Pt
12
+ ```
13
+
14
+ `Trigger` is an alias for `pptx2.enum.animation.PP_ANIM_TRIGGER`.
15
+
16
+ ## Triggers and delay
17
+
18
+ Every preset accepts an optional `trigger` and `delay` (milliseconds):
19
+
20
+ ```python
21
+ Entrance.fade(slide, shape) # default: ON_CLICK
22
+ Entrance.fly_in(slide, shape, trigger=Trigger.WITH_PREVIOUS)
23
+ Entrance.zoom(slide, shape, trigger=Trigger.AFTER_PREVIOUS, delay=500)
24
+ ```
25
+
26
+ ## Entrance presets
27
+
28
+ ```python
29
+ Entrance.appear(slide, shape)
30
+ Entrance.fade(slide, shape)
31
+ Entrance.fly_in(slide, shape, direction="bottom") # also "top", "left", "right"
32
+ Entrance.float_in(slide, shape)
33
+ Entrance.wipe(slide, shape)
34
+ Entrance.zoom(slide, shape)
35
+ Entrance.wheel(slide, shape)
36
+ Entrance.random_bars(slide, shape)
37
+ ```
38
+
39
+ ## Exit presets
40
+
41
+ ```python
42
+ Exit.disappear(slide, shape)
43
+ Exit.fade(slide, shape)
44
+ Exit.fly_out(slide, shape)
45
+ Exit.float_out(slide, shape)
46
+ Exit.wipe(slide, shape)
47
+ Exit.zoom(slide, shape)
48
+ ```
49
+
50
+ ## Emphasis presets
51
+
52
+ ```python
53
+ Emphasis.pulse(slide, shape)
54
+ Emphasis.spin(slide, shape)
55
+ Emphasis.teeter(slide, shape)
56
+ ```
57
+
58
+ ## Per-paragraph reveal
59
+
60
+ Reveal a text frame one paragraph at a time, fired by a single click:
61
+
62
+ ```python
63
+ body = slide.placeholders[1].text_frame
64
+ Entrance.fade(slide, body, by_paragraph=True)
65
+ ```
66
+
67
+ Supported presets for `by_paragraph=True`: `appear`, `fade`, `wipe`,
68
+ `zoom`, `wheel`, `random_bars`. The first paragraph fires on the
69
+ caller-supplied trigger (or `ON_CLICK`); subsequent paragraphs default
70
+ to `Trigger.AFTER_PREVIOUS`.
71
+
72
+ ## Sequencing — chain effects from one click
73
+
74
+ ```python
75
+ with slide.animations.sequence():
76
+ Entrance.fade(slide, title_shape)
77
+ Entrance.fly_in(slide, body_shape)
78
+ Emphasis.pulse(slide, badge_shape)
79
+ ```
80
+
81
+ Inside the `with` block:
82
+ - The first effect fires on `Trigger.ON_CLICK` (or whatever `start=` is
83
+ passed to `sequence(start=...)`).
84
+ - Every subsequent effect defaults to `Trigger.AFTER_PREVIOUS`.
85
+ - Explicit per-call triggers still win.
86
+
87
+ Sequences cannot be nested.
88
+
89
+ ## Grouping — animate sub-shapes as one cluster
90
+
91
+ `group()` is the right primitive for "this card / row / panel made of
92
+ several sub-shapes should animate as one visual unit." Inside the
93
+ block, the first effect uses the group's `start` trigger and every
94
+ subsequent effect defaults to `Trigger.WITH_PREVIOUS`:
95
+
96
+ ```python
97
+ for i, card in enumerate(cards):
98
+ with slide.animations.group(delay=0 if i == 0 else 200):
99
+ Entrance.fade(slide, card.body)
100
+ Entrance.fade(slide, card.title)
101
+ Entrance.fade(slide, card.blurb)
102
+ ```
103
+
104
+ This produces one `AFTER_PREVIOUS` start plus N-1 `WITH_PREVIOUS`
105
+ followers per card — much cheaper for PowerPoint to render than the
106
+ same number of independent click-groups, and dramatically reduces the
107
+ "slide takes a beat to appear" lag on dense decks.
108
+
109
+ Use `group()` for sub-shape clusters; use `sequence()` for cluster-to-
110
+ cluster chains. They can't be combined.
111
+
112
+ ## Inspecting and clearing animations
113
+
114
+ `SlideAnimations` is now iterable and supports `len()`:
115
+
116
+ ```python
117
+ print(len(slide.animations)) # how many entries?
118
+ for entry in slide.animations:
119
+ print(entry.kind, entry.preset, entry.trigger,
120
+ "shape=", entry.shape.name if entry.shape else None,
121
+ "duration=", entry.duration, "delay=", entry.delay)
122
+ ```
123
+
124
+ Each entry is a read-only `AnimationEntry`. To remove the entry from
125
+ the slide call `entry.remove()`; to wipe the whole slide call
126
+ `slide.animations.clear()` (returns the count of entries removed).
127
+
128
+ `slide.animations.purge_orphans()` is the narrower cousin — it only
129
+ removes entries whose target shape has been deleted.
130
+
131
+ ## Motion paths
132
+
133
+ ```python
134
+ MotionPath.line(slide, shape, dx=Inches(2), dy=Inches(1))
135
+ MotionPath.diagonal(slide, shape, dx=Inches(3), dy=Inches(2))
136
+ MotionPath.circle(slide, shape, radius=Inches(1), clockwise=True)
137
+ MotionPath.arc(slide, shape, dx=Inches(3), dy=Inches(0), height=0.4)
138
+ MotionPath.zigzag(slide, shape, dx=Inches(4), dy=Inches(0),
139
+ segments=6, amplitude=0.2)
140
+ MotionPath.spiral(slide, shape, radius=Inches(2),
141
+ turns=2.5, clockwise=True)
142
+
143
+ # Pass a raw OOXML motion-path expression
144
+ MotionPath.custom(slide, shape, "M 0 0 L 0.5 0.5 L 1 0")
145
+ ```
146
+
147
+ All preset constructors normalise EMU inputs against the slide
148
+ dimensions before emitting the path attribute, so the *absolute* travel
149
+ distance is preserved across slide sizes.
150
+
151
+ ## Round-trip safety
152
+
153
+ Animations authored in PowerPoint survive a read–modify–write cycle.
154
+ Generated effects are appended to the existing `<p:tnLst>` timing tree
155
+ without touching pre-existing `<p:par>` nodes:
156
+
157
+ ```python
158
+ prs = Presentation("hand-authored.pptx")
159
+ slide = prs.slides[0]
160
+ Entrance.fade(slide, slide.shapes[0]) # adds, doesn't disturb
161
+ prs.save("with-extra-fade.pptx")
162
+ ```
163
+
164
+ ## End-to-end example
165
+
166
+ ```python
167
+ from pptx2 import Presentation
168
+ from pptx2.animation import Entrance, Emphasis, MotionPath, Trigger
169
+ from pptx2.util import Inches
170
+
171
+ prs = Presentation()
172
+ slide = prs.slides.add_slide(prs.slide_layouts[5])
173
+ slide.shapes.title.text = "Animated demo"
174
+
175
+ box = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(8), Inches(1))
176
+ box.text_frame.text = "Click anywhere to animate"
177
+
178
+ # A 3-step click-driven sequence
179
+ with slide.animations.sequence():
180
+ Entrance.fade(slide, slide.shapes.title)
181
+ Entrance.fly_in(slide, box, direction="left")
182
+ Emphasis.pulse(slide, box)
183
+
184
+ # Extra effect after the sequence: a motion path on the box
185
+ MotionPath.arc(slide, box, dx=Inches(2), dy=Inches(0), height=0.3,
186
+ trigger=Trigger.AFTER_PREVIOUS)
187
+
188
+ prs.save("animated.pptx")
189
+ ```
@@ -0,0 +1,421 @@
1
+ # Basics — the inherited 1.0.2 surface
2
+
3
+ Everything in this file works the same as upstream `python-pptx 1.0.2`.
4
+ It's here so you don't have to leave the skill for boring boilerplate.
5
+
6
+ ## Open / create / save
7
+
8
+ ```python
9
+ from pptx2 import Presentation
10
+
11
+ prs = Presentation() # blank deck, default 16:9
12
+ prs = Presentation("template.pptx") # open existing
13
+ prs.save("out.pptx")
14
+ ```
15
+
16
+ `Presentation(...)` also accepts a binary file-like object — useful for
17
+ HTTP responses or in-memory generation:
18
+
19
+ ```python
20
+ import io
21
+ buf = io.BytesIO()
22
+ prs.save(buf)
23
+ buf.seek(0)
24
+ return buf.getvalue()
25
+ ```
26
+
27
+ ## Slide size
28
+
29
+ ```python
30
+ from pptx2.util import Inches
31
+
32
+ prs.slide_width = Inches(13.333) # 16:9 widescreen
33
+ prs.slide_height = Inches(7.5)
34
+ ```
35
+
36
+ ## Adding slides
37
+
38
+ ```python
39
+ title_layout = prs.slide_layouts[0] # 0 = Title, 1 = Title+Content,
40
+ blank_layout = prs.slide_layouts[6] # 5 = Title only, 6 = Blank, ...
41
+
42
+ slide = prs.slides.add_slide(title_layout)
43
+ slide.shapes.title.text = "Q4 Review"
44
+ slide.placeholders[1].text = "April 2026"
45
+ ```
46
+
47
+ Layouts are master-dependent; use `prs.slide_master.slide_layouts` if you
48
+ want to be explicit, or iterate `for L in prs.slide_layouts: print(L.name)`
49
+ to discover what the template ships.
50
+
51
+ ## Text boxes
52
+
53
+ ```python
54
+ from pptx2.util import Inches, Pt
55
+ from pptx2.dml.color import RGBColor
56
+
57
+ box = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(1))
58
+ tf = box.text_frame
59
+ tf.word_wrap = True
60
+
61
+ p = tf.paragraphs[0]
62
+ p.text = "Hello world"
63
+ p.font.name = "Inter"
64
+ p.font.size = Pt(36)
65
+ p.font.bold = True
66
+ p.font.color.rgb = RGBColor(0x1F, 0x29, 0x37)
67
+
68
+ p2 = tf.add_paragraph()
69
+ p2.text = "Subtitle goes here"
70
+ p2.font.size = Pt(18)
71
+ ```
72
+
73
+ ### One call instead of per-run styling
74
+
75
+ Setting `font.*` on every paragraph is the single most common source of
76
+ bloated deck-building code, and it silently misses runs you didn't
77
+ enumerate. `set_paragraph_defaults` applies to *every* paragraph and run
78
+ in the frame, including ones added later in the same breath:
79
+
80
+ ```python
81
+ tf.text = "Headline"
82
+ tf.add_paragraph().text = "Supporting line"
83
+
84
+ tf.set_paragraph_defaults(
85
+ font_name="Inter", size=Pt(14), bold=True, color="#333333",
86
+ )
87
+ ```
88
+
89
+ Accepted kwargs are exactly `font_name`, `size`, `bold`, `italic`,
90
+ `color` — all keyword-only, all optional. Note it does **not** take
91
+ spacing arguments; `space_before` / `space_after` / `line_spacing` are
92
+ per-paragraph (below).
93
+
94
+ ### Spacing and margins
95
+
96
+ ```python
97
+ p.space_before = Pt(6)
98
+ p.space_after = Pt(6)
99
+ p.line_spacing = 1.2 # multiple, or Pt(20) for exact
100
+
101
+ tf.margin_left = Inches(0.2) # also margin_right/top/bottom
102
+ ```
103
+
104
+ Paragraphs can also be built run-by-run when you need mixed styling in
105
+ one line:
106
+
107
+ ```python
108
+ p = tf.add_paragraph()
109
+ run = p.add_run(); run.text = "Bold lead-in. "
110
+ run.font.bold = True
111
+ run.font.underline = True
112
+ p.add_line_break() # soft break, stays in the paragraph
113
+ ```
114
+
115
+ ### Run-level type styling
116
+
117
+ `font` exposes the run-property knobs that separate "looks branded" from
118
+ "looks generated" — set them on a paragraph's `font` or a specific run's
119
+ `font`:
120
+
121
+ ```python
122
+ eyebrow.font.all_caps = True # or .small_caps = True (mutually exclusive)
123
+ eyebrow.font.letter_spacing = Pt(2) # tracking; negative tightens
124
+ old.font.strikethrough = True
125
+ units.font.superscript = True # or .subscript (share one baseline)
126
+ ```
127
+
128
+ All are tri-state: `None` (the default) inherits from the theme/master,
129
+ `True`/`False` write an explicit override. They round-trip and validate
130
+ against the OOXML schema.
131
+
132
+ ### Text effects (outline, shadow, glow)
133
+
134
+ Per-run glyph effects live on `run.font`, mirroring shape
135
+ `.line`/`.shadow`/`.glow`:
136
+
137
+ ```python
138
+ run.font.outline.color.rgb = "FF0000" # coloured glyph outline...
139
+ run.font.outline.width = Pt(1) # ...of a given stroke width
140
+ run.font.shadow.color.rgb = "808080"; run.font.shadow.blur_radius = Pt(3)
141
+ run.font.glow.color.rgb = "00B0F0"; run.font.glow.radius = Pt(6)
142
+ ```
143
+
144
+ Reads are non-mutating — nothing is written until you assign, so theme
145
+ inheritance is preserved.
146
+
147
+ ## Auto shapes
148
+
149
+ ```python
150
+ from pptx2.enum.shapes import MSO_SHAPE
151
+
152
+ card = slide.shapes.add_shape(
153
+ MSO_SHAPE.ROUNDED_RECTANGLE,
154
+ left=Inches(1), top=Inches(2),
155
+ width=Inches(4), height=Inches(2.5),
156
+ )
157
+ card.fill.solid()
158
+ card.fill.fore_color.rgb = RGBColor(0xF8, 0xFA, 0xFC)
159
+ card.line.color.rgb = RGBColor(0xE5, 0xE7, 0xEB)
160
+ card.line.width = Pt(1)
161
+ ```
162
+
163
+ ### Per-color alpha (transparency)
164
+
165
+ Both fill and line colours expose `alpha` in the `[0.0, 1.0]` range —
166
+ useful for hairline dividers, glow shapes, and translucent overlays.
167
+ Assign after a colour is set:
168
+
169
+ ```python
170
+ divider.line.color.rgb = RGBColor(0x0D, 0x0D, 0x0D)
171
+ divider.line.color.alpha = 0.08 # 8% opaque hairline
172
+ ```
173
+
174
+ ### Two-stop linear gradients
175
+
176
+ ```python
177
+ bar.fill.linear_gradient("#06D6FE", "#B14AED", angle=90) # top→bottom
178
+ ```
179
+
180
+ `angle` follows the OOXML convention: `0` is left→right, `90` is
181
+ top→bottom, `180` is right→left, `270` is bottom→top.
182
+
183
+ → **Multi-stop gradients, gradient kinds, mutable stop lists, and the
184
+ full alpha surface live in `effects.md`** — the canonical fills and
185
+ effects reference. Don't go looking in two places.
186
+
187
+ ## Pictures
188
+
189
+ ```python
190
+ pic = slide.shapes.add_picture(
191
+ "hero.jpg",
192
+ left=Inches(0), top=Inches(0),
193
+ width=prs.slide_width, height=prs.slide_height,
194
+ )
195
+ ```
196
+
197
+ ### Cropping
198
+
199
+ Crop values are *fractions of the original image*, not lengths — `0.1`
200
+ trims 10% off that edge. They compose with the placement box, so crop
201
+ first, then size:
202
+
203
+ ```python
204
+ pic = slide.shapes.add_picture("hero.png", Inches(1), Inches(1),
205
+ width=Inches(4))
206
+ pic.crop_left = 0.10 # also crop_right/top/bottom
207
+ pic.crop_top = 0.05
208
+ pic.alt_text = "Q4 revenue by segment" # set this; screen readers need it
209
+ ```
210
+
211
+ ### Anchored placement
212
+
213
+ `add_picture`, `add_shape`, and `add_textbox` accept an
214
+ ``anchor=`` keyword that collapses the
215
+ ``add → measure → reposition`` idiom for branding elements:
216
+
217
+ ```python
218
+ # Logo at bottom-right with a 0.25" margin, height-only sizing:
219
+ slide.shapes.add_picture(
220
+ "logo.png",
221
+ anchor="bottom-right",
222
+ margin=Inches(0.25),
223
+ height=Inches(0.32),
224
+ )
225
+
226
+ # Title centred in the top half of a parent card:
227
+ slide.shapes.add_textbox(
228
+ Inches(0), Inches(0), Inches(2), Inches(0.5),
229
+ anchor="top-center", margin=Inches(0.25),
230
+ container=card, # any shape with .width / .height
231
+ )
232
+ ```
233
+
234
+ `anchor` is one of `top-left`, `top-center`, `top-right`,
235
+ `middle-left`, `middle-center` (or bare `center`),
236
+ `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`.
237
+ Both `center` / `centre` spellings are accepted. `container` is the
238
+ slide by default; pass any shape (or anything exposing
239
+ `.width` / `.height`) to anchor inside a card / group / placeholder.
240
+
241
+ ## Grouping shapes
242
+
243
+ `slide.shapes.add_group_shape()` returns a `GroupShape` whose
244
+ `.shapes` collection has the same `add_*` methods as a slide. The
245
+ group's offset/extent shrink-wrap to its members as you add them.
246
+
247
+ ```python
248
+ group = slide.shapes.add_group_shape()
249
+ group.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(1), Inches(1), Inches(2), Inches(1))
250
+ group.shapes.add_shape(MSO_SHAPE.OVAL, Inches(4), Inches(2), Inches(1), Inches(1))
251
+
252
+ group.fill.solid() # tint the whole group (members paint on top)
253
+ group.fill.fore_color.rgb = "1F4E79"
254
+ group.move(Inches(0.5), Inches(0)) # translate group + every member, O(1)
255
+
256
+ for shape in group.walk(): # depth-first, recurses into nested groups
257
+ ... # filter shape.shape_type for leaves only
258
+
259
+ group.fit_to_children() # re-tighten bbox after editing members directly
260
+ promoted = group.ungroup() # dissolve; members keep their on-screen geometry
261
+ ```
262
+
263
+ - A group admits a **fill** but not a **line** — the OOXML schema has
264
+ no `a:ln` on `p:grpSpPr`, so there is intentionally no `group.line`.
265
+ (Outline a group by outlining a backing rectangle inside it.)
266
+ - `ungroup()` returns the promoted shapes and preserves z-order. It
267
+ raises `ValueError` on a rotated/flipped group; reset rotation and
268
+ flip to 0 first.
269
+
270
+ ## Tables
271
+
272
+ ```python
273
+ table_shape = slide.shapes.add_table(
274
+ rows=4, cols=3,
275
+ left=Inches(1), top=Inches(2),
276
+ width=Inches(8), height=Inches(3),
277
+ style="clean", # disable inherited style flags for hand-styled tables
278
+ )
279
+ table = table_shape.table
280
+
281
+ # Header
282
+ for i, label in enumerate(("Metric", "Value", "Δ QoQ")):
283
+ cell = table.cell(0, i)
284
+ cell.text = label
285
+ cell.text_frame.paragraphs[0].font.bold = True
286
+
287
+ # Body
288
+ for row, (k, v, d) in enumerate([("ARR", "$182M", "+27%"),
289
+ ("NDR", "131%", "+3%"),
290
+ ("CAC payback", "8 mo", "−1 mo")], start=1):
291
+ table.cell(row, 0).text = k
292
+ table.cell(row, 1).text = v
293
+ table.cell(row, 2).text = d
294
+ ```
295
+
296
+ Pass `style="clean"` whenever you plan to apply custom cell borders
297
+ or fills. The default inherited table style otherwise overlays them
298
+ and renders inconsistently across PowerPoint and LibreOffice.
299
+
300
+ (See `tables.md` for `Cell.borders`, the post-fork addition.)
301
+
302
+ ## Charts
303
+
304
+ ```python
305
+ from pptx2.chart.data import CategoryChartData
306
+ from pptx2.enum.chart import XL_CHART_TYPE
307
+
308
+ data = CategoryChartData()
309
+ data.categories = ["Q1", "Q2", "Q3", "Q4"]
310
+ data.add_series("ARR", (100, 130, 155, 182))
311
+
312
+ chart_shape = slide.shapes.add_chart(
313
+ XL_CHART_TYPE.COLUMN_CLUSTERED,
314
+ Inches(1), Inches(2), Inches(8), Inches(4.5),
315
+ data,
316
+ )
317
+ chart = chart_shape.chart
318
+ chart.has_title = True
319
+ chart.chart_title.text_frame.text = "ARR ($M)"
320
+ ```
321
+
322
+ (See `charts.md` for chart palettes, quick layouts, and per-series fills.)
323
+
324
+ ## Knowing what a shape is
325
+
326
+ Before touching a shape you found by iteration, ask what it actually is
327
+ — the accessors raise rather than return `None` when the shape has no
328
+ such content:
329
+
330
+ ```python
331
+ for shape in slide.shapes:
332
+ if shape.has_text_frame:
333
+ print(shape.text_frame.text)
334
+ if shape.has_table:
335
+ print(shape.table.rows)
336
+ if shape.has_chart:
337
+ print(shape.chart.chart_type)
338
+ if shape.is_placeholder:
339
+ print("placeholder idx", shape.placeholder_format.idx,
340
+ "type", shape.placeholder_format.type)
341
+ print(shape.shape_id, shape.name) # shape_id is unique per slide
342
+ ```
343
+
344
+ `shape.delete()` removes a shape *and* purges any animation timing
345
+ entries that targeted it — PowerPoint silently "repairs" decks with
346
+ orphan timing references, so prefer it over detaching the element by
347
+ hand.
348
+
349
+ ## Iterating an existing deck
350
+
351
+ ```python
352
+ prs = Presentation("input.pptx")
353
+ for slide in prs.slides:
354
+ for shape in slide.shapes:
355
+ if shape.has_text_frame:
356
+ for para in shape.text_frame.paragraphs:
357
+ for run in para.runs:
358
+ print(run.text)
359
+ ```
360
+
361
+ ## Common units
362
+
363
+ ```python
364
+ from pptx2.util import Inches, Pt, Cm, Emu, Mm
365
+
366
+ Inches(1) # 914400 EMU
367
+ Pt(12) # 152400 EMU
368
+ Cm(2.54) # ≈ Inches(1)
369
+ ```
370
+
371
+ Use these everywhere — never write the EMU integers directly.
372
+
373
+ Arithmetic on lengths is fine — python-pptx2 coerces float coordinates
374
+ to integer EMU at the API boundary, so this works:
375
+
376
+ ```python
377
+ card_w = (Inches(12.33) - Inches(0.25)) / 2 # produces a float
378
+ slide.shapes.add_chart(chart_type, x, y, card_w, height, data)
379
+ slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, card_w, h)
380
+ shape.width = card_w # setter coerces too
381
+ ```
382
+
383
+ Both ``/`` (true division → float) and ``//`` (floor division → int)
384
+ work; pick whichever reads more cleanly. The coercion is round-half-
385
+ to-even, so it's unbiased over long expression chains.
386
+
387
+ ### International & layout text properties
388
+
389
+ ```python
390
+ p = tf.paragraphs[0]
391
+ p.rtl = True # right-to-left (Hebrew / Arabic / Farsi)
392
+ p.start_at = 5 # numbered list starting at 5 (arabicPeriod)
393
+ p.set_numbered("romanLcPeriod", 3) # i. ii. iii. starting at 3
394
+
395
+ tf.column_count = 2 # two-column text body
396
+ tf.column_spacing = Pt(18) # gutter between columns
397
+
398
+ p.tab_stops.add_tab_stop(Inches(1), "center") # left | center | right | decimal
399
+ ```
400
+
401
+ ### Sections, slide order, and speaker notes
402
+
403
+ ```python
404
+ # Named sections (PowerPoint outline / slide-sorter groupings)
405
+ prs.sections.add("Intro", start_slide_index=0)
406
+ prs.sections.add("Body", start_slide_index=2)
407
+ for section in prs.sections:
408
+ print(section.name, section.slide_ids) # section.name is read/write
409
+
410
+ # Reorder slides without touching XML
411
+ prs.slides.move(0, 2) # send slide 0 to position 2
412
+ prs.slides.reorder([2, 0, 1]) # full permutation (indices or Slide objects)
413
+
414
+ # First-class speaker notes
415
+ slide.notes = "Remember to thank the sponsors." # creates the notes slide
416
+ print(slide.notes) # "" when the slide has no notes
417
+ ```
418
+
419
+ `start_slide_index` claims every slide from that position to the deck
420
+ end. `move` raises `IndexError` out of range; `reorder` raises
421
+ `ValueError` unless given a clean permutation.