3

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 Answers1

2

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.

scanny
  • 26,423
  • 5
  • 54
  • 80
  • Great! I tried the two first methods you mentioned. They both worked. The first one led, as you expected, to a repair by PowerPoint as was. However setting Color and position after creating the additional stop fixed that. The second one did initialize more variables. Yet "silently failed" unless setting the position after creation. – Mats Bengtsson Jun 11 '19 at 19:23