4

I have an application which print the receipt to receipt printer using ESC/POS. It needs to support multi-languages. Currently, I've tested with Chinese characters (both traditional and simplified) and Thai language. They all are working fine.

However, when I tried to print Vietnamese, some of the characters are being replace by "?".

Here is my code:

public static readonly string ESC = "\u001B";
...
...
...
Encoding enc = Encoding.GetEncoding(1258); //vietnamese code page
string content = "Cơm chiên với các loại gia vị truyền thống làm cho lưỡi của bạn";
string toPrint = ESC + "t" + char.ConvertFromUtf32(94) + "\n" + Encoding.GetEncoding("Latin1").GetString(enc.GetBytes(str));  //code page 94 is for vietnamese (WPC1258). It is get from printer

In the print out, some of the characters has been replaced with "?" (see attached image). Any idea?

Print Out

UPDATE

Base on Panagiotis Kanavos comment, I've tried the following:

Encoding enc = Encoding.GetEncoding(1258); //vietnamese code page
string content = "Cơm chiên với các loại gia vị truyền thống làm cho lưỡi của bạn";
string newStr = Encoding.GetEncoding("Latin1").GetString(enc.GetBytes(content));
string origStr = enc.GetString(Encoding.GetEncoding("Latin1").GetBytes(newStr));

The origStr contains ?. I confirm in Chinese and Thai, the origStr will be equal to content. But NOT for Vietnamese. Any idea?

UPDATE 2

I decided to Panagiotis Kanavos code which is bytesToPrint = enc.GetBytes("\x1Bt\x5E\n" + content);, but it gave me the exact same result which contains ? instead of the actual characters.

This is how I sent the content (either string or bytes) to printer.

