Настройка фигур презентаций в .NET
Обзор
Эта статья объясняет, как настраивать фигуры презентаций в Aspose.Slides путем редактирования геометрии фигур через точки редактирования и пути геометрии. Показано, как работать с GeometryPath и IGeometryPath для изменения существующих фигур, выполнения базовых операций редактирования пути, добавления или удаления точек и применения обновлённой геометрии к фигуре.
Также демонстрируется, как создавать пользовательские и составные фигуры, строить фигуры со скруглёнными углами, определять, закрыта ли геометрия фигуры, и использовать устаревшее преобразование между GeometryPath и GraphicsPath для дополнительных сценариев настройки геометрии.
Изменение фигуры с помощью точек редактирования
В PowerPoint можно использовать Edit Points для изменения квадрата:
- Переместить угол внутрь или наружу.
- Отрегулировать кривизну в точке.
- Добавить новые точки.
Редактирование точек позволяет изменять контур фигуры или использовать существующую фигуру в качестве основы для новой.
Советы по редактированию фигур

При редактировании фигур PowerPoint учитывайте следующие концепции:
- Фигура (или её путь) может быть закрытой или открытой.
- Путь фигуры состоит из контрольных точек, соединённых сегментами.
- Сегмент может быть прямым или кривым. Контрольные точки и ручки управления определяют его форму.
- Контрольные точки бывают угловыми, прямыми или гладкими:
- В угловой точке смежные сегменты могут соединяться под углом.
- В гладкой точке две ручки управления выровнены и находятся на одинаковом расстоянии от контрольной точки.
- В прямой точке две ручки управления выровнены, но могут находиться на разном расстоянии от контрольной точки.
- Перемещение контрольных точек или изменение их ручек меняет контур фигуры.
Для редактирования фигур PowerPoint через точки редактирования Aspose.Slides предоставляет класс GeometryPath и интерфейс IGeometryPath.
- Экземпляр GeometryPath представляет путь геометрии объекта IGeometryShape.
- Чтобы получить GeometryPath из экземпляра IGeometryShape, можно использовать метод IGeometryShape.GetGeometryPaths.
- Чтобы задать GeometryPath для фигуры, можно использовать методы: IGeometryShape.SetGeometryPath для фигур с одним путем и IGeometryShape.SetGeometryPaths для фигур с несколькими путями.
- Чтобы добавить сегменты, можно использовать методы из IGeometryPath.
- С помощью свойств IGeometryPath.Stroke и IGeometryPath.FillMode можно задать внешний вид пути геометрии.
- Свойство IGeometryPath.PathData позволяет получить путь геометрии GeometryShape в виде массива сегментов пути.
- В устаревших версиях, поддерживающих методы конвертации, можно преобразовать GeometryPath в GraphicsPath.
- Используйте методы GeometryPathToGraphicsPath и GraphicsPathToGeometryPath (из класса ShapeUtil) для преобразования между GeometryPath и GraphicsPath.
Простые операции редактирования
Используйте перечисленные методы для редактирования пути геометрии. Перегрузки, принимающие индекс, вставляют сегмент в позицию с нулевым базовым индексов в массиве сегментов пути.
| Операция | Метод |
|---|---|
| Добавление или вставка прямой линии. | GeometryPath.LineTo |
| Добавление или вставка кубической кривой Безье с двумя контрольными точками и конечной точкой. | GeometryPath.CubicBezierTo |
| Добавление или вставка квадратичной кривой Безье с одной контрольной точкой и конечной точкой. | GeometryPath.QuadraticBezierTo |
| Добавление эллиптической дуги с указанными размерами и углами в градусах. | GeometryPath.ArcTo |
| Замыкание текущей фигуры соединением её конечной точки с начальной. | GeometryPath.CloseFigure |
| Установка начальной точки фигуры. | GeometryPath.MoveTo |
| Удаление сегмента по нулевому базовому индексу. | GeometryPath.RemoveAt |
Добавление пользовательских точек к фигуре
Этот пример вставляет две точки в прямоугольную фигуру с помощью GeometryPath.LineTo и применяет отредактированный путь с помощью IGeometryShape.SetGeometryPath. Координаты локальны для фигуры: обе новые точки находятся в её центре, на 100 пунктов от левого края и 50 пунктов от верхнего. Индексы сегментов 1 и 4 размещают их на верхнем и нижнем краях, притягивая эти края внутрь.
using Aspose.Slides;
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var shape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);
var geometryPath = shape.GetGeometryPaths()[0];
geometryPath.LineTo(100, 50, 1);
geometryPath.LineTo(100, 50, 4);
shape.SetGeometryPath(geometryPath);

