/*
 * $Id: cgmin.c,v 1.1 2005-09-18 22:04:36 dhmunro Exp $
 * Define the CGM reader/echoer for GIST.
 */
/* Copyright (c) 2005, The Regents of the University of California.
 * All rights reserved.
 * This file is part of yorick (http://yorick.sourceforge.net).
 * Read the accompanying LICENSE file for details.
 */

#include "play.h"
#include "cgmin.h"
#include "engine.h"

#include <stdio.h>
#include "pstdio.h"

#include <string.h>
#include "pstdlib.h"

extern void CGMinfo(void);  /* defined here */

/* function that returns the number of pages in a CGM file family */
extern int CGM_pages(void);

/* Some commercial GKS packages (e.g.- ATC) set the palette to
   0==black, 1==white, then use color 1 as the foreground color, but
   print it as black (sigh).  This flag tries to accomodate such
   deviant behavior.  */
extern int bg0fg1;
int bg0fg1= 0;

typedef struct CGM CGM;
typedef unsigned char Octet;

Engine *outEngines[8];
int outTypes[8];

static int EchoPage(int number, int inc);

static CGM *NewCGM(char *name, p_file *file, int maxPages);
static CGM *DeleteCGM(CGM *cgm);
static void NextName(char *filename);
static int BeginCatalog(CGM *cgm);

static long ReadParameters(long nOctets, GpColor *colors, long maxColors);
static int SkipParameters(long nOctets);
static long ReadElement(int *klass, int *id);

static long DePascalify(char *text, Octet *pasctxt, long nMax);
static int Snarf16(Octet *param);
static long SnarfInteger(Octet *param);
static GpReal SnarfReal(Octet *param);
static void RememberPoint(GpReal x0, GpReal y0);
static char *SnarfMoreText(Octet *param);
static char *SnarfText(Octet *param);
static char *PathifyText(char *text, int path);
static GpReal SnarfSA(Octet *param, int md, GpReal dflt);
static int SnarfColor(Octet *param, long nOctets);
static void ConvertPoints(long n, short *vdc, GpReal *ndcx, GpReal *ndcy);
static int CheckScratch(long nPoints);

static void SetVDC(int *vdc);
static void GSetClip(int on);
static void ResetPalette(int forPage);

static int DoClass0(int id, long nOctets);
static int DoClass1(int id, long nOctets);
static int DoClass2(int id, long nOctets);
static int DoClass3(int id, long nOctets);
static int DoClass4(int id, long nOctets);
static int DoClass5(int id, long nOctets);
static int DoClass6(int id, long nOctets);
static int DoClass7(int id, long nOctets);

#define NCAR_RECORD_SIZE 1440L
#define N_BUFSIZE 4096L
static unsigned long n_fread(void *ptr, unsigned long nbytes, CGM *cgm);
static int n_fseek(CGM *cgm, long offset, int relative);
static long n_ftell(CGM *cgm);
static long n_fskip(void *ptr, long nbytes, CGM *cgm);

/* ------------------------------------------------------------------------ */

static int currentPage= 0;

static char metafileName[256];                    /* from BEGIN METAFILE */
static char pictureName[256];          /* from most recent BEGIN PICTURE */
static char metafileDescription[256];       /* from METAFILE DESCRIPTION */
static int isGistCGM= 0;  /* set if metafileDescription begins "Gist;  " */

static int nBytesInt= 2;
static int vdcIllegible, realIllegible, indexIllegible,
  cvalIllegible, cndxIllegible, cselIllegible;

#define N_GIST_FONTS 20
static char *gistFonts[]= {
  "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique",
  "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic",
  "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique",
  "Symbol", "Symbol", "Symbol", "Symbol",
  "NewCenturySchlbk-Roman", "NewCenturySchlbk-Bold",
  "NewCenturySchlbk-Italic", "NewCenturySchlbk-BoldItalic" };
static int defaultNumbers[]= {
  0, 8, 4, 12, 1, 9, 5, 13, 2, 10, 6, 14, 3, 11, 7, 15, 16, 17, 18, 19 };

/* Transformation from vdc->ndc is known on a per-page basis */
#define DEFAULT_METRIC (1.0/(25545.24*(ONE_INCH/25.4)))
static GpReal cgmScale= (1.0/25545.24), cgmScaleX= (1.0/25545.24),
  cgmScaleY= (1.0/25545.24), cgmOffsetX= 0.0, cgmOffsetY= 0.0;
int cgmLandscape= 0;
extern int cgmScaleToFit;
int cgmScaleToFit= 0;

/* May as well have a permanently allocated palette of maximal size */
static GpColorCell cgmPalette[256], defaultPalette[256];
static int cgmNColors= 0, defaultNColors=  0, cgmNColMax;
static int cgmColorChange= 0, alteredPalette= 0, setXPalette= 0,
  needPalette= 0;

struct CGMDesc {
  /* metafile descriptor elements */
  int colorMaxI, colorMaxV[6];
  int *fonts, nfonts, maxFonts;

  /* picture descriptor elements */
  int scaling;
  GpReal metric;
  int lwidth, msize, ewidth;
  int vdc[4];
  struct unused_color { unsigned char red, green, blue, gray; } background;

#define ABSTRACT 0
#define METRIC 1
#define INDEXED 0
#define DIRECT 1
#define ABSOLUTE 0
#define SCALED 1
};

/* cgmDesc represents default metafile and picture descriptor settings as
   defined in the ANSI X3.122 - 1986 (Part 3) standard */
struct CGMDesc cgmDesc= {
  63, {0, 0, 0, 255, 255, 255}, defaultNumbers, N_GIST_FONTS, 0,
  0, DEFAULT_METRIC, 1, 1, 1, {0, 0, 32767, 32767}, {255, 255, 255, 255} };

/* pageDesc represents cgmDesc, as modified by the defaults
   replacement section of the metafile header */
struct CGMDesc pageDesc= {
  63, {0, 0, 0, 255, 255, 255}, defaultNumbers, N_GIST_FONTS, 0,
  0, DEFAULT_METRIC, 1, 1, 1, {0, 0, 32767, 32767}, {255, 255, 255, 255} };

/* pictDesc represents pageDesc, as modified
   by any picture descriptor elements */
struct CGMDesc pictDesc= {
  63, {0, 0, 0, 255, 255, 255}, defaultNumbers, N_GIST_FONTS, 0,
  0, DEFAULT_METRIC, 1, 1, 1, {0, 0, 32767, 32767}, {255, 255, 255, 255} };

struct CGMControls {
  int auxColor, transparency;
  short clipBox[4];
  int clipOn, clipDefault;
};

/* cgmControls represent default control settings as defined in the
   ANSI X3.122 - 1986 (Part 3) standard */
struct CGMControls cgmControls= {
  0, 1, {0, 0, 32767, 32767}, 1, 1 };

/* pageControls represent cgmControls, as modified by the defaults
   replacement section of the metafile header */
struct CGMControls pageControls= {
  0, 1, {0, 0, 32767, 32767}, 1, 1 };

/* pictControls represents pageControls, as modified
   by any picture descriptor elements */
struct CGMControls pictControls= {
  0, 1, {0, 0, 32767, 32767}, 1, 1 };

struct NonGistAttribs {
  /* non-Gist constructs */
  int eVisible;         /* edge visibility separate from gistA.e.type */
  int hindex, pindex;   /* separate hatch and pattern indices */
  GpReal upX, upY, baseX, baseY;  /* character up and base vectors */
  GpReal expand, spacing;         /* other text attributes */
  int prec, path;                 /* text precision, path */
};

struct CGMAttributes {

  /* line attributes (GpLines, GpDisjoint) */
  GpLineAttribs l;

  /* marker attributes (GpMarkers) */
  GpMarkerAttribs m;

  /* filled area attributes (GpFill) */
  GpFillAttribs f;

  /* text attributes (GpText) */
  GpTextAttribs t;

  /* non-Gist constructs */
  struct NonGistAttribs ng;
};

/* cgmDefaults represent default attribute settings as defined in the
   ANSI X3.122 - 1986 (Part 3) standard */
static struct CGMAttributes cgmDefaults= {
  { FG_COLOR, L_SOLID, 1.0 },                     /* line attributes */
  { FG_COLOR, M_ASTERISK, 1.0 },                  /* marker attributes */
  { FG_COLOR, F_SOLID },                          /* fill attributes */
  { FG_COLOR, 0, 0.0156,
      TX_RIGHT, TH_NORMAL, TV_NORMAL },           /* text attributes */

  {0, 0, 0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 2, 0}   /* non-Gist attributes,
                                                     except pattern table */
  };

/* pageDefaults represent cgmDefaults, as modified by the defaults
   replacement section of the metafile header */
static struct CGMAttributes pageDefaults= {
  { FG_COLOR, L_SOLID, 1.0 },                     /* line attributes */
  { FG_COLOR, M_ASTERISK, 1.0 },                /* marker attributes */
  { FG_COLOR, F_SOLID },                          /* fill attributes */
  { FG_COLOR, 0, 0.0156,
      TX_RIGHT, TH_NORMAL, TV_NORMAL },           /* text attributes */

  {0, 0, 0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 2, 0}   /* non-Gist attributes,
                                                     except pattern table */
  };

/* current values of non-Gist attributes
   (current values of others in gistA) */
static struct NonGistAttribs nonGistA= {
  0, 0, 0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 2, 0};

/* ------------------------------------------------------------------------ */

struct CGM {
  char *name;
  p_file *file;   /* non-zero only if currently open */
  CGM *prev, *next;
  int firstPage, nPages, maxPages;
  long *pageAddress, catalogEnd;

  /* additional stuff to read NCAR CGM files */
  unsigned char *buffer;
  long pos, valid;
  int ncar;
  long ncar_rec, ncar_len;
};

static CGM *currentCGM= 0, *firstCGM= 0, *catalogCGM= 0;

static int cgmCmdLen;  /* 2 or 4, length of previous ReadElement command */
static Octet *cmdString= 0;
static Octet *currentCmd;
static long cmdLength;

