Skip to content

Commit d3546e2

Browse files
committed
refactor: enhance text and color handling with builder pattern support and new properties
1 parent 61ce349 commit d3546e2

File tree

8 files changed

+169
-134
lines changed

8 files changed

+169
-134
lines changed

examples/color_text.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Simple example of using tppt."""
2+
3+
from pathlib import Path
4+
5+
import tppt
6+
7+
EXAMPLE_DIR = Path(__file__).parent
8+
9+
10+
def main():
11+
"""Run the example."""
12+
# Create a presentation using the builder pattern
13+
presentation = (
14+
tppt.Presentation.builder()
15+
.slide(
16+
lambda slide: slide.BlankLayout()
17+
.builder()
18+
.text(
19+
lambda text: text.builder().text_frame(
20+
lambda text_frame: text_frame.builder().paragraph(
21+
lambda paragraph: paragraph.builder().run(
22+
lambda run: run.builder()
23+
.text("Hello, world!")
24+
.font(
25+
lambda font: font.builder().color(
26+
lambda color: color.builder().rgb("#0000FF")
27+
)
28+
)
29+
)
30+
),
31+
),
32+
left=(50, "pt"),
33+
top=(50, "pt"),
34+
width=(400, "pt"),
35+
height=(50, "pt"),
36+
)
37+
)
38+
.build()
39+
)
40+
41+
# Save the presentation
42+
presentation.save(Path(__file__).with_suffix(".pptx"))
43+
44+
print("Rich Text presentation created successfully!")
45+
46+
47+
if __name__ == "__main__":
48+
main()

examples/presentation_tree.json

Lines changed: 3 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -7,68 +7,11 @@
77
"slides": [
88
{
99
"slide_id": 256,
10-
"slide_layout_name": "Title Slide",
10+
"slide_layout_name": "Blank",
1111
"shapes": [
1212
{
13-
"name": "Title 1",
13+
"name": "TextBox 1",
1414
"shape_id": 2,
15-
"shape_type": "PLACEHOLDER (14)",
16-
"has_text_frame": true,
17-
"has_table": false,
18-
"has_chart": false,
19-
"width": 612.0,
20-
"height": 115.75,
21-
"rotation": 0.0,
22-
"left": 54.0,
23-
"top": 167.75,
24-
"placeholder_type": 3,
25-
"placeholder_idx": 0,
26-
"text_frame": {
27-
"text": "Hello, world!",
28-
"paragraphs": [
29-
{
30-
"text": "Hello, world!",
31-
"level": 0,
32-
"runs": [
33-
{
34-
"text": "Hello, world!",
35-
"font": {
36-
"color": {}
37-
}
38-
}
39-
]
40-
}
41-
]
42-
}
43-
},
44-
{
45-
"name": "Subtitle 2",
46-
"shape_id": 3,
47-
"shape_type": "PLACEHOLDER (14)",
48-
"has_text_frame": true,
49-
"has_table": false,
50-
"has_chart": false,
51-
"width": 504.0,
52-
"height": 138.0,
53-
"rotation": 0.0,
54-
"left": 108.0,
55-
"top": 306.0,
56-
"placeholder_type": 4,
57-
"placeholder_idx": 1,
58-
"text_frame": {
59-
"text": "",
60-
"paragraphs": [
61-
{
62-
"text": "",
63-
"level": 0,
64-
"runs": []
65-
}
66-
]
67-
}
68-
},
69-
{
70-
"name": "TextBox 3",
71-
"shape_id": 4,
7215
"shape_type": "TEXT_BOX (17)",
7316
"has_text_frame": true,
7417
"has_table": false,
@@ -97,65 +40,7 @@
9740
}
9841
}
9942
],
100-
"placeholders": [
101-
{
102-
"name": "Title 1",
103-
"shape_id": 2,
104-
"shape_type": "PLACEHOLDER (14)",
105-
"has_text_frame": true,
106-
"has_table": false,
107-
"has_chart": false,
108-
"width": 612.0,
109-
"height": 115.75,
110-
"rotation": 0.0,
111-
"left": 54.0,
112-
"top": 167.75,
113-
"placeholder_type": 3,
114-
"placeholder_idx": 0,
115-
"text_frame": {
116-
"text": "Hello, world!",
117-
"paragraphs": [
118-
{
119-
"text": "Hello, world!",
120-
"level": 0,
121-
"runs": [
122-
{
123-
"text": "Hello, world!",
124-
"font": {
125-
"color": {}
126-
}
127-
}
128-
]
129-
}
130-
]
131-
}
132-
},
133-
{
134-
"name": "Subtitle 2",
135-
"shape_id": 3,
136-
"shape_type": "PLACEHOLDER (14)",
137-
"has_text_frame": true,
138-
"has_table": false,
139-
"has_chart": false,
140-
"width": 504.0,
141-
"height": 138.0,
142-
"rotation": 0.0,
143-
"left": 108.0,
144-
"top": 306.0,
145-
"placeholder_type": 4,
146-
"placeholder_idx": 1,
147-
"text_frame": {
148-
"text": "",
149-
"paragraphs": [
150-
{
151-
"text": "",
152-
"level": 0,
153-
"runs": []
154-
}
155-
]
156-
}
157-
}
158-
],
43+
"placeholders": [],
15944
"notes_slide": {
16045
"shapes": [
16146
{

src/tppt/pptx/dml/color.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ColorFormatBuilder:
2525
def __init__(self, pptx_obj: PptxColorFormat) -> None:
2626
self._pptx = pptx_obj
2727

28-
def color(self, color: Color | LiteralColor) -> Self:
28+
def rgb(self, color: Color | LiteralColor) -> Self:
2929
self._pptx.rgb = to_pptx_rgb_color(color)
3030

3131
return self

src/tppt/pptx/shape/text.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from typing import Literal, NotRequired, Self
1+
from typing import Callable, Literal, NotRequired, Self
22

33
from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, PP_ALIGN
44
from pptx.shapes.autoshape import Shape as PptxShape
55

66
from tppt.pptx.converter import to_pptx_length, to_pptx_rgb_color
7-
from tppt.pptx.text.text_frame import TextFrame
7+
from tppt.pptx.text.text_frame import TextFrame, TextFrameBuilder
88
from tppt.types._color import Color, LiteralColor
99
from tppt.types._length import Length, LiteralLength
1010

@@ -70,8 +70,8 @@ def __init__(self, pptx_obj: PptxShape, data: TextData | None = None, /) -> None
7070

7171
self._pptx = pptx_obj
7272

73-
def text_frame(self) -> TextFrame:
74-
return TextFrame(self._pptx.text_frame)
73+
def builder(self) -> "TextBuilder":
74+
return TextBuilder(self._pptx)
7575

7676
def to_pptx(self) -> PptxShape:
7777
"""Convert to pptx shape."""
@@ -81,3 +81,20 @@ def to_pptx(self) -> PptxShape:
8181
def from_pptx(cls, pptx_obj: PptxShape) -> Self:
8282
"""Create from pptx shape."""
8383
return cls(pptx_obj)
84+
85+
86+
class TextBuilder:
87+
def __init__(self, pptx_obj: PptxShape) -> None:
88+
self._pptx = pptx_obj
89+
90+
def text_frame(
91+
self, callable: Callable[[TextFrame], TextFrame | TextFrameBuilder]
92+
) -> Self:
93+
text_frame = callable(TextFrame(self._pptx.text_frame))
94+
if isinstance(text_frame, TextFrameBuilder):
95+
text_frame._build()
96+
97+
return self
98+
99+
def _build(self) -> Text:
100+
return Text(self._pptx)

src/tppt/pptx/slide.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
"""Slide wrapper implementation."""
22

33
import os
4-
from typing import IO, TYPE_CHECKING, Any, Callable, Self, Unpack
4+
from typing import IO, TYPE_CHECKING, Any, Callable, Self, Unpack, overload
55

66
from pptx.slide import Slide as PptxSlide
77

88
from tppt.types import FilePath
99

1010
from .converter import PptxConvertible, to_pptx_length
1111
from .placeholder import SlidePlaceholder
12-
from .shape import Shape
12+
from .shape import RangeProps, Shape
1313
from .shape.picture import Picture, PictureData, PictureProps
1414
from .shape.table import DataFrame, Table, TableData, TableProps, dataframe2list
15-
from .shape.text import Text, TextData, TextProps
15+
from .shape.text import Text, TextBuilder, TextData, TextProps
1616
from .slide_layout import SlideLayout
1717
from .snotes_slide import NotesSlide
1818

@@ -91,8 +91,27 @@ def __init__(
9191
self._shape_registry: list[Callable[[Slide], Shape[Any]]] = []
9292
self._placeholder_registry = placeholder_registry
9393

94-
def text(self, text: str, **kwargs: Unpack[TextProps]) -> Self:
95-
data = TextData(type="text", text=text, **kwargs)
94+
@overload
95+
def text(self, text: str, **kwargs: Unpack[TextProps]) -> Self: ...
96+
97+
@overload
98+
def text(
99+
self, text: Callable[[Text], Text | TextBuilder], **kwargs: Unpack[RangeProps]
100+
) -> Self: ...
101+
102+
def text(
103+
self,
104+
text: str | Callable[[Text], Text | TextBuilder],
105+
**kwargs: Unpack[TextProps],
106+
) -> Self:
107+
data = TextData(
108+
type="text",
109+
text=text if isinstance(text, str) else "",
110+
top=kwargs["top"],
111+
left=kwargs["left"],
112+
width=kwargs["width"],
113+
height=kwargs["height"],
114+
)
96115

97116
self._shape_registry.append(
98117
lambda slide: Text(

src/tppt/pptx/text/font.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,56 @@
1-
from typing import Self
1+
from typing import Callable, Self
22

33
from pptx.text.text import Font as PptxFont
44

5-
from tppt.pptx.converter import PptxConvertible, to_pptx_length
6-
from tppt.pptx.dml.color import ColorFormat
5+
from tppt.pptx.converter import PptxConvertible, to_pptx_length, to_tppt_length
6+
from tppt.pptx.dml.color import ColorFormat, ColorFormatBuilder
77
from tppt.types._length import Length, LiteralLength
88

99

1010
class Font(PptxConvertible[PptxFont]):
1111
def __init__(self, pptx_obj: PptxFont) -> None:
1212
self._pptx = pptx_obj
1313

14-
def color(self) -> ColorFormat:
14+
@property
15+
def name(self) -> str | None:
16+
return self._pptx.name
17+
18+
@name.setter
19+
def name(self, name: str) -> None:
20+
self._pptx.name = name
21+
22+
@property
23+
def size(self) -> Length | None:
24+
return to_tppt_length(self._pptx.size)
25+
26+
@size.setter
27+
def size(self, size: Length) -> None:
28+
self._pptx.size = to_pptx_length(size)
29+
30+
@property
31+
def bold(self) -> bool | None:
32+
return self._pptx.bold
33+
34+
@bold.setter
35+
def bold(self, bold: bool) -> None:
36+
self._pptx.bold = bold
37+
38+
@property
39+
def italic(self) -> bool | None:
40+
return self._pptx.italic
41+
42+
@italic.setter
43+
def italic(self, italic: bool) -> None:
44+
self._pptx.italic = italic
45+
46+
@property
47+
def color(self) -> ColorFormat | None:
1548
return ColorFormat(self._pptx.color)
1649

50+
@color.setter
51+
def color(self, color: ColorFormat) -> None:
52+
self._pptx.color = color.to_pptx()
53+
1754
def builder(self) -> "FontBuilder":
1855
return FontBuilder(self._pptx)
1956

@@ -49,5 +86,14 @@ def italic(self, italic: bool) -> Self:
4986

5087
return self
5188

89+
def color(
90+
self, callable: Callable[[ColorFormat], ColorFormat | ColorFormatBuilder]
91+
) -> Self:
92+
color = callable(ColorFormat(self._pptx.color))
93+
if isinstance(color, ColorFormatBuilder):
94+
color._build()
95+
96+
return self
97+
5298
def _build(self) -> Font:
5399
return Font(self._pptx)

src/tppt/pptx/text/paragraph.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class Paragraph(PptxConvertible[PptxParagraph]):
1010
def __init__(self, pptx_obj: PptxParagraph) -> None:
1111
self._pptx = pptx_obj
1212

13+
@property
14+
def runs(self) -> list[Run]:
15+
return [Run(run) for run in self._pptx.runs]
16+
1317
def builder(self) -> "ParagraphBuilder":
1418
return ParagraphBuilder(self._pptx)
1519

0 commit comments

Comments
 (0)