Introduction
This article investigates the POSTNET barcode system, and demonstrates some basic functionality to create POSTNET barcodes using C# 2005 and GDI+.
Background
Many employers these days are fairly frugal, so having them purchase something as simple as postal barcode fonts sometimes can prove to be extremely difficult. Being in this position a time or two, I decided to investigate the postal barcode system and create a simple library that could draw the POSTNET barcodes when given the zip code.
POSTNET Barcode Overview
POSTNET barcodes can be composed of several pieces of information: frame bars, zip code, +4, delivery point code, and correction character (see image below).
Frame bars signify the beginning and the end of the barcode. The frame bars are mandatory, and are composed of a single Full Bar. The zip code is also mandatory, and is represented by 25 bars. The +4 is composed of 20 bars and is optional. The delivery point code is represented by 10 bars, is optional, and is normally the last two digits of the street address, post office box, rural route number, or highway contract route number. The correction character is mandatory, and is basically a check sum (I use the terms correction character and check sum interchangeably in this article).
| Each digit is represented by a distinct pattern composed of five bars (see image on left for patterns). Basically, there are two distinct types of bars: the Full Bar and the Half Bar. A Full Bar must be between a minimum of 0.115" and a maximum of 0.135" tall. A Half Bar must be between a minimum of 0.04" and a maximum of 0.06" tall. Both the Full Bar and the Half Bar must be between 0.015" and 0.025" wide.
In addition to the height and width specifications, there is spacing requirement between bars; it must be between 0.012" and 0.040".
Specifications for POSTNET Barcode
Specification | Nominal | Allowable Variance | Full Bar Height | 0.125" | +/-0.01" | Half Bar Height | 0.05" | +/-0.01" | Bar Width | 0.02" | +/-0.005" | Bar Spacing | 0.026" | +/-0.014 | Center Line Spacing | 0.0458" | +/-0.0042" |
(For a complete reference, please refer to the Postal Service POSTNET barcode specifications available here, or do a search on POSTNET barcode specifications.)
|
Correction Character
The correction character is a simple check sum which is calculated by adding each of the numbers in the zip code together, takes the modulus of the summed numbers by 10, and then subtracts this result from 10.
Example: Zip code + 4 is 12345-6789 with a delivery point of 01.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 = 46
46 % 10 = 6
10 - 6 = 4
4 % 10 = 4
So the correction character is 4; the last modulus is for the special case when the summed zip code is a multiple of 10.
Code
The Postnet
class is used to render the barcode using a handle to a Graphics
object or create a Bitmap
class. The only required data is the zip code; the +4 digits and the delivery point are optional. The correction character will be calculated by the Postnet
class.
Using the Code
The Postnet
class will handle all aspects of rendering the barcode. Simply create an instance of the Postnet
class, passing the zip code to the constructor as a parameter. Then, call the DrawPOSTNET
function passing the Graphics
object and the starting point as parameters. Once it returns from processing, handle any other drawing tasks, and then clean up by calling the Dispose
function of the Graphics
object.
Postnet ps = new Postnet( "12345", "6789", "01" );
System.Drawing.Graphics g = this.pictureBox1.CreateGraphics( );
ps.DrawPOSTNET( g, new System.Drawing.Point( 0, 0 ) );
g.Dispose( );
The second functionality that the Postnet
class provides is to create the barcode in the form a bitmap. To create a bitmap, create an instance of the Postnet
class passing the zip code to the constructor. Then, call the CreateBitmap
function passing the width and height for the bitmap that will be created.
Postnet ps = new Postnet( "12345", "6789", "01" );
pictureBox1.Image = ps.CreateBitmap( this.pictureBox1.Width,
this.pictureBox1.Height );
How the code works
The Postnet
class is relatively simple. It provides several constructors for various means of creation; several properties/attributes to set and get class data; the two public functions discussed above, and a few private functions which handle some of the grunt work.
When an instance of the class is created, it initializes the bar specifications by calling the InitBarSpecification
function. This function sets several pieces of information, which controls the various aspects of the bars, and initializes the DRAWING_INFO
structure (see code listed below).
private void InitBarSpecifications( )
{
di = new DRAWING_INFO( );
_fBarWidth = .022f;
_fTallBarHeight = 0.125f;
_fShortBarHeight = 0.05f;
_fBarSpacing = 0.02f;
}
The DRAWING_INFO
contains the instance of the brush used to draw the bars, the current x location, and the current y location.
private struct DRAWING_INFO
{
public System.Drawing.SolidBrush brush;
public float xStart;
public float yStart;
}
The DrawPOSTNET
function uses the CalculateCorrectionCharacter
function to calculate the check sum, renders the frame bars, and calls the DrawBars
function to render the zip code, +4, delivery point and correction character. In addition to these features, the DrawPOSTNET
function also saves the GraphicsState
of the Graphics
object, and restores it when it has finished drawing the barcode. One interesting item is the use of the g.PageUnit
and the g.PageScale
; if these are not set or get correctly, the barcode can be too large or too small. The PageUnit
and PageScale
are used to automatically adjust the rendering from pixels to inches. The PageUnit
is a System.Drawing.GraphicsUnit
enumeration type, and tells the GDI+ objects what type of units to convert the drawing to. The PageScale
is used to scale the rendering; so if the PageScale
is set to .5, then one inch on the drawing will be scaled to one half an inch on the actual device (monitor, printer, etc...).
public void DrawPOSTNET( System.Drawing.Graphics g,
System.Drawing.Point startPt )
{
CalculateCorrectionCharacter( );
System.Drawing.Drawing2D.GraphicsState gs = g.Save( );
g.PageUnit = System.Drawing.GraphicsUnit.Inch;
g.PageScale = 1f;
di.brush = new System.Drawing.SolidBrush( System.Drawing.Color.Black );
di.xStart = startPt.X;
di.yStart = startPt.Y;
g.FillRectangle( di.brush, di.xStart, di.yStart,
_fBarWidth, _fTallBarHeight );
di.xStart += ( _fBarWidth + _fBarSpacing );
string sTempValue = ZipCode.ToString( );
DrawBars( g, sTempValue, ref di );
if( Plus4.Length == 4 )
{
sTempValue = Plus4.ToString( );
DrawBars( g, sTempValue, ref di );
if( this.DeliveryPoint.Length > 0 && this.DeliveryPoint.Length < 3 )
{
sTempValue = DeliveryPoint.ToString( );
DrawBars( g, sTempValue, ref di );
}
}
sTempValue = this.CorrectionCharacter.ToString( );
DrawBars( g, sTempValue, ref di );
g.FillRectangle( di.brush, di.xStart, di.yStart,
_fBarWidth, _fTallBarHeight );
g.Restore( gs );
}
The CalculateCorrectionCharacter
function simply adds the digits in the zip code, +4, and delivery code; it then takes the modulus of the summed number by 10; subtracts that number from 10 and then takes the modulus of 10 again.
private void CalculateCorrectionCharacter( )
{
string sZipCode;
sZipCode = ZipCode.ToString( );
int iCorrectionCharacter = 0;
for( int i = 0; i < sZipCode.Length; i++ )
{
iCorrectionCharacter +=
Convert.ToInt32( sZipCode.Substring( i, 1 ) );
}
if( this.Plus4.Length == 4 )
{
string sPlus4 = Plus4.ToString( );
for( int i = 0; i < sPlus4.Length; i++ )
{
iCorrectionCharacter +=
Convert.ToInt32( sPlus4.Substring( i, 1 ) );
}
if( this.DeliveryPoint.Length > 0
&& this.DeliveryPoint.Length < 3 )
{
string sDeliveryPoint = this.DeliveryPoint.ToString( );
for( int i = 0; i < sDeliveryPoint.Length; i++ )
{
iCorrectionCharacter +=
Convert.ToInt32( sDeliveryPoint.Substring( i, 1 ) );
}
}
}
int iLeftOver = iCorrectionCharacter % 10;
this._iCorrectionCharacter = ( 10 - iLeftOver ) % 10;
}
The DrawBars
function uses the sNumber
parameter and the _aBarcodeValues
array to determine how the bar should be drawn. The _aBarcodeValues
array contains the number formats; 0 is a short bar and 1 is a tall bar.
string [] _aBarcodeValues = { "11000",
"00011",
"00101",
"00110",
"01001",
"01010",
"01100",
"10001",
"10010",
"10100" };
...
private void DrawBars( System.Drawing.Graphics g,
string sNumber, ref DRAWING_INFO di )
{
string sTempValue;
for( int i = 0; i < sNumber.Length; i++ )
{
sTempValue =
_aBarcodeValues[Convert.ToInt32( sNumber.Substring( i, 1 ) )];
for( int j = 0; j < sTempValue.Length; j++ )
{
if( sTempValue.Substring( j, 1 ) == "0" )
g.FillRectangle( di.brush, di.xStart, di.yStart +
( _fTallBarHeight - _fShortBarHeight ),
_fBarWidth, _fShortBarHeight );
else
g.FillRectangle( di.brush, di.xStart, di.yStart,
_fBarWidth, _fTallBarHeight );
di.xStart += ( _fBarWidth + _fBarSpacing );
}
}
}
The final function is the CreateBitmap
function. This function takes the desired width and height to create a new bitmap. It creates a Graphics
object from the Bitmap
, draws the barcode, and returns the Bitmap
object.
public System.Drawing.Bitmap CreateBitmap( int width, int height )
{
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap( width, height );
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage( bmp );
this.DrawPOSTNET( g, new System.Drawing.Point( 0, 0 ) );
g.Dispose( );
return bmp;
}
All done!
Well, that's it for the Postnet
class, and I should probably get back to work. Although this is probably not a hot topic, I do hope this will help someone out, at least by sharing a little knowledge.
History
- Version 1.0.0 - 12/1/2004 - Initial version.
- Version 1.1.0 - 12/7/2004 - Corrected the correction character calculation - thanks je_gonzalez.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.