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

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