Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Printing Address Labels From ASP.NET

0.00/5 (No votes)
27 Nov 2014 1  
Creating sheets of labels within a PDF document using PDFSharp

Introduction

Sometimes we need to print information from ASP.Net that needs to be formatted on the page in a very structured manner. ASP.Net can easily create a HTML page which can be printed, but this gives little consideration to how large the page is, where the text and images need to appear, or the margins and padding on the page.

When we want to print onto a sheet of pre-cut labels such as Avery labels (see here for example) we need to control the printing position very carefully. One of the best ways to do this is to create a PDF document and control the layout within that document, when we come to print that PDF document out these dimensions will be respected by the printer.

Licensing and External Libraries

I recently wrote an article used iTextSharp as a plug in library. I have since learnt that the iTextSharp AGPL license does not allow us to use it in a truely free manner and requires you to purchase a commercial license ($2,200) if you wish to use it in a commercial application, for this reason I will be using the PDFSharp library in this article. PDFSharp is released under the MIT license which means that we can use this code without restriction for no cost provided that we leave the copyright notices in place. For many people this is very acceptable.

You can find the PDFSharp website here, and their SoundForge project page here.

Using the code

This project will be using the PDFSharp library to create PDFs. To use this library you normally need to download the source code (found here), and then either insert the source code into your project or compile the library into a DLL and use that. For anyone who does not have the ability to compile the source files into a DLL I have done this for you and attached it to the article. You will find a download link at the top of this page. The DLL is already present in the source code for the project if you decide to download that.

The PDFSharp library is MIT licensed meaning that it is completely open and free to use provided you leave the copyright notices intact. The sample applies to the source code we have generated for this project.

If you want to download the complete project please download the file PdfSharpLabels.zip attached to this article.

I make several references to Avery Label format L7163 throughout this article; I will be using this label format throughout the article. It's specific characteristics are not important to the actual project, it was simple the first pack of labels I laid my hands on when starting this work.

Below we can see the project directory for our solution. This shows all of the files that we will be using to print labels.

Project Directory

The first step is to define our label format. To do this I have created an entity class that represents a sheet of pre-cut labels.

LabelFormat.vb

Below you can see the source code for LabelFormat.vb. This is an entity class that represents a single sheet of pre-cut labels such as Avery L7163. It contains all parameters that I have found important to accurately printing on a sheet of labels, if you are considering using the classes and methods associated with this article for another purpose (e.g. printing business cards) you may want to consider making an entity class for that application.

