Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

A C++ Class for Generating Bar Codes


JUL94: A C++ Class for Generating Bar Codes

A C++ Class for Generating Bar Codes

Outputting bar codes on PCL laser printers

Douglas Reilly

Doug owns Access Microsystems, a software-development house specializing in C/C++ software development. He is also the author of the BTFILER and BTVIEWER Btrieve file utilities. Doug can be contacted at 404 Midstreams Road, Brick, NJ 08724, or on CompuServe at 74040,607.


The application of bar-code technology extends far beyond scanning cans of peas in your local grocery store. In many industrial applications, bar codes have become essential for tracking items during production. Bar-code readers can now scan items as they quickly move down production lines.

With the advent of relatively inexpensive lasers that can scan from some distance, many areas of the transportation industry use bar codes to track trucks, trailers, and containers. Federal Express and United Parcel Service are using both stationary and mobile bar-code readers to track packages. In short, any application that depends upon accurate entry of information with little or no user intervention is a candidate for bar codes. Identity-verification applications can also use bar codes, although magnetic striping and other technologies are more secure: An identification bar code can be photocopied. To thwart this, such bar codes can be printed on a red background, which allows the bar code to be read but not copied.

Bar-Code Properties

Roger Palmer's The Bar Code Book, second edition (Helmers Publishing, 1991) lists no fewer than 17 different types, or "symbologies," of bar codes, all of which have several common properties. Each has a character set that's usually a subset of the ASCII character set. Some are strictly numeric, and others allow alphabetic and punctuation symbols. Some use characters separated by white space, while others are continuous, with no white space between characters. Continuous symbologies are more common.

Some symbologies use only two element widths: narrow and wide. Others use a variety of widths. Overall symbol width can be fixed or variable. Version A of the Universal Product Code (UPC) always encodes a fixed number of digits. Another symbology, Code 128, can encode any number of characters. Many symbologies have a check character. If this check character is limited to 0 through 9, it's called a "check digit." Most (though not all) symbologies are bidirectional; that is, they can be scanned left to right or right to left.

The hardware used to read a bar code can also impact the type of bar-code symbology used. Most bar-code readers are laser scanners, which have the advantage of being able to read a bar code from some distance. However, laser scanners are relatively expensive and not very portable. Relatively low-cost contact scanners are available, but the scanner must come in contact with the bar code during scanning. This can be difficult when the object being scanned is not flat (a can, for instance) or when contact requires the cooperation of a living thing (a cow in a cattle yard or a hospital patient). For contact scanners especially, a compact bar-code symbology is preferred.

Types of Bar Codes

The most familiar bar-code symbology is UPC version A. UPC is different from many symbologies in that it is a coding system as well as a symbology: The data encoded with UPC symbology in itself conveys some meaning.

Each UPC encodes 12 digits (UPC encodes digits only), divided into two sections of six digits each, surrounded by left, center, and right guard patterns. The first digit is a "number-system digit." Table 1 defines this digit. The next five digits are a manufacturer's code, the next five are the actual product code, and the last is a check digit. Check digits are also useful for applications where users enter numeric codes into any device that lacks the ability to validate the value directly. Check digits are designed to catch common data-entry errors, such as digit transpositions. Figure 1 shows how to calculate the check digit for UPC numbers.

Although it is considered a symbology, postnet (those long and short lines on the bottom of mail) does not meet the strictest definition of a bar code, since information is not encoded by the width of bars. Postnet is a "clocked" technology: The bottom section of the bars help the scanner track the code. The postnet symbology includes a single tall bar as a start and stop code and often a check digit calculated such that the sum of all data digits and the check digits mod 10 is 0.

Another common symbology is Code 128, which has an advantage over the previously discussed codes: It encodes alphabetic and punctuation characters as well as digits. Code 128 can also encode two digits into a single character, in the same fashion that binary-coded decimal (BCD) can pack two digits into a single 8-bit byte. Code 128 uses three distinct character sets and "shift" characters to switch from one to the other. The most useful character sets are set A (digits, alphabetic characters, and most punctuation) and set C (which includes 100 codes to encode "00" through "99" in a single digit).

Building Code-128 Bar Codes

Figure 2 is a Code-128 bar code that, when scanned by the appropriate reader, will decode as the text "CODE 128." Each digit contains three bars and three spaces, with an overall length of 11 modules for each character. There are not absolute tolerances for the width of the bars and spaces, but rather tolerances related to the width of a single module.

