DotPdf: Making Pages and Working with Coordinate Systems

March 30, 2017 Steve Hawley

From the Atalasoft Vault: This article was written by Steve in December 2011 following the release of Atalasoft's DotPdf SDK for PDF generation and programmatic templating. It's a reminder that PDF can be complex, but there are ways to simplify thinking about it, establishing standards, and breaking down hurdles. Also Smoots.

Early on in designing DotPdf, I decided that all coordinates should be expressed in PDF units.  Why?  This imposes either none or the same hardship on everyone.  Whether you work with inches, millimeters, cubits or whatever the process is the same.

PDF units are 1/72 of an inch.  This means that unless you're using only PDF units, you will likely need to convert your units to PDF units.

Fortunately, that's not hard.

public static double FromInch(double inches)  
return inches * 72;  
public static double ToInches(double pdf)  
return inches / 72;  
public static double FromMM(double mm)  
return mm * 25.4 * 72;  
public static double ToMM(double pdf)  
return pdf / (25.4 * 72);  

This will let you do unit conversions by calling the appropriate methods.

But you know there's an easier way.  Why should you feel obliged to litter your PDF generation code with a ton of calls to these unit converters?

The answer is in a simple object that implements IPdfRenderable.  IPdfRenderable is an interface that defines how an object can create content on a PDF page.  It is a very small interface and therefore very easy to create.  What we're going to do is take advantage of a PDF to have a transformation matrix applied to the entire page.  If we put this first on the page, we have the effect of converting all subsequent operations (and this includes all our shape objects) into the new coordinate space.   

using System;  
using Atalasoft.PdfDoc.Generating.Rendering;  
using Atalasoft.PdfDoc.Geometry;  
namespace PdfTransformingShape  
public class PageTransform : IPdfRenderable  
public PageTransform() : this(PdfTransform.Identity()) { }  
public PageTransform(PdfTransform transform)  
Transform = transform;  
public string Name { get; set; }  
public void Render(PdfPageRenderer r)  
if (Transform == null) throw new ArgumentNullException("Transform", "Transform may not be null");  
public PdfTransform Transform { get; set; }  

Now, you'll notice in this object that the only real work is in the Render() method.  Really, all I do is check for null, otherwise tell the renderer's drawing surface to apply the transformation.  I could check to see if the transform is the identity transform (so as to avoid redundant work), but that check already happens in ApplyTransformation, so you don't need to worry about it.

So how do you use it?  You make a document, make a page, add a PageTransform to the page, then do the rest of your work.

 Here is a simple example: 

PdfGeneratedDocument doc = new PdfGeneratedDocument();  
PdfGeneratedPage page = PdfDefaultPages.Letter;  
page.DrawingList.Add(new PageTransform(PdfTransform.Scale(FromInch(1.0))));  
page.DrawingList.Add(new PdfRectangle(new PdfBounds(3, 2, 1.5, 2.5), PdfColorFactory.FromRgb(.75, .25, .80)));  

You'll also notice that instead of constructing a PdfGeneratedPage directly, I'm going through the PdfDefaultPages object.  PdfDefaultPages is a collection of static factory properties, each of which will return a new PdfGeneratedPage of the appropriate size and orientation.  For me, this was also a must-have.  There is no reason to have to remember that a letter-size page is 612 x 792 PDF units and while you can use the conversion methods above (FromInch(8.5) x FromInch(11)), there is no need to do so. 

This example produces a single page PDF that looks like this (scaled down): 

Following the PDF coordinate system (which is proper Cartesian, not inverted Y), the lower left corner of the rectangle is at (3", 2") and the width is 1.5" and the height 2.5". 

So we see that by using the built-in page-level transformations, it's easy to change the units of the coordinate system to whatever you need it to be.  Anyone care to do Smoots?

About the Author

Steve Hawley

Steve was with Atalasoft from 2005 until 2015. He was responsible for the architecture and development of DotImage, and one of the masterminds behind Bacon Day. Steve has over 20 years of experience with companies like Bell Communications Research, Adobe Systems, Newfire, Presto Technologies.

Follow on Twitter More Content by Steve Hawley
Previous Article
Code 39 Barcode Generation
Code 39 Barcode Generation

Reading and writing Barcode 39 using Atalasoft DotImage.

Next Article
Any CPU Support (kind of)

AnyCPU is great. Period. You don’t need to think about architecture. It just works. Everyone loves to build...