How to Work with PDF Graphics in C++

How to Work with PDF Graphics in C++

Aspose.PDF FOSS for C++ includes a small vector-graphics API for drawing free-form shapes directly on a PDF page — separate from the library’s raster-rendering devices (BmpDevice, JpegDevice, TiffDevice), which convert existing page content to images. This guide walks through creating a Graph container, adding Circle, Ellipse, and Line shapes to it, styling those shapes with GraphInfo, and placing the finished Graph on a page. The library is added to a project via CMake.

Step-by-Step Guide

Step 1: Install the Package

Add the library as a CMake subdirectory and link against the aspose_pdf_foss target:

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

Verify the toolchain and headers resolve by compiling a minimal program:

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

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

A successful build that prints pages: 0 confirms the library is linked correctly.


Step 2: Import Required Classes

Include the headers for the document, the shape container, the three concrete shapes, and their styling types:

#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, and Line live in the Aspose::Pdf::Drawing namespace; GraphInfo, BorderInfo, and Color live directly under Aspose::Pdf.


Step 3: Create a Graph and Add It to a Page

Graph is a container that positions itself on the page through Left(), Top(), Width(), and Height(), and holds the shapes drawn inside it through Shapes(). Once configured, it is placed on the page through Page.Paragraphs().Add(), the same collection used for other page content:

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);

Step 4: Draw a Circle and an Ellipse

Circle describes its geometry with PosX(), PosY(), and Radius(). Ellipse uses a bounding box instead — Left(), Bottom(), Width(), and Height(). Both are added to the Graph by appending them, as Shape pointers, to the vector returned by 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));

Both shapes inherit CheckBounds(containerWidth, containerHeight) from Shape, which reports whether the shape’s current geometry fits inside a container of the given size:

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

Step 5: Draw a Line

Line stores its geometry differently: PositionArray() holds a flat std::vector<float> of coordinates — an x, y pair per point — rather than named properties. A two-point segment needs four values: start x, start y, end x, end y.

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

Step 6: Style Shapes with GraphInfo and the Container Border

Each shape’s stroke and fill come from a GraphInfo, read and set through Shape.GraphInfo() / GraphInfo(value). It exposes stroke width (LineWidth(), default 1.0f), stroke and fill color (Color(), FillColor()), and a dash pattern (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));

The Graph container itself carries a BorderInfo, set through Border(), which groups one GraphInfo per edge (Left(), Right(), Top(), Bottom()) plus a RoundedBorderRadius() for the container’s own frame:

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

Step 7: Save the Document

doc.Save("graphics.pdf");

Common Issues and Fixes

A shape I constructed never appears on the page

Constructing a Circle, Ellipse, or Line on its own has no effect until it is appended to the vector returned by the owning Graph’s Shapes(), and that Graph is added to a page through Page.Paragraphs().Add(). All three steps — construct, append, add the Graph to the page — are required.

Shapes render in the wrong position

Graph.Left() and Graph.Top() position the container on the page; shape properties (Circle.PosX()/PosY(), Ellipse.Left()/Bottom(), Line.PositionArray() coordinates) describe geometry within that container. Check both the container placement and the shape’s own coordinates when output looks offset.

CheckBounds returns false unexpectedly

CheckBounds(containerWidth, containerHeight) checks the shape’s own geometry against the dimensions passed in — typically the owning Graph’s Width()/Height(), not the page’s full media box. Passing the wrong dimensions produces a false negative even when the shape would actually fit inside the Graph.

A Line doesn’t appear, or appears as a single point

PositionArray() needs at least two (x, y) pairs — four float values — to describe a visible segment. A shorter vector does not form a line.

Confusing Circle with AnnotationType::Circle or CircleAnnotation

The library also has a separate PDF annotations API, where AnnotationType::Circle and CircleAnnotation represent circle-shaped markup annotations. The Circle class covered in this guide is unrelated — it lives in Aspose::Pdf::Drawing and draws directly into page content rather than adding an annotation object.

Frequently Asked Questions

What’s the difference between GraphInfo and BorderInfo?

GraphInfo styles a single stroke — line width, color, fill color, and dash pattern — and is used both on individual shapes (through Shape.GraphInfo()) and per edge inside a BorderInfo. BorderInfo groups four GraphInfo values (Left(), Right(), Top(), Bottom()) plus a RoundedBorderRadius() to describe a Graph container’s own border frame.

Can I fill a shape with a solid color?

Yes. GraphInfo.FillColor() sets the fill color used when the shape is drawn through its owning Graph.

Does Circle store a bounding box like Ellipse does?

No. Circle exposes PosX(), PosY(), and Radius(); Ellipse exposes Left(), Bottom(), Width(), and Height(). They don’t share a common geometry representation beyond the abstract Shape base and GraphInfo styling.

Can one Graph hold more than one shape?

Yes. Graph.Shapes() returns the full collection of Shape objects placed within that Graph — circles, ellipses, and lines can all be appended to the same container.

Is this the same as rendering a page to an image?

No. BmpDevice, JpegDevice, and TiffDevice rasterize existing page content to image formats. Graph, Shape, Circle, Ellipse, and Line construct new vector geometry directly in the PDF content while a document is being built or edited.

See Also