Как работать с графикой PDF в C++

Как работать с графикой PDF в C++

Aspose.PDF FOSS for C++ включает небольшую векторную графику API для рисования произвольных форм непосредственно на странице PDF — отдельно от растровых устройств библиотеки (BmpDevice, JpegDevice, TiffDevice), которые преобразуют существующее содержимое страницы в изображения. Это руководство проходит процесс создания контейнера Graph, добавления в него форм Circle, Ellipse и Line, стилизации этих форм с помощью GraphInfo и размещения готового Graph на странице. Библиотека добавляется в проект через CMake.

Пошаговое руководство

Шаг 1: Установить пакет

Добавьте библиотеку как поддиректорию CMake и свяжите её с целью aspose_pdf_foss:

add_subdirectory(aspose.pdf-foss-for-cpp)
target_link_libraries(your_app PRIVATE aspose_pdf_foss)

Проверьте, что инструментарий и заголовки разрешаются, скомпилировав минимальную программу:

#include <aspose/pdf/document.hpp>
#include <iostream>

int main() {
    Aspose::Pdf::Document doc;
    std::cout << "Linked OK — pages: " << doc.Pages().Count() << "\n";
}

Успешная сборка, выводящая pages: 0, подтверждает, что библиотека правильно связана.


Шаг 2: Импортировать необходимые классы

Подключите заголовочные файлы для документа, контейнера фигур, трех конкретных фигур и их типов оформления:

#include <aspose/pdf/document.hpp>
#include <aspose/pdf/drawing/graph.hpp>
#include <aspose/pdf/drawing/circle.hpp>
#include <aspose/pdf/drawing/ellipse.hpp>
#include <aspose/pdf/drawing/line.hpp>
#include <aspose/pdf/graph_info.hpp>
#include <aspose/pdf/border_info.hpp>
#include <aspose/pdf/color.hpp>

using namespace Aspose::Pdf;
using namespace Aspose::Pdf::Drawing;

Graph, Shape, Circle, Ellipse и Line находятся в пространстве имен Aspose::Pdf::Drawing; GraphInfo, BorderInfo и Color находятся непосредственно под Aspose::Pdf.


Шаг 3: Создать граф и добавить его на страницу

Graph — это контейнер, который позиционирует себя на странице с помощью Left(), Top(), Width() и Height(), и содержит нарисованные внутри него фигуры через Shapes(). После настройки он размещается на странице через Page.Paragraphs().Add(), ту же коллекцию, которая используется для другого содержимого страницы:

Document doc;
Page page = doc.Pages().Add();

auto graph = std::make_shared<Graph>();
graph->Left(50.0);
graph->Top(50.0);
graph->Width(400.0);
graph->Height(400.0);

page.Paragraphs().Add(graph);

Шаг 4: Нарисовать круг и эллипс

Circle описывает свою геометрию с помощью PosX(), PosY() и Radius(). Ellipse использует ограничивающий прямоугольник — Left(), Bottom(), Width() и Height(). Обе добавляются в Graph, добавляя их в виде указателей Shape в вектор, возвращаемый Shapes():

auto circle = std::make_unique<Circle>();
circle->PosX(150.0);
circle->PosY(150.0);
circle->Radius(75.0);
graph->Shapes().push_back(std::move(circle));

auto ellipse = std::make_unique<Ellipse>();
ellipse->Left(20.0);
ellipse->Bottom(20.0);
ellipse->Width(120.0);
ellipse->Height(60.0);
graph->Shapes().push_back(std::move(ellipse));

Обе фигуры наследуют CheckBounds(containerWidth, containerHeight) от Shape, который сообщает, помещается ли текущая геометрия фигуры в контейнер заданного размера:

bool circleFits = circle->CheckBounds(graph->Width(), graph->Height());

Шаг 5: Нарисовать линию

Line хранит свою геометрию иначе: PositionArray() содержит плоский std::vector<float> координат — пару x, y для каждой точки — вместо именованных свойств. Сегмент из двух точек требует четыре значения: начальный x, начальный y, конечный x, конечный y.

auto line = std::make_unique<Line>();
line->PositionArray({0.0f, 0.0f, 400.0f, 400.0f});
graph->Shapes().push_back(std::move(line));

Шаг 6: Оформление фигур с GraphInfo и границей контейнера

