在 VB.NET 和 C# 中使用 Code39 条形码





5.00/5 (16投票s)
本文将介绍如何在 VB.NET 和 C# 中创建 Code39 条形码
引言
本文的目的是创建一个简单的类,该类将根据输入的字符串生成 Code 39 条形码图像。
背景
我不会解释什么是 Code 39 条形码。如果您正在阅读此说明,很可能您已经知道它是什么。但为了唤起大家的记忆,根据维基百科,“Code 39(也称为 Alpha39、Code 3 of 9、Code 3/9、Type 39、USS Code 39 或 USD-3)是一种可变长度的离散条形码符号体系。” 仍根据维基百科上的信息,Code 39 规范定义了 43 个字符,包括:
- 大写字母 (A 到 Z)
- 数字 (0 到 9)
- 以及一些特殊字符 (-, ., $, /, +, %, 和空格)。
- 一个额外的字符(用“*”表示)用于开始和停止分隔符。
每个字符由九个元素组成:五个条和四个空。每个字符中的九个元素中有三个是宽的(二进制值 1),六个是窄的(二进制值 0)。例如,字母“D”编码为
D=bwbwBWbwB
其中 b 和 w 分别代表窄条和窄空;B 和 W 代表宽条和宽空。窄与宽的宽度比可在 1:2 到 1:3 之间选择。
全 ASCII Code 39
虽然“纯”Code 39 条形码仅限于上述 43 个字符,但 Code 39 有一个扩展,允许表示所有 ASCII 字符。此扩展称为全 ASCII Code 39。其中,符号 0-9、A-Z、“.”、“-”和空格与其在 Code 39 中的表示相同。小写字母、附加标点符号和控制字符通过 Code 39 的两个字符序列表示(参见下表)。
<!-- 行 # 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 -->编号 | 字符 | 编码 | 编号 | 字符 | 编码 | |
0 | NUL | %U | 64 | @ | %V | |
1 | SOH | $A | 65 | A | A | |
2 | STX | $B | 66 | B | B | |
3 | ETX | $C | 67 | C | C | |
4 | EOT | $D | 68 | D | D | |
5 | ENQ | $E | 69 | E | E | |
6 | ACK | $F | 70 | F | F | |
7 | BEL | $G | 71 | G | G | |
8 | BS | $H | 72 | H | H | |
9 | HT | $I | 73 | 我 | 我 | |
10 | LF | $J | 74 | J | J | |
11 | VT | $K | 75 | K | K | |
12 | FF | $L | 76 | L | L | |
13 | CR | $M | 77 | M | M | |
14 | SO | $N | 78 | N | N | |
15 | SI | $O | 79 | O | O | |
16 | DLE | $P | 80 | P | P | |
17 | DC1 | $Q | 81 | Q | Q | |
18 | DC2 | $R | 82 | R | R | |
19 | DC3 | $S | 83 | S | S | |
20 | DC4 | $T | 84 | T | T | |
21 | NAK | $U | 85 | U | U | |
22 | SYN | $V | 86 | V | V | |
23 | ETB | $W | 87 | W | W | |
24 | CAN | $X | 88 | X | X | |
25 | EM | $Y | 89 | Y | Y | |
26 | SUB | $Z | 90 | Z | Z | |
27 | ESC | %A | 91 | [ | %K | |
28 | FS | %B | 92 | \ | %L | |
29 | GS | %C | 93 | ] | %M | |
30 | RS | %D | 94 | ^ | %N | |
31 | US | %E | 95 | _ | %O | |
32 | [空格] | [空格] | 96 | ` | %W | |
33 | ! | /A | 97 | a | +A | |
34 | " | /B | 98 | b | +B | |
35 | # | /C | 99 | c | +C | |
36 | $ | /D | 100 | d | +D | |
37 | % | /E | 101 | e | +E | |
38 | & | /F | 102 | f | +F | |
39 | /G | 103 | g | +G | ||
40 | ( | /H | 104 | h | +H | |
41 | ) | /I | 105 | i | +I | |
42 | * | /J | 106 | j | +J | |
43 | + | /K | 107 | k | +K | |
44 | , | /L | 108 | l | +L | |
45 | - | - | 109 | m | +M | |
46 | . | . | 110 | n | +N | |
47 | / | /O | 111 | o | +O | |
48 | 0 | 0 | 112 | p | +P | |
49 | 1 | 1 | 113 | q | +Q | |
50 | 2 | 2 | 114 | r | +R | |
51 | 3 | 3 | 115 | s | +S | |
52 | 4 | 4 | 116 | t | +T | |
53 | 5 | 5 | 117 | u | +U | |
54 | 6 | 6 | 118 | v | +V | |
55 | 7 | 7 | 119 | w | +W | |
56 | 8 | 8 | 120 | x | +X | |
57 | 9 | 9 | 121 | 是 | +Y | |
58 | : | /Z | 122 | z | +Z | |
59 | ; | %F | 123 | { | %P | |
60 | < | %G | 124 | | | %Q | |
61 | = | %H | 125 | } | %R | |
62 | > | %I | 126 | ~ | %S | |
63 | ? | %J | 127 | DEL | %T,%X,%Y,%Z |
校验和
Code 39 可以有一个可选的模 43 校验位。要计算校验和位,每个字符都被分配一个值(参见下表)。
<!-- 行 # 2 --> <!-- 行 # 3 --> <!-- 行 # 4 --> <!-- 行 # 5 --> <!-- 行 # 6 --> <!-- 行 # 7 --> <!-- 行 # 8 --> <!-- 行 # 9 --> <!-- 行 # 10 --> <!-- 行 # 11 --> <!-- 行 # 12 --> <!-- 行 # 13 --> <!-- 行 # 14 --> <!-- 行 # 15 --> <!-- 行 # 16 --> <!-- 行 # 17 --> <!-- 行 # 18 --> <!-- 行 # 19 --> <!-- 行 # 20 --> <!-- 行 # 21 --> <!-- 行 # 22 --> <!-- 行 # 23 -->字符 | 值 | 字符 | 值 | |
0 | 0 | M | 22 | |
1 | 1 | N | 23 | |
2 | 2 | O | 24 | |
3 | 3 | P | 25 | |
4 | 4 | Q | 26 | |
5 | 5 | R | 27 | |
6 | 6 | S | 28 | |
7 | 7 | T | 29 | |
8 | 8 | U | 30 | |
9 | 9 | V | 31 | |
A | 10 | W | 32 | |
B | 11 | X | 33 | |
C | 12 | Y | 34 | |
D | 13 | Z | 35 | |
E | 14 | - | 36 | |
F | 15 | . | 37 | |
G | 16 | [空格] | 38 | |
H | 17 | $ | 39 | |
我 | 18 | / | 40 | |
J | 19 | + | 41 | |
K | 20 | % | 42 | |
L | 21 |
校验和计算方法如下:
- 获取条形码中每个字符的值(0 到 42),不包括开始和停止代码。
- 将这些值相加。
- 将结果除以 43。
- 余数是需要附加的校验字符的值。
上述算法也取自维基百科。
既然我们对 Code 39 条形码中数据编码方式有了一定的了解,那么让我们看看如何构建我们的类。在本文中,我将展示 VB.NET 代码,但同样的类也将提供 C# 版本供下载。
让我们首先将名为 Barcode39 的类分配给命名空间 Barcodes。如果运气好的话,我很快会发布另一篇文章,展示如何创建 Code128 条形码,我也会将其放在同一个命名空间中。
在下面的代码中,窄条和宽条的宽度值由 `WIDEBAR_WIDTH` 和 `NARROWBAR_WIDTH` 常量持有。这些值将窄条和宽条的比例设置为 1:2。
每个字符的符号(bwbw 字符序列)存储在一个 Hashtable 对象(名为 `mEncoding`)中,其中键是字符本身,值是字符的符号。此 Hashtable 对象在类构造函数中加载。
我们还有一个包含条形码符号体系编码的 43 个字符的 Char 元素数组。此数组中的元素与上表镜像,并且该数组也在构造函数中加载。
Option Explicit On
Option Strict On
Namespace Barcodes
Public Class Barcode39
Private Const WIDEBAR_WIDTH As Short = 2
Private Const NARROWBAR_WIDTH As Short = 1
Private Const NUM_CHARACTERS As Integer = 43
Private mEncoding As Hashtable = New Hashtable
Dim mCodeValue(NUM_CHARACTERS - 1) As Char
Public Sub New()
' Character, symbol
mEncoding.Add("*", "bWbwBwBwb")
mEncoding.Add("-", "bWbwbwBwB")
mEncoding.Add("$", "bWbWbWbwb")
mEncoding.Add("%", "bwbWbWbWb")
[.]
mEncoding.Add("X", "bWbwBwbwB")
mEncoding.Add("Y", "BWbwBwbwb")
mEncoding.Add("Z", "bWBwBwbwb")
mCodeValue(0) = "0"c
mCodeValue(1) = "1"c
mCodeValue(2) = "2"c
[.]
mCodeValue(40) = "/"c
mCodeValue(41) = "+"c
mCodeValue(42) = "%"c
End Sub
End Class
End Namespace
形成扩展字符串
下一个任务是构建一个相当简单的函数,该函数将检查输入字符串并检查是否存在标准 43 个字符之外的字符。如果存在,它将在其前面添加所需的特殊字符。该函数将返回输入字符串的扩展版本。
Private Function ExtendedString(ByVal s As String) As String
Dim Ch As Char
Dim KeyChar As Integer
Dim retVal As String = ""
For Each Ch In s
KeyChar = Asc(Ch)
Select Case KeyChar
Case 0
retVal &= "%U"
Case 1 To 26
retVal &= "$" & Chr(64 + KeyChar)
Case 27 To 31
retVal &= "%" & Chr(65 - 27 + KeyChar)
Case 33 To 44
retVal &= "/" & Chr(65 - 33 + KeyChar)
Case 47
retVal &= "/O"
Case 58
retVal &= "/Z"
Case 59 To 63
retVal &= "%" & Chr(70 - 59 + KeyChar)
Case 64
retVal &= "%V"
Case 91 To 95
retVal &= "%" & Chr(75 - 91 + KeyChar)
Case 96
retVal &= "%W"
Case 97 To 122
retVal &= "+" & Chr(65 - 97 + KeyChar)
Case 123 To 127
retVal &= "%" & Chr(80 - 123 + KeyChar)
Case Else
retVal &= Ch
End Select
Next
Return retVal
End Function
计算校验和
`Checksum` 函数遍历扩展字符串,对于其中的每个字符,它获取其值(这基本上与字符在 `mCodeValue` 数组中的索引一致),并将其添加到整数变量中。然后,该函数返回总和与 43 相除的余数。
Private Function CheckSum(ByVal sCode As String) As Integer
Dim CurrentSymbol As Char
Dim Chk As Integer
For j As Integer = 0 To sCode.Length - 1
CurrentSymbol = sCode.Chars(j)
Chk += GetSymbolValue(CurrentSymbol)
Next
Return Chk Mod (NUM_CHARACTERS)
End Function
Private Function GetSymbolValue(ByVal s As Char) As Integer
Dim k As Integer
For k = 0 To NUM_CHARACTERS - 1
If mCodeValue(k) = s Then
Return k
End If
Next
Return Nothing
End Function
附加属性
为了使该类更灵活,我为其添加了一些属性。以下是列表:
<!-- 行 # 2 --> <!-- 行 # 3 --> <!-- 行 # 4 --> <!-- 行 # 5 -->名称 | 类型 | 描述 |
显示字符串 | 布尔值 | 如果设置为 True,将在条形码图片下方添加编码字符串。 |
包含校验和位 | 布尔值 | 如果为 true,将计算校验和。 |
TextFont | 字体 | 当 ShowString = True 时打印的编码字符串的字体 |
TextColor | Color | 当 ShowString = True 时打印的编码字符串的颜色 |
上述属性将在构建条形码图片时使用。让我们将它们添加到类中。
Public ShowString As Boolean
Public IncludeCheckSumDigit As Boolean
Public TextFont As New Font("Courier New", 7)
Public TextColor As Color = Color.Black
核心部分
该类公开了一个公共函数 `GenerateBarcodeImage`,该函数将创建并准备一个 Graphics 对象,条形码图片将由另一个 Sub (DrawBarcode) 绘制到该对象上,并返回一个 Image 对象。该函数接受 3 个参数:
- 图像的高度 – 请记住,这将包括打印字符串所需的空间
- 图像的宽度 – 为其分配一个适当的值,否则条形码图片可能无法容纳在 Image 对象中
- 原始字符串
`GenerateBarcodeImage` 创建一个大小为 `ImageWidth` x `ImageHeight` 的 `PictureBox` 对象,并使用其中的 Graphics 对象进行绘制。然后,它组成扩展字符串,并通过添加前导和尾随星号以及(如有必要)校验位来格式化它。如果 `ShowString` 属性为 True,它还会写入原始字符串,最后调用 `DrawBarcode` 过程来绘制条形码并返回包含条形码图片的 Image 对象。
Public Function GenerateBarcodeImage(ByVal ImageWidth As Integer,
ByVal ImageHeight As Integer,
ByVal OriginalString As String) As Image
'-- create a image where to paint the bars
Dim pb As PictureBox
pb = New PictureBox
With pb
.Width = ImageWidth
.Height = ImageHeight
pb.Image = New Bitmap(.Width, .Height)
End With
'---------------------
'clear the image and set it to white background
Dim g As Graphics = Graphics.FromImage(pb.Image)
g.Clear(Color.White)
'get the extended string
Dim ExtString As String
ExtString = ExtendedString(OriginalString)
'-- This part format the sring that will be encoded
'-- The string needs to be surrounded by asterisks
'-- to make it a valid Code39 barcode
Dim EncodedString As String
Dim ChkSum As Integer
If IncludeCheckSumDigit = False Then
EncodedString = String.Format("{0}{1}{0}", "*", ExtString)
Else
ChkSum = CheckSum(ExtString)
EncodedString = String.Format("{0}{1}{2}{0}",
"*", ExtString, mCodeValue(ChkSum))
End If
'----------------------
'-- write the original string at the bottom if ShowString = True
Dim textBrush As New SolidBrush(TextColor)
If ShowString Then
If Not IsNothing(TextFont) Then
'calculates the height of the string
Dim H As Single = g.MeasureString(OriginalString, TextFont).Height
g.DrawString(OriginalString, TextFont, textBrush, 0, ImageHeight - H)
ImageHeight = ImageHeight - CShort(H)
End If
End If
'----------------------------------------
'THIS IS WHERE THE BARCODE DRAWING HAPPENS
DrawBarcode(g, EncodedString, ImageWidth, ImageHeight)
'IMAGE OBJECT IS RETURNED
Return pb.Image
End Function
`DrawBarcode` 的工作原理相当简单。对于编码字符串中的每个字符,它从 `mEncoding` Hashtable 获取符号。正如我们所知,这个符号是 b、w、B 和 W 字符的序列,对于这些字符中的每一个,函数都使用 Graphics 对象的 `FillRectangle` 方法创建一个具有适当宽度和颜色的条。这个适当的宽度和颜色由 `getBCSymbolWidth` 和 `getBCSymbolColor` 函数确定。
Private Sub DrawBarcode(ByVal g As Graphics,
ByVal EncodedString As String,
ByVal Width As Integer,
ByVal Height As Integer)
'Start drawing at 1, 1
Dim XPosition As Short = 0
Dim YPosition As Short = 0
Dim CurrentSymbol As String = String.Empty
'-- draw the bars
For j As Short = 0 To CShort(EncodedString.Length - 1)
CurrentSymbol = EncodedString.Chars(j)
Dim EncodedSymbol As String = mEncoding(CurrentSymbol).ToString
For i As Short = 0 To CShort(EncodedSymbol.Length - 1)
Dim CurrentCode As String = EncodedSymbol.Chars(i)
g.FillRectangle(getBCSymbolColor(CurrentCode),
XPosition, YPosition, getBCSymbolWidth(CurrentCode), Height)
XPosition = XPosition + getBCSymbolWidth(CurrentCode)
Next
'After each written full symbol we need a whitespace (narrow width)
g.FillRectangle(getBCSymbolColor("w"), XPosition, Yposition,
getBCSymbolWidth("w"), Height)
XPosition = XPosition + getBCSymbolWidth("w")
Next
'--------------------------
End Sub
Private Function getBCSymbolColor(ByVal symbol As String) As System.Drawing.Brush
If symbol = "W" Or symbol = "w" Then
getBCSymbolColor = Brushes.White
Else
getBCSymbolColor = Brushes.Black
End If
End Function
Private Function getBCSymbolWidth(ByVal symbol As String) As Short
If symbol = "B" Or symbol = "W" Then
getBCSymbolWidth = WIDEBAR_WIDTH
Else
getBCSymbolWidth = NARROWBAR_WIDTH
End If
End Function
就是这样。仅为说明目的,我添加了一个非常简单的窗体,它使用该类并包含在可下载文件中。
祝您编码愉快,
Stefano Castelli