Skip to content

Commit 54eee6f

Browse files
committed
feat: add Color class for RGB and hex color representation
1 parent fef3fc4 commit 54eee6f

File tree

6 files changed

+95
-46
lines changed

6 files changed

+95
-46
lines changed

examples/simple_example.py

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

55
import tppt
6+
from tppt.types import Color
67

78
EXAMPLE_DIR = Path(__file__).parent
89

@@ -24,6 +25,7 @@ def main():
2425
size=(60, "pt"),
2526
bold=True,
2627
italic=True,
28+
color=Color("#0000FF"),
2729
)
2830
.text(
2931
"Example of using tppt library", # Subtitle

src/tppt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Typed Python PowerPoint Tool"""
22

3+
from tppt import types as types
34
from tppt._pptx.presentation import Presentation
45
from tppt._pptx.shape import Shape
56
from tppt._pptx.slide import Slide, SlideBuilder

src/tppt/types/__init__.py

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@
44
from typing import Literal, TypeAlias
55

66
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
7-
from tppt._pptx.shape.table import TableCellStyle
87

9-
from ._color import Color
8+
from tppt._pptx.shape.table import TableCellStyle as TableCellStyle
9+
10+
from ._color import Color as Color
11+
from ._length import (
12+
Length as Length,
13+
)
14+
from ._length import (
15+
LiteralLength as LiteralLength,
16+
)
17+
from ._length import (
18+
Point as Point,
19+
)
1020
from ._length import (
11-
Length,
12-
LiteralLength,
13-
Point,
14-
to_length,
15-
to_point,
21+
to_length as to_length,
22+
)
23+
from ._length import (
24+
to_point as to_point,
1625
)
1726

1827
FilePath = str | pathlib.Path
@@ -31,22 +40,3 @@
3140
"TITLE_AND_VERTICAL_TEXT",
3241
"VERTICAL_TITLE_AND_TEXT",
3342
]
34-
35-
36-
def pt(value: int | float) -> LiteralLength:
37-
"""Create a point length."""
38-
return (int(value), "pt")
39-
40-
41-
__all__ = [
42-
"Color",
43-
"FilePath",
44-
"Length",
45-
"LiteralLength",
46-
"Point",
47-
"pt",
48-
"to_length",
49-
"to_point",
50-
"SlideLayoutType",
51-
"TableCellStyle",
52-
]

src/tppt/types/_color.py

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

3-
from pptx.dml.color import RGBColor
3+
from typing import overload
44

55
from tppt.exception import ColorInvalidFormatError
66

77

88
class Color:
9-
"""Represents a color in RGB or by name."""
9+
"""Represents a color in RGB or by hex code.
1010
11-
def __init__(self, value: str | tuple[int, int, int]):
11+
Examples:
12+
>>> Color("#00B")
13+
>>> Color("#00BB00")
14+
>>> Color(255, 0, 0)
15+
"""
16+
17+
@overload
18+
def __init__(self, r: str): ...
19+
20+
@overload
21+
def __init__(self, r: int, g: int, b: int): ...
22+
23+
def __init__(self, r: str | int, g: int | None = None, b: int | None = None):
1224
"""Initialize a Color instance."""
13-
if isinstance(value, str):
14-
if not value.startswith("#"):
15-
raise ColorInvalidFormatError(value)
25+
if isinstance(r, str):
26+
if not r.startswith("#"):
27+
raise ColorInvalidFormatError(r)
1628

17-
match len(value):
29+
match len(r):
1830
case 4:
19-
r = int(value[1:2], 16)
20-
g = int(value[2:3], 16)
21-
b = int(value[3:4], 16)
31+
# #123 は #112233 と同じであることに注意
32+
self.r = int(r[1:2] * 2, 16)
33+
self.g = int(r[2:3] * 2, 16)
34+
self.b = int(r[3:4] * 2, 16)
2235

23-
self.value = RGBColor(r, g, b)
2436
case 7:
25-
r = int(value[1:3], 16)
26-
g = int(value[3:5], 16)
27-
b = int(value[5:7], 16)
37+
self.r = int(r[1:3], 16)
38+
self.g = int(r[3:5], 16)
39+
self.b = int(r[5:7], 16)
2840

29-
self.value = RGBColor(r, g, b)
3041
case _:
31-
raise ColorInvalidFormatError(value)
42+
raise ColorInvalidFormatError(r)
3243

3344
else:
34-
r, g, b = value
3545
self.r = r
36-
self.g = g
37-
self.b = b
46+
self.g = g # type: ignore
47+
self.b = b # type: ignore
3848

3949
def __repr__(self) -> str:
4050
"""Return string representation."""

tests/test_color.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Tests for tppt.types._color module."""
2+
3+
import unittest
4+
5+
from tppt.exception import ColorInvalidFormatError
6+
from tppt.types._color import Color
7+
8+
9+
class TestColor(unittest.TestCase):
10+
"""Test cases for Color class."""
11+
12+
def test_init_with_rgb_hex_short(self):
13+
"""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
18+
19+
def test_init_with_rgb_hex_long(self):
20+
"""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
25+
26+
def test_init_with_rgb_tuple(self):
27+
"""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
32+
33+
def test_invalid_format_no_hash(self):
34+
"""Test initialization with invalid format (no # prefix)."""
35+
with self.assertRaises(ColorInvalidFormatError):
36+
Color("123456")
37+
38+
def test_invalid_format_wrong_length(self):
39+
"""Test initialization with invalid format (wrong length)."""
40+
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)"

tests/test_text.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_text_with_options(output) -> None:
2424
size=(24, "pt"),
2525
bold=True,
2626
italic=True,
27-
color=Color((255, 0, 0)), # 赤色
27+
color=Color("#FF0000"),
2828
margin_bottom=(10, "pt"),
2929
margin_left=(10, "pt"),
3030
vertical_anchor=MSO_ANCHOR.MIDDLE,

0 commit comments

Comments
 (0)