Personalizar formas de presentación en Java
Visión general
Este artículo explica cómo personalizar las formas de presentación en Aspose.Slides editando la geometría de las formas mediante puntos de edición y rutas de geometría. Muestra cómo trabajar con GeometryPath y IGeometryPath para modificar formas existentes, realizar operaciones básicas de edición de rutas, añadir o eliminar puntos y aplicar la geometría actualizada a una forma.
También demuestra cómo crear formas personalizadas y compuestas, construir formas con esquinas curvas, determinar si la geometría de una forma está cerrada, inspeccionar los elementos de la forma y convertir los contornos de texto de Java AWT en rutas de geometría.
Cambiar una forma usando puntos de edición
En PowerPoint, puedes usar Edit Points para modificar un cuadrado:
- Mover una esquina hacia dentro o fuera.
- Ajustar la curvatura en un punto.
- Añadir nuevos puntos.
Editar puntos te permite modificar el contorno de una forma o usar una forma existente como base para una nueva.
Consejos para la edición de formas

Al editar formas en PowerPoint, ten en cuenta estos conceptos:
- Una forma (o su ruta) puede estar cerrada o abierta.
- La ruta de una forma consta de puntos de anclaje conectados por segmentos.
- Un segmento puede ser recto o curvo. Los puntos de anclaje y los manejadores de control determinan su forma.
- Los puntos de anclaje existen como puntos de esquina, puntos rectos o puntos suaves:
- En un punto de esquina, los segmentos adyacentes pueden encontrarse en un ángulo.
- En un punto suave, los dos manejadores de control están alineados y a igual distancia del punto de anclaje.
- En un punto recto, los dos manejadores de control están alineados pero pueden estar a diferentes distancias del punto de anclaje.
- Mover los puntos de anclaje o ajustar sus manejadores cambia el contorno de la forma.
Para editar formas de PowerPoint mediante puntos de edición, Aspose.Slides ofrece la clase GeometryPath y la interfaz IGeometryPath.
- Una GeometryPath instancia representa una ruta de geometría del objeto IGeometryShape.
- Para obtener el GeometryPath desde la instancia IGeometryShape, puedes usar el método IGeometryShape.getGeometryPaths.
- Para establecer el GeometryPath de una forma, puedes usar estos métodos: IGeometryShape.setGeometryPath para formas con una única ruta y IGeometryShape.setGeometryPaths para formas con varias rutas.
- Para añadir segmentos, puedes usar los métodos bajo IGeometryPath.
- Usando los métodos IGeometryPath.setStroke y IGeometryPath.setFillMode puedes establecer la apariencia de una ruta de geometría.
- Usando el método IGeometryPath.getPathData puedes obtener la ruta de geometría de un GeometryShape como una matriz de segmentos de ruta.
- Para acceder a opciones adicionales de personalización de la geometría de la forma, puedes leer un java.awt.Shape a través de su PathIterator y reproducir los segmentos en una GeometryPath.
Operaciones de edición simples
Utiliza los siguientes métodos para editar una ruta de geometría. Las sobrecargas que aceptan un índice insertan un segmento en esa posición basada en cero en la matriz de segmentos de la ruta.
| Operación | Método |
|---|---|
| Añadir o insertar una línea recta. | GeometryPath.lineTo |
| Añadir o insertar una curva Bézier cúbica con dos puntos de control y un punto final. | GeometryPath.cubicBezierTo |
| Añadir o insertar una curva Bézier cuadrática con un punto de control y un punto final. | GeometryPath.quadraticBezierTo |
| Añadir un arco elíptico con las dimensiones y ángulos especificados en grados. | GeometryPath.arcTo |
| Cerrar la figura actual conectando su punto final con su punto inicial. | GeometryPath.closeFigure |
| Establecer el punto de inicio de una figura. | GeometryPath.moveTo |
| Eliminar un segmento en un índice basado en cero. | GeometryPath.removeAt |
Añadir puntos personalizados a una forma
Este ejemplo inserta dos puntos en una forma rectangular usando GeometryPath.lineTo y aplica la ruta editada con IGeometryShape.setGeometryPath. Las coordenadas son locales a la forma: ambos puntos nuevos están en su centro, a 100 puntos del borde izquierdo y 50 puntos del borde superior. Los índices de segmento 1 y 4 los sitúan en los bordes superior e inferior, tirando esos bordes hacia adentro.
import com.aspose.slides.*;
Presentation presentation = new Presentation();
try {
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);
IGeometryPath geometryPath = shape.getGeometryPaths()[0];
geometryPath.lineTo(100, 50, 1);
geometryPath.lineTo(100, 50, 4);
shape.setGeometryPath(geometryPath);
} finally {
presentation.dispose();
}

