summaryrefslogtreecommitdiff
path: root/devices/gdevlxm.c
blob: cc8e6f44c519750f835db311ea2450760400b531 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* Copyright (C) 2001-2023 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied,
   modified or distributed except as expressly authorized under the terms
   of the license contained in the file LICENSE in this distribution.

   Refer to licensing information at http://www.artifex.com or contact
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
   CA 94129, USA, for further information.
*/

/*
 * Lexmark 5700 ink-jet printer driver for Ghostscript
 *
 * defines the lxm5700m device for printing in black-and-white at 1200 dpi
 * doesn't handle color or any other resolution.
 * Native resolution appears to be 600 x 1200, but print bands are overlapped.
 *
 * I use the command
 * gs -sOutputFile=/dev/lp0 -sDevice=lxm5700m -dHeadSeparation=15 file.ps
 *
 * where HeadSeparation varies from print-cartridge to print-cartridge and
 * 16 (the default) usually works fine.
 *
 *   Stephen Taylor  setaylor@ma.ultranet.com  staylor@cs.wpi.edu
 */

#include "gdevprn.h"
#include "gsparams.h"

/* The procedure descriptors */
/* declare functions */
static dev_proc_print_page(lxm5700m_print_page);
static dev_proc_get_params(lxm_get_params);
static dev_proc_put_params(lxm_put_params);

/* set up dispatch table.  I follow gdevdjet in using gdev_prn_output_page */
/* Since the print_page doesn't alter the device, this device can print in the background */
static void
lxm5700m_initialize_device_procs(gx_device *dev)
{
    gdev_prn_initialize_device_procs_mono_bg(dev);

    set_dev_proc(dev, get_params, lxm_get_params);
    set_dev_proc(dev, put_params, lxm_put_params);
}

/* The device descriptors */

/* define a subclass with useful state in it. */
typedef struct lxm_device_s { /* a sub-class of gx_device_printer */
    gx_device_common;
    gx_prn_device_common;
    int headSeparation;
} lxm_device;

/* Standard lxm5700m device */
lxm_device far_data gs_lxm5700m_device = {
    prn_device_std_body(lxm_device, lxm5700m_initialize_device_procs, "lxm5700m",
        DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
        600, 600,	/* x dpi, y dpi */
        0.2, 0.0, 0.0, 0.0,			/* margins */
        1, lxm5700m_print_page),
    16   /* default headSeparation value */
};

/* I don't know the whole protocol for the printer, but let me tell
 * you about the fraction I use.
 * Each page begins with a header, which I describe as init1, init2, init3 --
 *  (I separate them, because I've seen output in which those sections
 *   seemed to appear independently, but I've deleted the only code I
 *   had that actually used them separately.)
 * Then there are a number of swipe commands, each of which describes one
 * swipe of the printhead.  Each swipe command begins with a fixed
 * header, which gives the number of bytes in the command,
 * left and right margins,
 * a vertical offset, some noise characters I don't know the meaning of,
 * and the table of bits.  The bits are given one column at a time, using a
 * simple compression scheme: a directory, consisting of two bytes, tells
 * which sixteen-bit intervals of the 208-bit column contain 1-bits; then
 * the appropriate number of sixteen-bit bit-maps follow.  (A zero-bit in the
 * directory indicates that a bitmap for that sector follows.) In the worst case,
 * this scheme would be bigger than the uncompressed bitmap, but it seems to
 * usually save 80-90%.  The biggest complication of the bitmap scheme is this:
 * There are two print-heads on the black cartridge, and they are 16 pixels
 * (or headSeparation) apart.  Odd columns of the swipe address one printhead,
 * and even columns the other.  On the following swipe, the printheads
 * addressed by even and odd columns are reversed.  I think the printheads might be
 * staggered, but the output I've seen staggers things in software;
 * adjacent vertical bits on the same head are not addressed; the missing
 * bits are written in by the second head when it passes (16 columns later.)
 * In my code, I call the state of addressing one head or the other "direction".
 * Originally I thought that the printhead was writing on both leftward and
 * rightward motion, and that which head was addressed by odd columns changed
 * accordingly.  I'm no longer sure this is true, but the head addressed by the
 * even columns does alternate with each swipe.
 */
/*
 * various output shorthands
 */

