使用 Python via Java 管理演示文稿形状

概述

Aspose.Slides for Python via Java 将幻灯片上的形状表示为有序的 ShapeCollection。该集合既是查找和修改形状的地方,也是它们堆叠顺序的来源:索引 0 为最底层形状,最后一个索引为最前层形状。

本文遵循该模型。首先说明如何可靠地识别形状并修改预设的形状调整点,然后展示如何克隆、移除、隐藏和重新排序形状。最后几节覆盖布局级格式、SVG 导出、对齐以及翻转设置。每个示例都是独立的,您可以只使用工作流所需的操作。

识别并查找形状

在处理已知文件时,集合索引很方便,但它们不是稳定的标识符。添加、删除或重新排序形状都会改变其索引。请根据演示文稿的创建和维护方式选择标识符:

  • Name 对于受开发者控制的模板很有用,并且可以在 PowerPoint 的“选择窗格”中轻松查看。名称可以编辑,但不保证唯一,因此如果代码依赖名称,请制定命名约定。
  • AlternativeText 在已有可访问性描述或作者提供的标签已经标识形状时很有用。它对用户可见,可能会本地化或为可访问性而重写,且不保证唯一。不要在不知情的情况下将有意义的可访问性文本用作数据库键。
  • OfficeInteropShapeId 是只读标识符,在同一幻灯片内唯一,且对应 PowerPoint 互操作使用的形状 ID。将其用于与 PowerPoint 集成或在形状生命周期内需要明确引用的场景。克隆或重新创建的形状是不同的形状,会获得自己的 ID。

相关的 getUniqueId 方法返回演示文稿范围的标识符,但该标识符面向插件,可能会被重新分配,不应视为永久的外部键。如果需要长期唯一性,请在应用程序数据中维护映射并验证预期的形状仍然存在。

下面的示例通过精确比较按名称搜索,并报告幻灯片范围的互操作 ID。当模板不包含期望的形状时,代码会报告该结果,而不是继续使用错误的对象。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation

presentation = Presentation("input.pptx")
try:
    slide = presentation.getSlides().get_Item(0)

    target_shape = None
    for shape in slide.getShapes():
        if shape.getName() == "RevenueChart":
            target_shape = shape
            break

    if target_shape is None:
        print("The shape 'RevenueChart' was not found on slide 1.")
    else:
        print(f"Found {target_shape.getName()}; interop ID: {target_shape.getOfficeInteropShapeId()}")
finally:
    presentation.dispose()

当操作特定于某种形状类型时,请在使用类型特定成员之前检查类型。此示例仅在命名对象是 AutoShape 时更新文本和替代文本。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import AutoShape, Presentation, SaveFormat

presentation = Presentation("input.pptx")
try:
    slide = presentation.getSlides().get_Item(0)

    candidate = None
    for shape in slide.getShapes():
        if shape.getName() == "StatusLabel":
            candidate = shape
            break

    if isinstance(candidate, AutoShape):
        candidate.getTextFrame().setText("Approved")
        candidate.setAlternativeText("Approval status: approved")
        presentation.save("identified-shape.pptx", SaveFormat.Pptx)
    else:
        print("'StatusLabel' is missing or is not an AutoShape.")
finally:
    presentation.dispose()

识别并修改预设形状调整

预设几何形状可以公开调整点,以控制角大小、箭头比例或弧度等特性。通过只读的 GeometryShape.getAdjustments 集合访问它们。该集合由形状提供,但每个 AdjustValue 包含可更改的数值。

不要仅依赖固定的集合索引。遍历调整项并检查只读的 getType 方法,其返回的 ShapeAdjustmentType 值描述该调整控制的内容。只读的 getName 方法提供额外的标识信息,特别是在同一预设包含多个语义相同类型的调整时非常有用。

使用与调整意义相匹配的数值方法:

调整类型 目的 要更改的值
CornerSize 圆角大小 setRawValue
ArrowTailThickness 箭尾粗细 setRawValue
ArrowheadLength 箭头长度 setRawValue
ArrowheadWidth 箭头宽度 setRawValue
StartAngle 饼形或弧形的起始角度 setAngleValue
EndAngle 饼形或弧形的结束角度 setAngleValue

getTypegetName 返回只读信息。getRawValuesetRawValue 使用预设原生几何单位的整数,而 getAngleValuesetAngleValue 使用度数。调整的数量、顺序、含义和有效范围取决于预设的 ShapeType。在一种预设下有效的数值在另一种预设中可能无效或产生不同效果。

getType 返回 ShapeAdjustmentType.Custom 时,API 并未识别标准语义。检查 getName、预设类型和现有数值,除非已知期望的含义和范围,否则保持调整不变。即使是已识别的类型,也要在选择数值前检查同类型是否出现多次。Connector 文章展示了连接器弯曲调整的这种情况。

下面的完整示例创建了三种预设形状的默认和修改版本。它遍历每个调整,报告其名称和类型,通过 setRawValue 更改尺寸相关数值,通过 setAngleValue 更改角度,并保存结果。左列保留默认几何,右列展示了调整后的圆角矩形、四向箭头和饼形。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation, SaveFormat, ShapeAdjustmentType, ShapeType

