/*
 ******************************************************************************
 *                                                                            *
 *    fastdump.c  version 1.1 of 19 June 1988    (C) L.J.M. de Wit 1988       *
 *                                                                            *
 * This software may be used and distributed freely if not used commercially  *
 * and the originator (me) is mentioned.                                      *
 *                                                                            *
 ******************************************************************************
 *
 * NAME
 *    fastdump - fast ascii screen dump
 *
 * SYNTAX
 *    fastdump.prg
 *
 * DESCRIPTION
 *    Fastdump does a fast screen dump whenever the Alt Help key is pressed.
 *    It reads the screen memory to match on font characters; matched
 *    characters are printed as-is, non-matched characters as spaces.
 *    It has some intelligence as to avoid printing trailing spaces.
 *    It is very useful as afterwards printer, e.g. after a compilation with
 *    error messages, or a program that exits with failure messages, and also
 *    for printing source/text from within an editor, or for programs that do
 *    not provide for hardcopy output.
 *
 *    Fastdump should be in the AUTO folder as FASTDUMP.PRG so that it is
 *    installed memory resident when the system is loaded. It then overrides
 *    the standard screen dump routine.
 *
 * BUGS
 *    The program does not provide for RS232 printing yet. The Centronics port
 *    is used.
 *
 *    The routine is not used when it is called from the XBIOS. This is because
 *    of old disk-based TOS versions that had the dump vector hardcoded. A
 *    future release of this program (if there will be any &-) will use the
 *    screen dump vector at address 0x502.
 *
 *    Characters in reverse video are not matched yet with their reverse images
 *    so that reverse video now results in spaces. Also something for the next
 *    release.
 *
 * DECISIONS
 *    The current version is compiled with a Lattice C compiler; people using
 *    other compilers should be aware of the fact that on entrance of do_dump()
 *    the contents of A4/A5 will be different from what it was when the
 *    program was started (being an interrupt-routine) so that register
 *    relative addressing cannot be used in this case (or A4 should be loaded
 *    when the routine is entered). For Lattice C absolute addressing is the
 *    default so that there was no problem in this case.
 *    After compilation the module should be linked only with the standard
 *    library (for the gemdos function). So the link file should contain:
 *            INPUT fastdump.bin
 *            LIBRARY clib.bin
 *    startup.bin is not used, and the function startup() is used as entry point.
 *    The size of the resulting code is much smaller now (not using stdio etc).
 *
 */

#include <osbind.h>
#include <portab.h>

#define DUMPFLG  (*(WORD *)0x4EE)            /* Alt Help pressed flag       */
#define SSHIFTMD (*(BYTE *)0x44C)            /* Screen Shift Mode           */
#define V_BAS_AD (*(LONG *)0x44E)            /* logical screen base address */
#define NVBLS    (*(WORD *)0x454)            /* length of this array        */
#define VBLQUEUE (*(que_ptr *)0x456)         /* array of VBL vectors        */
#define HZ_200   (*(LONG *)0x4BA)            /* 200 Hz system clock         */
#define CENTRONICS_BUSY (1 & *(BYTE *)0xFFFFFA01)
#define GI       ((BYTE *)0xFFFF8800)        /* I/O locations for sound chip*/

/* font definitions */
typedef struct font_header {
   WORD f_id;       /* font identifier; system font = 1 */
   WORD f_point;    /* font size in points */
   char f_name[32]; /* font name */
   WORD f_low;      /* lowest ascii code in font */
   WORD f_high;     /* highest ascii code in font */
   WORD f_top;      /* relative distance of top to base line */
   WORD f_ascent;   /* relative distance of ascent to base line */
   WORD f_half;     /* relative distance of half to base line */
   WORD f_descent;  /* relative distance of descent to base line */
   WORD f_bottom;   /* relative distance of bottom to base line */
   WORD f_maxcharw; /* maximal character width in font */
   WORD f_maxcellw; /* maximal cell width in font */
   WORD f_loffset;  /* left offset */
   WORD f_roffset;  /* right offset */
   WORD f_fatsiz;   /* degree of fattening */
   WORD f_ulsiz;    /* underline degree */
   WORD f_normask;  /* normal cancelled mask */
   WORD f_skewmask; /* skewing cancelled mask */
   WORD f_flag;     /* flag:
                     *  bit 0: system font;
                     *  bit 1: hor. offset;
                     *  bit 2: byte swap;
                     *  bit 3: non proportional font
                     */
   WORD *f_horoff;  /* pointer to horizontal offsets table */
   WORD *f_charoff; /* pointer to character offsets table */
   char *f_data;    /* pointer to font data table */
   WORD f_fontw;    /* total width of all chars in font */
   WORD f_fonth;    /* height of font (# scanlines) */
   struct font_header
          *f_next;  /* pointer to next font header */
} font_header;

