The PdfEditor class lets you add new content to an existing PDF without rewriting the rest of the document. The editor is instantiated with the source PDF bytes or file path and an optional password. The standard claimed by the source document and its existing page geometry are inherited by the editor. Content is added at absolute page coordinates using the dedicated Add methods. The methods that draw page content, such as AddText, AddImage or AddHtml, take the target page number as the first argument. Content that does not fit in the page continues on the following pages of the document when the element is set to continue, or on pages inserted after the current one when InsertContinuationPages is true. The resulting modified PDF is produced with PdfEditorSave.
Images are added by building a PdfImageElement (from a file path or an in-memory byte buffer) and passing it to PdfEditorAddImage(Int32, PdfImageElement). The first argument is the 1-based page number where the image should appear. The element carries position, sizing, alignment, opacity, rotation and accessibility metadata. The call returns a PdfImageRenderInfo with the axis-aligned BoundingBox, the rotated QuadPoints and the VisibleBounds of the image as it ended up on the page. Supported image formats are PNG (including transparency), JPEG and WebP.
Sizing follows the same rules as when creating a PDF from scratch. Set only Width or only Height to preserve the aspect ratio. Use ScaleDownToFit and EnlargeToFit to control auto-resize behaviour. Use HorizontalAlign to center or right-align the image inside the page.
Rotation is configured by RotationDegrees together with RotationPivot. The returned info exposes both an axis-aligned BoundingBox and the rotated QuadPoints. Use them to draw an axis-aligned outline or a rotated polygon through the four corners on the same page as the image.
When the source document is PDF/UA-1, PDF/UA-2 or PDF/A-2a, set Accessibility.AlternateText to a meaningful description of the image. Decorative images should be tagged with PdfStructureTypeArtifact so they are excluded from the reading order.
Provide either Width or Height to preserve the aspect ratio. Provide both to fit the image in that rectangle with its aspect ratio kept, or also set KeepAspectRatio to false to stretch it to exactly those dimensions. Combined with ScaleDownToFit and EnlargeToFit you can guarantee that an image never grows past its natural resolution while still shrinking it to fit a constrained area. Set HorizontalAlign to Center or Right and the X coordinate is computed automatically from the page dimensions inherited from the source document.
A rotated image still occupies a rectangle on the page. The BoundingBox is the tight axis-aligned rectangle that contains the rotated image. The QuadPoints give the four corners of the rotated quad. Use the BoundingBox to draw an axis-aligned outline and the QuadPoints to outline the rotated shape exactly.
An image taller than the space left on the page is cut at the bottom edge of the page by default. When ContinueOnNextPage is true, the part of the image that does not fit continues on the next pages with the same horizontal position and width, until the whole image is drawn. The editor has no page margins, so ContinuationBottomMargin gives the distance from the bottom of the pages to the lowest point the image can reach on each page and ContinuationTopMargin the distance from the top of the next pages to the continued image.
When InsertContinuationPages is true the pages where the image continues are inserted after the page where it starts, with the size and the orientation of that page, so the pages that follow in the document keep their content. When it is false, the default, the image continues on the following pages of the document, over their content; new pages are added at the end of the document only when the image needs more pages than the document has. RenderedRectangles, LastPageRectangle and LastPage of the returned PdfImageRenderInfo give the part of the image drawn on each page and the page where it ends.
using System;
using System.IO;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using EvoPdf_Next_AspNetDemo.Models;
using EvoPdf_Next_AspNetDemo.Models.PDF_Editor;
// Use EVO PDF Namespace
using EvoPdf.Next;
namespace EvoPdf_Next_AspNetDemo.Controllers.PDF_Editor
{
public class Add_Images_to_Existing_PDFController : Controller
{
private const int leftMargin = 36;
private const int topMargin = 36;
// The content area of the pages inside the demo margins, from the size of the first page of the loaded document
private int contentWidth;
private int contentHeight;
private readonly IWebHostEnvironment m_hostingEnvironment;
public Add_Images_to_Existing_PDFController(IWebHostEnvironment hostingEnvironment)
{
m_hostingEnvironment = hostingEnvironment;
}
public IActionResult Index()
{
var model = SetViewModel();
return View(model);
}
[HttpPost]
public async Task<IActionResult> EditPdf(Add_Images_to_Existing_PDF_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";
byte[] inputPdfBytes = null;
// If an uploaded file exists, use it with priority
if (model.PdfFile != null && model.PdfFile.Length > 0)
{
try
{
using var ms = new MemoryStream();
await model.PdfFile.CopyToAsync(ms);
inputPdfBytes = ms.ToArray();
}
catch (Exception ex)
{
throw new Exception("Failed to read the uploaded PDF file", ex);
}
}
else
{
// Otherwise, fall back to the URL
string pdfUrl = model.PdfFileUrl?.Trim();
if (string.IsNullOrWhiteSpace(pdfUrl))
throw new Exception("No PDF file provided: upload a file or specify a URL");
try
{
if (pdfUrl.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
string localPath = new Uri(pdfUrl).LocalPath;
inputPdfBytes = await System.IO.File.ReadAllBytesAsync(localPath);
}
else
{
using var httpClient = new System.Net.Http.HttpClient();
inputPdfBytes = await httpClient.GetByteArrayAsync(pdfUrl);
}
}
catch (Exception ex)
{
throw new Exception("Could not download the PDF file from URL", ex);
}
}
// The pages of an edited document have no margins: the demo keeps its content inside its own margins
string password = string.IsNullOrEmpty(model.OwnerPassword) ? model.UserPassword : model.OwnerPassword;
using PdfEditor pdfEditor = new PdfEditor(inputPdfBytes, password);
PdfPageInfo firstPageInfo = pdfEditor.GetPdfPageInfo(1);
// A rotation of 90 or 270 degrees swaps the width and the height of the page as it is displayed
bool firstPageTurned = firstPageInfo.Rotation % 180 != 0;
contentWidth = (int)(firstPageTurned ? firstPageInfo.PageSize.Height : firstPageInfo.PageSize.Width) - 2 * leftMargin;
contentHeight = (int)(firstPageTurned ? firstPageInfo.PageSize.Width : firstPageInfo.PageSize.Height) - 2 * topMargin;
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 smallFont = PdfFontManager.CreateFont(baseFont, 9f,
PdfFontStyle.Normal, PdfColor.DarkGray);
const int xLeft = leftMargin;
const int ySeparator = 10;
int currentPage = 1;
int crtYPos = topMargin;
string imagesPath = GetDemoImagesPath();
string transparentPngPath = Path.Combine(imagesPath, "transparent.png");
string jpegPath = Path.Combine(imagesPath, "image.jpg");
string tallImagePath = Path.Combine(imagesPath, "Tall_Report.png");
// ===== Title =====
PdfTextElement titleElement = new PdfTextElement(
"PDF Image Demo", titleFont)
{
X = xLeft,
Y = crtYPos,
Alignment = PdfTextAlignment.Center,
Width = contentWidth
};
titleElement.Accessibility.StructureType = PdfStructureType.Heading1;
var titleElementInfo = pdfEditor.AddText(currentPage, titleElement);
currentPage = titleElementInfo.LastPageRectangle.PageNumber;
crtYPos = (int)titleElementInfo.LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 1: PNG with custom width, ScaleDownToFit =====
crtYPos = AddSectionLabel(pdfEditor, ref currentPage, sectionFont,
"1. PNG with custom Width (ScaleDownToFit, EnlargeToFit)", xLeft, crtYPos, ySeparator);
PdfImageElement pngImage = new PdfImageElement(transparentPngPath)
{
X = xLeft,
Y = crtYPos,
Width = 150,
ScaleDownToFit = true,
EnlargeToFit = false,
Opacity = 0.85f
};
pngImage.Accessibility.AlternateText = "Transparent glass globe";
PdfImageRenderInfo pngInfo = pdfEditor.AddImage(currentPage, pngImage);
crtYPos = (int)pngInfo.BoundingBox.Bottom + ySeparator * 2;
// ===== Section 2: JPEG with alignment =====
EnsureSpaceOnPage(ref crtYPos, ref currentPage, 220, pdfEditor, contentHeight, topMargin);
crtYPos = AddSectionLabel(pdfEditor, ref currentPage, sectionFont,
"2. JPEG aligned Center horizontally", xLeft, crtYPos, ySeparator);
PdfImageElement jpgCentered = new PdfImageElement(jpegPath)
{
Y = crtYPos,
Height = 120,
HorizontalAlign = PdfElementHorizontalAlign.Center
};
jpgCentered.Accessibility.AlternateText = "Wooden house on a river";
PdfImageRenderInfo jpgInfo = pdfEditor.AddImage(currentPage, jpgCentered);
crtYPos = (int)jpgInfo.BoundingBox.Bottom + ySeparator * 2;
// ===== Section 3: Rotated image with QuadPoints-based outline =====
EnsureSpaceOnPage(ref crtYPos, ref currentPage, 280, pdfEditor, contentHeight, topMargin);
crtYPos = AddSectionLabel(pdfEditor, ref currentPage, sectionFont,
"3. Rotated 30 degrees clockwise - Bounding Box (red) vs Rotated Rectangle (blue)", xLeft, crtYPos, ySeparator);
PdfImageElement rotatedJpg = new PdfImageElement(jpegPath)
{
X = xLeft + 80,
Y = crtYPos + 10,
Width = 200,
Height = 150,
RotationDegrees = -30,
RotationPivot = PdfRotationPivot.TopLeft,
Opacity = 0.95f
};
rotatedJpg.Accessibility.AlternateText = "Wooden house rotated 30 degrees clockwise";
PdfImageRenderInfo rotatedInfo = pdfEditor.AddImage(currentPage, rotatedJpg);
// Outline the axis-aligned Bounds (red).
var aabb = rotatedInfo.BoundingBox;
pdfEditor.AddRectangle(currentPage, new PdfRectangleElement(aabb.X, aabb.Y, aabb.Width, aabb.Height)
{
FillColor = null,
BorderColor = PdfColor.Red,
Border = new PdfLineStyle { LineWidth = 1f, DashStyle = PdfLineDashStyle.Dotted }
});
// Outline the rotated QuadPoints (blue) using a polygon through the four corners.
var q = rotatedInfo.QuadPoints;
pdfEditor.AddPolygon(currentPage, new PdfPolygonElement(q.TopLeft, q.TopRight, q.BottomRight, q.BottomLeft)
{
FillColor = null,
BorderColor = PdfColor.Blue,
Border = new PdfLineStyle { LineWidth = 1.5f }
});
// Annotate with VisibleBounds info using the actual AABB bottom.
string visibleSummary = rotatedInfo.VisibleBounds == null
? "VisibleBounds: image fully outside the page"
: $"VisibleBounds=({rotatedInfo.VisibleBounds.X:F0}, {rotatedInfo.VisibleBounds.Y:F0}, " +
$"{rotatedInfo.VisibleBounds.Width:F0}x{rotatedInfo.VisibleBounds.Height:F0}) on page " +
$"{rotatedInfo.Page} ({rotatedInfo.PageWidth:F0}x{rotatedInfo.PageHeight:F0})";
PdfTextElement visibleLabel = new PdfTextElement(visibleSummary, smallFont)
{
X = xLeft,
Y = (int)aabb.Bottom + ySeparator,
Width = contentWidth
};
visibleLabel.Accessibility.StructureType = PdfStructureType.Artifact;
var visibleLabelInfo = pdfEditor.AddText(currentPage, visibleLabel);
currentPage = visibleLabelInfo.LastPageRectangle.PageNumber;
crtYPos = (int)visibleLabelInfo.LastPageRectangle.Bounds.Bottom + ySeparator * 2;
// ===== Section 4: Different rotation pivots =====
currentPage = pdfEditor.AddPage();
crtYPos = topMargin;
crtYPos = AddSectionLabel(pdfEditor, ref currentPage, sectionFont,
"4. Same image rotated 45 degrees around four different pivots", xLeft, crtYPos, ySeparator);
PdfRotationPivot[] pivots = new[]
{
PdfRotationPivot.TopLeft, PdfRotationPivot.TopRight,
PdfRotationPivot.BottomLeft, PdfRotationPivot.Center
};
const int cellW = 220;
const int cellH = 220;
int imgW = 100, imgH = 75;
for (int i = 0; i < pivots.Length; i++)
{
int col = i % 2;
int row = i / 2;
int cellX = xLeft + col * cellW;
int cellY = crtYPos + row * cellH;
// Draw the cell as a dashed reference frame.
pdfEditor.AddRectangle(currentPage, new PdfRectangleElement(cellX, cellY, cellW - 10, cellH - 10)
{
FillColor = null,
BorderColor = PdfColor.LightGray,
Border = new PdfLineStyle { LineWidth = 0.5f, DashStyle = PdfLineDashStyle.Dashed }
});
// Center the image inside the cell, then rotate by 45 around the chosen pivot.
int imgX = cellX + (cellW - 10 - imgW) / 2;
int imgY = cellY + (cellH - 10 - imgH) / 2 + 10;
PdfImageElement img = new PdfImageElement(jpegPath)
{
X = imgX,
Y = imgY,
Width = imgW,
Height = imgH,
RotationDegrees = 45,
RotationPivot = pivots[i]
};
img.Accessibility.AlternateText = $"Sample image rotated 45 degrees around {pivots[i]}";
PdfImageRenderInfo info = pdfEditor.AddImage(currentPage, img);
// Trace the rotated quad.
var rq = info.QuadPoints;
pdfEditor.AddPolygon(currentPage, new PdfPolygonElement(rq.TopLeft, rq.TopRight, rq.BottomRight, rq.BottomLeft)
{
FillColor = null,
BorderColor = PdfColor.Blue,
Border = new PdfLineStyle { LineWidth = 1f }
});
// Label.
PdfTextElement pivotLabel = new PdfTextElement(pivots[i].ToString(), smallFont)
{ X = cellX + 4, Y = cellY + 2, Width = cellW - 14 };
pivotLabel.Accessibility.StructureType = PdfStructureType.Artifact;
pdfEditor.AddText(currentPage, pivotLabel);
}
if (model.AddTallImage)
{
// ===== Section 5: Image taller than a page =====
currentPage = pdfEditor.AddPage();
crtYPos = topMargin;
crtYPos = AddSectionLabel(pdfEditor, ref currentPage, sectionFont,
"5. Image taller than a page" + (model.ContinueOnNextPage ? ", continued on the next pages" : ", cut at the bottom of the page"),
xLeft, crtYPos, ySeparator);
// A page added after the pages of the document: the image continues over it, or the pages where the
// image continues are inserted before it and it keeps its content, as the option asks
int pageAfterImage = pdfEditor.AddPage();
pdfEditor.AddText(pageAfterImage, new PdfTextElement("The page that followed the image", smallFont)
{
X = xLeft,
Y = topMargin
});
// The image is scaled to the content width. The pages of an edited document have no margins, so the
// continuation margins give the area of the pages where the image continues
PdfImageElement tallImage = new PdfImageElement(tallImagePath)
{
X = xLeft,
Y = crtYPos,
Width = contentWidth,
ContinueOnNextPage = model.ContinueOnNextPage,
InsertContinuationPages = model.InsertContinuationPages,
ContinuationTopMargin = topMargin,
ContinuationBottomMargin = topMargin
};
tallImage.Accessibility.AlternateText = "Annual activity report table";
PdfImageRenderInfo tallInfo = pdfEditor.AddImage(currentPage, tallImage);
// The rectangle of each part of the image, one per page; the content that follows the image is
// placed below the part drawn on its last page, or below the visible part when the image is cut
var partsText = new System.Text.StringBuilder(
"The image was drawn on " + tallInfo.RenderedRectangles.Count + " page(s):");
foreach (PdfRenderedRectangle part in tallInfo.RenderedRectangles)
{
partsText.AppendFormat(" page {0} ({1:F0}, {2:F0}, {3:F0}x{4:F0});", part.PageNumber,
part.Bounds.X, part.Bounds.Y, part.Bounds.Width, part.Bounds.Height);
}
currentPage = tallInfo.LastPage;
PdfRectangleF lastPart = tallInfo.LastPageRectangle.VisibleBounds ?? tallInfo.LastPageRectangle.Bounds;
PdfTextElement partsLabel = new PdfTextElement(partsText.ToString(), smallFont)
{
X = xLeft,
Y = (int)lastPart.Bottom + ySeparator,
Width = contentWidth
};
partsLabel.Accessibility.StructureType = PdfStructureType.Artifact;
pdfEditor.AddText(currentPage, partsLabel);
}
byte[] outPdfBuffer = pdfEditor.Save();
FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
fileResult.FileDownloadName = "PdfImageEditDemo.pdf";
return fileResult;
}
private int AddSectionLabel(PdfEditor editor, ref int currentPage, 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 = editor.AddText(currentPage, section);
currentPage = info.LastPageRectangle.PageNumber;
return (int)info.LastPageRectangle.Bounds.Bottom + separator;
}
private void EnsureSpaceOnPage(ref int crtYPos, ref int currentPage, int requestedHeight, PdfEditor pdfEditor, int contentHeight, int topMargin)
{
if (crtYPos + requestedHeight > contentHeight + topMargin)
{
// The content continues on the following page of the document, or on a new page after the last one
currentPage = currentPage < pdfEditor.PageCount ? currentPage + 1 : pdfEditor.AddPage();
crtYPos = topMargin;
}
}
private Add_Images_to_Existing_PDF_ViewModel SetViewModel()
{
var model = new Add_Images_to_Existing_PDF_ViewModel();
HttpRequest request = ControllerContext.HttpContext.Request;
UriBuilder uriBuilder = new UriBuilder
{
Scheme = request.Scheme,
Host = request.Host.Host,
Path = request.PathBase.ToString() + request.Path.ToString(),
Query = request.QueryString.ToString()
};
if (request.Host.Port != null)
uriBuilder.Port = (int)request.Host.Port;
string currentPageUrl = uriBuilder.Uri.AbsoluteUri;
string rootUrl = currentPageUrl.Substring(
0, currentPageUrl.Length - "Add_Images_to_Existing_PDF".Length);
// Default input is empty.pdf so this demo edits a fresh
// blank A4 page. The user can upload another PDF or paste
// a different URL
model.PdfFileUrl = rootUrl + "/DemoAppFiles/Input/PDF_Files/empty.pdf";
return model;
}
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");
}
}