1 /*
2  * drv-mps803.c - MPS803 printer driver.
3  *
4  * Written by
5  *  Thomas Bretz <tbretz@gsi.de>
6  *  Andreas Boose <viceteam@t-online.de>
7  *
8  * This file is part of VICE, the Versatile Commodore Emulator.
9  * See README for copyright notice.
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24  *  02111-1307  USA.
25  *
26  */
27 
28 /* #define DEBUG_MPS803 */
29 
30 #include "vice.h"
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "archdep.h"
37 #include "driver-select.h"
38 #include "drv-mps803.h"
39 #include "log.h"
40 #include "output-select.h"
41 #include "output.h"
42 #include "palette.h"
43 #include "sysfile.h"
44 #include "types.h"
45 
46 #ifdef DEBUG_MPS803
47 #define DBG(x) printf x
48 #else
49 #define DBG(x)
50 #endif
51 
52 #define MAX_COL 480
53 #define MAX_ROW 66 * 10
54 
55 #define MPS803_ROM_SIZE (7 * 512)
56 
57 #define MPS_REVERSE  0x01
58 #define MPS_CRSRUP   0x02 /* set in gfxmode (default) unset in businessmode */
59 #define MPS_BITMODE  0x04
60 #define MPS_DBLWDTH  0x08
61 #define MPS_REPEAT   0x10
62 #define MPS_ESC      0x20
63 #define MPS_QUOTED   0x40 /* odd number of quotes in line (textmode) */
64 #define MPS_BUSINESS 0x80 /* opened with SA = 7 in businessmode */
65 
66 struct mps_s {
67     uint8_t line[MAX_COL][7];
68     int repeatn;
69     int pos;
70     int tab;
71     uint8_t tabc[3];
72     int mode;
73 };
74 typedef struct mps_s mps_t;
75 
76 #ifdef USE_EMBEDDED
77 #include "printermps803.h"
78 #else
79 static uint8_t charset[512][7];
80 #endif
81 
82 static mps_t drv_mps803[NUM_OUTPUT_SELECT];
83 static palette_t *palette = NULL;
84 
85 /* Logging goes here.  */
86 static log_t drv803_log = LOG_ERR;
87 
88 /* ------------------------------------------------------------------------- */
89 /* MPS803 printer engine. */
90 
set_mode(mps_t * mps,unsigned int m)91 static void set_mode(mps_t *mps, unsigned int m)
92 {
93     mps->mode |= m;
94 }
95 
del_mode(mps_t * mps,unsigned int m)96 static void del_mode(mps_t *mps, unsigned int m)
97 {
98     mps->mode &= ~m;
99 }
100 
is_mode(mps_t * mps,unsigned int m)101 static int is_mode(mps_t *mps, unsigned int m)
102 {
103     return mps->mode & m;
104 }
105 
get_charset_bit(mps_t * mps,int nr,unsigned int col,unsigned int row)106 static int get_charset_bit(mps_t *mps, int nr, unsigned int col,
107                            unsigned int row)
108 {
109     int reverse, result;
110 
111     reverse = is_mode(mps, MPS_REVERSE);
112 
113     result = charset[nr][row] & (1 << (7 - col)) ? !reverse : reverse;
114 
115     return result;
116 }
117 
print_cbm_char(mps_t * mps,const uint8_t rawchar)118 static void print_cbm_char(mps_t *mps, const uint8_t rawchar)
119 {
120     unsigned int y, x;
121     int c, err = 0;
122 
123     c = (int)rawchar;
124 
125     /* in the ROM, graphics charset comes first, then business */
126     if (!is_mode(mps, MPS_CRSRUP)) {
127         c += 256;
128     }
129 
130     for (y = 0; y < 7; y++) {
131         if (is_mode(mps, MPS_DBLWDTH)) {
132             for (x = 0; x < 6; x++) {
133                 if ((mps->pos + x * 2) >= MAX_COL) {
134                     err = 1;
135                     break;
136                 }
137                 mps->line[mps->pos + x * 2][y] = get_charset_bit(mps, c, x, y);
138                 if ((mps->pos + x * 2 + 1) >= MAX_COL) {
139                     err = 1;
140                     break;
141                 }
142                 mps->line[mps->pos + x * 2 + 1][y] = get_charset_bit(mps, c, x, y);
143             }
144         } else {
145             for (x = 0; x < 6; x++) {
146                 if ((mps->pos + x) >= MAX_COL) {
147                     err = 1;
148                     break;
149                 }
150                 mps->line[mps->pos + x][y] = get_charset_bit(mps, c, x, y);
151             }
152         }
153     }
154 
155     if (err) {
156         log_error(drv803_log, "Printing beyond limit of %d dots.", MAX_COL);
157     }
158 
159     mps->pos += is_mode(mps, MPS_DBLWDTH) ? 12 : 6;
160 }
161 
write_line(mps_t * mps,unsigned int prnr)162 static void write_line(mps_t *mps, unsigned int prnr)
163 {
164     int x, y;
165 
166     for (y = 0; y < 7; y++) {
167         for (x = 0; x < 480; x++) {
168             output_select_putc(prnr, (uint8_t)(mps->line[x][y]
169                                             ? OUTPUT_PIXEL_BLACK : OUTPUT_PIXEL_WHITE));
170         }
171         output_select_putc(prnr, (uint8_t)(OUTPUT_NEWLINE));
172     }
173 
174     if (!is_mode(mps, MPS_BITMODE)) {
175         /* bitmode:  9 rows/inch (7lines/row * 9rows/inch=63 lines/inch) */
176         /* charmode: 6 rows/inch (7lines/row * 6rows/inch=42 lines/inch) */
177         /*   --> 63lines/inch - 42lines/inch = 21lines/inch missing */
178         /*   --> 21lines/inch / 9row/inch = 3lines/row missing */
179         output_select_putc(prnr, OUTPUT_NEWLINE);
180         output_select_putc(prnr, OUTPUT_NEWLINE);
181         output_select_putc(prnr, OUTPUT_NEWLINE);
182     }
183 
184     mps->pos = 0;
185 }
186 
clear_buffer(mps_t * mps)187 static void clear_buffer(mps_t *mps)
188 {
189     unsigned int x, y;
190 
191     for (x = 0; x < MAX_COL; x++) {
192         for (y = 0; y < 7; y++) {
193             mps->line[x][y] = 0;
194         }
195     }
196 }
197 
bitmode_off(mps_t * mps)198 static void bitmode_off(mps_t *mps)
199 {
200     del_mode(mps, MPS_BITMODE);
201 }
202 
print_bitmask(mps_t * mps,unsigned int prnr,const char c)203 static void print_bitmask(mps_t *mps, unsigned int prnr, const char c)
204 {
205     unsigned int y;
206     unsigned int i;
207 
208     if (!mps->repeatn) {
209         mps->repeatn=1;
210     }
211 
212     for (i = 0; i < (unsigned int)(mps->repeatn); i++) {
213         if (mps->pos >= MAX_COL) {  /* flush buffer*/
214             write_line(mps, prnr);
215             clear_buffer(mps);
216         }
217     for (y = 0; y < 7; y++) {
218         mps->line[mps->pos][y] = c & (1 << (y)) ? 1 : 0;
219     }
220 
221     mps->pos++;
222     }
223     mps->repeatn=0;
224 }
225 
print_char(mps_t * mps,unsigned int prnr,const uint8_t c)226 static void print_char(mps_t *mps, unsigned int prnr, const uint8_t c)
227 {
228     if (mps->tab) {     /* decode tab-number*/
229         mps->tabc[2 - mps->tab] = c;
230 
231         if (mps->tab == 1) {
232             mps->pos =
233                 is_mode(mps, MPS_ESC) ?
234                 mps->tabc[0] << 8 | mps->tabc[1] :
235                 atoi((char *)mps->tabc) * 6;
236 
237             del_mode(mps, MPS_ESC);
238         }
239 
240         mps->tab--;
241         return;
242     }
243 
244     if (is_mode(mps, MPS_ESC) && (c != 16)) {
245         del_mode(mps, MPS_ESC);
246     }
247 
248     if (is_mode(mps, MPS_REPEAT)) {
249         mps->repeatn = c;
250         del_mode(mps, MPS_REPEAT);
251         return;
252     }
253 
254     if (is_mode(mps, MPS_BITMODE) && (c & 128)) {
255         print_bitmask(mps, prnr, c);
256         return;
257     }
258 
259     /* it seems that CR works even in quote mode */
260     switch (c) {
261         case 13: /* CR*/
262             mps->pos = 0;
263             if (is_mode(mps, MPS_BUSINESS)) {
264                 del_mode(mps, MPS_CRSRUP);
265             } else {
266                 set_mode(mps, MPS_CRSRUP);
267             }
268             /* CR resets Quote mode, revers mode, ... */
269             del_mode(mps, MPS_QUOTED);
270             del_mode(mps, MPS_REVERSE);
271             write_line(mps, prnr);
272             clear_buffer(mps);
273             return;
274     }
275 
276     /* in text mode ignore most (?) other control chars when quote mode is active */
277     if (!is_mode(mps, MPS_QUOTED) || is_mode(mps, MPS_BITMODE)) {
278 
279         switch (c) {
280             case 8:
281                 set_mode(mps, MPS_BITMODE);
282                 return;
283 
284             case 10: /* LF*/
285                 write_line(mps, prnr);
286                 clear_buffer(mps);
287                 return;
288 
289 #ifdef notyet
290             /* Not really sure if the MPS803 recognizes this one... */
291             case 13 + 128: /* shift CR: CR without LF (from 4023 printer) */
292                 mps->pos = 0;
293                 if (is_mode(mps, MPS_BUSINESS)) {
294                     del_mode(mps, MPS_CRSRUP);
295                 } else {
296                     set_mode(mps, MPS_CRSRUP);
297                 }
298                 /* CR resets Quote mode, revers mode, ... */
299                 del_mode(mps, MPS_QUOTED);
300                 del_mode(mps, MPS_REVERSE);
301                 return;
302 #endif
303 
304             case 14: /* EN on*/
305                 set_mode(mps, MPS_DBLWDTH);
306                 if (is_mode(mps, MPS_BITMODE)) {
307                     bitmode_off(mps);
308                 }
309                 return;
310 
311             case 15: /* EN off*/
312                 del_mode(mps, MPS_DBLWDTH);
313                 if (is_mode(mps, MPS_BITMODE)) {
314                     bitmode_off(mps);
315                 }
316                 return;
317 
318             case 16: /* POS*/
319                 mps->tab = 2; /* 2 chars (digits) following, number of first char*/
320                 return;
321 
322             /*
323             * By sending the cursor up code [CHR$(145)] to your printer, following
324             * characters will be printed in cursor up (graphic) mode until either
325             * a carriage return or cursor down code [CHR$(17)] is detected.
326             *
327             * By sending the cursor down code [CHR$(17)] to your printer,
328             * following characters will be printed in business mode until either
329             * a carriage return or cursor up code [CHR$(145)] is detected.
330             */
331             case 17: /* crsr dn, enter businessmode local */
332                 del_mode(mps, MPS_CRSRUP);
333                 return;
334 
335             case 145: /* CRSR up, enter gfxmode local */
336                 set_mode(mps, MPS_CRSRUP);
337                 return;
338 
339             case 18:
340                 set_mode(mps, MPS_REVERSE);
341                 return;
342 
343             case 146: /* 18+128*/
344                 del_mode(mps, MPS_REVERSE);
345                 return;
346 
347             case 26: /* repeat last chr$(8) c times.*/
348                 set_mode(mps, MPS_REPEAT);
349                 mps->repeatn = 1;
350                 return;
351 
352             case 27:
353                 set_mode(mps, MPS_ESC); /* followed by 16, and number MSB, LSB*/
354                 return;
355         }
356 
357     }
358 
359     if (is_mode(mps, MPS_BITMODE)) {
360         return;
361     }
362 
363    /*
364     * When an odd number of CHR$(34) is detected in a line, the control
365     * codes $00-$1F and $80-$9F will be made visible by printing a
366     * reverse character for each of these controls. This will continue
367     * until an even number of quotes [CHR$(34)] has been received or until
368     * end of this line.
369     */
370     if (c == 34) {
371         mps->mode ^= MPS_QUOTED;
372     }
373 
374     if (mps->pos >= MAX_COL) {  /* flush buffer*/
375         write_line(mps, prnr);
376         clear_buffer(mps);
377     }
378 
379     if (is_mode(mps, MPS_QUOTED)) {
380         if (c <= 0x1f) {
381             set_mode(mps, MPS_REVERSE);
382             print_cbm_char(mps, (uint8_t)(c + 0x40));
383             del_mode(mps, MPS_REVERSE);
384             return;
385         }
386         if ((c >= 0x80) && (c <= 0x9f)) {
387             set_mode(mps, MPS_REVERSE);
388             print_cbm_char(mps, (uint8_t)(c - 0x20));
389             del_mode(mps, MPS_REVERSE);
390             return;
391         }
392     }
393 
394     print_cbm_char(mps, c);
395 }
396 
init_charset(uint8_t chrset[512][7],const char * name)397 static int init_charset(uint8_t chrset[512][7], const char *name)
398 {
399     uint8_t romimage[MPS803_ROM_SIZE];
400 
401     if (sysfile_load(name, romimage, MPS803_ROM_SIZE, MPS803_ROM_SIZE) < 0) {
402         log_error(drv803_log, "Could not load MPS-803 charset '%s'.", name);
403         return -1;
404     }
405 
406     memcpy(chrset, romimage, MPS803_ROM_SIZE);
407 
408     return 0;
409 }
410 
411 /* ------------------------------------------------------------------------- */
412 /* Interface to the upper layer.  */
413 
drv_mps803_open(unsigned int prnr,unsigned int secondary)414 static int drv_mps803_open(unsigned int prnr, unsigned int secondary)
415 {
416     /*
417      *  sa = 0: graphic mode.. . (default)
418      *  sa = 7: business mode
419      * This is *probably* incorrect: I suspect it happens anew for every
420      * OPEN CHANNEL SA (each PRINT# statement). Or maybe the state is
421      * even remembered for each SA separately.
422      */
423     if (secondary == 0) {
424         set_mode(&drv_mps803[prnr], MPS_CRSRUP);
425     } else if (secondary == 7) {
426         set_mode(&drv_mps803[prnr], MPS_BUSINESS);
427     } else if (secondary == DRIVER_FIRST_OPEN) {
428         /* Is this the first open? */
429         output_parameter_t output_parameter;
430 
431         output_parameter.maxcol = MAX_COL;
432         output_parameter.maxrow = MAX_ROW;
433         output_parameter.dpi_x = 60;    /* mps803 has different horizontal & vertical dpi - see pg 49 of the manual part H. */
434         output_parameter.dpi_y = 72;    /* NOTE - mixed dpi might not be liked by some image viewers */
435         output_parameter.palette = palette;
436 
437         return output_select_open(prnr, &output_parameter);
438     }
439 
440     return 0;
441 }
442 
drv_mps803_close(unsigned int prnr,unsigned int secondary)443 static void drv_mps803_close(unsigned int prnr, unsigned int secondary)
444 {
445     output_select_close(prnr);
446 }
447 
448 /*
449  * We would like to have calls for LISTEN and UNLISTEN as well...
450  * this may be important for emulating the proper cursor up/down
451  * mode associated with SA=0 or 7.
452  */
453 
drv_mps803_putc(unsigned int prnr,unsigned int secondary,uint8_t b)454 static int drv_mps803_putc(unsigned int prnr, unsigned int secondary, uint8_t b)
455 {
456     DBG(("drv_mps803_putc(%d,%d:$%02x)\n", prnr, secondary, b));
457     print_char(&drv_mps803[prnr], prnr, b);
458     return 0;
459 }
460 
drv_mps803_getc(unsigned int prnr,unsigned int secondary,uint8_t * b)461 static int drv_mps803_getc(unsigned int prnr, unsigned int secondary, uint8_t *b)
462 {
463     DBG(("drv_mps803_getc(%d,%d)\n", prnr, secondary));
464     return output_select_getc(prnr, b);
465 }
466 
drv_mps803_flush(unsigned int prnr,unsigned int secondary)467 static int drv_mps803_flush(unsigned int prnr, unsigned int secondary)
468 {
469     DBG(("drv_mps803_flush(%d,%d)\n", prnr, secondary));
470     return output_select_flush(prnr);
471 }
472 
drv_mps803_formfeed(unsigned int prnr)473 static int drv_mps803_formfeed(unsigned int prnr)
474 {
475     DBG(("drv_mps803_formfeed(%d)\n", prnr));
476     return 0;
477 }
478 
drv_mps803_init_resources(void)479 int drv_mps803_init_resources(void)
480 {
481     driver_select_t driver_select;
482 
483     driver_select.drv_name = "mps803";
484     driver_select.drv_open = drv_mps803_open;
485     driver_select.drv_close = drv_mps803_close;
486     driver_select.drv_putc = drv_mps803_putc;
487     driver_select.drv_getc = drv_mps803_getc;
488     driver_select.drv_flush = drv_mps803_flush;
489     driver_select.drv_formfeed = drv_mps803_formfeed;
490 
491     driver_select_register(&driver_select);
492 
493     return 0;
494 }
495 
drv_mps803_init(void)496 int drv_mps803_init(void)
497 {
498     const char *color_names[2] = {"Black", "White"};
499 
500     drv803_log = log_open("MPS-803");
501 
502     init_charset(charset, "mps803");
503 
504     palette = palette_create(2, color_names);
505 
506     if (palette == NULL) {
507         return -1;
508     }
509 
510     if (palette_load("mps803" FSDEV_EXT_SEP_STR "vpl", palette) < 0) {
511 #ifndef __LIBRETRO__
512         log_error(drv803_log, "Cannot load palette file `%s'.",
513                   "mps803" FSDEV_EXT_SEP_STR "vpl");
514 #endif
515         return -1;
516     }
517 
518     return 0;
519 }
520 
drv_mps803_shutdown(void)521 void drv_mps803_shutdown(void)
522 {
523     DBG(("drv_mps803_shutdown\n"));
524     palette_free(palette);
525 }
526