Click here to Skip to main content
15,884,020 members
Articles / Programming Languages / XML

Kube Receipt Printer

Rate me:
Please Sign up or sign in to vote.
4.70/5 (9 votes)
30 Mar 2009CPOL2 min read 85.7K   2.7K   21   29
Kube receipt printer

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.

C#
/// <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>
{
}

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:

XML
<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>

When you have the class for the data and the template for the layout, you can print the ticket with this piece of code:

C#
// 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!");
}

Points of Interest

The process to print is composed of three parts:

  1. The first one uses the XML file to create a list of CommandPrinter objects.
  2. The second one decodes every CommandPrinter in a byte sequence.
  3. A buffer with all the byte sequence is sent to the printer.
C#
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 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.

C#
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.

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>

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.

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>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Mediatech Solutions
Italy Italy
I’m an IT Project Manager for an Italian Betting Company and over the last 2 years I acquired experience in Betting area.
I have developed code in different object oriented languages (C#, C++, Java) for more than 10 years using a set of technology such as .Net, J2EE, multithreading, etc…

Comments and Discussions

 
PraiseIt works fine! Pin
rogledi17-Jul-18 4:54
rogledi17-Jul-18 4:54 
QuestionHow to create the template to use ? Pin
amorosik213-May-18 22:34
amorosik213-May-18 22:34 
QuestionVery good job! Pin
webspinner721-Aug-17 19:39
webspinner721-Aug-17 19:39 
QuestionPrint Preview Pin
kaka223-Jul-12 23:18
kaka223-Jul-12 23:18 
QuestionNeed help to start print after Notch Sign Pin
Member 808149914-Jun-12 1:07
Member 808149914-Jun-12 1:07 
QuestionUSB and Ethernet Printers Pin
htulkay2-Jan-12 3:33
htulkay2-Jan-12 3:33 
AnswerRe: USB and Ethernet Printers Pin
Alessandro Lentini2-Jan-12 4:36
professionalAlessandro Lentini2-Jan-12 4:36 
GeneralRe: USB and Ethernet Printers Pin
htulkay2-Jan-12 7:15
htulkay2-Jan-12 7:15 
Generalimage size Pin
Member 82896654-Oct-11 1:55
Member 82896654-Oct-11 1:55 
Hello Alessandro!
Thanks in advance for your code!!! I'm a newbie, your code is very useful for a little program that i'm developing.

The question is:
i've tried to print a custom logo but (like the code explain clearly) it's limited to a resolution of 256 x 256 max.
In this way the KUBE prints an image that doesn't fit paper width.

Is it possible to modify your code for printing a larger image, reaching the paper width (or at least 70mm) ?

Thanks!
Max
GeneralRe: image size Pin
Alessandro Lentini10-Oct-11 7:33
professionalAlessandro Lentini10-Oct-11 7:33 
GeneralAdding new language Pin
Member 325327612-Feb-11 5:01
Member 325327612-Feb-11 5:01 
AnswerRe: Adding new language Pin
Alessandro Lentini15-Feb-11 0:57
professionalAlessandro Lentini15-Feb-11 0:57 
GeneralRe: Adding new language Pin
trstormvn26-Oct-11 23:25
trstormvn26-Oct-11 23:25 
GeneralRe: Adding new language Pin
Alessandro Lentini27-Oct-11 0:01
professionalAlessandro Lentini27-Oct-11 0:01 
GeneralWriting in Arabic Pin
PeaceTiger25-Aug-10 15:22
PeaceTiger25-Aug-10 15:22 
GeneralRe: Writing in Arabic Pin
Alessandro Lentini30-Aug-10 3:14
professionalAlessandro Lentini30-Aug-10 3:14 
GeneralRe: Writing in Arabic Pin
Qa3qaa330-Aug-10 3:30
Qa3qaa330-Aug-10 3:30 
GeneralRe: Writing in Arabic Pin
Alessandro Lentini30-Aug-10 3:50
professionalAlessandro Lentini30-Aug-10 3:50 
QuestionHow to send this command directly to Network Printer? Pin
Ravi Lodhiya5-Aug-09 3:22
professionalRavi Lodhiya5-Aug-09 3:22 
AnswerRe: How to send this command directly to Network Printer? Pin
Alessandro Lentini9-Aug-09 13:45
professionalAlessandro Lentini9-Aug-09 13:45 
GeneralRe: How to send this command directly to Network Printer? Pin
webspinner721-Aug-17 19:42
webspinner721-Aug-17 19:42 
GeneralThanks Pin
dannygoh21-Jun-09 14:11
dannygoh21-Jun-09 14:11 
GeneralRe: Thanks Pin
Alessandro Lentini21-Jun-09 21:20
professionalAlessandro Lentini21-Jun-09 21:20 
GeneralRe: Thanks Pin
Member 1149255129-Apr-15 9:26
Member 1149255129-Apr-15 9:26 
GeneralRe: Thanks Pin
Alessandro Lentini30-Apr-15 22:30
professionalAlessandro Lentini30-Apr-15 22:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.