Three vector primitives cover most cases when the visual you need is more complex than a basic rectangle or circle. PdfPolylineElement renders an open piecewise-linear path. PdfPolygonElement renders a closed polygon described by a vertex list. PdfPathElement renders an arbitrary path built from move, line and Bezier curve operations.
Each primitive is added with a matching method on PdfDocument. Use PdfDocumentAddPolyline(PdfPolylineElement), PdfDocumentAddPolygon(PdfPolygonElement) or PdfDocumentAddPath(PdfPathElement).
A polyline is described by a list of PdfPointF vertices. It is stroke-only. There is no FillColor. The stroke is configured through a PdfLineStyle that carries line width, dash pattern, line cap and line join.
A polygon is closed automatically between its last and first vertex. Both fill and stroke are supported. Set FillColor to null for a stroke-only polygon. Set BorderColor to null for a fill-only polygon.
PdfPathElement is the most general primitive. Build the geometry with the fluent builder methods on the element itself. Each method returns the same instance so calls can be chained. Use PdfPathElementMoveTo(Single, Single), PdfPathElementLineTo(Single, Single), PdfPathElementCurveTo(Single, Single, Single, Single, Single, Single) (cubic Bezier) and PdfPathElementClose. Alternatively pass a pre-built operation array to the PdfPathElementPdfPathElement(PdfPathOperation) constructor. The path must always start with a MoveTo operation.
All three primitives accept rotation through RotationDegrees and RotationPivot. They all share the PdfLineStyle configuration for stroke. The properties are LineWidth, LineCap (Butt, Round or ProjectingSquare), LineJoin (Miter, Round or Bevel) and DashStyle (Solid, Dashed or Dotted). An optional CustomDashPattern defines an explicit dash array. FillOpacity, BorderOpacity and LineOpacity control transparency without changing the configured colours.
Pass a list of PdfPointF vertices to the PdfPolylineElement constructor. Round caps and joins are the safest default when the line width is large. Polylines are stroke-only.
A polygon is closed automatically between the last and first vertex. Set FillColor to null for a stroke-only outline. Set BorderColor to null for a fill-only shape. Set both to render a filled shape with a stroked border.
Build the path with the fluent builder methods on the element. Each method returns the element instance so calls can be chained. Alternatively pass a pre-built array of PdfPathOperation values to the constructor. The path must start with a MoveTo operation.
PdfPathElement heart = new PdfPathElement
{
FillColor = PdfColor.Crimson,
FillOpacity = 0.8f,
LineColor = PdfColor.DarkRed,
LineStyle = new PdfLineStyle { LineWidth = 1.5f, LineJoin = PdfLineJoinStyle.Round }
};
heart
.MoveTo(cx, cy + s * 0.25f)
.CurveTo(cx - s * 0.55f, cy + s * 0.55f,
cx - s * 1.10f, cy - s * 0.10f,
cx, cy - s * 0.35f)
.CurveTo(cx + s * 1.10f, cy - s * 0.10f,
cx + s * 0.55f, cy + s * 0.55f,
cx, cy + s * 0.25f)
.Close();
pdfDocument.AddPath(heart);using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using EvoPdf_Next_AspNetDemo.Models;
using EvoPdf_Next_AspNetDemo.Models.PDF_Creator;
// Use EVO PDF Namespace
using EvoPdf.Next;
namespace EvoPdf_Next_AspNetDemo.Controllers.PDF_Creator
{
public class Create_PDF_Documents_with_Paths_and_PolygonsController : Controller
{
private readonly IWebHostEnvironment m_hostingEnvironment;
public Create_PDF_Documents_with_Paths_and_PolygonsController(IWebHostEnvironment hostingEnvironment)
{
m_hostingEnvironment = hostingEnvironment;
}
public IActionResult Index()
{
var model = new Create_PDF_Documents_with_Paths_and_Polygons_ViewModel();
return View(model);
}
[HttpPost]
public ActionResult CreatePdf(Create_PDF_Documents_with_Paths_and_Polygons_ViewModel model)
{
if (!ModelState.IsValid)
{
var errorMessage = ModelStateHelper.GetModelErrors(ModelState);
throw new ValidationException(errorMessage);
}
// Set the license key received after purchase to use the library in licensed mode; leave it commented for demo mode
// Licensing.LicenseKey = "your-license-key";
PdfDocumentCreateSettings pdfCreateSettings = new PdfDocumentCreateSettings()
{
PageSize = PdfPageSize.A4,
PageOrientation = PdfPageOrientation.Portrait,
Margins = new PdfMargins(36, 36, 36, 36),
PdfStandard = model.PdfStandard,
Language = "en-US"
};
using PdfDocument pdfDocument = new PdfDocument(pdfCreateSettings);
pdfDocument.PdfDocumentInfo.Title = "PDF Polyline, Polygon and Path Demo";
string fontsPath = GetDemoFontsPath();
string fontFilePath = Path.Combine(fontsPath, "DejaVuSerif.ttf");
PdfBaseFont baseFont = PdfFontManager.CreateBaseFont(fontFilePath);
PdfFont titleFont = PdfFontManager.CreateFont(baseFont, 18f,
PdfFontStyle.Bold | PdfFontStyle.Underline, PdfColor.Black);
PdfFont sectionFont = PdfFontManager.CreateFont(baseFont, 14f,
PdfFontStyle.Bold, PdfColor.DarkBlue);
PdfFont labelFont = PdfFontManager.CreateFont(baseFont, 9f,
PdfFontStyle.Normal, PdfColor.DarkGray);
const int xLeft = 0;
const int ySeparator = 10;
int crtYPos = 0;
// ===== Title =====
PdfTextElement titleElement = new PdfTextElement(
"PDF Polyline, Polygon and Path Demo", titleFont)
{
X = xLeft,
Y = crtYPos,
Alignment = PdfTextAlignment.Center,
Width = pdfDocument.ContentWidth
};
titleElement.Accessibility.StructureType = PdfStructureType.Heading1;
crtYPos = (int)pdfDocument.AddText(titleElement).LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 1: PdfPolylineElement (open, with caps) =====
crtYPos = AddSectionLabel(pdfDocument, sectionFont,
"1. PdfPolylineElement (zigzag, round joins)", xLeft, crtYPos, ySeparator);
List<PdfPointF> zigzag = new List<PdfPointF>();
for (int i = 0; i <= 12; i++)
{
float x = xLeft + i * 30;
float y = crtYPos + (i % 2 == 0 ? 0 : 40);
zigzag.Add(new PdfPointF(x, y));
}
var zigzagInfo = pdfDocument.AddPolyline(new PdfPolylineElement(zigzag)
{
LineColor = PdfColor.DarkOrange,
LineStyle = new PdfLineStyle
{
LineWidth = 3f,
LineCap = PdfLineCapStyle.Round,
LineJoin = PdfLineJoinStyle.Round
}
});
int zigzagCapBottom = AddCaption(pdfDocument, labelFont,
"12-segment zigzag, LineWidth=3, Round caps and joins",
xLeft, (int)zigzagInfo.LastPageRectangle.Bounds.Bottom + 5, 400);
crtYPos = zigzagCapBottom + ySeparator * 2;
// Same path rotated
List<PdfPointF> zigzag2 = new List<PdfPointF>();
for (int i = 0; i <= 12; i++)
{
float x = xLeft + i * 30;
float y = crtYPos + (i % 2 == 0 ? 0 : 30);
zigzag2.Add(new PdfPointF(x, y));
}
var zigzag2Info = pdfDocument.AddPolyline(new PdfPolylineElement(zigzag2)
{
LineColor = PdfColor.SteelBlue,
LineStyle = new PdfLineStyle
{
LineWidth = 2f,
DashStyle = PdfLineDashStyle.Dashed,
LineJoin = PdfLineJoinStyle.Miter
},
RotationDegrees = -6,
RotationPivot = PdfRotationPivot.TopLeft
});
int zigzag2CapBottom = AddCaption(pdfDocument, labelFont,
"Same zigzag, dashed, rotated 6 deg clockwise",
xLeft, (int)zigzag2Info.LastPageRectangle.Bounds.Bottom + 5, 400);
crtYPos = zigzag2CapBottom + ySeparator * 2;
// ===== Section 2: PdfPolygonElement (triangle, hexagon, star) =====
EnsureSpaceOnPage(ref crtYPos, 200, pdfDocument);
crtYPos = AddSectionLabel(pdfDocument, sectionFont,
"2. PdfPolygonElement (triangle, regular hexagon, 5-point star)",
xLeft, crtYPos, ySeparator);
// Triangle
var triangleInfo = pdfDocument.AddPolygon(new PdfPolygonElement(new PdfPointF(xLeft + 60, crtYPos),
new PdfPointF(xLeft + 110, crtYPos + 90),
new PdfPointF(xLeft + 10, crtYPos + 90))
{
FillColor = PdfColor.LightSalmon,
FillOpacity = 0.7f,
BorderColor = PdfColor.DarkRed,
Border = new PdfLineStyle { LineWidth = 1.5f }
});
int triangleCapBottom = AddCaption(pdfDocument, labelFont, "triangle (filled)", xLeft + 10,
(int)triangleInfo.LastPageRectangle.Bounds.Bottom + 5, 110);
// Regular hexagon
var hexagonInfo = pdfDocument.AddPolygon(new PdfPolygonElement(
BuildRegularPolygon(xLeft + 200, crtYPos + 45, 50, 6, startAngleDeg: -90))
{
FillColor = PdfColor.LightGreen,
FillOpacity = 0.6f,
BorderColor = PdfColor.DarkGreen,
Border = new PdfLineStyle
{
LineWidth = 1.5f,
LineJoin = PdfLineJoinStyle.Round
}
});
int hexagonCapBottom = AddCaption(pdfDocument, labelFont, "regular hexagon", xLeft + 150,
(int)hexagonInfo.LastPageRectangle.Bounds.Bottom + 5, 110);
// 5-point star
var starInfo = pdfDocument.AddPolygon(new PdfPolygonElement(
BuildStar(xLeft + 340, crtYPos + 45, outerRadius: 50, innerRadius: 22, points: 5))
{
FillColor = PdfColor.Gold,
FillOpacity = 0.85f,
BorderColor = PdfColor.DarkGoldenRod,
Border = new PdfLineStyle { LineWidth = 1.2f, LineJoin = PdfLineJoinStyle.Miter, MiterLimit = 4f }
});
int starCapBottom = AddCaption(pdfDocument, labelFont, "5-point star", xLeft + 295,
(int)starInfo.LastPageRectangle.Bounds.Bottom + 5, 110);
crtYPos = Math.Max(Math.Max(triangleCapBottom, hexagonCapBottom), starCapBottom) + ySeparator * 2;
// ===== Section 3: PdfPathElement (heart via fluent API) =====
EnsureSpaceOnPage(ref crtYPos, 200, pdfDocument);
crtYPos = AddSectionLabel(pdfDocument, sectionFont,
"3. PdfPathElement (fluent MoveTo / LineTo / CurveTo / Close)",
xLeft, crtYPos, ySeparator);
// Heart shape built around (cx, cy) with size s.
float cx = xLeft + 80;
float cy = crtYPos + 80;
float s = 60f;
PdfPathElement heart = new PdfPathElement
{
FillColor = PdfColor.Crimson,
FillOpacity = 0.8f,
LineColor = PdfColor.DarkRed,
LineStyle = new PdfLineStyle { LineWidth = 1.5f, LineJoin = PdfLineJoinStyle.Round }};
heart
.MoveTo(cx, cy + s * 0.25f)
// Left (cubic Bezier upward to top center)
.CurveTo(
cx - s * 0.55f, cy + s * 0.55f,
cx - s * 1.10f, cy - s * 0.10f,
cx, cy - s * 0.35f)
// Right (cubic Bezier from top center back down)
.CurveTo(
cx + s * 1.10f, cy - s * 0.10f,
cx + s * 0.55f, cy + s * 0.55f,
cx, cy + s * 0.25f)
.Close();
var heartInfo = pdfDocument.AddPath(heart);
int heartCapBottom = AddCaption(pdfDocument, labelFont,
"heart (two cubic Beziers + Close, FillColor set)",
xLeft, (int)heartInfo.LastPageRectangle.Bounds.Bottom + 5, 250);
// Wave
PdfPathElement wave = new PdfPathElement
{
LineColor = PdfColor.SteelBlue,
LineStyle = new PdfLineStyle { LineWidth = 2f, LineCap = PdfLineCapStyle.Round }};
float waveStartX = xLeft + 200;
float waveY = crtYPos + 70;
wave.MoveTo(waveStartX, waveY);
for (int i = 0; i < 3; i++)
{
float x0 = waveStartX + i * 80;
wave.CurveTo(
x0 + 20, waveY - 30,
x0 + 60, waveY + 30,
x0 + 80, waveY);
}
var waveInfo = pdfDocument.AddPath(wave);
int waveCapBottom = AddCaption(pdfDocument, labelFont,
"stroke-only wave (3 cubic segments)",
xLeft + 200, (int)waveInfo.LastPageRectangle.Bounds.Bottom + 5, 250);
// Third triangle built via the params PdfPathOperation[] constructor
float triX = xLeft + 460;
float triY = crtYPos + 30;
var staticTriangle = new PdfPathElement(
PdfPathOperation.MoveTo(triX + 40, triY),
PdfPathOperation.LineTo(triX + 80, triY + 70),
PdfPathOperation.LineTo(triX, triY + 70),
PdfPathOperation.Close())
{
FillColor = PdfColor.LightSeaGreen,
FillOpacity = 0.6f,
LineColor = PdfColor.Teal,
LineStyle = new PdfLineStyle { LineWidth = 1.2f }
};
var triangleInfo3 = pdfDocument.AddPath(staticTriangle);
int triangle3CapBottom = AddCaption(pdfDocument, labelFont,
"static triangle (params PdfPathOperation[])",
(int)(triX - 10), (int)triangleInfo3.LastPageRectangle.Bounds.Bottom + 5, 110);
crtYPos = Math.Max(Math.Max(heartCapBottom, waveCapBottom), triangle3CapBottom) + ySeparator * 2;
// ===== Section 4: Path with rotation =====
EnsureSpaceOnPage(ref crtYPos, 220, pdfDocument);
crtYPos = AddSectionLabel(pdfDocument, sectionFont,
"4. PdfPathElement (arrow shape rotated 0, 45, 90, 135 degrees)",
xLeft, crtYPos, ySeparator);
float[] angles = { 0, 45, 90, 135 };
int arrowMaxBottom = crtYPos;
for (int i = 0; i < angles.Length; i++)
{
int cellX = xLeft + i * 120;
int cellY = crtYPos + 30;
PdfPathElement arrow = new PdfPathElement
{
FillColor = PdfColor.MediumPurple,
FillOpacity = 0.7f,
LineColor = PdfColor.Indigo,
LineStyle = new PdfLineStyle { LineWidth = 1f },
RotationDegrees = angles[i],
RotationPivot = PdfRotationPivot.Center
};
// Right-pointing arrow
arrow
.MoveTo(cellX, cellY + 10)
.LineTo(cellX + 50, cellY + 10)
.LineTo(cellX + 50, cellY)
.LineTo(cellX + 80, cellY + 20)
.LineTo(cellX + 50, cellY + 40)
.LineTo(cellX + 50, cellY + 30)
.LineTo(cellX, cellY + 30)
.Close();
var arrowInfo = pdfDocument.AddPath(arrow);
int capBottom = AddCaption(pdfDocument, labelFont, $"{angles[i]} deg", cellX,
(int)arrowInfo.LastPageRectangle.Bounds.Bottom + 5, 80);
arrowMaxBottom = Math.Max(arrowMaxBottom, capBottom);
}
crtYPos = arrowMaxBottom + ySeparator;
byte[] outPdfBuffer = pdfDocument.Save();
FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
fileResult.FileDownloadName = "PdfPathsPolygonsDemo.pdf";
return fileResult;
}
// Builds a regular N-sided polygon centered at (cx, cy) with circumscribed radius r
private static List<PdfPointF> BuildRegularPolygon(float cx, float cy, float r, int sides, float startAngleDeg)
{
var pts = new List<PdfPointF>(sides);
for (int i = 0; i < sides; i++)
{
double a = (startAngleDeg + i * 360.0 / sides) * Math.PI / 180.0;
pts.Add(new PdfPointF(
(float)(cx + r * Math.Cos(a)),
(float)(cy + r * Math.Sin(a))));
}
return pts;
}
// Builds an N-point star centered at (cx, cy)
private static List<PdfPointF> BuildStar(float cx, float cy, float outerRadius, float innerRadius, int points)
{
int total = points * 2;
var pts = new List<PdfPointF>(total);
for (int i = 0; i < total; i++)
{
float r = (i % 2 == 0) ? outerRadius : innerRadius;
double a = (-90 + i * 360.0 / total) * Math.PI / 180.0;
pts.Add(new PdfPointF(
(float)(cx + r * Math.Cos(a)),
(float)(cy + r * Math.Sin(a))));
}
return pts;
}
private int AddSectionLabel(PdfDocument doc, PdfFont sectionFont,
string label, int x, int y, int separator)
{
PdfTextElement section = new PdfTextElement(label, sectionFont)
{ X = x, Y = y };
section.Accessibility.StructureType = PdfStructureType.Heading2;
var info = doc.AddText(section);
return (int)info.LastPageRectangle.Bounds.Bottom + separator;
}
private int AddCaption(PdfDocument doc, PdfFont labelFont,
string caption, int x, int y, int width)
{
PdfTextElement t = new PdfTextElement(caption, labelFont)
{ X = x, Y = y, Width = width };
t.Accessibility.StructureType = PdfStructureType.Artifact;
var info = doc.AddText(t);
return (int)info.LastPageRectangle.Bounds.Bottom;
}
private void EnsureSpaceOnPage(ref int crtYPos, int requestedHeight, PdfDocument pdfDocument)
{
if (crtYPos + requestedHeight > pdfDocument.ContentHeight)
{
pdfDocument.AddPage();
crtYPos = 0;
}
}
private string GetDemoFilesPath() => m_hostingEnvironment.ContentRootPath + "/wwwroot" + "/DemoAppFiles/Input/";
private string GetDemoImagesPath() => Path.Combine(GetDemoFilesPath(), "Image_Files");
private string GetDemoFontsPath() => Path.Combine(GetDemoFilesPath(), "Font_Files");
private string GetDemoTextsPath() => Path.Combine(GetDemoFilesPath(), "Text_Files");
}
}