Click here to Skip to main content
12,758,462 members (30,572 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

49.5K views
2.3K downloads
19 bookmarked
Posted 30 Mar 2009

Kube Receipt Printer

, 30 Mar 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
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)

Share

About the Author

Alessandro Lentini
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…

You may also be interested in...

Comments and Discussions

 
QuestionPrint Preview Pin
kaka224-Jul-12 0:18
memberkaka224-Jul-12 0:18 
QuestionNeed help to start print after Notch Sign Pin
Member 808149914-Jun-12 2:07
memberMember 808149914-Jun-12 2:07 
QuestionUSB and Ethernet Printers Pin
htulkay2-Jan-12 4:33
memberhtulkay2-Jan-12 4:33 
AnswerRe: USB and Ethernet Printers Pin
Alessandro Lentini2-Jan-12 5:36
memberAlessandro Lentini2-Jan-12 5:36 
GeneralRe: USB and Ethernet Printers Pin
htulkay2-Jan-12 8:15
memberhtulkay2-Jan-12 8:15 
Generalimage size Pin
Member 82896654-Oct-11 2:55
memberMember 82896654-Oct-11 2:55 
GeneralRe: image size Pin
Alessandro Lentini10-Oct-11 8:33
memberAlessandro Lentini10-Oct-11 8:33 
GeneralAdding new language Pin
Member 325327612-Feb-11 6:01
memberMember 325327612-Feb-11 6:01 
AnswerRe: Adding new language Pin
Alessandro Lentini15-Feb-11 1:57
memberAlessandro Lentini15-Feb-11 1:57 
GeneralRe: Adding new language Pin
trstormvn27-Oct-11 0:25
membertrstormvn27-Oct-11 0:25 
GeneralRe: Adding new language Pin
Alessandro Lentini27-Oct-11 1:01
memberAlessandro Lentini27-Oct-11 1:01 
GeneralWriting in Arabic Pin
Basel Nimer25-Aug-10 16:22
memberBasel Nimer25-Aug-10 16:22 
GeneralRe: Writing in Arabic Pin
Alessandro Lentini30-Aug-10 4:14
memberAlessandro Lentini30-Aug-10 4:14 
GeneralRe: Writing in Arabic Pin
Qa3qaa330-Aug-10 4:30
memberQa3qaa330-Aug-10 4:30 
GeneralRe: Writing in Arabic Pin
Alessandro Lentini30-Aug-10 4:50
memberAlessandro Lentini30-Aug-10 4:50 
QuestionHow to send this command directly to Network Printer? Pin
Ravi Lodhiya5-Aug-09 4:22
memberRavi Lodhiya5-Aug-09 4:22 
AnswerRe: How to send this command directly to Network Printer? Pin
Alessandro Lentini9-Aug-09 14:45
memberAlessandro Lentini9-Aug-09 14:45 
GeneralThanks Pin
dannygoh21-Jun-09 15:11
memberdannygoh21-Jun-09 15:11 
GeneralRe: Thanks Pin
Alessandro Lentini21-Jun-09 22:20
memberAlessandro Lentini21-Jun-09 22:20 
GeneralRe: Thanks Pin
Member 1149255129-Apr-15 10:26
memberMember 1149255129-Apr-15 10:26 
GeneralRe: Thanks Pin
Alessandro Lentini30-Apr-15 23:30
memberAlessandro Lentini30-Apr-15 23:30 
GeneralRe: Thanks Pin
Member 114925518-May-15 0:48
memberMember 114925518-May-15 0:48 
GeneralRe: Thanks Pin
Alessandro Lentini8-May-15 1:37
memberAlessandro Lentini8-May-15 1:37 
GeneralRe: Thanks Pin
Member 1149255113-May-15 6:14
memberMember 1149255113-May-15 6:14 
GeneralRe: Thanks Pin
Alessandro Lentini2-Jun-15 23:16
memberAlessandro Lentini2-Jun-15 23:16 

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.

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