1+ """Unit-test suite for transparency functionality in `pptx.dml` module."""
2+
3+ from __future__ import annotations
4+
5+ import pytest
6+
7+ from pptx .dml .color import ColorFormat , RGBColor
8+ from pptx .dml .fill import FillFormat , _SolidFill
9+ from pptx .enum .dml import MSO_COLOR_TYPE , MSO_FILL
10+
11+ from ..oxml .unitdata .dml import (
12+ a_alpha ,
13+ a_solidFill ,
14+ an_srgbClr ,
15+ a_schemeClr ,
16+ )
17+ from ..unitutil .cxml import element
18+
19+
20+ class DescribeColorFormatTransparency (object ):
21+ """Unit-test suite for ColorFormat transparency property."""
22+
23+ def it_knows_its_transparency_value (self , transparency_get_fixture ):
24+ color_format , expected_transparency = transparency_get_fixture
25+ assert color_format .transparency == expected_transparency
26+
27+ def it_can_set_its_transparency_value (self , transparency_set_fixture ):
28+ color_format , transparency , expected_xml = transparency_set_fixture
29+ color_format .transparency = transparency
30+ assert color_format ._xFill .xml == expected_xml
31+
32+ def it_raises_on_transparency_get_for_NoneColor (self , _NoneColor_color_format ):
33+ # This should not raise - NoneColor should return 0.0 transparency
34+ transparency = _NoneColor_color_format .transparency
35+ assert transparency == 0.0
36+
37+ def it_raises_on_transparency_set_for_NoneColor (self , _NoneColor_color_format ):
38+ with pytest .raises (ValueError ):
39+ _NoneColor_color_format .transparency = 0.5
40+
41+ def it_raises_on_assign_invalid_transparency_value (self , rgb_color_format ):
42+ color_format = rgb_color_format
43+ with pytest .raises (ValueError ):
44+ color_format .transparency = 1.1
45+ with pytest .raises (ValueError ):
46+ color_format .transparency = - 0.1
47+
48+ def it_can_set_transparency_to_zero_removes_alpha_element (self , rgb_color_format ):
49+ """Setting transparency to 0.0 should remove the alpha element."""
50+ color_format = rgb_color_format
51+ # First set some transparency
52+ color_format .transparency = 0.5
53+ assert color_format ._color ._xClr .alpha is not None
54+
55+ # Then set to 0.0 - should remove alpha element
56+ color_format .transparency = 0.0
57+ assert color_format ._color ._xClr .alpha is None
58+ assert color_format .transparency == 0.0
59+
60+ def it_can_set_transparency_to_one_creates_zero_alpha (self , rgb_color_format ):
61+ """Setting transparency to 1.0 should create alpha element with val=0."""
62+ color_format = rgb_color_format
63+ color_format .transparency = 1.0
64+
65+ alpha_element = color_format ._color ._xClr .alpha
66+ assert alpha_element is not None
67+ assert alpha_element .val == 0.0
68+ assert color_format .transparency == 1.0
69+
70+ # fixtures -------------------------------------------------------
71+
72+ @pytest .fixture (
73+ params = [
74+ # no alpha element - fully opaque
75+ (lambda : an_srgbClr ().with_val ("FF0000" ), 0.0 ),
76+ # alpha = 100000 (100% opaque) - 0% transparent
77+ (lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
78+ a_alpha ().with_val (100000 )), 0.0 ),
79+ # alpha = 50000 (50% opaque) - 50% transparent
80+ (lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
81+ a_alpha ().with_val (50000 )), 0.5 ),
82+ # alpha = 0 (0% opaque) - 100% transparent
83+ (lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
84+ a_alpha ().with_val (0 )), 1.0 ),
85+ ]
86+ )
87+ def transparency_get_fixture (self , request ):
88+ xClr_bldr_fn , expected_transparency = request .param
89+ xClr_bldr = xClr_bldr_fn ()
90+ solidFill = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
91+ color_format = ColorFormat .from_colorchoice_parent (solidFill )
92+ return color_format , expected_transparency
93+
94+ @pytest .fixture (
95+ params = [
96+ # Set to 0.0 (fully opaque) - should remove alpha element
97+ (lambda : an_srgbClr ().with_val ("FF0000" ), 0.0 ,
98+ lambda : an_srgbClr ().with_val ("FF0000" )),
99+ # Set to 0.5 (50% transparent) - should add alpha=50000
100+ (lambda : an_srgbClr ().with_val ("FF0000" ), 0.5 ,
101+ lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
102+ a_alpha ().with_val (50000 ))),
103+ # Set to 1.0 (fully transparent) - should add alpha=0
104+ (lambda : an_srgbClr ().with_val ("FF0000" ), 1.0 ,
105+ lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
106+ a_alpha ().with_val (0 ))),
107+ # Set from existing alpha to different value
108+ (lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
109+ a_alpha ().with_val (75000 )), 0.3 ,
110+ lambda : an_srgbClr ().with_val ("FF0000" ).with_child (
111+ a_alpha ().with_val (70000 ))),
112+ ]
113+ )
114+ def transparency_set_fixture (self , request ):
115+ xClr_bldr_fn , transparency , expected_xClr_bldr_fn = request .param
116+
117+ xClr_bldr = xClr_bldr_fn ()
118+ solidFill = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
119+ color_format = ColorFormat .from_colorchoice_parent (solidFill )
120+
121+ expected_xClr_bldr = expected_xClr_bldr_fn ()
122+ expected_xml = a_solidFill ().with_nsdecls ().with_child (expected_xClr_bldr ).xml ()
123+
124+ return color_format , transparency , expected_xml
125+
126+ @pytest .fixture
127+ def rgb_color_format (self ):
128+ solidFill = a_solidFill ().with_nsdecls ().with_child (
129+ an_srgbClr ().with_val ("FF0000" )
130+ ).element
131+ return ColorFormat .from_colorchoice_parent (solidFill )
132+
133+ @pytest .fixture
134+ def _NoneColor_color_format (self ):
135+ solidFill = a_solidFill ().with_nsdecls ().element
136+ return ColorFormat .from_colorchoice_parent (solidFill )
137+
138+ # Additional validation tests
139+ def it_validates_transparency_value_range (self ):
140+ """Test that _validate_transparency_value works correctly."""
141+ from pptx .dml .color import ColorFormat
142+
143+ # Create a dummy ColorFormat instance for testing validation
144+ xClr_bldr = an_srgbClr ().with_val ("FF0000" )
145+ solidFill = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
146+ color_format = ColorFormat .from_colorchoice_parent (solidFill )
147+
148+ # Valid values should not raise
149+ color_format ._validate_transparency_value (0.0 )
150+ color_format ._validate_transparency_value (0.5 )
151+ color_format ._validate_transparency_value (1.0 )
152+
153+ # Invalid values should raise ValueError
154+ with pytest .raises (ValueError , match = "transparency must be number in range 0.0 to 1.0" ):
155+ color_format ._validate_transparency_value (- 0.1 )
156+ with pytest .raises (ValueError , match = "transparency must be number in range 0.0 to 1.0" ):
157+ color_format ._validate_transparency_value (1.1 )
158+ with pytest .raises (ValueError , match = "transparency must be number in range 0.0 to 1.0" ):
159+ color_format ._validate_transparency_value (2.0 )
160+
161+
162+ class DescribeFillFormatTransparency (object ):
163+ """Unit-test suite for FillFormat transparency property."""
164+
165+ def it_delegates_transparency_to_solid_fill_object (self ):
166+ """Test that FillFormat delegates transparency to its _SolidFill object."""
167+ # Create a _SolidFill directly
168+ xClr_bldr = an_srgbClr ().with_val ("FF0000" )
169+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
170+ solid_fill = _SolidFill (solidFill_elm )
171+
172+ # Create a FillFormat that wraps our _SolidFill
173+ from pptx .dml .fill import FillFormat
174+
175+ # Create a simple mock parent
176+ class MockParent :
177+ def __init__ (self ):
178+ self .eg_fillProperties = solidFill_elm
179+
180+ parent_mock = MockParent ()
181+ fill_format = FillFormat (parent_mock , solid_fill )
182+
183+ # Set transparency through FillFormat
184+ fill_format .transparency = 0.3
185+
186+ # Verify it's delegated to the underlying solid fill
187+ assert abs (fill_format ._fill .transparency - 0.3 ) < 0.001
188+ assert abs (fill_format .transparency - 0.3 ) < 0.001
189+
190+ def it_raises_on_transparency_access_for_non_solid_fills (self ):
191+ """Test that non-solid fills raise TypeError on transparency access."""
192+ from pptx .dml .fill import FillFormat , _NoFill
193+
194+ # Create a FillFormat with NoFill
195+ class MockParent :
196+ def __init__ (self ):
197+ self .eg_fillProperties = None
198+
199+ parent_mock = MockParent ()
200+ no_fill = _NoFill (None )
201+ fill_format = FillFormat (parent_mock , no_fill )
202+
203+ with pytest .raises (TypeError , match = "fill type .* has no transparency" ):
204+ fill_format .transparency
205+
206+ def it_raises_on_transparency_set_for_non_solid_fills (self ):
207+ """Test that non-solid fills raise TypeError on transparency set."""
208+ from pptx .dml .fill import FillFormat , _NoFill
209+
210+ # Create a FillFormat with NoFill
211+ class MockParent :
212+ def __init__ (self ):
213+ self .eg_fillProperties = None
214+
215+ parent_mock = MockParent ()
216+ no_fill = _NoFill (None )
217+ fill_format = FillFormat (parent_mock , no_fill )
218+
219+ with pytest .raises (TypeError , match = "fill type .* has no transparency" ):
220+ fill_format .transparency = 0.5
221+
222+
223+ class DescribeSolidFillTransparency (object ):
224+ """Unit-test suite for _SolidFill transparency property."""
225+
226+ def it_provides_access_to_transparency (self , solid_fill_transparency_fixture ):
227+ solid_fill , expected_transparency = solid_fill_transparency_fixture
228+ assert abs (solid_fill .transparency - expected_transparency ) < 0.001
229+
230+ def it_can_set_transparency (self , solid_fill_transparency_set_fixture ):
231+ solid_fill , transparency = solid_fill_transparency_set_fixture
232+ solid_fill .transparency = transparency
233+ assert abs (solid_fill .transparency - transparency ) < 0.001
234+
235+ def it_delegates_transparency_to_fore_color (self , solid_fill_obj ):
236+ """Test that _SolidFill delegates transparency to its fore_color."""
237+ solid_fill = solid_fill_obj
238+
239+ # Set transparency through _SolidFill
240+ solid_fill .transparency = 0.6
241+
242+ # Verify it's delegated to fore_color
243+ assert solid_fill .fore_color .transparency == 0.6
244+ assert solid_fill .transparency == 0.6
245+
246+ def it_has_correct_fill_type (self , solid_fill_obj ):
247+ """Test that _SolidFill reports correct fill type."""
248+ solid_fill = solid_fill_obj
249+ assert solid_fill .type == MSO_FILL .SOLID
250+
251+ # fixtures -------------------------------------------------------
252+
253+ @pytest .fixture (
254+ params = [
255+ # No alpha element - fully opaque
256+ (lambda : an_srgbClr ().with_val ("00FF00" ), 0.0 ),
257+ # Alpha = 80000 (80% opaque) - 20% transparent
258+ (lambda : an_srgbClr ().with_val ("00FF00" ).with_child (
259+ a_alpha ().with_val (80000 )), 0.2 ),
260+ # Alpha = 30000 (30% opaque) - 70% transparent
261+ (lambda : an_srgbClr ().with_val ("00FF00" ).with_child (
262+ a_alpha ().with_val (30000 )), 0.7 ),
263+ ]
264+ )
265+ def solid_fill_transparency_fixture (self , request ):
266+ xClr_bldr_fn , expected_transparency = request .param
267+ xClr_bldr = xClr_bldr_fn ()
268+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
269+
270+ solid_fill = _SolidFill (solidFill_elm )
271+ return solid_fill , expected_transparency
272+
273+ @pytest .fixture (
274+ params = [0.0 , 0.1 , 0.33 , 0.67 , 0.9 , 1.0 ]
275+ )
276+ def solid_fill_transparency_set_fixture (self , request ):
277+ transparency = request .param
278+ xClr_bldr = an_srgbClr ().with_val ("00FF00" )
279+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
280+
281+ solid_fill = _SolidFill (solidFill_elm )
282+ return solid_fill , transparency
283+
284+ @pytest .fixture
285+ def solid_fill_obj (self ):
286+ """Create a _SolidFill object for testing."""
287+ xClr_bldr = an_srgbClr ().with_val ("0000FF" )
288+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
289+
290+ return _SolidFill (solidFill_elm )
291+
292+
293+ class DescribeTransparencyIntegration (object ):
294+ """Integration tests for transparency across the entire stack."""
295+
296+ def it_works_end_to_end_with_solid_fill_and_color_format (self ):
297+ """Test transparency from ColorFormat through _SolidFill."""
298+ # Create a complete fill hierarchy using _SolidFill directly
299+ xClr_bldr = an_srgbClr ().with_val ("FF00FF" )
300+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
301+
302+ solid_fill = _SolidFill (solidFill_elm )
303+
304+ # Test setting transparency at _SolidFill level
305+ solid_fill .transparency = 0.4
306+
307+ # Verify it propagates through all levels
308+ assert abs (solid_fill .transparency - 0.4 ) < 0.001
309+ assert abs (solid_fill .fore_color .transparency - 0.4 ) < 0.001
310+
311+ # Verify the underlying XML has the alpha element
312+ alpha_elm = solid_fill .fore_color ._color ._xClr .alpha
313+ assert alpha_elm is not None
314+ assert abs (alpha_elm .val - 0.6 ) < 0.001 # 1.0 - 0.4 = 0.6 (60% opaque)
315+
316+ def it_handles_transparency_removal_correctly (self ):
317+ """Test that setting transparency to 0.0 removes alpha element."""
318+ xClr_bldr = an_srgbClr ().with_val ("FFFF00" ).with_child (a_alpha ().with_val (40000 ))
319+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
320+
321+ solid_fill = _SolidFill (solidFill_elm )
322+
323+ # Verify initial transparency (40000/100000 = 0.4 opaque = 0.6 transparent)
324+ assert abs (solid_fill .transparency - 0.6 ) < 0.001
325+ assert solid_fill .fore_color ._color ._xClr .alpha is not None
326+
327+ # Set transparency to 0.0
328+ solid_fill .transparency = 0.0
329+
330+ # Verify alpha element is removed
331+ assert solid_fill .transparency == 0.0
332+ assert solid_fill .fore_color ._color ._xClr .alpha is None
333+
334+ def it_handles_color_format_transparency_directly (self ):
335+ """Test ColorFormat transparency manipulation directly."""
336+ xClr_bldr = an_srgbClr ().with_val ("00FFFF" )
337+ solidFill_elm = a_solidFill ().with_nsdecls ().with_child (xClr_bldr ).element
338+
339+ color_format = ColorFormat .from_colorchoice_parent (solidFill_elm )
340+
341+ # Test setting various transparency values
342+ test_values = [0.0 , 0.25 , 0.5 , 0.75 , 1.0 ]
343+ for transparency in test_values :
344+ color_format .transparency = transparency
345+ assert abs (color_format .transparency - transparency ) < 0.001
346+
347+ if transparency == 0.0 :
348+ # No alpha element for fully opaque
349+ assert color_format ._color ._xClr .alpha is None
350+ else :
351+ # Alpha element should exist with correct value
352+ alpha_elm = color_format ._color ._xClr .alpha
353+ assert alpha_elm is not None
354+ expected_alpha = 1.0 - transparency
355+ assert abs (alpha_elm .val - expected_alpha ) < 0.001
0 commit comments