Printing Chinese and Special Symbols with Zebra Printers
Zebra printers in China usually come with SimSun font, which can handle Chinese characters without issues. However, it was discovered that it couldn't print chemical formulas with subscripts like C₁₂H₁₆Cl₂N₂
.
Also, for those accustomed to seeing sans-serif fonts on phones and computers, SimSun might feel unfamiliar. This article will also teach you how to upload Source Han Sans (called Noto Sans CJK by Google), which is an open-source sans-serif font family developed in collaboration between Adobe and Google.
Checking Characters in Fonts
Before downloading fonts to the printer, you can use the "Character Map" tool in Windows. Select the corresponding font and input the Unicode of the character.
For example, the Unicode for subscript 1 ₁
is U+2081
. As shown in the image above, this font doesn't have the corresponding character code. The closest to it is U+20A9
.
Let's look at another font that has this character:
Android System Integration for Printers
Printers can be connected to Android systems via USB, Bluetooth, or network. Download the Android development SDK for the printer from the official website. Here's a shared code snippet for printer discovery:
package com.zhaoxinsoft.lab.doamin.setting
import android.content.Context
import com.zebra.sdk.btleComm.BluetoothLeDiscoverer
import com.zebra.sdk.printer.discovery.BluetoothDiscoverer
import com.zebra.sdk.printer.discovery.DiscoveredPrinter
import com.zebra.sdk.printer.discovery.DiscoveryHandler
import com.zebra.sdk.printer.discovery.NetworkDiscoverer
import com.zebra.sdk.printer.discovery.UsbDiscoverer
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
class ZebraPrinterDiscover(private val context: Context) {
enum class PrinterType {
USB, BLUETOOTH, NETWORK, BLUETOOTH_LE
}
/**
* Discover printers by specified type, this function should be called in Dispatchers.IO
* @param type Printer type
*/
suspend fun discover(type: PrinterType): Result<List<DiscoveredPrinter>> =
suspendCancellableCoroutine { continuation ->
val printers = mutableListOf<DiscoveredPrinter>()
val handler = object : DiscoveryHandler {
override fun foundPrinter(printer: DiscoveredPrinter) {
printers.add(printer)
}
override fun discoveryFinished() {
if (continuation.isCancelled) return
continuation.resume(Result.success(printers))
}
override fun discoveryError(message: String) {
if (continuation.isCancelled) return
continuation.resume(Result.failure(Exception(message)))
}
}
when (type) {
PrinterType.USB -> UsbDiscoverer.findPrinters(context, handler)
PrinterType.BLUETOOTH -> BluetoothDiscoverer.findPrinters(context, handler)
PrinterType.NETWORK -> NetworkDiscoverer.findPrinters(handler)
else -> BluetoothLeDiscoverer.findPrinters(context, handler)
}
continuation.invokeOnCancellation {
continuation.resume(Result.failure(Exception("Cancelled")))
}
}
}
Font Download
Font Preparation
Zebra printers support TTF fonts, but many TTF fonts downloaded from the internet are variable TTFs (one file containing multiple weights). For example, the simplified Chinese version NotoSansSC[wght].ttf
downloaded from here.
Install the fonttools
tool to extract the desired weight:
pip install fonttools
Enter the following command to export the variant table:
ttx -t fvar "NotoSansSC[wght].ttf"
This will generate a NotoSansSC[wght].ttx
file, which is an XML file specifying the weights included in the font file. The larger the number, the bolder the font. Here we'll choose 400:
fonttools varLib.instancer "NotoSansSC[wght].ttf" wght=400 -o "NOTOSC_400.ttf"
This extracts one weight from the variable font, and this file can be used to send to the printer.
Downloading Fonts
When we say "download" here, we mean uploading the font to the printer, which might be clearer to understand as "uploading".
Use commands to download font files. Here's an example in Kotlin:
// Upload NOTOSC_400.ttf
val file = Helper.getFileFromAssets(context, "zebra/NOTOSC_400.ttf")
val zpl = "~DYE:NTSANSSC.TTF,B,T,10238812,,"
// Note: Font file names should not exceed 8 characters to avoid strange issues. The number is the file size, followed by two commas which cannot be omitted.
conn.sendAndWaitForResponse(
zpl.toByteArray(), 0, 0, null
)
_printer?.sendFileContents(file.absolutePath) { written, total ->
Log.d(TAG, "NTSANSSC.TTF Uploading $written/$total")
}
You can also use the Windows printer properties window. First use "Send Command", noting that the string of numbers is the size of the font file:
Then use "Send File" to transfer the selected file.
ZPL Printing Labels
Let's look at an example directly. Here's a Kotlin example for Android development:
val zpl = """
^XA
^MMT
^PW839
^LL655
^LS0
^CWR,E:NTSANSSC.TTF
^CWL,E:NTSANS_L.TTF
^SEE:GB18030.DAT^CI26
^FT1,100^ARN,73,74^FB838,1,0,C^^FDEthylenediamine Salt^FS
^FPH,6^FT0,200^ALN,66,65^FB839,1,35,C^^FDC₁₂H₁₆Cl₂N₂^FS
^BY6,3,257^FT84,525^BCN,,N,N,,A
^FD40050010000003^FS
^FT0,620^ALN,56,56^FB838,1,30,C^FD40050010000003^FS
^PQ1,,,Y
^XZ
"""
_printer?.connection?.write(zpl.toByteArray(Charset.forName("GB18030")))
Note that in ZPL, you need to use ^SEE:GB18030.DAT^CI26
to load the encoding file, and when sending the command, you should also use GB18030
encoding.
Here's a Python example:
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.1.29', 9100))
zpl = '''
^XA
^MMT
^PW839
^LL655
^LS0
^CWR,E:NTSANSSC.TTF
^CWL,E:NTSANS_L.TTF
^SEE:GB18030.DAT^CI26
^FT1,100^ARN,73,74^FB838,1,0,C^FDEthylenediamine Salt^FS
^FPH,6^FT0,200^ALN,66,65^FB839,1,35,C^FDC₁₂H₁₆Cl₂^FS
^BY6,3,257^FT84,525^BCN,,N,N,,A
^FD40050010000003^FS
^FT0,620^ALN,56,56^FB838,1,30,C^FD40050010000003^FS
^PQ1,,,Y
^XZ
'''
s.send(zpl.encode(encoding='gb18030'))
s.close()
Finally, here's the printing effect:
Common ZPL Command Reference
^XA^HWE:*.TTF^XZ
Output TTF font file list, results not printed on paper^XA^IDE:MIC000.TTF^XZ
Delete specified file~JR
Power-On Reset Used to reset all internal printer software, perform Power-On Self Test (POST), clear cache and DRAM, and reset communication parameters and defaults. The ~JR command achieves the same function as a manual power-on reset. (This works wonders when commands seem to be disobeyed)