4
votes

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;
}
2
Can you post enough code so that someone can reproduce the issue? stackoverflow.com/help/mcveDipen Shah
Hi Dipen, If you sent string toPrint to ESC printer, you will see the result.Sam
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
.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 thoughPanagiotis Kanavos
@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

2 Answers

0
votes

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

-2
votes

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