I am parsing a simple PowerPoint with three shapes. One shape is visibly to the left of the two other. But not when comparing it using python-pptx. The right side of that left shape (shape.left+shape.width) has a higher value than one of the other shapes left side (shape.left). The python-pptx result seem to indicate the right-hand shape starts within the left-hand shapes border. This seem to be caused by the group shape the right-hand shape is within.
What is the proper code to compare correctly that the right-hand shapes left side in fact is to the right of the left-hand shape?
I have tried removing the group, and then comparisons show expected values. I have tried creating new group shapes with shapes within, and again, they show expected values. However, the linked PowerPoint file at www.mibnet.se/LeftBoxIssue.pptx is an example where the group shape affects the normal result. When running the code, I do not know how the shapes were created. I need a generic way to test this special case correctly.
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
strStartPowerPoint=r".\LeftBoxIssue.pptx"
prs=Presentation(strStartPowerPoint)
slide=prs.slides[0]
for shpShape in slide.shapes:
if shpShape.shape_type == MSO_SHAPE_TYPE.GROUP:
print(shpShape.shapes[0].text+
" has Left="+str(shpShape.shapes[0].left)+
" and right="+
str(shpShape.shapes[0].left+shpShape.shapes[0].width))
else:
print(shpShape.text+" has Left="+str(shpShape.left)+
" and right="+str(shpShape.left+shpShape.width))
I expect the right hand shape to have its "left" value greater than the left-hand shapes "right" value. But instead, it prints a smaller value:
Left has Left=160326 and right=6254527
Right has Left=3291751 and right=3846370
A good place to start in understanding this is inspecting the group-shape XML:
print(group_shape._element.xml)
There you will find a child element that looks like this:
<p:grpSpPr>
<a:xfrm>
<a:off x="3347864" y="2204864"/>
<a:ext cx="3506688" cy="2930624"/>
<a:chOff x="3347864" y="2204864"/>
<a:chExt cx="3506688" cy="2930624"/>
</a:xfrm>
</p:grpSpPr>
The <a:chOff>
element represents the "child-offset" of shapes within the group. In this case, which is typical of shapes grouped in python-pptx
, note that the a:chOff
values are exactly the same as the a:off
values, which represent the top-left corner of the group-shape.
Using these two sets of values, you can calculate some interesting positions.
Absolute position of child shapes. This is child a:off
plus group a:off
minus group a:chOff
.
Relative position of child shapes (to the group-shape origin). This is child a:off
minus group a:chOff
.
You can get these extra child-offset values from the group with:
chOff = group_shape._element.xpath("./p:grpSpPr/a:xfrm/a:chOff")[0]
chOff_x = int(chOff["x"])
chOff_y = int(chOff["y"])
These values are in English Metric Units (EMU) which are described here along with how you might conveniently manipulate them:
https://python-pptx.readthedocs.io/en/latest/user/autoshapes.html#understanding-english-metric-units
python-pptx
always uses a child offset equal to the group shape position (a:off
) because that is convenient. Other packages may use other group-shape offsets that are more convenient to their purposes. For example, if you were to move a group, you could accomplish that by changing a:off
in the group only, without having to visit and update each of the child shape positions.