Click here to Skip to main content
15,885,278 members
Articles / General Programming / Algorithms
Tip/Trick

Printing an Image with textwrapping and Alignment in ESC/POS Thermal Printer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
14 Nov 2019CPOL1 min read 12.4K  
Algorithm for printing an bitmap image with textwrapping and alignment in ESC/POS thermal printer.

Introduction

First, I already have a receipt printer connected to Android device. As it is connected to the serial port already, I don't need to think about the connection. All I have to is only send the byte array to the printer. But this printer cannot print the Korean font since there is no Korean font in it. Furthermore, there is no time, so I needed a sample source for printing image data to receipt printer. If I could find that source, I thought I would be able to print out the Korean string to the printer.

Background

I found a source that I almost wanted by following the link below:

As you would know, if you click, you can analyze the source. And also, you have to understand that sample source code at first before learning how to work on my source below. Actually, you don't have to know all sample source. What I used is the code in functions in 'Utils' class because I will print all the text as images. You should start off from the function, 'decodeBitmap'.

You will know that this 'printPhoto' function does not print images above 250 pixels. So, I am just going to call this function per string per line.

Using the Code

The following code is the point where I start to print.

Kotlin
// I will send the Byte Array using this List sequentially per Items to Printer.
Val list: Mutablelist<bytearray> = Arraylist()

// First, Init Printer
Val Cmd_init = Bytearrayof(27, 64)
list.add(CMD_INIT)
list.add(byteArrayOf(0x1B, 0x21, 0x03))

// This is the String to print.
// I should process the character,'\n', as Line Feed.
// as you can see the Third Parameter. I should process the alignment for Text String.
Var Command = Getimagedata
              (logger, "Abcdefghijkl\n안녕하세요\nmnopqrstuvwxyg", Paint.Align.CENTER)
If(command != null) list.add((command))

// This is just for separate line. It's not important.
list.add("------------------------------------------\n".toByteArray())

// Current time printing
Val Now = System.currentTimeMillis()
Val Date = Date(now)
Val Sdfnow = Simpledateformat("시간: Yyyy/mm/dd  Hh:mm:ss a")
Val Formatdate = sdfNow.format(date)
Command = Getimagedata(logger, Formatdate)
If(command != null) list.add((command))

// Cutting command
Val Cmd_cut = Bytearrayof(0x1d, 0x56, 0)
list.add(CMD_CUT)

// Send all List Items data to printer
// You should not know about this.
// All you have to know is the codes bolded below.
Var prt: Printer? = Null

Try {
    Prt = Printer.newInstance()

    if (!prt!!.ready()) {
        logger.error("Printer Is Not Ready Before printing.")
        Return
    }

    For (I in list.indices) {
        prt.outputStream.write(list.get(i))
    }

    if (!prt.ready()) {
        logger.error("Printer Is Not Ready After printing.")
        Return
    }

} Catch (e: Throwable) {
    logger.error("An Exception Issued in Printing. $e")
} Finally {
    prt?.close()
}

The following function is 'getImageData'. This is the main code.

