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

Drawing Barcodes in Windows Part 5 - Code 128

0.00/5 (No votes)
14 Jun 2002 1  
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 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