/* Name incrementer copied from cgm.c */
static void NextName(char *filename)
{
  int i, len= filename? strlen(filename) : 0;
  if (len>4 && (strcmp(filename+len-4, ".cgm")==0 ||
                strcmp(filename+len-4, ".CGM")==0)) i= len-4;
  else if (len>5 && (strcmp(filename+len-5, ".ncgm")==0 ||
                     strcmp(filename+len-5, ".NCGM")==0)) i= len-5;
  else i= len;
  while (i-- > 0) {
    if (filename[i]=='9') {
      filename[i]= '0';
    } else {
      if (filename[i]=='Z' || filename[i]>='z') filename[i]= '0';
      else filename[i]++;
      break;
    }
  }
}

static CGM *NewCGM(char *name, p_file *file, int maxPages)
{
  CGM *cgm= (CGM *)p_malloc(sizeof(CGM)+strlen(name)+1);
  long *pageAddress= (long *)p_malloc(sizeof(long)*maxPages);

  if (!cgm || !pageAddress) {
    if (cgm) p_free(cgm);
    if (pageAddress) p_free(pageAddress);
    Warning("memory manager failed trying to open ", name);
    return 0;
  }

  cgm->name= ((char *)cgm)+sizeof(CGM);
  strcpy(cgm->name, name);
  if (!file) {
    NextName(cgm->name);
    file= p_fopen(cgm->name, "rb");
    if (!file) {
      p_free(cgm);
      p_free(pageAddress);
      return 0;
    }
  }
  cgm->file= file;
  cgm->prev= cgm->next= 0;
  cgm->firstPage= 1;
  cgm->nPages= 0;
  cgm->maxPages= maxPages;
  cgm->catalogEnd= -1;
  cgm->pageAddress= pageAddress;

  cgm->buffer = p_malloc(4096);
  cgm->pos = cgm->valid = 0;
  cgm->ncar = 0;
  cgm->ncar_rec = cgm->ncar_len = -1L;

  return cgm;
}

static CGM *DeleteCGM(CGM *cgm)
{
  CGM *next= cgm->next;
  if (cgm->prev) cgm->prev->next= next;
  if (cgm->pageAddress) p_free(cgm->pageAddress);
  if (cgm->buffer) p_free(cgm->buffer);
  p_free(cgm);
  return next;
}

static int BeginCatalog(CGM *cgm)
{
  CGM *oldCGM= currentCGM;
  int klass, id, bad;
  long nOctets;

  do {
    currentCGM= cgm;

    nOctets= ReadElement(&klass, &id);

    if (klass==0 && id==1) {
      bad= 0;
      for (;;) {
        SkipParameters(nOctets);
        nOctets= ReadElement(&klass, &id);
        if (nOctets>=0) {
          if (klass==0) {
            if (id==3 || id==2) break;  /* begin picture or end metafile */
            else if (id!=0) {
              if (cgm->ncar)
                Warning("bad class 0 element in metafile descriptor", "");
              bad= 1;
              break;
            }
          } else if (klass!=1 && klass!=6 && klass!=7) {
            if (cgm->ncar)
              Warning("bad element class in metafile descriptor", "");
            bad= 1;
            break;
          }
        } else {
          bad= 1;
          break;
        }
      }
    } else {
      if (cgm->ncar) Warning("BEGIN METAFILE element missing", "");
      bad= 1;
    }

    if (bad) {
      int ncar = cgm->ncar;
      cgm->ncar = 1;
      if (ncar || n_fseek(cgm, 0L, 0)) {
        currentCGM= oldCGM;
        Warning(cgm->name, " is not a binary CGM, cannot open");
        return 1;
      }
    }
  } while (bad);

  if (id==3) {
    cgm->pageAddress[0]= n_ftell(cgm)-cgmCmdLen;
    SkipParameters(nOctets);
    cgm->catalogEnd= n_ftell(cgm);
    cgm->nPages= 1;
  } else {
    cgm->catalogEnd= -1;
  }

  currentCGM= oldCGM;

  return 0;
}

int CatalogCGM(void)
{
  int klass, id, nPage;
  long nOctets;
  CGM *tmp, *cgm= currentCGM;

  if (!catalogCGM) return 0;

  while (catalogCGM->catalogEnd<0) {
    if (catalogCGM!=currentCGM) {
      p_fclose(catalogCGM->file);
      catalogCGM->file= 0;
    }
    nPage= catalogCGM->firstPage+catalogCGM->nPages;
    catalogCGM= NewCGM(catalogCGM->name, (void *)0, 256);
    if (!catalogCGM || BeginCatalog(catalogCGM)) {
      if (catalogCGM) {
        p_fclose(catalogCGM->file);
        DeleteCGM(catalogCGM);
        catalogCGM= 0;
      }
      return 0;
    }
    catalogCGM->firstPage= nPage;
  }

  n_fseek(catalogCGM, catalogCGM->catalogEnd, 0);

  for (;;) {
    currentCGM= catalogCGM;
    for (;;) {
      nOctets= ReadElement(&klass, &id);
      if (nOctets>=0) {
        if (klass==0) {
          if (id==3 || id==2) break;  /* begin picture or end metafile */
        }
        SkipParameters(nOctets);
      } else {
        Warning("missing END METAFILE element in ", catalogCGM->name);
        klass= 0;
        id= 2;
        break;
      }
    }
    currentCGM= cgm;

    if (id==3) {
      if (catalogCGM->maxPages==catalogCGM->nPages) {
        long *pa= (long *)p_malloc(sizeof(long)*(catalogCGM->maxPages+256));
        int i;
        if (!pa) {
          catalogCGM->catalogEnd= -1;
          if (catalogCGM!=currentCGM) p_fclose(catalogCGM->file);
          catalogCGM= 0;
          Warning("memory manager failed cataloging ", catalogCGM->name);
          break;
        }
        for (i=0 ; i<catalogCGM->nPages ; i++)
          pa[i]= catalogCGM->pageAddress[i];
        p_free(catalogCGM->pageAddress);
        catalogCGM->pageAddress= pa;
        catalogCGM->maxPages+= 256;
      }
      catalogCGM->pageAddress[catalogCGM->nPages++]=
        n_ftell(catalogCGM)-cgmCmdLen;
      currentCGM= catalogCGM;
      SkipParameters(nOctets);
      currentCGM= cgm;
      catalogCGM->catalogEnd= n_ftell(catalogCGM);
      break;

    } else { /* found END METAFILE */
      tmp= catalogCGM;
      tmp->catalogEnd= -1;
      if (tmp!=currentCGM) {
        p_fclose(tmp->file);
        tmp->file= 0;
      }
      tmp= tmp->next= NewCGM(catalogCGM->name, (void *)0, 256);
      if (tmp) {
        if (BeginCatalog(tmp)) {
          p_fclose(tmp->file);
          DeleteCGM(tmp);
          tmp= 0;
        } else {
          tmp->prev= catalogCGM;
          tmp->firstPage= catalogCGM->firstPage+catalogCGM->nPages;
        }
      }
      catalogCGM= tmp;
      if (!tmp) return 0;
    }
  }

  return 1;
}

/* ------------------------------------------------------------------------ */

int OpenCGM(char *file)
{
  p_file *f= p_fopen(file, "rb");
  CGM *cgm;
  int klass, id;
  long nOctets;

  if (!f) {
    Warning("unable to open CGM input file: ", file);
    return 1;
  }

  cgm= NewCGM(file, f, 256);
  if (!cgm) {
    p_fclose(f);
    return 1;
  }

  cmdString= 0;  /* just to be sure... */
  if (BeginCatalog(cgm)) {
    p_fclose(f);
    DeleteCGM(cgm);
    return 1;
  }

  if (n_fseek(cgm, 0L, 0)) {
    p_fclose(f);
    DeleteCGM(cgm);
    Warning("fseek failed for CGM, cannot open ", file);
    return 1;
  }

  if (currentCGM) p_fclose(currentCGM->file);
  if (catalogCGM && catalogCGM!=currentCGM) p_fclose(catalogCGM->file);
  while (firstCGM) firstCGM= DeleteCGM(firstCGM);
  currentCGM= firstCGM= catalogCGM= cgm;

  /* Read metafile descriptor, setting page defaults */
  for (;;) {
    nOctets= ReadElement(&klass, &id);
    if (nOctets<0) break;
    if (klass==0) {
      if (id==2 || id==3) break;
      else DoClass0(id, nOctets);
    } else if (klass==1) DoClass1(id, nOctets);
    else if (klass==6) DoClass6(id, nOctets);
    else if (klass==7) DoClass7(id, nOctets);
  }

  currentPage= 1;
  return 0;
}

int ReadCGM(int *mPage, int *nPage, int *sPage, int nPageGroups)
{
  int m0=0, m=1, n, s= 1;
  int echo_page_return= 0;

  if (!currentCGM) return 1;

  while (nPageGroups-- > 0) {
    m0= *mPage++;
    n= *nPage++;
    s= *sPage++;
    if ((m0<n && s<=0) || (m0>n && s>=0) || (s==0 && m0!=n)) {
      Warning("illegal page group, showing only first frame", "");
      n= m0;
      s= 1;
    } else if (s==0) {
      s= 1;
    }

    for (m=m0 ; s>0? m<=n : m>=n ; m+= s) {
      echo_page_return= EchoPage(m, m==m0? s : 0);
      if (echo_page_return) break;
    }
  }

  if (m==m0) {
    /* Last page group was empty, display first or last page */
    int offset;
    if (s>0) {
      while (currentCGM->next) currentCGM= currentCGM->next;
      while (currentCGM->nPages==0 && currentCGM->prev)
        currentCGM= currentCGM->prev;
      offset= currentCGM->nPages-1;
    } else {
      while (currentCGM->prev) currentCGM= currentCGM->prev;
      while (currentCGM->nPages==0 && currentCGM->next)
        currentCGM= currentCGM->next;
      offset= 0;
    }
    if (currentCGM->nPages>0) {
      if (echo_page_return!=2) EchoPage(currentCGM->firstPage+offset, 1);
    } else {
      Warning("no pictures in CGM file(s) ", currentCGM->name);
    }
  }

  return 0;
}

int CGMRelative(int offset)
{
  return currentPage+offset;
}