Kotlin
fun getImageData(logger:Logger, stringData:String, 
                 align:Paint.Align = Paint.Align.LEFT): ByteArray? {

    var command:ByteArray? = null
    try {
        val pnt: Paint = Paint()

        pnt.setAntiAlias(true)
        pnt.setColor(Color.BLACK)
        pnt.setTextSize(23f)

        // A real printlabel width (pixel)
        var xWidth = 385

        // A height per text line (pixel)
        var xHeight = 30

        // it can be changed if the align's value is CENTER or RIGHT
        var xPos = 0f

        // If the original string data's length is over the width of print label,
        // or '\n' character included,
        // it will be increased per line gerneating.
        var yPos = 27f

        // If the original string data's length is over the width of print label,
        // or '\n' character included,
        // each lines splitted from the original string are added in this list
        // 'PrintData' class has 3 members, x, y, and splitted string data.
        var finalStringDatas:MutableList<PrintData> = ArrayList()

        // if '\n' character included in the original string
        var tmpSplitList:List<String> = stringData.split('\n')
        for(i in 0..tmpSplitList.count()-1)
        {
            val tmpString = tmpSplitList[i]

            // calculate a width in each split string item.
            var fWidthOfString = pnt.measureText(tmpString)

            // If the each split string item's length is over the width of print label,
            if (fWidthOfString > xWidth)
            {
                var lastString = tmpString
                while(!lastString.isEmpty()) {

                    var tmpSubString = ""

                    // retrieve repeatedly until each split string item's length is 
                    // under the width of print label
                    while(fWidthOfString > xWidth)
                    {
                        if (tmpSubString.isEmpty())
                            tmpSubString = lastString.substring(0, lastString.length-1)
                        else
                            tmpSubString = tmpSubString.substring(0, tmpSubString.length-1)

                        fWidthOfString = pnt.measureText(tmpSubString)
                    }

                    // this each split string item is finally done.
                    if (tmpSubString.isEmpty())
                    {
                        // this last string to print is need to adjust align
                        if(align == Paint.Align.CENTER)
                        {
                            if(fWidthOfString < xWidth) {
                                xPos = ((xWidth - fWidthOfString) / 2)
                            }
                        }
                        else if(align == Paint.Align.RIGHT)
                        {
                            if(fWidthOfString < xWidth) {
                                xPos = xWidth - fWidthOfString
                            }
                        }
                        finalStringDatas.add(PrintData(xPos, yPos, lastString))
                        lastString = ""
                    }
                    else
                    {
                        // When this logic is reached out here, it means, 
                        // it's not necessary to calculate the x position
                        // 'cause this string line's width is almost the same 
                        // with the width of print label
                        finalStringDatas.add(PrintData(0f, yPos, tmpSubString))

                        // It means line is needed to increase
                        yPos += 27
                        xHeight += 30

                        lastString = lastString.replaceFirst(tmpSubString, "")
                        fWidthOfString = pnt.measureText(lastString)
                    }
                }
            }
            else
            {
                // This split string item's length is 
                // under the width of print label already at first.
                if(align == Paint.Align.CENTER)
                {
                    if(fWidthOfString < xWidth) {
                        xPos = ((xWidth - fWidthOfString) / 2)
                    }
                }
                else if(align == Paint.Align.RIGHT)
                {
                    if(fWidthOfString < xWidth) {
                        xPos = xWidth - fWidthOfString
                    }
                }
                finalStringDatas.add(PrintData(xPos, yPos, tmpString))
            }

            if (i != tmpSplitList.count()-1)
            {
                // It means the line is needed to increase
                yPos += 27
                xHeight += 30
            }
        }

        // If you want to print the text bold
        //pnt.setTypeface(Typeface.create(null as String?, Typeface.BOLD))

        // create bitmap by calculated width and height as upper.
        val bm:Bitmap = Bitmap.createBitmap(xWidth, xHeight, Bitmap.Config.ARGB_8888)
        val canvas: Canvas = Canvas(bm)
        canvas.drawColor(Color.WHITE)

        for(tmpItem in finalStringDatas)
            canvas.drawText(tmpItem.strData, tmpItem.xPos, tmpItem.yPos, pnt)

        command = Utils.decodeBitmap(bm)
    }
    catch (e: Exception) {
        logger.error(e.printStackTrace())
    }

    return command
}

The following class is 'PrintData'.

Kotlin
class PrintData (var xPos:Float, var yPos:Float, var strData:String)

History

  • 11/14/2019: First update
  • 11/15/2019: 'PrintData' class is added. Style changed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Wiseneosco
Korea (Republic of) Korea (Republic of)
You know I can't speak English well but I'm learning. If there anything wrong in my article. Understand me. Smile | :) I'm so thankful if you could correct this.
Anyway, I'm a software programmer in Korea. I have been doing development about 10 years.
I majored in Computer Science and Engineering. I'm using c# mainly. Bye!

Comments and Discussions

 
-- There are no messages in this forum --