The width of a single module (call it "x") must be 7.5 mils. The minimum bar height is 15 percent of the total length of the symbol, or 0.25 inches, whatever is greater. Code 128 requires a "quiet zone" of white space on either side of the actual code that is ten times the value of x. The width between the beginning of one black area to the next, or the beginning of one white area to the next, must be within ±0.20x of the expected starting point.

For the purposes of this discussion, keep in mind that using an x of three dots on a 300 DPI laser printer produces Code 128 symbols that I've always found readable, even when laminated in not-entirely clear plastic.

C++ Classes for PCL Printer Access

To create Code-128 symbols on a PCL 5-compatible laser printer, I wrote the PCL class declared in PCL.H (Listing One). Listing Two is PCL.CPP, the implementation of the class. As with all C++ classes, my goal was to make as much of the operation of the class as automatic and self contained as possible. To that end, the constructor and destructor handle virtually all the housekeeping for the class.

PCL is the page-description language all HP laser printers (and many laser printers from other vendors) use to control the output. Unlike controlling a dot-matrix printer, a page-description language allows access to anywhere on the page, much like screen handling under MS-DOS. Unlike screen handling, however, there's little feedback from the printer. Simple questions like, "Where is the cursor?" cannot be answered using PCL alone. For this reason, several variables in the class are used as state variables. These record the current x and y position; the current font; and font size, symbol set, and intensity settings.

Note that the x and y positions are not always accurate for the x dimension because the class as it exists has no way of determining the width of a character if it is part of a proportional font. A possible enhancement to the class would be inclusion of a font-metrics call that would allow the class to always know the current cursor position, as well as allowing proportional fonts to be centered.

The constructor's first task is to open a file (with a stdio.h C-style FILE *) using the filename passed to the constructor, or LPT1 by default. Production code should also add a test for device availability if the "filename" passed is actually LPTn, where n is the digit 1 through 3. Next, use the moveto() member function to position the cursor (to the position where actions, by default, occur). Like all C++ classes, constructors do not return a value. After construction and before the first use, the object can be tested by casting the object to a FILE * and testing for NULL.

All actual output to the file, including the codes required to move cursor positions and draw lines, comes through the outtext() member function. This function would be the ideal place for centralized printer-status testing. The lineto() member function is central to the business of drawing bar-code symbols, so it deserves some explanation. PCL has no native support for drawing lines. Horizontal and vertical lines can be drawn using the rectangle-drawing functions. All lines are simply one-dot-wide or one-dot-high rectangles. The lineto() function has no support for lines other than horizontal or vertical.

The pushPos() and popPos() member functions can be used to save and restore cursor position before and after performing some action. These functions use a PCL command to save the position, not relying upon x and y stored by the class. Other member functions set font values and manipulate the various state variables.

The destructor simply calls reset() (which issues a PCL reset command) and closes the output file.

Using the PCL Class to Print Code-128 Bar Codes

The code to print the Code-128 bar code is CODE128.CPP (which, along with executables and sample data, is available electronically, see "Availability," page 3). The bulk of the code initializes an array that controls the lines and spaces that make up the bar code. The CODE128 structure is made up of an integer value that holds the actual character value for each code for character-set A, and a character array that holds 1 for a line and 0 for a space for each of the 11 modules of a Code-128 bar code. Note that each code ends with a 0 as the 11th module in the code. The character values for "unprintable" characters (FF, CR, and so on) are represented as single spaces, since for any application I've had, these characters haven't been required.

The printCode128Str() function takes a string; x-, y-, and height coordinates; and a reference to a PCL object. First, a start character is printed using printCode128Ch(). Each of the 11 modules is made up of a three-dot-wide line or space. While three dots is the minimum, the maximum width is up to you, since Code 128 scales up nicely. For each data character, I loop through the array of CODE-128 structs to find the correct value and then print it using printCode128Ch(). Finally, I calculate a check digit. The Code 128 check digit is different than the UPC check digit mentioned previously. The data-character values are added together, plus 103 for the start character, and the total mod 103 is the check digit.

CODE123.CPP uses the PCL class to create bar codes from a list of text lines in a file whose name is passed as argument 1. To display the text of the code under the actual bar-code symbol, use a non-proportional font (compressed or lineprinter, in this case) and calculate the width of the text that's printing so it can center within the bar code.

Conclusion

Production code should have error handling of cases where the printer is not available. A DOS critical-error handler will handle most such errors, but a BIOS level check for printer status can also be useful if outputting directly to a printer.

