As you might know, our favourite teddy bear franchise is now opening stores in China. Since we’ve developed a fully integrated point of sale for the teddy bear industry, we’re helping them setup the first store over there. One of our challenges has been getting the receipt printer to recognize Simplified Chinese characters. We posted this issue on StackOverflow and contacted Epson support for the first time to try and resolve this issue but we ended up finding the solution ourselves. I’d like to share this solution with you (and some of the hoops we had to go through) as it might prove helpful to other point of sale developers out there.

 Chinese Teddy Bear Birth Certificate

For neophytes, using point of sale hardware is usually straightforward. We use the Microsoft Point of Sale SDK for .NET which is a .NET class library that interfaces with OPOS (OLE for POS). OPOS is the first widely-adopted POS device standard, allowing developers like us to write code that will work with hardware using a unified interface. I will spare you the details of how to get a handle on the printer (open it, claim it, enable it) and will focus on the actual printing portion.

string str = "this is a test";
PosPrinter printer = GetPrinter(); // open it, claim it, enable it. 
printer.PrintNormal(PrinterStation.Receipt, str);
ReleasePrinter(); // unclaim it. 
 

 

Easy enough?  This code worked for us for our stores in Québec (French), Spain (Spanish), and Denmark (Danish) and still worked for us in China when printing Latin characters, but all the Simplified Chinese characters appeared as question marks. 

Question Marks

The first thing to note is that you cannot use any plain old Epson TM-T88IV to print Chinese characters. You need the special multilingual version (which we have: TM-T88IVM). Second, you need to ensure that Epson OPOS sees it configured as the multilingual version, otherwise it won’t know it can print in simplified Chinese. In our tests, we were able to print to the printer via the sample application that comes with the Microsoft POS SDK.

Epson OPOS Configuration ms

Doing a bit of research, we found that we simply had to change the printer’s codepage (from 1252 to 936) for it to recognize the simplified Chinese characters. (Our CharacterSetList=255,437,850,852,858,860,863,865,866,936,998,999,1252 which implied that we could actually use this character set value. If we had not configured Epson OPOS to use the multilingual version, we would get an exception because 936 is not in the list.)

   1:  string str = "重新开始";
   2:  string str = "this is a test";
   3:  PosPrinter printer = GetPrinter(); // open it, claim it, enable it. 
   4:  printer.CharacterSet = 936;
   5:  printer.PrintNormal(PrinterStation.Receipt, str);
   6:  ReleasePrinter(); // unclaim it. 

 

However, this changed absolutely nothing. At this point, we contacted Epson support who could not help us. Our printer self tests showed the printer was capable of printing the characters, but we were still unable to print Chinese characters. Furthermore, the build-publish-test cycle was very slow because we did not have this printer on site (70 days to have it delivered from our regular supplier!) – we had to rely on our partner who was in China to help with the store setup. We tried dozens of things, but could not find the answer. We needed to have the printer on site – we had our partner ship one to us – it arrived three days later. We then decided to run another test:  printing the following website.

Some characters printed image

Interesting… some Chinese characters printed. But not where we were expecting them! I immediately realized it was encoding other (simpler) characters as Chinese characters. In this case, I took the first one above that was generated when the source string was ài. I did a few tests to confirm that àj, àk, àl were all printing different Chinese characters.

Having no knowledge of Chinese, finding the character’s unicode/decimal value in some character map was impossible for me. I had to reverse engineer the problem. 

  • à = 0xE0 in hex = 224 in decimal
  • i = 0x69 in hex = 105 in decimal
  • ài is therefore {224, 105} as confirmed by
byte[] source = Encoding.Unicode.GetBytes(text);

 

I looked up 0xE069 but found it was nothing. I then reloaded one of my old tests to convert this byte array to Code Page 936.

   1:  // simplified chinese
   2:  var encoding = Encoding.GetEncoding(936);
   3:   
   4:  // convert the text into a byte array
   5:  byte[] source = Encoding.Unicode.GetBytes(text);
   6:   
   7:  // convert that byte array to the new codepage. 
   8:  byte[] converted = Encoding.Convert(Encoding.Unicode, encoding, source);

 

Looking in the resulting byte array, I saw {145, 6}. However, converting this byte array back to a string and sending it to the printer did not work. It did not work because I was simply reconverting it back into a Unicode string (C#). I also did not have a PrintNormal method I could call that would accept a byte array. I therefore computed the decimal value of {145, 6} (256 * 145 + 6 = 37126) and looked it up to see it was indeed the character I was looking for ()!  I therefore implemented an ugly byte-by-byte conversion and sent it off to the printer. It worked!

   1:  StringBuilder builder = new StringBuilder();
   2:   
   3:  // simplified chinese
   4:  var encoding = Encoding.GetEncoding(936);
   5:   
   6:  // convert the text into a byte array
   7:  byte[] source = Encoding.Unicode.GetBytes(text);
   8:   
   9:  // convert that byte array to the new codepage. 
  10:  byte[] converted = Encoding.Convert(Encoding.Unicode, encoding, source);
  11:   
  12:  // take multi-byte characters and encode them as separate ascii characters 
  13:  foreach (byte b in converted)
  14:      builder.Append((char)b);
  15:   
  16:  // return the result
  17:  string result = builder.ToString();

Thanks to other Stack Overflow users, I found the following concise implementation.

string result =  Encoding.GetEncoding("ISO-8859-1").GetString(Encoding.GetEncoding(936).GetBytes(text));

Thus, it appears that although the printer supports multilingual characters, one needs to re-encode them in the target codepage and then back into Latin-1 encoding for the Epson TM-T88IV Multilingual to detect it properly. The only things left for us to fix was the string padding (because these characters are twice as wide as latin characters on our printer) and finish off the receipt translation before the grand opening.

Success!

As a side note, if any of our readers have experience with controls (ActiveX or other Windows-specific applications) that allow to print to receipt printer, control the cash drawer, and receive barcodes from a barcode scanner from within a web browser, Flash, or Silverlight, please let us know.