''' <summary>
''' Represents the layout of a sheet of labels such as Avery L7163.
''' </summary>
''' <remarks>All dimensions in Millimeters</remarks>
Public Class LabelFormat
    ''' <summary>
    ''' Numerical Id of the label format
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Id As Integer
    ''' <summary>
    ''' Name of the label format (e.g. Avery L7163)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Name As String
    ''' <summary>
    ''' Description of label format (e.g. A4 Sheet of 99.1 x 38.1mm address labels)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Description As String
    ''' <summary>
    ''' Width of page in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property PageWidth As Double
    ''' <summary>
    ''' Height of page in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property PageHeight As Double
    ''' <summary>
    ''' Margin between top of page and top of first label in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property TopMargin As Double
    ''' <summary>
    ''' Margin between left of page and left of first label in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LeftMargin As Double
    ''' <summary>
    ''' Width of individual label in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LabelWidth As Double
    ''' <summary>
    ''' Height of individual label in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LabelHeight As Double
    ''' <summary>
    ''' Padding on the left of an individual label, creates space between label edge and start of content
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LabelPaddingLeft As Double
    ''' <summary>
    ''' Padding on the Right of an individual label, creates space between label edge and end of content
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LabelPaddingRight As Double
    ''' <summary>
    ''' Padding on the top of an individual label, creates space between label edge and start of content
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LabelPaddingTop As Double
    ''' <summary>
    ''' Padding on the Bottom of an individual label, creates space between label edge and end of content
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property LabelPaddingBottom As Double
    ''' <summary>
    ''' Distance between top of one label and top of label below it in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property VerticalPitch As Double
    ''' <summary>
    ''' Distance between left of one label and left of label to the right of it in millimeters
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property HorizontalPitch As Double
    ''' <summary>
    ''' Number of labels going across the page
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ColumnCount As Integer
    ''' <summary>
    ''' Number of labels going down the page
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property RowCount As Integer

    ''' <summary>
    ''' Instantiate a new label sheet format definition
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()

    End Sub

    ''' <summary>
    ''' Instantiate a new label sheet format definition
    ''' </summary>
    ''' <param name="Id">Numerical Id of the label format</param>
    ''' <param name="Name">Name of the label format (e.g. Avery L7163)</param>
    ''' <param name="Description">Description of label format (e.g. A4 Sheet of 99.1 x 38.1mm address labels)</param>
    ''' <param name="PageWidth">Width of page in millimeters</param>
    ''' <param name="PageHeight">Height of page in millimeters</param>
    ''' <param name="TopMargin">Margin between top of page and top of first label in millimeters</param>
    ''' <param name="LeftMargin">Margin between left of page and left of first label in millimeters</param>
    ''' <param name="LabelWidth">Width of individual label in millimeters</param>
    ''' <param name="LabelHeight">Height of individual label in millimeters</param>
    ''' <param name="VerticalPitch">Distance between top of one label and top of label below it in millimeters</param>
    ''' <param name="HorizontalPitch">Distance between left of one label and left of label to the right of it in millimeters</param>
    ''' <param name="ColumnCount">Number of labels going across the page</param>
    ''' <param name="RowCount">Number of labels going down the page</param>
    ''' <param name="LabelPaddingLeft">Padding on the left of an individual label, creates space between label edge and start of content</param>
    ''' <param name="LabelPaddingRight">Padding on the Right of an individual label, creates space between label edge and end of content</param>
    ''' <param name="LabelPaddingTop">Padding on the top of an individual label, creates space between label edge and start of content</param>
    ''' <param name="LabelPaddingBottom">Padding on the Bottom of an individual label, creates space between label edge and end of content</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal Id As Integer,
                    ByVal Name As String,
                    ByVal Description As String,
                    ByVal PageWidth As Double,
                    ByVal PageHeight As Double,
                    ByVal TopMargin As Double,
                    ByVal LeftMargin As Double,
                    ByVal LabelWidth As Double,
                    ByVal LabelHeight As Double,
                    ByVal VerticalPitch As Double,
                    ByVal HorizontalPitch As Double,
                    ByVal ColumnCount As Integer,
                    ByVal RowCount As Integer,
                    Optional ByVal LabelPaddingLeft As Double = 0.0,
                    Optional ByVal LabelPaddingRight As Double = 0.0,
                    Optional ByVal LabelPaddingTop As Double = 0.0,
                    Optional ByVal LabelPaddingBottom As Double = 0.0)
        Me.Id = Id
        Me.Name = Name
        Me.Description = Description
        Me.PageWidth = PageWidth
        Me.PageHeight = PageHeight
        Me.TopMargin = TopMargin
        Me.LeftMargin = LeftMargin
        Me.LabelWidth = LabelWidth
        Me.LabelHeight = LabelHeight
        Me.VerticalPitch = VerticalPitch
        Me.HorizontalPitch = HorizontalPitch
        Me.ColumnCount = ColumnCount
        Me.RowCount = RowCount
        Me.LabelPaddingLeft = LabelPaddingLeft
        Me.LabelPaddingRight = LabelPaddingRight
        Me.LabelPaddingTop = LabelPaddingTop
        Me.LabelPaddingBottom = LabelPaddingBottom
    End Sub

End Class

LabelFormatBLL.vb

Now that we have an entity class that represents a sheet of labels we need a way of populating this class with records from a database or collection. This is the purpose of the LabelFormatBLL.vb class.

Normally we would retrieve records from a database, but for the purpose of this article I have simply populated the records manually. You can see this below.