void CGMinfo(void)
{
  if (currentCGM) {
    CGM *last= currentCGM;
    char msg[80];
    while (last->next) last= last->next;
    sprintf(msg, "At page %d out of %s%d in CGM file ",
            currentPage, catalogCGM? ">=":"",
            last->firstPage+last->nPages-1);
    p_stdout(msg);
    p_stdout(firstCGM->name);  /* name might be very long */
    p_stdout("\n");
  } else {
    p_stdout("(No CGM input file, use open command)\n");
  }
}

int CGM_pages(void)
{
  int num_pgs= 0;
  if (currentCGM) {
    CGM *last= currentCGM;
    while (last->next) last= last->next;
    num_pgs= last->firstPage+last->nPages-1;
  }
  return num_pgs;
}

static unsigned long
n_fread(void *ptr, unsigned long nbytes, CGM *cgm)
{
  if (!cgm->ncar && nbytes>0) {
    long valid = cgm->valid;
    long n = cgm->pos & (N_BUFSIZE-1);
    unsigned char *buf = cgm->buffer + n;
    unsigned char *out = ptr;
    long nread = 0;
    for (;;) {
      if (!valid) {
        valid = p_fread(cgm->file, cgm->buffer, N_BUFSIZE);
        if (valid < 0) return -1L;
        cgm->valid = valid;
        if (!valid) return nread;
      }
      n = valid - n;
      if (n > 0) {
        if ((unsigned long)n >= nbytes) {
          memcpy(out, buf, nbytes);
          cgm->pos += nbytes;
          if ((unsigned long)n == nbytes) cgm->valid = 0;
          return nread+nbytes;
        }
        memcpy(out, buf, n);
        out += n;
        nread += n;
        nbytes -= n;
        cgm->pos += n;
      }
      buf = cgm->buffer;
      cgm->valid = valid = 0;
      n = 0;
    }
  } else {
    return n_fskip(ptr, nbytes, cgm);
  }           
}

static int
n_fseek(CGM *cgm, long offset, int relative)
{
  long n = cgm->pos & (N_BUFSIZE-1);
  long pos0 = cgm->pos-n;
  long rel;

  if (relative) {
    if (cgm->ncar)
      /* this is from SkipParameters -- no choice but to read it all */
      return (n_fskip((void *)0, offset, cgm)==offset)? 0 : -1;
    offset += cgm->pos;
  }

  if (cgm->ncar) {
    /* offset either 0 or something returned by n_ftell */
    if (!offset) offset = 4;      /* offset==0 actually means offset==4 */
    rel = (offset-4) - ((offset-4)%NCAR_RECORD_SIZE);
    if (rel != cgm->ncar_rec) {
      long block = rel - (rel & (N_BUFSIZE-1));
      if (block!=pos0 || !cgm->valid) {
        long valid;
        cgm->valid = 0;
        cgm->pos = rel+4;
        if (p_fseek(cgm->file, block)) return -1;
        valid = p_fread(cgm->file, cgm->buffer, N_BUFSIZE);
        if (valid < 0) return -1;
        cgm->valid = valid;
        pos0 = block;
      }
      /* ncar record 4-byte header contains 2-byte record length
       * followed by 1 byte record type tag */
      cgm->ncar_rec = rel;
      if (rel+4-block <= cgm->valid)
        cgm->ncar_len = (((unsigned long)cgm->buffer[rel-block])<<8) |
          cgm->buffer[rel-block+1] |
          (((unsigned long)cgm->buffer[rel-block+2])<<16);
      else
        cgm->ncar_len = 0;
    }
  }

  rel = offset - pos0;
  if (rel >= 0) {
    if (rel < cgm->valid) {
      cgm->pos = pos0 + rel;
      if (cgm->valid) return 0;
      /* may need to reset eof indicator by calling p_fseek */
    }
  } else {
    if (offset < 0) return -1;
  }
  cgm->valid = 0;
  cgm->pos = offset;
  n = offset & (N_BUFSIZE-1);
  return p_fseek(cgm->file, offset-n);
}

static long
n_ftell(CGM *cgm)
{
  return cgm->pos;
}

static long
n_fskip(void *ptr, long nbytes, CGM *cgm)
{
  if (nbytes>0) {
    long valid = cgm->valid;
    long n = cgm->pos & (N_BUFSIZE-1);
    unsigned char *buf = cgm->buffer + n;
    unsigned char *out = ptr;
    long nread = 0;
    long rec_max = 0;
    for (;;) {
      if (!valid) {
        valid = p_fread(cgm->file, cgm->buffer, N_BUFSIZE);
        if (valid < 0) return -1L;
        cgm->valid = valid;
        if (!valid) return nread;
      }
      /* 3rd byte of ncar_len is tag, 0x3- means CGM record */
      if ((cgm->ncar_len&0xf00000)==0x300000) {
        rec_max = (cgm->ncar_rec+4+(cgm->ncar_len&0xffff)) - (cgm->pos-n);
        if (valid < rec_max) n = valid - n;
        else n = rec_max - n;
        if (n > 0) {
          if (n >= nbytes) {
            if (out) memcpy(out, buf, nbytes);
            cgm->pos += nbytes;
            if (valid<=rec_max && n==nbytes) cgm->valid = 0;
            return nread+nbytes;
          }
          if (out) memcpy(out, buf, n), out+=n;
          nread += n;
          nbytes -= n;
          cgm->pos += n;
        }
        if (valid <= rec_max) cgm->valid = valid = 0;
      }
      if (valid) {
        if (n_fseek(cgm, cgm->ncar_rec+NCAR_RECORD_SIZE+4, 0))
          return -1L;
        valid = cgm->valid;
        n = cgm->pos & (N_BUFSIZE-1);
        buf = cgm->buffer + n;
      } else {
        n = 0;
        buf = cgm->buffer;
      }
    }
  }
  return nbytes;
}

/* ------------------------------------------------------------------------ */

static int EchoPage(int number, int inc)
{
  CGM *cgm= currentCGM;
  int klass, id;
  long nOctets;
  int value= 0;

  if (!cgm) return 1;

  while (number<cgm->firstPage && cgm->prev) cgm= cgm->prev;
  while (number>=cgm->firstPage+cgm->nPages && cgm->next) cgm= cgm->next;

  if (number>=cgm->firstPage+cgm->nPages) {
    if (cgm->catalogEnd>0) {
      if (!amBatch) {
        p_stdout("gist: STANDBY -- still cataloging ");
        p_stdout(cgm->name);
        p_stdout("\n");
      }
      while (CatalogCGM()) {
        if (cgm->catalogEnd<0) cgm= cgm->next; /* cgm->next must be non-0! */
        if (number<cgm->firstPage+cgm->nPages) break;
      }
    }
    if (number>=cgm->firstPage+cgm->nPages) {
      if (inc<0) return 0;
      else if (inc==0) return 1;
      number= cgm->firstPage+cgm->nPages-1;
      while (cgm->nPages==0 && cgm->prev) cgm= cgm->prev;
      if (cgm->nPages==0) return 1;
      value= 2;  /* distinguish cases where we still draw something */
    }
  } else if (number<cgm->firstPage) {
    if (inc>0) return 0;
    else if (inc==0) return 1;
    number= cgm->firstPage;
    while (cgm->nPages==0 && cgm->next) cgm= cgm->next;
    if (cgm->nPages==0) return 1;
    value= 2;  /* distinguish cases where we still draw something */
  }

  if (cgm!=currentCGM) {
    if (cgm!=catalogCGM) {
      cgm->file= p_fopen(cgm->name, "rb");
      if (!cgm->file) {
        Warning("no longer able to open CGM ", cgm->name);
        return 1;
      }
    }
    if (currentCGM!=catalogCGM) {
      p_fclose(currentCGM->file);
      currentCGM->file= 0;
    }
    currentCGM= cgm;
  }

  currentPage= number;

  if (n_fseek(cgm, cgm->pageAddress[number-cgm->firstPage], 0)) {
    Warning("fseek failed to reach specified page in ", cgm->name);
    return 1;
  }

  gistError[0]= '\0';

  for (;;) {
    nOctets= ReadElement(&klass, &id);
    if (nOctets<0) break;
    if (klass==0) {
      if (id==5) { DoClass0(id, nOctets); break; }
      if (id==0 || id==3 || id==4) DoClass0(id, nOctets);
      else {
        Warning("misplaced delimiter element in CGM ", cgm->name);
        break;
      }
    } else if (klass==1) {
      Warning("ignoring misplaced class 1 element in CGM ", cgm->name);
    } else if (klass==2) DoClass2(id, nOctets);
    else if (klass==3) DoClass3(id, nOctets);
    else if (klass==4) DoClass4(id, nOctets);
    else if (klass==5) DoClass5(id, nOctets);
    else if (klass==6) DoClass6(id, nOctets);
    else if (klass==7) DoClass7(id, nOctets);
    else {
      Warning("ignoring element of unknown class in CGM ", cgm->name);
    }
  }
  GpFlush(0);

  if (gistError[0]) Warning("(WARNING) ", gistError);

  if (currentCGM==catalogCGM &&
      currentPage==catalogCGM->firstPage+catalogCGM->nPages-1) {
    catalogCGM->catalogEnd= n_ftell(catalogCGM);
  }

  return value;
}

/* ------------------------------------------------------------------------ */
/* Allow for architectures in which a short does not have the same
   size or order as presumed in the CGM binary encoding standard
   (a short presumed to be 2 bytes in big-endian order, i.e.- MSB first).  */

#ifdef HAS_CGM_ORDER
#define CGM_WORDS(octets, nwords) ((short *)octets)

#else
#define CGM_WORDS(octets, nwords) \
  (not_cgm_order?Deorder(octets, nwords):(short *)octets)
static int not_cgm_order = -1;
static short *Deorder(Octet *octets, long nwords);
static short *Deorder(Octet *octets, long nwords)
{
  short *words = (short *)octets;    /* reordering is done in place */
  short mask = (short)(-1 ^ 0xffff); /* mask for sign extension */
  long n = nwords;
  if (not_cgm_order < 0) {
    short cgm_order = 1;
    unsigned char *cgm_test = (unsigned char *)(&cgm_order);
    not_cgm_order = cgm_test[0]!=0;
    if (!not_cgm_order) return words;
  }
  words += n;
  octets += n<<1;
  while (n--) {
    octets -= 2;
    *(--words) = octets[0]<<8 | octets[1];
  }
  if (mask) {
    long i;
    for (i=0 ; i<nwords ; i++) if (words[i] & 0x8000) words[i] |= mask;
  }
  return words;
}
#endif

/* ------------------------------------------------------------------------ */

static Octet *paramList= 0;
static long maxParams= 0;
static long blockSize= 16384;  /* can hold any Gist-written data */

static char fontName[256];

static long ReadParameters(long nOctets, GpColor *colors, long maxColors)
{
  Octet *oldParams, cmd[2];
  long newSize, i;
  long n, nTot= 0;
  int more= nOctets & 0x8000;
  Octet *newParams= colors? colors : paramList;

  for (;;) {
    nOctets&= 0x7fff;
    n= nOctets;
    if (n&1) n++;

    if (!colors) {
      if (nTot+n > maxParams) {
        oldParams= paramList;
        newSize= (1+(nTot+n-1)/blockSize)*blockSize;
        paramList= (Octet *)p_malloc((sizeof(short)/2)*newSize);
        if (!paramList) {
          Warning("memory manager failed in CGM read", "");
          return -2L;
        }
        maxParams= newSize;
        for (i=0 ; i<nTot ; i++) paramList[i]= oldParams[i];
        if (oldParams) p_free(oldParams);
      }
      newParams= paramList;
    } else {
      if (nTot+n > maxColors) {
        SkipParameters(nOctets);
        return nTot;
      }
    }

    if (nOctets>0) {
      if (cmdString) {
        if (n>cmdLength) { n= cmdLength;  more= 0; }
        memcpy(paramList+nTot, currentCmd, n);
        currentCmd+= n;
        cmdLength-= n;
      } else {
        if (n_fread(newParams+nTot, n, currentCGM)!=(unsigned long)n) {
          Warning("fread failed on CGM file ", currentCGM->name);
          return -1L;
        }
      }
      nTot+= nOctets;  /* yes, may be 1 less than n */
    }

    if (!more) break;

    if (cmdString) {
      if (2>cmdLength) break;
      cmd[0]= *currentCmd++;
      cmd[1]= *currentCmd++;
      cmdLength-= 2;
    } else {
      if (n_fread(cmd, 2L, currentCGM)!=2L) {
        Warning("fread failed on CGM file ", currentCGM->name);
        return -1L;
      }
    }
    nOctets= cmd[0]<<8 | cmd[1];
    more= nOctets & 0x8000;
  }

  return nTot;
}

static int SkipParameters(long nOctets)
{
  Octet cmd[2];
  int more= nOctets & 0x8000;

  for (;;) {
    nOctets&= 0x7fff;
    if (nOctets&1) nOctets++;

    if (nOctets>0) {
      if (cmdString) {
        if (nOctets>cmdLength) { nOctets= cmdLength;  more= 0; }
        currentCmd+= nOctets;
        cmdLength-= nOctets;
      } else {
        if (n_fseek(currentCGM, nOctets, 1)) {
          Warning("fseek failed on CGM file ", currentCGM->name);
          return 1;
        }
      }
    }

    if (!more) break;

    if (cmdString) {
      if (2>cmdLength) break;
      cmd[0]= *currentCmd++;
      cmd[1]= *currentCmd++;
      cmdLength-= 2;
    } else {
      if (n_fread(cmd, 2L, currentCGM)!=2L) {
        Warning("fread failed (partition) on CGM file ", currentCGM->name);
        return 1;
      }
    }
    nOctets= cmd[0]<<8 | cmd[1];
    more= nOctets & 0x8000;
  }

  return 0;
}

static long ReadElement(int *klass, int *id)
{
  Octet cmd[2];
  unsigned int n;

  if (cmdString) {
    if (2>cmdLength) { *klass= *id= 0;   cmdLength= 0;  return 0; }
    cmd[0]= *currentCmd++;
    cmd[1]= *currentCmd++;
    cmdLength-= 2;
  } else {
    if (n_fread(cmd, 2L, currentCGM)!=2L) {
      Warning("fread failed (command) on CGM file ", currentCGM->name);
      return -1L;
    }
  }
  cgmCmdLen= 2;

  *klass= cmd[0]>>4;
  *id= (cmd[0]<<3 | cmd[1]>>5) & 0x7f;
  n= cmd[1] & 0x1f;

  if (n==0x1f) {
    if (cmdString) {
      if (2>cmdLength) { *klass= *id= 0;   cmdLength= 0;  return 0; }
      cmd[0]= *currentCmd++;
      cmd[1]= *currentCmd++;
      cmdLength-= 2;
    } else {
      if (n_fread(cmd, 2L, currentCGM)!=2L) {
        Warning("fread failed (command) on CGM file ", currentCGM->name);
        return -1L;
      }
    }
    n= cmd[0]<<8 | cmd[1];
    cgmCmdLen= 4;
  }

  return (long)n;
}

static long DePascalify(char *text, Octet *pasctxt, long nMax)
{
  long nTot= nMax>0? 1 : 0;
  long nChars= nMax>0? *pasctxt++ : 0;
  int more= 0;

  if (nMax>0) nMax--;

  if (nChars==0xff) {
    if (nMax<2) {
      text[0]= '\0';
      return nTot+nMax;  /* i.e.- original nMax */
    }
    more= pasctxt[0]&0x80;
    nChars= (pasctxt[0]&0x7f)<<8 | pasctxt[1];
    pasctxt+= 2;
    nTot+= 2;
    nMax-= 2;
  }

  if (nChars>nMax) {
    nChars= nMax;
    more= 0;
  }

  if (nChars>0) memcpy(text, pasctxt, nChars);
  text[nChars]= '\0';
  pasctxt+= nChars;
  nTot+= nChars;
  nMax-= nChars;

  while (more) {
    if (nMax<2) return nTot+nMax;
    more= pasctxt[0]&0x80;
    nChars= (pasctxt[0]&0x7f)<<8 | pasctxt[1];
    pasctxt+= 2;
    nTot+= 2;
    nMax-= 2;
    if (nChars>nMax) {
      nChars= nMax;
      more= 0;
    }
    pasctxt+= nChars;
    nTot+= nChars;
    nMax-= nChars;
  }

  return nTot;
}

static int Snarf16(Octet *param)
{
  int value= param[0]<<8 | param[1];
  return value;
}

static long SnarfInteger(Octet *param)
{
  long value= 0;
  int nBytes= nBytesInt;
  while (nBytes-- > 0) value= (value<<8) | *param++;
  return value;
}

static GpReal SnarfReal(Octet *param)
{
  long ipart= param[0]<<8 | param[1];
  long fpart= param[2]<<8 | param[3];
  GpReal value= (GpReal)fpart/65536.0;
  if (ipart & 0x8000) ipart= (-1L ^ 0xffff) | ipart;
  return value + (GpReal)ipart;
}

static char cgmText[256];
static int cgmTextLen= 0;
static GpReal cgmX0, cgmY0;

static void RememberPoint(GpReal x0, GpReal y0)
{
  cgmX0= x0;
  cgmY0= y0;
}

static char *SnarfMoreText(Octet *param)
{
  cgmTextLen+= DePascalify(cgmText+cgmTextLen, param, 256-cgmTextLen)-1;
  return cgmText;
}

static char *SnarfText(Octet *param)
{
  cgmTextLen= 0;
  return SnarfMoreText(param);
}

/* ARGSUSED */
static char *PathifyText(char *text, int path)
{
  if (path) {
    long len= strlen(cgmText);
    int i;
    if (path!=1 && len>128) {
      len= 128;
      cgmText[len]= '\0';
    }
    if (path!=3) {
      char tmp;
      for (i=0 ; i<len-1-i ; i++) {
        tmp= cgmText[i];
        cgmText[i]= cgmText[len-1-i];
        cgmText[len-1-i]= tmp;
      }
    }
    if (path!=1) {
      cgmText[2*len-1]= '\0';
      for (i=len-1 ; i>0 ; i--) {
        cgmText[2*i]= cgmText[i];
        cgmText[2*i-1]= '\n';
      }
    }
  }
  return cgmText;
}

static GpReal SnarfSA(Octet *param, int md, GpReal dflt)
{
  if (md==SCALED) {
    return SnarfReal(param);
  } else { /* md==ABSOLUTE */
    return ((GpReal)Snarf16(param))*cgmScale/dflt;
  }
}

static int SnarfColor(Octet *param, long nOctets)
{
  int color;
  if (!cselIllegible && nOctets==1) {
    /* always takes this branch if isGistCGM */
    color= param[0];
  } else if (cselIllegible==16 && nOctets==2) {
    color= param[1];  /* could use Snarf16, but top byte meaningless */
  } else {
    color= FG_COLOR;
  }
  if (color!=FG_COLOR) {
    if (isGistCGM) {
      if (color<10) color= (-1-color)&0xff;    /* standard colors */
      else { color-= 10; needPalette= 1; }     /* palette colors */
    } else {
      if (bg0fg1 && color==0) color= BG_COLOR;
      else if (bg0fg1 && color==1) color= FG_COLOR;
      else { if (bg0fg1) color-= 2; needPalette= 1; }
    }
  }
  return color;
}

static void ConvertPoints(long n, short *vdc, GpReal *ndcx, GpReal *ndcy)
{
  long i;
  for (i=0 ; i<n ; i++) {
    ndcx[i]= cgmScaleX*((GpReal)vdc[2*i])+cgmOffsetX;
    ndcy[i]= cgmScaleY*((GpReal)vdc[2*i+1])+cgmOffsetY;
  }
}

static GpReal *ndcX= 0, *ndcY= 0;

static int CheckScratch(long nPoints)
{
  if (nPoints > (ndcY-ndcX)) {
    GpReal *sav= ndcX;
    nPoints= (1+(nPoints-1)/1024)*1024;
    ndcX= (GpReal *)p_malloc(sizeof(GpReal)*2*nPoints);
    if (!ndcX) {
      ndcX= sav;
      return 1;
    }
    if (sav) p_free(sav);
    ndcY= ndcX+nPoints;
  }
  return nPoints<=0;   /* i.e.- success (0) if nPoints>0 */
}

