403 lines
13 KiB
Python
403 lines
13 KiB
Python
|
# 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 FillFormat(object):
|
||
|
"""
|
||
|
Provides access to the current fill properties object and provides
|
||
|
methods to change the fill type.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, eg_fill_properties_parent, fill_obj):
|
||
|
super(FillFormat, self).__init__()
|
||
|
self._xPr = eg_fill_properties_parent
|
||
|
self._fill = fill_obj
|
||
|
|
||
|
@classmethod
|
||
|
def from_fill_parent(cls, eg_fillProperties_parent):
|
||
|
"""
|
||
|
Return a |FillFormat| instance initialized to the settings contained
|
||
|
in *eg_fillProperties_parent*, which must be an element having
|
||
|
EG_FillProperties in its child element sequence in the XML schema.
|
||
|
"""
|
||
|
fill_elm = eg_fillProperties_parent.eg_fillProperties
|
||
|
fill = _Fill(fill_elm)
|
||
|
fill_format = cls(eg_fillProperties_parent, fill)
|
||
|
return fill_format
|
||
|
|
||
|
@property
|
||
|
def back_color(self):
|
||
|
"""Return a |ColorFormat| object representing background color.
|
||
|
|
||
|
This property is only applicable to pattern fills and lines.
|
||
|
"""
|
||
|
return self._fill.back_color
|
||
|
|
||
|
def background(self):
|
||
|
"""
|
||
|
Sets the fill type to noFill, i.e. transparent.
|
||
|
"""
|
||
|
noFill = self._xPr.get_or_change_to_noFill()
|
||
|
self._fill = _NoFill(noFill)
|
||
|
|
||
|
@property
|
||
|
def fore_color(self):
|
||
|
"""
|
||
|
Return a |ColorFormat| instance representing the foreground color of
|
||
|
this fill.
|
||
|
"""
|
||
|
return self._fill.fore_color
|
||
|
|
||
|
def gradient(self):
|
||
|
"""Sets the fill type to gradient.
|
||
|
|
||
|
If the fill is not already a gradient, a default gradient is added.
|
||
|
The default gradient corresponds to the default in the built-in
|
||
|
PowerPoint "White" template. This gradient is linear at angle
|
||
|
90-degrees (upward), with two stops. The first stop is Accent-1 with
|
||
|
tint 100%, shade 100%, and satMod 130%. The second stop is Accent-1
|
||
|
with tint 50%, shade 100%, and satMod 350%.
|
||
|
"""
|
||
|
gradFill = self._xPr.get_or_change_to_gradFill()
|
||
|
self._fill = _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 should be inherited
|
||
|
from the style hierarchy. An angle of 0.0 corresponds to
|
||
|
a left-to-right gradient. Increasing angles represent
|
||
|
counter-clockwise rotation of the line, for example 90.0 represents
|
||
|
a bottom-to-top 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).
|
||
|
"""
|
||
|
if self.type != MSO_FILL.GRADIENT:
|
||
|
raise TypeError("Fill is not of type MSO_FILL_TYPE.GRADIENT")
|
||
|
return self._fill.gradient_angle
|
||
|
|
||
|
@gradient_angle.setter
|
||
|
def gradient_angle(self, value):
|
||
|
if self.type != MSO_FILL.GRADIENT:
|
||
|
raise TypeError("Fill is not of type MSO_FILL_TYPE.GRADIENT")
|
||
|
self._fill.gradient_angle = value
|
||
|
|
||
|
@property
|
||
|
def gradient_stops(self):
|
||
|
"""|GradientStops| object providing access to stops of this gradient.
|
||
|
|
||
|
Raises |TypeError| when fill is not gradient (call `fill.gradient()`
|
||
|
first). Each stop represents a color between which the gradient
|
||
|
smoothly transitions.
|
||
|
"""
|
||
|
if self.type != MSO_FILL.GRADIENT:
|
||
|
raise TypeError("Fill is not of type MSO_FILL_TYPE.GRADIENT")
|
||
|
return self._fill.gradient_stops
|
||
|
|
||
|
@property
|
||
|
def pattern(self):
|
||
|
"""Return member of :ref:`MsoPatternType` indicating fill pattern.
|
||
|
|
||
|
Raises |TypeError| when fill is not patterned (call
|
||
|
`fill.patterned()` first). 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, although
|
||
|
relying on the default behavior is discouraged and may produce
|
||
|
rendering differences across client applications.
|
||
|
"""
|
||
|
return self._fill.pattern
|
||
|
|
||
|
@pattern.setter
|
||
|
def pattern(self, pattern_type):
|
||
|
self._fill.pattern = pattern_type
|
||
|
|
||
|
def patterned(self):
|
||
|
"""Selects the pattern fill type.
|
||
|
|
||
|
Note that calling this method does not by itself set a foreground or
|
||
|
background color of the pattern. Rather it enables subsequent
|
||
|
assignments to properties like fore_color to set the pattern and
|
||
|
colors.
|
||
|
"""
|
||
|
pattFill = self._xPr.get_or_change_to_pattFill()
|
||
|
self._fill = _PattFill(pattFill)
|
||
|
|
||
|
def solid(self):
|
||
|
"""
|
||
|
Sets the fill type to solid, i.e. a solid color. Note that calling
|
||
|
this method does not set a color or by itself cause the shape to
|
||
|
appear with a solid color fill; rather it enables subsequent
|
||
|
assignments to properties like fore_color to set the color.
|
||
|
"""
|
||
|
solidFill = self._xPr.get_or_change_to_solidFill()
|
||
|
self._fill = _SolidFill(solidFill)
|
||
|
|
||
|
@property
|
||
|
def type(self):
|
||
|
"""
|
||
|
Return a value from the :ref:`MsoFillType` enumeration corresponding
|
||
|
to the type of this fill.
|
||
|
"""
|
||
|
return self._fill.type
|
||
|
|
||
|
|
||
|
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.
|
||
|
"""
|
||
|
|
||
|
__slots__ = ("_gs", "_color")
|
||
|
|
||
|
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)
|