I've stumbled across a problem way beyond my area of expertise, and I don't have a mentor to turn to for help with this.
I have a receipt printer I need to interface with through an iOS app. The printer is located on the same network as the device(s), so I can address it through supported "Line Mode commands"
What I'd like to do is keep the code I have already that works cross-platform – i.e. it's a UIView/NSView, and if you're not familiar with OS X/iOS, it's just a standard vanilla view that I can render into PDF/PNG formats. Thankfully, the printer has a "raster graphics" mode that seems to be what I need.
Unfortunately, be it the broken English of the command spec, or my complete lack of knowledge of anything beyond basic C, or my complete lack of knowledge regarding graphics, I have no idea how to even get started from the command specifications I have. I know the printer and my networking works because I can address it over the network and send it basic feed commands. But, I have no idea how to go from a PNG -> whatever the printer needs to make it's 'raster mode' work.
The specification is available at http://www.star-m.jp/eng/service/usermanual/linemode_cm_en.pdf , and the page you'd want to start reading it if you want to help is 3-68, and the specific commands I'm having trouble even getting started with are on 3-78/3-79.
I can give you nothing but a checkmark but I assure you, you'll have my undying gratitude if you can even provide me even a point in the right direction.
Raster can be referencing images that are resolution dependent, and don't print well at other sizes. Raster is basically a rectangular group of pixels, or square dots that make up an image.
Do a hard reset on your printer. To do this you just need to turn off your printer, unplug for a few minutes and then plug the printer again. If that still doesn't work, try turning the printer and your computer off, and then start it back up again. Try uninstalling and then reinstalling your printer driver.
Highlight the text you want to print on the page and press Ctrl + P on a PC or Command + P on an Apple computer to open the print options.
This component is based on the print styles from HTML5 Boilerplate. It strips your document from background colors and changes font colors to black to save printer ink. Pictures will be scaled down to fit the page and anchors are underlined to distinguish them from regular text.
Having written a few printer drivers I can confirm that generally the documentation is confusing because of the way printers work. The document that you refer to doesn't actually seem to bad to me.
I think you're right to be printing in raster mode and that overall this is going to give the best results.
From the Star documentation I reckon you'll need to send :
1. \x1b*rR Initialize raster mode
2. \x1b*rA Enter raster mode
3. \x1b*rC Clear raster data
4. \x1b*rml
4. b\x##\x##\xAA\xAA\xAA....<DATA>..........
5. \x1b\x0C\x00 Raster Form feed(??) - should spit out the data.
6. \x1b*rB Clear raster data
Obv. in the above \x1b is the C encoding of ESC (i.e. character 27 0x1b).
From all of the documentation that I've been reading the following is how the images should be formatted in raster mode. When in line mode it is completely different as the vertical & horizontal are swapped. From THERMAL PRINTER PROGRAMMER'S MANUAL (TSP552,TSP552II,TSP2000)
This equates to the following bytes stream.
On the 4th command line it is effectively 'b' followed by two bytes definining the size. This size is computed as the number of pixels contained in the stream % 256 and / 256. So for 320x1 that'd 0x40,0x01
So, taking the above and plugging it into a simple test program you should test with this:
char rasterImage [] = {
0x1b, '*', 'r', 'R', // Initialize raster mode
0x1b, '*', 'r', 'A', // Enter raster mode
0x1b, '*', 'r', 'C', // Clear raster data
// n1 n2 d1 d2..
0x1b, 'b', 0x2, 0, 0x00, 0x00, // data
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x3F, 0xFC,
0x1b, 'b', 0x2, 0, 0x77, 0xEE,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0x0F, 0xF0,
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x3E, 0x7C,
0x1b, 'b', 0x2, 0, 0x38, 0x1C,
0x1b, 'b', 0x2, 0, 0x79, 0x9E,
0x1b, 'b', 0x2, 0, 0x73, 0xCE,
0x1b, 'b', 0x2, 0, 0x73, 0xCE,
0x1b, 'b', 0x2, 0, 0xF9, 0x9F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xFE, 0x7F,
0x1b, 'b', 0x2, 0, 0xFF, 0xFF,
0x1b, 'b', 0x2, 0, 0xFF, 0xFF,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00};
[self.currentDataBeingSent appendBytes:rasterImage length:sizeof(rasterImage)];
Simply squirt that out to the printer and you should get a picture as above. This is where you can easily tweak and play about with the exact commands to get something that's working. Often this is the only way I've ever managed to figure out what should be done.
Ref. comments.
If you have a byte per pixel then you will need to merge these into a series of bits; the following should do the job based on your pastebin code. I've also changed the char*
to be unsigned as it is signed can cause problems when bit manipulating.
NSUInteger bitmapBytePerRow = width/8;
NSUInteger bytesPerRow = 3 + bitmapBytePerRow;
[self.currentDataBeingSent = [NSMutableData dataWithLength:bytesPerRow * height];
[self.currentDataBeingSent appendBytes:initializeRaster length:sizeof(initializeRaster)];
[self.currentDataBeingSent appendBytes:enterRaster length:sizeof(enterRaster)];
NSUInteger byteOffset = 0;
for (NSUInteger y = 0; y < height; y++)
{
unsigned char *rasterCommandForRow = (unsigned char *)calloc(bytesPerRow, sizeof(char));
unsigned char *current_raster = rasterCommandForRow;
*current_raster++ = '\x6B';
*current_raster++ = (width*height) % 256;
*current_raster++ = (width*height) / 256;
unsigned char mask = '\x80' ;
unsigned char out = 0 ;
for (NSUInteger x = 0; x < width; x++)
{
if (*(data + (byteOffset * sizeof(char))))
out |= mask ;
byteOffset++;
mask >>= 1 ;
if( 0 == mask )
{
mask = '\x80' ;
*current_raster++ = out ;
if( out )
lastDot = nextOut ;
out = 0 ;
}
}
// handle partially finished byte .
if( ( '\x80' != mask ) && ( 0 != out ) )
*current_raster++ = out ;
[self.currentDataBeingSent appendBytes:rasterCommandForRow length:bytesPerRow];
}
Looking at the Mac CUPS support from Star it's got the source code for the driver which contains a lot of clues about how this should be done. Sometimes code is so much easier to read than documentation.
starcupsdrv-3.1.1_mac_20100423.zip\starcupsdrv-3.1.1_mac\SourceCode\
contains starcupsdrv-src-3.1.1.tar.gz\
sub folder starcupsdrv\src\
View rastertostar.c, the important bit is the calculation of the n1 / n2 values. These aren't at all X & Y but based on the pixel count, lastBlackPixel is the count of pixels from the source.
putchar('b');
putchar((char) ((lastBlackPixel > 0)?(lastBlackPixel % 256):1));
putchar((char) (lastBlackPixel / 256));
I've modified the code above to include the fixes, hopefully that'll be closer. If not post a scan of what comes out of the printer, it will be useful to diagnose what's happening.
For reference The code between 580:650 from jsStarUSB.cpp seems to me to be along the lines of what you need to produce a buffer (stored in nextOut
) that contains the raster data in the format to be sent directly to the printer.
I have a hunch this might be the same as the old Seiko printers, only yours is network enabled. If so, have a look at the C code here. It tries to output to a serial port /dev/cua, where it thinks the printer is.
But if the commands are the same, the code should help you. It takes as input the Portable Bitmap Format, which is plain ASCII text.
But I don't know. Microsoft indicates Star Micronics works the same as Epson LQ, in which case there is ample documentation.
Related links:
ESC/POS PDF Documentation, hundreds of pages
Command codes from the STAR website
Update! ;-) Try this, totally untested code:
/* Call with grayscale images of height 256, width 256. */
- (void) outputraster(char* pixels, int rows)
{
const char initializeRaster[] = "\x1B\x2A\x72\x52";
const char enterRaster[] = "\x1B\x2A\x72\x41";
const char formFeed[] = "\x1B\x0C\x00";
const char clearRaster[] = "\x1B\x2A\x72\x43";
const char exitRaster[] = "\x1B\x2A\x72\x42";
/* The FF means 255 lines: */
char setRasterPageLength[] "\x1B\x2A\x72\x50\xFF\x0";
/* The FF FF means 256 lines and 256 rows: */
char sendRasterData[] = "\x62\xFF\xFF";
[self sendBytes:initializeRaster ofLength:sizeof(initializeRaster)];
[self sendBytes:enterRaster ofLength:sizeof(enterRaster)];
[self sendBytes:clearRaster ofLength:sizeof(clearRaster)];
[self sendBytes:setRasterPageLength ofLength:sizeof(setRasterPageLength)];
[self sendBytes:sendRasterData ofLength:sizeof(sendRasterData)];
while (rows)
{
for (int x = 0; x < 255; x++)
{
[self sendBytes:pixels[x] ofLength:256];
}
rows --;
}
}
Update!
Explanations of bitmap format for a similar printer:
Also, look at pages 34 and on for an explanation of the bitmap format of a Star printer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With