Personalizar formas de presentación en PHP

Visión general

Este artículo explica cómo personalizar las formas de presentación en Aspose.Slides editando la geometría de la forma mediante puntos de edición y rutas de geometría. Muestra cómo trabajar con GeometryPath 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 curvadas, determinar si la geometría de una forma está cerrada, inspeccionar los elementos de la forma y convertir contornos de texto de Java AWT en rutas de geometría.

Cambiar una forma mediante puntos de edición

En PowerPoint, puedes usar Puntos de edición 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 editar formas

overview_image

Al editar formas de 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 la misma distancia del punto de anclaje.
    • En un punto recto, los dos manejadores de control están alineados pero pueden estar a distintas 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 proporciona la clase 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) dentro de 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
Definir el punto inicial 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 GeometryShape::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 colocan en los bordes superior e inferior, tirando esos bordes hacia dentro.

use aspose\slides\Presentation;
use aspose\slides\ShapeType;

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);

    $shape = $slide->getShapes()->addAutoShape(ShapeType::Rectangle, 100, 100, 200, 100);

    $geometryPath = $shape->getGeometryPaths()[0];
    $geometryPath->lineTo(100, 50, 1);
    $geometryPath->lineTo(100, 50, 4);

    $shape->setGeometryPath($geometryPath);
} finally {
    $presentation->dispose();
}

example1_image

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 GeometryShape::setGeometryPath actualiza el contorno de la forma.

use aspose\slides\Presentation;
use aspose\slides\ShapeType;

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);

    $shape = $slide->getShapes()->addAutoShape(ShapeType::Heart, 100, 100, 300, 300);

    $path = $shape->getGeometryPaths()[0];
    $path->removeAt(2);

    $shape->setGeometryPath($path);
} finally {
    $presentation->dispose();
}

example2_image

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 indentaciones; un paso de 72 grados separa uniformemente las cinco puntas. La ruta se cierra con GeometryPath::closeFigure y se aplica a una forma rectangular mediante GeometryShape::setGeometryPath, sustituyendo su contorno.

use aspose\slides\GeometryPath;
use aspose\slides\Presentation;
use aspose\slides\ShapeType;

$points = [];
$outerRadius = 100;
$innerRadius = 50;
$angleStep = 72;
for ($angle = -90; $angle < 270; $angle += $angleStep) {
    $radians = deg2rad($angle);
    $points[] = [$outerRadius * cos($radians) + $outerRadius, $outerRadius * sin($radians) + $outerRadius];
    $radians = deg2rad($angle + $angleStep / 2);
    $points[] = [$innerRadius * cos($radians) + $outerRadius, $innerRadius * sin($radians) + $outerRadius];
}

$starPath = new GeometryPath();
$starPath->moveTo($points[0][0], $points[0][1]);
for ($i = 1; $i < count($points); $i++) {
    $starPath->lineTo($points[$i][0], $points[$i][1]);
}
$starPath->closeFigure();

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);
    $shape = $slide->getShapes()->addAutoShape(ShapeType::Rectangle, 100, 100, $outerRadius * 2, $outerRadius * 2);
    $shape->setGeometryPath($starPath);
} finally {
    $presentation->dispose();
}

example3_image

Crear una forma compuesta personalizada

Este ejemplo define dos GeometryPath cerrados para el tercio superior y el tercio inferior de una forma. Aplicar ambas rutas con GeometryShape::setGeometryPaths crea una única forma compuesta con un hueco en el tercio medio.

use aspose\slides\GeometryPath;
use aspose\slides\Presentation;
use aspose\slides\ShapeType;

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);

    $shape = $slide->getShapes()->addAutoShape(ShapeType::Rectangle, 100, 100, 200, 100);

    $topPath = new GeometryPath();
    $topPath->moveTo(0, 0);
    $topPath->lineTo(java_values($shape->getWidth()), 0);
    $topPath->lineTo(java_values($shape->getWidth()), java_values($shape->getHeight()) / 3);
    $topPath->lineTo(0, java_values($shape->getHeight()) / 3);
    $topPath->closeFigure();

    $bottomPath = new GeometryPath();
    $bottomPath->moveTo(0, java_values($shape->getHeight()) / 3 * 2);
    $bottomPath->lineTo(java_values($shape->getWidth()), java_values($shape->getHeight()) / 3 * 2);
    $bottomPath->lineTo(java_values($shape->getWidth()), java_values($shape->getHeight()));
    $bottomPath->lineTo(0, java_values($shape->getHeight()));
    $bottomPath->closeFigure();

    $shape->setGeometryPaths([$topPath, $bottomPath]);
} finally {
    $presentation->dispose();
}

example4_image

Crear una forma personalizada con esquinas curvadas

Este ejemplo combina segmentos rectos con GeometryPath::arcTo para crear una forma con esquinas curvadas hacia adentro. Cada tamaño de esquina define el ancho y alto del arco en puntos, y el barrido de ‑90 grados dibuja un cuarto de elipse en sentido antihorario. La ruta completa se aplica con GeometryShape::setGeometryPath y la presentación se guarda en formato PPTX.

