Introduction
The purpose of this article is to create a simple class that will generate the image of a Code 39 barcode from a string as input.
Background
I will not explain what a Code 39 barcode is. If you are reading this note, chances are that you already know what that is. But just to refresh everyone's memory, according to Wikipedia, “Code 39 (also known as Alpha39, Code 3 of 9, Code 3/9, Type 39, USS Code 39, or USD-3) is a variable length, discrete barcode symbology.” Still following the information available on Wikipedia, the Code 39 specification defines 43 characters, consisting of :
- uppercase letters (A through Z)
- numeric digits (0 through 9)
- and a number of special characters (-, ., $, /, +, %, and space).
- an additional character (denoted '*') is used for both start and stop delimiters.
Each character is composed of nine elements: five bars and four spaces. Three of the nine elements in each character are wide (binary value 1), and six elements are narrow (binary value 0). For example, the letter “D” is coded as:
D=bwbwBWbwB
Where b and w stand respectively for narrow bar and narrow space; B and W for wide bar and wide space. The width ratio between narrow and wide may be chosen between 1:2 and 1:3.
Full ASCII Code 39
While a “pure” Code 39 barcode is restricted to the 43 characters that are listed above, there is extension of Code 39 that allows all ASCII characters to be represented. This extension is called Full ASCII Code 39. In it, the symbols 0-9, A-Z, ".", "-" and space are the same as their representations in Code 39. Lower case letters, additional punctuation characters and control characters are represented by sequences of two characters of Code 39 (see the following table).
Nr |
Character |
Encoding |
|
Nr |
Character |
Encoding |
<!-- Row # 2 -->
0 |
NUL |
%U |
|
64 |
@ |
%V |
<!-- Row # 3 -->
1 |
SOH |
$A |
|
65 |
A |
A |
<!-- Row # 4 -->
2 |
STX |
$B |
|
66 |
B |
B |
<!-- Row # 5 -->
3 |
ETX |
$C |
|
67 |
C |
C |
<!-- Row # 6 -->
4 |
EOT |
$D |
|
68 |
D |
D |
<!-- Row # 7 -->
5 |
ENQ |
$E |
|
69 |
E |
E |
<!-- Row # 8 -->
6 |
ACK |
$F |
|
70 |
F |
F |
<!-- Row # 9 -->
7 |
BEL |
$G |
|
71 |
G |
G |
<!-- Row # 10 -->
8 |
BS |
$H |
|
72 |
H |
H |
<!-- Row # 11 -->
9 |
HT |
$I |
|
73 |
I |
I |
<!-- Row # 12 -->
10 |
LF |
$J |
|
74 |
J |
J |
<!-- Row # 13 -->
11 |
VT |
$K |
|
75 |
K |
K |
<!-- Row # 14 -->
12 |
FF |
$L |
|
76 |
L |
L |
<!-- Row # 15 -->
13 |
CR |
$M |
|
77 |
M |
M |
<!-- Row # 16 -->
14 |
SO |
$N |
|
78 |
N |
N |
<!-- Row # 17 -->
15 |
SI |
$O |
|
79 |
O |
O |
<!-- Row # 18 -->
16 |
DLE |
$P |
|
80 |
P |
P |
<!-- Row # 19 -->
17 |
DC1 |
$Q |
|
81 |
Q |
Q |
<!-- Row # 20 -->
18 |
DC2 |
$R |
|
82 |
R |
R |
<!-- Row # 21 -->
19 |
DC3 |
$S |
|
83 |
S |
S |
<!-- Row # 22 -->
20 |
DC4 |
$T |
|
84 |
T |
T |
<!-- Row # 23 -->
21 |
NAK |
$U |
|
85 |
U |
U |
<!-- Row # 24 -->
22 |
SYN |
$V |
|
86 |
V |
V |
<!-- Row # 25 -->
23 |
ETB |
$W |
|
87 |
W |
W |
<!-- Row # 26 -->
24 |
CAN |
$X |
|
88 |
X |
X |
<!-- Row # 27 -->
25 |
EM |
$Y |
|
89 |
Y |
Y |
<!-- Row # 28 -->
26 |
SUB |
$Z |
|
90 |
Z |
Z |
<!-- Row # 29 -->
27 |
ESC |
%A |
|
91 |
[ |
%K |
<!-- Row # 30 -->
28 |
FS |
%B |
|
92 |
\ |
%L |
<!-- Row # 31 -->
29 |
GS |
%C |
|
93 |
] |
%M |
<!-- Row # 32 -->
30 |
RS |
%D |
|
94 |
^ |
%N |
<!-- Row # 33 -->
31 |
US |
%E |
|
95 |
_ |
%O |
<!-- Row # 34 -->
32 |
[space] |
[space] |
|
96 |
` |
%W |
<!-- Row # 35 -->
33 |
! |
/A |
|
97 |
a |
+A |
<!-- Row # 36 -->
34 |
" |
/B |
|
98 |
b |
+B |
<!-- Row # 37 -->
35 |
# |
/C |
|
99 |
c |
+C |
<!-- Row # 38 -->
36 |
$ |
/D |
|
100 |
d |
+D |
<!-- Row # 39 -->
37 |
% |
/E |
|
101 |
e |
+E |
<!-- Row # 40 -->
38 |
& |
/F |
|
102 |
f |
+F |
<!-- Row # 41 -->
39 |
|
/G |
|
103 |
g |
+G |
<!-- Row # 42 -->
40 |
( |
/H |
|
104 |
h |
+H |
<!-- Row # 43 -->
41 |
) |
/I |
|
105 |
i |
+I |
<!-- Row # 44 -->
42 |
* |
/J |
|
106 |
j |
+J |
<!-- Row # 45 -->
43 |
+ |
/K |
|
107 |
k |
+K |
<!-- Row # 46 -->
44 |
, |
/L |
|
108 |
l |
+L |
<!-- Row # 47 -->
45 |
- |
- |
|
109 |
m |
+M |
<!-- Row # 48 -->
46 |
. |
. |
|
110 |
n |
+N |
<!-- Row # 49 -->
47 |
/ |
/O |
|
111 |
o |
+O |
<!-- Row # 50 -->
48 |
0 |
0 |
|
112 |
p |
+P |
<!-- Row # 51 -->
49 |
1 |
1 |
|
113 |
q |
+Q |
<!-- Row # 52 -->
50 |
2 |
2 |
|
114 |
r |
+R |
<!-- Row # 53 -->
51 |
3 |
3 |
|
115 |
s |
+S |
<!-- Row # 54 -->
52 |
4 |
4 |
|
116 |
t |
+T |
<!-- Row # 55 -->
53 |
5 |
5 |
|
117 |
u |
+U |
<!-- Row # 56 -->
54 |
6 |
6 |
|
118 |
v |
+V |
<!-- Row # 57 -->
55 |
7 |
7 |
|
119 |
w |
+W |
<!-- Row # 58 -->
56 |
8 |
8 |
|
120 |
x |
+X |
<!-- Row # 59 -->
57 |
9 |
9 |
|
121 |
y |
+Y |
<!-- Row # 60 -->
58 |
: |
/Z |
|
122 |
z |
+Z |
<!-- Row # 61 -->
59 |
; |
%F |
|
123 |
{ |
%P |
<!-- Row # 62 -->
60 |
< |
%G |
|
124 |
| |
%Q |
<!-- Row # 63 -->
61 |
= |
%H |
|
125 |
} |
%R |
<!-- Row # 64 -->
62 |
> |
%I |
|
126 |
~ |
%S |
<!-- Row # 65 -->
63 |
? |
%J |
|
127 |
DEL |
%T,%X,%Y,%Z |
Checksum
Code 39 can have an optional modulo 43 check digit. To calculate the check sum digit, each character is assigned a value (see following table).
Char. |
Value |
|
Char. |
Value |
<!-- Row # 2 -->
0 |
0 |
|
M |
22 |
<!-- Row # 3 -->
1 |
1 |
|
N |
23 |
<!-- Row # 4 -->
2 |
2 |
|
O |
24 |
<!-- Row # 5 -->
3 |
3 |
|
P |
25 |
<!-- Row # 6 -->
4 |
4 |
|
Q |
26 |
<!-- Row # 7 -->
5 |
5 |
|
R |
27 |
<!-- Row # 8 -->
6 |
6 |
|
S |
28 |
<!-- Row # 9 -->
7 |
7 |
|
T |
29 |
<!-- Row # 10 -->
8 |
8 |
|
U |
30 |
<!-- Row # 11 -->
9 |
9 |
|
V |
31 |
<!-- Row # 12 -->
A |
10 |
|
W |
32 |
<!-- Row # 13 -->
B |
11 |
|
X |
33 |
<!-- Row # 14 -->
C |
12 |
|
Y |
34 |
<!-- Row # 15 -->
D |
13 |
|
Z |
35 |
<!-- Row # 16 -->
E |
14 |
|
- |
36 |
<!-- Row # 17 -->
F |
15 |
|
. |
37 |
<!-- Row # 18 -->
G |
16 |
|
[Space] |
38 |
<!-- Row # 19 -->
H |
17 |
|
$ |
39 |
<!-- Row # 20 -->
I |
18 |
|
/ |
40 |
<!-- Row # 21 -->
J |
19 |
|
+ |
41 |
<!-- Row # 22 -->
K |
20 |
|
% |
42 |
<!-- Row # 23 -->
L |
21 |
|
|
|
Here is how the checksum calculation is done:
- Take the value (0 through 42) of each character in the barcode excluding start and stop codes.
- Sum the values.
- Divide the result by 43.
- The remainder is the value of the checksum character to be appended.
The above algorithm is also taken from Wikepedia.
Now that we have somewhat of an idea of how data is encoded in a code 39 barcode, let's see how to build our class. In the article, I will show the VB.NET code but the same class will be available in C# for download.
Let's start by assigning the class, named Barcode39, to a Namespace Barcodes. If luck will have it, I will soon follow this article with another one that will show how to create Code128 barcodes and I will put that in the same Namespace.
In the code below, the values of width of narrow and wide bars are held by the WIDEBAR_WIDTH
and NARROWBAR_WIDTH
constants. Those values will set the ratio between narrow and wide bars to 1:2
The symbol of each character (the sequence of Bwbw characters) is stored in a Hashtable object (named mEncoding
), where the Key is the character itself and the value is the symbol of the character. This Hashtable object is loaded in the class constructor.
We also have an array of Char elements containing the 43 characters encoded by the barcode symbology. The elements in this array mirror the above table and the array is also loaded in the constructor.
Option Explicit On
Option Strict On
Namespace Barcodes
Public Class Barcode39
Private Const WIDEBAR_WIDTH As Short = 2
Private Const NARROWBAR_WIDTH As Short = 1
Private Const NUM_CHARACTERS As Integer = 43
Private mEncoding As Hashtable = New Hashtable
Dim mCodeValue(NUM_CHARACTERS - 1) As Char
Public Sub New()
mEncoding.Add("*", "bWbwBwBwb")
mEncoding.Add("-", "bWbwbwBwB")
mEncoding.Add("$", "bWbWbWbwb")
mEncoding.Add("%", "bwbWbWbWb")
[.]
mEncoding.Add("X", "bWbwBwbwB")
mEncoding.Add("Y", "BWbwBwbwb")
mEncoding.Add("Z", "bWBwBwbwb")
mCodeValue(0) = "0"c
mCodeValue(1) = "1"c
mCodeValue(2) = "2"c
[.]
mCodeValue(40) = "/"c
mCodeValue(41) = "+"c
mCodeValue(42) = "%"c
End Sub
End Class
End Namespace
Forming the extended string
The next task is to build a rather simple function that will examine the input string and check whether there are characters outside the standard 43. If there are, it will add the required special character in front of it. The function will return the extended version of the input string:
Private Function ExtendedString(ByVal s As String) As String
Dim Ch As Char
Dim KeyChar As Integer
Dim retVal As String = ""
For Each Ch In s
KeyChar = Asc(Ch)
Select Case KeyChar
Case 0
retVal &= "%U"
Case 1 To 26
retVal &= "$" & Chr(64 + KeyChar)
Case 27 To 31
retVal &= "%" & Chr(65 - 27 + KeyChar)
Case 33 To 44
retVal &= "/" & Chr(65 - 33 + KeyChar)
Case 47
retVal &= "/O"
Case 58
retVal &= "/Z"
Case 59 To 63
retVal &= "%" & Chr(70 - 59 + KeyChar)
Case 64
retVal &= "%V"
Case 91 To 95
retVal &= "%" & Chr(75 - 91 + KeyChar)
Case 96
retVal &= "%W"
Case 97 To 122
retVal &= "+" & Chr(65 - 97 + KeyChar)
Case 123 To 127
retVal &= "%" & Chr(80 - 123 + KeyChar)
Case Else
retVal &= Ch
End Select
Next
Return retVal
End Function
Calculating the checksum
The Checksum
function loops through the extended string and for each character in it, it gets the value (which basically coincides with the index of the character in the mCodeValue
array) to add it to an Integer variable. The function then returns the reminder of the division between the sum and 43.
Private Function CheckSum(ByVal sCode As String) As Integer
Dim CurrentSymbol As Char
Dim Chk As Integer
For j As Integer = 0 To sCode.Length - 1
CurrentSymbol = sCode.Chars(j)
Chk += GetSymbolValue(CurrentSymbol)
Next
Return Chk Mod (NUM_CHARACTERS)
End Function
Private Function GetSymbolValue(ByVal s As Char) As Integer
Dim k As Integer
For k = 0 To NUM_CHARACTERS - 1
If mCodeValue(k) = s Then
Return k
End If
Next
Return Nothing
End Function
Additional properties
To make the class a tad more flexible, I added few properties to it. Here is the list:
Name |
Type |
Description |
<!-- Row # 2 -->
ShowString |
Boolean |
If set to True, will add the encoded string below the barcode picture. |
<!-- Row # 3 -->
IncludeCheckSumDigit |
Boolean |
If true, will calculate the checksum. |
<!-- Row # 4 -->
TextFont |
Font |
Font of the encoded string printed when ShowString = True |
<!-- Row # 5 -->
TextColor |
Color |
Color of the encoded string printed when ShowString = True |
The above properties will be used when building the barcode picture. Let's add them to the class:
Public ShowString As Boolean
Public IncludeCheckSumDigit As Boolean
Public TextFont As New Font("Courier New", 7)
Public TextColor As Color = Color.Black
The meat
The class exposes a public function GenerateBarcodeImage
that will create and prepare a Graphics object where the barcode picture will be drawn by another Sub (DrawBarcode) and will return an Image object. The function takes 3 parameters:
- The height of the image – remember that will include the space needed to print the string
- The width of the image – assign an appropriate value to it or the barcode picture might not fit in the Image object
- Original String
GenerateBarcodeImage
creates a PictureBox
object with size ImageWidth
x ImageHeight
and uses the Graphics object from it for the painting. Then, it composes the extended string and formats it by adding the leading and closing asterisk and the checksum digit if necessary. It also writes the original string (if the ShowString property = True
) and finally, it calls the procedure DrawBarcode
that draws the barcode and returns the Image object with the barcode picture in it.
Public Function GenerateBarcodeImage(ByVal ImageWidth As Integer,
ByVal ImageHeight As Integer,
ByVal OriginalString As String) As Image
Dim pb As PictureBox
pb = New PictureBox
With pb
.Width = ImageWidth
.Height = ImageHeight
pb.Image = New Bitmap(.Width, .Height)
End With
Dim g As Graphics = Graphics.FromImage(pb.Image)
g.Clear(Color.White)
Dim ExtString As String
ExtString = ExtendedString(OriginalString)
Dim EncodedString As String
Dim ChkSum As Integer
If IncludeCheckSumDigit = False Then
EncodedString = String.Format("{0}{1}{0}", "*", ExtString)
Else
ChkSum = CheckSum(ExtString)
EncodedString = String.Format("{0}{1}{2}{0}",
"*", ExtString, mCodeValue(ChkSum))
End If
Dim textBrush As New SolidBrush(TextColor)
If ShowString Then
If Not IsNothing(TextFont) Then
Dim H As Single = g.MeasureString(OriginalString, TextFont).Height
g.DrawString(OriginalString, TextFont, textBrush, 0, ImageHeight - H)
ImageHeight = ImageHeight - CShort(H)
End If
End If
DrawBarcode(g, EncodedString, ImageWidth, ImageHeight)
Return pb.Image
End Function
The working of Drawarcode
is rather simple. For each character in the encoded string, it gets the symbol from the mEncoding
Hashtable. As we know, this symbol is a sequence of b,w,B and W characters, and for each of these characters the function uses the method FillRectangle
of the Graphics object to create a bar with the opportune width and color. This opportune width and color is determined by the functions getBCSymbolWidth
and getBCSymbolColor
.
Private Sub DrawBarcode(ByVal g As Graphics,
ByVal EncodedString As String,
ByVal Width As Integer,
ByVal Height As Integer)
Dim XPosition As Short = 0
Dim YPosition As Short = 0
Dim CurrentSymbol As String = String.Empty
For j As Short = 0 To CShort(EncodedString.Length - 1)
CurrentSymbol = EncodedString.Chars(j)
Dim EncodedSymbol As String = mEncoding(CurrentSymbol).ToString
For i As Short = 0 To CShort(EncodedSymbol.Length - 1)
Dim CurrentCode As String = EncodedSymbol.Chars(i)
g.FillRectangle(getBCSymbolColor(CurrentCode),
XPosition, YPosition, getBCSymbolWidth(CurrentCode), Height)
XPosition = XPosition + getBCSymbolWidth(CurrentCode)
Next
g.FillRectangle(getBCSymbolColor("w"), XPosition, Yposition,
getBCSymbolWidth("w"), Height)
XPosition = XPosition + getBCSymbolWidth("w")
Next
End Sub
Private Function getBCSymbolColor(ByVal symbol As String) As System.Drawing.Brush
If symbol = "W" Or symbol = "w" Then
getBCSymbolColor = Brushes.White
Else
getBCSymbolColor = Brushes.Black
End If
End Function
Private Function getBCSymbolWidth(ByVal symbol As String) As Short
If symbol = "B" Or symbol = "W" Then
getBCSymbolWidth = WIDEBAR_WIDTH
Else
getBCSymbolWidth = NARROWBAR_WIDTH
End If
End Function
And this is it. Just for illustrative purposes, I added a very simple form that uses the class and that is included in the downloadable files.
Happy coding,
Stefano Castelli