''' <summary>
''' Business logic layer for label formats.
''' </summary>
''' <remarks>As this is a demo and there is no database to use, the BLL will do the DAL work aswell.</remarks>
Public Class LabelFormatBLL
    Private Shared _mLabelFormats As List(Of LabelFormat)

    ''' <summary>
    ''' Return a list of all label formats in the database.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetLabelFormats() As List(Of LabelFormat)
        If IsNothing(_mLabelFormats) Then
            ' We are not using a database so manually create the labels here.
            _mLabelFormats = New List(Of LabelFormat)
            _mLabelFormats.Add(New LabelFormat(Id:=1,Name:="L7163", Description:="A4 Sheet of 99.1 x 38.1mm address labels", PageWidth:=210,PageHeight:=297,TopMargin:=15.1,LeftMargin:=4.7,LabelWidth:=99.1,LabelHeight:=38.1,VerticalPitch:=38.1,HorizontalPitch:=101.6,ColumnCount:=2,RowCount:=7,LabelPaddingTop:=5.0,LabelPaddingLeft:=8.0))
            _mLabelFormats.Add(New LabelFormat(Id:=2,Name:="L7169",Description:="A4 Sheet of 99.1 x 139mm BlockOut (tm) address labels",PageWidth:=210,PageHeight:=297,TopMargin:=9.5,LeftMargin:=4.6,LabelWidth:=99.1,LabelHeight:=139,VerticalPitch:=139,HorizontalPitch:=101.6,ColumnCount:=2,RowCount:=2,LabelPaddingTop:=5.0,LabelPaddingLeft:=8.0))
        End If
        Return _mLabelFormats
    End Function

    ''' <summary>
    ''' Return a single label format
    ''' </summary>
    ''' <param name="Id">integer reference for the label that is required.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetLabelFormat(ByVal Id As Integer) As LabelFormat
        GetLabelFormat = Nothing
        For Each lf As LabelFormat In GetLabelFormats()
            If lf.Id = Id Then
                ' Label format found. Return it.
                Return lf
            End If
        Next
    End Function
End Class

There are two methods of interest, one is GetLabelFormats() which returns all label formats, and the second method is GetLabelFormat() which returns a single format based on the ID reference for that label.

We would use GetLabelFormats() to populate a dropdown that allows the use to select which format they want to print to, the function GetLabelFormat() will be used to retrieve parameters once the individual format has been selected.

AddressesBLL.vb

AddressesBLL is used to produce some sample data for the project. It creates a list(of string) containing addresses so that we can print address labels. As this is only used for sample data I will not go into any detail explaining its use.

PdfLabelUtil.vb

The class PdfLabelUtil.vb is the main focus of this project, this class does all of the formatting and document creation. I have shown the code below, I will explain this code further on.

Imports System.IO
Imports PdfSharp
Imports PdfSharp.Drawing
Imports PdfSharp.Pdf

