I was looking at the code for the Dragon Curve and it struck me that this should be an easy fractal to represent in PDF. Even though the curve is usually represented with relative moves and turns which are trivial in PDF, I chose to stay closer to some of the examples in Rosetta Code to keep it consistent with their examples, if you wanted to side-by-side them. This is a great basic example on how to use DotPdf for generating graphics on a page.
The first thing was a class that wraps the Dragon curve code. There is one main entry that returns a PdfPath that represents the curve. You could parameterize it further by exposing length and split.
public class Dragon
{
private static double _sqrt2 = Math.Sqrt(2.0);
private PdfPath _path;
private double _angle;
private PdfPoint _currPoint;
private void Turn(double degrees)
{
_angle += degrees * Math.PI / 180.0;
}
private void Forward(double length)
{
_currPoint = _currPoint + new PdfPoint(Math.Cos(_angle) * length, Math.Sin(_angle) * length);
_path.LineTo(_currPoint);
}
private void MakeDragon(double length, int split, double d)
{
if (split == 0)
{
Forward(length);
}
else {
length /= _sqrt2;
Turn(d * 45);
MakeDragon(length, split - 1, 1);
Turn(-d * 90);
MakeDragon(length, split - 1, -1);
Turn(d * 45);
}
}
public PdfPath MakeDragon(IPdfColor color, PdfPoint startLocation)
{
_angle = 0;
_currPoint = new PdfPoint(startLocation);
_path = new PdfPath(color, .75);
_path.MoveTo(_currPoint);
MakeDragon(400, 12, 1);
return _path;
}
}
All that remains is to use this class in the context of a PdfDocument. First, I make a document, then a page, then I place the output of the Dragon class on the page. One thing you’ll note is that to make the page, I use the PdfDefaultPages class which is a set of factory properties each of which generates a new page in the size and orientation indicated by its name (I could’ve said eponymous size and orientation, but then I’d get a talking to about keeping my writing accessible. Have I mentioned that I love language recently?).
static void Main(string[] args)
{
PdfGeneratedDocument doc = new PdfGeneratedDocument();
doc.EmbedGeneratedContent = false;
PdfGeneratedPage page = PdfDefaultPages.LetterLandscape;
doc.Pages.Add(page);
Dragon dragon = new Dragon();
page.DrawingList.Add(dragon.MakeDragon(PdfColorFactory.FromRgb(.8, .25, .8), new PdfPoint(250, 250)));
doc.Save("dragon.pdf");
}
Seriously, that’s it. It’s a nice short, sweet example. Download DotPdf and run the code – you get a free trial!
Posted:
5/7/2012 2:46:56 PM by
Steve Hawley | with
0 comments
In this article, I’m going to talk about 80s era file formats and ways you can support them in .NET and keep your code sane, safe, and short, but to start, let’s talk about data file formats and why they are the way they are or were the way they were.
First, let’s consider why we even have data files. Persistence of data is one reason, but historically you would frequently see data files built because you couldn’t keep the entire dataset in memory. In the dim dark ages, you were lucky if you had virtual memory (I’m looking at you, Macintosh System 6 and earlier) or if it was lousy (I’m looking at you Windows 3.1 and earlier), so you would try to keep your memory footprint low. The obvious solution is to dump things to a temporary file then read them back in later when you needed them again. The trick is that you want to incur as little overhead as possible, so your reading and writing code had to be simple. A typical data structure might look like this:
typedef struct t_person {
char gender;
char age;
char firstname[14];
char lastname[16];
} t_person;
OK – that’s a quick, badly-designed structure for representing a person. So how would you write it out?
if (fwrite(somePerson, 1, sizeof(t_person), fp) != sizeof(t_person))
return FALSE; /* fail */
We like fwrite, oh yes we do. So much so that if we have an array of t_person, we might write this routine for dumping it out:
int writePeople(t_person *people, int count, FILE *fp)
{
int totalBytes = count * sizeof(t_person);
return fwrite(people, count, sizeof(t_person), fp) == totalBytes;
}
and then you think – oh dang, I didn’t write out how big the array was and then you realize that you’re filling a temp file with many heterogeneous types and wouldn’t it be better if the file was self identifying? So then you would write code like this:
int writeTag(INT32 tag, FILE *fp)
{
return fwrite(&tag, 1, sizeof(INT32), fp) == sizeof(INT32);
}
int writePeople(t_person *person, int count, FILE *fp)
{
if (!writeTag(kPersonTag, fp)) return FALSE;
if (!writeTag(count, fp)) return FALSE;
int totalBytes = count * sizeof(t_person);
return fwrite(people, count, sizeof(t_person), fp) == totalBytes;
}
and by solving a problem (forgot the array length and didn’t identify the type, I’ve created several new problems. First of, fwrite was fine to use if all I was writing was characters (this is a lie, by the way), but the moment I write a non-byte, I’m now writing the int with the implicit byte ordering of the processor running the code. That means that if the tag is 0x01020304, then on a little endian processor, you will write the following bytes in sequence: 04, 03, 02, 01, but on a big endian processor (such as a 68K), you would write 01, 02, 03, 04. This means that when you inevitably port your code from, say Windows to Macintosh, your reading code (if you use fread) won’t work. Ouch.
Why did we use fwrite again? Why did that seem like such a good idea? Well, we could write the entire data structure in one shot, one line of code. Otherwise, we would have to write code to write each element out in a platform-neutral way. You’re going to have to do that, because two things just entered the equation from C that made your life worse: some compilers take the liberty of changing your structure layout in order to make accessing the elements more efficient. So that lie back there about fwrite being fine with a struct of only characters: if your compiler inserts pad bytes, you’re writing more data than you suspect. Dang. Then, when C introduce enumerated values, they left it to the compiler to decide the data type best-suited for holding all the enumerated values. The problem is that some compilers made different decisions than others and so your struct size would be different, depending on the compiler.
At this point you realize that writing structs isn’t quite so easy, and maybe that job at the garden center is looking a whole lot nicer. Still, this gives you a little historical perspective.
Flash forward to current technologies. How can we read these 80’s era data structures into C# and not suffer. You could try to set up a struct in C# and play games with struct layout attributes and read the data in in one fell swoop, but trust me, this is not the way to go. If that was bad in the 80’s, it’s just as bad now.
Let’s make some assumptions – first, let’s pretend that our data file is in big endian order. Second, let’s assume that we already have a class has methods in it like this:
public class BigEndianReader {
public static bool Read(Stream stm, out ulong ul) { /* ... */ }
public static bool Read(Stream stm, out long ul) { /* ... */ }
public static bool Read(Stream stm, out uint ul) { /* ... */ }
public static bool Read(Stream stm, out int ul) { /* ... */ }
public static bool Read(Stream stm, out short ul) { /* ... */ }
public static bool Read(Stream stm, out ushort ul) { /* ... */ }
public static bool ReadScalar(Stream stm, Type ft, out object o)
{
if (ft == typeof(byte))
{
int b = stm.ReadByte();
if (b < 0) return false;
o = (byte)b;
return true;
}
if (ft == typeof(sbyte))
{
int b = stm.ReadByte();
if (b < 0) return false;
o = (sbyte)b;
return true;
}
if (ft == typeof(int))
{
int i = 0;
if (!Read(stm, out i)) return false;
o = i;
return true;
}
// ...
o = null;
return false;
}
}
This is a set of methods that handle reading in scalar types and one general routine for switching based on the type. This is very straightforward code – no surprises.
Now lets figure out how to read in an array of scalars without knowing it’s element type:
private static bool ReadIntoArray(Stream stm, Type ft, FieldInfo fi, object o)
{
Array arr = fi.GetValue(o) as Array;
if (arr == null) return false;
Type arrType = arr.GetType().GetElementType();
if (!IsScalar(arrType)) return false;
for (int i=0; i < arr.GetLength(0); i++) {
object val = null;
if (!ReadScalar(stm, arrType, out val)) return false;
arr.SetValue(val, i);
}
return true;
}
This will read in an array of scalars. How do we know that the array type is a scalar? We have a private predicate IsScalar() that tells us. It essentially checks to see if the type is byte, sbyte, short, ushort, etc.
Now this is where the fun part comes in. We write a routine to auto-populate a data structure using reflection:
public static bool ReadType(Stream stm, object o, params string[] names)
{
Type t = o.GetType();
foreach(string name in names)
{
FieldInfo fi = t.GetField(name);
if (fi == null)
throw new ArgumentException("unable to find field " + name);
Type ft = fi.FieldType;
if (IsScalar(ft))
{
object val = null;
if (!ReadScalar(stm, ft, out val)) return false;
fi.SetValue(o, val);
}
else if (IsArray(ft))
{
ReadIntoArray(stm, ft, fi, o);
}
}
}
In this routine, we pass in a Stream, an object, and a list of names of fields to be populated. We could also use properties if we wanted, but I’m sticking with fields right now. With all this in place, let’s set our stage for parsing data from TrueType fonts. Keep in mind that these structures are purely representational and are not part of a good API since the abstraction is wrong. Here is a class that represents the TrueType font header structure:
class TTFontHeader {
public uint TableVersion;
public uint FontRevision;
public uint CheckSumAdjustment;
public uint MagicNumber;
public ushort Flags;
public ushort UnitsPerEm;
public long Created;
public long Modified;
public short XMin;
public short YMin;
public short XMax;
public short YMax;
public ushort MacStyle;
public ushort LowestRecPPEM;
public short FontDirectionHint;
public short IndexToLocFormat;
public short GlyphDataFormat;
public static TTFontHeader FromStream(Stream stm)
{
TTFontHeader fh = new TTFontHeader();
if (!BigEndianReader.ReadType(stm, fh, "TableVersion", "FontRevision", "CheckSumAdjustment", "MagicNumber", "Flags",
"UnitsPerEm", "Created", "Modified", "XMin", "YMin", "XMax", "YMax", "MacStyle", "LowestRecPPEM",
"FontDirectionHint", "IndexToLocFormat", "GlyphDataFormat"))
throw new Exception("unable to read TTFont header");
return fh;
}
}
Now I’ve got very simple code to read in all the fields. Just to remind you of the hell of the 80’s, recall that for any given data structure you may have different versions. For example, the OS/2 metrics table inside TrueType files may include more fields depending on the version. We can work with that by writing code like this:
public static TTOS2WindowsMetrics FromStream(Stream stm)
{
ushort version = 0;
if (!BigEndianReader.Read(stm, out version))
throw new Exception("failure reading OS/2 version");
TTOS2WindowsMetrics met = new TTOS2WindowsMetrics(version);
string[] names = null;
switch (version) {
case 0: names = _v0FieldNames; break;
case 1: names = _v1FieldNames; break;
case 2: case 3: case 4: names = _v2and3and4Fields; break;
default: throw new Exception("unexpected OS/2 metrics version");
}
if (!BigEndianReader.ReadType(stm, met, names))
throw new Exception("failure reading OS/2 metrics");
return met;
}
One of the reasons why I like this is that it will fail fast if you rename fields (you unit tested that, right?) and should be highly round-trip testable which should make it easy to catch missed values, typos in strings and so on. Again, this example uses fields for all the data values. This is a decision to model the C structures as directly as possible. They could very easily have been properties or the code code be extended to handle either. When you build appropriate small tools, you can build robust higher level tools for making the mundane easier to do.
Posted:
5/4/2012 2:06:48 PM by
Steve Hawley | with
0 comments
DotPdf comes with a number of built-in shapes and as I mentioned in a previous blog, it’s easy to define new shapes. It’s very easy to make shapes, but sometimes we can get carried away and lose sense of what should be a shape and what should not be a shape. This blog is going to be about guidelines for categorizing your page elements and infrastructure for making large-scale document production easier.
First, let’s talk about what makes a shape. A shape should be:
-
Simple to represent in data (ie, you should be able to attach the [Serializable] attribute and not worry about what happens with default behaviors)
-
It should be blissfully unaware of the page on which it sits (or more precisely the drawing list in which it resides)
-
It should have absolutely no business logic
Now, let’s talk about what makes a shape generator. A shape generator can/should be:
-
Simple or complex in data representation (it may pull its data from elsewhere)
-
Aware (or unaware) of page layout
-
Nearly all logic (business or layout)
Let’s take an example – suppose you want to make a system to generate ebooks on the fly and you want to have page automatic page numbering. You could do that with a page number shape. It fits the categories above – for representation, you need the page number, the location on the page (but you don’t really care where). It’s pretty simple. At least it seems pretty simple, until you start considering how page numbers are laid out in reality. First, page numbers can be at the top or the bottom of the page. Second, the page numbers can be on verso (left) or recto (right) or center or alternating recto/verso. Finally page numbers can be in either Roman (upper or lower) or Arabic numerals. Phew.
This to me says that the actual page number itself is a shape, but everything around it is a generator that should be encapsulated. So let’s make that generator. First we’ll start with a few enumerations:
public enum VerticalPositionStyle
{
TopRelative,
BottomRelative
}
public enum HorizontalPositionStyle
{
Center,
Left,
Right,
Alternating
}
These enums will let us decide how to position the shape on the page. Now let’s think about the data that we need to represent a page number generator. We’ll need the page number, the name of the font resource, the font size, the offset from either the top or bottom of the page, the left and right margins and whether or not we are working in Roman numerals. So for this: let’s lay out some properties:
public string FontResourceName { get; set; }
public double FontSize { get; set; }
public int Current { get; set; }
public double VerticalOffset { get; set; }
public double LeftMargin { get; set; }
public double RightMargin { get; set; }
public bool IsRomanNumerals { get; set; }
public VerticalPositionStyle VerticalPositionStyle { get; set; }
public HorizontalPositionStyle HorizontalPositionStyle { get; set; }
These are all straightforward – if these were my own code for release, I would null check FontResourceName and range check FontSize at the very least, but this is sample code, so better to be brief.
Given these, we can set up a constructor:
public PageNumberGenerator(string fontResourceName, double fontSize)
{
if (fontResourceName == null) throw new ArgumentNullException("fontResourceName");
if (fontSize <= 0) throw new ArgumentOutOfRangeException("fontSize");
VerticalPositionStyle = VerticalPositionStyle.BottomRelative;
HorizontalPositionStyle = HorizontalPositionStyle.Alternating;
FontResourceName = fontResourceName;
FontSize = fontSize;
VerticalOffset = 72 * .75;
LeftMargin = 72.0;
RightMargin = 72.0;
IsRomanNumerals = false;
Current = 1;
}
Again, no surprises. So how do we generate the content? For this, I broke it out very procedurally:
public virtual IPdfRenderable NextPageNumber(PdfGeneratedPage page)
{
double y = GetYPosition(page);
double[] xs = GetXStartAndEnd(page);
PdfTextBox textBox = new PdfTextBox(new PdfBounds(xs[0], y, Math.Abs(xs[1] - xs[0]), FontSize + 2), FontResourceName, FontSize);
textBox.Alignment = GetAlignment();
textBox.Text = GetNumberText();
AdvanceCurrent();
return textBox;
}
In this code, I get the y position of the text and the start and end x coordinates. I make a PdfTextBox with bounds set so that the box covers the entire distance from left margin to right margin at the given y coordinate. This isn’t strictly necessary – we could just figure out where to put the text and use a PdfTextLine, but PdfTextBox will do all the alignment for us, so why sweat it? Finally, we set the text of the box, advance the page number and move on.
With the process written, we can worry about filling in the sub tasks. First – getting the y position:
protected virtual double GetYPosition(PdfGeneratedPage page)
{
switch (VerticalPositionStyle)
{
case VerticalPositionStyle.BottomRelative:
return VerticalOffset;
case VerticalPositionStyle.TopRelative:
return page.MediaBox.Top - VerticalOffset;
default:
throw new ArgumentException("VerticalPositionStyle", "VerticalPositionStyle is out of range");
}
}
For this, we calculate Y based on whether it needs to be relative to the bottom or the top. Now – getting the x positions:
protected virtual double[] GetXStartAndEnd(PdfGeneratedPage page)
{
double[] xs = new double[2];
xs[0] = LeftMargin;
xs[1] = page.MediaBox.Right - RightMargin;
return xs;
}
Again, straight forward. I don’t like making the array, but I like it more than using out parameters. It’d be nice to have tuples. Now getting the alignment:
protected virtual PdfTextAlignment GetAlignment()
{
switch (HorizontalPositionStyle)
{
case HorizontalPositionStyle.Center:
return PdfTextAlignment.Center;
case HorizontalPositionStyle.Left:
return PdfTextAlignment.Left;
case HorizontalPositionStyle.Right:
return PdfTextAlignment.Right;
case HorizontalPositionStyle.Alternating:
return (Current & 1) == 0 ? PdfTextAlignment.Left : PdfTextAlignment.Right;
default:
throw new ArgumentException("HorizontalPositionStyle", "HorizontalPositionStyle is out of range");
}
}
How easy! The only trick is for alternating – I use the 0 bit of the page number to determine odd/even and switch between left and right based on that. Now getting the number text and advancing the number:
protected virtual string GetNumberText()
{
return IsRomanNumerals ? Current.ToRoman(false) : Current.ToString();
}
protected virtual void AdvanceCurrent()
{
Current += 1;
}
For Roman numerals, I made an extension method on int that creates a string in Roman numerals from the int in either upper or lower case. I found a very nice implementation here and I wrapped it up in an extension method. I’ll post the whole thing at the end. So how does this feel when it’s put into usage? Not bad at all:
PdfGeneratedDocument doc = new PdfGeneratedDocument();
string tnr = doc.Resources.Fonts.AddFromFontName("Times New Roman");
PageNumberGenerator gen = new PageNumberGenerator(tnr, 10);
gen.IsRomanNumerals = true;
gen.HorizontalPositionStyle = HorizontalPositionStyle.Center;
for (int i = 0; i < 10; i++)
{
PdfGeneratedPage page = PdfDefaultPages.Letter;
page.DrawingList.Add(gen.NextPageNumber(page));
doc.Pages.Add(page);
}
gen.IsRomanNumerals = false;
gen.HorizontalPositionStyle = HorizontalPositionStyle.Alternating;
gen.Current = 1;
for (int i = 0; i < 5; i++)
{
PdfGeneratedPage page = PdfDefaultPages.Letter;
page.DrawingList.Add(gen.NextPageNumber(page));
doc.Pages.Add(page);
}
doc.Save("numberedpages.pdf");
In this code I’m making 15 pages, the first 5 are in Roman numerals, the rest in Arabic. Now as you’re reading through this code you’ll notice that all the procedural steps are protected virtual methods. Why? The answer is that by sublcassing the PageNumberGenerator you could make a number of changes that are tuned to your needs. For example, you could override the GetNumberText() method and make it look like this:
protected override string GetNumberTest()
{
string num = super.GetNumerText();
return "[" + "]";
}
and put each page number in a box. You could also do something much more flexible like this:
public string FormatString { get; set; }
protected override string GetNumberText()
{
string text = super.GetNumberText();
return String.Format(FomatString ?? text, text);
}
which would let you use any formatting text. You could set it to “Page {0} of 100”.
You could also override the NextPageNumber method and call the main code but add adornments like lines around the text or embed the page number in a colored circle.
You can see that by making careful decisions about whether a page object is a shape or comes from a shape generator, it’s easy to make very flexible code. Next time, I’ll talk about how you can generalize the shape generator concept and treat PDF generation as a process of corralling sets of page generators.
Here are the classes used in their entirety.
First PageNumberGenerator:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Atalasoft.PdfDoc.Generating.Rendering;
using Atalasoft.PdfDoc.Generating;
using Atalasoft.PdfDoc.Generating.Shapes;
using Atalasoft.PdfDoc.Geometry;
namespace PageNumberer
{
public enum VerticalPositionStyle
{
TopRelative,
BottomRelative
}
public enum HorizontalPositionStyle
{
Center,
Left,
Right,
Alternating
}
public class PageNumberGenerator
{
public PageNumberGenerator(string fontResourceName, double fontSize)
{
if (fontResourceName == null) throw new ArgumentNullException("fontResourceName");
if (fontSize <= 0) throw new ArgumentOutOfRangeException("fontSize");
VerticalPositionStyle = VerticalPositionStyle.BottomRelative;
HorizontalPositionStyle = HorizontalPositionStyle.Alternating;
FontResourceName = fontResourceName;
FontSize = fontSize;
VerticalOffset = 72 * .75;
LeftMargin = 72.0;
RightMargin = 72.0;
IsRomanNumerals = false;
Current = 1;
}
public virtual IPdfRenderable NextPageNumber(PdfGeneratedPage page)
{
double y = GetYPosition(page);
double[] xs = GetXStartAndEnd(page);
PdfTextBox textPath = new PdfTextBox(new PdfBounds(xs[0], y, Math.Abs(xs[1] - xs[0]), FontSize + 2), FontResourceName, FontSize);
textPath.Alignment = GetAlignment();
textPath.Text = GetNumberText();
AdvanceCurrent();
return textPath;
}
protected virtual double GetYPosition(PdfGeneratedPage page)
{
switch (VerticalPositionStyle)
{
case VerticalPositionStyle.BottomRelative:
return VerticalOffset;
case VerticalPositionStyle.TopRelative:
return page.MediaBox.Top - VerticalOffset;
default:
throw new ArgumentException("VerticalPositionStyle", "VerticalPositionStyle is out of range");
}
}
protected virtual double[] GetXStartAndEnd(PdfGeneratedPage page)
{
double[] xs = new double[2];
xs[0] = LeftMargin;
xs[1] = page.MediaBox.Right - RightMargin;
return xs;
}
protected virtual PdfTextAlignment GetAlignment()
{
switch (HorizontalPositionStyle)
{
case HorizontalPositionStyle.Center:
return PdfTextAlignment.Center;
case HorizontalPositionStyle.Left:
return PdfTextAlignment.Left;
case HorizontalPositionStyle.Right:
return PdfTextAlignment.Right;
case HorizontalPositionStyle.Alternating:
return (Current & 1) == 0 ? PdfTextAlignment.Left : PdfTextAlignment.Right;
default:
throw new ArgumentException("HorizontalPositionStyle", "HorizontalPositionStyle is out of range");
}
}
protected virtual string GetNumberText()
{
return IsRomanNumerals ? Current.ToRoman(false) : Current.ToString();
}
protected virtual void AdvanceCurrent()
{
Current += 1;
}
public string FontResourceName { get; set; }
public double FontSize { get; set; }
public int Current { get; set; }
public double VerticalOffset { get; set; }
public double LeftMargin { get; set; }
public double RightMargin { get; set; }
public bool IsRomanNumerals { get; set; }
public VerticalPositionStyle VerticalPositionStyle { get; set; }
public HorizontalPositionStyle HorizontalPositionStyle { get; set; }
}
}
and now RomanConverter:
using System;
namespace PageNumberer
{
public static class RomanConverter
{
private static int[] values = new int[] { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 };
private static string[] numerals = new string[] { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
public static string ToRoman(this int number, bool upper)
{
if (number < 0 || number > 3999)
throw new ArgumentOutOfRangeException("number", "Value must be in the range 0 - 3,999.");
if (number == 0) return "N";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 13; i++)
{
while (number >= values[i])
{
number -= values[i];
sb.Append(numerals[i]);
}
}
return upper ? sb.ToString() : sb.ToString().ToLower();
}
}
}
Finally the test code:
using System;
using Atalasoft.PdfDoc.Generating;
namespace PageNumberer
{
class Program
{
static void Main(string[] args)
{
PdfGeneratedDocument doc = new PdfGeneratedDocument();
string tnr = doc.Resources.Fonts.AddFromFontName("Times New Roman");
PageNumberGenerator gen = new PageNumberGenerator(tnr, 10);
gen.IsRomanNumerals = true;
gen.HorizontalPositionStyle = HorizontalPositionStyle.Center;
for (int i = 0; i < 10; i++)
{
PdfGeneratedPage page = PdfDefaultPages.Letter;
page.DrawingList.Add(gen.NextPageNumber(page));
doc.Pages.Add(page);
}
gen.IsRomanNumerals = false;
gen.HorizontalPositionStyle = HorizontalPositionStyle.Alternating;
gen.Current = 1;
for (int i = 0; i < 5; i++)
{
PdfGeneratedPage page = PdfDefaultPages.Letter;
page.DrawingList.Add(gen.NextPageNumber(page));
doc.Pages.Add(page);
}
doc.Save("numberedpages.pdf");
}
}
}
Posted:
2/23/2012 10:18:52 AM by
Steve Hawley | with
0 comments
One of the main ways of creating page content in DotPdf is to use shapes. We give you a number of “canned” shapes that are very easy to work with (circle, rectangle, path, text, etc.), but you will probably need to make your own shapes at some point. This article is going to show you one way to do that that is very easy.
Let’s say that you need to create a letterhead that needs to have a donut in the logo. You could just draw the donut directly, but let’s say that you’re trying to create a whole corporate presence around donuts and you need to draw a lot of donuts in a lot of places. The way to do that is to make a shape.
What is a donut? It’s two circles of different radii drawn at the same center with a fill color and an outline color. Great – these feel like things to pass to the constructor and for some properties. We’ll start by making a Donut class that is a subclass of PdfBaseShape:
using System;
using System.Drawing;
using Atalasoft.PdfDoc.Generating.Shapes;
using Atalasoft.PdfDoc.Generating;
using Atalasoft.PdfDoc.Geometry;
namespace DonutShape
{
[Serializable]
public class Donut : PdfBaseShape
{
public Donut(PdfPoint center, double innerRadius, double outerRadius, IPdfColor outlineColor, double linewidth, IPdfColor fillColor)
: base(outlineColor, linewidth, fillColor)
{
Center = center;
OuterRadius = outerRadius;
InnerRadius = innerRadius;
}
public Donut(PdfPoint center, double innerRadius, double outerRadius)
: this(center, innerRadius, outerRadius, PdfColorFactory.FromColor(Color.Black), 1.0, PdfColorFactory.FromColor(Color.Brown))
{
}
protected override PdfBaseShape CloneInstance()
{
return new Donut(Center, InnerRadius, OuterRadius); // colors will get set by the base class
}
protected override void DrawShape(Atalasoft.PdfDoc.Generating.Rendering.PdfPageRenderer r)
{
throw new NotImplementedException();
}
public PdfPoint Center { get; set; }
public double OuterRadius { get; set; }
public double InnerRadius { get; set; }
}
}
In this case, I made two constructors, one that takes the line color, line thickness and fill color and one that creates a default. Since colors and line styles are handled by the base class, we can just pass those on and forget about them for now. There are really only two pieces of work: be able to clone the shape and be able to draw the shape. Cloning is easy – just call a constructor with the center and radii. Drawing is also easy, but let’s concentrate on that code on its own:
protected override void DrawShape(Atalasoft.PdfDoc.Generating.Rendering.PdfPageRenderer r)
{
PdfCircle outer = new PdfCircle(Center, OuterRadius, OutlineColor, Style.Width, FillColor);
PdfCircle inner = new PdfCircle(Center, InnerRadius, OutlineColor, Style.Width, PdfColorFactory.FromColor(Color.White));
outer.Render(r);
inner.Render(r);
}
In this routine, I create two circles, one for the outer circle and one for the inner. The outer I create with the Donut’s line and fill properties. The inner I do the same except that the fill I set to white. I can try it out with the following test code:
PdfGeneratedDocument doc = new PdfGeneratedDocument();
PdfGeneratedPage page = PdfDefaultPages.Letter;
doc.Pages.Add(page);
Donut donut = new Donut(new PdfPoint(288, 400), 18, 100);
page.DrawingList.Add(donut);
doc.Save("donut.pdf");
When I run the app, I get a page that looks like this:

Hooray – that’s just what I expected! There’s a problem, though. If we draw the donut over the top of something else it will cover it up entirely, which is not what we expected:
PdfGeneratedDocument doc = new PdfGeneratedDocument();
PdfGeneratedPage page = PdfDefaultPages.Letter;
doc.Pages.Add(page);
// Add a red line from the center out
PdfPath path = new PdfPath(PdfColorFactory.FromColor(Color.Red), 8);
path.MoveTo(288, 400);
path.LineTo(500, 500);
page.DrawingList.Add(path);
Donut donut = new Donut(new PdfPoint(288, 400), 18, 100);
page.DrawingList.Add(donut);
doc.Save("donut.pdf");

The problem is clear – the inner circle is being painted white. This will cover up anything under it. In fact, we depend on that to cover up the brown donut. The solution is to change our approach. Instead of using two PdfCircle shapes, we’re going to use one PdfPath shape to represent both circles. A path is a collection of operations that include move, draw line, draw Bezier curve, close the path. The really cool thing is that a path can contain any number of possibly disjoint subpaths, so two circles are easy. To start off with, we need a routine that give a PdfPath shape will add in a circle composed of Bezier curves. To do this, I’ll break this out into a new method. The code presented here is based on an article by Don Lancaster. If you want to learn a great deal about the math behind Bezier curves, this is a great place to start.
protected override void DrawShape(Atalasoft.PdfDoc.Generating.Rendering.PdfPageRenderer r)
{
PdfPath path = new PdfPath(OutlineColor, Style.Width, FillColor);
MakeCircle(path, OuterRadius, Center);
MakeCircle(path, InnerRadius, Center);
path.Render(r);
}
private static PdfPath MakeCircle(PdfPath path, double radius, PdfPoint center)
{
double magic = 0.551784 * radius;
path.MoveTo(new PdfPoint(-radius, 0) + center);
path.CurveTo(new PdfPoint(-radius, magic) + center, new PdfPoint(-magic, radius) + center, new PdfPoint(0, radius) + center);
path.CurveTo(new PdfPoint(magic, radius) + center, new PdfPoint(radius, magic) + center, new PdfPoint(radius, 0) + center);
path.CurveTo(new PdfPoint(radius, -magic) + center, new PdfPoint(magic, -radius) + center, new PdfPoint(0, -radius) + center);
path.CurveTo(new PdfPoint(-magic, -radius) + center, new PdfPoint(-radius, -magic) + center, new PdfPoint(-radius, 0) + center);
return path;
}

And, tada, here is our shape. The question is, why isn’t the donut hole filled? The answer is that PDF has two different ways to fill paths. The first is called the Even-Odd Rule. The PDF renderer figures out how many lines have been crossed as it goes left to right. If the number is odd, it fills. If the number is even it doesn’t. This is the default method for filling. The other method is called the Non-Zero Winding Rule. It is more complicate and has to do with the direction that lines are drawn as they cross the scanline being filled. Lines that cross bottom to top add 1 to the winding number. Lines that cross top to bottom take one away. If the winding number is non-zero, the scanline gets filled. If this circle had been filled with NZW, the center would be brown and cover up the red line.
As a final best practice rule for making reusable shapes: make shapes as generic as possible and put them into their own assembly away from the rest of your application. This will make it easier to reuse the shape in other projects and will make it more easier to reload the PDF in DotPdf for further editing.
Posted:
1/23/2012 12:20:33 PM by
Steve Hawley | with
0 comments
Atalasoft is moving the blogs to new hosting software. In the meantime, here are a few favorites of mine from the past:
Posted:
1/19/2012 10:52:58 AM by
Steve Hawley | with
0 comments