Drawing Barcodes in Windows Part 5 - Code 128






4.79/5 (30 votes)
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