Удаление точек из фигуры
Этот пример изменяет контур в виде сердца, удаляя сегмент с нулевым базовым индексом 2 с помощью GeometryPath.RemoveAt. Применение изменённого пути через IGeometryShape.SetGeometryPath обновляет контур фигуры.
using Aspose.Slides;
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var shape = slide.Shapes.AddAutoShape(ShapeType.Heart, 100, 100, 300, 300);
var path = shape.GetGeometryPaths()[0];
path.RemoveAt(2);
shape.SetGeometryPath(path);

Создание пользовательской фигуры
В этом примере создаётся пятиконечная звезда с помощью GeometryPath. Чередующиеся внешние и внутренние радиусы 100 и 50 пунктов определяют вершины и впадины; шаг 72 градуса равномерно распределяет пять вершин. Путь замыкается с помощью GeometryPath.CloseFigure и применяется к прямоугольной фигуре через IGeometryShape.SetGeometryPath, заменяя её контур.
using System;
using System.Collections.Generic;
using System.Drawing;
using Aspose.Slides;
var points = new List<PointF>();
var outerRadius = 100f;
var innerRadius = 50f;
var angleStep = 72;
for (var angle = -90; angle < 270; angle += angleStep)
{
var radians = angle * (Math.PI / 180f);
var x = outerRadius * Math.Cos(radians);
var y = outerRadius * Math.Sin(radians);
points.Add(new PointF((float)x + outerRadius, (float)y + outerRadius));
radians = Math.PI * (angle + angleStep / 2) / 180.0;
x = innerRadius * Math.Cos(radians);
y = innerRadius * Math.Sin(radians);
points.Add(new PointF((float)x + outerRadius, (float)y + outerRadius));
}
var starPath = new GeometryPath();
starPath.MoveTo(points[0]);
for (var i = 1; i < points.Count; i++)
{
starPath.LineTo(points[i]);
}
starPath.CloseFigure();
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var shape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 100, 100, outerRadius * 2, outerRadius * 2);
shape.SetGeometryPath(starPath);

Создание составной пользовательской фигуры
В этом примере определяются два закрытых GeometryPath для верхней и нижней трети фигуры. Применение обоих путей через IGeometryShape.SetGeometryPaths создаёт одну составную фигуру с разрывом в средней трети.
using Aspose.Slides;
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var shape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);
var topPath = new GeometryPath();
topPath.MoveTo(0, 0);
topPath.LineTo(shape.Width, 0);
topPath.LineTo(shape.Width, shape.Height / 3);
topPath.LineTo(0, shape.Height / 3);
topPath.CloseFigure();
var bottomPath = new GeometryPath();
bottomPath.MoveTo(0, shape.Height / 3 * 2);
bottomPath.LineTo(shape.Width, shape.Height / 3 * 2);
bottomPath.LineTo(shape.Width, shape.Height);
bottomPath.LineTo(0, shape.Height);
bottomPath.CloseFigure();
shape.SetGeometryPaths(new GeometryPath[] { topPath, bottomPath });

