Introduction
In this article, I will show how it’s possible to use an XML file as a Command file to drive a custom Kube printer. In detail, the goal of the project is to use the printer with native commands (escape sequences) for best performance, but creating the layout through a simple XML file instead of using a complex fixed code. The printer can be driven both by USB and RS232 ports. Via RS232, it’s also possible to read the printer and the paper status.
Background
In the Kube manual, you can see all the supported commands. My library doesn’t support all commands yet, but it maps the major ones. Also, it’s simpler to implement the missed commands. Bitmaps can be printed, and inside the library, there are conversion functions. Custom char definition is supported as well. Strings are printed by converting special characters (international chars) in the correct escape sequence. The conversion is defined in an XML file, so you can add the missed chars – currently, I’ve defined some Spain and Italian chars only.
Using the Code
The library can be used inside every project. It’s necessary to design the classes for data interchange (you can see some examples below) in which the only rule is to use the IList
interface for encapsulating a sequence of data. This is possible because classes are read by the print engine using Reflection.
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;
…..
…..
}
public class ReceiptLegData
{
public DateTime EventDate = DateTime.MinValue;
public string EventDescription = null;
public string MarketDescription = null;
public string SelectionDescription = null;
public decimal Price = 0;
…..
…..
}
public class ReceiptLegDataList : List<ReceiptLegData>
{
}
To print data using one of the defined templates is simple, but you need to write XML for defining every template, of course. To apply a sequence of commands to an IList
, use a loop command. You can see an example below:
<Commands Path="xml" Language="IT">
<Command name="Reset"/>
<Command name="SetHAlign" Align="Center" />
<Command name="DefineImage" Filename="image\logo.png"/>
<Command name="PrintImageDefined" PrintMode="normal"/>
<Command name="NewLine" />
<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" />
<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>
<Command name="LineFeed" Line="1" />
<Command name="SetHAlign" Align="Left" />
<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" />
<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" />
<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" />
<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" />
<Command name="SetLeftMargin" Margin="60" />
<Command name="PrintString" String="My Company Info" LineFeed="Yes" />
<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" />
<Command name="SetLeftMargin" Margin="20" />
<Command name="PrintLabel" LabelKey="User" LineFeed="No" />
<Command name="PrintString" Mapping="UserName" LineFeed="Yes" />
<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" />
<Command name="CutPaper"/>
</Commands>
When you have the class for the data and the template for the layout, you can print the ticket with this piece of code:
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);
CustomPrinterEngine engine = new CustomPrinterEngine("Custom KUBE 80mm (200dpi)");
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!");
}
Points of Interest
The process to print is composed of three parts:
- The first one uses the XML file to create a list of
CommandPrinter
objects. - The second one decodes every
CommandPrinter
in a byte sequence. - A buffer with all the byte sequence is sent to the printer.
private CommandPrinterList GenerateCommandList(string languageKey,
string templateKey, object obj)
{
XmlDocument xmlTemplate = m_Cache.getTemplate(languageKey, templateKey);
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
derivate classes define every Kube command with the possible parameters. An image can also be printed. The ImageRasterHelper
converts the image in the correct byte sequence for the printer.
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;
}
Support for international characters is defined in another XML file (a class streaming XML file) that you can expand to handle all international characters supported by the printer.
="1.0"="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>
It’s also possible to define labels in different languages. The language used inside the template is defined with language attributes in the first line, while the XML file that defines labels can be expanded for more labels as you like.
="1.0"="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>