LA.NET [EN]

Feb 13

[You can dowload some demo code at the end of the post]


Last week I had to find a way to print to  TLP 2844 Zebra printer from a C# Windows Forms app. For those that don”t know, I”m speaking about a thermal transfer printer which is normally used for printing bar codes in labels. I had to perform the following tasks:


  1. write text in the label;
  2. write bar codes to the label;
  3. print images in the label.

This specific printer understands EPL2 and expects to receive one or more commands in text. If you go through the manual, you”ll see that there”s several things you can do, like build forms (which are useful when you”re using variables), print barcodes and even print images (which is really the hard part). For instance, if you want to print Testing, you”ll have to send something like this to the printer:


A10,10,0,0,1,1,1,N,”Testing”


If you have zebra and want to run a simple test, then save the following to a txt file:


<-
N<-
A10,10,0,0,1,1,1,N,”Testing”<-
P1<-


The previous file has a blank empty line followed by the N command (which clears the buffer of the printer)and then prints one copy (P1) with the text Testing at 10,10 (x,y). Sending the previous file to the printer is as simple as copying it to LPT1 (assuming you”ve installed your printer in that port):


copy demo.txt lpt1:


After this short introduction, it”s easy to see that we need to:


  • abstract the printer commands
  • write some code that communicates with the printer 

Abstracting the printer commands, can be easily done. In my case, I”ve just created an interface that looks like this:


public interface ICommand
{
    string GetCommandString();
}


and then it was just a matter of creating several classes that implement this interface. For instance, the PrintTextCommand looks like this:


public class PrintTextCommand:BasePositioning, ICommand
{
   private string _txt;
   private int _fontSelection;
   private PrintingRotation _rotation;
   private PrintingMultiplier _horizontalMultiplier;
   private PrintingMultiplier _verticalMultiplier;
   private PrintingReverse _printingReverse;
  


    public PrintTextCommand( int x, int y, string txt, PrintingRotation rotation, int fontSelection,
         PrintingMultiplier horizontalMultiplier, PrintingMultiplier verticalMultiplier, PrintingReverse printingReverse)
        :base(x, y)
    {
       _txt = txt;
       _fontSelection = fontSelection;
       _rotation = rotation;
       _horizontalMultiplier = horizontalMultiplier;
       _verticalMultiplier = verticalMultiplier;
       _printingReverse = printingReverse;
   } 


  public PrintTextCommand( int x, int y, string txt, int size)
     :this(x, y, txt, PrintingRotation.NoRotation, size, PrintingMultiplier.One,
                PrintingMultiplier.One, PrintingReverse.N)
  {}

  public PrintTextCommand( int x, int y, string txt)
     :this(x, y, txt, 2)
  {}

  #region ICommand Members

   string ICommand.GetCommandString()
   {
        return string.Format(“A{0},{1},{2},{3},{4},{5},{6},”{7}”n”,
            _pt.X, 
            _pt.Y,
            (int) _rotation,
            _fontSelection,
            (int)_horizontalMultiplier,
            (int)_verticalMultiplier,
            PrintingReverseMapper.Map(_printingReverse), 
           _txt);
   }

  #endregion
}

Since all of the commands need to specify their location, I”ve created a base class (BasePositioning) which is only used to hold the topleft point used by all the commands (yes, I”ll probably burn in hell for not defining a protected property and letting the derived classes access the internal field directly :)). Now, the most difficult of the commands was the one responsible for printing the image. When you think about zebra and images, you have 2 options: you can load the image in the printer”s memory or can just send it each time you need to print it. In my case, I decided to go with the 2nd option since I really dind”t want to go to all the sites where the code was going to be used in order to add the image to the memory of the printer (nor was I in the mood to write an installer that did just that).

Unfortunately, the EPL2 manual falls short and really won”t help you much when you decide to use the GW command. Don”t believe me, here”s what it has to say about sending graphics to the printer:

GW Command – Direct Graphic Write

Description: Use this command to load binary graphic data directly into the Image Buffer memory for immediate printing. The printer does not store graphic data sent directly to the image buffer. The graphic data is lost when the image has finished printing, power is removed or the printer is reset. Commands that size (Q and q) or clear (N and M) the image buffer will also remove graphic image data.