static UWORD g_fheader[] = {  0xA000,        /* LineA exception 0   */
                              0x222F,0x0004, /* MOVE.L  4(SP),D1    */
                              0xE581,        /* ASL.L   #2,D1       */
                              0x2031,0x1800, /* MOVE.L  0(A1,D1),D0 */
                              0x4E75 };      /* RTS                 */

static UWORD get_sr[]    = {  0x7000,        /* MOVEQ.L #0,D0       */
                              0x40C0,        /* MOVE.W  SR,D0       */
                              0x007C,0x0700, /* OR.W    #$700,SR    */
                              0x4E75 };      /* RTS                 */

static UWORD set_sr[]    = {  0x46EF,0x0006, /* MOVE.W 6(SP),SR     */
                              0x4E75 };      /* RTS                 */

WORD planes;
font_header *fhp;

static void do_dump(), startup();
static BYTE lstout(),  get_schar(),
            read_gi(), write_gi(), dummy();

typedef void (**que_ptr)();  /* que_ptr points to an array of pointers
                              *    to functions returning void
                              */

static void startup(base)
LONG *base;
{
   LONG memneed;
   extern LONG _mneed;
   WORD i, nvbls;
   LONG ssp;

   memneed = 0x100   +                    /* base page   */
             base[3] +                    /* text length */
             base[5] +                    /* data length */
             base[7] +                    /* bss  length */
             0x800   +                    /* workspace   */
             0x900;                       /* for stack   */

   ssp = Super(0);                        /* Supervisor mode             */
   nvbls = NVBLS;                         /* length of VBLQUEUE array    */

   for (i = 0; i < nvbls; i++) {
      if (VBLQUEUE[i] == (void (*)())0) {/* If empty slot found          */
         VBLQUEUE[i] = do_dump;          /* set vector for new routine   */
         break;
      }
   }

   Super(ssp);                            /* Back to User mode again     */

   if (i < nvbls) {                       /* If empty slot was found     */
      Ptermres(memneed,0);                /* make resident & terminate   */
   } else {                               /* else report the problem &   */
      Cconws("Cannot bind new VBL routine\r\n");
      Pterm(1);                           /* terminate with error status */
   }
}

static void do_dump()                     /* The interrupt routine       */
{
   WORD i, j;
   char *s, *t;
   WORD rows, cols;                       /* Rows & columns of screen    */
   char line[81];                         /* Holds a line to be printed  */
   BYTE status;                           /* Printer ready status        */
   BYTE rez;                              /* resolution 2 high 1 med 0 low*/

   if (DUMPFLG != 0) {                    /* Alt Help not pressed?       */
      return;
   }

   if ((rez = SSHIFTMD & 3) == 3) {
      rez = 2;
   }
   fhp = (*(font_header *(*)())g_fheader)
         ((rez == 2) ? 2 : 1);            /* pointer to standard font    */
   planes = 4 >> rez;                     /* # of bit planes: 4, 2 or 1  */
   rows = 25;
   cols = (rez == 0) ? 40 : 80;

   for (i = 0; i < rows; i++) {           /* Handle each screen row ...  */
      s = line; t = line;
      for (j = 0; j < cols; j++, s++) {   /* Handle each column per row  */
         *s = get_schar(i,j);             /* Get ASCII value at this pos.*/
         if ((*s != ' ') || (*t != ' ')) {/* t: last nonsp. or first sp. */
            t = s;
         }
      }
      *s = '\0';                          /* null-terminate line         */
      if (*t == ' ') {
         *t = '\0';                       /* discards trailing spaces    */
      }

      status = 0;
      for (s = line; *s != '\0'; s++) {   /* print each char/test status */
         status = lstout(*s);
         if (status != 0) break;          /* printer not ready; abort    */
      }

      if (status != 0) break;             /*        abort                */

      lstout('\r'); lstout('\n');         /* terminate line with CR/LF   */

      if (DUMPFLG != 0) {                 /* Alt Help pressed again?     */
         break;
      }
   }

   DUMPFLG = -1;                          /* No Hardcopy                 */
}