#define init1() \
        top(), \
        0xA5,0, 3, 0x40,4,5, \
        0xA5,0, 3, 0x40,4,6, \
        0xA5,0, 3, 0x40,4,7, \
        0xA5,0, 3, 0x40,4,8, \
        0xA5,0, 4, 0x40,0xe0,0x0b, 3

#define init2() \
        0xA5,0, 11, 0x40,0xe0,0x41, 0,0,0,0,0,0,0, 2, \
        0xA5,0, 6, 0x40, 5, 0,0,0x80,0 \

#define init3()  \
        0x1b,'*', 7,0x73,0x30, \
        0x1b,'*', 'm', 0, 0x14, 3, 0x84, 2, 0, 1, 0xf4, \
        0x1b,'*', 7,0x63, \
        0x1b,'*', 'm', 0, 0x42,  0, 0, \
        0xA5,0, 5, 0x40,0xe0,0x80, 8, 7, \
        0x1b,'*', 'm', 0, 0x40, 0x15, 7, 0x0f, 0x0f  \

#define top()  \
        0xA5,0, 6, 0x40, 3,3,0xc0,0x0f,0x0f \

#define fin()  \
        0x1b,'*', 7, 0x65 \

#define outByte(b) gp_fputc(b, prn_stream)

#define RIGHTWARD 0
#define LEFTWARD 1
/* number of pixels between even columns in output and odd ones*/
/* #define headSeparation 16 */
/* overlap between successive swipes of the print head */
#define overLap 104
/* height of printhead in pixels */
#define swipeHeight 208
/* number of shorts described by each column directory */
#define directorySize 13

/* ------ Driver procedures ------ */