Syntax: GWp1,p2,p3,p4DATA


btw, note that it”s missing the , char after p4. as you”d expect, p1 and p2 is the top-left coordinate. p3 is supposed to be the width of the image in bytes and p4 its height. Well, they don”t even give you a simple example of how to use this command. After loosing some time trying to find a sample, i thought it was time to learn something about images. Bob Powell”s site was great because it gave me what i needed: several samples and FAQs that explained several aspects related with GDI+ and graphics – it even a sample on how to convert an image to a 1bpp image (which is really the only kind of image i was interested in).


Going back to the command help, it said that p3 was the width of an image in bytes. After going through Bob”s FAQ, it was clear that after creating a Bitmap you could call the LockBits method to lock the bitmap in memory. When you do that, you end up receiving and instance of BitmapData which has several members that are important for getting the info I needed to send to the printer. For instance, the class exposes a property called Stride which returns the correct length in bytes of each line of the bitmap image (right what was needed for the p3 parameter!). The stride is important because without it you don”t know where each line of the bimap starts and ends (don”t forget that, in memory, the bitmap is just a plain array- Bob explains it well, so go there to get more info on what I mean). Before you start thinking that  getting the stride is a waste of time because you already know the width of the image in pixels, don”t forget that you also need to know the pixel format you”re using in order to get that width in bytes(for instance, in my case I got lucky because I only needed to print 1bpp images). Another thing you need to keep in mind is that the stride will always be equal or bigger than the width of the image in bytes. That”s because strides are always multiples of 4 bytes (at least, in 32 bits machines). If you”re really lucky, you”ll get images whose width (in bytes) is also a multiple of 4. If that doesn”t happen, then you do need to write more code to get the correct result printed in the Zebra. But I”ll speak about this in a minute.


So, since I had all the info I needed, it was time to write the Print1bppImageCommand. I”m only showing the GetCommandString method:


string ICommand.GetCommandString()
{
  BitmapData bits = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height),
  ImageLockMode.ReadOnly, _bmp.PixelFormat);


  byte[] imageBytes = new byte[bits.Height*bits.Stride];
  Marshal.Copy(bits.Scan0, imageBytes, 0, bits.Stride*bits.Height);

  int imgWidth = bits.Stride;
  int imgHeight = bits.Height;
  _bmp.UnlockBits(bits);

  return string.Format(“GW{0},{1},{2},{3},{4}n”,
        _pt.X,
        _pt.Y,
        imgWidth.ToString(),
        imgHeight.ToString(),
        Encoding.GetEncoding(1252).GetString(imageBytes));

}

As you can see, it”s really simple: After locking the bitmap, I create a new byte array and use the Marshal.Copy method to copy all the image bits into that array. Yes, I could have optimized the code and worked with direct pointers. I”ll leave that to you though ;)


what”s important to note is that I use the stride and the height of the bitmap to get the correct size of the buffer used to hold the bytes of the image. Another important thing: I”m using the 1252 encoding in order to copy the byte array to the string that is going to be sent to the printer (if you try to use ASCII, it simply won”t work – I guess that I could try to use the ANSII encoding – value 0, if i”m not mistaken – but since it worked well with this, I just went along and used it).


The 1st image I”ve used printed without any problems…since “I was the king of the world”, I decided to run a small demo for fellow workers. Guess what: I decided to choose another image and after printing it, i noticed a weird black bar on the right. what could it be? Remember me saying that the stride is always bigger or equal to the image width (in bytes)? Yep, that was the problem. You see, the 1st image was a “good one” because the stride was equal to its width. That didn”t happen in the 2nd case. In my case, solving this problem is easy. Since I”m printing 1bpp images and i was sure that their size was a multiple of 8 (since I”m using 1 bit per pixel image, that means that 8 pixels will be stored in a byte), I knew that i only needed to “clear” those extra bytes. In this case, clearing means setting the color to white (which really means setting the byte in the array to 255). That”s why I inserted the following code before the string.Format:


