The PdfDocument class lets you build a PDF from scratch and add content elements through a small object-oriented API.
Text is represented by EvoPdf.NextPdfTextElement. An instance is added to the current page with PdfDocumentAddText(PdfTextElement). Configure the text string, the font and any layout option before the call. The call returns a EvoPdf.NextPdfTextRenderInfo that describes where the text was drawn.
A text element needs at least a text string and a font. The steps below show how to build one and render it on a PDF page.
A PDF font is represented by the EvoPdf.NextPdfFont class. Instances are created through the static helpers on EvoPdf.NextPdfFontManager. You can use a standard built-in font or load a TrueType or OpenType font from a file.
Standard fonts are available in any PDF viewer without embedding. They cover the ASCII range and a small set of Latin-1 glyphs. Create one with PdfFontManagerCreateStandardFont(PdfStandardFont, Single, PdfFontStyle, PdfColor).
// Bold italic green Courier at 16 points
PdfFont fontCourier = PdfFontManager.CreateStandardFont(
PdfStandardFont.Courier, 16f,
PdfFontStyle.Bold | PdfFontStyle.Italic,
PdfColor.Green);For Unicode text outside the Latin-1 range you need a TrueType or OpenType font. The recommended approach is a two-step pattern. First load the font file once into a EvoPdf.NextPdfBaseFont with PdfFontManagerCreateBaseFont(String, Boolean, IListInt32). Then derive one or more styled EvoPdf.NextPdfFont instances from it with PdfFontManagerCreateFont(PdfBaseFont, Single, PdfFontStyle, PdfColor). The font subset is then embedded only once in the document. This is the most efficient path when the same face is used at several sizes, styles or colors.
string fontFilePath = Path.Combine(fontsPath, "DejaVuSerif.ttf");
// Load the font file into a base font (cached by physical path)
PdfBaseFont baseFont = PdfFontManager.CreateBaseFont(fontFilePath);
// Derive a styled PdfFont from the base font
PdfFont trueTypeFont = PdfFontManager.CreateFont(
baseFont, 16f,
PdfFontStyle.Normal, PdfColor.Black);A convenience overload PdfFontManagerCreateFont(String, Single, PdfFontStyle, PdfColor) that combines both steps in a single call is also available. It is convenient when the font is used only once in the document. For repeated use prefer the two-step pattern so the base font is loaded only once.
A PdfTextElement requires at least a text string and a font at construction time. It also exposes X and Y properties that define the location of the element relative to the top-left corner of the page content area. Page margins are excluded from these coordinates. Both default to zero.
PdfTextElement pdfText = new PdfTextElement("The text element string", fontCourier)
{
X = crtXPos,
Y = crtYPos
};The X and Y properties give the location of the text element relative to the top-left of the page content area. The page margins do not affect these coordinates.
The Width and Height properties define the bounding box of the element on the page. If Width is left at the default of zero, the text extends to the right edge of the available content area. If Height is left at the default of zero, the text extends downward as needed.
Use the Alignment property to select left, right, center or justified alignment inside the text bounds. The default value is Left.
A coloured background can be drawn behind the text by setting BackgroundColor. The opacity of the highlight is controlled by BackgroundOpacity (0 = transparent, 1 = opaque). The highlight is rendered as a non-semantic visual decoration. It does not become part of the reading order.
PdfTextElement highlighted = new PdfTextElement(text, bodyFont)
{
X = 0, Y = crtYPos, Width = pdfDocument.ContentWidth,
BackgroundColor = PdfColor.Yellow,
BackgroundOpacity = 0.4f
};
pdfDocument.AddText(highlighted);Render the text element on the current page by passing it to PdfDocumentAddText(PdfTextElement). The call returns a EvoPdf.NextPdfTextRenderInfo that carries the actual bounding rectangle on each page the text spans.
PdfTextRenderInfo textRenderInfo = pdfDocument.AddText(pdfText);
// Use the rendered geometry to position the next element
crtYPos = (int)textRenderInfo.LastPageRectangle.Bounds.Bottom + ySeparator;The LastPageRectangle tells you where the last line of text ended up. The Pages collection lists every page that received text along with the substring rendered on it. This makes it easy to build indexes or per-page summaries after the rendering is done.
By default a text element stops rendering when it runs out of space on the current page. Setting PdfTextElementContinueOnNextPage to true lets the engine break the text and continue rendering on subsequent pages.
Per-page handlers can be assigned to the PdfTextElementOnBeforePageRender and PdfTextElementOnAfterPageRender properties. Both are invoked once for every page the text spans. They receive a EvoPdf.NextPdfTextPageRenderInfo with the per-page substring, the page number and the RenderedRectangle that exposes both the axis-aligned Bounds and the rotated corner points. The After variant is the place to draw a border or any decoration that has to follow the actual text extent on each page.
PdfTextElement pdfText1 = new PdfTextElement(alfabetString, trueTypeFont)
{
X = crtXPos,
Y = crtYPos,
Alignment = PdfTextAlignment.Left,
ContinueOnNextPage = true
};
// Draw a blue border around the text rendered on each page
pdfText1.OnAfterPageRender = info =>
{
var bounds = info.RenderedRectangle.Bounds;
PdfRectangleElement border = new PdfRectangleElement(
bounds.X, bounds.Y,
bounds.Width, bounds.Height + 5)
{
BorderColor = PdfColor.Blue,
};
pdfDocument.AddRectangle(border);
};
pdfDocument.AddText(pdfText1);When the decoration has to be drawn before the text rather than after, assign the callback to PdfTextElementOnBeforePageRender instead. It receives the same EvoPdf.NextPdfTextPageRenderInfo and fires once per page just before the text is rendered. The two callbacks can be assigned together when a layout needs both layers.
Leading is the vertical spacing between consecutive lines of text. It is specified as a multiple of the font size used by the text element. The default leading factor is 1.2.
The text element renders right-to-left scripts such as Arabic or Hebrew correctly when PdfTextElementDirection is set to RightToLeft. The default value is LeftToRight.
The font used for the text must contain the glyphs of the target script. The standard built-in fonts only cover the Latin range. Use a TrueType font such as Noto Sans Arabic or Noto Sans Hebrew for the corresponding scripts.
string rtlFontFilePath = Path.Combine(fontsPath, "NotoSansArabic-Regular.ttf");
// Two-step font creation also works for one-off use
PdfBaseFont rtlBaseFont = PdfFontManager.CreateBaseFont(rtlFontFilePath);
PdfFont rtlTrueTypeFont = PdfFontManager.CreateFont(
rtlBaseFont, 16f,
PdfFontStyle.Normal, PdfColor.Black);
string rtlString = System.IO.File.ReadAllText(
Path.Combine(textsPath, "RightToLeft.txt"));
PdfTextElement pdfTextRtl = new PdfTextElement(rtlString, rtlTrueTypeFont)
{
X = crtXPos,
Y = crtYPos,
Direction = PdfTextDirection.RightToLeft
};
pdfDocument.AddText(pdfTextRtl);Use RotationDegrees together with RotationPivot to rotate the rendered text around one of the standard pivot points. Positive values rotate counter-clockwise. Negative values rotate clockwise.
The returned LastPageRectangle exposes both an axis-aligned Bounds and the four QuadPoints that follow the actual orientation of the rotated text. Use the QuadPoints to draw a polygon outline that matches the rotated extent exactly.
When the target document standard is PDF/UA-1, PDF/UA-2 or PDF/A-2a, every text element should set Accessibility.StructureType to the correct role. Use Heading1, Heading2 or Heading3 for the document hierarchy and Paragraph for body text. Tag decorative content such as page-number badges or watermark text with PdfStructureTypeArtifact so it is excluded from the reading order.
using System.IO;
using System.Text;
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_TextController : Controller
{
private readonly IWebHostEnvironment m_hostingEnvironment;
public Create_PDF_Documents_with_TextController(IWebHostEnvironment hostingEnvironment)
{
m_hostingEnvironment = hostingEnvironment;
}
public IActionResult Index()
{
var model = new Create_PDF_Documents_with_Text_ViewModel();
return View(model);
}
[HttpPost]
public ActionResult CreatePdf(Create_PDF_Documents_with_Text_ViewModel model)
{
if (!ModelState.IsValid)
{
var errorMessage = ModelStateHelper.GetModelErrors(ModelState);
throw new ValidationException(errorMessage);
}
// Set license key received after purchase to use the library in licensed mode
// 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 Text 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 bodyFont = PdfFontManager.CreateFont(baseFont, 11f,
PdfFontStyle.Normal, PdfColor.Black);
PdfFont smallFont = PdfFontManager.CreateFont(baseFont, 9f,
PdfFontStyle.Normal, PdfColor.DarkGray);
const int xLeft = 0;
const int ySeparator = 10;
int crtYPos = 0;
// ===== Section 1: Title =====
PdfTextElement titleElement = new PdfTextElement(
"PDF Text Demo", titleFont)
{
X = xLeft,
Y = crtYPos,
Alignment = PdfTextAlignment.Center,
Width = pdfDocument.ContentWidth
};
titleElement.Accessibility.StructureType = PdfStructureType.Heading1;
PdfTextRenderInfo titleInfo = pdfDocument.AddText(titleElement);
crtYPos = (int)titleInfo.LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 2: BackgroundColor + BackgroundOpacity =====
PdfTextElement sectionLabel1 = new PdfTextElement(
"1. BackgroundColor + BackgroundOpacity", sectionFont)
{ X = xLeft, Y = crtYPos };
sectionLabel1.Accessibility.StructureType = PdfStructureType.Heading2;
crtYPos = (int)pdfDocument.AddText(sectionLabel1).LastPageRectangle.Bounds.Bottom + ySeparator;
PdfTextElement highlighted = new PdfTextElement(
"This paragraph has a yellow highlight background drawn behind the text as an " +
"Artifact (it does not appear in the structure tree). The background covers the " +
"full column width and the actual used height.",
bodyFont)
{
X = xLeft,
Y = crtYPos,
Width = pdfDocument.ContentWidth,
BackgroundColor = PdfColor.Yellow,
BackgroundOpacity = 0.4f
};
crtYPos = (int)pdfDocument.AddText(highlighted).LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 3: OnBeforePageRender + OnAfterPageRender =====
PdfTextElement sectionLabel2 = new PdfTextElement(
"2. OnBeforePageRender (under) + OnAfterPageRender (over)", sectionFont)
{ X = xLeft, Y = crtYPos };
sectionLabel2.Accessibility.StructureType = PdfStructureType.Heading2;
crtYPos = (int)pdfDocument.AddText(sectionLabel2).LastPageRectangle.Bounds.Bottom + ySeparator;
PdfTextElement decoratedText = new PdfTextElement(
"Below text is drawn an under-layer rectangle in the OnBeforePageRender callback, " +
"while the OnAfterPageRender callback adds a thin border around the rendered area. " +
"Both callbacks receive the same PdfTextPageRenderInfo type but the rectangle in " +
"OnBeforePageRender is the predicted column area while in OnAfterPageRender it is " +
"the actual rendered area.",
bodyFont)
{
X = xLeft,
Y = crtYPos,
Width = pdfDocument.ContentWidth
};
// Under-layer painted before the text is drawn
decoratedText.OnBeforePageRender = preInfo =>
{
var col = preInfo.RenderedRectangle.Bounds;
pdfDocument.AddRectangle(new PdfRectangleElement(
col.X - 2, col.Y - 2, col.Width + 4, col.Height + 4)
{
FillColor = PdfColor.LightBlue,
FillOpacity = 0.35f,
BorderColor = null
});
};
// Post-render hook for the border that follows the actually used area
decoratedText.OnAfterPageRender = postInfo =>
{
var rect = postInfo.RenderedRectangle.Bounds;
pdfDocument.AddRectangle(new PdfRectangleElement(
rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4)
{
FillColor = null,
BorderColor = PdfColor.Blue,
Border = new PdfLineStyle { LineWidth = 1f }
});
};
crtYPos = (int)pdfDocument.AddText(decoratedText).LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 4: Rotated text + QuadPoints outline =====
EnsureSpaceOnPage(ref crtYPos, 180, pdfDocument);
PdfTextElement sectionLabel3 = new PdfTextElement(
"3. Rotated text with QuadPoints-based outline", sectionFont)
{ X = xLeft, Y = crtYPos };
sectionLabel3.Accessibility.StructureType = PdfStructureType.Heading2;
crtYPos = (int)pdfDocument.AddText(sectionLabel3).LastPageRectangle.Bounds.Bottom + ySeparator;
PdfTextElement rotated = new PdfTextElement(
"Rotated 25 degrees around the top-left corner. The polygon below follows the " +
"rotation using the four-corner QuadPoints returned by PdfTextPageRenderInfo.",
bodyFont)
{
X = xLeft + 60,
Y = crtYPos + 10,
Width = 350,
RotationDegrees = -25,
RotationPivot = PdfRotationPivot.TopLeft,
BackgroundColor = PdfColor.LightYellow,
BackgroundOpacity = 0.5f
};
// Trace a polygon along the rotated quad after rendering.
rotated.OnAfterPageRender = postInfo =>
{
var quad = postInfo.RenderedRectangle.QuadPoints;
pdfDocument.AddPolygon(new PdfPolygonElement(
quad.TopLeft, quad.TopRight, quad.BottomRight, quad.BottomLeft)
{
FillColor = null,
BorderColor = PdfColor.Red,
Border = new PdfLineStyle { LineWidth = 1.2f, DashStyle = PdfLineDashStyle.Dashed }
});
};
PdfTextRenderInfo rotatedInfo = pdfDocument.AddText(rotated);
// Advance Y past the rotated area's axis-aligned bounding box.
crtYPos = (int)rotatedInfo.LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 5: Multi-page continuation + per-page RenderedText =====
pdfDocument.AddPage();
crtYPos = 0;
PdfTextElement sectionLabel4 = new PdfTextElement(
"4. Multi-page continuation + per-page RenderedText", sectionFont)
{ X = xLeft, Y = crtYPos };
sectionLabel4.Accessibility.StructureType = PdfStructureType.Heading2;
crtYPos = (int)pdfDocument.AddText(sectionLabel4).LastPageRectangle.Bounds.Bottom + ySeparator;
// Build a long text that will overflow several pages.
string longTextSource = LoadAlphabetText();
StringBuilder longBuilder = new StringBuilder();
for (int i = 0; i < 8; i++) longBuilder.AppendLine(longTextSource);
string longText = longBuilder.ToString();
PdfTextElement multipage = new PdfTextElement(longText, bodyFont)
{
X = xLeft,
Y = crtYPos,
Width = pdfDocument.ContentWidth,
Alignment = PdfTextAlignment.Left,
ContinueOnNextPage = true,
BackgroundColor = PdfColor.WhiteSmoke,
BackgroundOpacity = 1f
};
// Per-page hook: draw a small page-number badge using OnAfterPageRender
multipage.OnAfterPageRender = info =>
{
var r = info.RenderedRectangle.Bounds;
string badge = $"page {info.RenderedRectangle.PageNumber} - {info.RenderedText.Length} chars";
// Place the badge to the right of the rendered text top edge
var badgeText = new PdfTextElement(badge, smallFont)
{
X = (float)r.Right - 110,
Y = (float)r.Y - 12,
Width = 110,
Alignment = PdfTextAlignment.Right
};
badgeText.Accessibility.StructureType = PdfStructureType.Artifact;
pdfDocument.AddText(badgeText);
};
PdfTextRenderInfo multipageInfo = pdfDocument.AddText(multipage);
// ===== Section 6: Summary of pages rendered =====
pdfDocument.AddPage();
crtYPos = 0;
PdfTextElement summaryLabel = new PdfTextElement(
"5. Summary: text rendered per page (from Pages list)", sectionFont)
{ X = xLeft, Y = crtYPos };
summaryLabel.Accessibility.StructureType = PdfStructureType.Heading2;
crtYPos = (int)pdfDocument.AddText(summaryLabel).LastPageRectangle.Bounds.Bottom + ySeparator;
// Walk the Pages list returned by the multipage render and report
// the first 80 characters of each page
for (int i = 0; i < multipageInfo.Pages.Count; i++)
{
var page = multipageInfo.Pages[i];
string preview = page.RenderedText.Length > 80
? page.RenderedText.Substring(0, 80).Replace("\n", " ").Replace("\r", "") + "..."
: page.RenderedText.Replace("\n", " ").Replace("\r", "");
string entry = $"Page {page.RenderedRectangle.PageNumber}: " +
$"{page.RenderedText.Length} chars, " +
$"bounds=({page.RenderedRectangle.Bounds.X:F0}, " +
$"{page.RenderedRectangle.Bounds.Y:F0}, " +
$"{page.RenderedRectangle.Bounds.Width:F0}x" +
$"{page.RenderedRectangle.Bounds.Height:F0}) - " +
$"\"{preview}\"";
PdfTextElement entryElement = new PdfTextElement(entry, smallFont)
{
X = xLeft,
Y = crtYPos,
Width = pdfDocument.ContentWidth
};
crtYPos = (int)pdfDocument.AddText(entryElement).LastPageRectangle.Bounds.Bottom + 4;
EnsureSpaceOnPage(ref crtYPos, 30, pdfDocument);
}
byte[] outPdfBuffer = pdfDocument.Save();
FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
fileResult.FileDownloadName = "PdfTextDemo.pdf";
return fileResult;
}
private string LoadAlphabetText()
{
string textsPath = GetDemoTextsPath();
string alphabetFilePath = Path.Combine(textsPath, "Alphabet.txt");
if (System.IO.File.Exists(alphabetFilePath))
return System.IO.File.ReadAllText(alphabetFilePath);
// Fallback so the demo runs even without the alphabet file.
return "The quick brown fox jumps over the lazy dog. " +
"Pack my box with five dozen liquor jugs. " +
"Sphinx of black quartz, judge my vow. " +
"How vexingly quick daft zebras jump. ";
}
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");
}
}