use aspose\slides\GeometryPath;
use aspose\slides\Point2DFloat;
use aspose\slides\Presentation;
use aspose\slides\SaveFormat;
use aspose\slides\ShapeType;

$shapeX = 20;
$shapeY = 20;
$shapeWidth = 300;
$shapeHeight = 200;

$leftTopSize = 50;
$rightTopSize = 20;
$rightBottomSize = 40;
$leftBottomSize = 10;

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);

    $shape = $slide->getShapes()->addAutoShape(ShapeType::Custom, $shapeX, $shapeY, $shapeWidth, $shapeHeight);

    $topLeft = new Point2DFloat($leftTopSize, 0);
    $topRight = new Point2DFloat($shapeWidth - $rightTopSize, 0);
    $rightBottom = new Point2DFloat($shapeWidth, $shapeHeight - $rightBottomSize);
    $bottomLeft = new Point2DFloat($leftBottomSize, $shapeHeight);
    $leftTop = new Point2DFloat(0, $leftTopSize);

    $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();
}

Determinar si la geometría de una forma está cerrada

El siguiente asistente comprueba si cada ruta de geometría no vacía termina con un segmento de cierre. Lee GeometryPath::getPathData y compara el comando del último segmento mediante PathSegment::getPathCommand con PathCommandType::Close. Devuelve false cuando no existen rutas no vacías. Esta comprobación analiza el comando final de cada ruta; no inspecciona cada subfigura dentro de una ruta. El ejemplo muestra true para un rectángulo.

use aspose\slides\PathCommandType;
use aspose\slides\Presentation;
use aspose\slides\ShapeType;

function isGeometryClosed($geometryShape)
{
    $hasNonemptyPath = false;
    foreach ($geometryShape->getGeometryPaths() as $geometryPath) {
        $segments = java_values($geometryPath->getPathData());
        if (count($segments) === 0) {
            continue;
        }
        $hasNonemptyPath = true;
        $lastSegment = $segments[count($segments) - 1];
        if (java_values($lastSegment->getPathCommand()) !== PathCommandType::Close) {
            return false;
        }
    }
    return $hasNonemptyPath;
}

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);
    $shape = $slide->getShapes()->addAutoShape(ShapeType::Rectangle, 100, 100, 200, 100);
    $closed = isGeometryClosed($shape);
    echo ($closed ? "true" : "false") . PHP_EOL;
} finally {
    $presentation->dispose();
}

Extraer e inspeccionar los elementos de una forma

Utiliza GeometryShape::createShapeElements para inspeccionar una forma existente como elementos que contienen puntos de ruta y tipos de punto. Cada ShapeElement devuelto permite obtener las matrices de puntos y tipos mediante los métodos getPathPoints y getPathTypes. Esta representación es útil para análisis de contorno 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 grupos.

use aspose\slides\Presentation;

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

    $shape = null;
    $shapes = $slide->getShapes();
    for ($shapeIndex = 0; $shapeIndex < java_values($shapes->size()); $shapeIndex++) {
        $candidate = $shapes->get_Item($shapeIndex);
        if (java_instanceof($candidate, java("com.aspose.slides.GeometryShape"))) {
            $shape = $candidate;
            break;
        }
    }

    if ($shape === null) {
        echo "No geometry shape found on the first slide." . PHP_EOL;
    } else {
        foreach ($shape->createShapeElements() as $element) {
            $points = java_values($element->getPathPoints());
            $types = array_values(unpack("C*", java_values($element->getPathTypes())));
            echo "Shape element:" . PHP_EOL;
            for ($i = 0; $i < count($points); $i++) {
                $pointType = $types[$i] & 0x07; // Mantener el tipo de segmento, excluyendo banderas.
                switch ($pointType) {
                    case 0:
                        echo "Start figure at " . $points[$i] . PHP_EOL;
                        break;
                    case 1:
                        echo "Line to " . $points[$i] . PHP_EOL;
                        break;
                    case 3:
                        echo "Cubic Bezier: controls " . $points[$i] . ", " . $points[$i + 1] . "; end " . $points[$i + 2] . PHP_EOL;
                        $i += 2; // Consumir ambos puntos de control y el punto final.
                        break;
                }
                if (($types[$i] & 0x80) !== 0) {
                    echo "Close subpath back to its starting point." . PHP_EOL;
                }
            }
        }
    }
} finally {
    $presentation->dispose();
}

Las matrices se corresponden por índice: $points[$i] es un punto y $types[$i] describe su función. El tipo 0 inicia una figura, y el tipo 1 dibuja una línea desde el punto anterior hasta el 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 detectar 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 una subruta cerrada. Por ejemplo, 129 (0x81) indica un extremo de línea que cierra una subruta, y 131 (0x83) indica un extremo Bézier que cierra una subruta. El puente PHP/Java devuelve la matriz de bytes como cadena binaria; al desempaquetarla con "C*" se obtienen valores de byte sin signo. Mascarar preserva los bits previstos. Comparar el valor bruto solo con 1 o 3 omitirá estos casos. Otras banderas, como guión (0x10) y marcador (0x20), no cambian el tipo base del segmento.

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 anchura y altura actuales. Por ejemplo, un rectángulo no rotado en (100, 200) con tamaño 120 × 80 tiene límites de contorno (100, 200)–(220, 280). No vuelvas a añadir la posición de la forma. Resta su X e 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 volteos horizontal y vertical. 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 y transformaciones del grupo; no asumas que los puntos extraídos son las coordenadas finales renderizadas en la diapositiva ni apliques una traslación o escala dos veces si ya está reflejada en el marco del hijo.

Inspección frente a edición. Las matrices extraídas describen el contorno; modificarlas no actualiza la forma original. Usa este enfoque cuando tu código de análisis o dibujo necesite matrices de puntos/tipos. Utiliza 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 suministra segmentos de movimiento, línea, cuadrática, cúbica y cierre, que el ejemplo reproduce en una GeometryPath.

El ejemplo usa una matriz de flotantes Java para que el puente PHP/Java conserve las coordenadas escritas por el iterador. El texto utiliza una fuente Arial de 40 puntos. El contorno se desplaza 20 puntos horizontalmente y se posiciona con su parte superior visual 10 puntos por debajo del borde superior de la forma. La ruta del texto se rellena, mientras que el rectángulo original queda sólo como contorno. Aplicar ambas rutas con GeometryShape::setGeometryPaths produce una única forma que contiene el rectángulo y el texto.

use aspose\slides\GeometryPath;
use aspose\slides\PathFillModeType;
use aspose\slides\Presentation;
use aspose\slides\ShapeType;

$presentation = new Presentation();
try {
    $slide = $presentation->getSlides()->get_Item(0);

    $shape = $slide->getShapes()->addAutoShape(ShapeType::Rectangle, 100, 100, 300, 100);
    
    $originalPath = $shape->getGeometryPaths()[0];
    $originalPath->setFillMode(PathFillModeType::None);

    $fontClass = java("java.awt.Font");
    $imageClass = java("java.awt.image.BufferedImage");
    $font = new Java("java.awt.Font", "Arial", $fontClass->PLAIN, 40);
    $image = new Java("java.awt.image.BufferedImage", 100, 100, $imageClass->TYPE_INT_ARGB);
    $graphics = $image->createGraphics();
    try {
        $fontContext = $graphics->getFontRenderContext();
        $glyphVector = $font->createGlyphVector($fontContext, "Text in shape");
        $textBounds = $glyphVector->getVisualBounds();
        $baseline = -java_values($textBounds->getY()) + 10;
        $graphicsPath = $glyphVector->getOutline(20.0, $baseline);
    } finally {
        $graphics->dispose();
    }

    $textPath = new GeometryPath();
    $pathIterator = $graphicsPath->getPathIterator(null);
    $iteratorClass = java("java.awt.geom.PathIterator");
    $arrayClass = java("java.lang.reflect.Array");
    $floatClass = java("java.lang.Float");
    $coordinates = $arrayClass->newInstance($floatClass->TYPE, 6);

    while (!java_values($pathIterator->isDone())) {
        $segmentType = java_values($pathIterator->currentSegment($coordinates));
        $points = java_values($coordinates);
        switch ($segmentType) {
            case java_values($iteratorClass->SEG_MOVETO):
                $textPath->moveTo($points[0], $points[1]);
                break;
            case java_values($iteratorClass->SEG_LINETO):
                $textPath->lineTo($points[0], $points[1]);
                break;
            case java_values($iteratorClass->SEG_QUADTO):
                $textPath->quadraticBezierTo($points[0], $points[1], $points[2], $points[3]);
                break;
            case java_values($iteratorClass->SEG_CUBICTO):
                $textPath->cubicBezierTo($points[0], $points[1], $points[2], $points[3], $points[4], $points[5]);
                break;
            case java_values($iteratorClass->SEG_CLOSE):
                $textPath->closeFigure();
                break;
        }
        $pathIterator->next();
    }
    $textPath->setFillMode(PathFillModeType::Normal);
    $shape->setGeometryPaths([$originalPath, $textPath]);
} finally {
    $presentation->dispose();
}

example5_image

Preguntas frecuentes

¿Qué ocurrirá con el relleno y el contorno después de reemplazar la geometría?

El estilo permanece asociado a la forma; sólo 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 gira con la forma porque está vinculada al propio sistema de coordenadas de la forma.

¿Puedo convertir una forma personalizada en una imagen para “bloquear” el resultado”?

Sí. Exporta el área de la diapositiva requerida o la propia forma a un formato raster; esto simplifica el trabajo posterior con geometrías complejas.