Click here to Skip to main content
Click here to Skip to main content

Drawing Barcodes in Windows Part 5 - Code 128

, 14 Jun 2002 BSD
Rate this:
Please Sign up or sign in to vote.
An article on drawing Code 128 barcodes to the screen or to the clipboard

Introduction

A recent project at work required that I write out barcode characters into a font file for an old photo-typesetter. This experience inspired me to start a side project writing some code that renders a barcode on the Windows screen, given the proper input. This series of articles is the result of that project.

Code 128 basics

Code 128 is a very high density alpha-numeric code that was introduced in 1981 and is being used in a variety of applications. Code 128 characters consist of 3 bars and 3 spaces, which are built by using 11 modules each of which can be black or white. Because all of the characters are built from the same 11 modules, there really is only one element width that needs to be defined, the module width. The bar/space patterns for the 107 data characters and the start/stop codes are listed below. Each pattern is 11 characters (modules) long, and is either 'b' (that module is a bar) or 's' (that module is a space). Note that there are three subsets to Code 128, Subset A and B cover the ASCII character set, and Subset C is a double-density numeric-only subset. Three different start characters tell the barcode reader which subset is the starting subset, and three shift characters allow changing subsets within a Code 128 barcode.

Code A

Code B

Code C

Value

Pattern

Space

Space

00

0

bbsbbssbbss

!

!

01

1

bbssbbsbbss

02

2

bbssbbssbbs

#

#

03

3

bssbssbbsss

$

$

04

4

bssbsssbbss

%

%

05

5

bsssbssbbss

&

&

06

6

bssbbssbsss

'

(

07

7

bssbbsssbss

(

)

08

8

bsssbbssbss

)

*

09

9

bbssbssbsss

*

*

10

10

bbssbsssbss

+

+

11

11

bbsssbssbss

,

,

12

12

bsbbssbbbss

-

-

13

13

bssbbsbbbss

.

.

14

14

bssbbssbbbs

/

/

15

15

bsbbbssbbss

0

0

16

16

bssbbbsbbss

1

1

17

17

bssbbbssbbs

2

2

18

18

bbssbbbssbs

3

3

19

19

bbssbsbbbss

4

4

20

20

bbssbssbbbs

5

5

21

21

bbsbbbssbss

6

6

22

22

bbssbbbsbss

7

7

23

23

bbbsbbsbbbs

8

8

24

24

bbbsbssbbss

9

9

25

25

bbbssbsbbss

:

:

26

26

bbbssbssbbs

;

;

27

27

bbbsbbssbss

<

<

28

28

bbbssbbsbss

Equal

Equal

29

29

bbbssbbssbs

>

>

30

30

bbsbbsbbsss

?

?

31

31

bbsbbsssbbs

@

@

32

32

bbsssbbsbbs

A

A

33

33

bsbsssbbsss

B

B

34

34

bsssbsbbsss

C

C

35

35

bsssbsssbbs

D

D

36

36

bsbbsssbsss

E

E

37

37

bsssbbsbsss

F

F

38

38

bsssbbsssbs

G

G

39

39

bbsbsssbsss

H

H

40

40

bbsssbsbsss

I

I

41

41

bbsssbsssbs

J

J

42

42

bsbbsbbbsss

K

K

43

43

bsbbsssbbbs

L

L

44

44

bsssbbsbbbs

M

M

45

45

bsbbbsbbsss

N

N

46

46

bsbbbsssbbs

O

O

47

47

bsssbbbsbbs

P

P

48

48

bbbsbbbsbbs

Q

Q

49

49

bbsbsssbbbs

R

R

50

50

bbsssbsbbbs

S

S

51

51

bbsbbbsbsss

T

T

52

52

bbsbbbsssbs

U

U

53

53

bbsbbbsbbbs

V

V

54

54

bbbsbsbbsss

W

W

55

55

bbbsbsssbbs

X

X

56

56

bbbsssbsbbs

Y

Y

57

57

bbbsbbsbsss

Z

Z

58

58

bbbsbbsssbs

[

[

59

59

bbbsssbbsbs

\

\

60

60

bbbsbbbbsbs

]

]

61

61

bbssbssssbs

^

^

62

62

bbbbsssbsbs

_

_

63

63

bsbssbbssss

NUL

