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

在 Windows 中绘制条形码第四部分 - Code 93

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (7投票s)

2002 年 6 月 15 日

BSD

12分钟阅读

viewsIcon

91481

downloadIcon

140

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

引言

我最近在工作中有一个项目,需要将条形码字符写入一个旧式照相排版机的字体文件中。这段经历启发我开始了一个副项目,编写一些代码,在给定正确输入的情况下,在 Windows 屏幕上渲染条形码。本系列文章就是这个项目的成果。

Code 93 基础知识

第四篇文章将介绍如何在 Windows 屏幕上绘制 Code 93 条形码。在开始讨论代码之前,我们需要了解 Code 93 条形码符号的一些基本事实。Code 93 是一种字母数字代码,可以通过四种“移位”字符来编码所有 ASCII 字符。Code 93 字符由 3 个条和 3 个空组成,这些条和空由 9 个模块构成,每个模块可以是黑色或白色。由于所有字符都由相同的 9 个模块构成,因此实际上只需要定义一个元素宽度,即模块宽度。47 个数据字符以及起始/停止码的条/空模式如下所示。每个模式长 9 个字符(模块),可以是“b”(该模块是条)或“s”(该模块是空)。请注意,起始码和停止码相同,只是停止码增加了一个“终止条”。

字符

模式

0

bsssbsbss

1

bsbssbsss

2

bsbsssbss

3

bsbssssbs

4

bssbsbsss

5

bssbssbss

6

bssbsssbs

7

bsbsbssss

8

bsssbssbs

9

bssssbsbs

A

bbsbsbsss

B

bbsbssbss

C

bbsbsssbs

D

bbssbsbss

E

bbssbssbs

F

bbsssbsbs

G

bsbbsbsss

H

bsbbssbss

bsbbsssbs

J

bssbbsbss

K

bsssbbsbs

L

bsbsbbsss

M

bsbssbbss

N

bsbsssbbs

O

bssbsbbss

P

bsssbsbbs

Q

bbsbbsbss

R

bbsbbssbs

S

bbsbsbbss

T

bbsbssbbs

U

bbssbsbbs

V

bbssbbsbs

W

bsbbsbbss

X

bsbbssbbs

Y

bssbbsbbs

Z

bssbbbsbs

-

bssbsbbbs

句号

bbbsbsbss

空格

bbbsbssbs

$

bbbssbsbs

/

bsbbsbbbs

bsbbbsbbs

%

bbsbsbbbs

SHIFT1

bssbssbbs

SHIFT2

bbbsbbsbs

SHIFT3

bbbsbsbbs

SHIFT4

bssbbssbs

开始

bsbsbbbbs

STOP

bsbsbbbbsb

每个 Code 93 条形码都有两个校验字符(称为“C”和“K”),它们紧跟在停止字符之前。校验字符“C”是加权消息字符的模 47 和,消息字符从右到左的权重序列为 {1,2,3....19,20}。校验字符“K”是加权消息字符的模 47 和,消息字符从右到左的权重序列为 {1,2,...14,15},从之前计算出的校验字符“C”开始。校验位计算的示例如下,使用消息“DATA”。

Data

D

A

T

A

“C”

“K”

数据值

13

10

29

10



“C”权重

4

3

2

1



“K”权重

5

4

3

2

1


要计算“C”,首先找到乘积之和:(13*4)+(10*3)+(29*2)+(10*1) = 150。将 150 除以 47 得到 3,余数为 9。 “C”的值是 9,对应于数字 9。

要计算“K”,首先找到乘积之和:(13*5)+(10*4)+(29*3)+(10*2)+(9*1) = 221。将 221 除以 47 得到 4,余数为 33。 “K”的值是 33,对应于字符 'X'。

完整的 Code 93 条形码图片如下所示。

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

全 ASCII Code 93

该库实现了全 ASCII Code 93。下表显示了如何使用 47 个字符的 Code 93 字符集来生成所有 ASCII 字符。

ASCII 值

ASCII 字符

Code 93 序列

ASCII 值

ASCII 字符

Code 93 序列

ASCII 值

ASCII 字符

Code 93 序列

ASCII 值

ASCII 字符

Code 93 序列

0

NUL

<S2>U

32

空格

空间

64

@

<S2>V

96

