DotPdf–Making Pages and Working with Coordinate Systems
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
{
[Serializable]
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");
r.DrawingSurface.ApplyTransformation(Transform);
}
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)));
doc.Pages.Add(page);
doc.Save("rectangle.pdf");
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?