Drawing Vector Graphics on a PDF Page
Drawing Vector Graphics on a PDF Page
Aspose.PDF FOSS for C++ includes a small vector-graphics API for drawing shapes directly onto a PDF page while a document is being built. The entry point is Graph, a container placed on a page that holds a collection of Shape objects; three concrete shapes — Circle, Ellipse, and Line — derive from Shape and each express their geometry differently. Per-shape stroke and fill styling comes from GraphInfo, and the Graph container itself carries a BorderInfo for framing. This is unrelated to page rasterization (BmpDevice, JpegDevice, TiffDevice), which converts existing page content to raster image formats rather than drawing new geometry.
The Graph Container and Adding Shapes to a Page
Graph positions and sizes itself on the page through Left(), Top(), Width(), and Height(), and its IsChangePosition() flag controls whether that position can shift relative to other page content. The shapes it draws live in the vector returned by Shapes() — std::vector<std::unique_ptr<Shape>> — populated by constructing a shape, configuring it, and moving it onto that vector. Once built, the Graph itself is placed on the page through Page.Paragraphs().Add(), the same collection used for other page-level content.
#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");Drawing Circles and Ellipses
Circle and Ellipse each express geometry as plain numeric properties rather than a shared point or rectangle type. Circle is a center point and radius — PosX(), PosY(), Radius() — while Ellipse is a bounding box — Left(), Bottom(), Width(), Height(). Both inherit CheckBounds(containerWidth, containerHeight) from Shape, which reports whether the shape’s current geometry fits inside a container of the given size without altering the shape itself.
#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);Drawing Lines with PositionArray
Line stores its geometry differently from Circle and Ellipse: PositionArray() returns a flat std::vector<float> holding an x, y pair for every point the line passes through, rather than a fixed set of named properties. Two points (four values) draw a straight segment; additional pairs draw a multi-segment path through those points in order. Like the other shapes, Line overrides CheckBounds(containerWidth, containerHeight) from 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);Styling Shapes with GraphInfo and BorderInfo
Each shape reads and writes its stroke and fill styling through Shape.GraphInfo() / GraphInfo(value). GraphInfo defaults to a LineWidth() of 1.0f with an empty DashArray(), and also exposes Color(), FillColor(), DashArray()/DashPhase() for dashed strokes, IsDoubled() for a doubled line, and SkewAngleX()/SkewAngleY(), ScalingRateX()/ScalingRateY(), and RotationAngle() for transforming the stroke. Graph itself carries a BorderInfo, set through Border(), which groups one GraphInfo per edge — Left(), Right(), Top(), Bottom() — plus a RoundedBorderRadius(), and can be constructed by naming which edges to style with a BorderSide flag (BorderSide::Left | BorderSide::Right, or BorderSide::All for every edge).
#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);Tips and Best Practices
- Call
CheckBoundsagainst the owningGraph’s ownWidth()/Height(), not the page’s full media box — the check validates a shape’s geometry against whatever container size you pass in, it doesn’t look at the page automatically. - Add shapes to
Shapes()before adding theGraphtoPage.Paragraphs()— constructing aCircle,Ellipse, orLinehas no visible effect until it is both appended to its owningGraphand thatGraphis added to a page. - Group related shapes under a single
Graphrather than oneGraphper shape;Left()/Top()/Width()/Height()position the whole container once, and every shape inShapes()is drawn relative to it. - Set
Shape.GraphInfo()explicitly for shapes that need a visible fill or a non-default stroke — the defaultGraphInfohas a1.0fline width, no fill, and an emptyDashArray(). - Build
Line.PositionArray()asx0, y0, x1, y1, …pairs in the order the path should be drawn — an odd-length vector describes an incomplete point.
Common Issues
| Issue | Cause | Fix |
|---|---|---|
A Circle, Ellipse, or Line never appears on the saved page | The shape was constructed but never appended to its Graph’s Shapes(), or the Graph was never added to the page | Push the shape onto graph->Shapes() and call page.Paragraphs().Add(graph) |
| Shape draws with a default thin black stroke | Shape.GraphInfo() was never set | Call shape->GraphInfo(customGraphInfo) before appending the shape to Shapes() |
Line is missing or looks like a single point | PositionArray() has fewer than two (x, y) pairs, or an odd number of values | Supply at least four values as x0, y0, x1, y1 |
| Only some edges of the container border are styled | BorderInfo was constructed with a BorderSide mask that excludes some edges | Use BorderSide::All, or OR together every BorderSide flag needed |
CheckBounds unexpectedly returns false | Dimensions passed to CheckBounds don’t match the container the shape is actually drawn in | Pass the owning Graph’s current Width()/Height() |
FAQ
What is the difference between Circle and AnnotationType::Circle?
Circle (this guide) is a drawing shape that draws directly into page content through a Graph. AnnotationType::Circle and CircleAnnotation belong to a separate annotations API and represent a circle-shaped markup annotation instead — the two are unrelated despite the shared name.
Can a single Graph contain more than one shape?
Yes. Graph.Shapes() returns the full std::vector<std::unique_ptr<Shape>> for that container, and any mix of Circle, Ellipse, and Line instances can be appended to it before the Graph is added to the page.
Do Circle and Ellipse share a geometry type?
No. Circle is a center point and radius (PosX, PosY, Radius); Ellipse is a bounding box (Left, Bottom, Width, Height). Both derive from Shape, but neither shares a common point or rectangle type with the other.
Is drawing shapes with Graph the same as rendering a page to an image?
No. Graph, Shape, Circle, Ellipse, and Line build new vector geometry into a page’s content while the document is generated. Converting an existing page to a raster image is a separate capability, handled by BmpDevice, JpegDevice, and TiffDevice.
What does GraphInfo.IsDoubled() do?
It marks the stroke as a doubled line rather than changing any other stroke property. It is a boolean flag read and set through IsDoubled() / IsDoubled(value) alongside the other GraphInfo stroke properties.
API Reference Summary
| Class/Method | Description |
|---|---|
Graph | Shape container placed on a page; positions itself with Left(), Top(), Width(), Height() |
Graph.Shapes() | Returns the std::vector<std::unique_ptr<Shape>> drawn by this container |
Graph.GraphInfo() / GraphInfo(value) | Gets/sets the container’s own stroke and fill styling |
Graph.Border() / Border(value) | Gets/sets the BorderInfo framing the container |
Shape | Abstract base for Circle, Ellipse, and Line; exposes GraphInfo() and CheckBounds() |
Shape.CheckBounds(w, h) | Reports whether the shape’s geometry fits inside a container of the given width/height |
Circle | Circle shape: PosX(), PosY(), Radius() |
Ellipse | Ellipse shape: bounding box via Left(), Bottom(), Width(), Height() |
Line | Line/path shape: PositionArray(), a flat std::vector<float> of x, y coordinates |
GraphInfo | Stroke/fill styling: LineWidth(), Color(), FillColor(), DashArray(), DashPhase(), IsDoubled(), RotationAngle(), SkewAngleX()/SkewAngleY(), ScalingRateX()/ScalingRateY() |
BorderInfo | Per-edge container border: Left(), Right(), Top(), Bottom() (each a GraphInfo), plus RoundedBorderRadius() |
BorderSide | Enum selecting edges when constructing a BorderInfo: None, Left, Top, Right, Bottom, All, Box |