LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

Simplified Chinese on Epson TM-T88IV Receipt Printer

clock January 15, 2010 14:27 by author JKealey

 

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.



ASP.NET callback not being fired in Internet Explorer

clock January 13, 2010 10:03 by author JKealey

Happy New Year!

Very brief news: We’ve been working hard on our software products for the last few months. We’re building a large line of business application for a service franchise and a few stores opened with our software in Ireland, Scotland, Mexico, and, more recently, China. We’ve also contributed some work to the jUCMNav open source project, which focuses on visually representing software requirements.

Today, we encountered a weird little bug in Internet Explorer and we thought it might be good to describe the workaround which we found thanks to StackOverflow.

We're using a control that uses ASP.NET callbacks (not postbacks) in our page. However, in certain circumstances, the control stops working.

Problem: ASP.NET callbacks are not fired in Internet Explorer

  • The control works fine in FireFox, Google Chrome, etc.
  • The control works fine if we do not use ASP.NET AJAX History. As soon as we call the following code, the callbacks stop working in IE (6, 7, and 8)
ScriptManager.GetCurrent(Page).AddHistoryPoint("h", "12");

 

  • I did some server side debugging to figure out that RaiseCallbackEvent was not fired in IE, but was fired for others.
  • I have debugged using Fiddler and observed that it was not querying the appropriate URL. The server returns an invalid request error.

    Root Cause : IE thinks the anchor (hash tag) is part of the filename.

    Other browsers send the HTTP POST to: test.aspx but Internet Explorer is sending it to: test.aspx%23&&h=12

This is because the URL in the browser's bar is test.aspx#&&h=12, because of our AJAX History Control. For some reason, it URL Encodes the hash tag, but not the rest, and appends it to the aspx filename. Other browsers don’t exhibit this behaviour.

 

Goal: Force IE to drop the AJAX history anchor when calling WebForm_DoCallback via JavaScript?

Technique 1: Add a query string parameter

I found that if the browser was at a certain URL such as test.aspx?test=ing that the control worked fine. This is because appending the AJAX history to this URL makes test.aspx?test=ing%23&&h=12. This implies that we’re sending an invalid test query string of (ing%23&&h=12), but since we’re not using this query string parameter, it does not really matter. However, this does give you an ugly address.

Technique 2: Change the form’s action parameter

Gabriel McAdams lead me in this direction on StackOverflow.

Reading the contents on WebForm_DoCallback, I didn't see anything that set the URL of the server call. This means that it is either getting it from the form action or sending it to the current page. Try setting the form's action attribute.

Indeed, this is the solution we were looking for. ASP.NET has a single server-side FORM element named aspnetForm (auto-generated). If you remove the hash-tag from the form’s action parameter, the control works in all browsers. I assumed that the following code in the right spot would fix the issue, but it did not since ASP.NET’s action element is auto-generated. Any changes made to action don’t seem to do anything. (By default, Action is empty, forcing the browser to decide what URL to use for the current page, which must be the root problem here.)

this.Page.Form.Action = HttpContext.Current.Request.Url.PathAndQuery; 

 

However, if you change it using JavaScript (here I am using jQuery to make my life easier), then your problem is solved.

<div onmousedown="$('#aspnetForm').attr('action', '<%= HttpContext.Current.Request.Url.PathAndQuery %>')">
<!-- my control is in here - I could have executed the above code after every postback, but was sufficient for my needs -->
</div>

 

I’m executing the code when the mouse button is pushed here (sufficient for my needs, and executed before the click event which caused my problems). A cleaner solution would have you output this automatically when necessary. Note that I had tried changing window.location.hash to an empty string before the callback, but this caused the page to scroll, left the hash character in the URL, and broke my AJAX history.

kick it on DotNetKicks.com



Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in