管理 Java 中的演示文稿形状

概述

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

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

识别并查找形状

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

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

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

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

import com.aspose.slides.*;

Presentation presentation = new Presentation("input.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IShape targetShape = null;
    for (IShape shape : slide.getShapes()) {
        if ("RevenueChart".equals(shape.getName())) {
            targetShape = shape;
            break;
        }
    }

    if (targetShape == null) {
        System.out.println("The shape 'RevenueChart' was not found on slide 1.");
    } else {
        System.out.println("Found " + targetShape.getName() + "; interop ID: " + targetShape.getOfficeInteropShapeId());
    }
} finally {
    presentation.dispose();
}

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

import com.aspose.slides.*;

Presentation presentation = new Presentation("input.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IShape candidate = null;
    for (IShape shape : slide.getShapes()) {
        if ("StatusLabel".equals(shape.getName())) {
            candidate = shape;
            break;
        }
    }

    if (candidate instanceof IAutoShape) {
        IAutoShape autoShape = (IAutoShape) candidate;
        autoShape.getTextFrame().setText("Approved");
        autoShape.setAlternativeText("Approval status: approved");
        presentation.save("identified-shape.pptx", SaveFormat.Pptx);
    } else {
        System.out.println("'StatusLabel' is missing or is not an AutoShape.");
    }
} finally {
    presentation.dispose();
}

识别并修改预设形状调整

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

不要仅依赖固定的集合索引。遍历所有调整并检查只读的 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 com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

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

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

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

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

    IGeometryShape[] shapesToAdjust = {
        modifiedRoundedRectangle,
        modifiedArrow,
        modifiedPie
    };

    for (IGeometryShape shape : shapesToAdjust) {
        for (int adjustmentIndex = 0; adjustmentIndex < shape.getAdjustments().size(); adjustmentIndex++) {
            IAdjustValue adjustment = shape.getAdjustments().get_Item(adjustmentIndex);
            System.out.println(shape.getName() + " / " + adjustment.getName() + ": " + adjustment.getType());

            switch (adjustment.getType()) {
                case ShapeAdjustmentType.CornerSize:
                    adjustment.setRawValue(5000);
                    break;
                case ShapeAdjustmentType.ArrowTailThickness:
                    adjustment.setRawValue(25000);
                    break;
                case ShapeAdjustmentType.ArrowheadLength:
                    adjustment.setRawValue(30000);
                    break;
                case ShapeAdjustmentType.ArrowheadWidth:
                    adjustment.setRawValue(40000);
                    break;
                case ShapeAdjustmentType.StartAngle:
                    adjustment.setAngleValue(30);
                    break;
                case ShapeAdjustmentType.EndAngle:
                    adjustment.setAngleValue(300);
                    break;
                case ShapeAdjustmentType.Custom:
                    System.out.println("Custom adjustment '" + adjustment.getName() + "' was not changed.");
                    break;
            }
        }
    }

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

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

修改形状集合

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

克隆形状

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

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

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide sourceSlide = presentation.getSlides().get_Item(0);
    IAutoShape sourceShape = sourceSlide.getShapes().addAutoShape(ShapeType.Rectangle, 40, 40, 180, 60);
    sourceShape.setName("SourceLabel");
    sourceShape.getTextFrame().setText("Source");

    ILayoutSlide blankLayout = presentation.getMasters().get_Item(0).getLayoutSlides().getByType(SlideLayoutType.Blank);
    ISlide destinationSlide = presentation.getSlides().addEmptySlide(blankLayout);

    IShape frontCloneShape = destinationSlide.getShapes().addClone(sourceShape, 80, 80);
    frontCloneShape.setName("FrontClone");
    if (frontCloneShape instanceof IAutoShape) {
        IAutoShape frontClone = (IAutoShape) frontCloneShape;
        frontClone.getTextFrame().setText("Front clone");
    } else {
        System.out.println("The front clone is not an AutoShape; its text was not changed.");
    }

    IShape backCloneShape = destinationSlide.getShapes().insertClone(0, sourceShape, 80, 180);
    backCloneShape.setName("BackClone");
    if (backCloneShape instanceof IAutoShape) {
        IAutoShape backClone = (IAutoShape) backCloneShape;
        backClone.getTextFrame().setText("Back clone");
    } else {
        System.out.println("The back clone is not an AutoShape; its text was not changed.");
    }

    presentation.save("cloned-shapes.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

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

删除形状

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

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

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

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

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

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

    for (int i = slide.getShapes().size() - 1; i >= 0; i--) {
        IShape shape = slide.getShapes().get_Item(i);
        if ("Temporary".equals(shape.getName())) {
            slide.getShapes().remove(shape);
        }
    }

    presentation.save("removed-shapes.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

删除后,形状计数以及后续形状的索引会改变。对未受影响形状的引用比保存的索引更可靠。同时考虑连接器、动画及其他可能引用已删除对象的演示文稿特性;删除可见形状可能会影响幻灯片的外观以外的内容。

隐藏形状

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

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

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

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

    for (IShape shape : slide.getShapes()) {
        if ("OptionalDecoration".equals(shape.getName())) {
            shape.setHidden(true);
        }
    }

    presentation.save("hidden-shape.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

隐藏并非删除或安全措施。对象仍可被用户或代码发现并取消隐藏,并且仍是演示文稿文件的一部分。

更改 Z 顺序

重叠的形状按照集合顺序绘制。reorder 将已有形状移动到目标索引而不进行克隆。索引 0 为最底层,size() - 1 为最前层。

import com.aspose.slides.*;
import java.awt.Color;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

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

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

    slide.getShapes().reorder(slide.getShapes().size() - 1, blueRectangle);
    presentation.save("reordered-shapes.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

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

检查布局幻灯片上的形状

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

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

import com.aspose.slides.*;

Presentation presentation = new Presentation("input.pptx");
try {
    for (ILayoutSlide layoutSlide : presentation.getLayoutSlides()) {
        for (IShape shape : layoutSlide.getShapes()) {
            int fillType = shape.getFillFormat().getFillType();
            double lineWidth = shape.getLineFormat().getWidth();
            System.out.println(layoutSlide.getName() + " / " + shape.getName() + ": fill=" + fillType + ", line width=" + lineWidth);
        }
    }
} finally {
    presentation.dispose();
}

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

将形状导出为 SVG

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

import com.aspose.slides.*;
import java.io.FileOutputStream;
import java.io.IOException;

Presentation presentation = new Presentation("input.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    if (slide.getShapes().size() == 0) {
        System.out.println("Slide 1 does not contain a shape to export.");
    } else {
        IShape shape = slide.getShapes().get_Item(0);
        try (FileOutputStream svgStream = new FileOutputStream("shape.svg")) {
            shape.writeAsSvg(svgStream);
        } catch (IOException exception) {
            System.out.println("The SVG file could not be written: " + exception.getMessage());
        }
    }
} finally {
    presentation.dispose();
}

渲染期间请保持演示文稿打开。输出取决于形状的格式以及字体、图像等资源。如果需要完整的合成,请导出幻灯片而非单个形状。调用方拥有流的所有权并必须关闭它。

对齐形状

SlideUtil.alignShapes 的重载可以对全部形状或选定的集合索引进行对齐。ShapesAlignmentType 指定边缘、中心线或分布模式。将 alignToSlide 设置为 true 使用幻灯片边缘;设为 false 则相对选定形状进行对齐。

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

import com.aspose.slides.*;

Presentation presentation = new Presentation();
try {
    ISlide slide = presentation.getSlides().get_Item(0);

    IAutoShape firstShape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 60, 80, 120, 50);
    IAutoShape secondShape = slide.getShapes().addAutoShape(ShapeType.Ellipse, 240, 160, 120, 50);
    IAutoShape thirdShape = slide.getShapes().addAutoShape(ShapeType.Triangle, 420, 240, 120, 50);
    firstShape.setName("FirstAlignedShape");
    secondShape.setName("SecondAlignedShape");
    thirdShape.setName("ThirdAlignedShape");

    int[] shapeIndexes = {slide.getShapes().indexOf(firstShape), slide.getShapes().indexOf(secondShape), slide.getShapes().indexOf(thirdShape)};

    SlideUtil.alignShapes(ShapesAlignmentType.AlignTop, true, slide, shapeIndexes);
    presentation.save("aligned-shapes.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

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

翻转形状

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

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

The shape before flipping

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

import com.aspose.slides.*;

Presentation presentation = new Presentation("sample.pptx");
try {
    IShape shape = presentation.getSlides().get_Item(0).getShapes().get_Item(0);
    IShapeFrame frame = shape.getFrame();

    System.out.println("Horizontal flip before change: " + frame.getFlipH());
    System.out.println("Vertical flip before change: " + frame.getFlipV());

    shape.setFrame(new ShapeFrame(frame.getX(), frame.getY(), frame.getWidth(), frame.getHeight(), NullableBool.True, NullableBool.True, frame.getRotation()));

    presentation.save("flipped-shape.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

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

The shape after flipping

常见问题

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

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

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

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

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

addClone 将克隆追加到集合的末尾,也就是 Z 顺序的前面。使用 insertClone 可选择初始索引,或在全部形状添加完后使用 reorder 调整顺序。

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

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