`

64

64

bsbssssbbss

SOH

a

65

65

bssbsbbssss

STX

b

66

66

bssbssssbbs

ETX

c

67

67

bssssbsbbss

EOT

d

68

68

bssssbssbbs

ENQ

e

69

69

bsbbssbssss

ACK

f

70

70

bsbbssssbss

BEL

g

71

71

bssbbsbssss

BS

h

72

72

bssbbssssbs

HT

i

73

73

bssssbbsbss

LF

j

74

74

bssssbbssbs

VT

k

75

75

bbssssbssbs

FF

l

76

76

bbssbsbssss

CR

m

77

77

bbbbsbbbsbs

SO

n

78

78

bbssssbsbss

SI

o

79

79

bsssbbbbsbs

DLE

p

80

80

bsbssbbbbss

DC1

q

81

81

bssbsbbbbss

DC2

r

82

82

bssbssbbbbs

DC3

s

83

83

bsbbbbssbss

DC4

t

84

84

bssbbbbsbss

NAK

u

85

85

bssbbbbssbs

SYN

v

86

86

bbbbsbssbss

ETB

w

87

87

bbbbssbsbss

CAN

x

88

88

bbbbssbssbs

EM

y

89

89

bbsbbsbbbbs

SUB

z

90

90

bbsbbbbsbbs

ESC

{

91

91

bbbbsbbsbbs

FS

|

92

92

bsbsbbbbsss

GS

}

93

93

bsbsssbbbbs

RS

~

94

94

bsssbsbbbbs

US

DEL

95

95

bsbbbbsbsss

FNC3

FNC3

96

96

bsbbbbsssbs

FNC2

FNC2

97

97

bbbbsbsbsss

Shift

Shift

98

98

bbbbsbsssbs

Switch Code C

Switch Code C

99

99

bsbbbsbbbbs

Switch Code B

FNC4

Switch Code B

100

bsbbbbsbbbs

FNC4

Switch Code A

Switch Code A

101

bbbsbsbbbbs

FNC1

FNC1

FNC1

102

bbbbsbsbbbs

START Code A

START Code A

START Code A

103

bbsbsbbbbss

START Code B

START Code B

START Code B

104

bbsbssbssss

START Code C

START Code C

START Code C

105

bbsbssbbbss

STOP

STOP

STOP

106

bbsssbbbsbsbb

Each Code 128 barcode has a check digit that immediately precedes the stop character. The check digit is a weighted sum of the data characters, modulus 103. The data characters are weight from left to right by the infinite sequence {1,2,3,...} An example of the check digit calculations is shown below, using the message "DATA".

Start A

D

A

T

A

Check Digit

Stop


36

33

52

33




1

2

3

4



To calculate the check digit, first find the sum of products: (36*1)+(33*2)+(52*3)+(33*4) = 390. Divide 390 by 103 to get 3 with a remainder of 81. The value of the check digit is 81 which corresponds to the ASCII character DC1. A picture of the entire barcode is shown below.

Note that the barcode reader uses the check digits to decode the barcode, but does not transmit them.

The Barcode Bitmap Workspace

There are three different projects in the Barcode Bitmap workspace. The first and most important project is the bblib project. This project is a static library where code to draw all of the different types of barcodes exists. This also is the main piece of code discussed in this series of articles. Another project Barcode Bitmap workspace is the bbdll project. This project is simply a regular DLL wrapper around the bblib static library. The final project in the Barcode Bitmap workspace is the DLL client project. This project is a simple dialog-based application that calls the bbdll DLL to draw barcodes in the dialog, or put barcodes on the clipboard as Windows bitmaps.

The base class CBarcode

The base class for all the barcode types discussed in this series of articles is the CBarcode class. The class declaration is listed below.

class CBarcode
{
    public:
        CBarcode();
        void LoadData(CString csMessage, double dNarrowBar, double dFinalHeight, 
                      HDC pDC, int nStartingXPixel, int nStartingYPixel, 
                      double dRatio = 1.0);
        virtual void DrawBitmap() = 0;
        virtual void BitmapToClipboard() = 0;
        virtual ~CBarcode();
        long GetBarcodePixelWidth();
        long GetBarcodePixelHeight();
    protected:
        CString m_csMessage;
        HDC m_hDC;
        long m_nFinalBarcodePixelWidth;
        long m_nNarrowBarPixelWidth;
        long m_nPixelHeight;
        long m_nStartingXPixel;
        long m_nStartingYPixel;
        long m_nSymbology;
        long m_nWideBarPixelWidth;
        virtual void DrawPattern(CString csPattern) = 0;
};

There are a few things to note about the CBarcode class. First note that it has data members that contain all of the useful data needed to draw a barcode message. This data includes the narrow element pixel width, the wide element pixel width, the message, and the symbology. Second the class has data members that contain information about how to output the barcode message. This data includes a device context handle, and a starting X and Y pixel. Third the class has some public member functions to intialize the class by loading data, and obtain information about the barcode message, namely its pixel height and width. Fourth the class has several abstract member functions that make this class an abstract base class. Any classes derived from CBarcode will be expected to implement these functions.

The CCode128 class

The CCode128 class is the class to implement to draw a Code 128 barcode. The class declaration is listed below.

class CCode128 : public CBarcode  
{
public:
    CCode128();
    virtual ~CCode128();

public:
    void    BitmapToClipboard();
    void    DrawBitmap();
    void     LoadData(CString csMessage, double dNarrowBar, 
                     double dFinalHeight, HDC pDC, int nStartingXPixel,
                     int nStartingYPixel, long nStartingSubset );
private:
    long    GetCheckDigit();
    void    DrawPattern(CString csPattern);
    CString    RetrievePattern(long c);

    long    m_nCurrentSubset;
};

The class has three public functions BitmapToClipboard() and DrawBitmap(), and LoadData(). The class also has a data member, m_nCurrentSubset, which holds the current subset while drawing the barcode, and is initialized to the starting subset. The steps to use the class are simple, declare an instance of the class, call LoadData() to initialize class data, and then call either BitmapToClipboard() if you want to put a bitmap of the barcode on the clipboard, or call DrawBitmap() to draw the barcode message.

Drawing a Barcode to a Device Context

The following code snipet is an example using DrawBitmap().

CString            csMessage;
double            dNarrowBar,dHeight;
HDC            pDC;
long            nStartingXPixel, nStartingYPixel, nCode128StartingSubset;
CCode128            oBarcode;

// assign variable values here

// call LoadData and draw the barcode
oBarcode.LoadData(csMessage,dNarrowBar,dHeight,pDC,nStartingXPixel,
                  nStartingYPixel,nCode128StartingSubset);
oBarcode.DrawBitmap();

Drawing a Barcode to the Clipboard

The following code snipet is an example using BitmapToClipboard().

HDC            hDC = NULL;
double            dNarrowbar,dHeight;
CCode128            oBarcode;

// assign variable values here

// call LoadData and BitmapToClipboard()
oBarcode.LoadData(csMessage,dNarrowBar,dHeight,hDC,0,0,nCode128StartingSubset);
oBarcode.BitmapToClipboard();

Note that when using the BitmapToClipboard() function, you can pass a null device context handle and zeroes for the starting X and Y pixel in the LoadData() call. Obviously the starting X and Y pixels are meaningless on the clipboard, but what about the null device context handle? The answer to that question can be found by looking at this code snipet from the BitmapToClipboard() function.

CDC    memDC;
memDC.CreateCompatibleDC(NULL);

So the BitmapToClipboard() function creates its own memory device context by using the memDC.CreateCompatibleDC(NULL) function call. A quick look at the MSDN documentation shows that if you pass a NULL value to CreateCompatibleDC, the device context created is compatible with the screen.

CCode128::LoadData() details

The code for CCode128::LoadData() is listed below.
void CCode128::LoadData(CString csMessage, double dNarrowBar, 
                        double dFinalHeight, HDC pDC, 
                        int nStartingXPixel, int nStartingYPixel, 
                        long nStartingSubset )
{
    // call base class version
    CBarcode::LoadData(csMessage, dNarrowBar, dFinalHeight, pDC, 
                            nStartingXPixel, nStartingYPixel);

    // set additional data
    m_nCurrentSubset = nStartingSubset;
}

As you can see, the CCode128::LoadData() gets most of its functionality from the base class function CBarcode::LoadData() which is discussed below. The Code 128 specific attribute that made this function necessary is m_nCurrentSubset, which is initialized to the starting subset of the Code 128 barcode you are drawing.

CBarcode::LoadData() details

The parameters for CBarcode::LoadData() deserve some further explanation and this seems like the place to do it. The first parameter, csMessage is simply the message you wish to be drawn as a Code 128 barcode. The next parameter dNarrowBar is the width of each module in inches. The parameter dHeight is the height of the barcode in inches. The parameter pDC is a handle to the device context that the barcode will be drawn in. The next two parameters, nStartingXPixel and nStartingYPixel define the coordinates to start drawing the barcode. The final parameter, dRatio is the ratio of wide/narrow element widths, and has no use in a Code 128 barcode. If you look at the declaration of CBarcode::LoadData(), you'll see the parameter dRatio has a default value of 1.0. So when using LoadData() for a Code 128 barcode, you can just leave out the dRatio parameter. If you remember the declaration of the CBarcode class above, you'll remember that it stores all width and height information in pixels, and that it stores the narrow element width and the wide element width instead of the narrow element width and the wide/narrow element width ratio. Clearly CBarcode::LoadData() is doing some behind the scenes conversion work.

The first step to that conversion work is to get the X axis and Y axis dpi, which is done by the following code, taken from CBarcode::LoadData().

CDC    tempDC;
tempDC.Attach(m_hDC);
nXAxisDpi = tempDC.GetDeviceCaps(LOGPIXELSX);
nYAxisDpi = tempDC.GetDeviceCaps(LOGPIXELSY);
tempDC.Detach();

Once you have the X and Y axis dpi, you can calculate the pixel height, narrow element pixel width, and wide element pixel width as shown in the following code snipet.

// load the final attributes that depend on the device context
m_nPixelHeight = (int)((nYAxisDpi*dFinalHeight)+0.5);
m_nNarrowBarPixelWidth = (int)((nXAxisDpi*dNarrowBar)+0.5);
m_nWideBarPixelWidth = (int)(dRatio*m_nNarrowBarPixelWidth);

Note the rounding effect when calculating the narrow element pixel width and the wide element pixel width. The narrow element width has a lower limit of one pixel, so the barcode you can produce is limited by the physical limitations of the output device. Also note that for Code 128, the ratio will always be 1.0 and the member variable m_nWideBarPixelWidth will always equal m_nNarrowBarPixelWidth and will not be used.

Next you can calculate the final barcode pixel width, this operation is symbology specific and the Code 128 code excerpt is listed below.

// get final character width
nTemp = m_csMessage.GetLength();

m_nFinalBarcodePixelWidth = ((nTemp*11)+35)*m_nNarrowBarPixelWidth;

This code computes the width of a Code 128 barcode by taking the message length, multiplying it by 11 (11 modules per character), adding 35 (start character, check digit, and stop character plus 2 module termination bar), and multiplying that total by the module width to get the total barcode pixel width.

CCode128::DrawBitmap() details

The DrawBitmap() function is where each message character is drawn.. A listing of the CCode128::DrawBitmap() function is listed below.

void CCode128::DrawBitmap()
{
    long    nChar,nNextChar,nCharacterPosition,nCheckDigit;

    // calculate the check digit
    nCheckDigit = GetCheckDigit();
    
    // draw start character for current subset
    if (m_nCurrentSubset==SUBSETA)
        DrawPattern(RetrievePattern(103));
    else if (m_nCurrentSubset==SUBSETB)
        DrawPattern(RetrievePattern(104));
    else if (m_nCurrentSubset==SUBSETC)
        DrawPattern(RetrievePattern(105));
    
    // initialize position in message    
    nCharacterPosition = 0;

    while (nCharacterPosition < m_csMessage.GetLength())
    {
        if (m_nCurrentSubset==SUBSETC)
        {
            // if it's a switch to subsetA - same character (103) for all subsets
            if (g_nASCIItoCode128SubsetAB[SUBSETA]
                              [m_csMessage.GetAt(nCharacterPosition)]==101)
            {
                // draw the startA code
                DrawPattern(RetrievePattern(101));

                // we've moved one message character
                nCharacterPosition++;

                // actually change the subset
                m_nCurrentSubset = SUBSETA;
            }
            // if it's a switch to subsetB - same character (104) for all subsets
            else if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                     [m_csMessage.GetAt(nCharacterPosition)]==100)
            {
                // draw the startB code
                DrawPattern(RetrievePattern(100));

                // we've moved one message character
                nCharacterPosition++;

                // actually change the subset
                m_nCurrentSubset = SUBSETB;
            }
            // it's FNC1 - just print it out
            else if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                     [m_csMessage.GetAt(nCharacterPosition)]==102)
            {
                // draw the FNC1
                DrawPattern(RetrievePattern(100));

                // we've moved one message character
                nCharacterPosition++;
            }
            // it's a digit - pull two at a time
            else
            {
                CString        csTemp;

                // get the next two characters
                csTemp = m_csMessage.Mid(nCharacterPosition,2);

                // convert them to longs
                nChar = atol((const char *)csTemp);

                // draw the code 128 character
                DrawPattern(RetrievePattern(nChar));

                // we've moved two message characters
                nCharacterPosition += 2;
            }
        }
        // we're in SUBSETA or SUBSETB
        else
        {
            // handle upper ASCII characters if necessary
            long nTemp2 = m_csMessage.GetAt(nCharacterPosition);
            if (nTemp2<-1)
                nTemp2 = nTemp2&255;
            
            // retrieve the message character
            nChar = g_nASCIItoCode128SubsetAB[m_nCurrentSubset][nTemp2];

            // draw the char
            DrawPattern(RetrievePattern(nChar));

            // we've moved one character position
            nCharacterPosition++;

            // if switch in SUBSETA
            if (m_nCurrentSubset==SUBSETA)
            {
                if (nChar==100)
                    m_nCurrentSubset = SUBSETB;
                else if (nChar==99)
                    m_nCurrentSubset = SUBSETC;
            }
            // if switch in SUBSETB
            else if (m_nCurrentSubset==SUBSETB)
            {
                if (nChar==101)
                    m_nCurrentSubset = SUBSETA;
                else if (nChar==99)
                    m_nCurrentSubset = SUBSETC;
            }
            // if a shift character
            else if (nChar==98)
            {
                // shift subsets for the next character only
                if (m_nCurrentSubset==SUBSETA)
                    nNextChar = g_nASCIItoCode128SubsetAB[SUBSETB]
                                          [m_csMessage.GetAt(nCharacterPosition)];
                else
                    nNextChar = g_nASCIItoCode128SubsetAB[SUBSETA]
                                          [m_csMessage.GetAt(nCharacterPosition)];

                // draw the shifted character
                DrawPattern(RetrievePattern(nChar));

                // since we've handled two characters advance character position again
                nCharacterPosition++;
            }
        }
    }

    // draw check digit
    DrawPattern(RetrievePattern(nCheckDigit));
    
    // draw stop character
    DrawPattern(RetrievePattern(106));

    return;
}

The CCode128::DrawBitmap() function starts out by calculating the check digit using CCode128::GetCheckDigit(). Then it draws the correct start character, based on the current subset value m_nCurrentSubset. Then the code steps through every character in the message and draws the characters by retrieving the Code 128 characters necessary to draw that message character.

On each pass thru the while loop in CCode128::DrawBitmap(), an if statement executes two different pieces of code, one for Subset C, and the other for Subset A and Subset B. If the current subset is Subset C, the code in this if branch executes for the next message character.

if (m_nCurrentSubset==SUBSETC)
{
    // if it's a switch to subsetA - same character (103) for all subsets
    if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                 [m_csMessage.GetAt(nCharacterPosition)]==101)
    {
        // draw the startA code
        DrawPattern(RetrievePattern(101));

        // we've moved one message character
        nCharacterPosition++;

        // actually change the subset
        m_nCurrentSubset = SUBSETA;
    }
    // if it's a switch to subsetB - same character (104) for all subsets
    else if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                 [m_csMessage.GetAt(nCharacterPosition)]==100)
    {
        // draw the startB code
        DrawPattern(RetrievePattern(100));

        // we've moved one message character
        nCharacterPosition++;

        // actually change the subset
        m_nCurrentSubset = SUBSETB;
    }
    // it's FNC1 - just print it out
    else if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                [m_csMessage.GetAt(nCharacterPosition)]==102)
    {
        // draw the FNC1
        DrawPattern(RetrievePattern(100));

        // we've moved one message character
        nCharacterPosition++;
    }
    // it's a digit - pull two at a time
    else
    {
        CString        csTemp;

        // get the next two characters
        csTemp = m_csMessage.Mid(nCharacterPosition,2);

        // convert them to longs
        nChar = atol((const char *)csTemp);

        // draw the code 128 character
        DrawPattern(RetrievePattern(nChar));

        // we've moved two message characters
        nCharacterPosition += 2;
    }
}

If you are in Subset C, four things can legally happen. The next character can switch you to Subset A, the next character can switch you to Subset B, the next character is FNC1 (the function keys indicate reader specific behavior), or the next character is a digit. If the next character is a digit, you need to pull off the next two characters to determine which Code 128 character to draw (remember the double-density numeric-only nature of Subset C).

If the current subset is Subset A or Subset B, the code in the else branch executes for the next message character.

// we're in SUBSETA or SUBSETB
else
{
    // handle upper ASCII characters if necessary
    long nTemp2 = m_csMessage.GetAt(nCharacterPosition);
    if (nTemp2<-1)
        nTemp2 = nTemp2&255;
            
    // retrieve the message character
    nChar = g_nASCIItoCode128SubsetAB[m_nCurrentSubset][nTemp2];

    // draw the char
    DrawPattern(RetrievePattern(nChar));

    // we've moved one character position
    nCharacterPosition++;

    // if switch in SUBSETA
    if (m_nCurrentSubset==SUBSETA)
    {
        if (nChar==100)
            m_nCurrentSubset = SUBSETB;
        else if (nChar==99)
            m_nCurrentSubset = SUBSETC;
    }
    // if switch in SUBSETB
    else if (m_nCurrentSubset==SUBSETB)
    {
        if (nChar==101)
            m_nCurrentSubset = SUBSETA;
        else if (nChar==99)
            m_nCurrentSubset = SUBSETC;
    }
    // if a shift character
    else if (nChar==98)
    {
        // shift subsets for the next character only
        if (m_nCurrentSubset==SUBSETA)
            nNextChar = g_nASCIItoCode128SubsetAB[SUBSETB]
                                           [m_csMessage.GetAt(nCharacterPosition)];
        else
            nNextChar = g_nASCIItoCode128SubsetAB[SUBSETA]
                                           [m_csMessage.GetAt(nCharacterPosition)];

        // draw the shifted character
        DrawPattern(RetrievePattern(nChar));

        // since we've handled two characters advance character position again
        nCharacterPosition++;
    }
}

The 2 dimensional array g_nASCIItoCode128SubsetAB[2][207] is used to convert ASCII message characters to Code 128 Subset A and Subset B characters. The first index of the array is the subset, SUBSETA or SUBSETB, and the second index is the ASCII value of the current message character. The value at this position is the Code 128 character for the current message character in the current subset. The declaration of the array is in CCode128.h and is shown in the code fragment below.

const long    g_nASCIItoCode128SubsetAB[2][207] =
 {{64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  95,  96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106},
  {-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
  64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  -1,  -1,  -1,  95,  96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106}};

Note that the special characters in Subset A and B, characters 95 through 106 (all start characters, FNC1 through FNC 4, all shift characters, and the stop character), are shifted into the upper ASCII characters by adding 100 to the Code 128 characters. So these characters are specified in the message using ASCII 195 through 206. ASCII characters that have no Code 128 equivalent are denoted by a -1. The code now draws the retrieved Code 128 character using DrawPattern(). Then the code uses an if statement to handle the characters that shift the subset, similar to how Subset C code does. One new thing is the single character shift (Code 128 character 98), which toggles between Subset A and Subset B, but only for the character right after the shift character. This case is handled in the last nested if statement. Finally, DrawBitmap() finishes up by drawing the previously calculated check digit, followed by the stop character.

There are 3 private member functions used in DrawBitmap(). The function CCode128::GetCheckDigit() calculates the Code 128 check digit for the message. CCode128::RetrievePattern() is basically a giant switch statement, retrieving the pattern for any Code 128 character passed to it. CCode128::DrawPattern() draws the pattern passed to it, the pattern is a CString in the form of “bssbbbsbbss” (the character '0' in Subset A and Subset B; the number 16 in Subset C) like the Code 128 character data mentioned above.

CCode128::GetCheckDigit() details

The source code for CCode128::GetCheckDigit() is listed below.

long CCode128::GetCheckDigit()
{
    long    nSum=0,nCurrentSubset=0,nCode128Char,nNextChar,nWeight,nCharacterPosition;

    // start character
    if (m_nCurrentSubset==SUBSETA)
    {
        nSum = 103;
        nCurrentSubset = SUBSETA;
    }
    else if (m_nCurrentSubset==SUBSETB)
    {
        nSum = 104;
        nCurrentSubset = SUBSETB;
    }
    else if (m_nCurrentSubset==SUBSETC)
    {
        nSum = 105;
        nCurrentSubset = SUBSETC;
    }

    // intialize the values
    nCharacterPosition = 0;
    nWeight = 1;

    while (nCharacterPosition<(m_csMessage.GetLength()))
    {
        // if SUBSETC
        if (nCurrentSubset==SUBSETC)
        {
            // if it's a switch to SUBSETA - same character in all subsets
            if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                            [m_csMessage.GetAt(nCharacterPosition)]==101)
            {
                // we're switching to subsetA
                nCode128Char = 101;
                
                // add the change subset character to the sum
                nSum+= (nWeight*nCode128Char);
                
                // we've moved one message character
                nCharacterPosition++;

                // we've moved one weight value
                nWeight++;

                // actually change the subset
                nCurrentSubset = SUBSETA;
            }
            // if it's a switch to SUBSETB - same character in all subsets
            else if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                        [m_csMessage.GetAt(nCharacterPosition)]==100)
            {
                // we're switching to subset B
                nCode128Char = 100;
                
                // add the change subset character to the sum
                nSum+= (nWeight*nCode128Char);
                
                // we've moved one message character
                nCharacterPosition++;

                // we've moved one weight value
                nWeight++;

                // actually switch the subset
                nCurrentSubset = SUBSETB;
            }
            // it's FNC1 - just print it out
            else if (g_nASCIItoCode128SubsetAB[SUBSETA]
                                         [m_csMessage.GetAt(nCharacterPosition)]==102)
            {
                // we're switching to subset B
                nCode128Char = 102;
                
                // add the change subset character to the sum
                nSum+= (nWeight*nCode128Char);
                
                // we've moved one message character
                nCharacterPosition++;

                // we've moved one weight value
                nWeight++;
            }
            // its a digit - process two at a time
            else
            {
                CString        csTemp;

                // get the next two characters
                csTemp = m_csMessage.Mid(nCharacterPosition,2);

                // convert them to longs
                nCode128Char = atol((const char *)csTemp);

                // add the weighted balue
                nSum += (nWeight*nCode128Char);

                // we've moved two message characters
                nCharacterPosition += 2;

                // we've moved one weight value
                nWeight++;
            }
        }
        // it's SUBSETA or SUBSETB
        else 
        {
            // handle upper ASCII characters if necessary
            long nTemp2 = m_csMessage.GetAt(nCharacterPosition);
            if (nTemp2<-1)
                nTemp2 = nTemp2&255;
            
            // retrieve the message character
            nCode128Char = g_nASCIItoCode128SubsetAB[nCurrentSubset][nTemp2];

            // add the weighted value to our sum
            nSum+= (nWeight*nCode128Char);

            // we've moved one character position
            nCharacterPosition++;

            // we've moved one weight value
            nWeight++;

            // if switch in SUBSETA
            if (nCurrentSubset==SUBSETA)
            {
                if (nCode128Char==100)
                    nCurrentSubset = SUBSETB;
                else if (nCode128Char==99)
                    nCurrentSubset = SUBSETC;
            }
            // if switch in SUBSETB
            else if (nCurrentSubset==SUBSETB)
            {
                if (nCode128Char==101)
                    nCurrentSubset = SUBSETA;
                else if (nCode128Char==99)
                    nCurrentSubset = SUBSETC;
            }
            // handle single character switch
            else if (nCode128Char==98)
            {
                // shift subsets for the next character only
                if (nCurrentSubset==SUBSETA)
                    nNextChar = g_nASCIItoCode128SubsetAB[SUBSETB]
                                                 [m_csMessage.GetAt(nCharacterPosition)];
                else
                    nNextChar = g_nASCIItoCode128SubsetAB[SUBSETA]
                                                 [m_csMessage.GetAt(nCharacterPosition)];

                // add weighted value to the sum
                nSum += (nWeight*nNextChar);

                // since we've handled two characters advance position and weight again
                nCharacterPosition++;
                nWeight++;
            }
        }
    }

    // return the modulus
    return (nSum%103);
}

The CCode::GetCheckDigit() function is very similar to CCode128::DrawBitmap(), except that when CCode128::DrawBitmap() would draw the character, CCode::GetCheckDigit() adds the weighted value to a running sum. CCode::GetCheckDigit() then returns the weighted sum modulus 103 as the check digit.

CCode128::DrawPattern() details

The CCode128::DrawPattern() function draws a single Code 128 barcode character in the passed device context. The CCode128::DrawPattern() function is listed below.

void CCode128::DrawPattern(CString csPattern)
{
    int            i,nXPixel,nYPixel;
    CDC            oDC;

    // attach to the device context
    oDC.Attach(m_hDC);

    // initialize X pixel value
    nXPixel = m_nStartingXPixel;
    
    for (i=0;i<csPattern.GetLength();i++)
    {
        // X value for loop
        for (nXPixel=m_nStartingXPixel;
             nXPixel<m_nStartingXPixel+m_nNarrowBarPixelWidth;
             nXPixel++)
        {
            // Y value for loop
            for (nYPixel=m_nStartingYPixel;
                 nYPixel<m_nStartingYPixel+m_nPixelHeight;
                 nYPixel++)
            {
                // if this is a bar
                if (csPattern.GetAt(i)=='b')
                    oDC.SetPixelV(nXPixel,nYPixel,COLORBLACK);
                else
                    oDC.SetPixelV(nXPixel,nYPixel,COLORWHITE);
            }
        }

        // advance the starting position
        m_nStartingXPixel+= m_nNarrowBarPixelWidth;
    }

    // detach from the device context
    oDC.Detach();
    
    return;
}

The CCode128::DrawPattern() function is basically three loops. The outermost loop loops thru every module in the passed pattern (bssbbbsbbss). The middle loop loops through every X pixel in the current module width. The innermost loop loops through every Y pixel in the current X pixel. In the center of the three loops is a simple if statement that determines if this module is a bar or a space, and sets the current pixel to black or white for a bar or space. This function is repeated for the start character, all the message characters, the check digit, and the stop character to draw the complete Code 128 barcode.

Summary

Thats it for drawing Code 128 barcodes. Some additions to the library I hope to make in the future involve message error-checking, and adding UPC,EAN, and 2D barcode symbologies. I hope you find this class library useful.

Reference

The Bar Code Book - A Comprehensive Guide To Reading, Printing, Specifying, and Applying Bar Code and Other Machine-Readable Symbols 4th Edition

By Roger C. Palmer

Copyright 1989,1991, 1995, 2001 by Helmers Publishing, Inc.

ISBN 0-911261-13-3

License

This article, along with any associated source code and files, is licensed under The BSD License

Share

About the Author

Neil Van Eps
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
QuestionLink is broken Pinmemberrulodepelo24-Jan-14 1:17 
Bugsource file link error Pinmemberyonjh18-Mar-13 1:01 
GeneralRe: source file link error PinmemberMember 100929862-Jul-13 8:54 
QuestionCode 128 with Application Identifiers PinmemberZacbr13-May-12 15:09 
GeneralMy vote of 5 Pinmembermanoj kumar choubey26-Feb-12 20:39 
GeneralPrint PinmemberRicardo Luceac25-Apr-08 4:16 
GeneralRe: Print PinmemberRoberto Italy7-May-08 21:39 
QuestionStart A code error? PinmemberRoberto Italy9-Sep-07 23:35 
AnswerRe: Start A code error? Pinmemberyfyuan5-May-08 18:07 
GeneralRe: Start A code error? PinmemberJinFeei23-Jun-08 1:23 
GeneralRe: Start A code error? Pinmembercodemind23-Nov-09 10:29 
GeneralHelp me Pinmembermedop823-May-07 1:58 
GeneralSpeed Optimization PinmemberMonty23-Apr-06 3:38 
GeneralMemory Leak PinmemberMonty23-Apr-06 0:15 
GeneralExcellant...! Pinmemberkolu narayan21-Feb-06 12:12 
GeneralBug when code is too short PinmemberDarvalo16-Feb-06 23:31 
GeneralChecksum wrong and not switching subsets PinmemberSimon Sweetman21-Dec-05 17:18 
GeneralC#.net or VB.net version PinmemberGangLIN27-Sep-05 16:42 
GeneralWrong StartA sequence :) Pinmembertaoo_27-Jul-05 23:34 
QuestionHow can I draw barcode in the full rectangle region Pinmemberdai_xiao_hua24-Jun-05 22:51 
QuestionEAN/UPC 128 anytime? PinmemberWilliam Yeung20-Sep-04 20:14 
AnswerRe: EAN/UPC 128 anytime? Pinmembertyc1008-Oct-04 10:52 
GeneralRe: EAN/UPC 128 anytime? Pinmemberhasanali007-Mar-05 23:27 
Generalwrong bar codes generated Pinmembermahesh kumar s6-Sep-04 6:32 
GeneralRe: wrong bar codes generated Pinmembersmjones25-May-05 10:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 15 Jun 2002
Article Copyright 2002 by Neil Van Eps
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid