1 /*
2  * drv-1520.c - 1520 plotter driver.
3  *
4  * Written by
5  *  Olaf Seibert <rhialto@falu.nl>
6  *
7  * This file is part of VICE, the Versatile Commodore Emulator.
8  * See README for copyright notice.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23  *  02111-1307  USA.
24  *
25  */
26 
27 #include "vice.h"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <assert.h>
33 #include <math.h>
34 
35 #include "archdep.h"
36 #include "driver-select.h"
37 #include "drv-1520.h"
38 #include "lib.h"
39 #include "log.h"
40 #include "output-select.h"
41 #include "output.h"
42 #include "palette.h"
43 #include "types.h"
44 
45 /*#define DEBUG1520       1*/
46 /*#define DEBUG1520_A   1*/
47 
48 /*
49  * Each line segment is 0,2 mm.
50  * At 150 DPI that would be 11,8 pixels.
51  */
52 
53 #define MAX_Y_COORD             998
54 #define MIN_Y_COORD             -998
55 #define STEPS_PER_MM            5
56 #define MAX_COL                 480
57 #define MAX_ROW                 (MAX_Y_COORD - MIN_Y_COORD + 1)
58 #define VERTICAL_CLEARANCE      (10 * STEPS_PER_MM)     /* 10 mm */
59 #define PIXELS_PER_STEP         5
60 
61 #define X_PIXELS                (PIXELS_PER_STEP * (MAX_COL+1))
62 #define Y_PIXELS                (PIXELS_PER_STEP * (MAX_ROW+1))
63 
64 #define CR                      13
65 
66 /*
67  * Since the plotter's y coordinates follow the usual mathematic
68  * convention (positive y axis is "up"), but programs like to number
69  * rows from the top down, we'll invert the logical plotter coordinates
70  * to obtain array indices (which start at 0 and increase as you go down
71  * the page).
72  */
73 
74 enum command_stage {
75     start,
76     letter_seen,
77     x_seen,
78     y_seen
79 };
80 
81 struct plot_s {
82     int prnr;
83     uint8_t (*sheet)[Y_PIXELS][X_PIXELS];
84     int colour;         /* sa = 2 */
85     int colour_accu;
86     int charsize;       /* sa = 3; segment multiplication */
87     int charsize_accu;
88     int rotation;       /* sa = 4 */
89     int rotation_accu;
90     int scribe;         /* sa = 5 */
91     int scribe_accu;
92     int scribe_state;
93     int lowercase;      /* sa = 6 */
94     int lowercase_accu;
95     int quote_mode;
96     enum command_stage command_stage;   /* sa = 1 */
97     int command;
98     int command_x;
99     int command_y;
100     int command_flags;
101     int abs_origin_x, abs_origin_y;     /* relative to start of page */
102     int rel_origin_x, rel_origin_y;     /* relative to abs_origin */
103     int cur_x, cur_y;                   /* relative to abs_origin */
104     int lowest_y;                       /* relative to start of page */
105 };
106 typedef struct plot_s plot_t;
107 
108 static plot_t drv_1520[NUM_OUTPUT_SELECT];
109 static palette_t *palette = NULL;
110 
111 /* Logging goes here.  */
112 static log_t drv1520_log = LOG_ERR;
113 
114 #define PIXEL_INDEX_WHITE       0
115 #define PIXEL_INDEX_BLACK       1
116 
117 static uint8_t tochar[] = {
118     OUTPUT_PIXEL_WHITE,         /* paper */
119     OUTPUT_PIXEL_BLACK,         /* 1520 colour numbers: 0 - 3 */
120     OUTPUT_PIXEL_BLUE,
121     OUTPUT_PIXEL_GREEN,
122     OUTPUT_PIXEL_RED,
123 };
124 
125 /* ------------------------------------------------------------------------- */
126 /* 1520 plotter engine. */
127 
write_lines(plot_t * mps,int lines)128 static void write_lines(plot_t *mps, int lines)
129 {
130     int x, y;
131     int prnr = mps->prnr;
132 
133 #if DEBUG1520
134     log_message(drv1520_log, "write_lines: %d lines", lines);
135 #endif
136 
137     lines *= PIXELS_PER_STEP;
138 
139     for (y = 0; y < lines; y++) {
140         for (x = 0; x < X_PIXELS; x++) {
141             output_select_putc(prnr, tochar[(*mps->sheet)[y][x]]);
142         }
143         output_select_putc(prnr, (uint8_t)OUTPUT_NEWLINE);
144     }
145 }
146 
147 /*
148  * The current location becomes the new hard origin.
149  * Anything that is now too far away in the "up" direction
150  * can be printed for sure and erased from our buffered sheet.
151  */
reset_hard_origin(plot_t * mps)152 static void reset_hard_origin(plot_t *mps)
153 {
154 #if DEBUG1520
155     log_message(drv1520_log, "reset_hard_origin: abs_origin_y=%d lowest_y=%d cur_y=%d", mps->abs_origin_y, mps->lowest_y, mps->cur_y);
156 #endif
157     mps->abs_origin_x += mps->cur_x;
158     mps->abs_origin_y += mps->cur_y;
159 #if DEBUG1520
160     log_message(drv1520_log, "                 : abs_origin_y=%d lowest_y=%d", mps->abs_origin_y, mps->lowest_y);
161 #endif
162 
163     mps->cur_x = mps->cur_y = 0;
164 
165     /* If the hard origin is reset, does the relative origin too reset?
166      * Assume "yes" for now.
167      */
168     mps->rel_origin_x = mps->rel_origin_y = 0;
169 
170     if (mps->abs_origin_y < -MAX_Y_COORD) {
171         int y_segments = -mps->abs_origin_y - MAX_Y_COORD;
172         int y_pixels = y_segments * PIXELS_PER_STEP;
173         int remaining_y_pixels = Y_PIXELS - y_pixels;
174 #if DEBUG1520
175         log_message(drv1520_log, "reset_hard_origin: output and shift %d pixels (%d Y-coordinates)", y_pixels, y_segments);
176 #endif
177 
178         write_lines(mps, y_segments);
179         memmove(&(*mps->sheet)[0][0],                   /* destination */
180                 &(*mps->sheet)[y_pixels][0],            /* source */
181                 remaining_y_pixels * X_PIXELS);
182 
183         memset(&(*mps->sheet)[remaining_y_pixels][0],
184                 PIXEL_INDEX_WHITE,
185                 y_pixels * X_PIXELS);
186 
187         mps->abs_origin_y += y_segments;
188         mps->lowest_y += y_segments;
189 #if DEBUG1520
190     log_message(drv1520_log, "                 : abs_origin_y=%d lowest_y=%d", mps->abs_origin_y, mps->lowest_y);
191 #endif
192     }
193 }
194 
eject(plot_t * mps)195 static void eject(plot_t *mps)
196 {
197 #if DEBUG1520
198     log_message(drv1520_log, "eject");
199 #endif
200     write_lines(mps, -mps->lowest_y + 1);
201 
202     memset(&(*mps->sheet)[0][0], PIXEL_INDEX_WHITE, Y_PIXELS * X_PIXELS);
203 
204     mps->abs_origin_x = 0;
205     mps->abs_origin_y = -VERTICAL_CLEARANCE;
206     mps->cur_x = 0;
207     mps->cur_y = 0;
208     mps->lowest_y = mps->abs_origin_y;
209 }
210 
vice_min(int a,int b)211 static inline int vice_min(int a, int b)
212 {
213     return a < b ? a : b;
214 }
215 
216 /* FIXME: dead code? */
217 #if 0
218 static inline int vice_max(int a, int b)
219 {
220     return a > b ? a : b;
221 }
222 #endif
223 
mix(uint8_t * old,int new)224 static void mix(uint8_t *old, int new)
225 {
226     if (*old == PIXEL_INDEX_WHITE) {
227         *old = new;
228     } else if (*old == new) {
229         /* no action needed */
230     } else {
231         /* multiple colours on top of each other make "black" */
232         *old = PIXEL_INDEX_BLACK;
233     }
234 }
235 
plot(plot_t * mps,int x,int y)236 static void plot(plot_t *mps, int x, int y)
237 {
238 #if DEBUG1520_A
239     log_message(drv1520_log, "plot: (%4d,%4d) scribe=%d state=%d", x, y, mps->scribe, mps->scribe_state);
240 #endif
241     if (mps->scribe) {
242         if (mps->scribe_state < mps->scribe) {
243             mix(&(*mps->sheet)[y][x], PIXEL_INDEX_BLACK + mps->colour);
244         }
245         mps->scribe_state++;
246         if (mps->scribe_state >= 2 * mps->scribe) {
247             mps->scribe_state = 0;
248         }
249     } else {
250         mix(&(*mps->sheet)[y][x], PIXEL_INDEX_BLACK + mps->colour);
251     }
252 }
253 
254 
255 
256 /*
257  * Standard line drawing algorithm from Bresenham.
258  * See http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Simplification
259  * (retrieved Sat Nov 30 11:09:21 CET 2013).
260  */
bresenham(plot_t * mps,int x0,int y0,int x1,int y1)261 static void bresenham(plot_t *mps, int x0, int y0, int x1, int y1)
262 {
263     int dx = abs(x0 - x1);
264     int dy = abs(y0 - y1);
265 
266     int sx = (x0 < x1) ? 1 : -1;
267     int sy = (y0 < y1) ? 1 : -1;
268     int err = dx - dy;
269 #if DEBUG1520_B
270     log_message(drv1520_log, "bresenham: (%4d,%4d)...(%4d,%4d)",  x0, y0, x1, y1);
271 #endif
272 
273     for (;;) {
274         int e2;
275         plot(mps, x0, y0);
276         if (x0 == x1 && y0 == y1)
277             break;
278         e2 = 2 * err;
279         if (e2 > -dy) {
280             err -= dy;
281             x0  += sx;
282         }
283         if (x0 == x1 && y0 == y1) {
284             plot(mps, x0, y0);
285             break;
286         }
287         if (e2 < dx) {
288             err += dx;
289             y0  += sy;
290         }
291     }
292 }
293 
294 /*
295  * Inspired by
296  * http://www.zoo.co.uk/~murphy/thickline/index.html
297  * in particular the parallel version, but using the structure of the
298  * existing bresenham() function so that it functions in all octants.
299  */
bresenham_par(plot_t * mps,int x0,int y0,int x1,int y1,int wd)300 static void bresenham_par(plot_t *mps, int x0, int y0, int x1, int y1, int wd)
301 {
302     int dy = abs(x0 - x1);      /* rotated by 90 degrees */
303     int dx = abs(y0 - y1);
304 
305     int sy = (x0 <  x1) ? 1 : -1;
306     int sx = (y0 >= y1) ? 1 : -1;
307     int err = dx - dy;
308     int xo = 0, yo = 0;
309     int scribe = mps->scribe_state;
310 #if DEBUG1520_B
311     log_message(drv1520_log, "bresenham_par: (%4d,%4d)...(%4d,%4d) dx=%d dy=%d sx=%d sy=%d",  x0, y0, x1, y1, dx, dy, sx, sy);
312 #endif
313 
314     /* Draw middle of the line */
315     bresenham(mps, x0, y0, x1, y1);
316 
317     /* Draw parallel lines on both sides of the middle */
318     for (wd--; wd > 0; wd -= 2) {
319         int e2;
320         e2 = 2 * err;
321         if (e2 > -dy) {
322             err -= dy;
323             xo  += sx;
324             /* mps->colour = vice_max(1, (mps->colour+1) % 4); *//* DEBUG */
325             mps->scribe_state = scribe;
326             bresenham(mps, x0 + xo, y0 + yo, x1 + xo, y1 + yo);
327             if (wd > 1) {
328                 mps->scribe_state = scribe;
329                 bresenham(mps, x0 - xo, y0 - yo, x1 - xo, y1 - yo);
330             }
331         }
332         if (e2 < dx) {
333             err += dx;
334             yo  += sy;
335             /* mps->colour = vice_max(1, (mps->colour+1) % 4); *//* DEBUG */
336             mps->scribe_state = scribe;
337             bresenham(mps, x0 + xo, y0 + yo, x1 + xo, y1 + yo);
338             if (wd > 1) {
339                 mps->scribe_state = scribe;
340                 bresenham(mps, x0 - xo, y0 - yo, x1 - xo, y1 - yo);
341             }
342         }
343     }
344 }
345 
346 #define Assert(val, expr) do {  \
347         if (!(expr)) {          \
348             log_error(drv1520_log, "%s %d: assertion %s failed: %d", __FILE__, __LINE__, #expr, val);\
349             return;             \
350         }                       \
351     } while (0)
352 
draw(plot_t * mps,int from_x,int from_y,int to_x,int to_y)353 static void draw(plot_t *mps, int from_x, int from_y, int to_x, int to_y)
354 {
355 #if DEBUG1520_C
356     log_error(drv1520_log, "draw:                                 (%4d,%4d)...(%4d,%4d)", from_x, from_y, to_x, to_y);
357 #endif
358     /*
359      * Convert to absolute plotter coordinates.
360      * Add 1 here to make room for the line thickness.
361      */
362     from_x += mps->abs_origin_x + 1;
363     from_y += mps->abs_origin_y + 1;
364       to_x += mps->abs_origin_x + 1;
365       to_y += mps->abs_origin_y + 1;
366 
367     mps->lowest_y = vice_min(mps->lowest_y, vice_min(from_y, to_y));
368 
369 #if DEBUG1520_C
370     log_message(drv1520_log, "draw: applied abs origin (%4d,%4d): (%4d,%4d)...(%4d,%4d) lowest_y=%d",  mps->abs_origin_x, mps->abs_origin_y, from_x, from_y, to_x, to_y, mps->lowest_y);
371 #endif
372     /* Flip y-axis */
373     from_y = -from_y;
374       to_y = -  to_y;
375 
376 #if DEBUG1520_C
377     log_message(drv1520_log, "draw: flip y:                         (%4d,%4d)...(%4d,%4d)",  from_x, from_y, to_x, to_y);
378 #endif
379 
380     Assert(from_x, from_x > 0);
381     Assert(  to_x,   to_x > 0);
382     Assert(from_x, from_x <= MAX_COL);
383     Assert(  to_x,   to_x <= MAX_COL);
384 
385     Assert(from_y, from_y > 0);
386     Assert(  to_y,   to_y > 0);
387     Assert(from_y, from_y <= MAX_ROW);
388     Assert(  to_y,   to_y <= MAX_ROW);
389 
390     /* Convert to bitmap resolution and coordinates */
391 
392     from_x *= PIXELS_PER_STEP;
393     from_y *= PIXELS_PER_STEP;
394       to_x *= PIXELS_PER_STEP;
395       to_y *= PIXELS_PER_STEP;
396 
397     /* Every line re-starts the dashed line state */
398     mps->scribe_state = 0;
399 
400     /*bresenham(mps, from_x, from_y, to_x, to_y);*/
401     bresenham_par(mps, from_x, from_y, to_x, to_y, PIXELS_PER_STEP);
402 }
403 
404 /*
405  * Reverse-engineered drawing commands for the characters.
406  */
407 char *punct[32] = {
408     /*   */ NULL,
409     /* ! */ "66 d 8624 u 88 d 888886222224 u",
410     /* " */ "988888 d 2 u 96 d 2",
411     /* # */ "9 d 8888 u 1 d 6666 u 7 d 2222 u 9 d 4444",
412     /* $ */ "96 d 6697447966 u 7 d 222222",
413     /* % */ "8 d 9999 u 444 d 4268 u 332 d 6248",
414     /* & */ "6666 d 777789321123699",               /* long line first */
415     /* ' */ "888899 d 22",
416     /* ( */ "6666 d 478888896",
417     /* ) */ "66   d 698888874",
418     /* * */ "8 d 9999 u 4444 d 3333 u 7788 d 2222", /* vertical down last */
419     /* + */ "888 d 6666 u 11 d 8888",               /* right, then down */
420     /* , */ "66 d 98426",
421     /* - */ "888 d 66666",
422     /* . */ "666 d 8624",
423     /* / */ "d 9999",
424     /* 0 */ "8 d 8888966322221447 9999",
425     /* 1 */ "88888 d 9222222 u 4 d 66",             /* down */
426     /* 2 */ "88888 d 9663211116666",
427     /* 3 */ "88888 d 9663214 u 6 d321447",
428     /* 4 */ "9966 d 44448999222222",
429     /* 5 */ "8 d 3669887444886666",
430     /* 6 */ "888 d 966322144788889663",
431     /* 7 */ "d 9999884444",
432     /* 8 */ "999 d 98744123 u 66 d 3214478966",
433     /* 9 */ "8 d 36698888744123669",
434     /* : */ "99 d 8624 u 88 d 8624",
435     /* ; */ "96 d 98426 u 88 d 8426",
436     /* < */ "999988 d 111333",
437     /* = */ "88 d 6666 u 7744 d 6666",
438     /* > */ "d 999777",
439     /* ? */ "88888 d 96632142 u 2 d 2"
440 };
441 /* typically 4 units wide, and 6 units high. */
442 char *uppercase[32] = {
443     /* @ */ "669 d 8884422266 98874412223666", /* anti-clockwise from inside */
444     /* A */ "d 888899332222 u 888 d 4444",
445     /* B */ "d 888888666321444 u 666 d 321444",
446     /* C */ "99998 d 744122223669",
447     /* D */ "d 888888666322221444",
448     /* E */ "6666 d 4444 888888 6666 u 1114 d 666 u 1114",
449     /* F */ "d 8888886666 u 1114 d 666",
450     /* G */ "99998 d 74412222366688844",
451     /* H */ "d 888888 u 222 d 6666 u 888 d 222222",
452     /* I */ "6 d 66 u 4 d 888888 u 4 d 66",
453     /* J */ "8 d 36988888",
454     /* K */ "d 888888 u 6666 d 1111 u 9 d 333",
455     /* L */ "888888 d 222222 6666",
456     /* M */ "d 888888332 u 8 d 99222222",
457     /* N */ "d 888888 u 2 d 3333 u 2 d 888888",
458     /* O */ "8 d 8888966322221447",
459     /* P */ "d 888888666321444",
460     /* Q */ "8 d 8888966322221447 u 96 d 33",
461     /* R */ "d 888888666321444 u 6 d 333",
462     /* S */ "8 d 36698744789663",
463     /* T */ "66 d 888888 u 44 d 6666",
464     /* U */ "888888 d 22222366988888",
465     /* V */ "888888 d 222233998888",
466     /* W */ "888888 d 222222998 u 2 d 33888888",
467     /* X */ "d 899998 u 4444 d 233332 ",
468     /* Y */ "66 d 888778 u 6666 d 211", /* ?? */
469     /* Z */ "888888 d 66662111126666",
470     /* [ */ "66 d 44 888888 66",
471 /* pound */ "99999 d 41222266 u 887 d 44 u 1 d 1369 u 4 d 7",   /* ?? */
472     /* ] */ "d 666 888888 44 ",
473     /* ^ */ "66 d 88888 11 u 99 d 33",
474     /* <-*/ "66999 d 44444 333 u 777 d 999",
475 };
476 /* Typically 3 units wide and 4 high (plus ascenders / descenders). */
477 char *lowercase[32] = {
478     /* --*/ "888 d 666666",
479     /* a */ "8888 d 66322147896323",
480     /* b */ "888888 d 22222266988744",
481     /* c */ "999 d 74122369",
482     /* d */ "999888 d 22222244788966",
483     /* e */ "88 d 666874122366",
484     /* f */ "6 d 888886 u 112 d 66",/* ?? */
485     /* g */ "996 d 1478963222147",
486     /* h */ "d 888888 u 22 d 663222",
487     /* i */ "66 d 888 u 8 d 8",
488     /* j */ "d 369888 u 8 d 8",
489     /* k */ "d 888888 u 336 d 111 u 9 d 33",
490     /* l */ "888889 d 222222 6",
491     /* m */ "d 8888 u 2 d 93222 u 888 d 93222",
492     /* n */ "d 8888 u 2 d 963222",
493     /* o */ "8 d 8896322147",           /* clockwise */
494     /* p */ "8 d 66987442222",          /* anti-clockwise */
495     /* q */ "6663 d 4888884412366",     /* anti-clockwise */
496     /* r */ "8888 d 3222 u 888 d 963",  /* from serif down, then top bow */
497     /* s */ "8 d 369747963",
498     /* t */ "8888 d 66 u 78 d 2222226", /* down */
499     /* u */ "8888 d 22226668888",       /* down right up */
500     /* v */ "8888 d 22339988",
501     /* w */ "8888 d 222398 u 2 d 39888",
502     /* x */ "d 9999 u 4444 d 3333",
503     /* y */ "8888 d 233 u 998 d 21111", /* top left to middle, top right to left bottom */
504     /* z */ "8888 d 666611116666",      /* top to bottom */
505     /* | */ "9998888 d 22222222",
506     /* __*/ "2 d 66666666",
507     /* ^_*/ "d 666666777111",/* too wide? */
508     /* pi*/ "6 d 88866222 u 44488 d 9669 ",/* clockwise square, then hat */
509     /* []*/ "d 8888886666622222244444",
510 };
511 
512 int dir[20] = { /* delta-x, delta-y */
513      0,  0,     /* 0 */
514     -1, -1,     /* 1   7 8 9 */
515      0, -1,     /* 2   4 5 6 */
516      1, -1,     /* 3   1 2 3 */
517     -1,  0,     /* 4 */
518      0,  0,     /* 5 */
519      1,  0,     /* 6 */
520     -1,  1,     /* 7 */
521      0,  1,     /* 8 */
522      1,  1,     /* 9 */
523 };
524 
525 #define LINEFEED       10
526 #define LW              4       /* letter width */
527 #define SW              6       /* letter pitch */
528 #define LH              6       /* letter height */
529 
draw_char(plot_t * mps,char * commands)530 static void draw_char(plot_t *mps, char *commands)
531 {
532     int x = mps->cur_x;
533     int y = mps->cur_y;
534     int s = mps->charsize;
535     int pen_down = 0;
536     char command;
537 
538     /*
539      * For normal and rotated letters, the tops of the
540      * (upper case) letters line up. Since we draw down from the
541      * starting point (when rotated), move "letter height" units up.
542      */
543     if (mps->rotation) {
544         y += LH * s;
545         x += s;
546     }
547 
548     while ((command = *commands++) != '\0') {
549         if (command == 'u') {
550             pen_down = 0;
551         } else if (command == 'd') {
552             pen_down = 1;
553         } else if (command >= '0' && command <= '9') {
554             int num = 2 * (command - '0');
555             int newx, newy;
556 
557             if (mps->rotation) {
558                 newx = x + s * dir[num + 1];
559                 newy = y - s * dir[num];
560             } else {
561                 newx = x + s * dir[num];
562                 newy = y + s * dir[num + 1];
563             }
564 
565             /* TODO: this is probably not how it was really handled: */
566             newx = vice_min(MAX_COL, newx);
567             if (pen_down) {
568                 draw(mps, x, y, newx, newy);
569             }
570 
571             x = newx;
572             y = newy;
573         }
574     }
575 }
576 
print_char_text(plot_t * mps,uint8_t c)577 static void print_char_text(plot_t *mps, uint8_t c)
578 {
579     int underline = 0;
580     char **tab;
581 
582     if (c == CR) {
583         mps->cur_x = 0;
584         mps->cur_y -= mps->charsize * LINEFEED;
585         reset_hard_origin(mps);
586         mps->quote_mode = 0;
587         return;
588     }
589 
590     if (c == 0xFF) {    /* Handle pi */
591         c = 0xDE;
592     }
593 
594     switch (c & 0x60) {
595         case 0x00:
596             if (mps->quote_mode) {
597                 c += 0x40;
598                 underline++;
599                 tab = uppercase;
600             } else {
601                 tab = NULL;
602             }
603             break;
604         case 0x20:
605             tab = punct;
606             break;
607         case 0x40:
608             tab = uppercase;
609             break;
610         case 0x60:
611         default:        /* pacify gcc */
612             tab = NULL;
613             break;
614     }
615 
616     if (c == 0x22) {
617         mps->quote_mode = !mps->quote_mode;
618     }
619 
620     if (tab == uppercase) {
621         /* shifted char XOR lowercase mode */
622         if (!(c & 0x80) != !mps->lowercase) {
623             tab = lowercase;
624         }
625     }
626 
627     if (tab && tab[c & 0x1F]) {
628         draw_char(mps, tab[c & 0x1F]);
629     }
630 
631     mps->cur_x += mps->charsize * SW;
632 
633     if (underline) {
634         draw(mps, mps->cur_x - mps->charsize * SW, mps->cur_y - 1,
635                   mps->cur_x                     , mps->cur_y - 1);
636     }
637 }
638 
639 #define INCOMPLETE      99999
640 
641 /*
642  * Parse a number until a CR is seen.
643  * Expects *accu to start out at 0, and resets it to 0 when it returns
644  * the final result.
645  * Calls before that return INCOMPLETE.
646  * White space is ignored.
647  * Other characters reset the accumulator to 0.
648  */
numparser(int * accu,uint8_t c)649 static int numparser(int *accu, uint8_t c)
650 {
651     /*
652      * TODO: what does it do with other characters?
653      * TODO: what happens if a command is not finished with a CR
654      *       before the unlisten is sent? Does the unlisten cause
655      *       the command to be executed, or is it the CR?
656      *       And if it is the CR, is the state remembered when
657      *       the printing to the secondary address is continued?
658      */
659 
660     if (c >= '0' && c <= '9') {
661         *accu *= 10;
662         *accu += c - '0';
663     } else if (c == CR) {
664         int result = *accu;
665         *accu = 0;
666         return result;
667     } else if (c == ' ' || c == 29 /* crsr right */) {
668         /* Ignore */
669     } else {
670         *accu = 0;
671     }
672 
673     return INCOMPLETE;
674 }
675 
676 #define FNUM_START      0x01
677 #define FNUM_MINUS      0x02
678 #define FNUM_DOT        0x04
679 #define FNUM_E          0x08
680 
681 /*
682  * This parser considers a number finished when a space or other
683  * non-numeric character is seen but ignores leading spaces.
684  * It expects *accu to start out as 0 and leaves the result in there.
685  *
686  * It does a very approximate parsing of floating point numbers:
687  * if it sees an E indicating an exponent, the result is 0.
688  * The rationale is that if the exponent is negative, the number is less
689  * than 1 and thus truncates to 0.
690  * If the exponent is positive, it is likely to be at least 7, and
691  * therefore the number overflows rather a lot.
692  * (the 1520 indeed interprets 240E0 and 24E1 etc as 0!)
693  */
fnumsparser(int * accu,int * state,uint8_t c)694 static int fnumsparser(int *accu, int *state, uint8_t c)
695 {
696     /*
697      * TODO: what does it do with other characters?
698      * TODO: what if 2 numbers run together because the second one
699      *       is negative: e.g.  10-10
700      */
701 
702     if (*state & FNUM_START) {
703         if (c == '-') {
704             *accu = 0;
705             *state |=  FNUM_MINUS;
706             *state &= ~FNUM_START;
707         } else if (c >= '0' && c <= '9') {
708             *accu = (c - '0');
709             *state &= ~FNUM_START;
710         } else if (c == '.') {
711             *accu = 0;
712             *state |=  FNUM_DOT;        /* ignore further digits */
713             *state &= ~FNUM_START;
714         } else  if (c == ' ' || c == 29 /* crsr right */) {
715             /* Ignore */
716         } else {
717             /* TODO: what to do here? */
718         }
719     } else {
720         if (c >= '0' && c <= '9') {
721             if (!(*state & (FNUM_DOT|FNUM_E))) {
722                 *accu *= 10;
723                 *accu += (c - '0');
724                 if (*accu > 998) {
725                     *accu = 998;
726                 }
727             }
728         } else if (c == '.') {
729             *state |= FNUM_DOT; /* ignore further digits */
730         } else if (c == 'E') {
731             *state |= FNUM_E;   /* ignore further digits */
732             *accu = 0;          /* number too small or too large anyway */
733         } else if (c == '-') {
734             if (*state & FNUM_E) {
735                 /* ignore it */
736             } else {
737                 goto done;
738             }
739         } else {
740 done:
741             /* SPACE, CR, other junk -> Finished */
742             if (*state & FNUM_MINUS) {
743                 *accu = - *accu;
744                 *state &= ~FNUM_MINUS;
745             }
746             *state = FNUM_START;
747             return *accu;
748         }
749     }
750 
751     return INCOMPLETE;
752 }
753 
print_char_plot(plot_t * mps,const uint8_t c)754 static void print_char_plot(plot_t *mps, const uint8_t c)
755 {
756     int value;
757 
758     /*
759      * TODO: what does it do with incorrect command letters?
760      */
761     switch (mps->command_stage) {
762     case start:
763         if (strchr("HIMDRJ", c)) {
764             mps->command = c;
765             mps->command_x = 0;
766             mps->command_y = 0;
767             mps->command_flags = FNUM_START;
768             mps->command_stage = letter_seen;
769 #if DEBUG1520
770             log_message(drv1520_log, "print_char_plot: command_stage := letter_seen");
771 #endif
772         }
773         break;
774     case letter_seen:
775         value = fnumsparser(&mps->command_x, &mps->command_flags, c);
776         if (value != INCOMPLETE) {
777             mps->command_stage = x_seen;
778 #if DEBUG1520
779             log_message(drv1520_log, "print_char_plot: command_stage := x_seen");
780 #endif
781         }
782         break;
783     case x_seen:
784         value = fnumsparser(&mps->command_y, &mps->command_flags, c);
785         if (value != INCOMPLETE) {
786             mps->command_stage = y_seen;
787 #if DEBUG1520
788             log_message(drv1520_log, "print_char_plot: command_stage := y_seen");
789 #endif
790         }
791         break;
792     case y_seen:
793         /* By now we really hope to see a CR - ignore anything else. */
794         break;
795     }
796 
797     if (c == CR) {
798         int new_x, new_y;
799 #if DEBUG1520
800             log_message(drv1520_log, "print_char_plot: executing command %c %d %d", mps->command, mps->command_x, mps->command_y);
801 #endif
802 
803         /* TODO: Q: what if not enough numbers are given?
804          * A: It seems that 0 is used.
805          *
806          * TODO: Q: what if the numbers are too large? >= 999
807          * A1: x > 480: head tries to move there anyway; motor buzzes.
808          */
809 
810 
811         switch (mps->command) {
812         case 'H':       /* move to absolute origin */
813             mps->cur_x = 0;
814             mps->cur_y = 0;
815             break;
816         case 'I':       /* set relative origin here */
817             mps->rel_origin_x = mps->cur_x;
818             mps->rel_origin_y = mps->cur_y;
819             break;
820         case 'M':       /* move to (x,y) relative to absolute origin */
821             mps->cur_x = mps->command_x;
822             mps->cur_y = mps->command_y;
823             break;
824         case 'R':       /* move to (x,y) relative to relative origin */
825             mps->cur_x = mps->rel_origin_x + mps->command_x;
826             mps->cur_y = mps->rel_origin_y + mps->command_y;
827             break;
828         case 'D':       /* draw to (x,y) relative to absolute origin */
829             new_x = mps->command_x;
830             new_y = mps->command_y;
831             draw(mps, mps->cur_x, mps->cur_y, new_x, new_y);
832             mps->cur_x = new_x;
833             mps->cur_y = new_y;
834             break;
835         case 'J':       /* draw to (x,y) relative to relative origin */
836             new_x = mps->rel_origin_x + mps->command_x;
837             new_y = mps->rel_origin_y + mps->command_y;
838             draw(mps, mps->cur_x, mps->cur_y, new_x, new_y);
839             mps->cur_x = new_x;
840             mps->cur_y = new_y;
841             break;
842         default:        /* some error - ignore */
843             break;
844         }
845 #if DEBUG1520
846             log_message(drv1520_log, "print_char_plot: cur = (%4d, %4d) rel origin = (%4d, %4d)", mps->cur_x, mps->cur_y, mps->rel_origin_x, mps->rel_origin_y);
847 #endif
848 
849         mps->command_stage = start;
850         mps->command = '?';
851     }
852 }
853 
print_char_select_colour(plot_t * mps,const uint8_t c)854 static void print_char_select_colour(plot_t *mps, const uint8_t c)
855 {
856     int value;
857 
858     value = numparser(&mps->colour_accu, c);
859 
860     if (value != INCOMPLETE) {
861         mps->colour = value % 4;
862     }
863 }
864 
print_char_select_character_size(plot_t * mps,const uint8_t c)865 static void print_char_select_character_size(plot_t *mps, const uint8_t c)
866 {
867     int value;
868 
869     value = numparser(&mps->charsize_accu, c);
870 
871     if (value != INCOMPLETE) {
872         mps->charsize = 1 << (value % 4);
873     }
874 }
875 
print_char_select_character_rotation(plot_t * mps,const uint8_t c)876 static void print_char_select_character_rotation(plot_t *mps, const uint8_t c)
877 {
878     int value;
879 
880     value = numparser(&mps->rotation_accu, c);
881 
882     if (value != INCOMPLETE) {
883         mps->rotation = value % 2;
884     }
885 }
886 
print_char_select_scribe(plot_t * mps,const uint8_t c)887 static void print_char_select_scribe(plot_t *mps, const uint8_t c)
888 {
889     int value;
890 
891     value = numparser(&mps->scribe_accu, c);
892 
893     if (value != INCOMPLETE) {
894         mps->scribe = (value % 16) * PIXELS_PER_STEP;
895 #if DEBUG1520
896         log_message(drv1520_log, "print_char_select_scribe: scribe = %d (%d)", mps->scribe, value);
897 #endif
898     } else {
899 #if DEBUG1520
900         log_message(drv1520_log, "print_char_select_scribe: scribe_accu = %d", mps->scribe_accu);
901 #endif
902     }
903 }
904 
print_char_select_lowercase(plot_t * mps,const uint8_t c)905 static void print_char_select_lowercase(plot_t *mps, const uint8_t c)
906 {
907     int value;
908 
909     value = numparser(&mps->lowercase_accu, c);
910 
911     if (value != INCOMPLETE) {
912         mps->lowercase = value % 2;
913     }
914 }
915 
draw_one_square(plot_t * mps,int colour)916 static void draw_one_square(plot_t *mps, int colour)
917 {
918     mps->colour = colour;
919 
920 #define SZ (4 * STEPS_PER_MM)   /* 4 mm */
921 
922     draw(mps, mps->cur_x   , mps->cur_y   , mps->cur_x   , mps->cur_y+SZ);
923     draw(mps, mps->cur_x   , mps->cur_y+SZ, mps->cur_x+SZ, mps->cur_y+SZ);
924     draw(mps, mps->cur_x+SZ, mps->cur_y+SZ, mps->cur_x+SZ, mps->cur_y   );
925     draw(mps, mps->cur_x+SZ, mps->cur_y   , mps->cur_x   , mps->cur_y   );
926 
927     mps->cur_x += SZ + STEPS_PER_MM;    /* 1mm in between squares */
928 }
929 
draw_four_squares(plot_t * mps)930 static void draw_four_squares(plot_t *mps)
931 {
932     int c;
933 
934     for (c = 1; c < 5; c++) {   /* blue, green, red, black */
935         draw_one_square(mps, c % 4);
936     }
937     mps->cur_x = 0;
938     mps->cur_y -= 4 * STEPS_PER_MM;
939     reset_hard_origin(mps);
940     mps->colour = 0;
941 }
942 
943 
power_on_reset(plot_t * mps)944 static void power_on_reset(plot_t *mps)
945 {
946     int prnr = mps->prnr;
947 
948     if (mps->sheet) {
949         lib_free(mps->sheet);
950     }
951 
952     memset(mps, 0, sizeof(*mps));
953 
954     mps->prnr = prnr;
955     mps->charsize = 1 << 1;
956     mps->sheet = lib_calloc(Y_PIXELS, X_PIXELS * sizeof(uint8_t));
957     mps->abs_origin_x = 0;
958     mps->abs_origin_y = -VERTICAL_CLEARANCE;
959 
960     draw_four_squares(mps);
961 }
962 
print_char_reset(plot_t * mps,const uint8_t c)963 static void print_char_reset(plot_t *mps, const uint8_t c)
964 {
965     if (c == CR) {
966         power_on_reset(mps);
967     }
968 }
969 
970 /* ------------------------------------------------------------------------- */
971 /* Interface to the upper layer.  */
972 
drv_1520_open(unsigned int prnr,unsigned int secondary)973 static int drv_1520_open(unsigned int prnr, unsigned int secondary)
974 {
975 #if DEBUG1520
976     log_message(drv1520_log, "drv_1520_open: sa=%d prnr=%d", secondary, prnr);
977 #endif
978 
979     /* Is this the first open? */
980     if (secondary == DRIVER_FIRST_OPEN) {
981         output_parameter_t output_parameter;
982         plot_t *mps = &drv_1520[prnr];
983 
984         output_parameter.maxcol = X_PIXELS;
985         output_parameter.maxrow = Y_PIXELS;
986         output_parameter.dpi_x = (PIXELS_PER_STEP * STEPS_PER_MM * 254) / 10;
987         output_parameter.dpi_y = (PIXELS_PER_STEP * STEPS_PER_MM * 254) / 10;
988         output_parameter.palette = palette;
989 
990         drv_1520[prnr].prnr = prnr;
991         power_on_reset(mps);
992 
993         return output_select_open(prnr, &output_parameter);
994     } else if (secondary > 7) {
995         return -1;
996     }
997 
998     return 0;
999 }
1000 
drv_1520_close(unsigned int prnr,unsigned int secondary)1001 static void drv_1520_close(unsigned int prnr, unsigned int secondary)
1002 {
1003 #if DEBUG1520
1004     log_message(drv1520_log, "drv_1520_close: sa=%d prnr=%d", secondary, prnr);
1005 #endif
1006 
1007     /* Is this the last close? */
1008     if (secondary == DRIVER_LAST_CLOSE) {
1009         plot_t *mps = &drv_1520[prnr];
1010 
1011 #if DEBUG1520
1012         log_message(drv1520_log, "drv_1520_close: last close");
1013 #endif
1014         eject(mps);
1015 
1016         if (mps->sheet) {
1017             lib_free(mps->sheet);
1018             mps->sheet = NULL;
1019             output_select_close(prnr);
1020         }
1021     }
1022 }
1023 
drv_1520_putc(unsigned int prnr,unsigned int secondary,uint8_t b)1024 static int drv_1520_putc(unsigned int prnr, unsigned int secondary, uint8_t b)
1025 {
1026     plot_t *mps = &drv_1520[prnr];
1027 
1028 #if DEBUG1520
1029     log_message(drv1520_log, "drv_1520_putc: sa=%d b='%c' (%d) prnr=%d", secondary, b, b, prnr);
1030 #endif
1031 
1032     switch (secondary) {
1033     case 0:
1034         print_char_text(mps, b);
1035         break;
1036     case 1:
1037         print_char_plot(mps, b);
1038         break;
1039     case 2:
1040         print_char_select_colour(mps, b);
1041         break;
1042     case 3:
1043         print_char_select_character_size(mps, b);
1044         break;
1045     case 4:
1046         print_char_select_character_rotation(mps, b);
1047         break;
1048     case 5:
1049         print_char_select_scribe(mps, b);
1050         break;
1051     case 6:
1052         print_char_select_lowercase(mps, b);
1053         break;
1054     case 7:
1055         print_char_reset(mps, b);
1056         break;
1057     default:
1058         return -1;
1059     }
1060 
1061     return 0;
1062 }
1063 
drv_1520_getc(unsigned int prnr,unsigned int secondary,uint8_t * b)1064 static int drv_1520_getc(unsigned int prnr, unsigned int secondary, uint8_t *b)
1065 {
1066     return output_select_getc(prnr, b);
1067 }
1068 
drv_1520_flush(unsigned int prnr,unsigned int secondary)1069 static int drv_1520_flush(unsigned int prnr, unsigned int secondary)
1070 {
1071 #if DEBUG1520
1072     log_message(drv1520_log, "drv_1520_flush");
1073 #endif
1074     return output_select_flush(prnr);
1075 }
1076 
drv_1520_formfeed(unsigned int prnr)1077 static int drv_1520_formfeed(unsigned int prnr)
1078 {
1079 #if DEBUG1520
1080     log_message(drv1520_log, "drv_1520_formfeed");
1081 #endif
1082     plot_t *mps = &drv_1520[prnr];
1083 
1084     if (mps->prnr == (int)prnr && mps->sheet != NULL) {
1085         eject(mps);
1086     }
1087     return 0;
1088 }
1089 
drv_1520_init_resources(void)1090 int drv_1520_init_resources(void)
1091 {
1092     driver_select_t driver_select;
1093 #if DEBUG1520
1094     log_message(drv1520_log, "drv_1520_init_resources");
1095 #endif
1096 
1097     driver_select.drv_name = "1520";
1098     driver_select.drv_open = drv_1520_open;
1099     driver_select.drv_close = drv_1520_close;
1100     driver_select.drv_putc = drv_1520_putc;
1101     driver_select.drv_getc = drv_1520_getc;
1102     driver_select.drv_flush = drv_1520_flush;
1103     driver_select.drv_formfeed = drv_1520_formfeed;
1104 
1105     driver_select_register(&driver_select);
1106 
1107     return 0;
1108 }
1109 
drv_1520_init(void)1110 int drv_1520_init(void)
1111 {
1112     static const char *color_names[5] =
1113     {
1114         "Black", "White", "Blue", "Green", "Red"
1115     };
1116 
1117     drv1520_log = log_open("plot1520");
1118 
1119 #if DEBUG1520
1120     log_message(drv1520_log, "drv_1520_init");
1121 #endif
1122 
1123     palette = palette_create(5, color_names);
1124 
1125     if (palette == NULL) {
1126         return -1;
1127     }
1128 
1129     if (palette_load("1520" FSDEV_EXT_SEP_STR "vpl", palette) < 0) {
1130 #ifndef __LIBRETRO__
1131         log_error(drv1520_log, "Cannot load palette file `%s'.",
1132                   "1520" FSDEV_EXT_SEP_STR "vpl");
1133 #endif
1134         return -1;
1135     }
1136 
1137     return 0;
1138 }
1139 
drv_1520_shutdown(void)1140 void drv_1520_shutdown(void)
1141 {
1142 #if DEBUG1520
1143     log_message(drv1520_log, "drv_1520_shutdown");
1144 #endif
1145     palette_free(palette);
1146 }
1147