How to add an additional gradient fill stop, besides the default two, for a shape, using python-pptx

Viewed 303

I have a shape and can change the shape to use a gradient fill. If it since before have more gradient stops than the default two, I can parse them, change them, and remove them. However, I cannot find how to add an additional gradient stop. Which is what I want. The documentation quite correctly states three Gradient Stops to be a common need.

I have found the "gradient_stops._gsLst", but do not know the correct way to add a new gradient stop.

I have tried to use the gradient_stops.append() which gives me a clear hint on what is expected (an lxml.etree._Element). But I cannot find anything describing the correct way to create such an Element. I expect the solution could be to provide the "some new GradientStop Element" mentioned below:

shape.fill.gradient_stops._gsLst.append("some new GradientStop Element")

Trying to take a copy of one of the existing Gradient Stop elements feel like too much experimenting...

1 Answers

Well, there's no API support for this yet as you've discovered. However, the gradient-stop element (<a:gs>) is defined as a OneOrMore child element, which means a new one can be added to the end of those in the CT_GradientStopList object (<a:gsLst>) by calling .add_gs() on that object.

So something like this will do something along the right direction:

>>> len(gradient_stops)
2
>>> gsLst = gradient_stops._gsLst
>>> gsLst.add_gs()
<CT_GradientStop object ... or something like that>
>>> len(gradient_stops)
3
new_gradient_stop = gradient_stops[-1]

The question is whether it will be a valid gradient-stop element or whether it will trigger a repair error; I'm betting it wouldn't be populated enough and there's no easy way to add the child elements using the API.

Perhaps a better approach is to parse it in from XML:

from pptx.oxml import parse_xml
from pptx.oxml.ns import nsdecls

new_gs = parse_xml(
    '<a:gs pos="0" %s>\n'
    '  <a:schemeClr val="accent1">\n'
    '    <a:tint val="100000"/>\n'
    '    <a:shade val="100000"/>\n'
    '    <a:satMod val="130000"/>\n'
    "  </a:schemeClr>\n"
    "</a:gs>\n" % nsdecls("a")
)
gradient_stops._gsLst.append(new_gs)

This is about the same as doing a deepcopy on one of the stops that's already there, except that you can specify the color type, like a:srgbClr if you don't want to use a theme color. But, if you know one you have is just like the one you want and just needs a different position and some color tweaking or whatever, the deepcopy approach might suit better.

from copy import deepcopy

gsLst = gradient_stops._gsLst
new_gs = deepcopy(gsLst[0])
gsLst.append(new_gs)
new_gradient_stop = gradient_stops[-1]

I do vaguely remember it being important that the positions are strictly increasing, I think it ignores any that are not more than the prior stop, so probably good to keep that in the back of your mind.

Related