Kube 收据打印机






4.70/5 (9投票s)
Kube 收据打印机。
引言
在本文中,我将展示如何使用 XML 文件作为命令文件来驱动一个自定义的 Kube 打印机。具体来说,该项目的目标是使用打印机的原生命令(转义序列)以获得最佳性能,但通过一个简单的 XML 文件来创建布局,而不是使用复杂的固定代码。该打印机可以通过 USB 和 RS232 端口驱动。通过 RS232,也可以读取打印机和纸张状态。
背景
在 Kube 手册中,你可以看到所有支持的命令。我的库目前还没有支持所有命令,但它映射了主要的命令。此外,实现缺失的命令也比较简单。可以打印位图,并且在库内部有转换函数。也支持自定义字符定义。字符串通过将特殊字符(国际字符)转换为正确的转义序列来打印。转换在 XML 文件中定义,因此你可以添加缺失的字符——目前,我只定义了一些西班牙和意大利字符。
Using the Code
该库可以在任何项目中使用。有必要为数据交换设计类(你可以在下面看到一些示例),其中唯一的规则是使用 IList
接口来封装一个数据序列。这是可能的,因为类通过反射被打印引擎读取。
/// <summary>
/// Class to represent a receipt.
/// Receipt contains a Header with more details (legs).
/// </summary>
public class ReceiptData
{
public string ReceiptID = null;
public decimal Amount = 0;
public decimal FinalPrice = 0;
public decimal PossibleReturn = 0;
public DateTime PlaceDate = DateTime.MinValue;
public string UserName = null;
public string TerminalID = null;
public string BetType = null;
public ReceiptLegDataList Legs = null;
…..
…..
}
/// <summary>
/// Class to rappresent a receipt leg (detail).
/// </summary>
public class ReceiptLegData
{
public DateTime EventDate = DateTime.MinValue;
public string EventDescription = null;
public string MarketDescription = null;
public string SelectionDescription = null;
public decimal Price = 0;
…..
…..
}
/// <summary>
/// Class for a List of ReceiptLegData
/// </summary>
public class ReceiptLegDataList : List<ReceiptLegData>
{
}
要使用其中一个定义的模板打印数据很简单,但当然你需要编写 XML 来定义每个模板。要将一系列命令应用于 IList
,请使用循环命令。你可以在下面看到一个例子
<Commands Path="xml" Language="IT">
<!-- Reset -->
<Command name="Reset"/>
<!-- Print company logo -->
<Command name="SetHAlign" Align="Center" />
<Command name="DefineImage" Filename="image\logo.png"/>
<Command name="PrintImageDefined" PrintMode="normal"/>
<Command name="NewLine" />
<!-- Starting sets and print receiptID (barcode) -->
<Command name="LineFeed" Line="1" />
<Command name="SetFont" Font="Large" />
<Command name="SetLeftMargin" Margin="20" />
<Command name="SetHAlign" Align="Center" />
<Command name="SetCharSize" X="2" Y="1" />
<Command name="PrintString" Mapping="ReceiptID" LineFeed="Yes" />
<Command name="SetCharSize" X="1" Y="1" />
<Command name="SetHAlign" Align="Left" />
<Command name="LineFeed" Line="2" />
<Command name="SetFont" Font="Small" />
<Command name="SetLeftMargin" Margin="20" />
<!-- Print legs info -->
<Command name="Loop" On="Legs">
<Command name="PrintString" Mapping="EventDescription" LineFeed="No" />
<Command name="SetPosition" X="400" />
<Command name="PrintString" Mapping="EventDate"
Format="dd.MM.yy HH:mm" LineFeed="Yes" />
<Command name="PrintString" Mapping="MarketDescription" LineFeed="Yes" />
<Command name="PrintString" String="(" LineFeed="No" />
<Command name="PrintString" Mapping="SelectionDescription" LineFeed="No" />
<Command name="PrintString" String="): " LineFeed="No" />
<Command name="SetPosition" X="400" />
<Command name="PrintString" Mapping="Price" Format="0.00" LineFeed="No" />
<Command name="UnitFeed" Unit="2" />
<Command name="PrintString" String="______________________________
________________________" LineFeed="Yes" />
</Command>
<!-- Print summary info -->
<Command name="LineFeed" Line="1" />
<Command name="SetHAlign" Align="Left" />
<!-- total stake -->
<Command name="PrintLabel" LabelKey="TotalStake" LineFeed="No" />
<Command name="SetPosition" X="230" />
<Command name="PrintMoney" Mapping="Amount" ShowCurrency="Yes"
LineFeed="Yes" FixedLen="15" FillOnLeft="true" />
<!-- total price -->
<Command name="PrintLabel" LabelKey="TotalPrice" LineFeed="No" />
<Command name="SetPosition" X="230" />
<Command name="PrintMoney" Mapping="FinalPrice" ShowCurrency="No"
LineFeed="Yes" FixedLen="11" FillOnLeft="true" />
<!-- total price -->
<Command name="PrintLabel" LabelKey="MaxReturn" LineFeed="No" />
<Command name="SetPosition" X="230" />
<Command name="PrintMoney" Mapping="PossibleReturn" ShowCurrency="Yes"
LineFeed="Yes" FixedLen="15" FillOnLeft="true" />
<Command name="LineFeed" Line="1" />
<!-- place date time -->
<Command name="SetHAlign" Align="Left" />
<Command name="PrintLabel" LabelKey="PlaceDate" LineFeed="No" />
<Command name="PrintString" Mapping="PlaceDate" Format="dd.MM.yy" LineFeed="No" />
<Command name="PrintLabel" LabelKey="PlaceTime" LineFeed="No" />
<Command name="PrintString" Mapping="PlaceDate" Format="HH:mm:ss" LineFeed="No" />
<Command name="PrintString" String=" h" LineFeed="Yes" />
<!-- company info -->
<Command name="SetLeftMargin" Margin="60" />
<Command name="PrintString" String="My Company Info" LineFeed="Yes" />
<!-- terminal -->
<Command name="PrintString" String="Sucursal " LineFeed="No" />
<Command name="PrintString" Mapping="TerminalID" LineFeed="No" />
<Command name="PrintString" String=" " LineFeed="No" />
<Command name="PrintLabel" LabelKey="TerminalID" LineFeed="Yes" />
<Command name="PrintString" String="Apuesta Contrapartida - " LineFeed="No" />
<Command name="PrintString" Mapping="BetType" LineFeed="Yes" />
<!-- user -->
<Command name="SetLeftMargin" Margin="20" />
<Command name="PrintLabel" LabelKey="User" LineFeed="No" />
<Command name="PrintString" Mapping="UserName" LineFeed="Yes" />
<!-- Print receiptID as barcode -->
<Command name="LineFeed" Line="1" />
<Command name="SetHAlign" Align="Center" />
<Command name="PrintBarCode" Mapping="ReceiptID" Font="Large"
TextPosition="Down" Height="162" Barcode="CODE93"/>
<Command name="SetHAlign" Align="Left" />
<Command name="SetHAlign" Align="Center" />
<Command name="NewLine" />
<Command name="LineFeed" Line="3" />
<!-- Cut paper -->
<Command name="CutPaper"/>
</Commands>
当你拥有数据的类和布局的模板时,你可以使用这段代码打印票据
// Load templates
TemplateManager templateManager = new TemplateManager();
templateManager.Init(@"xml\InternationalChars.xml");
templateManager.LoadTemplate(@"xml\TemplateES.xml", "ES", BetReceipt);
templateManager.LoadTemplate(@"xml\CashReportES.xml", "ES", CashReport);
templateManager.LoadTemplate(@"xml\PaidReportES.xml", "ES", PaidReport);
templateManager.LoadTemplate(@"xml\AuthES.xml", "ES", AuthReport);
// Create printer engine (USB mode)
CustomPrinterEngine engine = new CustomPrinterEngine("Custom KUBE 80mm (200dpi)");
// Create printer engine (RS232 mode)
CustomPrinterEngine engine = new CustomPrinterEngine("COM11", 19200,
System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One);
ReceiptData receipt = new ReceiptData();
receipt.BetType = "multiple";
receipt.Amount = 10;
receipt.FinalPrice = (decimal)12.5;
receipt.PlaceDate = DateTime.Now;
receipt.PossibleReturn = 50;
receipt.ReceiptID = "123456789";
receipt.TerminalID = "A010";
receipt.UserName = "Alfredo";
receipt.AddLeg(DateTime.Now, "Roma - Sampdoria", "1X2", "1", 5);
receipt.AddLeg(DateTime.Now.AddDays(3).AddHours(10), "Mìlan - Juventus",
"Odd/Even", "Odd", (decimal)2.5);
receipt.AddLeg(DateTime.Now.AddDays(2).AddHours(8), "La Coroña - Real Madrid",
"1X2", "2", (decimal)3.1);
engine.DataToPrint = receipt;
engine.Print(templateManager, "ES", BetReceipt);
if(!engine.IsPrinted)
{
MessageBox.Show("Ticket 1 no printed!");
}
关注点
打印过程由三个部分组成
- 第一部分使用 XML 文件创建一个
CommandPrinter
对象列表。 - 第二部分将每个
CommandPrinter
解码为字节序列。 - 包含所有字节序列的缓冲区被发送到打印机。
private CommandPrinterList GenerateCommandList(string languageKey,
string templateKey, object obj)
{
// Get template
XmlDocument xmlTemplate = m_Cache.getTemplate(languageKey, templateKey);
// Generate command list
CommandPrinterList commandList = new CommandPrinterList();
AttributeHashtable attributes =
new AttributeHashtable(xmlTemplate.FirstChild.Attributes);
string parmLanguage = attributes["language"];
string parmPath = attributes["path"];
m_LabelMapper = LocalizeLabelMapper.getLanguage(parmPath, parmLanguage);
commandList.AddRange(ParseChild(xmlTemplate.FirstChild, obj, m_LabelMapper));
return commandList;
}
private CommandPrinter[] ParseChild(XmlNode node, Object obj,
LocalizeLabelMapper htLabels)
{
CommandPrinterList list = new CommandPrinterList();
foreach (XmlNode command in node.ChildNodes)
{
list.AddRange(ParseCommand(command, obj, htLabels));
}
return list.ToArray();
}
private CommandPrinter[] ParseCommandLoop(XmlNode command,
Object master, LocalizeLabelMapper htLabels)
{
AttributeHashtable attributes = new AttributeHashtable(command.Attributes);
string loopField = attributes["on"];
System.Reflection.FieldInfo field = master.GetType().GetField(loopField);
IList iList = (IList)field.GetValue(master);
CommandPrinterList list = new CommandPrinterList();
foreach (object obj in iList)
{
list.AddRange(ParseChild(command, obj, htLabels));
}
return list.ToArray();
}
private CommandPrinter[] ParseCommand(XmlNode command,
Object obj, LocalizeLabelMapper htLabels)
{
if (command is XmlComment)
{
return new CommandPrinter[]{ null };
}
if (!command.Name.Equals("Command", StringComparison.OrdinalIgnoreCase))
{
throw new Exception("Invalid node type. Command element is expected");
}
AttributeHashtable attributes = new AttributeHashtable(command.Attributes);
string cmdName = attributes["name"].ToLower();
if (cmdName.Equals("loop"))
{
return ParseCommandLoop(command, obj, htLabels);
}
CommandPrinter commandPrinter = null;
switch (cmdName)
{
case "linefeed":
commandPrinter = ParseCommandPrintAndFeed(attributes);
break;
case "unitfeed":
commandPrinter = ParseCommandPrintAndUnitFeed(attributes);
break;
…..
…..
case "papertocut":
commandPrinter = ParseCommandAlignPaperToCut(attributes);
break;
case "reset":
commandPrinter = ParseCommandReset(attributes);
break;
}
if (commandPrinter == null)
{
throw new Exception("Command unkown");
}
return new CommandPrinter[]{commandPrinter};
}
CommandPrinter
派生类定义每个 Kube 命令及其可能的参数。也可以打印图像。ImageRasterHelper
将图像转换为打印机的正确字节序列。
public static byte[] ConvertBitmap(Bitmap bitmap, bool bIncludeSize)
{
int baseIndex = ((bIncludeSize) ? 2 : 0);
int xSize = (bitmap.Width / 8);
if (xSize * 8 != bitmap.Width)
{
xSize++;
}
int ySize = (bitmap.Height / 8);
if (ySize * 8 != bitmap.Height)
{
ySize++;
}
if (xSize < 1 || xSize > 255 || ySize < 1 ||
ySize > 48 || xSize * ySize > 1536)
{
throw new Exception("Incorrect size");
}
byte[] raw = new byte[xSize * ySize * 8 + ((bIncludeSize) ? 2 : 0)];
for (int i = 0; i < raw.Length; raw[i++] = 0) ;
if (bIncludeSize)
{
raw[0] = (byte)(xSize & 0x00FF);
raw[1] = (byte)(ySize & 0x00FF);
}
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
Color color = bitmap.GetPixel(x, y);
if (RGBGreatEgual(color, 255, 255, 128))
{
continue;
}
int idx = (ySize * x) + y / 8;
byte mask = (byte)(0x80 >> (y % 8));
raw[idx + baseIndex] |= mask;
}
}
return raw;
}
对国际字符的支持在另一个 XML 文件(一个类流 XML 文件)中定义,你可以扩展它以处理打印机支持的所有国际字符。
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCharConvert
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CharConvert>
<OrigChar>241</OrigChar>
<InternationalChar>
<char>27</char>
<char>82</char>
<char>7</char>
<char>124</char>
</InternationalChar>
</CharConvert>
…..
…..
<CharConvert>
<OrigChar>176</OrigChar>
<InternationalChar>
<char>27</char>
<char>82</char>
<char>6</char>
<char>91</char>
</InternationalChar>
</CharConvert>
</ArrayOfCharConvert>
也可以定义不同语言的标签。模板中使用的语言通过第一行的语言属性定义,而定义标签的 XML 文件可以根据需要扩展以包含更多标签。
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfLocalizeLabelItem
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LocalizeLabelItem>
<key>Money</key>
<value>EUR</value>
</LocalizeLabelItem>
<LocalizeLabelItem>
<key>TotalStake</key>
<value>Totale scommesso:</value>
</LocalizeLabelItem>
...
...
</ArrayOfLocalizeLabelItem>