Extracting Effective AutoShape Path from a PowerPoint Presentation in Python

Hi! I am using aspose.slides for Python and I am having troubles extracting the effective path geometry for autoshape objects. The get_geometry_paths() of the shape returns only the base geometry path (for example if it is inherited from a rectangle it only returns the bounding box of the shape). As I understand the shape adjustments attribute contains information about the particular adjustments applied to the base shape, and the adjustments depend on the particular autoshape type. Is there a way to apply these adjustments in some way and extract the exact geometry for the autshape (without converting to SVG and then parsing the SVG - since as I saw that is quite slow to do and I am processing a lot of presentations)?

@adisa.bolic,
Thank you for posting the question.

To help you, we need more details. Could you please share a PowerPoint presentation with a sample shape and describe your requirements and expectations for the case?

You can zip the file and upload an archive here.

Here is a sample presentation with some autoshape in the first slides. What I expect is for each shape to get a list of (x, y) coordinates and the commands I suppose (moveto, lineto, bezier curve …) that would match the exact outline of the shapes.

sample.pptx.zip (26.8 KB)

@adisa.bolic,
Thank you for the sample presentation. To extract commands and coordinates from geometry paths, you should use GeometryPath.path_data, PathSegment.path_command, and PathSegment.segment_data properties. Please take a look at the following code example:

with slides.Presentation("sample.pptx") as presentation:
    for shape in presentation.slides[0].shapes:
        if isinstance(shape, AutoShape):
            print("Shape name: ", shape.name)
            paths = shape.get_geometry_paths()

            for path in paths:
                for path_segment in path.path_data:
                    print("Command: ", path_segment.path_command)
                    if path_segment.segment_data.length > 0:
                        print("Coordinates:", end=" ")
                        for  item in path_segment.segment_data:
                            print(item, end="; ")
                        print()
            print()

Output:

Shape name:  Google Shape;54;p13
Command:  1
Coordinates: 0.0; 15.602362632751465; 
Command:  2
Coordinates: 112.25196838378906; 15.602362632751465; 
Command:  2
Coordinates: 112.25196838378906; 0.0; 
Command:  2
Coordinates: 143.45669555664062; 31.20472526550293; 
Command:  2
Coordinates: 112.25196838378906; 62.40945053100586; 
Command:  2
Coordinates: 112.25196838378906; 46.80708694458008; 
Command:  2
Coordinates: 0.0; 46.80708694458008; 
Command:  0

Shape name:  Google Shape;55;p13
Command:  1
Coordinates: 0.0; 0.0; 
Command:  2
Coordinates: 108.46063232421875; 0.0; 
Command:  2
Coordinates: 147.968505859375; 39.50787353515625; 
Command:  2
Coordinates: 108.46063232421875; 79.0157470703125; 
Command:  2
Coordinates: 0.0; 79.0157470703125; 
Command:  2
Coordinates: 39.50787353515625; 39.50787353515625; 
Command:  0

Shape name:  Google Shape;56;p13
Command:  1
Coordinates: 0.0; 23.7595157623291; 
Command:  3
Coordinates: 71.72834777832031; 23.7595157623291; 180.0; 180.0; 
Command:  2
Coordinates: 143.45669555664062; 118.79757690429688; 
Command:  3
Coordinates: 71.72834777832031; 23.7595157623291; 0.0; 180.0; 
Command:  0
Command:  1
Coordinates: 143.45669555664062; 23.7595157623291; 
Command:  3
Coordinates: 71.72834777832031; 23.7595157623291; 0.0; 180.0; 
Command:  1
Coordinates: 0.0; 23.7595157623291; 
Command:  3
Coordinates: 71.72834777832031; 23.7595157623291; 180.0; 180.0; 
Command:  2
Coordinates: 143.45669555664062; 118.79757690429688; 
Command:  3
Coordinates: 71.72834777832031; 23.7595157623291; 0.0; 180.0; 
Command:  0

Commands are values from the PathCommandType enumeration.

