65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Windows 中绘制条形码第五部分 - Code 128

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (30投票s)

2002 年 6 月 15 日

BSD

14分钟阅读

viewsIcon

280561

downloadIcon

908

一篇关于将 Code 128 条形码绘制到屏幕或剪贴板的文章。

引言

最近在工作中的一个项目要求我将条码字符写入一个用于旧式照相排版机的字体文件中。这次经历启发我开始一个业余项目,编写一些代码,在给定输入的情况下,在 Windows 屏幕上渲染条码。这一系列文章就是该项目的成果。

Code 128 基础

Code 128 是一种密度非常高的字母数字码,于 1981 年推出,并被广泛应用于各种场合。Code 128 字符由 3 个条和 3 个空组成,它们由 11 个模块构建而成,每个模块可以是黑色或白色。因为所有字符都由相同的 11 个模块构成,所以实际上只需要定义一个元素宽度,即模块宽度。下面列出了 107 个数据字符以及开始/停止码的条/空模式。每个模式长 11 个字符(模块),要么是 'b'(该模块是条),要么是 's'(该模块是空)。请注意,Code 128 有三个子集,子集 A 和 B 覆盖了 ASCII 字符集,而子集 C 是一个双密度纯数字子集。三个不同的起始字符告诉条码读取器哪个子集是起始子集,而三个切换字符允许在 Code 128 条码内部更改子集。

代码 A

代码 B

代码 C

模式

空间

空间

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

等于

等于

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

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

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

切换代码 C

切换代码 C

99

99

bsbbbsbbbbs

切换代码 B

FNC4

切换代码 B

100

bsbbbbsbbbs

FNC4

切换代码 A

切换代码 A

101

bbbsbsbbbbs

FNC1

FNC1

FNC1

102

bbbbsbsbbbs

起始代码 A

起始代码 A

起始代码 A

103

bbsbsbbbbss

起始代码 B

起始代码 B

起始代码 B

104

bbsbssbssss

起始代码 C

起始代码 C

起始代码 C

105

bbsbssbbbss

STOP

STOP

STOP

106

bbsssbbbsbsbb

每个 Code 128 条码都有一个校验位,它紧跟在停止符之前。校验位是数据字符的加权和,模 103。数据字符从左到右按无限序列 {1,2,3,...} 进行加权。下面使用消息 "DATA" 展示了一个校验位计算的例子。

起始 A

D

A

T

A

校验位

停止


36

33

52

33




1

2

3

4



要计算校验位,首先计算乘积之和: (36*1)+(33*2)+(52*3)+(33*4) = 390。将 390 除以 103 得到 3,余数为 81。校验位的值是 81,对应 ASCII 字符 DC1。下面显示了整个条码的图片。

请注意,条码读取器使用校验位来解码条码,但不会传输它们。

条形码位图工作区

Barcode Bitmap 工作区中有三个不同的项目。第一个也是最重要的项目是 bblib 项目。这个项目是一个静态库,其中包含了绘制所有不同类型条码的代码。这也是本系列文章讨论的主要代码。Barcode Bitmap 工作区中的另一个项目是 bbdll 项目。这个项目只是 bblib 静态库的一个常规 DLL 包装器。Barcode Bitmap 工作区中的最后一个项目是 DLL 客户端项目。这个项目是一个简单的基于对话框的应用程序,它调用 bbdll DLL 在对话框中绘制条码,或将条码作为 Windows 位图放到剪贴板上。

基类 CBarcode

本系列文章中讨论的所有条码类型的基类是 CBarcode 类。该类的声明如下所示。

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;
};

关于 CBarcode 类,有几点需要注意。首先,它包含的数据成员包含了绘制条码消息所需的所有有用数据。这些数据包括窄元素像素宽度、宽元素像素宽度、消息和码制。其次,该类的数据成员包含了如何输出条码消息的信息。这些数据包括设备上下文句柄以及起始的 X 和 Y 像素坐标。第三,该类有一些公共成员函数,用于通过加载数据来初始化类,并获取有关条码消息的信息,即其像素高度和宽度。第四,该类有几个抽象成员函数,使这个类成为一个抽象基类。任何从 CBarcode 派生的类都应实现这些函数。

CCode128 类

CCode128 类是用于实现绘制 Code 128 条码的类。该类的声明如下所示。

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;
};

