Wed, 05 Aug 2009 15:04:55 +0100
Fixed printing so uncompressed mode is no longer used; now sends an uncompressed Packbits stream instead.
Added SetOption and GetOption, so can now turn autocut and mirror on and off. Defaults to both off after init.
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) == 0) {
54 return dev;
55 } else {
56 free(dev);
57 return NULL;
58 }
59 }
61 int pt_GetStatus(pt_Device *dev)
62 {
63 #ifdef DEBUG_SKIP_STATUS_READ
64 unsigned char buf[32];
65 memset(buf, 0x00, 32);
66 buf[0] = 0x80;
67 buf[1] = 0x20;
68 buf[2] = 0x42;
69 buf[3] = 0x30;
70 buf[4] = 0x4b;
71 buf[5] = 0x30;
72 buf[10] = 0x0c;
73 buf[11] = 0x01;
74 #else
75 // REQUEST STATUS
76 fprintf(dev->fp, "%c%c%c", ESC, 'i', 'S');
78 // Read status buffer from printer
79 unsigned char buf[32];
80 int timeout = 128;
81 do {
82 fread(buf, 1, 32, dev->fp);
83 } while ((buf[0] != 0x80) && (timeout-- > 0));
85 // Check for timeout
86 if (timeout == 0) {
87 // Timeout
88 return PT_ERR_TIMEOUT;
89 }
90 #endif
92 #ifdef DEBUG
93 printf("DEBUG: Printer status buffer = \n");
94 hex_dump(buf, 32);
95 #endif
97 // Decode the status buffer, store the results in the device object
98 dev->errorInfo[0] = buf[8];
99 dev->errorInfo[1] = buf[9];
100 dev->mediaWidth = buf[10];
101 dev->mediaType = buf[11];
102 dev->mediaLength = buf[17];
103 dev->statusType = buf[18];
104 dev->phaseType = buf[19];
105 dev->phaseHi = buf[20];
106 dev->phaseLo = buf[21];
107 dev->notification = buf[22];
109 // Set pixel width (label width in pixels)
110 if (dev->mediaWidth >= 24) {
111 // Label tape is 24mm or wider. Print head is 128 dots at 180dpi,
112 // which is 18.06mm. Thus, we can only print on the centre 18mm
113 // of a tape that is wider than 18mm.
114 dev->pixelWidth = 128;
115 } else {
116 // Print head is 180dpi. Pixel size is mediaWidth * dpi. If we
117 // multiply by ten, then divide by 254, we can avoid using
118 // floating point to convert from inches to mm. The -2 is a
119 // safety margin -- one pixel on either side of the label.
120 // This is far closer than Brother suggest, but hey-ho.
121 dev->pixelWidth = ((dev->mediaWidth * 180 * 10) / 254) - 2;
122 }
124 // Set printing parameters to defaults --
125 // Mirror off
126 // Autocut on
127 dev->mirror = false;
128 dev->autocut = false;
130 // Operation succeeded
131 return PT_ERR_SUCCESS;
132 }
134 int pt_SetOption(pt_Device *dev, PT_E_OPTION option, int value)
135 {
136 // trap dev == NULL
137 if (dev == NULL) {
138 return PT_ERR_BAD_PARAMETER;
139 }
141 // set option
142 switch(option) {
143 case PT_OPTION_MIRROR: // Mirror
144 dev->mirror = (value ? 1 : 0);
145 return PT_ERR_SUCCESS;
147 case PT_OPTION_AUTOCUT: // Auto-cutter enable/disable
148 dev->autocut = (value ? 1 : 0);
149 return PT_ERR_SUCCESS;
151 default:
152 return PT_ERR_BAD_PARAMETER;
153 }
154 }
156 int pt_GetOption(pt_Device *dev, PT_E_OPTION option, int *value)
157 {
158 // trap dev == NULL or value == NULL
159 if ((dev == NULL) || (value == NULL)) {
160 return PT_ERR_BAD_PARAMETER;
161 }
163 // get option value
164 switch(option) {
165 case PT_OPTION_MIRROR: // Mirror
166 *value = dev->mirror;
167 return PT_ERR_SUCCESS;
169 case PT_OPTION_AUTOCUT: // Auto-cutter enable/disable
170 *value = dev->autocut;
171 return PT_ERR_SUCCESS;
173 default:
174 return PT_ERR_BAD_PARAMETER;
175 }
176 }
178 int pt_Print(pt_Device *dev, gdImagePtr *labels, int count)
179 {
180 int err;
181 gdImagePtr *curLabel = labels;
183 // trap dev == NULL
184 if (dev == NULL) {
185 return PT_ERR_BAD_PARAMETER;
186 }
188 // trap labels == NULL
189 if (labels == NULL) {
190 return PT_ERR_BAD_PARAMETER;
191 }
193 // trap count == 0
194 if (count == 0) {
195 return PT_ERR_BAD_PARAMETER;
196 }
198 // Request current status from the printer
199 if ((err = pt_GetStatus(dev)) != PT_ERR_SUCCESS) {
200 return err;
201 }
203 // Make sure the printer has tape, and is ready
204 if ((dev->errorInfo[0] != 0x00) || (dev->errorInfo[1] != 0x00)) {
205 return PT_ERR_PRINTER_NOT_READY;
206 }
208 // Send the print initialisation commands
209 //
210 // ESC i M -- Set Mode
211 fprintf(dev->fp, "%ciM%c", ESC,
212 (dev->autocut ? 0x40 : 0x00) | (dev->mirror ? 0x80 : 0x00));
214 // ESC i K -- Set Expanded Mode
215 fprintf(dev->fp, "%ciK%c", ESC, 0x00);
217 // ESC i R {n1} -- Set Raster Graphics Transfer Mode
218 // {n1} = 0x01 ==> Raster Graphics mode
219 fprintf(dev->fp, "%ciR%c", ESC, 0x01);
221 // M {n1} -- Set Compression Mode
222 // {n1} = 0x00 ==> no compression
223 // Doesn't seem to work on the PT-2450DX...
224 // {n1} = 0x01 ==> reserved
225 // {n1} = 0x02 ==> TIFF/Packbits
226 // But this works fine on the PT-2450DX...
227 fprintf(dev->fp, "M%c", 0x02);
229 // Loop over the images that were passed in
230 for (int imnum=0; imnum < count; imnum++) {
231 // Make sure image is the right size for this label tape
232 if (gdImageSY(*curLabel) != dev->pixelWidth) {
233 return PT_ERR_LABEL_TOO_WIDE;
234 }
236 // Trap a label with a width of zero
237 // I'm not sure if this can happen, but it's a single if statement, so
238 // probably worth checking anyway...
239 if (gdImageSX(*curLabel) == 0) {
240 return PT_ERR_LABEL_ZERO_LENGTH;
241 }
243 // Get the index of the colour "white" (RGB:255,255,255) in the Gd image
244 int col_white = gdImageColorResolve(*curLabel, 255, 255, 255);
246 // Iterate left-to-right over the source image
247 for (int xpos = 0; xpos < gdImageSX(*curLabel); xpos++) {
248 char bitbuf[128/8]; // 128-dot printhead, 8 bits per byte
249 int rowclear = true; // TRUE if entire row is clear, else FALSE
251 // Fill bit buffer with zeroes
252 memset(bitbuf, 0x00, sizeof(bitbuf));
254 // Calculate left-side margin for this label size
255 // Again, 128-dot printhead.
256 int margin = (128 / 2) - (dev->pixelWidth / 2);
258 // Copy data from the image to the bit-buffer
259 for (int ypos = 0; ypos < gdImageSY(*curLabel); ypos++) {
260 // Get pixel from gd, is it white?
261 if (gdImageGetPixel(*curLabel, xpos, ypos) != col_white) {
262 // No. Set the bit.
263 int bit = 1 << (7 - ((margin+ypos) % 8));
264 bitbuf[(margin+ypos) / 8] |= bit;
266 // Clear the "row is clear" flag
267 rowclear = false;
268 }
269 }
271 // We now have the image data in bitbuf, and a flag to say whether
272 // there are any black pixels in the buffer. We can pack full-rows
273 // by sending a "Z" character, i.e. "this row is entirely blank".
274 // If not, we have to send a (128/8)=16 byte chunk of image data.
275 if (rowclear) {
276 // Row is clear -- send a "clear row" command
277 fprintf(dev->fp, "Z");
278 } else {
279 // Row is not clear -- send the pixel data
280 //
281 // TODO: the printer supports Packbits compression. Implement!
282 // TODO: After Packbits is implemented, ((128/8)+1) must be
283 // changed to the length of the Packbits compressed data.
284 // (note: 128/8 is the printhead size in bytes, the +1 is for
285 // the control byte we add below...)
286 fprintf(dev->fp, "G%c%c", ((128/8)+1) & 0xff, ((128/8)+1) >> 8);
288 // This printer asks for Packbits compressed data. In this
289 // case, we send a "run of N" control byte and fake it...
290 fputc(sizeof(bitbuf) - 1, dev->fp);
291 for (int i=0; i<sizeof(bitbuf); i++) {
292 fputc(bitbuf[i], dev->fp);
293 }
294 }
295 }
297 // Is this the last label?
298 if (imnum == (count-1)) {
299 // Yes, send an End Job command
300 fprintf(dev->fp, "%c", 0x1A);
301 } else {
302 // No, more labels to print. Send a formfeed.
303 fprintf(dev->fp, "%c", 0x0C);
304 }
306 // Move onto the next label
307 curLabel++;
308 }
310 // Operation successful.
311 return PT_ERR_SUCCESS;
312 }
314 void pt_Close(pt_Device *dev)
315 {
316 // Sanity check -- make sure dev is not null
317 if (dev == NULL) return;
319 // Close the printer stream
320 fclose(dev->fp);
322 // Release the memory allocated to the status buffer
323 free(dev);
324 }