Another useful addition to the PCL class would be the previously mentioned font-metrics member function that would allow you to determine the width of a string of proportional-font characters. The ability to draw other elements besides horizontal and vertical boxes would also be useful. The printCode128Str() function could be enhanced to take advantage of character-set C of Code 128, so strings of four or more characters could be compressed as two digits per 11 module symbol.

Table 1: UPC system digits. (Source: The Bar Code Book, second edition, Roger C. Palmer, Helmers Publishing, 1991.)

    Digit   Meaning

    0       92,000 manufacturer numbers, 8,000 locally assigned numbers
    1       Reserved
    2       Random-weight consumer packages
    3       Drug products
    4       In-store marking without format
    5       UPC coupons
    6       Manufacturer ID numbers
    7       Manufacturer ID numbers
    8       Reserved
    9       Reserved

Figure 1: Calculating the check digit for UPC numbers. Use the number-system digit (in this example, 0), the five-digit manufacturer's code (49200), and the product code (05675). Thanks to Paul Pazniokas at Posse Inc.

<B>Step 1</B>   Start at the right, sum up digits in the odd position:
 0      4      9      2      0      0      0      5      6      7      5
 0      +      9      +      0      +      0      +      6      +      5 = 20
<B>Step 2</B>   Multiply the value from Step 1 by 3:
             20 * 3 = 60
<B>Step 3 </B>  Sum up the digits in the even position:
 0      4      9      2      0      0      0      5      6      7      5
 4      +      2      +      0      +      5      +      7      =     18
<B>Step 4</B>   Add the results of Steps #2 and #3:
             60 + 18 = 78
<B>Step 5</B>   The check digit is the smallest number that produces a multiple of 10 when added to the result of Step #4 (in this case, 2).

Figure 2 A Code-128 bar code that decodes as "CODE 128."

Listing One



#ifndef PCL_H
#define PCL_H

// just two of MANY symbol sets
#define SYMSET_ROMAN8    8
#define SYMSET_PC8      10

// Just 4 of MANY typefaces.
#define TYPEFACE_CGTIMES  4101
#define TYPEFACE_UNIVERS  4148
#define TYPEFACE_LINEPRT     0
#define TYPEFACE_COURIER     3

class PCL {
   int x;
   int y;
   int curPoints;
   int curTypeface;
   int curBold;
   int curSymset;
   int curProp;
   int curPitch;
   char dest[120];
   FILE *out;
public:
  // dest is destination of printout...
   PCL(char *tdest);
  // destructor
   ~PCL();
  // The next two functions look lots like Borland's BGI interface.
  //    This is an intentional modeling.
   int moveto(int newX,int newY);
   int lineto(int destX,int destY);
   void outtextxy(int newX,int newY,char *text);
   void outtext(char *text);
   void setFont(int proportional,int symSet,int points,int bold,int typeface);
   void reset()
   {
      char temp[10];
      if ( out )
      {
         sprintf(temp,"%cE",27);
         outtext(temp);
      }
   }
   void bold(int boldNum=3)
   {
      setFont(curProp,curSymset,curPoints,boldNum,curTypeface);
   }
   void noBold()
   {
      setFont(curProp,curSymset,curPoints,0,curTypeface);
   }
   int getBold()
   {
      return(curBold);
   }
   void compressed()
   {
      char temp[40];
      setFont(0,SYMSET_PC8,10,0,TYPEFACE_LINEPRT);
      if ( out )
      {
         sprintf(temp,"%c&k2S",27);
         outtext(temp);
      }
   }
   void normal()
   {
      char temp[40];
      setFont(0,SYMSET_PC8,12,0,TYPEFACE_COURIER);
      if ( out )
      {
         sprintf(temp,"%c&k0S",27);
         outtext(temp);
      }
   }
   int rectangle(int left,int right,int top,int bottom,int shade);
   int box(int left,int right,int top,int bottom);
   void popPos()
   {
      char temp[40];
      if ( out )
      {
         sprintf(temp,"%c&f1S",27);
         outtext(temp);
      }
   }
   void pushPos()
   {
      char temp[40];
      if ( out )
      {
         sprintf(temp,"%c&f0S",27);
         outtext(temp);
      }
   }
   int fontHeight();
   void setLPI(int lpi)
   {
      char temp[40];
      if ( out )
      {
         sprintf(temp,"%c&l%dD",27,lpi);
         outtext(temp);
      }
   }
   void setVMI(int vmi)
   {
      char temp[40];
      if ( out )
      {
         sprintf(temp,"%c&l%dC",27,vmi);
         outtext(temp);
      }
   }
   void setPitch(int pitch)
   {
      char temp[40];
      if ( out!=0 )
      {
         curPitch=pitch;
         if ( pitch==12 )
         {
            sprintf(temp,"%c&k2S",27);
         }
         else if ( pitch==10 )
         {
            sprintf(temp,"%c&k0S",27);
         }
         else if ( pitch==16 )  // really 16.5 to 16.7, but use an int.
         {
            sprintf(temp,"%c&k4S",27);
         }
         outtext(temp);
         sprintf(temp,"%c(s%dH",27,pitch);
         outtext(temp);
      }
   }
  // just in case you really do want to violate encapsulation...
   operator FILE *()
   {
      return(out);
   }
};
#endif