Создание пользовательской фигуры со скруглёнными углами
Этот пример комбинирует прямые сегменты с GeometryPath.ArcTo для создания фигуры со скруглёнными внутреннями углами. Размер каждого угла задаёт ширину и высоту дуги в пунктах, а sweep -90 градус записывает против часовой стрелки четверть эллипса. Завершённый путь применяется через IGeometryShape.SetGeometryPath, а презентация сохраняется в формате PPTX.
using System.Drawing;
using Aspose.Slides;
using Aspose.Slides.Export;
var shapeX = 20f;
var shapeY = 20f;
var shapeWidth = 300f;
var shapeHeight = 200f;
var leftTopSize = 50f;
var rightTopSize = 20f;
var rightBottomSize = 40f;
var leftBottomSize = 10f;
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var topLeft = new PointF(leftTopSize, 0);
var topRight = new PointF(shapeWidth - rightTopSize, 0);
var rightBottom = new PointF(shapeWidth, shapeHeight - rightBottomSize);
var bottomLeft = new PointF(leftBottomSize, shapeHeight);
var leftTop = new PointF(0, leftTopSize);
var 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();
var shape = slide.Shapes.AddAutoShape(ShapeType.Custom, shapeX, shapeY, shapeWidth, shapeHeight);
shape.SetGeometryPath(geometryPath);
presentation.Save("output.pptx", SaveFormat.Pptx);
Определение, закрыта ли геометрия фигуры
Следующий вспомогательный метод проверяет, заканчивается ли каждый непустой путь геометрии закрывающим сегментом. Он читает IGeometryPath.PathData и сравнивает конечный сегмент’s IPathSegment.PathCommand с PathCommandType.Close. Возвращает false, если нет непустых путей. Проверка осуществляется только по финальной команде каждого пути; субфигуры внутри пути не просматриваются. Пример выводит True для прямоугольника.
using System;
using Aspose.Slides;
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var shape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 100, 100, 200, 100);
Console.WriteLine(IsGeometryClosed(shape));
bool IsGeometryClosed(IGeometryShape geometryShape)
{
var hasNonemptyPath = false;
foreach (var geometryPath in geometryShape.GetGeometryPaths())
{
var segments = geometryPath.PathData;
if (segments.Length == 0)
{
continue;
}
hasNonemptyPath = true;
var lastSegment = segments[segments.Length - 1];
if (lastSegment.PathCommand != PathCommandType.Close)
{
return false;
}
}
return hasNonemptyPath;
}
Извлечение и инспекция элементов фигуры
Используйте IGeometryShape.CreateShapeElements для инспекции существующей фигуры как набора элементов, содержащих точки пути и типы точек. Приведите каждый возвращённый IShapeElement к типу ShapeElement для чтения его массивов PathPoints и PathTypes. Такое представление удобно для анализа контура или передачи геометрии пользовательским инструментам рисования.
Пример открывает презентацию, находит первую геометрическую фигуру непосредственно на первом слайде и выводит её сегменты пути. Используйте презентацию, содержащую фигуру с прямыми и кривыми сегментами, чтобы увидеть оба типа вывода. Поиск внутри групп не производится.
using System;
using System.Linq;
using Aspose.Slides;
using var presentation = new Presentation("input.pptx");
var slide = presentation.Slides[0];
var shape = slide.Shapes.OfType<IGeometryShape>().FirstOrDefault();
if (shape == null)
{
Console.WriteLine("No geometry shape found on the first slide.");
return;
}
foreach (IShapeElement element in shape.CreateShapeElements())
{
var shapeElement = (ShapeElement)element;
var points = shapeElement.PathPoints;
var types = shapeElement.PathTypes;
Console.WriteLine("Shape element:");
for (var i = 0; i < points.Length; i++)
{
var pointType = types[i] & 0x07; // Keep the segment type, excluding flags.
switch (pointType)
{
case 0:
Console.WriteLine($"Start figure at {points[i]}");
break;
case 1:
Console.WriteLine($"Line to {points[i]}");
break;
case 3:
Console.WriteLine($"Cubic Bezier: controls {points[i]}, {points[i + 1]}; end {points[i + 2]}");
i += 2; // Consume both control points and the endpoint.
break;
}
if ((types[i] & 0x80) != 0)
{
Console.WriteLine("Close subpath back to its starting point.");
}
}
}
Массивы сопоставляются по индексу: points[i] — точка, types[i] — её роль. Тип 0 начинается фигура, тип 1 — линия от предыдущей точки к текущей. Кубический сегмент Безье начинается в предыдущей конечной точке и использует три последовательных типа 3: два контрольных пункта и новую конечную точку. Цикл обрабатывает эти три записи вместе и проверяет конечную точку на замыкание.
Типы точек также содержат флаги. Маска 0x07 выделяет тип сегмента, а 0x80 проверяется отдельно для обнаружения закрытого подпотока. Например, 129 (0x81) означает конечную точку линии, закрывающую подпоток, а 131 (0x83) — конечную точку Безье, закрывающую подпоток. Сравнение «сырого» значения только с 1 или 3 упустит такие случаи. Другие флаги, такие как тире (0x10) и маркер (0x20), базовый тип сегмента не меняют.
Координаты и преобразования. Точки измеряются в пунктах (1/72 дюйма), X растёт вправо, Y — вниз. Для не сгруппированной фигуры извлечённые координаты уже включают её X и Y и отражают текущую ширину и высоту. Например, не вращённый прямоугольник в (100, 200) размером 120 × 80 имеет границы контура (100, 200)–(220, 280). Не добавляйте позицию фигуры повторно. Вычтите её X и Y, чтобы получить координаты относительно её ненавёрнутой рамки.
Извлечённые точки не учитывают Rotation фигуры и горизонтальное/вертикальное отражения. Чтобы восстановить отображаемый контур, примените эти преобразования вокруг центра фигуры. Для сгруппированных фигур также учитывайте преобразования координат группы; не предполагайте, что извлечённые точки уже являются окончательными координатами слайда, и не применяйте трансляцию или масштаб дважды, если они уже учтены в рамке дочернего объекта.
Инспекция против редактирования. Извлечённые массивы описывают контур; их изменение не обновит оригинальную фигуру. Выбирайте такой подход, когда нужен анализ точек/типов или пользовательский код рисования. Используйте GetGeometryPaths, когда нужны команды геометрии в локальной системе координат фигуры или требуется редактировать её геометрию. Применяйте отредактированные пути через SetGeometryPath или SetGeometryPaths.
Преобразование GeometryPath в GraphicsPath (System.Drawing.Drawing2D)
Следующий пример требует устаревшей сборки Aspose.Slides for .NET, поддерживающей ShapeUtil.GraphicsPathToGeometryPath, например версии 25.10, и Windows‑окружения с System.Drawing.Common. Методы конвертации были удалены в версии 25.11.
Пример комбинирует прямоугольный контур с текстом, преобразованным в геометрию. Для контура устанавливается IGeometryPath.FillMode — отключён заливка. GraphicsPath хранит контур текста, который ShapeUtil.GraphicsPathToGeometryPath преобразует в заполненный путь геометрии. Применение обеих фигур через IGeometryShape.SetGeometryPaths создаёт одну фигуру, содержащую прямоугольник и текст. Для обратного преобразования используйте ShapeUtil.GeometryPathToGraphicsPath.
using System.Drawing;
using System.Drawing.Drawing2D;
using Aspose.Slides;
using Aspose.Slides.Util;
using var presentation = new Presentation();
var slide = presentation.Slides[0];
var shape = slide.Shapes.AddAutoShape(ShapeType.Rectangle, 100, 100, 300, 100);
var originalPath = shape.GetGeometryPaths()[0];
originalPath.FillMode = PathFillModeType.None;
using var graphicsPath = new GraphicsPath();
using var fontFamily = new FontFamily("Arial");
using var stringFormat = StringFormat.GenericDefault;
graphicsPath.AddString("Text in shape", fontFamily, (int)FontStyle.Bold, 40, new PointF(10, 10), stringFormat);
var textPath = ShapeUtil.GraphicsPathToGeometryPath(graphicsPath);
textPath.FillMode = PathFillModeType.Normal;
shape.SetGeometryPaths(new[] { originalPath, textPath });

FAQ
Что происходит с заливкой и контуром после замены геометрии?
Стиль остаётся привязанным к фигуре; меняется только контур. Заливка и контур автоматически применяются к новой геометрии.
Как правильно вращать пользовательскую фигуру совместно с её геометрией?
Используйте свойство Rotation фигуры; геометрия вращается вместе с фигурой, так как привязана к её собственной системе координат.
Можно ли преобразовать пользовательскую фигуру в изображение, чтобы «зафиксировать» результат?
Да. Экспортируйте нужный slide или саму shape в растровый формат; это упрощает дальнейшую работу со сложными геометриями.