该类有三个公共函数:BitmapToClipboard()、DrawBitmap() 和 LoadData()。该类还有一个数据成员 m_nCurrentSubset,它在绘制条码时保存当前子集,并被初始化为起始子集。使用该类的步骤很简单:声明一个类的实例,调用 LoadData() 来初始化类数据,然后如果要将条码的位图放到剪贴板上,就调用 BitmapToClipboard(),或者调用 DrawBitmap() 来绘制条码消息。

将条形码绘制到设备上下文

以下代码片段是使用 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();

将条形码绘制到剪贴板

以下代码片段是使用 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();

请注意,在使用 BitmapToClipboard() 函数时,你可以在 LoadData() 调用中传递一个空的设备上下文句柄,并将起始 X 和 Y 像素设为零。显然,起始 X 和 Y 像素在剪贴板上没有意义,但是空的设备上下文句柄呢?这个问题的答案可以在 BitmapToClipboard() 函数的这段代码片段中找到。

CDC    memDC;
memDC.CreateCompatibleDC(NULL);

所以 BitmapToClipboard() 函数通过使用 memDC.CreateCompatibleDC(NULL) 函数调用来创建自己的内存设备上下文。快速查看 MSDN 文档可以发现,如果向 CreateCompatibleDC 传递一个 NULL 值,创建的设备上下文将与屏幕兼容。

CCode128::LoadData() 详解

CCode128::LoadData() 的代码如下所示。
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;
}

如你所见,CCode128::LoadData() 的大部分功能来自基类函数 CBarcode::LoadData(),下面将对其进行讨论。使得这个函数变得必要的是 Code 128 的特定属性 m_nCurrentSubset,它被初始化为您正在绘制的 Code 128 条码的起始子集。

CBarcode::LoadData() 详细信息

CBarcode::LoadData() 的参数值得进一步解释,这里似乎是进行解释的好地方。第一个参数 csMessage 就是你希望绘制成 Code 128 条码的消息。下一个参数 dNarrowBar 是每个模块的宽度,单位是英寸。参数 dHeight 是条码的高度,单位是英寸。参数 pDC 是将要绘制条码的设备上下文的句柄。接下来的两个参数 nStartingXPixel 和 nStartingYPixel 定义了开始绘制条码的坐标。最后一个参数 dRatio 是宽/窄元素的宽度比,在 Code 128 条码中没有用处。如果你查看 CBarcode::LoadData() 的声明,你会发现参数 dRatio 的默认值为 1.0。因此,当为 Code 128 条码使用 LoadData() 时,你可以直接省略 dRatio 参数。如果你还记得上面 CBarcode 类的声明,你会记得它以像素为单位存储所有的宽度和高度信息,并且它存储的是窄元素宽度和宽元素宽度,而不是窄元素宽度和宽/窄元素宽度比。显然,CBarcode::LoadData() 在后台做了一些转换工作。

转换工作的第一步是获取 X 轴和 Y 轴的 dpi,这是通过以下代码完成的,摘自 CBarcode::LoadData()。

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

一旦有了 X 和 Y 轴 DPI,就可以计算像素高度、窄元素像素宽度和宽元素像素宽度,如下面的代码片段所示。

// 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);

注意在计算窄元素像素宽度和宽元素像素宽度时的舍入效应。窄元素宽度的下限是一个像素,所以你能生成的条码受到输出设备物理限制的制约。还要注意,对于 Code 128,比率将始终为 1.0,成员变量 m_nWideBarPixelWidth 将始终等于 m_nNarrowBarPixelWidth,并且不会被使用。

接下来你可以计算最终的条码像素宽度,这个操作是特定于码制的,下面列出了 Code 128 的代码摘录。

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

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

这段代码通过将消息长度乘以 11(每个字符 11 个模块),加上 35(起始符、校验位和停止符加上 2 个模块的终止条),然后将总和乘以模块宽度,来计算 Code 128 条码的宽度,从而得到总的条码像素宽度。

CCode128::DrawBitmap() 详解

DrawBitmap() 函数是绘制每个消息字符的地方。下面列出了 CCode128::DrawBitmap() 函数。

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;
}

CCode128::DrawBitmap() 函数首先使用 CCode128::GetCheckDigit() 计算校验位。然后,它根据当前子集值 m_nCurrentSubset 绘制正确的起始符。接着,代码遍历消息中的每个字符,并通过检索绘制该消息字符所需的 Code 128 字符来绘制它们。

