Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / algorithm

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

5.00/5 (1 vote)
14 Nov 2019CPOL1 min read 13.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)