# encoding: utf-8
"""DrawingML objects related to fill."""
from __future__ import absolute_import, division, print_function, unicode_literals
from pptx.compat import Sequence
from pptx.dml.color import ColorFormat
from pptx.enum.dml import MSO_FILL
from pptx.oxml.dml.fill import (
CT_BlipFillProperties,
CT_GradientFillProperties,
CT_GroupFillProperties,
CT_NoFillProperties,
CT_PatternFillProperties,
CT_SolidColorFillProperties,
)
from pptx.shared import ElementProxy
from pptx.util import lazyproperty
class _Fill(object):
"""
Object factory for fill object of class matching fill element, such as
_SolidFill for ``<a:solidFill>``; also serves as the base class for all
fill classes
"""
def __new__(cls, xFill):
if xFill is None:
fill_cls = _NoneFill
elif isinstance(xFill, CT_BlipFillProperties):
fill_cls = _BlipFill
elif isinstance(xFill, CT_GradientFillProperties):
fill_cls = _GradFill
elif isinstance(xFill, CT_GroupFillProperties):
fill_cls = _GrpFill
elif isinstance(xFill, CT_NoFillProperties):
fill_cls = _NoFill
elif isinstance(xFill, CT_PatternFillProperties):
fill_cls = _PattFill
elif isinstance(xFill, CT_SolidColorFillProperties):
fill_cls = _SolidFill
else:
fill_cls = _Fill
return super(_Fill, cls).__new__(fill_cls)
@property
def back_color(self):
"""Raise TypeError for types that do not override this property."""
tmpl = "fill type %s has no background color, call .patterned() first"
raise TypeError(tmpl % self.__class__.__name__)
@property
def fore_color(self):
"""Raise TypeError for types that do not override this property."""
tmpl = (
"fill type %s has no foreground color, call .solid() or .pattern"
"ed() first"
)
raise TypeError(tmpl % self.__class__.__name__)
@property
def pattern(self):
"""Raise TypeError for fills that do not override this property."""
tmpl = "fill type %s has no pattern, call .patterned() first"
raise TypeError(tmpl % self.__class__.__name__)
@property
def type(self): # pragma: no cover
tmpl = ".type property must be implemented on %s"
raise NotImplementedError(tmpl % self.__class__.__name__)
class _BlipFill(_Fill):
@property
def type(self):
return MSO_FILL.PICTURE
class _GradFill(_Fill):
"""Proxies an `a:gradFill` element."""
def __init__(self, gradFill):
self._element = self._gradFill = gradFill
@property
def gradient_angle(self):
"""Angle in float degrees of line of a linear gradient.
Read/Write. May be |None|, indicating the angle is inherited from the
style hierarchy. An angle of 0.0 corresponds to a left-to-right
gradient. Increasing angles represent clockwise rotation of the line,
for example 90.0 represents a top-to-bottom gradient. Raises
|TypeError| when the fill type is not MSO_FILL_TYPE.GRADIENT. Raises
|ValueError| for a non-linear gradient (e.g. a radial gradient).
"""
# ---case 1: gradient path is explicit, but not linear---
path = self._gradFill.path
if path is not None:
raise ValueError("not a linear gradient")
# ---case 2: gradient path is inherited (no a:lin OR a:path)---
lin = self._gradFill.lin
if lin is None:
return None
# ---case 3: gradient path is explicitly linear---
# angle is stored in XML as a clockwise angle, whereas the UI
# reports it as counter-clockwise from horizontal-pointing-right.
# Since the UI is consistent with trigonometry conventions, we
# respect that in the API.
clockwise_angle = lin.ang
counter_clockwise_angle = (
0.0 if clockwise_angle == 0.0 else (360.0 - clockwise_angle)
)
return counter_clockwise_angle
@gradient_angle.setter
def gradient_angle(self, value):
lin = self._gradFill.lin
if lin is None:
raise ValueError("not a linear gradient")
lin.ang = 360.0 - value
@lazyproperty
def gradient_stops(self):
"""|_GradientStops| object providing access to gradient colors.
Each stop represents a color between which the gradient smoothly
transitions.
"""
return _GradientStops(self._gradFill.get_or_add_gsLst())
@property
def type(self):
return MSO_FILL.GRADIENT
class _GrpFill(_Fill):
@property
def type(self):
return MSO_FILL.GROUP
class _NoFill(_Fill):
@property
def type(self):
return MSO_FILL.BACKGROUND
class _NoneFill(_Fill):
@property
def type(self):
return None
class _PattFill(_Fill):
"""Provides access to patterned fill properties."""
def __init__(self, pattFill):
super(_PattFill, self).__init__()
self._element = self._pattFill = pattFill
@lazyproperty
def back_color(self):
"""Return |ColorFormat| object that controls background color."""
bgClr = self._pattFill.get_or_add_bgClr()
return ColorFormat.from_colorchoice_parent(bgClr)
@lazyproperty
def fore_color(self):
"""Return |ColorFormat| object that controls foreground color."""
fgClr = self._pattFill.get_or_add_fgClr()
return ColorFormat.from_colorchoice_parent(fgClr)
@property
def pattern(self):
"""Return member of :ref:`MsoPatternType` indicating fill pattern.
Returns |None| if no pattern has been set; PowerPoint may display the
default `PERCENT_5` pattern in this case. Assigning |None| will
remove any explicit pattern setting.
"""
return self._pattFill.prst
@pattern.setter
def pattern(self, pattern_type):
self._pattFill.prst = pattern_type
@property
def type(self):
return MSO_FILL.PATTERNED
class _SolidFill(_Fill):
"""Provides access to fill properties such as color for solid fills."""
def __init__(self, solidFill):
super(_SolidFill, self).__init__()
self._solidFill = solidFill
@lazyproperty
def fore_color(self):
"""Return |ColorFormat| object controlling fill color."""
return ColorFormat.from_colorchoice_parent(self._solidFill)
@property
def type(self):
return MSO_FILL.SOLID
class _GradientStops(Sequence):
"""Collection of |GradientStop| objects defining gradient colors.
A gradient must have a minimum of two stops, but can have as many more
than that as required to achieve the desired effect (three is perhaps
most common). Stops are sequenced in the order they are transitioned
through.
"""
def __init__(self, gsLst):
self._gsLst = gsLst
def __getitem__(self, idx):
return _GradientStop(self._gsLst[idx])
def __len__(self):
return len(self._gsLst)
class _GradientStop(ElementProxy):
"""A single gradient stop.
A gradient stop defines a color and a position.
"""
def __init__(self, gs):
super(_GradientStop, self).__init__(gs)
self._gs = gs
@lazyproperty
def color(self):
"""Return |ColorFormat| object controlling stop color."""
return ColorFormat.from_colorchoice_parent(self._gs)
@property
def position(self):
"""Location of stop in gradient path as float between 0.0 and 1.0.
The value represents a percentage, where 0.0 (0%) represents the
start of the path and 1.0 (100%) represents the end of the path. For
a linear gradient, these would represent opposing extents of the
filled area.
"""
return self._gs.pos
@position.setter
def position(self, value):
self._gs.pos = float(value)