src/ptouch.c

Fri, 25 Sep 2009 10:50:44 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Fri, 25 Sep 2009 10:50:44 +0100
changeset 21
629637abfe1f
parent 20
f8ca98e5f586
permissions
-rw-r--r--

added dots-per-inch to status readback

     1 /****************************************************************************
     2  * Project:   P-touch printer driver library
     3  * Developer: Philip Pemberton
     4  * Purpose:   Make Brother P-touch (PT-series) printers do something besides
     5  *            gather dust.
     6  *
     7  *            Currently supports:
     8  *              PT-2450DX
     9  ****************************************************************************/
    11 // TODO: disable
    12 //#define DEBUG
    14 // This debug option forces Request Status to always "see" a good status
    15 // block. Mostly useful for testing using write-to-file mode.
    16 #define DEBUG_SKIP_STATUS_READ
    18 #include <stdio.h>
    19 #include <stdlib.h>
    20 #include <stdbool.h>
    21 #include <string.h>
    22 #include <gd.h>
    23 #include "hexdump.h"
    24 #include "ptouch.h"
    26 #define ESC 0x1b
    28 pt_Device *pt_Initialise(char *path)
    29 {
    30 	pt_Device	*dev;
    31 	FILE		*prn;
    33 	// Try and open the printer device
    34 	prn = fopen(path, "r+b");
    35 	if (prn == NULL) {
    36 		return NULL;
    37 	}
    39 	// Printer device open, send an init command and read the status
    40 	fprintf(prn, "%c%c", ESC, '@');
    42 	// Allocate memory for the device block
    43 	dev = malloc(sizeof(pt_Device));
    44 	if (dev == NULL) {
    45 		fclose(prn);
    46 		return NULL;
    47 	}
    49 	// Store the file pointer
    50 	dev->fp = prn;
    52 	// Memory allocation OK, now get the printer's status
    53 	if (pt_GetStatus(dev) != PT_ERR_SUCCESS) {
    54 		// GetStatus failed, close the device and exit.
    55 		pt_Close(dev);
    56 		return NULL;
    57 	}
    59 	// Set printing parameters to defaults --
    60 	//   Mirror off
    61 	//   Autocut off
    62 	//   Separator ticks off
    63 	dev->mirror = false;
    64 	dev->autocut = false;
    65 	dev->separator = false;
    67 	return dev;
    68 }
    70 int pt_GetStatus(pt_Device *dev)
    71 {
    72 #ifdef DEBUG_SKIP_STATUS_READ
    73 	unsigned char buf[32];
    74 	memset(buf, 0x00, 32);
    75 	buf[0] = 0x80;
    76 	buf[1] = 0x20;
    77 	buf[2] = 0x42;
    78 	buf[3] = 0x30;
    79 	buf[4] = 0x4b;
    80 	buf[5] = 0x30;
    81 	buf[10] = 0x0c;
    82 	buf[11] = 0x01;
    83 #else
    84 	// REQUEST STATUS
    85 	fprintf(dev->fp, "%c%c%c", ESC, 'i', 'S');
    87 	// Read status buffer from printer
    88 	unsigned char buf[32];
    89 	int timeout = 128;
    90 	do {
    91 		fread(buf, 1, 32, dev->fp);
    92 	} while ((buf[0] != 0x80) && (timeout-- > 0));
    94 	// Check for timeout
    95 	if (timeout == 0) {
    96 		// Timeout
    97 		return PT_ERR_TIMEOUT;
    98 	}
    99 #endif
   101 #ifdef DEBUG
   102 	printf("DEBUG: Printer status buffer = \n");
   103 	hex_dump(buf, 32);
   104 #endif
   106 	// Decode the status buffer, store the results in the device object
   107 	dev->errorInfo[0] = buf[8];
   108 	dev->errorInfo[1] = buf[9];
   109 	dev->mediaWidth   = buf[10];
   110 	dev->mediaType    = buf[11];
   111 	dev->mediaLength  = buf[17];
   112 	dev->statusType   = buf[18];
   113 	dev->phaseType    = buf[19];
   114 	dev->phaseHi      = buf[20];
   115 	dev->phaseLo      = buf[21];
   116 	dev->notification = buf[22];
   118 	// Set DPI
   119 	dev->dpiPrinthead = 180;
   120 	dev->dpiLabel     = 180;
   122 	// Set pixel width (label width in pixels)
   123 	if (dev->mediaWidth >= 24) {
   124 		// Label tape is 24mm or wider. Print head is 128 dots at 180dpi,
   125 		// which is 18.06mm. Thus, we can only print on the centre 18mm
   126 		// of a tape that is wider than 18mm.
   127 		dev->pixelWidth = 128;
   128 	} else {
   129 		// Print head is 180dpi. Pixel size is mediaWidth * dpi. If we
   130 		// multiply by ten, then divide by 254, we can avoid using
   131 		// floating point to convert from inches to mm. The -2 is a
   132 		// safety margin -- one pixel on either side of the label.
   133 		// This is far closer than Brother suggest, but hey-ho.
   134 		dev->pixelWidth = ((dev->mediaWidth * dev->dpiPrinthead * 10) / 254) - 2;
   135 	}
   137 	// Operation succeeded
   138 	return PT_ERR_SUCCESS;
   139 }
   141 int pt_SetOption(pt_Device *dev, PT_E_OPTION option, int value)
   142 {
   143 	// trap dev == NULL
   144 	if (dev == NULL) {
   145 		return PT_ERR_BAD_PARAMETER;
   146 	}
   148 	// set option
   149 	switch(option) {
   150 		case PT_OPTION_MIRROR:		// Mirror
   151 			dev->mirror = (value ? 1 : 0);
   152 			return PT_ERR_SUCCESS;
   154 		case PT_OPTION_AUTOCUT:		// Auto-cutter enable/disable
   155 			dev->autocut = (value ? 1 : 0);
   156 			return PT_ERR_SUCCESS;
   158 		case PT_OPTION_SEPARATOR:	// Separator ticks enable/disable
   159 			dev->separator = (value ? 1 : 0);
   160 			return PT_ERR_SUCCESS;
   162 		default:
   163 			return PT_ERR_BAD_PARAMETER;
   164 	}
   165 }
   167 int pt_GetOption(pt_Device *dev, PT_E_OPTION option, int *value)
   168 {
   169 	// trap dev == NULL or value == NULL
   170 	if ((dev == NULL) || (value == NULL)) {
   171 		return PT_ERR_BAD_PARAMETER;
   172 	}
   174 	// get option value
   175 	switch(option) {
   176 		case PT_OPTION_MIRROR:			// Mirror
   177 			*value = dev->mirror;
   178 			return PT_ERR_SUCCESS;
   180 		case PT_OPTION_AUTOCUT:		// Auto-cutter enable/disable
   181 			*value = dev->autocut;
   182 			return PT_ERR_SUCCESS;
   184 		case PT_OPTION_SEPARATOR:	// Separator ticks enable/disable
   185 			*value = dev->separator;
   186 			return PT_ERR_SUCCESS;
   188 		default:
   189 			return PT_ERR_BAD_PARAMETER;
   190 	}
   191 }
   193 int pt_Print(pt_Device *dev, gdImagePtr *labels, int count)
   194 {
   195 	int err;
   196 	gdImagePtr *curLabel = labels;
   198 	// trap dev == NULL
   199 	if (dev == NULL) {
   200 		return PT_ERR_BAD_PARAMETER;
   201 	}
   203 	// trap labels == NULL
   204 	if (labels == NULL) {
   205 		return PT_ERR_BAD_PARAMETER;
   206 	}
   208 	// trap count == 0
   209 	if (count == 0) {
   210 		return PT_ERR_BAD_PARAMETER;
   211 	}
   213 	// Request current status from the printer
   214 	if ((err = pt_GetStatus(dev)) != PT_ERR_SUCCESS) {
   215 		return err;
   216 	}
   218 	// Make sure the printer has tape, and is ready
   219 	if ((dev->errorInfo[0] != 0x00) || (dev->errorInfo[1] != 0x00)) {
   220 		return PT_ERR_PRINTER_NOT_READY;
   221 	}
   223 	// Send the print initialisation commands
   224 	//
   225 	// ESC i M -- Set Mode
   226 	fprintf(dev->fp, "%ciM%c", ESC,
   227 			(dev->autocut ? 0x40 : 0x00) | (dev->mirror ? 0x80 : 0x00));
   229 	// ESC i K -- Set Expanded Mode
   230 	fprintf(dev->fp, "%ciK%c", ESC, 0x00);
   232 	// ESC i R {n1} -- Set Raster Graphics Transfer Mode
   233 	//   {n1} = 0x01 ==> Raster Graphics mode
   234 	fprintf(dev->fp, "%ciR%c", ESC, 0x01);
   236 	// M {n1} -- Set Compression Mode
   237 	//   {n1} = 0x00 ==> no compression
   238 	//   	Doesn't seem to work on the PT-2450DX...
   239 	//   {n1} = 0x01 ==> reserved
   240 	//   {n1} = 0x02 ==> TIFF/Packbits
   241 	//   	But this works fine on the PT-2450DX...
   242 	fprintf(dev->fp, "M%c", 0x02);
   244 	// Loop over the images that were passed in
   245 	for (int imnum=0; imnum < count; imnum++) {
   246 		// Make sure image is the right size for this label tape
   247 		if (gdImageSY(*curLabel) != dev->pixelWidth) {
   248 			return PT_ERR_LABEL_TOO_WIDE;
   249 		}
   251 		// Trap a label with a width of zero
   252 		// I'm not sure if this can happen, but it's a single if statement, so
   253 		// probably worth checking anyway...
   254 		if (gdImageSX(*curLabel) == 0) {
   255 			return PT_ERR_LABEL_ZERO_LENGTH;
   256 		}
   258 		// Get the index of the colour "white" (RGB:255,255,255) in the Gd image
   259 		int col_white = gdImageColorResolve(*curLabel, 255, 255, 255);
   261 		// Iterate left-to-right over the source image
   262 		for (int xpos = 0; xpos < gdImageSX(*curLabel); xpos++) {
   263 			char bitbuf[128/8];		// 128-dot printhead, 8 bits per byte
   264 			int rowclear = true;	// TRUE if entire row is clear, else FALSE
   266 			// Fill bit buffer with zeroes
   267 			memset(bitbuf, 0x00, sizeof(bitbuf));
   269 			// Calculate left-side margin for this label size
   270 			// Again, 128-dot printhead.
   271 			int margin = (128 / 2) - (dev->pixelWidth / 2);
   273 			// Copy data from the image to the bit-buffer
   274 			// If the image is too tall for the label, only the topmost part
   275 			// will be printed -- the margin is enforced by (margin+ypos).
   276 			for (int ypos = 0; ypos < gdImageSY(*curLabel); ypos++) {
   277 				// Get pixel from gd, is it white?
   278 				if (gdImageGetPixel(*curLabel, xpos, ypos) != col_white) {
   279 					// No. Set the bit.
   280 					int bit = 1 << (7 - ((margin+ypos) % 8));
   281 					bitbuf[(margin+ypos) / 8] |= bit;
   283 					// Clear the "row is clear" flag
   284 					rowclear = false;
   285 				}
   286 			}
   288 			// We now have the image data in bitbuf, and a flag to say whether
   289 			// there are any black pixels in the buffer. We can pack full-rows
   290 			// by sending a "Z" character, i.e. "this row is entirely blank".
   291 			// If not, we have to send a (128/8)=16 byte chunk of image data.
   292 			if (rowclear) {
   293 				// Row is clear -- send a "clear row" command
   294 				fprintf(dev->fp, "Z");
   295 			} else {
   296 				// Row is not clear -- send the pixel data
   297 				//
   298 				// TODO: the printer supports Packbits compression. Implement!
   299 				// TODO: After Packbits is implemented, ((128/8)+1) must be
   300 				//   changed to the length of the Packbits compressed data.
   301 				//   (note: 128/8 is the printhead size in bytes, the +1 is for
   302 				//   the control byte we add below...)
   303 				fprintf(dev->fp, "G%c%c", ((128/8)+1) & 0xff, ((128/8)+1) >> 8);
   305 				// This printer asks for Packbits compressed data. In this
   306 				// case, we send a "run of N" control byte and fake it...
   307 				fputc(sizeof(bitbuf) - 1, dev->fp);
   308 				for (int i=0; i<sizeof(bitbuf); i++) {
   309 					fputc(bitbuf[i], dev->fp);
   310 				}
   311 			}
   312 		}
   314 		// Print separator line
   315 		if (dev->separator) {
   316 			char bitbuf[128/8];		// 128-dot printhead, 8 bits per byte
   318 			// Calculate margin for this label size
   319 			// Again, 128-dot printhead.
   320 			int margin = (128 / 2) - (dev->pixelWidth / 2);
   322 			// One blank line
   323 			fprintf(dev->fp, "Z");
   325 			// Dotted line (assumes printhead size of 128 dots and that
   326 			// bitbuf has same size as printhead)
   327 			fprintf(dev->fp, "G%c%c", ((128/8)+1)&0xff, ((128/8)+1) >> 8);
   329 			// Clear the bit buffer
   330 			memset(&bitbuf, 0, sizeof(bitbuf));
   332 			// Draw the tick marks
   333 			int step = (dev->pixelWidth / 4);
   334 			for (int i=(margin+step); i<(margin+step+(step/2)); i++) {
   335 				// top tick mark
   336 				int bit = 1 << (7 - (i % 8));
   337 				bitbuf[i / 8] |= bit;
   338 			}
   339 			for (int i=(margin+(2*step)); i<(margin+(2*step)+(step/2)); i++) {
   340 				// bottom tick mark
   341 				int bit = 1 << (7 - (i % 8));
   342 				bitbuf[i / 8] |= bit;
   343 			}
   345 			// TODO: Add Packbits compression code?
   346 			// This printer asks for Packbits compressed data. In this
   347 			// case, we send a "run of N" control byte and fake it...
   348 			fputc(sizeof(bitbuf) - 1, dev->fp);
   349 			for (int i=0; i<sizeof(bitbuf); i++) {
   350 				// Draw tick-marks from the bit buffer
   351 				fputc(bitbuf[i], dev->fp);
   352 			}
   354 			// One final blank line
   355 			fprintf(dev->fp, "Z");
   356 		}
   359 		// Is this the last label?
   360 		if (imnum == (count-1)) {
   361 			// Yes, send an End Job command
   362 			fprintf(dev->fp, "%c", 0x1A);
   363 		} else {
   364 			// No, more labels to print. Send a formfeed.
   365 			fprintf(dev->fp, "%c", 0x0C);
   366 		}
   368 		// Move onto the next label
   369 		curLabel++;
   370 	}
   372 	// Operation successful.
   373 	return PT_ERR_SUCCESS;
   374 }
   376 void pt_Close(pt_Device *dev)
   377 {
   378 	// Sanity check -- make sure dev is not null
   379 	if (dev == NULL) return;
   381 	// Close the printer stream
   382 	fclose(dev->fp);
   384 	// Release the memory allocated to the status buffer
   385 	free(dev);
   386 }