static void SetVDC(int *vdc)
{
 /* Try to fit the VDC extent onto an 8.5 by 11 inch page
    as best we can... */
 long dx= (long)vdc[2]-(long)vdc[0];
 long dy= (long)vdc[3]-(long)vdc[1];
 int landscape= (dy>0?dy:-dy) < (dx>0?dx:-dx);
 long xOffset, yOffset;

 if (landscape!=cgmLandscape) {
   int i;
   cgmLandscape= landscape;
   for (i=0 ; i<8 ; i++)
     if (outEngines[i]) GpLandscape(outEngines[i], landscape);
 }

 if (pictDesc.scaling==ABSTRACT) {
   /* Gist produced files (at default scale) have 11->26400 and
      8.5->20400 exactly.  For either of these cases, we want to
      reproduce the default cgmScale factor of 25545.24 exactly.  */
   if (cgmScaleToFit) {
     long dv= (dx>0?dx:-dx)<(dy>0?dy:-dy)? dy : dx;
     cgmScale= (19200.0/(GpReal)dv) / 25545.24;
     xOffset= (long)((0.25*ONE_INCH)/cgmScale);
     yOffset= (long)((2.75*ONE_INCH)/cgmScale);
   } else if (cgmLandscape) { /* 11-by-8.5 paper */
     if (17*dx > 22*dy) {
       yOffset= (17*dx-22*dy)/44;
       xOffset= 0;
       cgmScale= (26400.0/(GpReal)dx) / 25545.24;
     } else {
       xOffset= (22*dy-17*dx)/34;
       yOffset= 0;
       cgmScale= (20400.0/(GpReal)dy) / 25545.24;
     }
   } else {                   /* 8.5-by-11 paper */
     if (17*dy > 22*dx) {
       xOffset= (17*dy-22*dx)/44;
       yOffset= 0;
       cgmScale= (26400.0/(GpReal)dy) / 25545.24;
     } else {
       yOffset= (22*dx-17*dy)/17;
       xOffset= 0;
       cgmScale= (20400.0/(GpReal)dx) / 25545.24;
     }
   }
 } else { /* pictDesc.scaling==METRIC */
   xOffset= yOffset= 0;
   cgmScale= pictDesc.metric*(ONE_INCH/25.4);
 }
 if (cgmScale<0.0) cgmScale= -cgmScale;

 cgmScaleX= dx>0? cgmScale : -cgmScale;
 cgmScaleY= dy>0? cgmScale : -cgmScale;
 cgmOffsetX= -cgmScaleX*(GpReal)(vdc[0]-xOffset);
 cgmOffsetY= -cgmScaleY*(GpReal)(vdc[1]-yOffset);
}

static GpTransform unitTrans= {{0.0, 2.0, 0.0, 2.0}, {0.0, 2.0, 0.0, 2.0}};

static void GSetClip(int on)
{
  if (on) {
    GpReal xbox[2], ybox[2];
    ConvertPoints(2L, pictControls.clipBox, xbox, ybox);
    if (xbox[0]<xbox[1]) {
      gistT.viewport.xmin= gistT.window.xmin= xbox[0];
      gistT.viewport.xmax= gistT.window.xmax= xbox[1];
    } else {
      gistT.viewport.xmin= gistT.window.xmin= xbox[1];
      gistT.viewport.xmax= gistT.window.xmax= xbox[0];
    }    
    if (ybox[0]<ybox[1]) {
      gistT.viewport.ymin= gistT.window.ymin= ybox[0];
      gistT.viewport.ymax= gistT.window.ymax= ybox[1];
    } else {
      gistT.viewport.ymin= gistT.window.ymin= ybox[1];
      gistT.viewport.ymax= gistT.window.ymax= ybox[0];
    }
    GpSetTrans(&gistT);
    gistClip= 1;
  } else {
    GpSetTrans(&unitTrans);
    gistClip= 0;
  }
}

static void ResetPalette(int forPage)
{
  int i;

  if (forPage) {
    int offset= isGistCGM? 10 : (bg0fg1? 2 : 0);
    int refreshPalette;
    for (i=0 ; i<cgmNColors ; i++) cgmPalette[i]= defaultPalette[i];
    cgmNColors= defaultNColors;
    /* Reset color mode on all engines, and
       set palette on any new engines */
    for (i=0 ; i<8 ; i++) {
      if (outEngines[i]) {
        refreshPalette= (cgmNColors>offset &&
                         (alteredPalette ||
                          outEngines[i]->palette!=cgmPalette+offset ||
                          outEngines[i]->nColors!=cgmNColors-offset));
        if (outTypes[i]==2) {
          setXPalette= refreshPalette;
        } else {
          outEngines[i]->colorMode= 0;
          if (refreshPalette) GpSetPalette(outEngines[i], cgmPalette+offset,
                                           cgmNColors-offset);
        }
      }
    }
    alteredPalette= 0;
    needPalette= 0;

  } else {
    int g;
    cgmPalette[0] = P_RGB(255, 255, 255);     /* background */
    cgmPalette[1] = P_RGB(0, 0, 0);           /* foreground */
    cgmPalette[2] = P_RGB(0, 0, 0);           /* black */
    cgmPalette[3] = P_RGB(255, 255, 255);     /* white */
    cgmPalette[4] = P_RGB(255, 0, 0);         /* red */
    cgmPalette[5] = P_RGB(0, 255, 0);         /* green */
    cgmPalette[6] = P_RGB(0, 0, 255);         /* blue */
    cgmPalette[7] = P_RGB(0, 255, 255);       /* cyan */
    cgmPalette[8] = P_RGB(255, 0, 255);       /* magenta */
    cgmPalette[9] = P_RGB(255, 255, 0);       /* yellow */
    GpPutGray(10, cgmPalette);
    for (i=0 ; i<230 ; i++) {
      g = i + ((i+5)/9);
      cgmPalette[i+10] = P_RGB(g,g,g);        /* 230 grays */
    }
    GpPutRGB(230, cgmPalette+10);
    cgmNColors= 240;
    for (i=0 ; i<cgmNColors ; i++) defaultPalette[i]= cgmPalette[i];
    defaultNColors= cgmNColors;
    cgmNColMax= 256;
    alteredPalette= 1;  /* only used in forPage branch above */
  }

  cgmColorChange= 0;
}

static int cgmSmoothing= 0;

/* ------------------------------------------------------------------------ */

static int DoClass0(int id, long nOctets)
{
  /* Delimiter elements, class 0 */
  switch (id) {
  case 0:   /* NO-OP */
    SkipParameters(nOctets);
    break;

  case 1:   /* BEGIN METAFILE */
    {
      nOctets= ReadParameters(nOctets, (void *)0, 0L);
      DePascalify(metafileName, paramList, nOctets);
      /* Reset default page specifications to CGM defaults */
      pageDesc= cgmDesc;
      pageControls= cgmControls;
      pageDefaults= cgmDefaults;
      isGistCGM= 0;
      cgmLandscape= 0;
      /* set default palette */
      ResetPalette(0);
      /* reset illegibility flags */
      nBytesInt= 2;
      vdcIllegible= realIllegible= indexIllegible=
        cvalIllegible= cndxIllegible= cselIllegible= 0;
    }
    break;

  case 2:   /* END METAFILE */
    SkipParameters(nOctets);
    break;

  case 3:   /* BEGIN PICTURE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    DePascalify(pictureName, paramList, nOctets);
    /* Reset default page specifications */
    pictDesc= pageDesc;
    pictControls= pageControls;
    gistA.l= pageDefaults.l;
    gistA.m= pageDefaults.m;
    gistA.f= pageDefaults.f;
    gistA.t= pageDefaults.t;
    nonGistA= pageDefaults.ng;
    cgmSmoothing= 0;
    ResetPalette(1);

    /* Clear any previous picture, or begin a new page */
    GpClear(0, CONDITIONALLY);
#ifdef NEED_PICTURE_HOOKS
    {
      extern void begin_picture_hook(void);
      begin_picture_hook();
    }
#endif
    break;

  case 4:   /* BEGIN PICTURE BODY */
    SkipParameters(nOctets);
    SetVDC(pictDesc.vdc);
    GSetClip(pictControls.clipOn);
    break;

  case 5:   /* END PICTURE */
    SkipParameters(nOctets);
#ifdef NEED_PICTURE_HOOKS
    {
      extern void end_picture_hook(void);
      end_picture_hook();
    }
#endif
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 0 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass1(int id, long nOctets)
{
  /* Metafile descriptor elements, class 1 */
  switch (id) {
  case 1:   /* METAFILE VERSION */
    SkipParameters(nOctets);
    break;

  case 2:   /* METAFILE DESCRIPTION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    DePascalify(metafileDescription, paramList, nOctets);
    if (strncmp(metafileDescription, "Gist;  ", 7)==0) isGistCGM= 1;
    else isGistCGM= 0;
    if (!amBatch) {
      p_stdout(currentCGM->name);
      p_stdout(" metafile description:\n");
      p_stdout(metafileDescription + (isGistCGM? 7 : 0));
      p_stdout("\n");
    }
    break;

  case 3:   /* VDC TYPE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=2 || paramList[0]!=0 || paramList[1]!=0) {
      Warning("CGM illegible, does not use integer VDC space", "");
      vdcIllegible= 1;
      return 1;
    }
    break;

  case 4:   /* INTEGER PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==nBytesInt) nBytesInt= SnarfInteger(paramList)>>3;
    else nBytesInt= -1;
    break;

  case 5:   /* REAL PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=3*nBytesInt || SnarfInteger(paramList)!=1 ||
        SnarfInteger(paramList+nBytesInt)!=16 ||
        SnarfInteger(paramList+2*nBytesInt)!=16) {
      Warning("CGM may be partly illegible, not 32 bit fixed point reals", "");
      realIllegible= 1;
      return 1;
    }
    break;

  case 6:   /* INDEX PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=nBytesInt || SnarfInteger(paramList)!=16) {
      Warning("CGM may be partly illegible, not 16 bit index precision", "");
      indexIllegible= 1;
      return 1;
    }
    break;

  case 7:   /* COLOR PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=nBytesInt ||
        ((cvalIllegible= SnarfInteger(paramList))!=8 && cvalIllegible!=16)) {
      Warning("CGM may be partly illegible, not 8 or 16 bit color precision",
              "");
      cvalIllegible= 1;
      return 1;
    } else {
      /* 8 is the default value, 16 grudgingly supported to allow ATC GKS
         produced files */
      if (cvalIllegible==8) cvalIllegible= 0;
    }
    break;

  case 8:   /* COLOR INDEX PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    { int cip= 8;
      if (nOctets!=nBytesInt ||
          (cip= SnarfInteger(paramList))!=8) {
        Warning("CGM may be partly illegible, not 8 bit color index precision",
                "");
        cndxIllegible= cselIllegible= (cip==16? 16 : 1);
        return 1;
      }
    }
    break;

  case 9:   /* MAXIMUM COLOR INDEX */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==1 && !cndxIllegible) {
      pageDesc.colorMaxI= paramList[0];
      cgmNColMax= pageDesc.colorMaxI+1;
    }
    break;

  case 10:  /* COLOR VALUE EXTENT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==6 && !cvalIllegible) {
      pageDesc.colorMaxV[0]= paramList[0];
      pageDesc.colorMaxV[1]= paramList[1];
      pageDesc.colorMaxV[2]= paramList[2];
      pageDesc.colorMaxV[3]= paramList[3];
      pageDesc.colorMaxV[4]= paramList[4];
      pageDesc.colorMaxV[5]= paramList[5];
    } else if (nOctets==12 && cvalIllegible==16) {
      pageDesc.colorMaxV[0]= Snarf16(paramList);
      pageDesc.colorMaxV[1]= Snarf16(paramList+2);
      pageDesc.colorMaxV[2]= Snarf16(paramList+4);
      pageDesc.colorMaxV[3]= Snarf16(paramList+6);
      pageDesc.colorMaxV[4]= Snarf16(paramList+8);
      pageDesc.colorMaxV[5]= Snarf16(paramList+10);
    }
    break;

  case 11:  /* METAFILE ELEMENT LIST */
    SkipParameters(nOctets);
    break;

  case 12:  /* METAFILE DEFAULTS REPLACEMENT */
    {
      nOctets= ReadParameters(nOctets, (void *)0, 0L);
      if (nOctets>0) {
        int klass, id;
        cmdString= (Octet *)p_malloc(sizeof(Octet)*nOctets);
        if (!cmdString) {
          Warning("memory manager failed in DoClass1(DEFAULTS)", "");
          return 1;
        }
        memcpy(cmdString, paramList, nOctets);
        currentCmd= cmdString;
        cmdLength= nOctets;
        while (cmdLength>0) {
          nOctets= ReadElement(&klass, &id);
          if (nOctets>=0) {
            switch (klass) {
            case 2:
              pictDesc= pageDesc;
              DoClass2(id, nOctets);
              pageDesc= pictDesc;
              break;
            case 3:
              pictControls= pageControls;
              DoClass3(id, nOctets);
              pageControls= pictControls;
              break;
            case 5:
              gistA.l= pageDefaults.l;
              gistA.m= pageDefaults.m;
              gistA.f= pageDefaults.f;
              gistA.t= pageDefaults.t;
              nonGistA= pageDefaults.ng;
              DoClass5(id, nOctets);
              pageDefaults.l= gistA.l;
              pageDefaults.m= gistA.m;
              pageDefaults.f= gistA.f;
              pageDefaults.t= gistA.t;
              pageDefaults.ng= nonGistA;
              break;
            case 6:
              DoClass6(id, nOctets);
              break;
            default:
              /* Other commands are illegal, but whining is pointless */
              break;
            }
          }
        }
        p_free(cmdString);
        cmdString= 0;
      }
    }
    break;

  case 13:  /* FONT LIST */
    {
      long nPart;
      Octet *pasctxt= paramList;
      int i, *oldFonts;
      nOctets= ReadParameters(nOctets, (void *)0, 0L);
      pageDesc.nfonts= 0;
      do {
        nPart= DePascalify(fontName, pasctxt, nOctets);
        pasctxt+= nPart;
        nOctets-= nPart;
        for (i=0 ; i<N_GIST_FONTS ; i++)
          if (strcmp(fontName, gistFonts[i])==0) break;
        if (i>=N_GIST_FONTS) i= 0;
        if (pageDesc.nfonts>=pageDesc.maxFonts) {
          oldFonts= pageDesc.fonts;
          pageDesc.fonts= (int *)p_malloc(sizeof(int)*(pageDesc.nfonts+20));
          if (!pageDesc.fonts) {
            pageDesc.fonts= oldFonts;
            Warning("memory manager failed in DoClass1(FONT LIST)", "");
            return 2;
          }
          pageDesc.maxFonts= pageDesc.nfonts+20;
          for (i=0 ; i<pageDesc.nfonts ; i++) pageDesc.fonts[i]= oldFonts[i];
          if (oldFonts && oldFonts!=defaultNumbers) p_free(oldFonts);
        }
        pageDesc.fonts[pageDesc.nfonts++]= i;
      } while (nOctets>0);
    }
    break;

  case 14:  /* CHARACTER SET LIST */
    SkipParameters(nOctets);
    break;

  case 15:  /* CHARACTER CODING ANNOUNCER */
    SkipParameters(nOctets);
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 1 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass2(int id, long nOctets)
{
  /* Picture descriptor elements, class 2 */
  switch (id) {
  case 1:   /* SCALING MODE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets>2 && paramList[1]) pictDesc.scaling= METRIC;
    else pictDesc.scaling= ABSTRACT;
    if (!realIllegible && nOctets==6) {
      if (pictDesc.scaling==METRIC) pictDesc.metric= SnarfReal(paramList+2);
      else pictDesc.metric= DEFAULT_METRIC;
    } else if (pictDesc.scaling==METRIC) {
      pictDesc.metric= DEFAULT_METRIC;
    }
    break;

  case 2:   /* COLOR SELECTION MODE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=2 || paramList[1]) {
      Warning("CGM may be partly illegible, not indexed color mode", "");
      cselIllegible= 1;
      return 1;
    } else cselIllegible= cndxIllegible;
    break;

  case 3:   /* LINE WIDTH SPECIFICATION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=2 || paramList[1]) pictDesc.lwidth= SCALED;
    else pictDesc.lwidth= ABSOLUTE;
    break;

  case 4:   /* MARKER SIZE SPECIFICATION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=2 || paramList[1]) pictDesc.msize= SCALED;
    else pictDesc.msize= ABSOLUTE;
    break;

  case 5:   /* EDGE WIDTH SPECIFICATION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=2 || paramList[1]) pictDesc.ewidth= SCALED;
    else pictDesc.ewidth= ABSOLUTE;
    break;

  case 6:   /* VDC EXTENT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets==8) {
      short *vdc= CGM_WORDS(paramList, 4L);
      pictDesc.vdc[0]= vdc[0];
      pictDesc.vdc[1]= vdc[1];
      pictDesc.vdc[2]= vdc[2];
      pictDesc.vdc[3]= vdc[3];
      SetVDC(pictDesc.vdc);
      if (pictControls.clipDefault) {
        pictControls.clipBox[0]= vdc[0];
        pictControls.clipBox[1]= vdc[1];
        pictControls.clipBox[2]= vdc[2];
        pictControls.clipBox[3]= vdc[3];
      }
    }
    break;

  case 7:   /* BACKGROUND COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    {
      int red, green, blue;
      if (!cvalIllegible && nOctets==3) {
        /* This is actually ignored... */
        red= paramList[0];
        green= paramList[1];
        blue= paramList[2];
        /* GpPutNTSC(1, &pictDesc.background); */
      } else if (cvalIllegible==16 && nOctets==6) {
        /* This is actually ignored... */
        red= Snarf16(paramList)>>8;
        green= Snarf16(paramList+2)>>8;
        blue= Snarf16(paramList+4)>>8;
        /* GpPutNTSC(1, &pictDesc.background); */
      } else {
        nOctets= 0;
        red= green= blue= 0;
      }
      if (nOctets) {
        if (pictDesc.background.red!=red ||
            pictDesc.background.green!=green ||
            pictDesc.background.blue!=blue) {
          char msg[80];
          /* One warning each time the bozo tries to change it... */
          sprintf(msg,
                  "WARNING- BACKGROUND COLOR %02x%02x%02x not supported.\n",
                  pictDesc.background.red, pictDesc.background.green,
                  pictDesc.background.blue);
          p_stdout(msg);
          p_stdout("   You may want to set \"Gist*background:\" X resource.\n");
        }
        pictDesc.background.red= red;
        pictDesc.background.green= green;
        pictDesc.background.blue= blue;
      }
    }
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 2 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass3(int id, long nOctets)
{
  /* Control elements, class 3 */
  switch (id) {
  case 1:   /* VDC INTEGER PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets!=nBytesInt || SnarfInteger(paramList)!=16) {
      Warning("CGM illegible, not 16 bit integer VDC coordinates", "");
      vdcIllegible= 1;
      return 1;
    } else vdcIllegible= 0;
    break;

  case 2:   /* VDC REAL PRECISION */
    SkipParameters(nOctets);
    break;

  case 3:   /* AUXILLIARY COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!cselIllegible && nOctets==1) pictControls.auxColor= paramList[0];
    break;

  case 4:   /* TRANSPARENCY */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==2) {
      pictControls.transparency= paramList[1];
      gistA.t.opaque= !pictControls.transparency;
    }
    break;

  case 5:   /* CLIP RECTANGLE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets==8) {
      short *clipBox= CGM_WORDS(paramList, 4L);
      pictControls.clipBox[0]= clipBox[0];
      pictControls.clipBox[1]= clipBox[1];
      pictControls.clipBox[2]= clipBox[2];
      pictControls.clipBox[3]= clipBox[3];
      if (pictControls.clipOn) GSetClip(1);
      pictControls.clipDefault= 0;
    }
    break;

  case 6:   /* CLIP INDICATOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==2) {
      int clipOn= Snarf16(paramList);
      if (clipOn!=pictControls.clipOn) {
        pictControls.clipOn= clipOn;
        if (clipOn) GSetClip(1);
        else GSetClip(0);
      }
    }
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 3 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass4(int id, long nOctets)
{
  /* Graphical primitive elements, class 4 */

  if (cgmColorChange) {
    /* If a palette has been specified for this page, dump it to all
       non-X engines.  */
    int i, offset= isGistCGM? 10 : (bg0fg1? 2 : 0);
    for (i=0 ; i<8 ; i++) {
      if (outEngines[i] && outTypes[i]!=2) {
        outEngines[i]->colorMode= 1;
        GpSetPalette(outEngines[i], cgmPalette+offset, cgmNColors-offset);
      }
    }
    alteredPalette= 1;
    cgmColorChange= 0;
    setXPalette= 1;
  }
  if (setXPalette && (needPalette || id==9)) {
    /* X engines change palette only when necessary -- that is, if a
       non-standard color has been referenced, or this is a cell array.  */
    int i, offset= isGistCGM? 10 : (bg0fg1? 2 : 0);
    for (i=0 ; i<8 ; i++) {
      if (outEngines[i] && outTypes[i]==2) {
        outEngines[i]->colorMode= alteredPalette;
        GpSetPalette(outEngines[i], cgmPalette+offset, cgmNColors-offset);
      }
    }
    setXPalette= 0;
  }

  switch (id) {
  case 1:   /* POLYLINE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=4) {
      long nPoints= nOctets>>2;
      short *vdc= CGM_WORDS(paramList, nPoints<<1);
      int (*Lines)(long, const GpReal *, const GpReal *);

      if (cgmSmoothing) {
        long i;
        gistA.dl.closed= 0;
        gistA.dl.smooth= cgmSmoothing;
        gistA.dl.marks= gistA.dl.rays= 0;
        cgmSmoothing= 0;
        nPoints= nPoints/3;
        for (i=0 ; i<nPoints ; i++) {
          vdc[2*i]= vdc[6*i];
          vdc[2*i+1]= vdc[6*i+1];
        }
        Lines= &GaLines;
      } else {
        Lines= &GpLines;
      }

      if (!CheckScratch(nPoints)) {
        ConvertPoints(nPoints, vdc, ndcX, ndcY);
        Lines(nPoints, ndcX, ndcY);
      }
    }
    break;

  case 2:   /* DISJOINT POLYLINE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=8) {
      long nPoints= (nOctets>>2)&(~1L);
      short *vdc= CGM_WORDS(paramList, nPoints<<1);

      if (!CheckScratch(nPoints)) {
        long i, nSegs= nPoints>>1;
        for (i=0 ; i<nSegs ; i++) {
          ndcX[i]= cgmScaleX*(GpReal)vdc[4*i]+cgmOffsetX;
          ndcY[i]= cgmScaleY*(GpReal)vdc[4*i+1]+cgmOffsetY;
          ndcX[i+nSegs]= cgmScaleX*(GpReal)vdc[4*i+2]+cgmOffsetX;
          ndcY[i+nSegs]= cgmScaleY*(GpReal)vdc[4*i+3]+cgmOffsetY;
        }
        GpDisjoint(nSegs, ndcX, ndcY, ndcX+nSegs, ndcY+nSegs);
      }
    }
    break;

  case 3:   /* POLYMARKER */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=4) {
      long nPoints= nOctets>>2;
      short *vdc= CGM_WORDS(paramList, nPoints<<1);
      if (!CheckScratch(nPoints)) {
        ConvertPoints(nPoints, vdc, ndcX, ndcY);
        GpMarkers(nPoints, ndcX, ndcY);
      }
    }
    break;

  case 4:   /* TEXT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=7) {
      short ndc[2], final;
      GpReal x0, y0;
      char *text;
      ndc[0]= Snarf16(paramList);
      ndc[1]= Snarf16(paramList+2);
      ConvertPoints(1L, ndc, &x0, &y0);
      final= Snarf16(paramList+4);
      text= SnarfText(paramList+6);
      if (final) {
        GpText(x0, y0, PathifyText(text,nonGistA.path));
        cgmTextLen= 0;
      }
      else RememberPoint(x0, y0);
    }
    break;

  case 5:   /* RESTRICTED TEXT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=11) {
      short ndc[2], final;
      GpReal x0, y0;
      char *text;
      /* ignore box width, box height parameters */
      ndc[0]= Snarf16(paramList+4);
      ndc[1]= Snarf16(paramList+6);
      ConvertPoints(1L, ndc, &x0, &y0);
      final= Snarf16(paramList+8);
      text= SnarfText(paramList+10);
      if (final) {
        GpText(x0, y0, PathifyText(text,nonGistA.path));
        cgmTextLen= 0;
      }
      else RememberPoint(x0, y0);
    }
    break;

  case 6:   /* APPEND TEXT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=12) {
      short final;
      char *text;
      final= Snarf16(paramList+2);
      text= SnarfMoreText(paramList+2);
      if (final) GpText(cgmX0, cgmY0, PathifyText(text,nonGistA.path));
    }
    break;

  case 7:   /* POLYGON */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets>=4) {
      long nPoints= nOctets>>2;
      short *vdc= CGM_WORDS(paramList, nPoints<<1);
      if (!CheckScratch(nPoints)) {
        int type= gistA.e.type;
        if (!nonGistA.eVisible) gistA.e.type= L_NONE;
        ConvertPoints(nPoints, vdc, ndcX, ndcY);
        if (gistA.f.style!=F_EMPTY) {
          GpFill(nPoints, ndcX, ndcY);
        } else if (gistA.e.type!=L_NONE) {
          gistA.dl.closed= 1;
          gistA.dl.smooth= 0;
          gistA.dl.marks= gistA.dl.rays= 0;
          GaLines(nPoints, ndcX, ndcY);
          gistA.dl.closed= 0;
        }
        gistA.e.type= type;
      }
    }
    break;

  case 8:   /* POLYGON SET */
    SkipParameters(nOctets);
    break;

  case 9:   /* CELL ARRAY */
    if (!cselIllegible && !vdcIllegible &&
        (nOctets&0x7fff)>=14+3*nBytesInt && !(nBytesInt&1)) {
      long width, height;
      int colorPrec, mode;
      short pqr[6];

      nOctets-= ReadParameters(14L+3*nBytesInt, (void *)0, 0L);
      pqr[0]= Snarf16(paramList);
      pqr[1]= Snarf16(paramList+2);
      pqr[2]= Snarf16(paramList+4);
      pqr[3]= Snarf16(paramList+6);
      pqr[4]= Snarf16(paramList+8);
      pqr[5]= Snarf16(paramList+10);
      width= SnarfInteger(paramList+12);
      height= SnarfInteger(paramList+12+nBytesInt);
      colorPrec= SnarfInteger(paramList+12+2*nBytesInt);
      mode= Snarf16(paramList+12+3*nBytesInt);

      if (mode==1 && (colorPrec==0 || colorPrec==8) &&
          pqr[4]==pqr[2] && pqr[5]==pqr[1]) {
        long nColumns= width&1? width+1 : width;
        long nCells= nColumns*height;
        GpReal pqx[2], pqy[2];
        GpColor *colors= (GpColor *)p_malloc(sizeof(GpColor)*nCells);
        if (colors) {
          if (ReadParameters(nOctets, colors, nCells)==nCells) {
            if (isGistCGM) {
              long i;
              for (i=0 ; i<nCells ; i++) colors[i]-= 10;
            }  /* bg0fg1 ignored here */
            ConvertPoints(2L, pqr, pqx, pqy);
            GpCells(pqx[0], pqy[0], pqx[1], pqy[1], width, height,
                    nColumns, colors);
          }
          p_free(colors);
        } else {
          SkipParameters(nOctets);
          Warning("memory manager failed in DoClass4(CELL ARRAY)", "");
        }
      } else {
        SkipParameters(nOctets);
      }
    } else {
      SkipParameters(nOctets);
    }
    break;

  case 10:  /* GENERALIZED DRAWING PRIMITIVE */
    SkipParameters(nOctets);
    break;

  case 11:  /* RECTANGLE */
    SkipParameters(nOctets);
    break;

  case 12:  /* CIRCLE */
    SkipParameters(nOctets);
    break;

  case 13:  /* CIRCULAR ARC 3 POINT */
    SkipParameters(nOctets);
    break;

  case 14:  /* CIRCULAR ARC 3 POINT CLOSE */
    SkipParameters(nOctets);
    break;

  case 15:  /* CIRCULAR ARC CENTER */
    SkipParameters(nOctets);
    break;

  case 16:  /* CIRCULAR ARC CENTER CLOSE */
    SkipParameters(nOctets);
    break;

  case 17:  /* ELLIPSE */
    SkipParameters(nOctets);
    break;

  case 18:  /* ELLIPTICAL ARC */
    SkipParameters(nOctets);
    break;

  case 19:  /* ELLIPTICAL ARC CLOSE */
    SkipParameters(nOctets);
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 4 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass5(int id, long nOctets)
{
  /* Attribute elements, class 5 */
  switch (id) {
  case 1:   /* LINE BUNDLE INDEX */
    /* Treat line bundle index as identical to line type */

  case 2:   /* LINE TYPE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) gistA.l.type= Snarf16(paramList);
    if (gistA.l.type<=0 || gistA.l.type>5) {
      if (gistA.l.type<=0) gistA.l.type= 1-gistA.l.type;
      if (gistA.l.type>5) gistA.l.type= gistA.l.type%5 + 1;
    }
    break;

  case 3:   /* LINE WIDTH */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if ((pictDesc.lwidth==ABSOLUTE && !vdcIllegible && nOctets==2) ||
        (pictDesc.lwidth==SCALED && !realIllegible && nOctets==4))
      gistA.l.width= SnarfSA(paramList, pictDesc.lwidth,
                             DEFAULT_LINE_WIDTH);
    break;

  case 4:   /* LINE COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    gistA.l.color= SnarfColor(paramList, nOctets);
    break;

  case 5:   /* MARKER BUNDLE INDEX */
    /* Treat marker bundle index as identical to marker type */

  case 6:   /* MARKER TYPE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) gistA.m.type= Snarf16(paramList);
    if (gistA.m.type<=0 || gistA.m.type>5) {
      if (gistA.m.type<=0) gistA.m.type= 1-gistA.m.type;
      if (gistA.m.type>5) gistA.m.type= gistA.m.type%5 + 1;
    }
    break;

  case 7:   /* MARKER SIZE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if ((pictDesc.msize==ABSOLUTE && !vdcIllegible && nOctets==2) ||
        (pictDesc.msize==SCALED && !realIllegible && nOctets==4))
      gistA.m.size= SnarfSA(paramList, pictDesc.msize,
                            DEFAULT_MARKER_SIZE);
    break;

  case 8:   /* MARKER COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    gistA.m.color= SnarfColor(paramList, nOctets);
    break;

  case 9:   /* TEXT BUNDLE INDEX */
    /* Treat text bundle index as identical to text font index */

  case 10:  /* TEXT FONT INDEX */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) gistA.t.font= Snarf16(paramList);
    if (gistA.t.font==0) gistA.t.font= 1;
    gistA.t.font--;
    if (gistA.t.font<0 || gistA.t.font>=pageDesc.nfonts) {
      if (gistA.t.font<0) gistA.t.font= -gistA.t.font;
      if (gistA.t.font>=pageDesc.nfonts)
        gistA.t.font= gistA.t.font%pageDesc.nfonts;
    }
    gistA.t.font= pageDesc.fonts[gistA.t.font];
    break;

  case 11:  /* TEXT PRECISION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==2) nonGistA.prec= Snarf16(paramList);
    break;

  case 12:  /* CHARACTER EXPANSION FACTOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!realIllegible && nOctets==4)
      nonGistA.expand= SnarfReal(paramList);
    break;

  case 13:  /* CHARACTER SPACING */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!realIllegible && nOctets==4)
      nonGistA.spacing= SnarfReal(paramList);
    break;

  case 14:  /* TEXT COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    gistA.t.color= SnarfColor(paramList, nOctets);
    break;

  case 15:  /* CHARACTER HEIGHT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets==2) {
      /* Point size is somewhat greater than CGM "character height"--
         use the same factor of 0.8 used by the Gist CGMEngine to assure
         that, at least, Gist-produced text is rendered correctly.  */
      gistA.t.height= (1.0/0.8)*cgmScale*((GpReal)Snarf16(paramList));
    }
    break;

  case 16:  /* CHARACTER ORIENTATION */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets==8) {
      short vdc[2];
      vdc[0]= Snarf16(paramList);
      vdc[1]= Snarf16(paramList+2);
      ConvertPoints(1L, vdc, &nonGistA.upX, &nonGistA.upY);
      nonGistA.upX -= cgmOffsetX;
      nonGistA.upY -= cgmOffsetY;
      vdc[0]= Snarf16(paramList+4);
      vdc[1]= Snarf16(paramList+6);
      ConvertPoints(1L, vdc, &nonGistA.baseX, &nonGistA.baseY);
      nonGistA.baseX -= cgmOffsetX;
      nonGistA.baseY -= cgmOffsetY;
      if (nonGistA.baseX>nonGistA.baseY) {
        if (nonGistA.baseX>-nonGistA.baseY) gistA.t.orient= TX_RIGHT;
        else gistA.t.orient= TX_DOWN;
      } else {
        if (nonGistA.baseX>-nonGistA.baseY) gistA.t.orient= TX_UP;
        else gistA.t.orient= TX_LEFT;
      }
    }
    break;

  case 17:  /* TEXT PATH */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==2) nonGistA.path= Snarf16(paramList);
    break;

  case 18:  /* TEXT ALIGNMENT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets>4) {
      gistA.t.alignH= Snarf16(paramList);
      gistA.t.alignV= Snarf16(paramList+2);
      if (gistA.t.alignH<0 || gistA.t.alignH>3) gistA.t.alignH= 0;
      if (gistA.t.alignV<0 || gistA.t.alignV>5) gistA.t.alignV= 0;
    }
    break;

  case 19:  /* CHARACTER SET INDEX */
    SkipParameters(nOctets);
    break;

  case 20:  /* ALTERNATE CHARACTER SET INDEX */
    SkipParameters(nOctets);
    break;

  case 21:  /* FILL BUNDLE INDEX */
    /* Treat fill bundle index as identical to interior style */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) gistA.f.style= Snarf16(paramList);
    break;

  case 22:  /* INTERIOR STYLE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==2) gistA.f.style= Snarf16(paramList);
    break;

  case 23:  /* FILL COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    gistA.f.color= SnarfColor(paramList, nOctets);
    break;

  case 24:  /* HATCH INDEX */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) nonGistA.hindex= Snarf16(paramList);
    break;

  case 25:  /* PATTERN INDEX */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) nonGistA.pindex= Snarf16(paramList);
    break;

  case 26:  /* EDGE BUNDLE INDEX */
    /* Treat edge bundle index as identical to edge type */

  case 27:  /* EDGE TYPE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!indexIllegible && nOctets==2) gistA.e.type= Snarf16(paramList);
    break;

  case 28:  /* EDGE WIDTH */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if ((pictDesc.ewidth==ABSOLUTE && !vdcIllegible && nOctets==2) ||
        (pictDesc.ewidth==SCALED && !realIllegible && nOctets==4))
      gistA.e.width= SnarfSA(paramList, pictDesc.ewidth, DEFAULT_LINE_WIDTH);
    break;

  case 29:  /* EDGE COLOR */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    gistA.e.color= SnarfColor(paramList, nOctets);
    break;

  case 30:  /* EDGE VISIBILITY */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (nOctets==2) nonGistA.eVisible= Snarf16(paramList);
    break;

  case 31:  /* FILL REFERENCE POINT */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets==4) {
      short refp[2];
      refp[0]= Snarf16(paramList);
      refp[1]= Snarf16(paramList+2);
      /* ConvertPoints(1L, refp, &gistA.f.refpX, &gistA.f.refpY); */
    }
    break;

  case 32:  /* PATTERN TABLE */
    SkipParameters(nOctets);
    break;

  case 33:  /* PATTERN SIZE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if (!vdcIllegible && nOctets==8) {
      short hw[2];
      short hx= Snarf16(paramList);
      short hy= Snarf16(paramList+2);
      short wx= Snarf16(paramList+4);
      short wy= Snarf16(paramList+6);
      if (hx<0) hx= -hx;
      if (hy<0) hy= -hy;
      if (wx<0) wx= -wx;
      if (wy<0) wy= -wy;
      if (wx<hx) hw[0]= hx;
      else hw[0]= wx;
      if (hy<wy) hw[1]= wy;
      else hw[1]= hy;
      /* ConvertPoints(1L, hw, &gistA.f.sizeX, &gistA.f.sizeY); */
    }
    break;

  case 34:  /* COLOR TABLE */
    nOctets= ReadParameters(nOctets, (void *)0, 0L);
    if ((!cndxIllegible || cndxIllegible==16) &&
        ((!cvalIllegible && nOctets>=4) ||
         (cvalIllegible==16 && nOctets>=7))) {
      int i, begin= (cndxIllegible? paramList[1] : paramList[0]);
      int i0= (cndxIllegible? 2 : 1);
      int nColors= cvalIllegible? (nOctets-i0)/6 : (nOctets-i0)/3;
      unsigned long mins[3], deltas[3];
      int r, g, b;
      mins[0]= pictDesc.colorMaxV[0];
      mins[1]= pictDesc.colorMaxV[1];
      mins[2]= pictDesc.colorMaxV[2];
      deltas[0]= pictDesc.colorMaxV[3]-mins[0];
      deltas[1]= pictDesc.colorMaxV[4]-mins[1];
      deltas[2]= pictDesc.colorMaxV[5]-mins[2];
#undef COLOR_8
#define COLOR_8(i,n) (255L*((n)-mins[i]))/deltas[i]
#undef COLOR_16
#define COLOR_16(i,n) (255L*(((unsigned int)(n))-mins[i]))/deltas[i]
      if (begin+nColors>cgmNColMax) nColors= cgmNColMax-begin;
      for (i=0 ; i<nColors ; i++) {
        if (!cvalIllegible) {
          r = COLOR_8(0,paramList[3*i+i0]);
          g = COLOR_8(1,paramList[3*i+i0+1]);
          b = COLOR_8(2,paramList[3*i+i0+2]);
        } else {
          r = COLOR_16(0,Snarf16(paramList+6*i+i0));
          g = COLOR_16(1,Snarf16(paramList+6*i+i0+2));
          b = COLOR_16(2,Snarf16(paramList+6*i+i0+4));
        }
        cgmPalette[i+begin] = P_RGB(r, g, b);
      }
      if (nColors>0) {
        int offset= isGistCGM? 10 : (bg0fg1? 2 : 0);
        GpPutNTSC(nColors, cgmPalette+begin);
        if (!cgmColorChange || cgmNColors<begin+nColors)
          cgmNColors= begin+nColors;
        if (cgmNColors>offset) cgmColorChange= 1;
      }
    }
    break;

  case 35:  /* ASPECT SOURCE FLAGS */
    SkipParameters(nOctets);
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 5 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass6(int id, long nOctets)
{
  /* Escape elements, class 6 */
  switch (id) {
  case 1:   /* ESCAPE */
    SkipParameters(nOctets);
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 6 CGM element", "");
    break;
  }

  return 0;
}

static int DoClass7(int id, long nOctets)
{
  /* External elements, class 7 */
  switch (id) {
  case 1:   /* MESSAGE */
    SkipParameters(nOctets);
    break;

  case 2:   /* APPLICATION DATA */
    if (isGistCGM) {
      nOctets= ReadParameters(nOctets, (void *)0, 0L);
      if (nOctets==3) cgmSmoothing= Snarf16(paramList);
    } else {
      SkipParameters(nOctets);
    }
    break;

  default:
    SkipParameters(nOctets);
    Warning("non-standard class 7 CGM element", "");
    break;
  }

  return 0;
}

/* ------------------------------------------------------------------------ */
