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






4.36/5 (7投票s)
一篇关于将 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
是将绘制条形码的设备上下文的句柄。接下来的两个参数 nStartingXPixel
和 nStartingYPixel
定义了开始绘制条形码的坐标。最后一个参数 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