Skip to content

Commit 786018f

Browse files
committed
feat: add shape.
1 parent 571e501 commit 786018f

File tree

5 files changed

+174
-3
lines changed

5 files changed

+174
-3
lines changed

examples/add_shape_example.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Example of adding shapes with background and text."""
2+
3+
from pathlib import Path
4+
5+
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
6+
from pptx.util import Inches
7+
8+
import tppt
9+
10+
11+
def main():
12+
"""Create a PowerPoint with box shapes that have background color and text."""
13+
tppt.Presentation.builder().slide(
14+
lambda slide: slide.BlankLayout()
15+
.builder()
16+
.add_shape(
17+
MSO_AUTO_SHAPE_TYPE.RECTANGLE,
18+
lambda shape: (
19+
shape.fill.solid().fore_color.set_rgb("#0066ff"),
20+
shape.set_text("Sample Box with Text"),
21+
)[-1],
22+
left=Inches(1),
23+
top=Inches(1),
24+
width=Inches(4),
25+
height=Inches(2),
26+
)
27+
.add_shape(
28+
MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE,
29+
lambda shape: (
30+
shape.fill.solid().fore_color.set_rgb("#ff6600"),
31+
shape.set_text("Rounded Box"),
32+
)[-1],
33+
left=Inches(6),
34+
top=Inches(1),
35+
width=Inches(3),
36+
height=Inches(1.5),
37+
)
38+
.add_shape(
39+
MSO_AUTO_SHAPE_TYPE.OVAL,
40+
lambda shape: (
41+
shape.fill.solid().fore_color.set_rgb("#00ff00"),
42+
shape.set_text("Circle"),
43+
)[-1],
44+
left=Inches(1),
45+
top=Inches(4),
46+
width=Inches(2),
47+
height=Inches(2),
48+
)
49+
).save(Path(__file__).with_suffix(".pptx"))
50+
51+
52+
if __name__ == "__main__":
53+
main()

src/tppt/pptx/dml/color.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Literal, assert_never, cast
1+
from typing import Literal, Self, assert_never, cast
22

33
from lxml.etree import _Element
44
from pptx.dml.color import ColorFormat as PptxColorFormat
@@ -92,6 +92,11 @@ def brightness(self) -> float:
9292
def brightness(self, value: float) -> None:
9393
self._pptx.brightness = value
9494

95+
def set_brightness(self, value: float) -> Self:
96+
"""Set brightness value and return self for method chaining."""
97+
self.brightness = value
98+
return self
99+
95100
@property
96101
def rgb(self) -> Color:
97102
solid_fill = cast(
@@ -118,6 +123,11 @@ def rgb(self, color: Color | LiteralColor | PptxRGBColor):
118123
if alpha := srgbClr.find("a:alpha", namespace):
119124
srgbClr.remove(alpha)
120125

126+
def set_rgb(self, color: Color | LiteralColor | PptxRGBColor) -> Self:
127+
"""Set RGB color value and return self for method chaining."""
128+
self.rgb = color
129+
return self
130+
121131
@property
122132
def theme_color(self) -> MSO_THEME_COLOR | None:
123133
"""Theme color value of this color.
@@ -133,3 +143,10 @@ def theme_color(self) -> MSO_THEME_COLOR | None:
133143
@theme_color.setter
134144
def theme_color(self, value: LiteralThemeColor | MSO_THEME_COLOR | None) -> None:
135145
self._pptx.theme_color = to_pptx_theme_color(value)
146+
147+
def set_theme_color(
148+
self, value: LiteralThemeColor | MSO_THEME_COLOR | None
149+
) -> Self:
150+
"""Set theme color value and return self for method chaining."""
151+
self.theme_color = value
152+
return self

src/tppt/pptx/dml/fill.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ def pattern(self) -> MSO_PATTERN_TYPE:
3838
def pattern(self, value: MSO_PATTERN_TYPE) -> None:
3939
self._pptx.pattern = value
4040

