Click here to Skip to main content
Licence CPOL
First Posted 30 Mar 2009
Views 19,067
Downloads 720
Bookmarked 12 times

Kube Receipt Printer

By | 30 Mar 2009 | Article
In this article, I wish to 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.

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.

/// <summary>
/// Class to rappresent 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:

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

// 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.
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.

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

About the Author

Alessandro Lentini

Software Developer (Senior)

Italy Italy

Member

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…

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionUSB and Ethernet Printers Pinmemberhtulkay3:33 2 Jan '12  
AnswerRe: USB and Ethernet Printers PinmemberAlessandro Lentini4:36 2 Jan '12  
GeneralRe: USB and Ethernet Printers Pinmemberhtulkay7:15 2 Jan '12  
Generalimage size PinmemberMember 82896651:55 4 Oct '11  
GeneralRe: image size PinmemberAlessandro Lentini7:33 10 Oct '11  
GeneralAdding new language PinmemberMember 32532765:01 12 Feb '11  
AnswerRe: Adding new language PinmemberAlessandro Lentini0:57 15 Feb '11  
GeneralRe: Adding new language [modified] Pinmembertrstormvn23:25 26 Oct '11  
GeneralRe: Adding new language PinmemberAlessandro Lentini0:01 27 Oct '11  
GeneralWriting in Arabic PinmemberBasel Nimer15:22 25 Aug '10  
GeneralRe: Writing in Arabic PinmemberAlessandro Lentini3:14 30 Aug '10  
GeneralRe: Writing in Arabic PinmemberQa3qaa33:30 30 Aug '10  
GeneralRe: Writing in Arabic PinmemberAlessandro Lentini3:50 30 Aug '10  
QuestionHow to send this command directly to Network Printer? PinmemberRavi Lodhiya3:22 5 Aug '09  
AnswerRe: How to send this command directly to Network Printer? PinmemberAlessandro Lentini13:45 9 Aug '09  
GeneralThanks Pinmemberdannygoh14:11 21 Jun '09  
GeneralRe: Thanks PinmemberAlessandro Lentini21:20 21 Jun '09  

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

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120517.1 | Last Updated 30 Mar 2009
Article Copyright 2009 by Alessandro Lentini
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid