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
pptx2/shared.py ADDED
@@ -0,0 +1,82 @@
1
+ """Objects shared by pptx2 modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from pptx2.opc.package import XmlPart
9
+ from pptx2.oxml.xmlchemy import BaseOxmlElement
10
+ from pptx2.types import ProvidesPart
11
+
12
+
13
+ class ElementProxy(object):
14
+ """Base class for lxml element proxy classes.
15
+
16
+ An element proxy class is one whose primary responsibilities are fulfilled by manipulating the
17
+ attributes and child elements of an XML element. They are the most common type of class in
18
+ python-pptx other than custom element (oxml) classes.
19
+ """
20
+
21
+ def __init__(self, element: BaseOxmlElement):
22
+ self._element = element
23
+
24
+ def __eq__(self, other: object) -> bool:
25
+ """Return |True| if this proxy object refers to the same oxml element as does *other*.
26
+
27
+ ElementProxy objects are value objects and should maintain no mutable local state.
28
+ Equality for proxy objects is defined as referring to the same XML element, whether or not
29
+ they are the same proxy object instance.
30
+ """
31
+ if not isinstance(other, ElementProxy):
32
+ return False
33
+ return self._element is other._element
34
+
35
+ def __ne__(self, other: object) -> bool:
36
+ if not isinstance(other, ElementProxy):
37
+ return True
38
+ return self._element is not other._element
39
+
40
+ @property
41
+ def element(self):
42
+ """The lxml element proxied by this object."""
43
+ return self._element
44
+
45
+
46
+ class ParentedElementProxy(ElementProxy):
47
+ """Provides access to ancestor objects and part.
48
+
49
+ An ancestor may occasionally be required to provide a service, such as add or drop a
50
+ relationship. Provides the :attr:`_parent` attribute to subclasses and the public
51
+ :attr:`parent` read-only property.
52
+ """
53
+
54
+ def __init__(self, element: BaseOxmlElement, parent: ProvidesPart):
55
+ super(ParentedElementProxy, self).__init__(element)
56
+ self._parent = parent
57
+
58
+ @property
59
+ def parent(self):
60
+ """The ancestor proxy object to this one.
61
+
62
+ For example, the parent of a shape is generally the |SlideShapes| object that contains it.
63
+ """
64
+ return self._parent
65
+
66
+ @property
67
+ def part(self) -> XmlPart:
68
+ """The package part containing this object."""
69
+ return self._parent.part
70
+
71
+
72
+ class PartElementProxy(ElementProxy):
73
+ """Provides common members for proxy-objects that wrap a part's root element, e.g. `p:sld`."""
74
+
75
+ def __init__(self, element: BaseOxmlElement, part: XmlPart):
76
+ super(PartElementProxy, self).__init__(element)
77
+ self._part = part
78
+
79
+ @property
80
+ def part(self) -> XmlPart:
81
+ """The package part containing this object."""
82
+ return self._part
pptx2/skill/SKILL.md ADDED
@@ -0,0 +1,450 @@
1
+ ---
2
+ name: python-pptx2
3
+ description: Build PowerPoint (.pptx) decks from Python with the python-pptx2 library — a fork of power-pptx / python-pptx. Use this skill whenever the user wants to generate, mutate, lint, theme, animate, or render PowerPoint decks programmatically. The headline reason this line exists is **space-awareness**: text that doesn't overflow its box and shapes that don't slide off the edges of the slide. Reach for it especially when generation is dynamic (LLM, DB, CLI, JSON spec) and the deck has to look right without manual cleanup. Other features include native LaTeX equations, visual effects, animations, transitions, theme writer, design tokens, slide recipes, slide thumbnails, chart palettes, SVG embedding, 3D, and SmartArt text substitution.
4
+ ---
5
+
6
+ # python-pptx2
7
+
8
+ `python-pptx2` is a fork of `power-pptx` (and, through it, `python-pptx`),
9
+ distributed on PyPI as `python-pptx2` and imported as `import pptx2`.
10
+ Use it for every PowerPoint generation / mutation task.
11
+
12
+ ## The headline: space-aware authoring
13
+
14
+ The single biggest reason this fork exists is to make programmatic
15
+ decks **physically correct**: text doesn't overflow its container,
16
+ shapes don't sit off the slide, and elements that overlap do so on
17
+ purpose. Three layered tools — used together — catch ~all real-world
18
+ issues:
19
+
20
+ 1. **`TextFrame.fit_text(...)`** measures with Pillow font metrics
21
+ and bakes a fitting size into the XML *before* save.
22
+ 2. **`text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE`** lets
23
+ PowerPoint shrink at render time as a fallback.
24
+ 3. **`slide.lint()`** catches what slipped through; `auto_fix()`
25
+ nudges off-slide shapes back inside.
26
+
27
+ **Read `references/space-aware-authoring.md` first** if the user is
28
+ generating decks from any dynamic input. It's the reason this skill
29
+ exists.
30
+
31
+ The whole upstream 1.0.2 API still works — the rest of this skill
32
+ focuses on the post-fork additions because they're what's most often
33
+ missed by snippets pulled from the wider internet.
34
+
35
+ ## Cheat sheet (most common operations)
36
+
37
+ The 25 calls that cover ~90% of deck-generation tasks. Reach for
38
+ `references/geometry-and-arrows.md` for the full surface; this is the
39
+ working set:
40
+
41
+ ```python
42
+ from pptx2 import Presentation, BBox, audit
43
+ from pptx2.diagrams import horizontal_pipeline, hub_and_spoke, cycle
44
+ from pptx2.enum.shapes import MSO_SHAPE
45
+ from pptx2.util import Inches, Pt
46
+
47
+ # --- open / save ---
48
+ prs = Presentation() # new blank deck
49
+ prs = Presentation("file.pptx") # open existing
50
+ prs.save("out.pptx")
51
+
52
+ # --- slides ---
53
+ slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
54
+ slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
55
+
56
+ # --- geometry (BBox is splattable into add_*) ---
57
+ bb = BBox.from_inches(1, 2, 8, 4)
58
+ left, right = bb.split_h([1, 1], gap=Inches(0.2))
59
+ inner = bb.inset(all=Inches(0.2))
60
+
61
+ # --- n-up rows/grids: never hand-compute (avail - (n-1)*gap) / n ---
62
+ for cell in bb.columns(3, gap=Pt(16)): # equal columns; .rows(n) too
63
+ slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, *cell)
64
+
65
+ # --- text (one call) ---
66
+ slide.shapes.add_text(bb, text="Hello",
67
+ size_pt=24, bold=True,
68
+ color="#0B5CFF", align="center")
69
+
70
+ # --- native equation from LaTeX (pip install "python-pptx2[math]") ---
71
+ slide.shapes.add_equation(bb, latex=r"\frac{a}{b}", size_pt=28)
72
+ para = slide.shapes.add_text(bb, text="Euler: ").text_frame.paragraphs[0]
73
+ para.add_math(r"e^{i\pi}+1=0")
74
+
75
+ # --- shape with chainable colour ---
76
+ slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *bb) \
77
+ .fill_hex("#FFFFFF").line_hex("#0D0D0D", weight_pt=1.25)
78
+
79
+ # --- flat card: no theme drop-shadow, radius in points ---
80
+ card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, *inner)
81
+ card.shadow.clear() # kills the inherited effectRef too
82
+ card.corner_radius = Pt(6) # not adjustments[0]
83
+
84
+ # --- arrow with proper triangular head + auto edge routing ---
85
+ start_shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *left)
86
+ end_shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *right)
87
+ slide.shapes.add_arrow(start=start_shape, end=end_shape,
88
+ head="triangle", color="#0B5CFF", weight_pt=1.5)
89
+
90
+ # --- format-preserving text replacement (templated placeholders) ---
91
+ title_shape.set_text_preserving_format("New title")
92
+
93
+ # --- picture replacement (broken / sub-quality picture → native shapes) ---
94
+ picture.replace_with(lambda slide, bbox: ..., padding=Inches(0.1))
95
+
96
+ # --- diagram recipes ---
97
+ horizontal_pipeline(slide, bb, steps=["Extract", "Classify", "Enrich"])
98
+ hub_and_spoke(slide, bb, centre="Agent",
99
+ spokes=["Memory", "Tools", "Planning"])
100
+
101
+ # --- table styling without dropping to raw fill/font loops ---
102
+ table.format_cells(rows=0, fill="#1F2937", color="#FFFFFF", bold=True)
103
+ table.format_cells(rows=slice(1, None), size_pt=11, align="right")
104
+
105
+ # --- space-aware fit (returns the size it applied) ---
106
+ size = tf.fit_text(font_family="Inter", max_size=24)
107
+
108
+ # --- single-call cleanup before save ---
109
+ slide.tidy() # lints + safe auto-fixes
110
+
111
+ # --- tell the linter an overlap is deliberate (widest -> narrowest) ---
112
+ slide.lint_group_overlaps(card, accent, label) # one visual cluster
113
+ badge.allow_overlap_with(card) # exactly this one pair
114
+ card.layer, badge.layer_above = "card", "card" # also asserts z-order
115
+
116
+ # --- validate at save time (off by default) ---
117
+ prs.lint_on_save = "raise" # or "warn"; raises before writing
118
+
119
+ # --- whole-deck audit (markdown summary) ---
120
+ print(audit(prs).markdown())
121
+
122
+ # --- render thumbnails ---
123
+ from pptx2.render import render_slides
124
+ render_slides(prs, slides=[0, 1, 2], out_dir="thumbs",
125
+ name_template="slide-{:02d}.png")
126
+ ```
127
+
128
+ That whole sheet fits on one screen — it's the working set.
129
+
130
+ ## When to use this skill
131
+
132
+ - The user wants to **generate a deck** from Python or a JSON / dict spec
133
+ - The user is concerned about **text overflow** or **layout correctness**
134
+ in generated decks (lead with `space-aware-authoring.md`)
135
+ - The user wants to **add visual effects** (shadow, glow, soft edges,
136
+ blur, reflection, alpha) to shapes
137
+ - The user wants **animations**, **transitions**, or **motion paths**
138
+ - The user wants to **read or write a theme** (palette + fonts), or
139
+ apply one from a `.potx`
140
+ - The user wants to **lint / auto-fix** geometry issues
141
+ - The user wants to **import a slide** between decks or **apply a template**
142
+ - The user wants a **design system** (tokens, recipes, Grid/Stack layout)
143
+ - The user wants **chart palettes**, **quick layouts**, or per-series
144
+ gradient/pattern fills
145
+ - The user wants **slide thumbnails** rendered to PNG
146
+ - The user wants **3D** primitives (bevels / extrusion) or **SmartArt
147
+ text substitution**
148
+ - The user wants **native SVG embedding** with PNG fallback
149
+
150
+ ## Install
151
+
152
+ ```bash
153
+ pip install python-pptx2
154
+ ```
155
+
156
+ The `cairosvg` dependency is optional — install only if you want
157
+ `add_svg_picture(...)` to auto-rasterise the PNG fallback. `pyyaml` is
158
+ optional too — install only if you want `DesignTokens.from_yaml`.
159
+
160
+ ## Reference snippets
161
+
162
+ This skill ships a `references/` directory with focused recipe
163
+ collections. Read just the file you need — they're self-contained.
164
+
165
+ | File | What it covers |
166
+ |---|---|
167
+ | `references/space-aware-authoring.md` | **READ THIS FIRST.** Pre-flight measurement (`fit_text`, `TextFitter.best_fit_font_size`), `auto_size` flags, the linter, and a robust layout pattern. **Phase 2 + Phase 6 text-fit estimator.** |
168
+ | `references/geometry-and-arrows.md` | `BBox` value object (`columns`/`rows`/`split_h`/`grid`), `add_text` / `add_arrow` / `fill_hex` / `line_hex` convenience, `set_text_preserving_format`, `Picture.replace_with`, `Slide.tidy()`, diagram recipes (`horizontal_pipeline`, `hub_and_spoke`, `cycle`, `decision_tree`, `comparison_columns`), `audit(prs)`. **v2.8.** |
169
+ | `references/lint.md` | Detail on `slide.lint()`, issue types, `auto_fix`, and the `from_spec(..., lint="raise")` hook. **Phase 2.** |
170
+ | `references/design.md` | `DesignTokens`, `shape.style` facade, `Grid` / `Stack` layout primitives (geometry-safe placement), slide recipes (`title_slide`, `bullet_slide`, `kpi_slide`, `quote_slide`, `image_hero_slide`), starter pack. **Phase 9.** |
171
+ | `references/math.md` | Native PowerPoint equations from LaTeX (`add_equation`, `paragraph.add_math`). Requires `python-pptx2[math]`. **v2.13.** |
172
+ | `references/basics.md` | The 1.0.2 surface: `Presentation`, slides, placeholders, shapes, textboxes, tables, pictures, charts. Quick-reference cheatsheet. |
173
+ | `references/effects.md` | **The canonical fills-and-effects reference.** Shadow (including `shadow.clear()`), glow, soft edges, blur, reflection, alpha-tinted colors, gradient fills (linear / radial / rectangular / shape), line ends/caps/joins/compound. Other files link here rather than repeat it. **Phase 3 + Phase 6.** |
174
+ | `references/animations.md` | `Entrance` / `Exit` / `Emphasis` presets, triggers, by-paragraph reveal, sequencing context manager, motion paths. **Phase 5.** |
175
+ | `references/transitions.md` | Per-slide and deck-wide transitions including Morph and other `p14:` extensions. **Phase 4.** |
176
+ | `references/compose.md` | `from_spec` (JSON authoring with built-in lint), `import_slide`, `apply_template`. **Phase 2 + Phase 7.** |
177
+ | `references/theme.md` | Reading + writing the theme palette and fonts; `theme.apply(...)`; theme-aware color resolution via `pptx2.inherit.resolve_color`. **Phase 6 + Phase 7.** |
178
+ | `references/picture-effects.md` | Picture transparency / brightness / contrast / recolor (grayscale / sepia / washout / duotone) and SVG embedding. **Phase 6.** |
179
+ | `references/charts.md` | Chart palettes, quick layouts, per-series gradient/pattern fills, plus the inherited chart API. **Phase 10.** |
180
+ | `references/render.md` | Slide thumbnails via LibreOffice. **Phase 10.** |
181
+ | `references/three-d.md` | Bevels and extrusion via `shape.three_d`. **Phase 8.** |
182
+ | `references/smart-art.md` | Text substitution inside an existing template's SmartArt. **Phase 8.** |
183
+ | `references/tables.md` | The inherited table API, plus `cell.format(...)` / `table.format_cells(...)` styling, `Cell.borders`, and `fit_to_box`. |
184
+ | `references/end-to-end-deck.md` | A complete worked example: tokens, recipes, animations, transitions, charts, **and a lint pass before save**. |
185
+
186
+ ## Top-level imports beyond `Presentation`
187
+
188
+ These are stable package-root re-exports — prefer them over deeper
189
+ import paths:
190
+
191
+ ```python
192
+ from pptx2 import (
193
+ Presentation,
194
+ # Immutable rectangular region; splats into add_* APIs.
195
+ BBox,
196
+ # One-call deck audit (lint + picture + empty-slide + font checks).
197
+ audit, AuditReport,
198
+ # Figure adapters — Plotly / Matplotlib / SVG / HTML → slide picture.
199
+ # Third-party deps are imported lazily; missing deps surface a clear
200
+ # FigureBackendUnavailable with the right pip install command.
201
+ add_plotly_figure, add_matplotlib_figure,
202
+ add_svg_figure, add_html_figure,
203
+ FigureBackendUnavailable,
204
+ MathBackendUnavailable,
205
+ # Shape-level building blocks (token-driven; return small
206
+ # dataclasses exposing constituent shapes for further tweaks).
207
+ add_kpi_card, add_progress_bar,
208
+ KpiCard, ProgressBar,
209
+ )
210
+ ```
211
+
212
+ ## House rules for code you write
213
+
214
+ 1. **Always `from pptx2 import Presentation`** — never invent another
215
+ import path.
216
+ 2. **Default to space-aware patterns** for any text the user controls
217
+ at runtime: `fit_text` *or* `auto_size = TEXT_TO_FIT_SHAPE`, plus a
218
+ `slide.lint()` pass before save.
219
+ 3. **Reads should not mutate.** All effect / color / line proxies in
220
+ python-pptx2 return `None` for unset properties; assign `None` to
221
+ clear.
222
+ 4. **Use EMU through helpers**: `Inches`, `Pt`, `Emu`, `Cm` from
223
+ `pptx2.util`. Never write raw EMU integers when a helper exists.
224
+ 5. **Use `BBox` / `Grid` / `Stack` for placement** when you have more
225
+ than two shapes on a slide — they compute geometry from the slide's
226
+ real dimensions (or a region you hand them), so you can't
227
+ accidentally walk off the right edge, and there's no column
228
+ arithmetic to get wrong.
229
+ 6. **Prefer recipes for whole-slide layouts** when the user wants a
230
+ "good enough" pitch deck; drop down to direct `add_shape` /
231
+ `add_textbox` only when the recipes don't fit.
232
+ 7. **Save once at the end** — build the deck in memory, then call
233
+ `prs.save(...)`. Don't open and re-save inside loops.
234
+ 8. **For released-version constraints**: pin `python-pptx2>=2.8.0`
235
+ when generating requirements files — that's the minimum that
236
+ ships the `BBox`, `add_text`, `add_arrow`, `diagrams`, and
237
+ `audit` surface used in this skill.
238
+
239
+ ## A space-aware mini-template
240
+
241
+ The pattern you'll reach for most often:
242
+
243
+ ```python
244
+ from pptx2 import Presentation
245
+ from pptx2.enum.text import MSO_AUTO_SIZE
246
+ from pptx2.util import Inches
247
+
248
+ prs = Presentation()
249
+ slide = prs.slides.add_slide(prs.slide_layouts[5])
250
+ slide.shapes.title.text = "Q4 Review"
251
+
252
+ # Body box that has to swallow runtime-supplied text
253
+ box = slide.shapes.add_textbox(Inches(0.6), Inches(1.6),
254
+ Inches(12), Inches(5))
255
+ tf = box.text_frame
256
+ tf.word_wrap = True
257
+ tf.text = USER_SUPPLIED_BODY
258
+
259
+ # Belt: pick a determined size now using Pillow font metrics
260
+ tf.fit_text(font_family="Inter", max_size=24)
261
+
262
+ # Braces: let PowerPoint shrink on the way down if a user later edits
263
+ tf.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
264
+
265
+ # Catch anything that slipped through. auto_fix() mutates the slide
266
+ # (nudges OffSlide shapes in, autofits overflowing frames, restacks
267
+ # contradicted layer declarations) and refreshes report.issues itself.
268
+ slide.lint().auto_fix()
269
+ report = slide.lint()
270
+ errors = [i for i in report.issues if i.severity.value == "error"]
271
+ if errors:
272
+ raise RuntimeError("\n".join(str(e) for e in errors))
273
+
274
+ prs.save("out.pptx")
275
+ ```
276
+
277
+ ## Recent additions worth knowing
278
+
279
+ These changes ship after v2.5 and are easy to miss:
280
+
281
+ - **`Chart.recolour(palette)`** is the recommended single entry
282
+ point — auto-dispatches per chart type (per-point on pie /
283
+ doughnut, per-series otherwise). `apply_palette` warns and
284
+ routes when called on a doughnut.
285
+ - **`Chart.line_color`** and **`Chart.apply_dark_theme(text=, line=)`**
286
+ pin axis lines + gridlines for dark-deck styling.
287
+ - **Horizontal bar charts (`BAR_*`)** now default to top-to-bottom
288
+ reading order (`reverse_order=True`). Override with
289
+ `chart.category_axis.reverse_order = False` for legacy ordering.
290
+ Column charts are unaffected.
291
+ - **`anchor=` keyword on `add_picture` / `add_shape` / `add_textbox`**
292
+ collapses corner / centre placement to one call (see
293
+ `references/basics.md`).
294
+ - **`add_table(..., style="clean")`** disables every inherited style
295
+ flag — use it whenever you'll set custom cell borders or fills.
296
+ - **`add_kpi_card(slide, ...)` / `add_progress_bar(slide, ...)`** —
297
+ shape-level building blocks beneath the slide-level recipes
298
+ (see `references/design.md`).
299
+ - **`shape.shadow.clear()`** is the way to guarantee *no* shadow.
300
+ Clearing the individual shadow properties (or the deprecated
301
+ `shadow.inherit = False`) leaves the shape's `<a:effectRef idx="2"/>`
302
+ in place — a soft drop shadow in most themes — so cards keep a
303
+ phantom shadow nobody asked for. `clear()` drops the explicit shadow
304
+ elements *and* re-points the effect reference, keeping glow / soft
305
+ edges / blur / reflection intact.
306
+ - **`shape.corner_radius`** reads and writes a rounded rectangle's
307
+ radius as a real length (`card.corner_radius = Pt(6)`), instead of the
308
+ `adjustments[0]` fraction-of-the-shorter-side that has to be eyeballed
309
+ per shape size.
310
+ - **`BBox.columns(n, gap=...)` / `BBox.rows(n, gap=...)`** replace the
311
+ `(available - (n - 1) * gap) / n` loop for card rows, stat grids, and
312
+ panels. `Grid.from_box(box, cols=..., rows=...)` puts a full grid over
313
+ a region rather than the whole slide.
314
+ - **`cell.format(...)` / `table.format_cells(rows=..., cols=..., ...)`**
315
+ style table cells with the same keywords as `add_text` (`fill`,
316
+ `color`, `bold`, `size_pt`, `align`, `anchor`, `margin`) — no more
317
+ `cell.fill.solid(); cell.fill.fore_color.rgb = ...` loops.
318
+ - **`fit_text` is explicit about fallback metrics.** It returns the size
319
+ it applied, and warns (`FontMetricsWarning`) when a *named* family
320
+ isn't installed and measurement silently drops to Pillow's default
321
+ font — naming one is what makes it audible, so omitting `font_family`
322
+ stays quiet while `font_family="Calibri"` does not. `strict=True`
323
+ raises instead; `pptx2.text.fonts.font_is_installed("Inter")`
324
+ checks up front.
325
+ - **Float coordinates from arithmetic are coerced** at constructor
326
+ entry and at `shape.left/top/width/height` setters, so
327
+ `(Inches(N) - gutter) / 2` style expressions can be passed straight
328
+ through. Pre-2.6.1 these produced float-valued `<a:off>` / `<a:ext>`
329
+ attributes that PowerPoint rejected with the "Repair?" dialog.
330
+
331
+ ## Anti-patterns to avoid
332
+
333
+ LLM-generated python-pptx2 code falls into the same handful of traps.
334
+ Flagging them up front saves the trial-and-error round.
335
+
336
+ - **Don't** access `tf.paragraphs` twice and compare wrapper objects
337
+ with `is`. The property returns *fresh* `_Paragraph` objects every
338
+ call, so `p is not para` is always true — your filter will remove
339
+ the wrong paragraph. Use `set_text_preserving_format(new_text)` for
340
+ the common "replace text, keep formatting" case.
341
+ - **Don't** assume `add_connector(MSO_CONNECTOR.STRAIGHT, ...)` puts
342
+ an arrowhead on the line. It produces a bare line. Use
343
+ `slide.shapes.add_arrow(start, end, head="triangle")` — it sets the
344
+ arrowhead, inset, edge routing, and colour in one call.
345
+ - **Don't** size a diagram to a broken picture's bbox when there's an
346
+ enclosing card. The card area is what you want. Use
347
+ `picture.enclosing_container()` to find the right box, then
348
+ `picture.replace_with(builder)`.
349
+ - **Don't** delete a picture and then assume sibling shape indexes
350
+ are stable. Process in reverse index order, or capture the shapes
351
+ to mutate *before* iterating.
352
+ - **Don't** import `RGBColor` / `PP_ALIGN` / `MSO_VERTICAL_ANCHOR`
353
+ for every styling call. Use the hex-string and short-name kwargs:
354
+ `slide.shapes.add_text(bb, text="…", color="#0B5CFF", align="center",
355
+ anchor="middle")`. Hex strings, tuples, and `RGBColor` all work
356
+ everywhere a colour is accepted.
357
+ - **Don't** write raw EMU integers. `BBox.from_inches(1, 2, 8, 4)`
358
+ for regions, `Inches(1)` / `Pt(12)` for individual lengths. Float
359
+ arithmetic on EMU is fine — coordinates are coerced at the setter.
360
+ - **Don't** lint + auto_fix + lint again to clear safe issues. Use
361
+ `slide.tidy()` — it's the one-call wrapper.
362
+ - **Don't** hand-roll `col_w = (avail - (n - 1) * gap) / n` and a running
363
+ cursor for card rows or stat grids. `bb.columns(n, gap=Pt(16))` (and
364
+ `bb.rows(...)`, `bb.grid(cols, rows)`, `Grid.from_box(bb, cols=...)`)
365
+ return exact, drift-free boxes.
366
+ - **Don't** try to remove a shadow by assigning `None` to
367
+ `shadow.blur_radius` / `distance`, or by `shadow.inherit = False`.
368
+ Neither touches the theme effect style, so the shadow is still
369
+ rendered. Use `shape.shadow.clear()` — the only call that does.
370
+ - **Don't** set a corner radius by guessing `adjustments[0]` (it's a
371
+ fraction of the shorter side, so the same value means a different
372
+ radius on every differently-sized card). Use
373
+ `shape.corner_radius = Pt(6)`.
374
+ - **Don't** invent OMML / `a14:m` XML, and don't rasterise ordinary
375
+ formulas with matplotlib just to get them on a slide. Use
376
+ `slide.shapes.add_equation(bb, latex=r"\frac{a}{b}")` or
377
+ `paragraph.add_math(...)` (`pip install "python-pptx2[math]"`).
378
+ - **Don't** style table cells with `cell.fill.solid()` +
379
+ `cell.fill.fore_color.rgb` + per-run font loops. Use
380
+ `cell.format(...)` / `table.format_cells(...)` — and note that a
381
+ cell's anchor and insets belong on the cell, not on its text frame.
382
+ - **Don't** trust `fit_text` when the font isn't installed. The
383
+ measurement falls back to Pillow's default metrics and the result is
384
+ an estimate — bundle the `.ttf` and pass `font_file=`, or pass
385
+ `strict=True` so the build fails instead of shipping a guess.
386
+
387
+ ## Common pitfalls
388
+
389
+ - **Calling `shape.shadow.inherit`** raises `DeprecationWarning`. Read
390
+ individual properties (`blur_radius`, `distance`, `direction`,
391
+ `color`) and check for `None` instead; to *remove* a shadow call
392
+ `shape.shadow.clear()`.
393
+ - **`fit_text` degrades to a best guess for uninstalled fonts.** Font
394
+ metrics come from the machine running the build, so a brand face that
395
+ isn't installed (the usual case in a container or CI) is measured with
396
+ Pillow's default font. Check with
397
+ `pptx2.text.fonts.font_is_installed(...)`, pass `font_file=` with
398
+ the real `.ttf`, or use `strict=True`. `slide.lint()` is unaffected —
399
+ its overflow check is font-agnostic — which is why the lint pass is
400
+ worth keeping even when the metrics are exact.
401
+ - **Bare-int sizes in `DesignTokens` typography** are interpreted as
402
+ **EMU**, not points. Use floats (`44.0`) or `Pt(44)` to mean
403
+ 44-point font.
404
+ - **Recipes use the Blank layout**, so `slide.shapes.title` is `None`.
405
+ Address shapes by index (`slide.shapes[0]`, `slide.shapes[1]`, …).
406
+ - **`add_svg_picture` without `cairosvg` and without a `png_fallback`**
407
+ raises `CairoSvgUnavailable`. Either install cairosvg or supply a
408
+ pre-rasterised PNG.
409
+ - **`auto_fix()` repairs geometry, not judgment.** It clamps `OffSlide`,
410
+ flips `TextOverflow` frames to `TEXT_TO_FIT_SHAPE`, snaps
411
+ `OffGridDrift`, and restacks `LayerOrderViolation`. It will never
412
+ touch `ShapeCollision`, `LowContrast`, `MinFontSize` or
413
+ `ZOrderAnomaly` — those need a designer. Prefer `tf.fit_text(...)`
414
+ *before* save over relying on the overflow fix.
415
+ - **Slide thumbnails require `soffice` on PATH** (LibreOffice).
416
+ Otherwise you get `ThumbnailRendererUnavailable`.
417
+ - **`MSO_PATTERN_TYPE.ERCENT_40`** is the upstream typo and emits a
418
+ `DeprecationWarning`. Use `PERCENT_40`.
419
+ - **Calling `chart.apply_palette` on a pie / doughnut** emits a
420
+ `UserWarning` and routes through `color_by_category`. Use
421
+ `chart.recolour(palette)` directly to silence it.
422
+
423
+ ## Where to look in the project
424
+
425
+ If the user has the `python-pptx2` repo checked out alongside this
426
+ skill, these paths are useful for source-of-truth lookup:
427
+
428
+ - `src/pptx2/lint.py` — `SlideLintReport`, `TextOverflow`, `OffSlide`,
429
+ `ShapeCollision`, `LayerOrderViolation`, `LintSeverity`.
430
+ - `src/pptx2/shapes/base.py` — `lint_group`, `lint_skip`,
431
+ `allow_overlap_with`, `layer` / `layer_above` (overlap intent).
432
+ - `src/pptx2/text/text.py`, `src/pptx2/text/layout.py` — `fit_text`,
433
+ `TextFitter`, `_best_fit_font_size`.
434
+ - `src/pptx2/animation.py` — `Entrance`, `Exit`, `Emphasis`,
435
+ `MotionPath`, `SlideAnimations`.
436
+ - `src/pptx2/compose/` — `from_spec`, plus the `import_slide` /
437
+ `apply_template` re-exports.
438
+ - `src/pptx2/theme.py`, `src/pptx2/inherit.py` — theme reader/writer and
439
+ `resolve_color`.
440
+ - `src/pptx2/dml/effect.py`, `src/pptx2/dml/picture.py`,
441
+ `src/pptx2/dml/line.py` — Phase 3/6 visual effects, picture filters,
442
+ line-end formatting.
443
+ - `src/pptx2/design/` — `tokens`, `style`, `layout`, `recipes`.
444
+ - `src/pptx2/chart/palettes.py`, `src/pptx2/chart/quick_layouts.py`.
445
+ - `src/pptx2/render.py` — slide-thumbnail renderer.
446
+ - `src/pptx2/smart_art.py`, `src/pptx2/_svg.py`.
447
+ - `examples/starter_pack/` — three example token sets and a build script.
448
+
449
+ The user-facing Sphinx documentation under `docs/user/` mirrors the
450
+ sections in this skill and is a good source of additional prose.
@@ -0,0 +1,78 @@
1
+ """Bundled Claude Code skill for python-pptx2.
2
+
3
+ Ships the ``SKILL.md`` and reference markdown that drive the Claude
4
+ Code (and Claude Agent SDK) ``python-pptx2`` skill, so that pip-installing
5
+ python-pptx2 is enough to make the skill available wherever the library
6
+ runs.
7
+
8
+ Typical usage from a shell::
9
+
10
+ # Install into the current user's Claude skills directory
11
+ python -m pptx2.skill install
12
+
13
+ # Or just print where the skill files live inside the package
14
+ python -m pptx2.skill path
15
+
16
+ Programmatic usage::
17
+
18
+ from pptx2.skill import skill_root, install_skill
19
+
20
+ src = skill_root() # pathlib.Path inside the package
21
+ dest = install_skill() # default: ~/.claude/skills/python-pptx2
22
+ dest = install_skill(target="/some/other/dir/python-pptx2")
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import shutil
28
+ from pathlib import Path
29
+ from typing import Optional
30
+
31
+ __all__ = ["skill_root", "install_skill", "DEFAULT_INSTALL_DIR"]
32
+
33
+
34
+ #: Default destination directory for ``install_skill``.
35
+ #:
36
+ #: Matches the convention that Claude Code uses for user-level skills.
37
+ #: Override by passing ``target=`` to :func:`install_skill`.
38
+ DEFAULT_INSTALL_DIR = Path.home() / ".claude" / "skills" / "python-pptx2"
39
+
40
+
41
+ def skill_root() -> Path:
42
+ """Return the directory inside the installed package that holds the skill.
43
+
44
+ Contains ``SKILL.md`` and a ``references/`` subdirectory. This is
45
+ a regular filesystem path even when the package is installed inside
46
+ a wheel, because setuptools copies ``package_data`` files onto disk.
47
+ """
48
+ return Path(__file__).resolve().parent
49
+
50
+
51
+ def install_skill(
52
+ target: Optional[Path] = None, *, overwrite: bool = True
53
+ ) -> Path:
54
+ """Copy the bundled skill to *target* (default :data:`DEFAULT_INSTALL_DIR`).
55
+
56
+ Returns the destination directory. When *overwrite* is ``True``
57
+ (the default), an existing skill at *target* is replaced; pass
58
+ ``overwrite=False`` to raise :class:`FileExistsError` instead.
59
+ """
60
+ src = skill_root()
61
+ dest = Path(target) if target is not None else DEFAULT_INSTALL_DIR
62
+
63
+ if dest.exists():
64
+ if not overwrite:
65
+ raise FileExistsError(
66
+ f"refusing to overwrite existing skill at {dest!s}; "
67
+ "pass overwrite=True or remove it first"
68
+ )
69
+ shutil.rmtree(dest)
70
+
71
+ dest.parent.mkdir(parents=True, exist_ok=True)
72
+ # Copy SKILL.md and references/ but not __init__.py / __main__.py.
73
+ dest.mkdir()
74
+ shutil.copy2(src / "SKILL.md", dest / "SKILL.md")
75
+ refs_src = src / "references"
76
+ if refs_src.is_dir():
77
+ shutil.copytree(refs_src, dest / "references")
78
+ return dest
@@ -0,0 +1,64 @@
1
+ """Command-line entry point for the bundled Claude skill.
2
+
3
+ Usage::
4
+
5
+ python -m pptx2.skill install [TARGET] # copy skill into target dir
6
+ python -m pptx2.skill path # print on-disk skill source
7
+ python -m pptx2.skill # same as `path`
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ from pptx2.skill import DEFAULT_INSTALL_DIR, install_skill, skill_root
17
+
18
+
19
+ def main(argv: list[str] | None = None) -> int:
20
+ parser = argparse.ArgumentParser(
21
+ prog="python -m pptx2.skill",
22
+ description=(
23
+ "Manage the bundled python-pptx2 Claude skill. "
24
+ "Ships SKILL.md and reference docs to your Claude skills directory."
25
+ ),
26
+ )
27
+ sub = parser.add_subparsers(dest="cmd")
28
+
29
+ install = sub.add_parser(
30
+ "install", help="copy the bundled skill into a Claude skills directory"
31
+ )
32
+ install.add_argument(
33
+ "target",
34
+ nargs="?",
35
+ type=Path,
36
+ default=None,
37
+ help=f"destination directory (default: {DEFAULT_INSTALL_DIR})",
38
+ )
39
+ install.add_argument(
40
+ "--no-overwrite",
41
+ action="store_true",
42
+ help="error out if the destination already exists",
43
+ )
44
+
45
+ sub.add_parser("path", help="print the package-internal skill directory")
46
+
47
+ args = parser.parse_args(argv)
48
+
49
+ if args.cmd == "install":
50
+ try:
51
+ dest = install_skill(args.target, overwrite=not args.no_overwrite)
52
+ except FileExistsError as exc:
53
+ print(str(exc), file=sys.stderr)
54
+ return 1
55
+ print(f"installed python-pptx2 skill -> {dest}")
56
+ return 0
57
+
58
+ # default + `path`
59
+ print(skill_root())
60
+ return 0
61
+
62
+
63
+ if __name__ == "__main__":
64
+ raise SystemExit(main())