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

Code39 Barcodes in VB.NET and C#

0.00/5 (No votes)
24 Sep 2015 9  
The article will illustrate how to create a Code39 barcode in VB.NET and C#

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).

<!-- Row # 2 --> <!-- Row # 3 --> <!-- Row # 4 --> <!-- Row # 5 --> <!-- Row # 6 --> <!-- Row # 7 --> <!-- Row # 8 --> <!-- Row # 9 --> <!-- Row # 10 --> <!-- Row # 11 --> <!-- Row # 12 --> <!-- Row # 13 --> <!-- Row # 14 --> <!-- Row # 15 --> <!-- Row # 16 --> <!-- Row # 17 --> <!-- Row # 18 --> <!-- Row # 19 --> <!-- Row # 20 --> <!-- Row # 21 --> <!-- Row # 22 --> <!-- Row # 23 --> <!-- Row # 24 --> <!-- Row # 25 --> <!-- Row # 26 --> <!-- Row # 27 --> <!-- Row # 28 --> <!-- Row # 29 --> <!-- Row # 30 --> <!-- Row # 31 --> <!-- Row # 32 --> <!-- Row # 33 --> <!-- Row # 34 --> <!-- Row # 35 --> <!-- Row # 36 --> <!-- Row # 37 --> <!-- Row # 38 --> <!-- Row # 39 --> <!-- Row # 40 --> <!-- Row # 41 --> <!-- Row # 42 --> <!-- Row # 43 --> <!-- Row # 44 --> <!-- Row # 45 --> <!-- Row # 46 --> <!-- Row # 47 --> <!-- Row # 48 --> <!-- Row # 49 --> <!-- Row # 50 --> <!-- Row # 51 --> <!-- Row # 52 --> <!-- Row # 53 --> <!-- Row # 54 --> <!-- Row # 55 --> <!-- Row # 56 --> <!-- Row # 57 --> <!-- Row # 58 --> <!-- Row # 59 --> <!-- Row # 60 --> <!-- Row # 61 --> <!-- Row # 62 --> <!-- Row # 63 --> <!-- Row # 64 --> <!-- Row # 65 -->
Nr Character Encoding   Nr Character Encoding
0 NUL %U   64 @ %V
1 SOH $A   65 A A
2 STX $B   66 B B
3 ETX $C   67 C C
4 EOT $D   68 D D
5 ENQ $E   69 E E
6 ACK $F   70 F F
7 BEL $G   71 G G
8 BS $H   72 H H
9 HT $I   73 I I
10 LF $J   74 J J
11 VT $K   75 K K
12 FF $L   76 L L
13 CR $M   77 M M
14 SO $N   78 N N
15 SI $O   79 O O
16 DLE $P   80 P P
17 DC1 $Q   81 Q Q
18 DC2 $R   82 R R
19 DC3 $S   83 S S
20 DC4 $T   84 T T
21 NAK $U   85 U U
22 SYN $V   86 V V
23 ETB $W   87 W W
24 CAN $X   88 X X
25 EM $Y   89 Y Y
26 SUB $Z   90 Z Z
27 ESC %A   91 [ %K
28 FS %B   92 \ %L
29 GS %C   93 ] %M
30 RS %D   94 ^ %N
31 US %E   95 _ %O
32 [space] [space]   96 ` %W
33 ! /A   97 a +A
34 " /B   98 b +B
35 # /C   99 c +C
36 $ /D   100 d +D
37 % /E   101 e +E
38 & /F   102 f +F
39   /G   103 g +G
40 ( /H   104 h +H
41 ) /I   105 i +I
42 * /J   106 j +J
43 + /K   107 k +K
44 , /L   108 l +L
45 - -   109 m +M
46 . .   110 n +N
47 / /O   111 o +O
48 0 0   112 p +P
49 1 1   113 q +Q
50 2 2   114 r +R
51 3 3   115 s +S
52 4 4   116 t +T
53 5 5   117 u +U
54 6 6   118 v +V
55 7 7   119 w +W
56 8 8   120 x +X
57 9 9   121 y +Y
58 : /Z   122 z +Z
59 ; %F   123 { %P
60 < %G   124 | %Q
61 = %H   125 } %R
62 > %I   126 ~ %S
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).

<!-- Row # 2 --> <!-- Row # 3 --> <!-- Row # 4 --> <!-- Row # 5 --> <!-- Row # 6 --> <!-- Row # 7 --> <!-- Row # 8 --> <!-- Row # 9 --> <!-- Row # 10 --> <!-- Row # 11 --> <!-- Row # 12 --> <!-- Row # 13 --> <!-- Row # 14 --> <!-- Row # 15 --> <!-- Row # 16 --> <!-- Row # 17 --> <!-- Row # 18 --> <!-- Row # 19 --> <!-- Row # 20 --> <!-- Row # 21 --> <!-- Row # 22 --> <!-- Row # 23 -->
Char. Value   Char. Value
0 0   M 22
1 1   N 23
2 2   O 24
3 3   P 25
4 4   Q 26
5 5   R 27
6 6   S 28
7 7   T 29
8 8   U 30
9 9   V 31
A 10   W 32
B 11   X 33
C 12   Y 34
D 13   Z 35
E 14   - 36
F 15   . 37
G 16   [Space] 38
H 17   $ 39
I 18   / 40
J 19   + 41
K 20   % 42
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()
         '       Character, symbol
         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:

<!-- Row # 2 --> <!-- Row # 3 --> <!-- Row # 4 --> <!-- Row # 5 -->
Name Type Description
ShowString Boolean If set to True, will add the encoded string below the barcode picture.
IncludeCheckSumDigit Boolean If true, will calculate the checksum.
TextFont Font Font of the encoded string printed when ShowString = True
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

    '-- create a image where to paint the bars
    Dim pb As PictureBox
    pb = New PictureBox
    With pb
       .Width = ImageWidth
       .Height = ImageHeight
       pb.Image = New Bitmap(.Width, .Height)
    End With
    '---------------------

    'clear the image and set it to white background
    Dim g As Graphics = Graphics.FromImage(pb.Image)
    g.Clear(Color.White)


    'get the extended string
    Dim ExtString As String
    ExtString = ExtendedString(OriginalString)


    '-- This part format the sring that will be encoded
    '-- The string needs to be surrounded by asterisks
    '-- to make it a valid Code39 barcode
    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
    '----------------------

    '-- write the original string at the bottom if ShowString = True
    Dim textBrush As New SolidBrush(TextColor)
    If ShowString Then
       If Not IsNothing(TextFont) Then
          'calculates the height of the string
          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
    '----------------------------------------

    'THIS IS WHERE THE BARCODE DRAWING HAPPENS
    DrawBarcode(g, EncodedString, ImageWidth, ImageHeight)

    'IMAGE OBJECT IS RETURNED
    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)

     'Start drawing at 1, 1
     Dim XPosition As Short = 0
     Dim YPosition As Short = 0

     Dim CurrentSymbol As String = String.Empty

     '-- draw the bars
     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

        'After each written full symbol we need a whitespace (narrow width)
        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

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