Thu, 24 Sep 2009 17:18:28 +0100
Added support for separator ticks between labels
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 pixel width (label width in pixels)
119 if (dev->mediaWidth >= 24) {
120 // Label tape is 24mm or wider. Print head is 128 dots at 180dpi,
121 // which is 18.06mm. Thus, we can only print on the centre 18mm
122 // of a tape that is wider than 18mm.
123 dev->pixelWidth = 128;
124 } else {
125 // Print head is 180dpi. Pixel size is mediaWidth * dpi. If we
126 // multiply by ten, then divide by 254, we can avoid using
127 // floating point to convert from inches to mm. The -2 is a
128 // safety margin -- one pixel on either side of the label.
129 // This is far closer than Brother suggest, but hey-ho.
130 dev->pixelWidth = ((dev->mediaWidth * 180 * 10) / 254) - 2;
131 }
133 // Operation succeeded
134 return PT_ERR_SUCCESS;
135 }
137 int pt_SetOption(pt_Device *dev, PT_E_OPTION option, int value)
138 {
139 // trap dev == NULL
140 if (dev == NULL) {
141 return PT_ERR_BAD_PARAMETER;
142 }
144 // set option
145 switch(option) {
146 case PT_OPTION_MIRROR: // Mirror
147 dev->mirror = (value ? 1 : 0);
148 return PT_ERR_SUCCESS;
150 case PT_OPTION_AUTOCUT: // Auto-cutter enable/disable
151 dev->autocut = (value ? 1 : 0);
152 return PT_ERR_SUCCESS;
154 case PT_OPTION_SEPARATOR: // Separator ticks enable/disable
155 dev->separator = (value ? 1 : 0);
156 return PT_ERR_SUCCESS;
158 default:
159 return PT_ERR_BAD_PARAMETER;
160 }
161 }
163 int pt_GetOption(pt_Device *dev, PT_E_OPTION option, int *value)
164 {
165 // trap dev == NULL or value == NULL
166 if ((dev == NULL) || (value == NULL)) {
167 return PT_ERR_BAD_PARAMETER;
168 }
170 // get option value
171 switch(option) {
172 case PT_OPTION_MIRROR: // Mirror
173 *value = dev->mirror;
174 return PT_ERR_SUCCESS;
176 case PT_OPTION_AUTOCUT: // Auto-cutter enable/disable
177 *value = dev->autocut;
178 return PT_ERR_SUCCESS;
180 case PT_OPTION_SEPARATOR: // Separator ticks enable/disable
181 *value = dev->separator;
182 return PT_ERR_SUCCESS;
184 default:
185 return PT_ERR_BAD_PARAMETER;
186 }
187 }
189 int pt_Print(pt_Device *dev, gdImagePtr *labels, int count)
190 {
191 int err;
192 gdImagePtr *curLabel = labels;
194 // trap dev == NULL
195 if (dev == NULL) {
196 return PT_ERR_BAD_PARAMETER;
197 }
199 // trap labels == NULL
200 if (labels == NULL) {
201 return PT_ERR_BAD_PARAMETER;
202 }
204 // trap count == 0
205 if (count == 0) {
206 return PT_ERR_BAD_PARAMETER;
207 }
209 // Request current status from the printer
210 if ((err = pt_GetStatus(dev)) != PT_ERR_SUCCESS) {
211 return err;
212 }
214 // Make sure the printer has tape, and is ready
215 if ((dev->errorInfo[0] != 0x00) || (dev->errorInfo[1] != 0x00)) {
216 return PT_ERR_PRINTER_NOT_READY;
217 }
219 // Send the print initialisation commands
220 //
221 // ESC i M -- Set Mode
222 fprintf(dev->fp, "%ciM%c", ESC,
223 (dev->autocut ? 0x40 : 0x00) | (dev->mirror ? 0x80 : 0x00));
225 // ESC i K -- Set Expanded Mode
226 fprintf(dev->fp, "%ciK%c", ESC, 0x00);
228 // ESC i R {n1} -- Set Raster Graphics Transfer Mode
229 // {n1} = 0x01 ==> Raster Graphics mode
230 fprintf(dev->fp, "%ciR%c", ESC, 0x01);
232 // M {n1} -- Set Compression Mode
233 // {n1} = 0x00 ==> no compression
234 // Doesn't seem to work on the PT-2450DX...
235 // {n1} = 0x01 ==> reserved
236 // {n1} = 0x02 ==> TIFF/Packbits
237 // But this works fine on the PT-2450DX...
238 fprintf(dev->fp, "M%c", 0x02);
240 // Loop over the images that were passed in
241 for (int imnum=0; imnum < count; imnum++) {
242 // Make sure image is the right size for this label tape
243 if (gdImageSY(*curLabel) != dev->pixelWidth) {
244 return PT_ERR_LABEL_TOO_WIDE;
245 }
247 // Trap a label with a width of zero
248 // I'm not sure if this can happen, but it's a single if statement, so
249 // probably worth checking anyway...
250 if (gdImageSX(*curLabel) == 0) {
251 return PT_ERR_LABEL_ZERO_LENGTH;
252 }
254 // Get the index of the colour "white" (RGB:255,255,255) in the Gd image
255 int col_white = gdImageColorResolve(*curLabel, 255, 255, 255);
257 // Iterate left-to-right over the source image
258 for (int xpos = 0; xpos < gdImageSX(*curLabel); xpos++) {
259 char bitbuf[128/8]; // 128-dot printhead, 8 bits per byte
260 int rowclear = true; // TRUE if entire row is clear, else FALSE
262 // Fill bit buffer with zeroes
263 memset(bitbuf, 0x00, sizeof(bitbuf));
265 // Calculate left-side margin for this label size
266 // Again, 128-dot printhead.
267 int margin = (128 / 2) - (dev->pixelWidth / 2);
269 // Copy data from the image to the bit-buffer
270 // If the image is too tall for the label, only the topmost part
271 // will be printed -- the margin is enforced by (margin+ypos).
272 for (int ypos = 0; ypos < gdImageSY(*curLabel); ypos++) {
273 // Get pixel from gd, is it white?
274 if (gdImageGetPixel(*curLabel, xpos, ypos) != col_white) {
275 // No. Set the bit.
276 int bit = 1 << (7 - ((margin+ypos) % 8));
277 bitbuf[(margin+ypos) / 8] |= bit;
279 // Clear the "row is clear" flag
280 rowclear = false;
281 }
282 }
284 // We now have the image data in bitbuf, and a flag to say whether
285 // there are any black pixels in the buffer. We can pack full-rows
286 // by sending a "Z" character, i.e. "this row is entirely blank".
287 // If not, we have to send a (128/8)=16 byte chunk of image data.
288 if (rowclear) {
289 // Row is clear -- send a "clear row" command
290 fprintf(dev->fp, "Z");
291 } else {
292 // Row is not clear -- send the pixel data
293 //
294 // TODO: the printer supports Packbits compression. Implement!
295 // TODO: After Packbits is implemented, ((128/8)+1) must be
296 // changed to the length of the Packbits compressed data.
297 // (note: 128/8 is the printhead size in bytes, the +1 is for
298 // the control byte we add below...)
299 fprintf(dev->fp, "G%c%c", ((128/8)+1) & 0xff, ((128/8)+1) >> 8);
301 // This printer asks for Packbits compressed data. In this
302 // case, we send a "run of N" control byte and fake it...
303 fputc(sizeof(bitbuf) - 1, dev->fp);
304 for (int i=0; i<sizeof(bitbuf); i++) {
305 fputc(bitbuf[i], dev->fp);
306 }
307 }
308 }
310 // Print separator line
311 if (dev->separator) {
312 char bitbuf[128/8]; // 128-dot printhead, 8 bits per byte
314 // Calculate margin for this label size
315 // Again, 128-dot printhead.
316 int margin = (128 / 2) - (dev->pixelWidth / 2);
317 printf("calc margin %d\npixelwidth %d\n", margin, dev->pixelWidth);
319 // One blank line
320 fprintf(dev->fp, "Z");
322 // Dotted line (assumes printhead size of 128 dots and that
323 // bitbuf has same size as printhead)
324 fprintf(dev->fp, "G%c%c", ((128/8)+1)&0xff, ((128/8)+1) >> 8);
326 // Clear the bit buffer
327 memset(&bitbuf, 0, sizeof(bitbuf));
329 // Draw the tick marks
330 int step = (dev->pixelWidth / 4);
331 for (int i=(margin+step); i<(margin+step+(step/2)); i++) {
332 // top tick mark
333 int bit = 1 << (7 - (i % 8));
334 bitbuf[i / 8] |= bit;
335 }
336 for (int i=(margin+(2*step)); i<(margin+(2*step)+(step/2)); i++) {
337 // bottom tick mark
338 int bit = 1 << (7 - (i % 8));
339 bitbuf[i / 8] |= bit;
340 }
342 // TODO: Add Packbits compression code?
343 // This printer asks for Packbits compressed data. In this
344 // case, we send a "run of N" control byte and fake it...
345 fputc(sizeof(bitbuf) - 1, dev->fp);
346 for (int i=0; i<sizeof(bitbuf); i++) {
347 // Draw tick-marks from the bit buffer
348 fputc(bitbuf[i], dev->fp);
349 }
351 // One final blank line
352 fprintf(dev->fp, "Z");
353 }
356 // Is this the last label?
357 if (imnum == (count-1)) {
358 // Yes, send an End Job command
359 fprintf(dev->fp, "%c", 0x1A);
360 } else {
361 // No, more labels to print. Send a formfeed.
362 fprintf(dev->fp, "%c", 0x0C);
363 }
365 // Move onto the next label
366 curLabel++;
367 }
369 // Operation successful.
370 return PT_ERR_SUCCESS;
371 }
373 void pt_Close(pt_Device *dev)
374 {
375 // Sanity check -- make sure dev is not null
376 if (dev == NULL) return;
378 // Close the printer stream
379 fclose(dev->fp);
381 // Release the memory allocated to the status buffer
382 free(dev);
383 }