在 CCode128::DrawBitmap() 的 while 循环的每一次遍历中,一个 if 语句会执行两段不同的代码,一段用于子集 C,另一段用于子集 A 和子集 B。如果当前子集是子集 C,则该 if 分支中的代码会为下一个消息字符执行。

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;
    }
}

如果你在子集 C 中,有四种情况是合法的。下一个字符可以将你切换到子集 A,下一个字符可以将你切换到子集 B,下一个字符是 FNC1(功能键表示读取器特定的行为),或者下一个字符是数字。如果下一个字符是数字,你需要取出接下来的两个字符来确定要绘制哪个 Code 128 字符(记住子集 C 的双密度纯数字特性)。

如果当前子集是子集 A 或子集 B,则 else 分支中的代码会为下一个消息字符执行。

// 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++;
    }
}

二维数组 g_nASCIItoCode128SubsetAB[2][207] 用于将 ASCII 消息字符转换为 Code 128 子集 A 和子集 B 的字符。数组的第一个索引是子集,SUBSETA 或 SUBSETB,第二个索引是当前消息字符的 ASCII 值。这个位置的值就是当前消息字符在当前子集中的 Code 128 字符。该数组的声明在 CCode128.h 中,如下面的代码片段所示。

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}};

请注意,子集 A 和 B 中的特殊字符,即字符 95 到 106(所有起始符、FNC1 到 FNC4、所有切换符和停止符),通过将 Code 128 字符加上 100 来移位到较高的 ASCII 字符。因此,这些字符在消息中使用 ASCII 195 到 206 来指定。没有等效 Code 128 字符的 ASCII 字符用 -1 表示。现在代码使用 DrawPattern() 绘制检索到的 Code 128 字符。然后,代码使用一个 if 语句来处理切换子集的字符,类似于子集 C 代码的处理方式。一个新情况是单字符切换(Code 128 字符 98),它在子集 A 和子集 B 之间切换,但仅对切换符紧随其后的那个字符有效。这种情况在最后一个嵌套的 if 语句中处理。最后,DrawBitmap() 通过绘制先前计算的校验位,然后是停止符来完成绘制。

DrawBitmap() 中使用了 3 个私有成员函数。函数 CCode128::GetCheckDigit() 计算消息的 Code 128 校验位。CCode128::RetrievePattern() 基本上是一个巨大的 switch 语句,用于检索传递给它的任何 Code 128 字符的模式。CCode128::DrawPattern() 绘制传递给它的模式,模式是一个 CString,格式为“bssbbbsbbss”(在子集 A 和子集 B 中是字符 '0';在子集 C 中是数字 16),就像上面提到的 Code 128 字符数据一样。

CCode128::GetCheckDigit() 详解

CCode128::GetCheckDigit() 的源代码如下所示。

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);
}

CCode::GetCheckDigit() 函数与 CCode128::DrawBitmap() 非常相似,不同之处在于,当 CCode128::DrawBitmap() 会绘制字符时,CCode::GetCheckDigit() 会将加权值添加到一个运行总和中。然后 CCode::GetCheckDigit() 返回加权和模 103 的结果作为校验位。

CCode128::DrawPattern() 详解

CCode128::DrawPattern() 函数在传入的设备上下文中绘制单个 Code 128 条码字符。CCode128::DrawPattern() 函数如下所示。

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;
}

CCode128::DrawPattern() 函数基本上是三个循环。最外层的循环遍历传入模式(bssbbbsbbss)中的每个模块。中间的循环遍历当前模块宽度中的每个 X 像素。最内层的循环遍历当前 X 像素中的每个 Y 像素。在三个循环的中心是一个简单的 if 语句,它判断当前模块是条还是空,并相应地将当前像素设置为黑色或白色。这个函数会对起始符、所有消息字符、校验位和停止符重复执行,以绘制出完整的 Code 128 条码。

摘要

关于绘制 Code 128 条码的内容就到此为止了。我希望将来能对这个库做一些补充,包括消息错误检查,以及添加 UPC、EAN 和二维条码码制。希望您觉得这个类库有用。

参考

条形码书籍 - 读取、打印、指定和应用条形码及其他机器可读符号的综合指南 第 4 版

作者:Roger C. Palmer

版权所有 1989, 1991, 1995, 2001 Helmers Publishing, Inc.

ISBN 0-911261-13-3

© . All rights reserved.