/* Send the page to the printer. */
static int
lxm5700m_print_page(gx_device_printer *pdev, gp_file *prn_stream)
{
    int lnum,minX, maxX, i, l, highestX, leastX, extent;
    int direction = RIGHTWARD;
    int lastY = 0;
    int code = 0;

    int line_size = gdev_mem_bytes_per_scan_line((gx_device *)pdev);
    /* Note that in_size is a multiple of 8. */
    int in_size = line_size * (swipeHeight);
    int swipeBuf_size = in_size;
    byte *buf1 = (byte *)gs_malloc(pdev->memory, in_size, 1, "lxm_print_page(buf1)");
    byte *swipeBuf =
        (byte *)gs_malloc(pdev->memory, swipeBuf_size, 1, "lxm_print_page(swipeBuf)");
    byte *in = buf1;

    /* Check allocations */
    if ( buf1 == 0 || swipeBuf == 0 ) {
        code = gs_error_VMerror;
        goto xit;
    }

    {	/* Initialize the printer and reset the margins. */
        static const char init_string[] = {
            init1(),
            init2(),
            init3()
        };
        gp_fwrite(init_string, 1, sizeof(init_string), prn_stream);
    }
    /* Print lines of graphics */
    for (lnum=0; lnum < pdev->height-swipeHeight ; ) { /* increment in body */
        byte *in_data;
        register byte *outp;
        int lcnt;

        {	/* test for blank scan lines.  We  maintain the */
            /* loop invariant lnum <pdev->height, but modify lnum */
            int l;

            for (l=lnum; l<pdev->height; l++) {
                /* Copy 1 scan line and test for all zero. */
                code = gdev_prn_get_bits(pdev, l, in, &in_data);
                if (code < 0)
                    goto xit;
                if ( in_data[0] != 0 ||
                     memcmp((char *)in_data, (char *)in_data + 1, line_size - 1)
                     ) {
                    break;
                }
            }/* end for l */

            /* now l is the next non-blank scan line */
            if (l >= pdev->height) {/* if there are no more bits on this page */
                lnum = l;
                break;			/* end the loop and eject the page*/
            }

            /* leave room for following swipe to reinforce these bits */
            if (l-lnum > overLap) lnum = l - overLap;

            /* if the first non-blank near bottom of page */
            if (lnum >=pdev->height - swipeHeight) {
                /* don't move the printhead over empty air*/
                lnum = pdev->height - swipeHeight;
            }
        }

        /* Copy the the scan lines. */
        code = lcnt = gdev_prn_copy_scan_lines(pdev, lnum, in, in_size);
        if (code < 0)
            goto xit;
        if ( lcnt < swipeHeight ) {
            /* Pad with lines of zeros. */
            memset(in + lcnt * line_size, 0,
                   in_size - lcnt * line_size);
        }

        /* compute right and left margin for this swipe */
        minX = line_size;
        maxX = 0;
        for (l=0; l<swipeHeight; l++) {/* for each line of swipe */
            for (i=0; i<minX; i++) {/* look for left-most non-zero byte*/
                if (in[l*line_size+i] !=0) {
                    minX = i;
                    break;
                }
            }
            for (i=line_size-1; i>=maxX; i--) {/* look for right-most */
                if (in[l*line_size+i] !=0) {
                    maxX = i;
                    break;
                }
            }
        }
        minX = (minX&(-2)); /* truncate to even */
        maxX = (maxX+3)&-2; /* raise to even */

        highestX = maxX*8-1;
        leastX = minX*8;
        extent = highestX -leastX +1;

        outp = swipeBuf;

        /* macro, not fcn call.  Space penalty is modest, speed helps */
#define buffer_store(x)\
        {\
            if (outp-swipeBuf>=swipeBuf_size) {\
                size_t  outp_offset = outp - swipeBuf;\
                size_t  swipeBuf_size_new = swipeBuf_size * 2;\
                byte*   swipeBuf_new = gs_malloc(pdev->memory, swipeBuf_size_new, 1, "lxm_print_page(swipeBuf_new)");\
                if (!swipeBuf_new) { code = gs_error_VMerror; goto xit; }\
                memcpy(swipeBuf_new, swipeBuf, swipeBuf_size);\
                gs_free(pdev->memory, swipeBuf, swipeBuf_size, 1, "lxm_print_page(swipeBuf)");\
                swipeBuf_size = swipeBuf_size_new;\
                swipeBuf = swipeBuf_new;\
                outp = swipeBuf + outp_offset;\
            }\
            *outp++ = (x);\
        }

            {/* work out the bytes to store for this swipe*/

                int sx, sxBy8, sxMask;
                int words[directorySize];
                bool f, sum;
                int retval=0;
                int j,c,y;
                int j1,c1;
                int i,b,x, directory ;

                /* want to set up pointers for (upto two) stripes covered by the output*/

                /* now for each column covered by output: */
                for (x=leastX; x<=highestX; x++) {
                    for (i=0; i<directorySize; i++) {
                        words[i] = 0;
                    }
                    directory = 0x2000;	/* empty directory != 0 */

                    /* prime loops: make comparisons here */
                    switch (direction) {
                    case(RIGHTWARD):
                        sx = (x&1)==1 ? x : x-(((lxm_device*)pdev)->headSeparation);
                        j1 = (x&1);	/* even if x even, odd if x odd */
                        break;
                    default:	/* shouldn't happen ... but compilation checks */
                    case(LEFTWARD):
                        sx = (x&1)==0 ? x : x-((lxm_device*)pdev)->headSeparation;
                        j1 = 1-(x&1);	/* odd if x even, even if x odd */
                    }
                    c1 = 0x8000 >> j1;

                    sxBy8 = sx/8;
                    sxMask = 0x80>>(sx%8);

                    /* loop through all the swipeHeight bits of this column.

                    Note that <sx> looks like it can get out of range, so we
                    check for this here. This fixes bug 701842.

                    [An alternative might be to change above code from 'maxX
                    = (maxX+3)&-2' to 'maxX = (maxX+1)&-2', but that might be
                    risky. */
                    if (sx < pdev->width) {
                        for (i = 0, b=1, y= sxBy8+j1*line_size; i < directorySize; i++,b<<=1) {
                            sum = false;
                            for (j=j1,c=c1 /*,y=i*16*line_size+sxBy8*/; j<16; j+=2, y+=2*line_size, c>>=2) {
                                f = (in[y]&sxMask);
                                if (f) {
                                    words[i] |= c;
                                    sum |= f;
                                }
                            }
                            if (!sum) directory |=b;
                        }
                    }
                    retval+=2;
                    buffer_store(directory>>8); buffer_store(directory&0xff);
                    if (directory != 0x3fff) {
                        for (i=0; i<directorySize; i++) {
                            if (words[i] !=0) {
                                buffer_store(words[i]>>8) ; buffer_store(words[i]&0xff);
                                retval += 2;
                            }
                        }
                    }
                }
#undef buffer_store
            }
        {/* now write out header, then buffered bits */
            int leastY = lnum;

            /* compute size of swipe, needed for header */
            int sz = 0x1a + outp - swipeBuf;

            /* put out header*/
            int deltaY = 2*(leastY - lastY);  /* vert coordinates here are 1200 dpi */
            lastY = leastY;
            outByte(0x1b); outByte('*'); outByte(3);
            outByte(deltaY>>8); outByte(deltaY&0xff);
            outByte(0x1b); outByte('*'); outByte(4); outByte(0); outByte(0);
            outByte(sz>>8); outByte(sz&0xff); outByte(0); outByte(3);
            outByte(1); outByte(1); outByte(0x1a);
            outByte(0);
            outByte(extent>>8); outByte(extent&0xff);
            outByte(leastX>>8); outByte(leastX&0xff);
            outByte(highestX>>8); outByte(highestX&0xff);
            outByte(0); outByte(0);
            outByte(0x22); outByte(0x33); outByte(0x44);
            outByte(0x55); outByte(1);
            /* put out bytes */
            gp_fwrite(swipeBuf,1,outp-swipeBuf,prn_stream);
        }
            lnum += overLap;
            direction ^= 1;
    }/* ends the loop for swipes of the print head.*/

    /* Eject the page and reinitialize the printer */
    {
        static const char bottom[] = {
            fin() /*,  looks like I can get away with only this much ...
            init1(),
            init3(),
            fin()   ,
            top(),
            fin()  */
        };
        gp_fwrite(bottom, 1, sizeof(bottom), prn_stream);
    }
    gp_fflush(prn_stream);

xit:
    if ( buf1 )
        gs_free(pdev->memory, (char *)buf1, in_size, 1, "lxm_print_page(buf1)");
    if ( swipeBuf )
        gs_free(pdev->memory, (char *)swipeBuf, swipeBuf_size, 1, "lxm_print_page(swipeBuf)");
    if (code < 0)
        return_error(code);
    return code;
}

