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






4.79/5 (30投票s)
一篇关于将 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