Рисование векторной графики на странице PDF
Рисование векторной графики на странице PDF
Aspose.PDF FOSS for C++ включает небольшую векторную графическую API для рисования фигур непосредственно на странице PDF во время построения документа. Точкой входа является Graph, контейнер, размещённый на странице и содержащий коллекцию объектов Shape; три конкретные фигуры — Circle, Ellipse и Line — наследуются от Shape и каждая описывает свою геометрию по-разному. Стиль обводки и заливки для каждой фигуры берётся из GraphInfo, а сам контейнер Graph несёт BorderInfo для оформления. Это не имеет отношения к растеризации страницы (BmpDevice, JpegDevice, TiffDevice), которая преобразует существующее содержимое страницы в растровые форматы изображений, а не рисует новую геометрию.
Контейнер графа и добавление фигур на страницу
Graph позиционирует и задаёт свой размер на странице с помощью Left(), Top(), Width() и Height(), а его флаг IsChangePosition() определяет, может ли эта позиция смещаться относительно другого содержимого страницы. Фигуры, которые он рисует, находятся в векторе, возвращаемом Shapes() — std::vector<std::unique_ptr<Shape>> — который заполняется путём создания фигуры, её настройки и перемещения в этот вектор. После построения сам Graph размещается на странице через Page.Paragraphs().Add(), ту же коллекцию, что используется для другого содержимого уровня страницы.
#include <aspose/pdf/document.hpp>
#include <aspose/pdf/drawing/graph.hpp>
#include <aspose/pdf/drawing/circle.hpp>
using namespace Aspose::Pdf;
using namespace Aspose::Pdf::Drawing;
Document doc;
Page page = doc.Pages().Add();
auto graph = std::make_shared<Graph>();
graph->Left(36.0);
graph->Top(36.0);
graph->Width(500.0);
graph->Height(300.0);
auto marker = std::make_unique<Circle>();
marker->PosX(60.0);
marker->PosY(60.0);
marker->Radius(30.0);
graph->Shapes().push_back(std::move(marker));
page.Paragraphs().Add(graph);
doc.Save("shapes.pdf");Рисование кругов и эллипсов
Circle и Ellipse описывают геометрию как простые числовые свойства, а не как общий тип точки или прямоугольника. Circle представляет собой центральную точку и радиус — PosX(), PosY(), Radius() — тогда как Ellipse представляет ограничивающий прямоугольник — Left(), Bottom(), Width(), Height(). Оба наследуют CheckBounds(containerWidth, containerHeight) от Shape, который сообщает, помещается ли текущая геометрия фигуры в контейнер заданного размера без изменения самой фигуры.
#include <aspose/pdf/drawing/circle.hpp>
#include <aspose/pdf/drawing/ellipse.hpp>
using namespace Aspose::Pdf::Drawing;
Circle circle;
circle.PosX(120.0);
circle.PosY(120.0);
circle.Radius(60.0);
bool circleFits = circle.CheckBounds(500.0, 300.0);
Ellipse ellipse;
ellipse.Left(220.0);
ellipse.Bottom(20.0);
ellipse.Width(250.0);
ellipse.Height(100.0);
bool ellipseFits = ellipse.CheckBounds(500.0, 300.0);Рисование линий с помощью PositionArray
Line хранит свою геометрию иначе, чем Circle и Ellipse: PositionArray() возвращает плоский std::vector<float>, содержащий пару x, y для каждой точки, через которую проходит линия, вместо фиксированного набора именованных свойств. Две точки (четыре значения) рисуют прямой отрезок; дополнительные пары рисуют многосегментный путь через эти точки по порядку. Как и другие фигуры, Line переопределяет CheckBounds(containerWidth, containerHeight) из Shape.
#include <aspose/pdf/drawing/line.hpp>
using namespace Aspose::Pdf::Drawing;
Line path;
path.PositionArray({0.0f, 0.0f, 250.0f, 0.0f, 125.0f, 200.0f});
bool pathFits = path.CheckBounds(500.0, 300.0);Стилизация фигур с помощью GraphInfo и BorderInfo
Каждая фигура читает и записывает стили обводки и заливки через Shape.GraphInfo() / GraphInfo(value). GraphInfo по умолчанию имеет LineWidth() 1.0f с пустым DashArray(), а также предоставляет Color(), FillColor(), DashArray()/DashPhase() для пунктирных обводок, IsDoubled() для двойной линии и SkewAngleX()/SkewAngleY(), ScalingRateX()/ScalingRateY() и RotationAngle() для преобразования обводки. Сам Graph содержит BorderInfo, задаваемый через Border(), который группирует один GraphInfo на ребро — Left(), Right(), Top(), Bottom() — плюс RoundedBorderRadius(), и может быть построен путем указания, какие ребра стилизовать с помощью флага BorderSide (BorderSide::Left | BorderSide::Right или BorderSide::All для всех ребер).
#include <aspose/pdf/graph_info.hpp>
#include <aspose/pdf/border_info.hpp>
#include <aspose/pdf/border_side.hpp>
#include <aspose/pdf/color.hpp>
using namespace Aspose::Pdf;
GraphInfo stroke;
stroke.LineWidth(2.5f);
stroke.Color(Color::FromRgb(1.0, 0.0, 0.0));
stroke.DashArray({3, 2});
stroke.DashPhase(1);
stroke.IsDoubled(true);
// Style only the left and right edges of the container border.
BorderInfo border{BorderSide::Left | BorderSide::Right, 3.0f};
border.RoundedBorderRadius(4.0);
graph->GraphInfo(stroke);
graph->Border(border);Советы и лучшие практики
- Вызовите
CheckBoundsотносительно собственногоWidth()/Height()принадлежащегоGraph, а не полной медиабокса страницы — проверка валидирует геометрию фигуры относительно размера контейнера, который вы передаёте, и не смотрит автоматически на страницу. - Добавляйте фигуры в
Shapes()перед тем, как добавитьGraphвPage.Paragraphs()— созданиеCircle,EllipseилиLineне оказывает видимого эффекта, пока они не будут добавлены к своему владельцуGraphи этотGraphне будет добавлен на страницу. - Группируйте связанные фигуры под одним
Graph, а не по отдельномуGraphна каждую фигуру;Left()/Top()/Width()/Height()позиционируют весь контейнер единожды, и каждая фигура вShapes()рисуется относительно него. - Устанавливайте
Shape.GraphInfo()явно для фигур, которым требуется видимая заливка или нестандартная обводка — по умолчаниюGraphInfoимеет ширину линии1.0f, без заливки и пустойDashArray(). - Создайте
Line.PositionArray()как парыx0, y0, x1, y1, …в порядке, в котором должен быть нарисован путь — вектор нечётной длины описывает неполную точку.
Распространённые проблемы
| Проблема | Причина | Исправление |
|---|---|---|
Элемент Circle, Ellipse или Line никогда не появляется на сохранённой странице | Фигура была построена, но никогда не была добавлена к Graph’s Shapes(), либо Graph никогда не был добавлен на страницу | Поместите фигуру в graph->Shapes() и вызовите page.Paragraphs().Add(graph) |
| Фигура рисуется с тонкой чёрной обводкой по умолчанию | Shape.GraphInfo() никогда не был установлен | Вызовите shape->GraphInfo(customGraphInfo) перед добавлением фигуры к Shapes() |
Line отсутствует или выглядит как одиночная точка | У PositionArray() менее двух пар (x, y) либо нечётное количество значений | Предоставьте как минимум четыре значения в виде x0, y0, x1, y1 |
| Только некоторые края границы контейнера стилизованы | BorderInfo был построен с BorderSide маской, которая исключает некоторые края | Используйте BorderSide::All, или объедините при помощи OR каждый нужный флаг BorderSide |
CheckBounds неожиданно возвращает false | Переданные в CheckBounds размеры не соответствуют контейнеру, в котором фактически отрисовывается фигура | Передайте текущие Width()/Height() владельца Graph |
FAQ
В чём разница между Circle и AnnotationType::Circle?
Circle (это руководство) — это форма рисования, которая рисует непосредственно в содержимое страницы через Graph. AnnotationType::Circle и CircleAnnotation относятся к отдельному набору аннотаций API и представляют собой аннотацию разметки в виде круга — эти два элемента не связаны, несмотря на одинаковое название.
Может ли один Graph содержать более одной формы?
Да. Graph.Shapes() возвращает полный std::vector<std::unique_ptr<Shape>> для этого контейнера, и любой набор экземпляров Circle, Ellipse и Line может быть добавлен к нему до того, как Graph будет добавлен на страницу.
Имеют ли Circle и Ellipse общий тип геометрии?
Нет. Circle — это центральная точка и радиус (PosX, PosY, Radius); Ellipse — это ограничивающий прямоугольник (Left, Bottom, Width, Height). Оба происходят от Shape, но ни один не имеет общей точки или типа прямоугольника с другим.
Является ли рисование фигур с помощью Graph тем же самым, что рендеринг страницы в изображение?
Нет. Graph, Shape, Circle, Ellipse и Line создают новую векторную геометрию в содержимом страницы во время генерации документа. Преобразование существующей страницы в растровое изображение — отдельная возможность, реализуемая BmpDevice, JpegDevice и TiffDevice.
Что делает GraphInfo.IsDoubled()?
Он помечает штрих как двойную линию, не изменяя никаких других свойств штриха. Это логический флаг, читаемый и устанавливаемый через IsDoubled() / IsDoubled(value) вместе с другими свойствами штриха GraphInfo.
API Reference Сводка
| Класс/Метод | Описание: |
|---|---|
Graph | Контейнер фигуры, размещённый на странице; позиционирует себя с помощью Left(), Top(), Width(), Height() |
Graph.Shapes() | Возвращает std::vector<std::unique_ptr<Shape>>, отрисованный этим контейнером |
Graph.GraphInfo() / GraphInfo(value) | Получает/устанавливает собственные стили обводки и заполнения контейнера |
Graph.Border() / Border(value) | Получает/устанавливает BorderInfo, ограничивающий контейнер |
Shape | Абстрактный базовый класс для Circle, Ellipse и Line; предоставляет GraphInfo() и CheckBounds() |
Shape.CheckBounds(w, h) | Сообщает, помещается ли геометрия формы внутри контейнера заданной ширины/высоты |
Circle | Форма круга: PosX(), PosY(), Radius() |
Ellipse | Эллипс: ограничивающий прямоугольник через Left(), Bottom(), Width(), Height() |
Line | Линия/контур: PositionArray(), плоский std::vector<float> из координат x, y |
GraphInfo | Оформление обводки/заполнения: LineWidth(), Color(), FillColor(), DashArray(), DashPhase(), IsDoubled(), RotationAngle(), SkewAngleX()/SkewAngleY(), ScalingRateX()/ScalingRateY() |
BorderInfo | Граничная рамка контейнера по краям: Left(), Right(), Top(), Bottom() (каждая GraphInfo), плюс RoundedBorderRadius() |
BorderSide | Перечисление, выбирающее ребра при построении BorderInfo: None, Left, Top, Right, Bottom, All, Box |