/*
 * There are a number of parameters which can differ between ink cartridges.
 * The Windows driver asks you to recalibrate every time you load a new
 * cartridge.
 * most of the parameters adjusted there relate to color, and so this
 * monotone driver doesn't need them.  However, the Lexmark 5700 black
 * cartridge has two columns of dots, separated by about 16 pixels.
 * This `head separation' distance can vary between cartridges, so
 * we provide a parameter to set it.  In my small experience I've not
 * set the corresponding parameter in windows to anything greater than 17
 * or smaller than 15, but it would seem that it can vary from 1 to 32,
 * based on the calibration choices offered.
 *
 * As I understand the rules laid out in gsparams.h,
 * lxm_get_params is supposed to return the current values of parameters
 * and lxm_put_params is supposed to set up values in the lxm_device
 * structure which can be used by the lxm5700m_print_page routine.
 * I've copied my routines from gdevcdj.c
 */

static int
lxm_get_params(gx_device *pdev, gs_param_list *plist)
{
    lxm_device* const ldev = (lxm_device*)pdev;
    int code = gdev_prn_get_params(pdev, plist);

    if ( code < 0 ) return code;
    code = param_write_int(plist,
                           "HeadSeparation",
                           (int *)&(ldev->headSeparation));

    return code;
}

/* put_params is supposed to check all the parameters before setting any. */
static int
lxm_put_params(gx_device *pdev, gs_param_list *plist)
{
    int ecode;
    lxm_device* const ldev = (lxm_device*)pdev;
    int trialHeadSeparation=ldev->headSeparation;
    int code = param_read_int(plist, "HeadSeparation", &trialHeadSeparation);

    if ( trialHeadSeparation < 1 || trialHeadSeparation > 32 )
        param_signal_error(plist, "HeadSeparation", gs_error_rangecheck);
    /* looks like param_signal_error is not expected to return */
    ecode = gdev_prn_put_params(pdev, plist);	/* call super class put_params */
    if ( code < 0 ) return code;
    if (ecode < 0) return ecode;

    /* looks like everything okay; go ahead and set headSeparation */
    ldev->headSeparation = trialHeadSeparation;
    if ( code == 1) return ecode; /* I guess this means there is no "HeadSeparation" parameter */
    return 0;
}