`

<S2>W

1

SOH

<S1>A

33

!

<S3>A

65

A

A

97

a

<S4>A

2

STX

<S1>B

34

<S3>B

66

B

B

98

b

<S4>B

3

ETX

<S1>C

35

#

<S3>C

67

C

C

99

c

<S4>C

4

EOT

<S1>D

36

$

$

68

D

D

100

d

<S4>D

5

ENQ

<S1>E

37

%

%

69

E

E

101

e

<S4>E

6

ACK

<S1>F

38

&

<S3>F

70

F

F

102

f

<S4>F

7

BEL

<S1>G

39

'

<S3>G

71

G

G

103

g

<S4>G

8

BS

<S1>H

40

(

<S3>H

72

H

H

104

h

<S4>H

9

HT

<S1>I

41

)

<S3>I

73

105

i

<S4>I

10

LF

<S1>J

42

*

<S3>J

74

J

J

106

j

<S4>J

11

VT

<S1>K

43

+

+

75

K

K

107

k

<S4>K

12

FF

<S1>L

44

逗号

<S3>L

76

L

L

108

l

<S4>L

13

CR

<S1>M

45

连字符

Hyphen

77

M

M

109

m

<S4>M

14

SO

<S1>N

46

句号

周期

78

N

N

110

n

<S4>N

15

SI

<S1>O

47

/

/

79

O

O

111

o

<S4>O

16

DLE

<S1>P

48

0

0

80

P

P

112

p

<S4>P

17

DC1

<S1>Q

49

1

1

81

Q

Q

113

q

<S4>Q

18

DC2

<S1>R

50

2

2

82

R

R

114

r

<S4>R

19

DC3

<S1>S

51

3

3

83

S

S

115

s

<S4>S

20

DC4

<S1>T

52

4

4

84

T

T

116

t

<S4>T

21

NAK

<S1>U

53

5

5

85

U

U

117

u

<S4>U

22

SYN

<S1>V

54

6

6

86

V

V

118

v

<S4>V

23

ETB

<S1>W

55

7

7

87

W

W

119

w

<S4>W

24

CAN

<S1>X

56

8

8

88

X

X

120

x

<S4>X

25

EM

<S1>Y

57

9

9

89

Y

Y

121

<S4>Y

26

SUB

<S1>Z

58

:

<S3>Z

90

Z

Z

122

z

<S4>Z

27

ESC

<S2>A

59

;

<S2>F

91

[

<S2>K

123

{

<S2>P

28

FS

<S2>B

60

<

<S2>G

92

\

<S2>L

124

|

<S2>Q

29

GS

<S2>C

61

等于

<S2>H

93

]

<S2>M

125

}

<S2>R

30

RS

<S2>D

62

>

<S2>I

94

^

<S2>N

126

~

<S2>S

31

US

<S2>E

63

?

<S2>J

95

_

<S2>O

127

DEL

<S2>T

条形码位图工作区

条形码位图工作区中有三个不同的项目。第一个也是最重要的项目是 bblib 项目。这个项目是一个静态库,所有不同类型条形码的代码都存在于此。这也是本系列文章讨论的主要代码部分。条形码位图工作区的另一个项目是 bbdll 项目。这个项目只是一个常规的 DLL 包装器,围绕 bblib 静态库。条形码位图工作区的最后一个项目是 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 派生的任何类都必须实现这些函数。

CCode93 类

CCode93 类是用于绘制 Code93 条形码的类。类声明如下所示。

class CCode93 : public CBarcode  
{
public:
    CCode93();
    virtual ~CCode93();
    void BitmapToClipboard();
    void DrawBitmap();
private:
    void    ASCIItoCode93Sequence( long nASCIINumber,
                                    long *nFirstNumber, 
                                    long *nSecondNumber);
    void    DrawCheckDigits();
    void    DrawPattern(CString csPattern);
    CString    RetrievePattern( long c );
}

该类有两个公共函数 BitmapToClipboard()DrawBitmap(),此外它还继承了 CBarcode 类的 LoadData() 函数。使用该类的步骤很简单:声明该类的一个实例,调用 LoadData() 初始化类数据,然后调用 BitmapToClipboard() 将条形码的位图放入剪贴板,或者调用 DrawBitmap() 绘制条形码消息。

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

以下代码片段是使用 DrawBitmap(). 的示例。

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

// assign variable values here

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

将条形码绘制到剪贴板

以下代码片段是使用 BitmapToClipboard() 的示例。

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

// assign variable values here

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

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

CDC    memDC;
memDC.CreateCompatibleDC(NULL);

因此,BitmapToClipboard() 函数通过调用 memDC.CreateCompatibleDC(NULL) 函数来创建自己的内存设备上下文。快速查看 MSDN 文档会发现,如果将 NULL 值传递给 CreateCompatibleDC,则创建的设备上下文与屏幕兼容。

CBarcode::LoadData() 详细信息

CBarcode::LoadData() 的参数值得进一步解释,现在是合适的地方。第一个参数 csMessage 是您希望绘制为 Code 93 条形码的消息。下一个参数 dNarrowBar 是每个模块的宽度(以英寸为单位)。参数 dHeight 是条形码的高度(以英寸为单位)。参数 pDC 是将绘制条形码的设备上下文的句柄。接下来的两个参数 nStartingXPixelnStartingYPixel 定义了开始绘制条形码的坐标。最后一个参数 dRatio 是宽/窄元素宽度的比率,在 Code 93 条形码中没有用处。如果您查看 CBarcode::LoadData() 的声明,您会发现参数 dRatio 的默认值为 1.0。因此,在使用 LoadData() 进行 Code 93 条形码绘制时,您可以省略 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 93,比率始终为 1.0,并且成员变量 m_nWideBarPixelWidth 始终等于 m_nNarrowBarPixelWidth,并且不会被使用。

接下来,您可以计算最终的条形码像素宽度,此操作特定于符号,下面列出了 Code 39 的代码摘录。

nTemp = m_csMessage.GetLength();

m_nFinalBarcodePixelWidth = (((nTemp+4)*9)+1)*m_nNarrowBarPixelWidth;

此代码通过获取消息长度,加 4(起始码、“C”校验位、“K”校验位和停止码),将总和乘以 9(因为有 9 个模块),将该积加 1(用于停止码终止条),然后将最终总和乘以模块的像素宽度来计算 Code 93 条形码的宽度。

CCode93::DrawBitmap() 详细信息

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

void CCode93::DrawBitmap()
{
    long        i,nFirstNumber,nSecondNumber;
    CString        csCurrentPattern;

    // draw start character
    DrawPattern(RetrievePattern(47));
    
    // draw each character in the message
    for (i=0;i<m_csMessage.GetLength();i++)
    {
        // get current ASCII character
        ASCIItoCode93Sequence((long)m_csMessage.GetAt(i),
                               &nFirstNumber,&nSecondNumber);

        DrawPattern(RetrievePattern(nFirstNumber));
        if (nSecondNumber!=-1)
            DrawPattern(RetrievePattern(nSecondNumber));
    }

    // add the check digit characters
    DrawCheckDigits();

    // draw stop character
    DrawPattern(RetrievePattern(48));

    return;
}

CCode93::DrawBitmap() 函数首先绘制起始码。然后代码遍历消息中的每个字符,通过检索绘制该消息字符所需的 Code 93 字符(记住这可能是一个或两个 Code 93 字符)来绘制字符。然后绘制两个校验位,最后是停止字符。DrawBitmap() 中使用了 4 个私有成员函数。ASCIItoCode93Sequence() 函数在两个变量 nFirstNumber 和 nSecondNumber 中返回绘制传入的 ASCII 字符所需的两个数字。如果当前消息字符可以用一个 Code 93 字符绘制,则 nSecondNumber 变量设置为 -1,并且 if 语句会跳过绘制第二个 Code 93 字符。CCode93::RetrievePattern() 基本上是一个巨大的 switch 语句,用于检索传递给它的任何 Code 93 字符的模式。CCode93::DrawPattern() 绘制传递给它的模式,该模式是一个 CString,形式为“bsssbsbss”(字符 '0'),如上面提到的 Code 93 字符数据。.

CCode93::DrawPattern() 详细信息

CCode93::DrawPattern() 函数在传递的设备上下文中绘制单个 Code 93 条形码字符。下面是 CCode93::DrawPattern() 函数的列表。

void CCode93::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;
}

CCode93::DrawPattern() 函数基本上是三个循环。最外层的循环遍历传递的模式(bsssbsbss)中的每个模块。中间的循环遍历当前模块宽度中的每个 X 像素。最里面的循环遍历当前 X 像素中的每个 Y 像素。三个循环的中心是一个简单的 if 语句,它决定该模块是条还是空,并为条或空将当前像素设置为黑色或白色。此函数会为起始字符、所有消息字符、两个校验位和停止字符重复执行,以绘制完整的 Code 93 条形码。

摘要

这就是绘制 Code93 条形码的内容。本系列的 第五部分 将介绍绘制 Code 128 条形码。希望您发现这个类库很有用。

参考

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

作者:Roger C. Palmer

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

ISBN 0-911261-13-3

© . All rights reserved.