Skip to content

Commit d54f71d

Browse files
committed
refactor: update color handling to use RGBColor and simplify color conversion logic
1 parent debc34f commit d54f71d

File tree

9 files changed

+77
-129
lines changed

9 files changed

+77
-129
lines changed

examples/formatted_text.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def functional_text(
2424
text: str,
2525
bold: bool = False,
2626
italic: bool = False,
27-
color: tppt.types.Color | tppt.types.LiteralColor | None = None,
27+
color: tppt.types.RGBColor | tppt.types.LiteralColor | None = None,
2828
) -> tppt.pptx.Text:
2929
run = text_obj.text_frame.add_paragraph().add_run()
3030
run.text = text

examples/simple_example.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from pathlib import Path
44

55
import tppt
6-
from tppt.types import Color
76

87
EXAMPLE_DIR = Path(__file__).parent
98

@@ -32,7 +31,7 @@ def main():
3231
size=(60, "pt"),
3332
bold=True,
3433
italic=True,
35-
color=Color("#0000FF"),
34+
color="#0000FF",
3635
)
3736
.text(
3837
"Example of using tppt library", # Subtitle

src/tppt/pptx/converter.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
runtime_checkable,
1111
)
1212

13-
from pptx.dml.color import RGBColor as PptxRGBColor
1413
from pptx.util import Cm as PptxCm
1514
from pptx.util import Emu as PptxEmu
1615
from pptx.util import Inches as PptxInches
@@ -19,7 +18,6 @@
1918
from pptx.util import Pt as PptxPt
2019