41+
def set_pattern(self, value: MSO_PATTERN_TYPE) -> "FillFormat":
42+
"""Set pattern."""
43+
self.pattern = value
44+
return self
45+
4146
def patterned(self) -> "PattFill":
4247
"""Set the fill type to patterned."""
4348
self._pptx.patterned()
@@ -78,6 +83,11 @@ def gradient_angle(self) -> Angle | None:
7883
def gradient_angle(self, value: Angle) -> None:
7984
self._pptx.gradient_angle = to_pptx_angle(value)
8085

86+
def set_gradient_angle(self, value: Angle) -> "GradFill":
87+
"""Set gradient angle."""
88+
self.gradient_angle = value
89+
return self
90+
8191
@property
8292
def gradient_stops(self) -> "GradientStops":
8393
"""Gradient stops."""
@@ -110,6 +120,11 @@ def pattern(self) -> MSO_PATTERN_TYPE:
110120
def pattern(self, value: LiteralPatternType | MSO_PATTERN_TYPE) -> None:
111121
self._pptx.pattern = to_pptx_pattern_type(value)
112122

123+
def set_pattern(self, value: LiteralPatternType | MSO_PATTERN_TYPE) -> "PattFill":
124+
"""Set pattern."""
125+
self.pattern = value
126+
return self
127+
113128

114129
class SolidFill(Fill[PptxSolidFill]):
115130
"""Solid fill."""
@@ -152,3 +167,8 @@ def position(self) -> float:
152167
@position.setter
153168
def position(self, value: float) -> None:
154169
self._pptx.position = value
170+
171+
def set_position(self, value: float) -> "GradientStop":
172+
"""Set position."""
173+
self.position = value
174+
return self