Обводка и заливка каждой формы берутся из GraphInfo, читаются и задаются через Shape.GraphInfo() / GraphInfo(value). Он предоставляет ширину обводки (LineWidth(), по умолчанию 1.0f), цвет обводки и заливки (Color(), FillColor()) и шаблон пунктирной линии (DashArray(), DashPhase()):

GraphInfo stroke;
stroke.LineWidth(2.0f);
stroke.Color(Color::FromRgb(1.0, 0.0, 0.0));
stroke.FillColor(Color::FromRgb(1.0, 0.9, 0.9));

Сам контейнер Graph содержит BorderInfo, задаваемый через Border(), который группирует по одному GraphInfo на каждое ребро (Left(), Right(), Top(), Bottom()) плюс RoundedBorderRadius() для собственного кадра контейнера:

BorderInfo border{BorderSide::All, 1.5f, Color::Black()};
border.RoundedBorderRadius(6.0);
graph->Border(border);

Шаг 7: Сохранить документ

doc.Save("graphics.pdf");

Распространённые проблемы и их решения

Фигура, которую я построил, никогда не появляется на странице

Создание Circle, Ellipse или Line самостоятельно не оказывает эффекта, пока он не будет добавлен к вектору, возвращаемому Graph в его Shapes(), и этот Graph не будет добавлен на страницу через Page.Paragraphs().Add(). Требуются все три шага — создание, добавление, добавление Graph на страницу.

Фигуры отрисовываются в неверном положении

Graph.Left() и Graph.Top() позиционируют контейнер на странице; свойства фигуры (Circle.PosX()/PosY(), Ellipse.Left()/Bottom(), координаты Line.PositionArray()) описывают геометрию внутри этого контейнера. Проверьте как размещение контейнера, так и собственные координаты фигуры, если вывод выглядит смещённым.

CheckBounds returns false unexpectedly

CheckBounds(containerWidth, containerHeight) проверяет собственную геометрию фигуры относительно переданных размеров — обычно это Width()/Height() принадлежащего Graph, а не полная медиабокc страницы. Передача неверных размеров приводит к ложному отрицательному результату, даже если фигура фактически помещается внутри Graph.

A Line не появляется, или появляется как одна точка

PositionArray() требует как минимум две пары (x, y) — четыре значения float — чтобы описать видимый сегмент. Более короткий вектор не образует линию.

Confusing Circle with AnnotationType::Circle or CircleAnnotation

Библиотека также имеет отдельный API для аннотаций PDF, где AnnotationType::Circle и CircleAnnotation представляют аннотации в виде кругов. Класс Circle, рассмотренный в этом руководстве, не связан с этим — он находится в Aspose::Pdf::Drawing и рисует непосредственно в содержимое страницы, а не добавляет объект аннотации.

Часто задаваемые вопросы

В чём разница между GraphInfo и BorderInfo?

GraphInfo задаёт стиль отдельного штриха — ширину линии, цвет, цвет заливки и шаблон штриха — и используется как для отдельных фигур (через Shape.GraphInfo()), так и для каждого ребра внутри BorderInfo. BorderInfo объединяет четыре значения GraphInfo (Left(), Right(), Top(), Bottom()) плюс RoundedBorderRadius(), чтобы описать собственную рамку границы контейнера Graph.

Могу ли я залить форму сплошным цветом?

Да. GraphInfo.FillColor() задаёт цвет заливки, используемый при рисовании формы через её владеющий Graph.

Сохраняет ли Circle ограничивающий прямоугольник, как делает Ellipse?

Нет. Circle раскрывает PosX(), PosY() и Radius(); Ellipse раскрывает Left(), Bottom(), Width() и Height(). Они не имеют общей геометрической репрезентации, кроме абстрактного базового Shape и стиля GraphInfo.

Можно ли один Graph содержать более одной формы?

Да. Graph.Shapes() возвращает полную коллекцию объектов Shape, размещённых внутри этого Graph — круги, эллипсы и линии могут быть добавлены в один и тот же контейнер.

Это то же самое, что отрисовка страницы в изображение?

Нет. BmpDevice, JpegDevice и TiffDevice растеризуют существующее содержимое страницы в форматы изображений. Graph, Shape, Circle, Ellipse и Line создают новую векторную геометрию непосредственно в PDF-контенте во время создания или редактирования документа.

См. также:

 Русский