int realWidth = bits.Width/8; //only works for fixed size 1bpp images where width % 8 == 0
if( realWidth != imgWidth )
{
  int bytesToClear = imgWidth – realWidth;
  for( int i = 0; i < imgHeight; i++ )
  {
     int pos = realWidth + imgWidth * i;
     for( int counter = 0; counter < bytesToClear; counter++, pos++ )
     {
        imageBytes[pos] = 255;
     }
  }
}


And that”s it! My 1bpp image is getting correctly printed. If you have a colored image, then you need to go to Bob”s site and see how you can convert it to a 1bpp pixel. Btw, note that if you do need to do that, you might also need to clear individual “bits” (instead of bytes).


After finishing these commands, I only needed to create the Label base class that is responsible for letting you define several ICommands which can be sent to the printer.


After having this code working, it was only a matter of building the correct code for sending it to the printer. This time, I got lucky and found this article in codeproject. I just needed to change it slightly so that it used the same enconding I was using to save the image bytes in the string that is going to be sent to the printer.


You can download my wrappers and demo code from here.

34 comments so far

  1. rkm
    12:26 pm - 2-20-2007

    hi, i”m developing an application and yesterday i receive the nice zebra tlp 2844… :S

    i have ti print label on it, only text and i am working in visual studio 2005 in C#,,, muy question… do i need some dll to make the printer work, or i should do like your example, writing a text file o sending to the printer… i”m confused and afraid… thankz

  2. rkm
    1:22 pm - 2-20-2007

    i”ve tried your code, but it give”s me this
    {“Identificador no válido.rnNombre del parámetro: handle”}

    not valid identifier. parameter name: handle

    :(

  3. Luis Abreu
    4:32 pm - 2-22-2007

    Hello.

    the code I”ve written won”t work if you try to print to the usb port. on the other hand, if you have the printer on the usb port, you can share it and then you can pass the name of the shared printer to the printing code (ex.:\machinenamesharedprintername).

    btw, note that the sample writes to a file only for tests. you can remove that line and send it directly to the printer by using the interop code.

  4. rkm
    8:25 am - 2-25-2007

    i got problems now printing several labels

  5. Luis Abreu
    9:35 am - 2-26-2007

    hello.

    what sort of prpblems? I had some problems until i got the size of the label right. after that, i”m able to print several labels at a time…

  6. Bubba Ole
    11:01 pm - 3-16-2007

    How do I reduce the size of the file to less than 64 k?
    I send in 21 to 54 k and the label size baloons to megabytes and the printer would just ignore it.

  7. Husain
    8:00 am - 4-15-2007

    Hi,

    I tried your approach and it works very nicely.. my only issue is how do I get information back from the printer.. some commands return values from the printer.. any ideas?

    Thanks!
    Husain

  8. Cale
    10:09 pm - 4-18-2007

    It seems to be sending the data to the printer but all i”m getting is a flashing data light. Its a ZebraZ4M Plus.

  9. Tom
    9:13 am - 4-20-2007

    Thx for your article, works great with my Zebra S4M

  10. ladyada
    4:17 am - 5-25-2007

    wow exactly what i need, ive been struggling w/python & GW for 2 hours trying to setup a nice backend fedex label printing thingy.

    ps. typo in your first example, should be ”A20,20,0,1,1,1,N,”Testing”” (only 7 args)

  11. ladyada
    4:21 am - 5-25-2007

    (or maybe its only a typo if you”re using an LP2844? dunno if TLP”s have different syntax)

  12. Luis Abreu
    8:11 am - 5-25-2007

    Husain:

    hum…can you give me an example of command that works like that? I”m not a zebra guru, so i thought that when a command returned info, it would do that by priting it in a label.

    ladyada:
    i wrote the code for the EPL2 programming language, which means that if your zevra understands it, it should work without any problems. i”ve jus topened the EPL2 manual and this is what”s shown there for the A command:

    Syntax Ap1,p2,p3,p4,p5,p6,p7,”DATA”

    Are you saying that your zebra understands epl2 but this command doesn”t work?

  13. sherwinzhu
    8:02 pm - 5-27-2007

    I use “\machinenamesharedprintername” in the code, but the exception is also throw. why?
    Can you explain the “_printerPath” para of CreateFile method. I am also confused with the port of printer, how can I user the remote printer?

    I am a chinese student, my english is not well.

    Thank you!
    sherwinzhu@buaa.edu.cn

  14. Luis Abreu
    8:23 pm - 5-27-2007

    hello.

    hum…i think that when i”ve written the code, it worked without any problems with shared printers. the _printerPath field is supposed to indicate the path to the printer (for instance, you can use LPT1 if you have the printer attached to your pc and you normally instantiate it through the constructor of the class). the _port is used to keep the safe file handle used by the code.

    btw, do note that this class will not print if the printer is connected through the usb port (in that case, you can simply share the printer and use the printer”s shared name to make it work).

  15. Luis Abreu
    8:24 pm - 5-27-2007

    hello.

    hum…i think that when i”ve written the code, it worked without any problems with shared printers. the _printerPath field is supposed to indicate the path to the printer (for instance, you can use LPT1 if you have the printer attached to your pc and you normally instantiate it through the constructor of the class). the _port is used to keep the safe file handle used by the code.

    btw, do note that this class will not print if the printer is connected through the usb port (in that case, you can simply share the printer and use the printer”s shared name to make it work).

  16. Luis Abreu
    8:24 pm - 5-27-2007

    hello.

    hum…i think that when i”ve written the code, it worked without any problems with shared printers. the _printerPath field is supposed to indicate the path to the printer (for instance, you can use LPT1 if you have the printer attached to your pc and you normally instantiate it through the constructor of the class). the _port is used to keep the safe file handle used by the code.

    btw, do note that this class will not print if the printer is connected through the usb port (in that case, you can simply share the printer and use the printer”s shared name to make it work).

  17. sherwinzhu
    9:03 pm - 7-18-2007

    I send these command to printer,
    but it doesn”t work.
    The printer can”t start to print, unless i send another bar code command which can work.

    Can you help me?

    I want to print text only on a label using Zebra 888.

    sherwinzhu@buaa.edu.cn

    command code

    N
    Q880,30
    q800
    I2
    A260,10,0,8,1,1,N,”? ? ? ? ? ?”
    A70,50,0,8,1,1,N,”??”
    A300,50,0,8,1,1,N,”??”
    A500,50,0,8,1,1,N,”??”
    A620,50,0,8,1,1,N,”????”
    A20,84,0,8,1,1,N,”??????”
    A188,84,0,8,1,1,N,”??????????”
    A532,91,0,3,1,1,N,”1″
    A556,91,0,3,1,1,N,”8888888888888888″
    A188,118,0,8,1,1,N,”???????”
    A20,152,0,8,1,1,N,”ABCDEFG”
    A188,152,0,8,1,1,N,”abcdefgabcdefg”
    A532,159,0,3,1,1,N,”1″
    A556,159,0,3,1,1,N,”8888888888888888″
    A188,186,0,8,1,1,N,”abcdef”
    A20,220,0,8,1,1,N,”abcdefg”
    A188,220,0,8,1,1,N,”ABCDEFGABCDEFG”
    A532,227,0,3,1,1,N,”1″
    A556,227,0,3,1,1,N,”8888888888888888″
    A188,254,0,8,1,1,N,”ABCDEF”
    A20,288,0,8,1,1,N,”abc???”
    A188,288,0,8,1,1,N,”ABCDEFGabcdefg”
    A532,295,0,3,1,1,N,”1″
    A556,295,0,3,1,1,N,”8888888888888888″
    A188,322,0,8,1,1,N,”?????”
    A20,356,0,8,1,1,N,”??????”
    A188,356,0,8,1,1,N,”??????????”
    A532,363,0,3,1,1,N,”1″
    A556,363,0,3,1,1,N,”8888888888888888″
    A188,390,0,8,1,1,N,”???????”
    A20,424,0,8,1,1,N,”ABCDEFG”
    A188,424,0,8,1,1,N,”abcdefgabcdefg”
    A532,431,0,3,1,1,N,”1″
    A556,431,0,3,1,1,N,”8888888888888888″
    A188,458,0,8,1,1,N,”abcdef”
    A20,492,0,8,1,1,N,”abcdefg”
    A188,492,0,8,1,1,N,”ABCDEFGABCDEFG”
    A532,499,0,3,1,1,N,”1″
    A556,499,0,3,1,1,N,”8888888888888888″
    A188,526,0,8,1,1,N,”ABCDEF”
    A20,560,0,8,1,1,N,”abc???”
    A188,560,0,8,1,1,N,”ABCDEFGabcdefg”
    A532,567,0,3,1,1,N,”1″
    A556,567,0,3,1,1,N,”8888888888888888″
    A188,594,0,8,1,1,N,”?????”
    A20,628,0,8,1,1,N,”??????”
    A188,628,0,8,1,1,N,”??????????”
    A532,635,0,3,1,1,N,”1″
    A556,635,0,3,1,1,N,”8888888888888888″
    A188,662,0,8,1,1,N,”???????”
    A20,696,0,8,1,1,N,”ABCDEFG”
    A188,696,0,8,1,1,N,”abcdefgabcdefg”
    A532,703,0,3,1,1,N,”1″
    A556,703,0,3,1,1,N,”8888888888888888″
    A188,730,0,8,1,1,N,”abcdef”
    A20,764,0,8,1,1,N,”abcdefg”
    A188,764,0,8,1,1,N,”ABCDEFGABCDEFG”
    A532,771,0,3,1,1,N,”1″
    A556,771,0,3,1,1,N,”8888888888888888″
    A188,798,0,8,1,1,N,”ABCDEF”
    P1,1

  18. Wilson
    6:16 am - 7-30-2007

    I tried to save .pcx graphic into printer with GM command. But it looks no working. Could you pls tell me how to save graphic?

  19. jxp
    7:42 am - 11-8-2007

    what is PrintingRotation and PrintingMultiplier ?

  20. anchoret
    8:39 am - 11-8-2007

    how to print chinese font?

  21. Alessandro
    4:07 pm - 11-26-2007

    Luís,

    Muito obrigado, seu código foi de grande ajuda.

    Abraços,
    Alessandro

  22. Steve
    4:04 am - 1-11-2008

    Thank you for this example!

    Something interesting happens when I change the size (fontSelection) from 4 to 5, it jumps in size to gigantic. I”m trying to understand how to get a little bigger than size 4.

  23. Steve
    4:14 am - 1-11-2008

    I figured it out. I set the size to 2 and both PrintingMultipliers to two.

    Thank you again for the wonderful example!

  24. xOR
    9:48 am - 4-10-2008

    HI,

    i have tlp 2844 and working with Serbian language, but my printer can”t print some serbian characters. c,c,ž etc.
    I”ve tried the I command (codepage1250) like:
    I8,B,001

    but it still doesn”t print. Can someone help me with this please?

    Thanks.

  25. navneet
    11:11 am - 8-6-2008

    can u pls tell me how to print images ?
    actually in my case images are coming dynamically from data and i cant send their hexa decimal code every time???/

    any idea???pls help

  26. Eduardo Xavier
    10:09 pm - 8-20-2008

    Thanks for you code. It”s very smart and somehow simple. I just took some time to figure 180º degree issues out but now I just had printed what I want.

    Best

  27. Magno
    11:31 pm - 12-10-2008

    Hi, anyone have any idea in how print in tlp 2844 the code bar dun-14 (ean-14) ??? Thanks in advance.
    Magno
    magno_jr@terra.com.br

  28. Anita
    4:37 pm - 4-1-2009

    Hi! I”m spanish … I don”t speak english but i need you say: thank you, thank you very much!!!! you are the best!

  29. ANthony Benavidez
    1:37 pm - 4-8-2009

    Instead of using the Zebra Driver, change the driver to the Windows “Generic/Text” printer driver. You can the spool your raw EPL or ZPL code directly to the printer. You can test this by putting your raw code into a flat text file and then use the “COPY” command from a DOS prompt, as follows:
    copy c:testepl.txt LPT1
    Also, I would omit any formatting command, such as label size, darkness, speed, etc… You are better off setting defaults within the printer itself, instead of trying to do it programatically.

  30. Julia
    6:42 am - 4-13-2009

    IZebraPrinter.PreparePrinter()
    {
    _output = new FileStream(_port, FileAccess.Write);
    }

    I tried to run with your demo, but encountered the error on the above with prompt: invalid handle
    ======================================
    can kindly give me help with it?
    Julia

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>