Generating Notepaper with PdfSharp

PdfSharp is a lovely library that lets you generate/draw pdf documents programmatically. I used it to generate some notepaper and found it easy to work with once the basics of the objects are understood. We need to create a document, give it some properties, then we can save it.


using PdfSharp.Drawing;
using PdfSharp.Pdf;

...

PdfDocument document = new PdfDocument
{
	PageLayout = PdfPageLayout.SinglePage,
	PageMode = PdfPageMode.UseNone
};

document.Save(filename);


The next obvious thing is to add some pages. Each page can have it’s own size and orientation.


PdfPage newPage = document.AddPage();
newPage.Size = PdfSharp.PageSize.A4;
newPage.Orientation = PdfSharp.PageOrientation.Landscape;


Now that we have a page or two, we want to put some content into them. PdfSharp has a number of objects that are analogs of the GDI drawing objects in Windows, things like Graphics, Pens, Brushes etc. and a set of similar methods to draw lines, rectangles, ellipses.

In order to be able to draw some notepaper, I wanted to be able to have a white border around each page and maybe print on all the page or print an A5 signature as two halves of a landscape A4 page. This meant I needed something to represent one or more printable areas for each page.


public class PrintableArea
{
	public int Left { get; set; }
	public int Right { get; set; }
	public int Top { get; set; }
	public int Bottom { get; set; }

	public static IEnumerable<PrintableArea> FromPage(PdfPage page, int border, bool split)
	{
		var areas = new List<PrintableArea>();

		if (split)
		{
			PrintableArea pane1 = new PrintableArea
			{
				Left = border,
				Right = ((int)page.Width / 2) - border,
				Top = border,
				Bottom = (int)page.Height - border
			};

			PrintableArea pane2 = new PrintableArea
			{
				Left = pane1.Right + (2 * border),
				Right = (int)page.Width - border,
				Top = border,
				Bottom = (int)page.Height - border
			};

			areas.Add(pane1);
			areas.Add(pane2);
		}
		else
		{
			PrintableArea single = new PrintableArea
			{
				Left = border,
				Right = (int)page.Width - border,
				Top = border,
				Bottom = (int)page.Height - border
			};

			areas.Add(single);
		}

		return areas;
	}
}


Each kind of paper - graph, dotted, music manuscript, lined - implies an interface. Let’s call it IPageRenderer.


public interface IPageRenderer
{
	int PageBorder { get; set; }

	void Render(PdfPage page, IEnumerable<PrintableArea> areas);
}


So now we can treat a list of renderings as a group using a page generator.


public class PageGenerator
{
	public bool SplitPages { get; set; }

	public PdfSharp.PageSize PageSize { get; set; }

	public PdfSharp.PageOrientation PageOrientation { get; set; }

	public int PageBorder { get; set; }

	public void GeneratePages(PdfDocument document, int pages, IEnumerable<IPageRenderer> renderers)
	{
		foreach(var renderer in renderers)
		{
			renderer.PageBorder = this.PageBorder;

			for (int page = 0; page < pages; ++page)
			{
				PdfPage newPage = document.AddPage();
				newPage.Size = this.PageSize;
				newPage.Orientation = this.PageOrientation;

				var areas = PrintableArea.FromPage(newPage, this.PageBorder, this.SplitPages);
				renderer.Render(newPage, areas);
			}
		}
	}
}

...


var renderers = new List<IPageRenderer>();
renderers.Add(new ManuscriptPaperRenderer(58, 6));
renderers.Add(new DottedPaperRenderer(15, 1));
renderers.Add(new LinedPaperRenderer(18));
renderers.Add(new GraphPaperRenderer(16));

PageGenerator pageGenerator = new PageGenerator
{
	PageBorder = pageBorder,
	PageOrientation = pageOrientation,
	PageSize = PdfSharp.PageSize.A4,
	SplitPages = splitPages
};

int pagesEach = 1;
pageGenerator.GeneratePages(document, pagesEach, renderers);

			
			




Finally, the implementation of each of the different types of renderer.

Music Manuscript


public class ManuscriptPaperRenderer : IPageRenderer
{
	private readonly int staveSpacing;

	private readonly int lineSpacing;

	public ManuscriptPaperRenderer(int staveSpacing, int lineSpacing)
	{
		this.staveSpacing = staveSpacing;
		this.lineSpacing = lineSpacing;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.Black, 0.0);

			foreach (PrintableArea area in areas)
			{
				for (int horizontalLine = area.Top; horizontalLine < area.Bottom; horizontalLine += this.staveSpacing)
				{
					DrawStave(gfx, pen, horizontalLine, area.Left, area.Right, this.lineSpacing);
				}
			}
		}
	}

	private static void DrawStave(XGraphics graphics, XPen pen, int topLine, int start, int end, int lineSpacing)
	{
		int linesPerStave = 5;
		for (int horizontalLine = 0; horizontalLine < linesPerStave; horizontalLine++)
		{
			graphics.DrawLine(pen, start, topLine + (horizontalLine * lineSpacing), end, topLine + (horizontalLine * lineSpacing));
		}

		// draw end caps to lines
		graphics.DrawLine(pen, start, topLine, start, topLine + ((linesPerStave - 1) * lineSpacing));
		graphics.DrawLine(pen, end, topLine, end, topLine + ((linesPerStave - 1) * lineSpacing));
	}
}


Dotted


public class DottedPaperRenderer : IPageRenderer
{
	private readonly int gridSize;

	private readonly int dotSize;

	public DottedPaperRenderer(int gridSize, int dotSize)
	{
		this.gridSize = gridSize;
		this.dotSize = dotSize;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.LightGray, 0.0);

			foreach (PrintableArea area in areas)
			{
				for (int x = area.Left; x < area.Right; x += this.gridSize)
				{
					for (int y = area.Top; y < area.Bottom; y += this.gridSize)
					{
						gfx.DrawEllipse(pen, x, y, this.dotSize, this.dotSize);
					}
				}
			}
		}
	}
}


Lined


public class LinedPaperRenderer : IPageRenderer
{
	private readonly int lineSpacing;

	public LinedPaperRenderer(int lineSpacing)
	{
		this.lineSpacing = lineSpacing;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.LightGray, 0.0);

			foreach (PrintableArea area in areas)
			{
				for (int horizontalLine = area.Top; horizontalLine < area.Bottom; horizontalLine += this.lineSpacing)
				{
					gfx.DrawLine(pen, area.Left, horizontalLine, area.Right, horizontalLine);
				}
			}
		}
	}
}


Graph


public class GraphPaperRenderer : IPageRenderer
{
	private readonly int gridSize;

	public GraphPaperRenderer(int gridSize)
	{
		this.gridSize = gridSize;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.LightGray, 0.0);

			foreach(PrintableArea area in areas)
			{
				for (int verticalLine = area.Left; verticalLine < area.Right; verticalLine += gridSize)
				{
					gfx.DrawLine(pen, verticalLine, area.Top, verticalLine, area.Bottom);
				}

				for (int horizontalLine = area.Top; horizontalLine < area.Bottom; horizontalLine += gridSize)
				{
					gfx.DrawLine(pen, area.Left, horizontalLine, area.Right, horizontalLine);
				}
			}
		}
	}
}


Examples of each type of paper are available for download.