2120
from tppt.types._angle import Angle, Degrees, LiteralAngle
22-
from tppt.types._color import Color, LiteralColor, to_color
2321
from tppt.types._length import (
2422
CentiMeters,
2523
EnglishMetricUnits,
@@ -89,35 +87,6 @@ def to_tppt_length(length: PptxLength | None) -> Length | None:
8987
return to_length((length.emu, "emu")) if length else None
9088

9189

92-
@overload
93-
def to_pptx_rgb_color(color: Color | LiteralColor) -> PptxRGBColor: ...
94-
95-
96-
@overload
97-
def to_pptx_rgb_color(color: Color | LiteralColor | None) -> PptxRGBColor | None: ...
98-
99-
100-
def to_pptx_rgb_color(color: Color | LiteralColor | None) -> PptxRGBColor | None:
101-
if color is None:
102-
return None
103-
104-
color = to_color(color)
105-
106-
return PptxRGBColor(color.r, color.g, color.b)
107-
108-
109-
@overload
110-
def to_tppt_color(color: PptxRGBColor) -> Color: ...
111-
112-
113-
@overload
114-
def to_tppt_color(color: PptxRGBColor | None) -> Color | None: ...
115-
116-
117-
def to_tppt_color(color: PptxRGBColor | None) -> Color | None:
118-
return Color(*color) if color else None
119-
120-
12190
PptxAngle: TypeAlias = float
12291

12392

src/tppt/pptx/dml/color.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from pptx.dml.color import ColorFormat as PptxColorFormat
44
from pptx.enum.dml import MSO_THEME_COLOR
55

6-
from tppt.pptx.converter import PptxConvertible, to_pptx_rgb_color, to_tppt_color
7-
from tppt.types import Color, LiteralColor
6+
from tppt.pptx.converter import PptxConvertible
7+
from tppt.types import LiteralColor, RGBColor, to_color
88

99

1010
class ColorFormat(PptxConvertible[PptxColorFormat]):
@@ -25,12 +25,12 @@ def brightness(self, value: float) -> None:
2525
self._pptx.brightness = value
2626

2727
@property
28-
def rgb(self) -> Color | None:
29-
return to_tppt_color(self._pptx.rgb)
28+
def rgb(self) -> RGBColor | None:
29+
return self._pptx.rgb
3030

3131
@rgb.setter
32-
def rgb(self, color: Color | LiteralColor):
33-
self._pptx.rgb = to_pptx_rgb_color(color)
32+
def rgb(self, color: RGBColor | LiteralColor):
33+
self._pptx.rgb = to_color(color)
3434

3535
@property
3636
def theme_color(self) -> MSO_THEME_COLOR:

src/tppt/pptx/shape/text.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, PP_ALIGN
44
from pptx.shapes.autoshape import Shape as PptxShape
55

6-
from tppt.pptx.converter import to_pptx_length, to_pptx_rgb_color
6+
from tppt.pptx.converter import to_pptx_length
77
from tppt.pptx.text.text_frame import TextFrame
8-
from tppt.types._color import Color, LiteralColor
8+
from tppt.types._color import LiteralColor, RGBColor, to_color
99
from tppt.types._length import Length, LiteralLength
1010

1111
from . import RangeProps, Shape
@@ -17,7 +17,7 @@ class TextProps(RangeProps):
1717
size: NotRequired[Length | LiteralLength]
1818
bold: NotRequired[bool]
1919
italic: NotRequired[bool]
20-
color: NotRequired[Color | LiteralColor]
20+
color: NotRequired[RGBColor | LiteralColor]
2121
margin_bottom: NotRequired[Length | LiteralLength]
2222
margin_left: NotRequired[Length | LiteralLength]
2323
vertical_anchor: NotRequired[MSO_ANCHOR]
@@ -52,7 +52,7 @@ def __init__(self, pptx_obj: PptxShape, data: TextData | None = None, /) -> None
5252
if (italic := data.get("italic")) is not None:
5353
font.italic = italic
5454
if (color := data.get("color")) is not None:
55-
font.color.rgb = to_pptx_rgb_color(color)
55+
font.color.rgb = to_color(color)
5656
if (margin_bottom := data.get("margin_bottom")) is not None:
5757
p.space_after = to_pptx_length(margin_bottom)
5858
if (margin_left := data.get("margin_left")) is not None:

src/tppt/types/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
from tppt.pptx.shape import RangeProps as _RangeProps
77

8-
from ._color import Color as Color
98
from ._color import LiteralColor as LiteralColor
9+
from ._color import RGBColor as RGBColor
10+
from ._color import to_color as to_color
1011
from ._length import (
1112
CentiMeters as CentiMeters,
1213
)

src/tppt/types/_color.py

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,48 @@
11
"""Color types for tppt."""
22

3-
from typing import TypeAlias, overload
3+
from typing import TypeAlias, assert_never, overload
4+
5+
from pptx.dml.color import RGBColor as _PptxRGBColor
46

57
from tppt.exception import ColorInvalidFormatError
68

79
LiteralColor: TypeAlias = tuple[int, int, int] | str
10+
RGBColor: TypeAlias = _PptxRGBColor
811

912

10-
class Color:
11-
"""Represents a color in RGB or by hex code.
13+
@overload
14+
def to_color(color: RGBColor | LiteralColor) -> RGBColor: ...
1215

13-
Examples:
14-
>>> Color("#00B")
15-
>>> Color("#00BB00")
16-
>>> Color(255, 0, 0)
17-
"""
1816

19-
@overload
20-
def __init__(self, r: str): ...
17+
@overload
18+
def to_color(color: RGBColor | LiteralColor | None) -> RGBColor | None: ...
2119

22-
@overload
23-
def __init__(self, r: int, g: int, b: int): ...
2420

25-
def __init__(self, r: str | int, g: int | None = None, b: int | None = None):
26-
"""Initialize a Color instance."""
27-
if isinstance(r, str):
28-
if not r.startswith("#"):
29-
raise ColorInvalidFormatError(r)
21+
def to_color(color: RGBColor | LiteralColor | None) -> RGBColor | None:
22+
match color:
23+
case None:
24+
return None
25+
case tuple():
26+
return RGBColor(*color)
27+
case str():
28+
if not color.startswith("#"):
29+
raise ColorInvalidFormatError(color)
3030

31-
match len(r):
31+
match len(color):
3232
case 4:
3333
# Note that #123 is the same as #112233
34-
self.r = int(r[1:2] * 2, 16)
35-
self.g = int(r[2:3] * 2, 16)
36-
self.b = int(r[3:4] * 2, 16)
34+
r = int(color[1:2] * 2, 16)
35+
g = int(color[2:3] * 2, 16)
36+
b = int(color[3:4] * 2, 16)
37+
return RGBColor(r, g, b)
3738

3839
case 7:
39-
self.r = int(r[1:3], 16)
40-
self.g = int(r[3:5], 16)
41-
self.b = int(r[5:7], 16)
40+
r = int(color[1:3], 16)
41+
g = int(color[3:5], 16)
42+
b = int(color[5:7], 16)
43+
return RGBColor(r, g, b)
4244

4345
case _:
44-
raise ColorInvalidFormatError(r)
45-
46-
else:
47-
self.r = r
48-
self.g = g # type: ignore
49-
self.b = b # type: ignore
50-
51-
def __repr__(self) -> str:
52-
"""Return string representation."""
53-
return f"Color({self.r}, {self.g}, {self.b})"
54-
55-
56-
def to_color(color: Color | LiteralColor) -> Color:
57-
if isinstance(color, Color):
58-
return color
59-
else:
60-
if isinstance(color, tuple):
61-
return Color(*color)
62-
else:
63-
return Color(color)
46+
raise ColorInvalidFormatError(color)
47+
case _:
48+
assert_never(color)

tests/test_text.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, PP_ALIGN
44

55
import tppt
6-
from tppt.types._color import Color
76

87

98
def test_text_with_options(output) -> None:
@@ -26,7 +25,7 @@ def test_text_with_options(output) -> None:
2625
size=(24, "pt"),
2726
bold=True,
2827
italic=True,
29-
color=Color("#0000FF"),
28+
color="#0000FF",
3029
margin_bottom=(10, "pt"),
3130
margin_left=(10, "pt"),
3231
vertical_anchor=MSO_ANCHOR.MIDDLE,

tests/test_types_color.py

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,73 @@
33
import unittest
44

55
from tppt.exception import ColorInvalidFormatError
6-
from tppt.types._color import Color, to_color
6+
from tppt.types._color import RGBColor, to_color
77

88

99
class TestColor(unittest.TestCase):
1010
"""Test cases for Color class."""
1111

1212
def test_init_with_rgb_hex_short(self):
1313
"""Test initialization with short hex color code (#RGB)."""
14-
color = Color("#123")
15-
assert color.r == 0x11
16-
assert color.g == 0x22
17-
assert color.b == 0x33
14+
color = to_color("#123")
15+
r, g, b = color
16+
assert r == 0x11
17+
assert g == 0x22
18+
assert b == 0x33
1819

1920
def test_init_with_rgb_hex_long(self):
2021
"""Test initialization with long hex color code (#RRGGBB)."""
21-
color = Color("#123456")
22-
assert color.r == 0x12
23-
assert color.g == 0x34
24-
assert color.b == 0x56
22+
color = to_color("#123456")
23+
r, g, b = color
24+
assert r == 0x12
25+
assert g == 0x34
26+
assert b == 0x56
2527

2628
def test_init_with_rgb_tuple(self):
2729
"""Test initialization with RGB tuple."""
28-
color = Color(10, 20, 30)
29-
assert color.r == 10
30-
assert color.g == 20
31-
assert color.b == 30
30+
color = RGBColor(10, 20, 30)
31+
r, g, b = color
32+
assert r == 10
33+
assert g == 20
34+
assert b == 30
3235

3336
def test_invalid_format_no_hash(self):
3437
"""Test initialization with invalid format (no # prefix)."""
3538
with self.assertRaises(ColorInvalidFormatError):
36-
Color("123456")
39+
to_color("123456")
3740

3841
def test_invalid_format_wrong_length(self):
3942
"""Test initialization with invalid format (wrong length)."""
4043
with self.assertRaises(ColorInvalidFormatError):
41-
Color("#12345") # 6 characters (including #) is invalid
42-
43-
def test_repr(self):
44-
"""Test string representation."""
45-
color = Color(10, 20, 30)
46-
assert repr(color) == "Color(10, 20, 30)"
44+
to_color("#12345") # 6 characters (including #) is invalid
4745

4846

4947
class TestToColor(unittest.TestCase):
5048
"""Test cases for to_color function."""
5149

52-
def test_to_color_with_color_instance(self):
53-
"""Test to_color with a Color instance."""
54-
original = Color(10, 20, 30)
55-
result = to_color(original)
56-
assert result is original
57-
5850
def test_to_color_with_tuple(self):
5951
"""Test to_color with a RGB tuple."""
6052
result = to_color((10, 20, 30))
61-
assert isinstance(result, Color)
62-
assert result.r == 10
63-
assert result.g == 20
64-
assert result.b == 30
53+
assert isinstance(result, RGBColor)
54+
r, g, b = result
55+
assert r == 10
56+
assert g == 20
57+
assert b == 30
6558

6659
def test_to_color_with_str(self):
6760
"""Test to_color with a hex string."""
6861
result = to_color("#123456")
69-
assert isinstance(result, Color)
70-
assert result.r == 0x12
71-
assert result.g == 0x34
72-
assert result.b == 0x56
62+
assert isinstance(result, RGBColor)
63+
r, g, b = result
64+
assert r == 0x12
65+
assert g == 0x34
66+
assert b == 0x56
7367

7468
def test_to_color_with_short_hex(self):
7569
"""Test to_color with a short hex string."""
7670
result = to_color("#123")
77-
assert isinstance(result, Color)
78-
assert result.r == 0x11
79-
assert result.g == 0x22
80-
assert result.b == 0x33
71+
assert isinstance(result, RGBColor)
72+
r, g, b = result
73+
assert r == 0x11
74+
assert g == 0x22
75+
assert b == 0x33

0 commit comments

Comments
 (0)