[DllImport("Winspool.drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);

[DllImport("Winspool.drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);

[DllImport("Winspool.drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);

[DllImport("Winspool.drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

[DllImport("Winspool.drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

[DllImport("Winspool.drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);

[DllImport("Winspool.drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

public static bool SendBytesToPrinter(string printerName, IntPtr pBytes, int dwCount, string docName = null, string dataType = "RAW")
{
    DOCINFOA di = new DOCINFOA();
    di.pDocName = string.IsNullOrWhiteSpace(docName) ? string.Empty : docName;
    di.pDataType = string.IsNullOrWhiteSpace(dataType) ? "RAW" : dataType;

    IntPtr hPrinter = new IntPtr(0); int dwError = 0, dwWritten = 0; bool bSuccess = false;
    if (OpenPrinter(printerName.Normalize(), out hPrinter, IntPtr.Zero))
    {
        if (StartDocPrinter(hPrinter, 1, di))
        {
            if (StartPagePrinter(hPrinter))
            {
                bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
                EndPagePrinter(hPrinter);
            }
            EndDocPrinter(hPrinter);
        }
        ClosePrinter(hPrinter);
    }

    if (bSuccess == false)
        dwError = Marshal.GetLastWin32Error();
    return bSuccess;
}

public static bool SendBytesToPrinter(string printerName, byte[] bytes, string docName)
{
    int dwCount = bytes.Length;
    IntPtr ptrBytes = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(byte)) * bytes.Length);
    try
    {
        Marshal.Copy(bytes, 0, ptrBytes, bytes.Length);
        SendBytesToPrinter(printerName, ptrBytes, dwCount, docName);
    }
    finally { Marshal.FreeCoTaskMem(ptrBytes); }
    return true;
}

public static bool SendStringToPrinter(string printerName, string str, string docName)
{
    int dwCount = str.Length;
    IntPtr ptrBytes = Marshal.StringToCoTaskMemAnsi(str);
    try { SendBytesToPrinter(printerName, ptrBytes, dwCount, docName); }
    finally { Marshal.FreeCoTaskMem(ptrBytes); }
    return true;
}
Sam
  • 1,826
  • 26
  • 58
  • Can you post enough code so that someone can reproduce the issue? https://stackoverflow.com/help/mcve – Dipen Shah Oct 24 '18 at 17:31
  • Hi Dipen, If you sent `string toPrint` to ESC printer, you will see the result. – Sam Oct 25 '18 at 07:06
  • The code itself mangled the string when it used ` Encoding.GetEncoding("Latin1").GetString(enc.GetBytes(str));`. Vietnamese is *not* Latin1 and any character that can't be converted to Latin1 is replaced by `?`. The `content` string *is already a Unicode string*. .NET uses UTF16 already. Does the *POS* support Unicode though? Was it configured to use a *specific* codepage, eg Latin1, or Vietnamese or UTF8? *How* do you send the string to the POS? – Panagiotis Kanavos Oct 30 '18 at 13:24
  • 1
    .NET's writer classes like StreamWriter use UTF8 by default. If you use a writer to write to the POS it should just work, without codepage conversions. If you copy a byte array, you *have* to find out the configured codepage. The best option would be to configure the device to use UTF8, and use `Encoding.UTF8.GetBytes()` *only* to get the correct bytes. In any other case you need to create an Encoding object, eg with `Encoding.GetEncoding(1258)` and use its `GetBytes()` method *only*, to get the correct bytes. You'll lose any characters that can't be converted to that codepage though – Panagiotis Kanavos Oct 30 '18 at 13:30
  • @PanagiotisKanavos, Unfortunately, for ESC/P print, that is they way it works. You must convert it back to Latin before sending it to ESC/P. It will then match with the selected printer's code page (the code page selected when printer is initialized). Currently, the code works for Chinese/Japanese/Thai language. – Sam Nov 01 '18 at 02:58
  • @Sam you still don't explain what that way is. Do you write directly to a serial port? Print to a text printer? Use a stream? *What* printer model are you talking about? Where is your code? And no, no printer requires that you destroy your data before sending it. That's what converting a different codepage to Latin 1 does. Chinese *won't* work, it will be mangled the moment you switch codepages. Thai won't work either. If you want to print Vietnamese, *change* the printer's codepage. – Panagiotis Kanavos Nov 01 '18 at 07:58
  • @Sam what printer, what model? Did you check the *manual, possible control codes? BTW `char.ConvertFromUtf32(94)` is just the `^` character. The code is prepending *control codes* to the string, specifically `"\x1Bt^\n"`. `\x1B` or `\u001b` are the same character, ESC. I suspect the printer expects control sequences to start with ESC and withe a newline, which means you sent the `^t` code to the printer – Panagiotis Kanavos Nov 01 '18 at 08:07
  • @PanagiotisKanavos I am using **ISSYZONE POS ITPP012**. 94 is the Printer CodePage for Vietnamese. Each printer has its own unique code page. For example, for EPSON, the CodePage is 52 for Vietnamese (https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=32). *ESC t* is used to tell the printer the code page to use. In my case above, *ESC t 94* to tell the printer to use code page 94. – Sam Nov 01 '18 at 08:16
  • In that case you should use the `1258` encoded bytes *ONLY*. Send the control sequence to the printer *first* and then send the bytes. Or concatenate the sequence and vietnamese text and convert all of them to 1258. The control secuence characters fall in the ASCII range so they won't be modified: `var bytesToPrint = enc.GetBytes("\x1Bt\x5E\n" + content);` – Panagiotis Kanavos Nov 01 '18 at 08:18
  • Don't try to conver the bytes to `string`. Strings in .NET are UTF16 and definitely *not* what the printer expects. BTW you still haven't posted the code that sends the text to the printer. Some POS printers appear as serial ports, in which case you need to write the bytes to the serial port – Panagiotis Kanavos Nov 01 '18 at 08:26
  • You can also open the printer port as if it were a file [as shown here](https://stackoverflow.com/questions/2136942/printing-in-parallel-port-dot-matrix-over-c-sharp), eg `var wr = new StreamWriter(@"\\.\LPT1"); wr.WriteLine(someString);`. You can specify an encoding in the constructor too, so you may not need to explicitly call `enc.GetBytes()`, just write out the strings, eg : `using(var wr = new StreamWriter(@"\\.\LPT1",true,enc)){ wr.WriteLine(sequence); wr.WriteLin(content);}` – Panagiotis Kanavos Nov 01 '18 at 08:36
  • @PanagiotisKanavos, I think you might have hit the nail. What I did is I convert the string back using encoding `1258` and it gave me ***?***. I'm updating my question now. – Sam Nov 01 '18 at 08:44
  • @Sam the update is no different than the original. The problem is the conversion to Latin 1. *Don't* do that. Post the code that actully writes to the printer. Why are you sending *strings* instead of the bytes? C# strings have *no codepage*, they are UTF16. – Panagiotis Kanavos Nov 01 '18 at 09:04
  • @PanagiotisKanavos, I've tried to print using `bytesToPrint = enc.GetBytes("\x1Bt\x5E\n" + content);` and it gave me the exact same result. I've updated the code on how I send bytes to printer – Sam Nov 01 '18 at 10:11
  • @PanagiotisKanavos, using `bytesToPrint = enc.GetBytes("\x1Bt\x5E\n" + content);` display garbage for Thai. The Vietnamese still contains the `?` characters :( – Sam Nov 01 '18 at 10:22

2 Answers2

0

I found the answer! It is not my code, but limitation of CodePage 1258 (see Convert text to Latin encoding and decode back problem for Vietnamese).

The question still remains, how to print in Vietnamese?

There is a guy name HICURIN that managed to solve the problem (see Print Unicode Characters to POS printer). Hopefully, he will be kindly to share his code with us.

Cheers, Sam

Sam
  • 1,826
  • 26
  • 58
-2

This is all you need to be able to correctly print out Vietnamese characters on your printouts using C#

edit your code to this

public static readonly string ESC = "\u001B";
...
...
...
//Encoding enc = Encoding.GetEncoding(1258); //vietnamese code page
string content = "Cơm chiên với các loại gia vị truyền thống làm cho lưỡi của bạn";
string toPrint = ESC + "t" + char.ConvertFromUtf32(94) + "\n"; 
// First you need to convert the vietnamese string to utf-8 bytes.
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(content); 
// Convert utf-8 bytes to a string.
toPrint += System.Text.Encoding.UTF8.GetString(utf8Bytes);

Try the above code and let me know if you still get funny characters...

indago
  • 2,041
  • 3
  • 29
  • 48
  • Hi Indago, it doesn't work :( in fact, it make it worst. Now, other languages is no longer works. Chinese no longer work and so does Thai. You must use an appropriate encoding. In Vietnamese, it is 1258. Did you managed to get to work at your end? – Sam Oct 30 '18 at 06:21
  • C# (and Windows) use UTF16 which is why the `content` can compile in the first place. It's Unicode. There's no reason to use `UTF8.GetBytes`. If the *POS* doesn't support UTF16 or UTF8 though, it will have to be configured – Panagiotis Kanavos Oct 30 '18 at 13:21
  • BTW there's no if or but about this. SO is a .NET application, which is why it can display any character, including the Vietnamese text in this question, without special processing – Panagiotis Kanavos Oct 30 '18 at 13:23
  • @PanagiotisKanavos .NET can display the character ok... Just when print it out... It works fine on Chinese, Japanese and Thai... just doesn't work on Vietnamese – Sam Nov 01 '18 at 02:52