Introduction
This article came about as a response to a request to extend the UPC-A barcode example to include EAN-8 and EAN-13. In this article, we will look at the EAN-13 specification and examine some code that can produce EAN-13 barcodes.
EAN-13 Background
The EAN-13 barcode is composed of 13 digits, which are made up of the following sections: the first 2 or 3 digits are the country code, the next 5 to 7 digits are the manufacturer code, the next 3 to 5 digits are the product code, and the last digit is the checksum digit.
The figure below shows a typical EAN-13 barcode.
Country Code
The Country Code is a 2 or 3 digit number, which is used to identify the country that assigned the manufacturer code. All EAN-13 barcodes that begin with "0" are UPC-A barcodes. The following table contains the Country Codes:
00-13: USA & Canada |
20-29: In-Store Functions |
30-37: France |
40-44: Germany |
45: Japan (also 49) |
46: Russian Federation |
471: Taiwan |
474: Estonia |
475: Latvia |
477: Lithuania |
479: Sri Lanka |
480: Philippines |
482: Ukraine |
484: Moldova |
485: Armenia |
486: Georgia |
487: Kazakhstan |
489: Hong Kong |
49: Japan (JAN-13) |
50: United Kingdom |
520: Greece |
528: Lebanon |
529: Cyprus |
531: Macedonia |
535: Malta |
539: Ireland |
54: Belgium & Luxembourg |
560: Portugal |
569: Iceland |
57: Denmark |
590: Poland |
594: Romania |
599: Hungary |
600 & 601: South Africa |
609: Mauritius |
611: Morocco |
613: Algeria |
619: Tunisia |
622: Egypt |
625: Jordan |
626: Iran |
64: Finland |
690-692: China |
70: Norway |
729: Israel |
73: Sweden |
740: Guatemala |
741: El Salvador |
742: Honduras |
743: Nicaragua |
744: Costa Rica |
746: Dominican Republic |
750: Mexico |
759: Venezuela |
76: Switzerland |
770: Colombia |
773: Uruguay |
775: Peru |
777: Bolivia |
779: Argentina |
780: Chile |
784: Paraguay |
785: Peru |
786: Ecuador |
789: Brazil |
80 - 83: Italy |
84: Spain |
850: Cuba |
858: Slovakia |
859: Czech Republic |
860: Yugloslavia |
869: Turkey |
87: Netherlands |
880: South Korea |
885: Thailand |
888: Singapore |
890: India |
893: Vietnam |
899: Indonesia |
90 & 91: Austria |
93: Australia |
94: New Zealand |
955: Malaysia |
977: International Standard Serial Number for Periodicals (ISSN) |
978: International Standard Book Numbering (ISBN) |
979: International Standard Music Number (ISMN) |
980: Refund receipts |
981 & 982: Common Currency Coupons |
99: Coupons |
Manufacturer Code
The EAN Manufacturer Code is a variable length number. Typically, 5 digit codes are assigned to companies, however, some companies do not produce enough products to warrant a 5 digit product code, and in such cases the EAN will issue Manufacturer Codes longer than 5 digits.
Product Code
The manufacturer is free to assign its own product codes, but they must make sure that each product code is unique within their product codes. The product codes can be as few as 3 digits long, or as long as 5 digits depending on the length of the country and manufacturer codes.
Checksum Digit
The checksum digit is calculated using the country code, manufacturer's code, and the product code. The odd numbers starting with the right most digits are multiplied by 3 and added to the sum, while the even numbers are simply added to the sum. The reason for the EAN-13 check sum being calculated in reverse order (starting with the right most digit and considering it as being odd instead of even) is for compatibility with UPC-A barcodes. The modulus of 10 is then taken of the summed total. This is subtracted from 10, and the modulus of 10 is taken again.
For example: EAN-13 001234567890 |
Country Code : 00 |
Manufacturer's Code : 12345 |
Product Code : 67890 |
The last or right most digit is '0' and is considered odd, so multiply it by 3, the second right most digit '9' is even so just add it, etc... |
(0 * 3) + 9 + (8 * 3) + 7 + (6 * 3) + 5 + (4 * 3) + 3 + (2 * 3) + 1 + (0 * 3) + 0 = 85 |
85 % 10 = 5 |
( ( 10 - 5 ) % 10 ) = 5 |
Symbol Size
The specifications for the EAN-13 barcode specify the nominal size as 37.29mm wide and 25.93mm high. Based upon this nominal size, the EAN-13 barcode can be scaled by a magnification factor of 0.8 to 2.0. Scaling the barcode will produce a barcode between the minimal allowable size of 29.83mm wide by 20.74mm high and the maximum allowable size of 74.58mm wide and 51.86mm high.
Digit Patterns
Each digit in a EAN-13 bar code is composed of a series of two spaces and two bars. Each digit is drawn within a space that is 7 modules wide. In addition to the 13 digits, which make up a EAN-13 barcode, the barcode symbol also has two quite zones, a lead block, a separator, and a trailing block. Each quite zone is 9 modules wide, the lead and trailing blocks are a series of lines and spaces in the format of bar, space, bar. The separator is signified by the sequence space / bar / space / bar / space.
|
|
Special Symbol |
Pattern |
Quite Zone |
000000000 |
Lead / Trailer |
101 |
Separator |
01010 |
where '0' represents space and '1' denotes a bar. |
In addition to the special symbol patterns listed above, the EAN-13 barcode symbol uses three distinct digit patterns as well, the Left Digit Odd Parity pattern, the Left Digit Even Parity pattern and the Right Digit pattern. The Left Digit patterns start with spaces, and the Right Digit pattern starts with bars (see table below).
Number |
Left Digits |
Right Digits |
Odd Parity |
Even Parity |
0 |
0001101 |
0100111 |
1110010 |
1 |
0011001 |
0110011 |
1100110 |
2 |
0010011 |
0011011 |
1101100 |
3 |
0111101 |
0100001 |
1000010 |
4 |
0100011 |
0011101 |
1011100 |
5 |
0110001 |
0111001 |
1001110 |
6 |
0101111 |
0000101 |
1010000 |
7 |
0111011 |
0010001 |
1000100 |
8 |
0110111 |
0001001 |
1001000 |
9 |
0001011 |
0010111 |
1110100 |
where a '0' denotes a space and '1' represents a bar.
The first digit of the Country Code is used to determine the parity of each digit of the Manufacturer's Code, see the Determining Number Parity section. The Right Digit pattern is typically used to draw the product code and the checksum digit. However, it will be used to render part of the manufacturer's code if the country code is greater than 2, or if the manufacturer's code is greater than 5.
Determining Number Parity
The first digit of the Country Code in an EAN-13 barcode is not encoded, it is used to determine the parity of the digits in the manufacturer code. The second digit of the country code is always odd and the manufacturer's code will have three left-hand numbers that use even parity and two that use odd parity, except a UPC-A compatible barcode which uses all odd parity. The table below outlines the parity for the numbers in the Manufacturer's Code.
First Country Code Digit |
Parity |
Second Country Code Digit |
Manufacturer Code Digits |
1 |
2 |
3 |
4 |
5 |
0 (UPC-A) |
Odd |
Odd |
Odd |
Odd |
Odd |
Odd |
1 |
Odd |
Odd |
Even |
Odd |
Even |
Even |
2 |
Odd |
Odd |
Even |
Even |
Odd |
Even |
3 |
Odd |
Odd |
Even |
Even |
Even |
Odd |
4 |
Odd |
Even |
Odd |
Odd |
Even |
Even |
5 |
Odd |
Even |
Even |
Odd |
Odd |
Even |
6 |
Odd |
Even |
Even |
Even |
Odd |
Odd |
7 |
Odd |
Even |
Odd |
Even |
Odd |
Even |
8 |
Odd |
Even |
Odd |
Even |
Even |
Odd |
9 |
Odd |
Even |
Even |
Odd |
Even |
Odd |
For example, if the country code is 75 then based upon the 1st country code digit and the above table, the 2nd digit of the country code, 5, would be Odd. The 1st digit of the Manufacturer Code would use the Even pattern, the 2nd digit would use the Odd pattern, the 3rd digit would use the Even pattern, the 4th would be Odd, and finally the 5th digit would use the Even pattern.
Using the code
First, we will examine how to use the Ean13
class, and then we'll examine how the Ean13
class works.
Using the Ean13 Class
The code excerpt below uses the Ean13
class to draw a EAN-13 barcode in a picture box control:
private void DrawEan13( )
{
System.Drawing.Graphics g = this.picBarcode.CreateGraphics( );
g.FillRectangle(
new System.Drawing.SolidBrush(System.Drawing.SystemColors.Control),
new Rectangle(0, 0, picBarcode.Width, picBarcode.Height));
upc = new Ean13( );
upc.CountryCode = "12";
upc.ManufacturerCode = "34567";
upc.ProductCode = "89012";
upc.Scale =
(float)Convert.ToDecimal(cboScale.Items [cboScale.SelectedIndex]);
upc.DrawEan13Barcode( g, new System.Drawing.Point( 0, 0 ) );
g.Dispose( );
}
The first step for the DrawEan13
function is to create an instance of the Ean13
class, and then set the country code, manufacturer code, the product code, and the scale factor properties (the check sum will be calculated by the Ean13
class). Once these properties are set, a call to the DrawEan13Barcode
function is made, passing a Graphics
object and a Point
, which indicates the starting position to draw at, this will cause the barcode to be drawn in the picture box starting at point (0, 0).
The Ean13 Class
The most significant variables are listed below:
private float _fWidth = 37.29f;
private float _fHeight = 25.93f;
private float _fFontSize = 8.0f;
private float _fScale = 1.0f;
private string [] _aOddLeft = { "0001101", "0011001", "0010011", "0111101",
"0100011", "0110001", "0101111", "0111011",
"0110111", "0001011" };
private string [] _aEvenLeft = { "0100111", "0110011", "0011011", "0100001",
"0011101", "0111001", "0000101", "0010001",
"0001001", "0010111" };
private string [] _aRight = { "1110010", "1100110", "1101100", "1000010",
"1011100", "1001110", "1010000", "1000100",
"1001000", "1110100" };
private string _sQuiteZone = "000000000";
private string _sLeadTail = "101";
private string _sSeparator = "01010";
The _fWidth
, _fHeight
, and the _fScale
variables are initialized with the nominal size. When the barcode is rendered, its actual size will be determined by the nominal size, and the scale factor, as discussed in the Symbol Size section of this article. The variables _aOddLeft
, _aEvenLeft
, _aRight
, _sQuiteZone
, _sLeadTail
, and _sSeparator
are all string representations of the bar/space graphics, which represent the various parts of an EAN-13 barcode. Essentially, a '1' represents a bar and a '0' represents a space, so _sSeparator
would cause a space-bar-space-bar-space to be rendered. An alternate method to using a string could be to use a binary representation, where a 0 bit would be a space and a 1 bit is a bar.
There are four primary functions which provide the majority of the functionality for the Ean13
class. The workhorse of these functions is DrawEan13Barcode
, which uses several functions as helper functions. The helper functions are: CalculateChecksumDigit
, ConvertToDigitPatterns
, ConvertLeftPattern
which will be discussed first. There is also a fifth function, CreateBitmap
, which provides an easy means for creating a bitmap image.
The first helper function DrawEan13Barcode
calls the CalculateChecksumDigit
function, which uses the country code, manufacturer code, and product code to calculate the barcode's check sum.
public void CalculateChecksumDigit( )
{
string sTemp =
this.CountryCode + this.ManufacturerCode + this.ProductCode;
int iSum = 0;
int iDigit = 0;
for( int i = sTemp.Length; i >= 1; i-- )
{
iDigit = Convert.ToInt32( sTemp.Substring( i - 1, 1 ) );
if( i % 2 == 0 )
{
iSum += iDigit * 3;
}
else
{
iSum += iDigit * 1;
}
}
int iCheckSum = ( 10 - ( iSum % 10 ) ) % 10;
this.ChecksumDigit = iCheckSum.ToString( );
}
The CalculateChecksumDigit
function calculates the check sum using the method discussed in the Checksum Digit section listed above.
The second helper function used is the ConvertToDigitPatterns
function. This function takes the individual numbers of the manufacturer code, and the product number, and converts them to the string representation of the barcode graphics.
private string ConvertToDigitPatterns(string inputNumber, string [] patterns)
{
System.Text.StringBuilder sbTemp = new StringBuilder( );
int iIndex = 0;
for( int i = 0; i < inputNumber.Length; i++ )
{
iIndex = Convert.ToInt32( inputNumber.Substring( i, 1 ) );
sbTemp.Append( patterns[iIndex] );
}
return sbTemp.ToString( );
}
The ConvertToDigitPatterns
function requires two parameters:
The inputNumber
will be either the manufacturer number, or the product number, and the patterns
will be the _aOddLeft
, _aEvenLeft
, or the _aRight
array depending on whether the inputNumber
is the manufacturer number or the product number.
The ConvertLeftPatterns
is used to create the left hand patterns discussed in the Determining Number Parity section. The ConvertLeftPatterns
determines the country code, and calls the appropriate country code converter.
private string ConvertLeftPattern( string sLeft )
{
switch( sLeft.Substring( 0, 1 ) )
{
case "0":
return CountryCode0( sLeft.Substring( 1 ) );
case "1":
return CountryCode1( sLeft.Substring( 1 ) );
case "2":
return CountryCode2( sLeft.Substring( 1 ) );
case "3":
return CountryCode3( sLeft.Substring( 1 ) );
case "4":
return CountryCode4( sLeft.Substring( 1 ) );
case "5":
return CountryCode5( sLeft.Substring( 1 ) );
case "6":
return CountryCode6( sLeft.Substring( 1 ) );
case "7":
return CountryCode7( sLeft.Substring( 1 ) );
case "8":
return CountryCode8( sLeft.Substring( 1 ) );
case "9":
return CountryCode9( sLeft.Substring( 1 ) );
default:
return "";
}
}
Each country code has its own separate converter function. CountryCode0
is a UPC-A barcode so it will use only the left hand odd parity patterns. CountryCode1
through CountryCode9
each uses the appropriate parity patterns discussed in the Determining Number Parity section. See the CountryCode1
code below for an example of a country code converter function:
private string CountryCode1( string sLeft )
{
System.Text.StringBuilder sReturn = new StringBuilder( );
sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 0, 1 ),
this._aOddLeft ) );
sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 1, 1 ),
this._aOddLeft ) );
sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 2, 1 ),
this._aEvenLeft ) );
sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 3, 1 ),
this._aOddLeft ) );
sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 4, 1 ),
this._aEvenLeft ) );
sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 5, 1 ),
this._aEvenLeft ) );
return sReturn.ToString( );
}
The CountryCode1
function uses the parity patterns discussed in the Determining Number Parity section to send each number to the ConvertToDigitPatterns
function with the correct left hand pattern. The sLeft
parameter should contain the 2nd digit of the Country Code and the Manufacturer's Code.
Finally the workhorse; the DrawEan13Barcode
handles the rendering of the barcode graphics and requires two parameters:
This function begins by determining the width and height for the barcode by scaling the nominal width and height by the scale factor. The lineWidth
is based upon the total number of modules required to render an EAN-13 barcode. The total number of modules, 113, is determined by the following: for example:
EAN-13 code - 1234567890128
Barcode Section |
Numeric Value |
Graphic Representation |
Number of Modules |
Quite Zone |
N/A |
000000000 |
9 modules |
Lead |
N/A |
101 |
3 modules |
1st Digit of Country Code |
1 digit - "1" |
Used to determine the parity. |
2nd Digit of Country Code |
1 digit - "2" |
0010011 |
7 modules |
Manufacturer Number |
5 digits = "34567" |
01111010011101011000100001010010001 |
5 digits * 7 modules = 35 modules |
Separator |
N/A |
01010 |
5 modules |
Product Number |
5 digits = "89012" |
10010001110100111001011001101101100 |
5 digits * 7 modules = 35 modules |
Check Sum |
1 digit = "8" |
1001000 |
7 modules |
Trailer |
N/A |
101 |
3 modules |
Quite Zone |
N/A |
000000000 |
9 modules |
To determine the total module width, simply add the individual parts: 9 + 3 + 7 + 35 + 5 + 35 + 7 + 3 + 9 = 113. The 2nd digit of the Country code, 2, will have an odd parity, the 1st digit of the manufacturer's code, 3, will use the Odd pattern, the 2nd digit, 4, will use the Even pattern, the 3rd digit, 5, would use the Odd pattern, the 4th and 5th digits, 6 and 7, would use the Even pattern.
public void DrawEan13Barcode(System.Drawing.Graphics g, System.Drawing.Point pt)
{
float width = this.Width * this.Scale;
float height = this.Height * this.Scale;
float lineWidth = width / 113f;
System.Drawing.Drawing2D.GraphicsState gs = g.Save( );
g.PageUnit = System.Drawing.GraphicsUnit.Millimeter;
g.PageScale = 1;
System.Drawing.SolidBrush brush =
new System.Drawing.SolidBrush(System.Drawing.Color.Black);
float xPosition = 0;
System.Text.StringBuilder strbEAN13 = new System.Text.StringBuilder( );
System.Text.StringBuilder sbTemp = new System.Text.StringBuilder( );
float xStart = pt.X;
float yStart = pt.Y;
float xEnd = 0;
System.Drawing.Font font =
new System.Drawing.Font("Arial", this._fFontSize * this.Scale);
this.CalculateChecksumDigit( );
sbTemp.AppendFormat( "{0}{1}{2}{3}",
this.CountryCode,
this.ManufacturerCode,
this.ProductCode,
this.ChecksumDigit );
string sTemp = sbTemp.ToString( );
string sLeftPattern = "";
sLeftPattern = ConvertLeftPattern(sTemp.Substring( 0, 7 ));
strbEAN13.AppendFormat( "{0}{1}{2}{3}{4}{1}{0}",
this._sQuiteZone, this._sLeadTail,
sLeftPattern, this._sSeparator,
ConvertToDigitPatterns(sTemp.Substring( 7 ), this._aRight));
string sTempUPC = strbEAN13.ToString( );
float fTextHeight = g.MeasureString( sTempUPC, font ).Height;
for( int i = 0; i < strbEAN13.Length; i++ )
{
if( sTempUPC.Substring( i, 1 ) == "1" )
{
if( xStart == pt.X )
xStart = xPosition;
if( ( i > 12 && i < 55 ) || ( i > 57 && i < 101 ) )
g.FillRectangle( brush, xPosition, yStart,
lineWidth, height - fTextHeight );
else
g.FillRectangle( brush, xPosition, yStart, lineWidth, height );
}
xPosition += lineWidth;
xEnd = xPosition;
}
xPosition =
xStart - g.MeasureString(this.CountryCode.Substring( 0, 1 ), font).Width;
float yPosition = yStart + ( height - fTextHeight );
g.DrawString( sTemp.Substring( 0, 1 ), font, brush,
new System.Drawing.PointF( xPosition, yPosition ) );
xPosition +=
(g.MeasureString(sTemp.Substring( 0, 1 ), font).Width + 43 * lineWidth) -
(g.MeasureString( sTemp.Substring( 1, 6 ), font ).Width);
g.DrawString( sTemp.Substring( 1, 6 ), font, brush,
new System.Drawing.PointF( xPosition, yPosition ) );
xPosition +=
g.MeasureString(sTemp.Substring( 1, 6 ), font).Width + (11 * lineWidth);
g.DrawString( sTemp.Substring( 7 ), font, brush,
new System.Drawing.PointF( xPosition, yPosition ) );
g.Restore( gs );
}
The function uses the CalculateChecksumDigit
function to calculate the correct check sum digit, next the call to ConvertLeftPatterns
is made, and then the ConvertToDigitPatterns
function is used to convert the product code and check sum number of the EAN-13 barcode number to a string representation. Once the number has been converted over to a string representation, the code uses the string representation to render the barcode, 1 will cause a rectangle to be drawn, and 0 will cause the code to skip drawing a rectangle. If the code draws a rectangle, it also takes into consideration whether it needs to shorten the rectangle to allow space for the manufacturer's number and the product number. Once the barcode is completely rendered, the code then determines the position, and draws the country code, the manufacturer's number, the product number, and the check sum digit.
The CreateBitmap
function simply creates a Bitmap
object, and uses the DrawEan13Barcode
function to render the barcode to the Bitmap
object, and then it returns the Bitmap
.
public System.Drawing.Bitmap CreateBitmap( )
{
float tempWidth = ( this.Width * this.Scale ) * 100 ;
float tempHeight = ( this.Height * this.Scale ) * 100;
System.Drawing.Bitmap bmp =
new System.Drawing.Bitmap( (int)tempWidth, (int)tempHeight );
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp);
this.DrawEan13Barcode( g, new System.Drawing.Point( 0, 0 ) );
g.Dispose( );
return bmp;
}
Special Thanks
I would like to thank m@u for pointing out the flaw in my original article and source code. I would also like to thank MArmbruckner for testing version 2.0 and making sure the barcodes would scan properly.
History
- Version 1.0 - Initial application.
- Version 2.0 - Revised application to use the correct barcode patterns based on the 1st digit of the country code.