''' <summary>
''' Utility class for creating labels within a PDF document
''' </summary>
''' <remarks></remarks>
Public Class PdfLabelUtil
    Public Shared Function GeneratePdfLabels(ByVal Addresses As List(Of String),
                                      ByVal lf As LabelFormat,
                                      Optional ByVal QtyEachLabel As Integer = 1) As MemoryStream
        GeneratePdfLabels = New MemoryStream

        ' The label sheet is basically a table and each cell is a single label

        ' Format related
        Dim CellsPerPage As Integer = lf.RowCount * lf.ColumnCount
        Dim CellsThisPage As Integer = 0
        Dim ContentRectangle As XRect       ' A single cell content rectangle. This is the rectangle that can be used for contents and accounts for margins and padding.
        Dim ContentSize As XSize            ' Size of content area inside a cell.
        Dim ContentLeftPos As Double        ' left edge of current content area.
        Dim ContentTopPos As Double         ' Top edge of current content area

        ' Layout related
        Dim StrokeColor As XColor = XColors.DarkBlue
        Dim FillColor As XColor = XColors.DarkBlue
        Dim Pen As XPen = New XPen(StrokeColor, 0.1)
        Dim Brush As XBrush = New XSolidBrush(FillColor)
        Dim Gfx As XGraphics
        Dim Path As XGraphicsPath

        Dim LoopTemp As Integer = 0         ' Counts each itteration. Used with QtyEachLabel
        Dim CurrentColumn As Integer = 1
        Dim CurrentRow As Integer = 1
        Dim Doc As New PdfDocument
        Dim page As PdfPage = Nothing
        AddPage(Doc, page, lf)
        Gfx = XGraphics.FromPdfPage(page)

        ' Ensure that at least 1 of each label is printed.
        If QtyEachLabel < 1 Then QtyEachLabel = 1

        ' Define the content area size
        ContentSize = New XSize(XUnit.FromMillimeter(lf.LabelWidth - lf.LabelPaddingLeft - lf.LabelPaddingRight).Point,
                             XUnit.FromMillimeter(lf.LabelHeight - lf.LabelPaddingTop - lf.LabelPaddingBottom).Point)

        If Not IsNothing(Addresses) Then
            If Addresses.Count > 0 Then
                ' We actually have addresses to output.
                For Each Address As String In Addresses
                    ' Once for each address
                    For LoopTemp = 1 To QtyEachLabel
                        ' Once for each copy of this address.
                        If CellsThisPage = CellsPerPage Then
                            ' This pages worth of cells are filled up. Create a new page
                            AddPage(Doc, page, lf)
                            Gfx = XGraphics.FromPdfPage(page)
                            CellsThisPage = 0
                        End If

                        ' Calculate which row and column we are working on.
                        CurrentColumn = (CellsThisPage + 1) Mod lf.ColumnCount
                        CurrentRow = Fix((CellsThisPage + 1) / lf.ColumnCount)

                        If CurrentColumn = 0 Then
                            ' This occurs when you are working on the last column of the row.
                            ' This affects the count for column and row
                            CurrentColumn = lf.ColumnCount
                        Else
                            ' We are not viewing the last column so this number will be decremented by one.
                            CurrentRow = CurrentRow + 1
                        End If

                        ' Calculate the left position of the current cell.
                        ContentLeftPos = ((CurrentColumn - 1) * lf.HorizontalPitch) + lf.LeftMargin + lf.LabelPaddingLeft

                        ' Calculate the top position of the current cell.
                        ContentTopPos = ((CurrentRow - 1) * lf.VerticalPitch) + lf.TopMargin + lf.LabelPaddingTop

                        ' Define the content rectangle.
                        ContentRectangle = New XRect(New XPoint(XUnit.FromMillimeter(ContentLeftPos).Point, XUnit.FromMillimeter(ContentTopPos).Point),
                                                     ContentSize)

                        Path = New XGraphicsPath

                        ' Add the address string to the page.
                        Path.AddString(Address,
                                        New XFontFamily("Arial"),
                                        XFontStyle.Regular,
                                        11,
                                        ContentRectangle,
                                        XStringFormats.TopLeft)

                        Gfx.DrawPath(Pen, Brush, Path)

                        ' Increment the cell count
                        CellsThisPage = CellsThisPage + 1
                    Next LoopTemp
                Next
                ' Output the document
                Doc.Save(GeneratePdfLabels, False)
            End If
        End If
    End Function

    Private Shared Sub AddPage(ByRef Doc As PdfDocument,
                        ByRef Page As PdfPage,
                        ByVal lf As LabelFormat)
        Page = Doc.AddPage
        Page.Width = XUnit.FromMillimeter(lf.PageWidth)
        Page.Height = XUnit.FromMillimeter(lf.PageHeight)
    End Sub
End Class

We first have to import references to the PDFSharp library. We do not need to import the Drawing and Pdf sections but it makes the syntax in the rest of the class easier to read.

Imports PdfSharp
Imports PdfSharp.Drawing
Imports PdfSharp.Pdf

The method we call from outside the class is GeneratePDFLabels() and this returns a MemoryStream object that represents a PDF document.

At the start we declare all of our variables and go on to define our colours and drawing objects. All text is drawn on the canvas as paths, the Pen is used for drawing the outline of the text and the Stroke is used for filling in the text. This allows us to have different outline and fill colours.

We then define some variables that are used for counting itterations and then we define the document object and page object. You will see a call to AddPage() immediately after the page is defined. This small method creates a new PDF page within the PDF document using the width and height that are defined in the label format.

Page = Doc.AddPage
Page.Width = XUnit.FromMillimeter(lf.PageWidth)
Page.Height = XUnit.FromMillimeter(lf.PageHeight)

You should notice that the dimensions are given from the XUnit helper. This is part of the PDFSharp framework and allows you to set dimensions from any unit you desire (in this case millimeters) and ensures that PDFSharp scales them correctly.

We then generate our content box.

' Define the content area size
ContentSize = New XSize(XUnit.FromMillimeter(lf.LabelWidth - lf.LabelPaddingLeft - lf.LabelPaddingRight).Point,
             XUnit.FromMillimeter(lf.LabelHeight - lf.LabelPaddingTop - lf.LabelPaddingBottom).Point)

This is a rectangle that sets the usable space that each label can use. This is defined as the width and height of the individual label, minus the internal padding that is defined. The padding is useful to allow for variances in the cut of the label sheet and (more importantly) for the sheet of labels moving inside the printer while it is being fed.

the next few lines of code calculate which column and row we are currently working on, the results are then used to calculate the position of the new label.

' Calculate the left position of the current cell.
ContentLeftPos = ((CurrentColumn - 1) * lf.HorizontalPitch) + lf.LeftMargin + lf.LabelPaddingLeft