presentation = Presentation()
try:
    slide = presentation.getSlides().get_Item(0)

    # 为默认和调整后的形状列添加标题。
    default_column_label = slide.getShapes().addAutoShape(ShapeType.Rectangle, 40, 20, 250, 30)
    default_column_label.getTextFrame().setText("Default preset geometry")
    adjusted_column_label = slide.getShapes().addAutoShape(ShapeType.Rectangle, 390, 20, 250, 30)
    adjusted_column_label.getTextFrame().setText("Modified adjustment values")

    slide.getShapes().addAutoShape(ShapeType.RoundCornerRectangle, 80, 70, 160, 70)
    modified_rounded_rectangle = slide.getShapes().addAutoShape(ShapeType.RoundCornerRectangle, 430, 70, 160, 70)
    modified_rounded_rectangle.setName("ModifiedRoundedRectangle")

    slide.getShapes().addAutoShape(ShapeType.QuadArrow, 80, 180, 160, 110)
    modified_arrow = slide.getShapes().addAutoShape(ShapeType.QuadArrow, 430, 180, 160, 110)
    modified_arrow.setName("ModifiedQuadArrow")

    slide.getShapes().addAutoShape(ShapeType.Pie, 95, 330, 130, 130)
    modified_pie = slide.getShapes().addAutoShape(ShapeType.Pie, 445, 330, 130, 130)
    modified_pie.setName("ModifiedPie")

    shapes_to_adjust = [modified_rounded_rectangle, modified_arrow, modified_pie]

    for shape in shapes_to_adjust:
        for adjustment_index in range(shape.getAdjustments().size()):
            adjustment = shape.getAdjustments().get_Item(adjustment_index)
            print(f"{shape.getName()} / {adjustment.getName()}: {adjustment.getType()}")

            if adjustment.getType() == ShapeAdjustmentType.CornerSize:
                adjustment.setRawValue(5000)
            elif adjustment.getType() == ShapeAdjustmentType.ArrowTailThickness:
                adjustment.setRawValue(25000)
            elif adjustment.getType() == ShapeAdjustmentType.ArrowheadLength:
                adjustment.setRawValue(30000)
            elif adjustment.getType() == ShapeAdjustmentType.ArrowheadWidth:
                adjustment.setRawValue(40000)
            elif adjustment.getType() == ShapeAdjustmentType.StartAngle:
                adjustment.setAngleValue(30)
            elif adjustment.getType() == ShapeAdjustmentType.EndAngle:
                adjustment.setAngleValue(300)
            elif adjustment.getType() == ShapeAdjustmentType.Custom:
                print(f"Custom adjustment '{adjustment.getName()}' was not changed.")

    presentation.save("preset-shape-adjustments.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

在更改数值之前检查语义类型可以使代码意图明确,并避免假设特定集合索引在不同预设形状间具有相同含义。

修改形状集合

add、clone、remove 和 reorder 方法会立即作用于集合。如果一次操作改变了形状的数量或顺序,请勿继续依赖操作前捕获的索引。

克隆形状

addClone 创建一个独立副本并将其追加到目标集合。insertClone 也创建副本,但将其放置在指定的 z 顺序索引。接受坐标的重载在不改变尺寸的情况下移动克隆;接受宽度和高度的重载则可以同时调整大小。

示例创建目标幻灯片,将带标签的矩形克隆到前面,并在后面插入第二个克隆。对任一克隆的更改都不影响源形状。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import AutoShape, Presentation, SaveFormat, ShapeType, SlideLayoutType

presentation = Presentation()
try:
    source_slide = presentation.getSlides().get_Item(0)
    source_shape = source_slide.getShapes().addAutoShape(ShapeType.Rectangle, 40, 40, 180, 60)
    source_shape.setName("SourceLabel")
    source_shape.getTextFrame().setText("Source")

    blank_layout = presentation.getMasters().get_Item(0).getLayoutSlides().getByType(SlideLayoutType.Blank)
    destination_slide = presentation.getSlides().addEmptySlide(blank_layout)

    front_clone_shape = destination_slide.getShapes().addClone(source_shape, 80, 80)
    front_clone_shape.setName("FrontClone")
    if isinstance(front_clone_shape, AutoShape):
        front_clone_shape.getTextFrame().setText("Front clone")
    else:
        print("The front clone is not an AutoShape; its text was not changed.")

    back_clone_shape = destination_slide.getShapes().insertClone(0, source_shape, 80, 180)
    back_clone_shape.setName("BackClone")
    if isinstance(back_clone_shape, AutoShape):
        back_clone_shape.getTextFrame().setText("Back clone")
    else:
        print("The back clone is not an AutoShape; its text was not changed.")

    presentation.save("cloned-shapes.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

克隆会复制形状的内容和格式,包括名称和替代文本。当这些值必须唯一时,请为克隆分配新的逻辑标识符。复杂形状使用的资源由演示文稿处理,但克隆仍然是集合中的新项目,拥有新的形状标识。

移除形状

remove 从其集合中删除特定形状对象。在索引遍历期间移除多个匹配项时,请从末端向前遍历,以保持其余索引有效。

此示例移除所有具有指定名称的形状。它读取当前索引处的形状,而不是固定的集合项,并且没有不必要的强制转换。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation, SaveFormat, ShapeType

presentation = Presentation()
try:
    slide = presentation.getSlides().get_Item(0)

    keep_shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 40, 40, 140, 60)
    keep_shape.setName("Keep")

    first_temporary_shape = slide.getShapes().addAutoShape(ShapeType.Ellipse, 220, 40, 80, 80)
    first_temporary_shape.setName("Temporary")

    second_temporary_shape = slide.getShapes().addAutoShape(ShapeType.Triangle, 340, 40, 100, 80)
    second_temporary_shape.setName("Temporary")

    for i in range(slide.getShapes().size() - 1, -1, -1):
        shape = slide.getShapes().get_Item(i)
        if shape.getName() == "Temporary":
            slide.getShapes().remove(shape)

    presentation.save("removed-shapes.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

移除后,形状计数以及后续形状的索引会改变。对未受影响的形状的引用比保存的索引更可靠。还要考虑连接器、动画等可能引用被移除对象的演示文稿特性;移除可见形状可能会影响幻灯片的外观之外的其他方面。

隐藏形状

Hidden 设置为 True 会保留形状在集合中,但阻止其在普通幻灯片放映中出现。其索引、格式和内容仍可供代码使用,因此隐藏适用于可能稍后恢复的可选元素。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation, SaveFormat, ShapeType

presentation = Presentation()
try:
    slide = presentation.getSlides().get_Item(0)

    visible_shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 40, 40, 160, 60)
    visible_shape.setName("VisibleLabel")

    optional_shape = slide.getShapes().addAutoShape(ShapeType.Moon, 240, 40, 100, 100)
    optional_shape.setName("OptionalDecoration")

    for shape in slide.getShapes():
        if shape.getName() == "OptionalDecoration":
            shape.setHidden(True)

    presentation.save("hidden-shape.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

隐藏并不是删除或安全保密。对象仍然可以被用户或代码发现并取消隐藏,它仍是演示文稿文件的一部分。

更改 Z 顺序

重叠的形状按照集合顺序绘制。reorder 在不克隆的情况下将已有形状移动到目标索引。索引 0 为最底层,集合 size 减一为最前层。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import FillType, Presentation, SaveFormat, ShapeType
from java.awt import Color

presentation = Presentation()
try:
    slide = presentation.getSlides().get_Item(0)

    blue_rectangle = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 220, 120)
    blue_rectangle.setName("BlueRectangle")
    blue_rectangle.getFillFormat().setFillType(FillType.Solid)
    blue_rectangle.getFillFormat().getSolidFillColor().setColor(Color.BLUE)

    orange_ellipse = slide.getShapes().addAutoShape(ShapeType.Ellipse, 180, 140, 220, 120)
    orange_ellipse.setName("OrangeEllipse")
    orange_ellipse.getFillFormat().setFillType(FillType.Solid)
    orange_ellipse.getFillFormat().getSolidFillColor().setColor(Color.ORANGE)

    slide.getShapes().reorder(slide.getShapes().size() - 1, blue_rectangle)
    presentation.save("reordered-shapes.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

矩形最初创建时位于椭圆之后。将其移动到最后索引后会置于前面。请在添加或克隆所有相关形状后再确定最终的 Z 顺序,因为这些操作会追加或插入新集合项,可能改变原有堆叠。

检查布局幻灯片上的形状

普通幻灯片、布局幻灯片和母版幻灯片拥有各自的形状集合。布局集合中的形状并非普通幻灯片上同位形状的同一对象。需要了解或更改布局提供的格式时,请检查布局形状。

下面的示例读取每个布局形状的 FillFormatLineFormat,而不假设每个形状都是 AutoShape

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation

presentation = Presentation("input.pptx")
try:
    for layout_slide in presentation.getLayoutSlides():
        for shape in layout_slide.getShapes():
            fill_type = shape.getFillFormat().getFillType()
            line_width = shape.getLineFormat().getWidth()
            print(f"{layout_slide.getName()} / {shape.getName()}: fill={fill_type}, line width={line_width}")
finally:
    presentation.dispose()

编辑布局可能影响使用该布局的多个幻灯片。更改布局形状前,请确定普通幻灯片是继承该对象还是包含本地覆盖,并测试使用该布局的每张幻灯片。

将形状导出为 SVG

ShapewriteAsSvg 方法将单个形状的渲染内容写入流。结果仅包含该形状,而不包括整个幻灯片背景或相邻形状。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation
from pathlib import Path
from java.io import ByteArrayOutputStream

presentation = Presentation("input.pptx")
try:
    slide = presentation.getSlides().get_Item(0)

    if slide.getShapes().size() == 0:
        print("Slide 1 does not contain a shape to export.")
    else:
        shape = slide.getShapes().get_Item(0)
        svg_stream = ByteArrayOutputStream()
        try:
            shape.writeAsSvg(svg_stream)
            svg_bytes = bytes(svg_stream.toByteArray())
            Path("shape.svg").write_bytes(svg_bytes)
        except OSError as exception:
            print(f"The SVG file could not be written: {exception}")
        finally:
            svg_stream.close()
finally:
    presentation.dispose()

在渲染期间保持演示文稿打开。输出受形状格式以及字体、图像等资源影响。若需要完整的组合,请导出幻灯片而非单个形状。调用者拥有流的所有权,需要自行关闭。

对齐形状

SlideUtil.alignShapes 重载可对齐所有形状或选定的集合索引。ShapesAlignmentType 指定边缘、中心线或分布模式。将 align_to_slide 设置为 True 使用幻灯片边缘;设置为 False 则相对于彼此对齐选中的形状。

此示例将三个形状对齐到幻灯片的上边缘。对齐前会立即将返回的形状引用转换为当前索引。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import Presentation, SaveFormat, ShapeType, ShapesAlignmentType, SlideUtil

presentation = Presentation()
try:
    slide = presentation.getSlides().get_Item(0)

    first_shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 60, 80, 120, 50)
    second_shape = slide.getShapes().addAutoShape(ShapeType.Ellipse, 240, 160, 120, 50)
    third_shape = slide.getShapes().addAutoShape(ShapeType.Triangle, 420, 240, 120, 50)
    first_shape.setName("FirstAlignedShape")
    second_shape.setName("SecondAlignedShape")
    third_shape.setName("ThirdAlignedShape")

    shape_indexes = jpype.JArray(jpype.JInt)([slide.getShapes().indexOf(first_shape), slide.getShapes().indexOf(second_shape), slide.getShapes().indexOf(third_shape)])

    SlideUtil.alignShapes(ShapesAlignmentType.AlignTop, True, slide, shape_indexes)
    presentation.save("aligned-shapes.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

对齐会改变位置,而不是 Z 顺序。相对对齐通常至少需要两个形状,水平或垂直分布则需要足够的形状来定义间距。如在调用方法前修改了集合,请重新计算索引。

翻转形状

ShapeFrame 类存储位置、大小、水平和垂直翻转设置以及旋转。其 getFlipHgetFlipV 值使用 NullableBoolTrue 启用翻转,False 禁用,NotDefined 保持未指定/默认状态。

下面的输入演示文稿包含一个未翻转的形状。

翻转前的形状

示例保留其他所有帧值,仅替换两个翻转设置。这一点很重要,因为为 Frame 赋新值会替换整个帧。

import jpype
import asposeslides

if not jpype.isJVMStarted():
    jpype.startJVM()

from asposeslides.api import NullableBool, Presentation, SaveFormat, ShapeFrame

presentation = Presentation("sample.pptx")
try:
    shape = presentation.getSlides().get_Item(0).getShapes().get_Item(0)
    frame = shape.getFrame()

    print(f"Horizontal flip before change: {frame.getFlipH()}")
    print(f"Vertical flip before change: {frame.getFlipV()}")

    flipped_frame = ShapeFrame(frame.getX(), frame.getY(), frame.getWidth(), frame.getHeight(), NullableBool.True_, NullableBool.True_, frame.getRotation())
    shape.setFrame(flipped_frame)

    presentation.save("flipped-shape.pptx", SaveFormat.Pptx)
finally:
    presentation.dispose()

保存后的形状在水平和垂直方向上均被镜像,同时保持其位置、大小和旋转不变。

翻转后的形状

常见问题

我应该使用集合索引作为形状标识符吗?

仅在短期处理且在使用索引前集合不会变化的情况下使用。对于已编写的模板,请优先使用已验证的 NameAlternativeText 约定;对于幻灯片范围的互操作工作,请使用 OfficeInteropShapeId

隐藏形状会从 Z 顺序中移除吗?

不会。隐藏的形状仍然保留在集合中,索引不变。它可以被查找、重新排序、编辑或再次显示。

为什么克隆的形状会出现在另一个形状前面?

addClone 会将克隆追加到集合末尾,即 Z 顺序的最前端。使用 insertClone 可以指定初始索引,或在全部形状添加完毕后使用 reorder 调整顺序。

我可以使用固定索引来识别预设形状的调整吗?

仅在验证了确切预设和集合布局后才可以。更推荐遍历 GeometryShape.getAdjustments 并检查 AdjustValue.getType;当同一语义类型出现多次时,可使用 AdjustValue.getName 作为补充信息。