PdfGeneratedPage–Architectural Whys (and a Few Hows)
If you are creating new pages in a PDF document using DotPdf, you’re going to be using PdfGeneratedPage objects. This is a very simple class in that it consists of really only 6 properties, 5 of which all deal with page dimensions in some way: MediaBox, CropBox, ArtBox, TrimBox, BleedBox and of these most people are only really likely to use MediaBox (the dimensions of the physical media/paper) and CropBox (a subrectangle of the MediaBox that determines the visible portion of the page). You may be shocked to find out that there really is not a lot of code in there either – mostly argument checking.
DrawingList is a collection that is meant to hold the things that go on the page. If you look at the actual declaration of a PdfDrawingList, it’s this:
public class PdfDrawingList : List<IPdfRenderable>, IPdfRenderable, IPdfResourceConsumer
{ /* ... */ }
IPdfRenderable defines the interface for objects that know how to render themselves. So PdfDrawingList is a list of IPdfRenderable. However, it also implements IPdfRenderable. Why? Rather than just be a collection, it is convenient that any collection of IPdfRenderable should also be IPdfRenderable. Even though PdfDrawingList doesn’t actually mark the page, it will delegate that responsibility to the objects it contains.
PdfDrawingList also implements IPdfResourceConsumer. This is an interface that says “I hold onto the name of a PDF Resource (font, colorspace, image) in some way”. This interface is used when a resource gets renamed. This allows a resource consumer to rename any resources it contains.
Could you implement your own container class? Absolutely – there’s nothing magic about them. Here’s an example of how you might make a BinaryTreeDrawingList:
[Serializable]
public class BinaryTreeDrawingList : IPdfRenderable, IPdfResourceConsumer
{
public IPdfRenderable Left { get; set; }
public IPdfRenderable Right { get; set; }
public String Name { get; set; }
public void Render(PdfPageRenderer r)
{
if (Left != null) Left.Render(r);
if (Right != null) Right.Render(r);
}
public void NotifyResourceRenamed(GlobalResources gr, PdfResourceClass resourceClass,
string originalName, string newName)
{
IPdfResourceConsumer rc = Left as IPdfResourceConsumer;
if (rc != null) rc.NotifyResourceRenamed(gr, resourceClass, originalName, newName);
rc = Right as IPdfResourceConsumer;
if (rc != null) rc.NotifyResourceRenamed(gr, resourceClass, originalName, newName);
}
}
This is a very simple definition – just create Left and Right nodes for the children and delegate rendering and resource renaming to them. Now, for a complete definition, you probably want to implement IEnumerable<IPdfGeneratable> (which should be yield the left or right children if they are non-null) so that you can use linq expressions on the contents. Maybe you want IEnumerable to deeply iterate too.
Another why question: why did I make this a collection of IPdfGeneratable rather than a collection PdfBaseShape, which is the base class for all our shapes? Simple – I didn’t want to take away your freedom of design. IPdfGeneratable is a very simple interface – two members. PdfBaseShape is far more complicated and chances are it includes things that you might not need. If you hate our shapes or for whatever reason you feel that they don’t meet your needs, I invite and encourage you to build things that do meet your needs. For example, let’s say that you have a WinForms control that you’ve used for years to display graphs from a data object. You could add the IPdfGeneratable interface to your already existing object and be able to now use it to generate PDF content as well as appear in a WinForms UI.
Since PdfDrawingList is IPdfGeneratable, you can put a drawing list inside a drawing list (please don’t put a drawing list inside itself or a child of itself, it will eventually cause a stack overflow just like you would expect). You can, for example, write code that generates a logo or letterhead and returns a PdfDrawingList that you can put directly into a new PdfGeneratedPage object. This is an easy way to get aggregations, simple layering/grouping, etc.
You also will note that IPdfGeneratable contains a Name property. Why? That’s entirely up to you. My intent was this: create a way to name objects and our customers will be able to create templates with names on them, store them (say in a database or a document store), load the templates later and then modify them. How do you modify them? Well, you could use the types of the objects in the drawing list, or you could just name them for what they are.
For example, I wrote the following utility function in test code to fill fields in a PDF template document:
public static void FillField(IList<IPdfGeneratable> things, string fieldName, string fieldValue)
{
string finalField = "Field:" + fieldName;
var fields = from thing in things where thing.Name == finalField
select thing as IPdfTextContainer;
foreach (IPdfTextContainer field in fields)
{
field.Text = fieldValue;
}
}
This makes it easy to do something like FillField(page.DrawingList, “Date”, DateTime.Now.ToString(“MM/dd/yy”)); which will find all objects named “Field:Date” on the page and set the text of the field to the current date. It doesn’t matter what the type of the actual object is, as long as it implements IPdfTextContainer. This means that you could make an compound shape (let’s day a drawing of the sun with the date in the middle) and this will work just as well as if you just a PdfTextLine.
Again, if you don’t like the notion of using a string within the object as an identifier, don’t use it – add your own mechanism for identifying particular objects, add it to you object that implements IPdfGeneratable and go to town. You can even turn things inside out. Let’s say that you like our circle type and don’t want to reimplement it, but you want a different identifier. Build and adapter class that contains a PdfCircle object and implements IPdfGeneratable, delegating to the PdfCircle code. Since the interface is simple, it’s short work.
Enjoy and start making cool PDFs!