Great, thank you! I managed to use that code to extract the coordinates. I have one more follow up question though, in that sample presentation there is a shape with ARCs (the third one in your output, the ARC command is 3). From IGeometryPath | Aspose.Sildes for Python via .NET API Reference I see that the 4 coordinates correspond to the width, height, start angle and sweep angle of the arc. But what are the center x and y coordinates for the ellipsis that generates this arc? That is, how do I render the arc properly? I have tried using the last point in the path as the center x and y coordinates, but that didn’t work out for me.

@adisa.bolic

Would it be possible if you can please share the code snippet that you have tried so far along with the sample files as it would help us in investigating your requirements accordingly.

Sure, here is the code snippet:

import aspose.slides as slides
from aspose.slides import AutoShape
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.path import Path
import numpy as np

points = []
fig, axs = plt.subplots(4, figsize=(16, 16))
i = -1

commands_to_mtpl = {
    slides.PathCommandType.MOVE_TO: Path.MOVETO,
    slides.PathCommandType.LINE_TO: Path.LINETO,
    slides.PathCommandType.QUAD_BEZIER_TO: Path.CURVE3,
    slides.PathCommandType.CUBIC_BEZIER_TO: Path.CURVE4,
    slides.PathCommandType.CLOSE: Path.CLOSEPOLY,
}

with slides.Presentation("sample.pptx") as presentation:
    for shape in presentation.slides[0].shapes:
        points = []
        codes = []
        arcs = []
        if isinstance(shape, AutoShape):
            i += 1
            print("Shape name: ", shape.name)
            paths = shape.get_geometry_paths()
            last_point = []
            for path in paths:
                for path_segment in path.path_data:
                    if path_segment.segment_data.length > 0:
                        command = path_segment.path_command
                        print("Command: ", command)

                        print("Coordinates:", end=" ")
                        if command == slides.PathCommandType.ARC_TO:
                            w = path_segment.segment_data[0] / presentation.slide_size.size.width
                            h = path_segment.segment_data[1] / presentation.slide_size.size.height
                            a1 = path_segment.segment_data[2]
                            a2 = path_segment.segment_data[3]
                            a = [last_point, w, h, a1, a2]
                            arcs.append(a)
                        else:
                            codes += [commands_to_mtpl[command]] * (len(path_segment.segment_data) // 2)
                            for x, y in zip(path_segment.segment_data[0::2], path_segment.segment_data[1::2]):
                                points.append(
                                    [x / presentation.slide_size.size.width, y / presentation.slide_size.size.height])
                                print(x / presentation.slide_size.size.width, y / presentation.slide_size.size.height,
                                      end="; ")
                                last_point = [x / presentation.slide_size.size.width,
                                              y / presentation.slide_size.size.height]
                        print()

            print()
            points = np.array(points).astype(np.float32)
            points = np.vstack((points[:, 0], 1 - points[:, 1])).T
            path = Path(points, codes)
            axs[i].add_patch(patches.PathPatch(path, lw=1, edgecolor=(1, 0, 0), facecolor=(0, 1, 0)))
            for arc in arcs:
                axs[i].add_patch(patches.Arc((arc[0][0], arc[0][1]),
                                             arc[1],
                                             arc[2],
                                             theta1=arc[3],
                                             theta2=arc[3] + arc[4]))
            axs[i].set_xlim(0, 1)
            axs[i].set_ylim(0, 1)

plt.show()

and also attached is the sample presentation I am using. The two objects without curves render correctly, but the bottom two objects don’t, since I am interpreting the arc parameters wrong probably (see attached image for the output of the above snippet).

I also came across one more issue - the paths don’t seem to have a closing code (you can see in the first two shapes that there is no red edge line between the first and last point). I have tried adding this explicitly, which works for shapes that are really closed, but fails for the ones that are not. Is there a way to determine whether a shape is closed?

image.png (45.8 KB)

sample.zip (24.1 KB)

@adisa.bolic

We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): SLIDESPYNET-139

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.