Listing Two


#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "pcl.h"        // my header that describes the PCL class

PCL::PCL(char *tdest)
{
   if ( tdest==0 )
   {
      strcpy(dest,"LPT1");
   }
   else
   {
      strcpy(dest,tdest);
   }
  // out will be 0 if dest can't be opened.
   out=fopen(dest,"a+b");
   if ( out!=0 )
   {
      char temp[20];
      moveto(0,0);
     // For these and all control codes, refer to the Owner's Manual of a
     //    PCL 5 compatible laser printer.
      sprintf(temp,"%c&l0O",27);
      outtext(temp);
   }
}
PCL::~PCL()
{
   if ( out!=0 )
   {
      reset();
      fclose(out);
      out=0;
   }
}
void PCL::outtextxy(int newX,int newY,char *text)
   {
      moveto(newX,newY);
      outtext(text);
   }
// EVERYTHING goes through here.  Even member functions that could access the
//   FILE * directly use outtext() so that changes to underlying implementation
//   (use the streams IO, etc.) are transparent to all external and internal
//   functions, except the constructor, destructor, and outtext.
void PCL::outtext(char *text)
   {
      if ( out!=0 )
      {
         fprintf(out,"%s",text);
      }
   }
void PCL::setFont(int proportional,int symSet,int points,int bold,int typeface)
{
   if ( out )
   {
      char temp[40];
      sprintf(temp,"%c(s%dU",27,symSet);
      outtext(temp);
      sprintf(temp,"%c(s%dP",27,proportional);
      outtext(temp);
      sprintf(temp,"%c(s%dV",27,points);
      outtext(temp);
      sprintf(temp,"%c(s%dB",27,bold);
      outtext(temp);
      sprintf(temp,"%c(s%dT",27,typeface);
      outtext(temp);
     // set the state variables...
      curPoints=points;
      curSymset=symSet;
      curTypeface=typeface;
      curBold=bold;
      curProp=proportional;
   }
}
int PCL::box(int left,int right,int top,int bottom)
{
   if ( left>right || top>bottom )
   {
      return(0);
   }
   moveto(left,top);
   lineto(left,bottom);
   lineto(right,bottom);
   moveto(right,top);
   lineto(right,bottom);
   moveto(left,top);
   lineto(right,top);
   x=right;
   y=top;
   return(1);
}
int PCL::lineto(int destX,int destY)
   {
      int top,bottom,left,right;

      top=destY;
      bottom=y;
      left=destX;
      right=x;
      if ( top>bottom )
      {
         int t;
         t=top;
         top=bottom;
         bottom=t;
      }
      if ( left>right )
      {
         int t;
         t=left;
         left=right;
         right=t;
      }
      if ( out!=0 )
      {
         char temp[40];
         moveto(left,top);
         if ( left==right )
         {
            sprintf(temp,"%c*c1a%db0P",27,bottom-top);
         }
         else
         {
            sprintf(temp,"%c*c%da1b0P",27,right-left);
         }
         outtext(temp);
         moveto(destX,destY);
         x=destX;
         y=destY;
      }
      return(0);
   }
int PCL::moveto(int newX,int newY)
   {
      if ( out!=NULL )
      {
         char temp[40];
         sprintf(temp,"%c*p%dx%dY",27,newX,newY);
         outtext(temp);
         x=newX;
         y=newY;
      }
      return(0);
   }

int PCL::rectangle(int left,int right,int top,int bottom,int shade)
{
   moveto(left,top);
   if ( out )
   {
      char temp[40];
      sprintf(temp,"%c*c%da%dB%c*c%dg2P",27,(right-left),(bottom-top),27,shade);
      outtext(temp);
   }
   moveto(left,top);
   return(0);
}
int PCL::fontHeight()
{
   double tFloat;
   tFloat=(double)curPoints/72.0;
   tFloat*=300.0;
   return((int)tFloat);
}

Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.