Eliminar puntos de una forma
Este ejemplo modifica el contorno de una forma de corazón eliminando el segmento en el índice basado en cero 2 con GeometryPath.removeAt. Aplicar la ruta modificada con IGeometryShape.setGeometryPath actualiza el contorno de la forma.
import com.aspose.slides.*;
Presentation presentation = new Presentation();
try {
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Heart, 100, 100, 300, 300);
IGeometryPath path = shape.getGeometryPaths()[0];
path.removeAt(2);
shape.setGeometryPath(path);
} finally {
presentation.dispose();
}

Crear una forma personalizada
Este ejemplo construye una estrella de cinco puntas con GeometryPath. Los radios exteriores e interiores alternados de 100 y 50 puntos definen las puntas y las hendiduras; un paso de 72 grados separa las cinco puntas de forma uniforme. La ruta se cierra con GeometryPath.closeFigure y se aplica a una forma rectangular usando IGeometryShape.setGeometryPath, sustituyendo su contorno.
import com.aspose.slides.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
List<Point2D.Float> points = new ArrayList<>();
float outerRadius = 100f;
float innerRadius = 50f;
int angleStep = 72;
for (int angle = -90; angle < 270; angle += angleStep)
{
double radians = angle * (Math.PI / 180f);
double x = outerRadius * Math.cos(radians);
double y = outerRadius * Math.sin(radians);
Point2D.Float point = new Point2D.Float((float)x + outerRadius, (float)y + outerRadius);
points.add(point);
radians = Math.PI * (angle + angleStep / 2) / 180.0;
x = innerRadius * Math.cos(radians);
y = innerRadius * Math.sin(radians);
point = new Point2D.Float((float)x + outerRadius, (float)y + outerRadius);
points.add(point);
}
GeometryPath starPath = new GeometryPath();
starPath.moveTo(points.get(0));
for (int i = 1; i < points.size(); i++)
{
starPath.lineTo(points.get(i));
}
starPath.closeFigure();
Presentation presentation = new Presentation();
try {
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, outerRadius * 2, outerRadius * 2);
shape.setGeometryPath(starPath);
} finally {
presentation.dispose();
}

Crear una forma personalizada compuesta
Este ejemplo define dos objetos GeometryPath cerrados para el tercio superior e inferior de una forma. Aplicar ambas rutas con IGeometryShape.setGeometryPaths crea una única forma compuesta con un hueco en su tercio central.
import com.aspose.slides.*;
Presentation presentation = new Presentation();
try {
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);
GeometryPath topPath = new GeometryPath();
topPath.moveTo(0, 0);
topPath.lineTo(shape.getWidth(), 0);
topPath.lineTo(shape.getWidth(), shape.getHeight() / 3);
topPath.lineTo(0, shape.getHeight() / 3);
topPath.closeFigure();
GeometryPath bottomPath = new GeometryPath();
bottomPath.moveTo(0, shape.getHeight() / 3 * 2);
bottomPath.lineTo(shape.getWidth(), shape.getHeight() / 3 * 2);
bottomPath.lineTo(shape.getWidth(), shape.getHeight());
bottomPath.lineTo(0, shape.getHeight());
bottomPath.closeFigure();
shape.setGeometryPaths(new GeometryPath[] { topPath, bottomPath });
} finally {
presentation.dispose();
}