src/tppt/pptx/shape/__init__.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Shape wrapper implementation."""
22

3-
from typing import TYPE_CHECKING, TypedDict
3+
from typing import TYPE_CHECKING, Self, TypedDict
44

55
from pptx.shapes import Subshape as PptxSubshape
66
from pptx.shapes.autoshape import Shape as PptxShape
@@ -66,6 +66,11 @@ def height(self) -> Length:
6666
def height(self, value: Length | LiteralLength | PptxLength) -> None:
6767
self._pptx.height = to_pptx_length(value)
6868

69+
def set_height(self, value: Length | LiteralLength | PptxLength) -> Self:
70+
"""Set height value and return self for method chaining."""
71+
self.height = value
72+
return self
73+
6974
@property
7075
def left(self) -> Length:
7176
return to_length(self._pptx.left)
@@ -74,6 +79,11 @@ def left(self) -> Length:
7479
def left(self, value: Length | LiteralLength | PptxLength) -> None:
7580
self._pptx.left = to_pptx_length(value)
7681

82+
def set_left(self, value: Length | LiteralLength | PptxLength) -> Self:
83+
"""Set left position value and return self for method chaining."""
84+
self.left = value
85+
return self
86+
7787
@property
7888
def name(self) -> str:
7989
return self._pptx.name
@@ -82,6 +92,11 @@ def name(self) -> str:
8292
def name(self, value: str) -> None:
8393
self._pptx.name = value
8494

95+
def set_name(self, value: str) -> Self:
96+
"""Set name value and return self for method chaining."""
97+
self.name = value
98+
return self
99+
85100
@property
86101
def part(self) -> "PptxBaseSlidePart":
87102
return self._pptx.part
@@ -98,6 +113,11 @@ def rotation(self) -> float:
98113
def rotation(self, value: float) -> None:
99114
self._pptx.rotation = value
100115

116+
def set_rotation(self, value: float) -> Self:
117+
"""Set rotation value and return self for method chaining."""
118+
self.rotation = value
119+
return self
120+
101121
@property
102122
def shadow(self) -> "ShadowFormat":
103123
from tppt.pptx.dml.effect import ShadowFormat
@@ -120,6 +140,11 @@ def top(self) -> Length:
120140
def top(self, value: Length | LiteralLength | PptxLength) -> None:
121141
self._pptx.top = to_pptx_length(value)
122142

143+
def set_top(self, value: Length | LiteralLength | PptxLength) -> Self:
144+
"""Set top position value and return self for method chaining."""
145+
self.top = value
146+
return self
147+
123148
@property
124149
def width(self) -> Length:
125150
return to_length(self._pptx.width)
@@ -128,6 +153,11 @@ def width(self) -> Length:
128153
def width(self, value: Length | LiteralLength | PptxLength) -> None:
129154
self._pptx.width = to_pptx_length(value)
130155

156+
def set_width(self, value: Length | LiteralLength | PptxLength) -> Self:
157+
"""Set width value and return self for method chaining."""
158+
self.width = value
159+
return self
160+
131161

132162
class Shape(BaseShape[GenericPptxShape]):
133163
@property
@@ -158,6 +188,11 @@ def text(self) -> str:
158188
def text(self, text: str) -> None:
159189
self._pptx.text = text
160190

191+
def set_text(self, text: str) -> Self:
192+
"""Set text value and return self for method chaining."""
193+
self.text = text
194+
return self
195+
161196
@property
162197
def text_frame(self) -> "TextFrame":
163198
from tppt.pptx.text.text_frame import TextFrame

src/tppt/pptx/slide.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@
2727
from tppt.types import FilePath
2828

2929
from .converter import PptxConvertible, to_pptx_length
30-
from .shape import BaseShape, RangeProps
30+
from .shape import BaseShape, RangeProps, Shape
3131
from .shape.picture import Picture, PictureData, PictureProps
3232
from .shape.placeholder import SlidePlaceholder
3333
from .shape.text import Text, TextData, TextProps
3434
from .slide_layout import SlideLayout
3535
from .table.table import DataFrame, Table, TableData, TableProps, dataframe2list
3636

3737
if TYPE_CHECKING:
38+
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
39+
3840
from tppt.pptx.shape.background import Background
3941

4042
from .notes_slide import NotesSlide
@@ -355,6 +357,50 @@ def _register(slide: Slide) -> Chart:
355357

356358
return self
357359

360+
@overload
361+
def add_shape(
362+
self,
363+
shape_type: "MSO_AUTO_SHAPE_TYPE",
364+
/,
365+
**kwargs: Unpack[RangeProps],
366+
) -> Self: ...
367+
368+
@overload
369+
def add_shape(
370+
self,
371+
shape_type: "MSO_AUTO_SHAPE_TYPE",
372+
shape: Callable[[Shape], Shape],
373+
/,
374+
**kwargs: Unpack[RangeProps],
375+
) -> Self: ...
376+
377+
def add_shape(
378+
self,
379+
shape_type: "MSO_AUTO_SHAPE_TYPE",
380+
shape: Callable[[Shape], Shape] | None = None,
381+
/,
382+
**kwargs: Unpack[RangeProps],
383+
) -> Self:
384+
"""Add a shape to the slide."""
385+
386+
def _register(slide: Slide) -> Shape:
387+
shape_obj = Shape(
388+
slide.to_pptx().shapes.add_shape(
389+
shape_type,
390+
to_pptx_length(kwargs["left"]),
391+
to_pptx_length(kwargs["top"]),
392+
to_pptx_length(kwargs["width"]),
393+
to_pptx_length(kwargs["height"]),
394+
)
395+
)
396+
if shape is not None:
397+
return shape(shape_obj)
398+
else:
399+
return shape_obj
400+
401+
self._shape_registry.append(_register)
402+
return self
403+
358404
def _build(self, slide: PptxSlide) -> Slide:
359405
tppt_slide = Slide(slide)
360406

0 commit comments

Comments
 (0)