static BYTE lstout(c)                     /* Send one character          */
char c;                                   /* to Centronics port          */
{
   LONG start;
   BYTE not_ready;

   start = HZ_200;
   do {                                   /* poll the status             */
      not_ready = CENTRONICS_BUSY;
      dummy();                            /* to prevent erroneous optim. */
   } while (not_ready &&
                (HZ_200 - start < 6000)); /* 30 sec. timeout             */

   if (!not_ready) {                      /* The actual printing         */
      BYTE val;
      LONG istatus;

      istatus = (*(LONG (*)())get_sr)();  /* Save SR                     */
      val = read_gi(7);
      write_gi(7,val | 0x80);
      (*(LONG (*)())set_sr)(istatus);     /* Restore SR                  */
      write_gi(15,c);

      val = read_gi(14);
      write_gi(14,val & 0xDF);
      val = read_gi(14);
      write_gi(14,val | 0x20);
   }

   return not_ready;                      /* return 0 for OK             */
}

static BYTE read_gi(reg)                  /* Read from G.I. sound chip   */
BYTE reg;
{
   LONG istatus;

   istatus = (*(LONG (*)())get_sr)();     /* Save SR                     */
   GI[0] = reg;
   reg = dummy();                         /* prevents optimizing away .. */
   reg = GI[0];                           /* ... this statement          */
   (*(LONG (*)())set_sr)(istatus);        /* Restore SR                  */

   return reg;
}

static BYTE dummy()                       /* This function is used to    */
{                                         /* fool the compiler so that   */
   return 0;                              /* no statement is optimized   */
}                                         /* away (volatile variables)   */

static BYTE write_gi(reg,val)             /* Write to G.I. sound chip    */
BYTE reg,val;
{
   LONG istatus;

   istatus = (*(LONG (*)())get_sr)();     /* Save SR                     */
   GI[0] = reg;
   GI[2] = val;
   reg = dummy();                         /* prevents optimizing away .. */
   reg = GI[0];                           /* ... this statement          */
   (*(LONG (*)())set_sr)(istatus);        /* Restore SR                  */

   return reg;
}

static BYTE get_schar(i,j)                /* Find match in a font for    */
WORD i,j;                                 /* the character bit image at  */
{                                         /* position (row,col)          */
   register WORD l;                       /* line counter                */
   register BYTE *cpd,                    /* char ptr into font_data     */
                 *cps;                    /* char ptr into ch_img[]      */
   register WORD p, c;
   WORD *curadd,                          /* screen address of top word  */
        sval;                             /* will hold a word of image   */
   BYTE ch_img[16],                       /* will hold image as bytes    */
        or_all;                           /* OR of all bytes of image    */
   UWORD w_p_l,                           /* # words per line            */
         maxl;                            /* height of a char in lines   */

   w_p_l = (planes == 1) ? 40 : 80;
   maxl = (planes == 1) ? 16 : 8;
   curadd = (WORD *)(V_BAS_AD + i * 1280 + (j & ~1) * planes);

   /* prepare ch_img[] to hold a adjusted copy of the bit image */
   or_all = 0;
   for (l = 0; l < maxl; l++) {
      sval = 0;
      for (p = 0; p < planes; p++) {      /* OR in all colour bit planes */
         sval |= curadd[l * w_p_l + p];
      }

      ch_img[l] = (j & 1) ? sval & 0xff   /* Take lower or upper byte    */
                   : (sval >> 8) & 0xff;  /* as appropriate              */
      or_all |= ch_img[l];                /* Keeps inclusive Or of all   */
   }

   /* search */
   if (or_all == 0) {                     /* Not a pixel set             */
      c = ' ';                            /* then space will be printed  */
   } else {                               /* else for each char in font  */
      for (c = fhp->f_low; c <= fhp->f_high; c++) {
         cpd = fhp->f_data + (fhp->f_charoff[c] >> 3);
         cps = ch_img;
         for (l = 0; l < maxl; l++) {     /* Compare each line (byte)    */
            if (*cps++ != *cpd) break;    /* Match failed at this line   */
            cpd += fhp->f_fontw;
         }
         if (l >= maxl) break;            /* All lines matched           */
      }

      if ((c == '\0') || (c > fhp->f_high)) { /* If no match             */
         c = ' ';                         /* use space instead           */
      }
   }

   return (BYTE)c;                        /* Return matched char or space*/
}