' Calculate the top position of the current cell.
ContentTopPos = ((CurrentRow - 1) * lf.VerticalPitch) + lf.TopMargin + lf.LabelPaddingTop

It is interesting to note that when we calculate the position of the new label on the page we offset according to the label pitch, not the label width. This is because there are sometimes gaps between the labels which the width would not account for.

Now that we know how large the content area is and we know where on the page it is we can create the rectangle that defines the confines of the text.

' Define the content rectangle.
ContentRectangle = New XRect(New XPoint(XUnit.FromMillimeter(ContentLeftPos).Point, XUnit.FromMillimeter(ContentTopPos).Point),
                            ContentSize)

The last step for this individual label is to add the text which is taken from the address list. We could have defined the font and style at the top of the page but instead it was done here.

Path = New XGraphicsPath

' Add the address string to the page.
Path.AddString(Address,
               New XFontFamily("Arial"),
               XFontStyle.Regular,
               11,
               ContentRectangle,
               XStringFormats.TopLeft)

Gfx.DrawPath(Pen, Brush, Path)

Finally, once the document has been completed and all labels have been added to it we need to output the document to the memory stream. The False option means that the memorystream is left open, this allows us to read it in the handler that will output the PDF to the client.

 Doc.Save(GeneratePdfLabels, False)

LabelHandler.ashx

The handler in the project is used to call PdfLabelUtil.vb and then output the resulting PDF to the client.

 Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        If IsNumeric(context.Request.QueryString("Id")) Then
            Using MemStream As MemoryStream = PdfLabelUtil.GeneratePdfLabels(AddressesBLL.GetAddresses,
                                                                             LabelFormatBLL.GetLabelFormat(CInt(context.Request.QueryString("Id"))),
                                                                             1)
                If Not IsNothing(MemStream) Then
                    With context.Response
                        .Clear()
                        .ContentType = "application/pdf"
                        .AddHeader("content-length", MemStream.Length.ToString())
                        .AppendHeader("Content-Disposition", "inline; filename=AddressLabels.pdf")
                        .BinaryWrite(MemStream.ToArray())
                        .Flush()
                        .Close()
                        .End()
                    End With
                End If
            End Using
        Else
            context.Response.ContentType = "text/plain"
            context.Response.Write("ID Missing")
        End If
    End Sub

We can see that the handler needs to be called with a querystring parameter representing the label format that is desired for printing out, e.g. LabelHandler.ashx?Id=1

The handler retrieves the list of addresses from AddressBLL, and the label format from LabelFormatBLL before calling PdfLabelUtil.GeneratePdfLabels. Once complete the stream is output as a PDF document with the filename "AddressLabels.pdf".

Default.aspx

Default.aspx is used simply to provide a user interface from which we can call the handler which generates the PDF document.

<div>
    <asp:DropDownList ID="ddlLabelFormats" runat="server"></asp:DropDownList>
    <br /><br />
    <asp:Button ID="btnPrintLabels" runat="server" Text="Print Address Labels" />
</div>

There is a dropdown which displays all of the available label formats and a button that triggers the Response.Redirect to LabelHandler.ashx.

The dropdown is populated, and the Response.Redirect is triggered using the two code blocks below.

''' <summary>
    ''' Populate the label formats into the drop down list
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub LoadLabelFormats()
        With ddlLabelFormats
            .DataSource = LabelFormatBLL.GetLabelFormats
            .DataValueField = "Id"
            .DataTextField = "Name"
            .DataBind()
        End With
    End Sub

    ''' <summary>
    ''' Print the label
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub btnPrintLabels_Click(sender As Object, e As EventArgs) Handles btnPrintLabels.Click
        Dim li As ListItem = CType(ddlLabelFormats.SelectedItem, ListItem)
        If Not IsNothing(li) Then
            ' Label format selection from drop down list.
            Response.Redirect("LabelHandler.ashx?Id=" & li.Value.ToString)
        End If
    End Sub

Results

When you call the handler or click the 'Print' button on the Default.aspx page you will be presented with a PDF document of text (addresses) strategically placed on the page to line up with a sheet of labels. Below you can see what this looks like when you print out onto the pre-cut label sheet.

The result of printing out onto a pre-cut label sheet

I hope this is useful to you and you manage to find an application within your projects. If you have any problems, questions or concerns please use the comments section below.

History

Revision 1.0 2014-11-27 This is the first copy of this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here