Как работать с графикой 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-контенте во время создания или редактирования документа.