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