Crear una forma personalizada con esquinas curvas
Este ejemplo combina segmentos rectos con GeometryPath.arcTo para crear una forma con esquinas curvas hacia adentro. Cada tamaño de esquina establece el ancho y alto del arco en puntos, y el barrido de -90 grados dibuja un cuarto de elipse en sentido antihorario. La ruta completada se aplica con IGeometryShape.setGeometryPath, y la presentación se guarda en formato PPTX.
import com.aspose.slides.*;
import java.awt.geom.Point2D;
float shapeX = 20f;
float shapeY = 20f;
float shapeWidth = 300f;
float shapeHeight = 200f;
float leftTopSize = 50f;
float rightTopSize = 20f;
float rightBottomSize = 40f;
float leftBottomSize = 10f;
Presentation presentation = new Presentation();
try {
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Custom, shapeX, shapeY, shapeWidth, shapeHeight);
Point2D.Float topLeft = new Point2D.Float(leftTopSize, 0);
Point2D.Float topRight = new Point2D.Float(shapeWidth - rightTopSize, 0);
Point2D.Float rightBottom = new Point2D.Float(shapeWidth, shapeHeight - rightBottomSize);
Point2D.Float bottomLeft = new Point2D.Float(leftBottomSize, shapeHeight);
Point2D.Float leftTop = new Point2D.Float(0, leftTopSize);
GeometryPath geometryPath = new GeometryPath();
geometryPath.moveTo(topLeft);
geometryPath.lineTo(topRight);
geometryPath.arcTo(rightTopSize, rightTopSize, 180, -90);
geometryPath.lineTo(rightBottom);
geometryPath.arcTo(rightBottomSize, rightBottomSize, -90, -90);
geometryPath.lineTo(bottomLeft);
geometryPath.arcTo(leftBottomSize, leftBottomSize, 0, -90);
geometryPath.lineTo(leftTop);
geometryPath.arcTo(leftTopSize, leftTopSize, 90, -90);
geometryPath.closeFigure();
shape.setGeometryPath(geometryPath);
presentation.save("output.pptx", SaveFormat.Pptx);
} finally {
presentation.dispose();
}
Descubrir si la geometría de una forma está cerrada
El siguiente asistente verifica si cada ruta de geometría no vacía termina con un segmento de cierre. Lee IGeometryPath.getPathData y compara el comando de la ruta del segmento final IPathSegment.getPathCommand con PathCommandType.Close. Devuelve false cuando no hay rutas no vacías. Esto comprueba el comando final de cada ruta; no inspecciona cada subfigura dentro de una ruta. El ejemplo imprime true para un rectángulo.
import com.aspose.slides.*;
class GeometryInspector {
boolean isGeometryClosed(IGeometryShape geometryShape) {
boolean hasNonemptyPath = false;
for (IGeometryPath geometryPath : geometryShape.getGeometryPaths()) {
IPathSegment[] segments = geometryPath.getPathData();
if (segments.length == 0) {
continue;
}
hasNonemptyPath = true;
IPathSegment lastSegment = segments[segments.length - 1];
if (lastSegment.getPathCommand() != PathCommandType.Close) {
return false;
}
}
return hasNonemptyPath;
}
}
Presentation presentation = new Presentation();
try {
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);
GeometryInspector inspector = new GeometryInspector();
boolean closed = inspector.isGeometryClosed(shape);
System.out.println(closed);
} finally {
presentation.dispose();
}
Extraer e inspeccionar elementos de forma
Utiliza IGeometryShape.createShapeElements para inspeccionar una forma existente como elementos que contienen puntos de ruta y tipos de punto. Convierte cada IShapeElement devuelto a ShapeElement para obtener las matrices de puntos y tipos mediante los métodos getPathPoints y getPathTypes . Esta representación es útil para el análisis de contornos o para pasar la geometría a herramientas de dibujo personalizadas.
El ejemplo abre input.pptx, encuentra la primera forma de geometría directamente en la primera diapositiva y muestra sus segmentos de ruta. Usa una presentación que contenga una forma con segmentos rectos y curvos para ver ambos tipos de salida. No busca dentro de los grupos.
import com.aspose.slides.*;
import java.awt.geom.Point2D;
Presentation presentation = new Presentation("input.pptx");
try {
ISlide slide = presentation.getSlides().get_Item(0);
IGeometryShape shape = null;
for (IShape candidate : slide.getShapes()) {
if (candidate instanceof IGeometryShape) {
shape = (IGeometryShape) candidate;
break;
}
}
if (shape == null) {
System.out.println("No geometry shape found on the first slide.");
} else {
for (IShapeElement element : shape.createShapeElements()) {
ShapeElement shapeElement = (ShapeElement) element;
Point2D.Float[] points = shapeElement.getPathPoints();
byte[] types = shapeElement.getPathTypes();
System.out.println("Shape element:");
for (int i = 0; i < points.length; i++) {
int pointType = types[i] & 0x07; // Mantener el tipo de segmento, excluyendo banderas.
switch (pointType) {
case 0:
System.out.println("Start figure at " + points[i]);
break;
case 1:
System.out.println("Line to " + points[i]);
break;
case 3:
System.out.println("Cubic Bezier: controls " + points[i] + ", " + points[i + 1] + "; end " + points[i + 2]);
i += 2; // Consumir ambos puntos de control y el punto final.
break;
}
if ((types[i] & 0x80) != 0) {
System.out.println("Close subpath back to its starting point.");
}
}
}
}
} finally {
presentation.dispose();
}
Las matrices corresponden por índice: points[i] es un punto y types[i] describe su función. El tipo 0 abre una figura, y el tipo 1 dibuja una línea desde el punto anterior al punto actual. Un segmento Bézier cúbico comienza en el punto final anterior y utiliza tres entradas consecutivas de tipo 3: dos puntos de control seguidos del nuevo punto final. El bucle consume esas tres entradas juntas y verifica el punto final para el cierre.
Los tipos de punto también contienen banderas. Aplica una máscara con 0x07 para identificar el tipo de segmento y prueba 0x80 por separado para detectar un subtrazo cerrado. Por ejemplo, 129 (0x81) significa un punto final de línea que cierra un subtrazo, y 131 (0x83) significa un punto final Bézier que cierra uno. Java almacena estas banderas en bytes con signo; la máscara preserva los valores de bits previstos. Comparar el valor bruto solo con 1 o 3 omitiría estos casos. Otras banderas, como guión (0x10) y marcador (0x20), no cambian el tipo de segmento base.
Coordenadas y transformaciones. Los puntos se miden en puntos (1/72 de pulgada), con X aumentando hacia la derecha y Y aumentando hacia abajo. Para una forma sin agrupar, las coordenadas extraídas ya incluyen la posición devuelta por getX y getY y reflejan su ancho y alto actuales. Por ejemplo, un rectángulo sin rotar en (100, 200) con tamaño 120 × 80 tiene límites de contorno (100, 200)–(220, 280). No añadas de nuevo la posición de la forma. Resta su X y Y para obtener coordenadas relativas a su marco sin rotar.
Los puntos extraídos no aplican la rotación de la forma, devuelta por getRotation, ni los giros horizontales y verticales. Para reproducir el contorno mostrado, aplica esas transformaciones alrededor del centro de la forma. Para formas agrupadas, también ten en cuenta la asignación de coordenadas del grupo y sus transformaciones; no asumas que los puntos extraídos son las coordenadas finales renderizadas de la diapositiva ni apliques una traslación o escala dos veces si ya están reflejadas en el marco del hijo.
Inspección versus edición. Las matrices extraídas describen el contorno; modificarlas no actualiza la forma original. Elige este enfoque cuando tu análisis o código de dibujo necesite matrices de punto/tipo. Usa getGeometryPaths cuando necesites comandos de geometría en el sistema de coordenadas local de la forma o quieras editar su geometría. Aplica rutas editadas mediante setGeometryPath o setGeometryPaths.
Convertir una forma Java AWT a GeometryPath
Este ejemplo combina un contorno rectangular con texto convertido a geometría. El método setFillMode del contorno desactiva su relleno. Un GlyphVector crea el contorno del texto en un java.awt.Shape. Su PathIterator proporciona segmentos de movimiento, línea, cuadrática, cúbica y cierre, que el ejemplo reproduce en una GeometryPath.
El texto usa una fuente Arial de 40 puntos. El contorno está desplazado 20 puntos horizontalmente y posicionado con su parte superior visual 10 puntos bajo el borde superior de la forma. La ruta del texto está rellena, mientras que el rectángulo original sigue siendo un contorno. Aplicar ambas rutas con IGeometryShape.setGeometryPaths produce una única forma que contiene el rectángulo y el texto.
import com.aspose.slides.*;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.font.GlyphVector;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.geom.PathIterator;
import java.awt.image.BufferedImage;
Presentation presentation = new Presentation();
try {
// Crear nueva forma
ISlide slide = presentation.getSlides().get_Item(0);
IAutoShape shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 300, 100);
// Obtener la ruta de geometría de la forma
IGeometryPath originalPath = shape.getGeometryPaths()[0];
originalPath.setFillMode(PathFillModeType.None);
// Crear nueva ruta gráfica con texto
Shape graphicsPath;
Font font = new Font("Arial", Font.PLAIN, 40);
String text = "Text in shape";
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
try
{
FontRenderContext fontContext = graphics.getFontRenderContext();
GlyphVector glyphVector = font.createGlyphVector(fontContext, text);
Rectangle2D textBounds = glyphVector.getVisualBounds();
float baseline = (float) -textBounds.getY() + 10;
graphicsPath = glyphVector.getOutline(20f, baseline);
}
finally {
graphics.dispose();
}
// Convertir la ruta gráfica a ruta de geometría
IGeometryPath textPath = new GeometryPath();
PathIterator pathIterator = graphicsPath.getPathIterator(null);
float[] points = new float[6];
while (!pathIterator.isDone())
{
int segmentType = pathIterator.currentSegment(points);
switch (segmentType)
{
case PathIterator.SEG_MOVETO:
textPath.moveTo(points[0], points[1]);
break;
case PathIterator.SEG_LINETO:
textPath.lineTo(points[0], points[1]);
break;
case PathIterator.SEG_QUADTO:
textPath.quadraticBezierTo(points[0], points[1], points[2], points[3]);
break;
case PathIterator.SEG_CUBICTO:
textPath.cubicBezierTo(points[0], points[1], points[2], points[3], points[4], points[5]);
break;
case PathIterator.SEG_CLOSE:
textPath.closeFigure();
break;
}
pathIterator.next();
}
textPath.setFillMode(PathFillModeType.Normal);
// Establecer la combinación de la nueva ruta de geometría y la ruta de geometría original en la forma
shape.setGeometryPaths(new IGeometryPath[] { originalPath, textPath });
} finally {
presentation.dispose();
}

Preguntas frecuentes
¿Qué ocurrirá con el relleno y el contorno después de reemplazar la geometría?
El estilo permanece con la forma; solo cambia el contorno. El relleno y el contorno se aplican automáticamente a la nueva geometría.
¿Cómo rotar correctamente una forma personalizada junto con su geometría?
Utiliza el método setRotation de la forma; la geometría rota con la forma porque está vinculada al propio sistema de coordenadas de la forma.
¿Puedo convertir una forma personalizada a una imagen para “bloquear” el resultado?
Sí. Exporta la zona de la slide requerida o la propia shape a un formato raster; esto simplifica el trabajo posterior con geometrías complejas.