/*
 * $Id: draw0.c,v 1.1 2005-09-18 22:04:29 dhmunro Exp $
 * Virtual functions for Drauing class.
 */
/* 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 "draw.h"
#include "gtext.h"
#include "play.h"
#include "pstdlib.h"

extern char *strcpy(char *, const char *);

extern double log10(double);
#define SAFELOG0 (-999.)
#define SAFELOG(x) ((x)>0? log10(x) : ((x)<0? log10(-(x)) : -999.))

extern void Gd_KillRing(void *elv);

static void KillElement(GdElement *el);
static void LinesKill(void *el);
static void DisjointKill(void *el);
static void TextKill(void *el);
static void CellsKill(void *el);
static void PolysKill(void *el);
static void MeshKill(void *el);
static void FilledKill(void *el);
static void VectorsKill(void *el);
static void KillConGrps(GeLines **grps, int ngrps);
static void ContoursKill(void *el);
static void SystemKill(void *el);
extern void Gd_KillMeshXY(void *vMeshEl);

static int LinesGet(void *el);
static int DisjointGet(void *el);
static int TextGet(void *el);
static int CellsGet(void *el);
static int PolysGet(void *el);
static int MeshGet(void *el);
static int FilledGet(void *el);
static int VectorsGet(void *el);
static int ContoursGet(void *el);
static int SystemGet(void *el);
extern void Gd_MeshXYGet(void *vMeshEl);

static int LinesSet(void *el, int xyzChanged);
extern void Gd_LinesSubSet(void *el);
static int DisjointSet(void *el, int xyzChanged);
static int TextSet(void *el, int xyzChanged);
static int CellsSet(void *el, int xyzChanged);
static int PolysSet(void *el, int xyzChanged);
static void MeshXYSet(void *vMeshEl, int xyzChanged);
static int MeshSet(void *el, int xyzChanged);
static int FilledSet(void *el, int xyzChanged);
static int VectorsSet(void *el, int xyzChanged);
static int ContoursSet(void *el, int xyzChanged);
static int SystemSet(void *el, int xyzChanged);

static int LinesDraw(void *el, int xIsLog, int yIsLog);
static int DisjointDraw(void *el, int xIsLog, int yIsLog);
static int TextDraw(void *el, int xIsLog, int yIsLog);
static int CellsDraw(void *el, int xIsLog, int yIsLog);
static int PolysDraw(void *el, int xIsLog, int yIsLog);
static void MeshDrawSet(GaQuadMesh *mesh, void *vMeshEl,
                        int xIsLog, int yIsLog);
static int MeshDraw(void *el, int xIsLog, int yIsLog);
static int FilledDraw(void *el, int xIsLog, int yIsLog);
static int VectorsDraw(void *el, int xIsLog, int yIsLog);
static int ContoursDraw(void *el, int xIsLog, int yIsLog);
static int SystemDraw(void *el, int xIsLog, int yIsLog);

static int LinesScan(void *el, int flags, GpBox *limits);
static int DisjointScan(void *el, int flags, GpBox *limits);
static int TextScan(void *el, int flags, GpBox *limits);
static int CellsScan(void *el, int flags, GpBox *limits);
static int PolysScan(void *el, int flags, GpBox *limits);
static int MeshXYScan(void *vMeshEl, int flags, GpBox *limits, GpBox *box);
static int MeshScan(void *el, int flags, GpBox *limits);
static int FilledScan(void *el, int flags, GpBox *limits);
static int VectorsScan(void *el, int flags, GpBox *limits);
static int ContoursScan(void *el, int flags, GpBox *limits);
static int SystemScan(void *el, int flags, GpBox *limits);

static void NoMargin(void *el, GpBox *margin);
static void LinesMargin(void *el, GpBox *margin);
static void DisjointMargin(void *el, GpBox *margin);
static void TextMargin(void *el, GpBox *margin);
static void MeshMargin(void *el, GpBox *margin);
static void VectorsMargin(void *el, GpBox *margin);
static void ContoursMargin(void *el, GpBox *margin);

extern void Gd_ScanZ(long n, const GpReal *z, GpReal *zmin, GpReal *zmax);
static int ScanMn(long n, GpReal *x, GpReal *y, GpReal ymin,
                  GpReal *xmin, GpReal *xmax);
static int ScanMx(long n, GpReal *x, GpReal *y, GpReal ymax,
                  GpReal *xmin, GpReal *xmax);
static int ScanMnMx(long n, GpReal *x, GpReal *y, GpReal ymin, GpReal ymax,
                    GpReal *xmin, GpReal *xmax);
static void ScanRXY(long n, GpReal *x, GpReal *y,
                    int flags, GpBox *limits, GpBox *box);
static int GetLogZ(long n, GpReal *z, GpReal **zlog,
                   GpReal *zmin, GpReal *zmax);
static int Get_LogZ(long n, long nndc, GpReal *z, GpReal **zlog,
                    GpReal *zmin, GpReal *zmax);

extern int Gd_MakeContours(GeContours *con);
extern int Gd_DrawRing(void *elv, int xIsLog, int yIsLog,
                       GeSystem *sys, int t);
extern void Gd_NextMeshBlock(long *ii, long *jj, long len, long iMax,
                             int *reg, int region);

/* ------------------------------------------------------------------------ */
/* Set virtual function tables */

extern GdOpTable *GetDrawingOpTables(void);
static GdOpTable opTables[E_SYSTEM+1]= {
  { E_NONE, 0, 0, 0, 0, 0, 0 },
  { E_LINES, &LinesKill, &LinesGet, &LinesSet,
      &LinesDraw, &LinesScan, &LinesMargin },
  { E_DISJOINT, &DisjointKill, &DisjointGet, &DisjointSet,
      &DisjointDraw, &DisjointScan, &DisjointMargin },
  { E_TEXT, &TextKill, &TextGet, &TextSet,
      &TextDraw, &TextScan, &TextMargin },
  { E_MESH, &MeshKill, &MeshGet, &MeshSet,
      &MeshDraw, &MeshScan, &MeshMargin },
  { E_FILLED, &FilledKill, &FilledGet, &FilledSet,
      &FilledDraw, &FilledScan, &NoMargin },
  { E_VECTORS, &VectorsKill, &VectorsGet, &VectorsSet,
      &VectorsDraw, &VectorsScan, &VectorsMargin },
  { E_CONTOURS, &ContoursKill, &ContoursGet, &ContoursSet,
      &ContoursDraw, &ContoursScan, &ContoursMargin },
  { E_CELLS, &CellsKill, &CellsGet, &CellsSet,
      &CellsDraw, &CellsScan, &NoMargin },
  { E_POLYS, &PolysKill, &PolysGet, &PolysSet,
      &PolysDraw, &PolysScan, &NoMargin },
  { E_SYSTEM, &SystemKill, &SystemGet, &SystemSet,
      &SystemDraw, &SystemScan, &NoMargin }
};

/* this is called at the first call to GdNewDrawing */
GdOpTable *GetDrawingOpTables(void)
{
  return opTables;
}

/* ------------------------------------------------------------------------ */
/* Destructors for drawing elements are private, accessed via the
   Kill virtual function */

void Gd_KillRing(void *elv)
{
  GdElement *el, *next= elv;
  while ((el= next)) {
    next= el->next;
    if (el == next) next= 0;
    el->ops->Kill(el);
  }
}

static void KillElement(GdElement *el)
{
  GdElement *next= el->next;
  if (el->legend) p_free(el->legend);
  if (next) {
    if (next==el) next= 0;
    else { next->prev= el->prev;   el->prev->next= next; }
  }
  p_free(el);
  return;
}

static void LinesKill(void *el)
{
  GeLines *lines= el;
  if (lines->x) p_free(lines->x);
  if (lines->y) p_free(lines->y);
  if (lines->xlog) p_free(lines->xlog);
  if (lines->ylog) p_free(lines->ylog);
  KillElement(el);
}

static void DisjointKill(void *el)
{
  GeDisjoint *lines= el;
  if (lines->x) p_free(lines->x);
  if (lines->y) p_free(lines->y);
  if (lines->xlog) p_free(lines->xlog);
  if (lines->ylog) p_free(lines->ylog);
  if (lines->xq) p_free(lines->xq);
  if (lines->yq) p_free(lines->yq);
  if (lines->xqlog) p_free(lines->xqlog);
  if (lines->yqlog) p_free(lines->yqlog);
  KillElement(el);
}

static void TextKill(void *el)
{
  GeText *text= el;
  if (text->text) p_free(text->text);
  KillElement(el);
}

static void CellsKill(void *el)
{
  GeCells *cells= el;
  if (cells->colors) p_free(cells->colors);
  KillElement(el);
}

static void PolysKill(void *el)
{
  GePolys *polys= el;
  if (polys->x) p_free(polys->x);
  if (polys->y) p_free(polys->y);
  if (polys->xlog) p_free(polys->xlog);
  if (polys->ylog) p_free(polys->ylog);
  if (polys->pn) p_free(polys->pn);
  if (polys->colors) p_free(polys->colors);
  KillElement(el);
}

static void MeshKill(void *el)
{
  Gd_KillMeshXY(el);
  KillElement(el);
}

static void FilledKill(void *el)
{
  GeFill *fill= el;
  Gd_KillMeshXY(el);
  if (fill->colors) {
    if (!(fill->noCopy&NOCOPY_COLORS)) p_free(fill->colors);
    else if (GdFree) GdFree(fill->colors);
  }
  KillElement(el);
}

static void VectorsKill(void *el)
{
  GeVectors *vec= el;
  Gd_KillMeshXY(el);
  if (!(vec->noCopy&NOCOPY_UV)) {
    if (vec->u) p_free(vec->u);
    if (vec->v) p_free(vec->v);
  } else if (GdFree) {
    if (vec->u) GdFree(vec->u);
    if (vec->v) GdFree(vec->v);
  }
  KillElement(el);
}

static void KillConGrps(GeLines **grps, int ngrps)
{
  int i;
  for (i=0 ; i<ngrps ; i++) { Gd_KillRing(grps[i]);  grps[i]= 0; }
}

static void ContoursKill(void *el)
{
  GeContours *con= el;
  Gd_KillMeshXY(el);
  if (con->z) {
    if (!(con->noCopy&NOCOPY_Z)) p_free(con->z);
    else if (GdFree) GdFree(con->z);
  }
  if (con->levels) p_free(con->levels);
  if (con->groups) {
    KillConGrps(con->groups, con->nLevels);
    p_free(con->groups);
  }
  KillElement(el);
}

static void SystemKill(void *el)
{
  GeSystem *sys= el;
  Gd_KillRing(sys->elements);
  KillElement(el);
}

void Gd_KillMeshXY(void *vMeshEl)
{
  GeMesh *meshEl= vMeshEl;
  GaQuadMesh *mesh= &meshEl->mesh;
  int noCopy= meshEl->noCopy;
  if (!(noCopy&NOCOPY_MESH)) {
    if (mesh->x) p_free(mesh->x);
    if (mesh->y) p_free(mesh->y);
  } else if (GdFree) {
    if (mesh->x) GdFree(mesh->x);
    if (mesh->y) GdFree(mesh->y);
  }
  if (mesh->reg) {
    if (!(noCopy&NOCOPY_REG)) p_free(mesh->reg);
    else if (GdFree) GdFree(mesh->reg);
  }
  if (mesh->triangle) {
    if (!(noCopy&NOCOPY_TRI)) p_free(mesh->triangle);
    else if (GdFree) GdFree(mesh->triangle);
  }
}

/* ------------------------------------------------------------------------ */
/* GetProps virtual function loads gistA, gistD from GdElement */

static int LinesGet(void *el)
{
  GeLines *e= el;
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.n= e->n;
  gistD.x= e->x;
  gistD.y= e->y;
  gistA.l= e->l;
  gistA.dl= e->dl;
  gistA.m= e->m;
  return E_LINES;
}

static int DisjointGet(void *el)
{
  GeDisjoint *e= el;
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.n= e->n;
  gistD.x= e->x;
  gistD.y= e->y;
  gistD.xq= e->xq;
  gistD.yq= e->yq;
  gistA.l= e->l;
  return E_DISJOINT;
}

static int TextGet(void *el)
{
  GeText *e= el;
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.x0= e->x0;
  gistD.y0= e->y0;
  gistD.text= e->text;
  gistA.t= e->t;
  return E_TEXT;
}

static int CellsGet(void *el)
{
  GeCells *e= el;
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.px= e->px;
  gistD.py= e->py;
  gistD.qx= e->qx;
  gistD.qy= e->qy;
  gistD.width= e->width;
  gistD.height= e->height;
  gistD.colors= e->colors;
  gistA.rgb = e->rgb;
  return E_CELLS;
}

static int PolysGet(void *el)
{
  GePolys *e= el;
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.n= e->n;
  gistD.x= e->x;
  gistD.y= e->y;
  gistD.pn= e->pn;
  gistD.colors= e->colors;
  gistA.e = e->e;
  gistA.rgb = e->rgb;
  return E_POLYS;
}

static int MeshGet(void *el)
{
  GeMesh *e= el;
  Gd_MeshXYGet(el);
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.boundary= e->boundary;
  gistD.inhibit= e->inhibit;
  gistA.l= e->l;
  return E_MESH;
}

static int FilledGet(void *el)
{
  GeFill *e= el;
  Gd_MeshXYGet(el);
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.nColumns= e->nColumns;
  gistD.colors= e->colors;
  gistA.e= e->e;
  gistA.rgb = e->rgb;
  return E_FILLED;
}

static int VectorsGet(void *el)
{
  GeVectors *e= el;
  Gd_MeshXYGet(el);
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.u= e->u;
  gistD.v= e->v;
  gistD.scale= e->scale;
  gistA.l= e->l;
  gistA.f= e->f;
  gistA.vect= e->vect;
  return E_VECTORS;
}

static int ContoursGet(void *el)
{
  GeContours *e= el;
  Gd_MeshXYGet(el);
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  gistD.z= e->z;
  gistD.nLevels= e->nLevels;
  gistD.levels= e->levels;
  gistA.l= e->l;
  gistA.dl= e->dl;
  gistA.m= e->m;
  return E_CONTOURS;
}

static int SystemGet(void *el)
{
  GeSystem *e= el;
  gistD.hidden= e->el.hidden;
  gistD.legend= e->el.legend;
  return E_SYSTEM;
}

void Gd_MeshXYGet(void *vMeshEl)
{
  GeMesh *meshEl= vMeshEl;
  GaQuadMesh *mesh= &meshEl->mesh;
  gistD.noCopy= meshEl->noCopy;
  gistD.mesh.iMax= mesh->iMax;
  gistD.mesh.jMax= mesh->jMax;
  gistD.mesh.x= mesh->x;
  gistD.mesh.y= mesh->y;
  gistD.mesh.reg= mesh->reg;
  gistD.mesh.triangle= mesh->triangle;
  gistD.region= meshEl->region;
}

/* ------------------------------------------------------------------------ */
/* SetProps virtual function loads GdElement from gistA, gistD */

static int LinesSet(void *el, int xyzChanged)
{
  GeLines *e= el;
  Gd_LinesSubSet(el);
  e->el.legend= gistD.legend;
  if (xyzChanged & CHANGE_XY) {
    e->n= gistD.n;
    e->x= gistD.x;
    e->y= gistD.y;
    if (e->xlog) { p_free(e->xlog); e->xlog= 0; }
    if (e->ylog) { p_free(e->ylog); e->ylog= 0; }
  }
  return 0;
}

void Gd_LinesSubSet(void *el)
{
  GeLines *e= el;
  e->el.hidden= gistD.hidden;
  e->l= gistA.l;
  e->dl= gistA.dl;
  e->m= gistA.m;
}

static int DisjointSet(void *el, int xyzChanged)
{
  GeDisjoint *e= el;
  e->el.hidden= gistD.hidden;
  e->el.legend= gistD.legend;
  e->n= gistD.n;
  e->x= gistD.x;
  e->y= gistD.y;
  e->xq= gistD.xq;
  e->yq= gistD.yq;
  e->l= gistA.l;
  if (xyzChanged & CHANGE_XY) {
    if (e->xlog) { p_free(e->xlog); e->xlog= 0; }
    if (e->ylog) { p_free(e->ylog); e->ylog= 0; }
    if (e->xqlog) { p_free(e->xqlog); e->xqlog= 0; }
    if (e->yqlog) { p_free(e->yqlog); e->yqlog= 0; }
  }
  return 0;
}

/* ARGSUSED */
static int TextSet(void *el, int xyzChanged)
{
  GeText *e= el;
  e->el.hidden= gistD.hidden;
  e->el.legend= gistD.legend;
  e->x0= gistD.x0;
  e->y0= gistD.y0;
  e->text= gistD.text;
  e->t= gistA.t;
  return 0;
}

/* ARGSUSED */
static int CellsSet(void *el, int xyzChanged)
{
  GeCells *e= el;
  e->el.hidden= gistD.hidden;
  e->el.legend= gistD.legend;
  e->px= gistD.px;
  e->py= gistD.py;
  e->qx= gistD.qx;
  e->qy= gistD.qy;
  e->width= gistD.width;
  e->height= gistD.height;
  e->colors= gistD.colors;
  return 0;
}

static int PolysSet(void *el, int xyzChanged)
{
  GePolys *e= el;
  Gd_LinesSubSet(el);
  e->el.legend= gistD.legend;
  if (xyzChanged & CHANGE_XY) {
    e->n= gistD.n;
    e->x= gistD.x;
    e->y= gistD.y;
    if (e->xlog) { p_free(e->xlog); e->xlog= 0; }
    if (e->ylog) { p_free(e->ylog); e->ylog= 0; }
  }
  e->pn= gistD.pn;
  e->colors= gistD.colors;
  return 0;
}

static void MeshXYSet(void *vMeshEl, int xyzChanged)
{
  GeMesh *meshEl= vMeshEl;
  GaQuadMesh *mesh= &meshEl->mesh;
  meshEl->el.legend= gistD.legend;
  meshEl->el.hidden= gistD.hidden;
  meshEl->noCopy= gistD.noCopy;
  mesh->iMax= gistD.mesh.iMax;
  mesh->jMax= gistD.mesh.jMax;
  mesh->x= gistD.mesh.x;
  mesh->y= gistD.mesh.y;
  mesh->reg= gistD.mesh.reg;
  mesh->triangle= gistD.mesh.triangle;
  if (xyzChanged & CHANGE_XY) {
    if (meshEl->xlog) { p_free(meshEl->xlog); meshEl->xlog= 0; }
    if (meshEl->ylog) { p_free(meshEl->ylog); meshEl->ylog= 0; }
  }
  meshEl->region= gistD.region;
}

static int MeshSet(void *el, int xyzChanged)
{
  GeMesh *e= el;
  MeshXYSet(el, xyzChanged);
  e->boundary= gistD.boundary;
  e->inhibit= gistD.inhibit;
  e->l= gistA.l;
  return 0;
}

static int FilledSet(void *el, int xyzChanged)
{
  GeFill *e= el;
  MeshXYSet(el, xyzChanged);
  e->nColumns= gistD.nColumns;
  e->colors= gistD.colors;
  e->e= gistA.e;
  return 0;
}

static int VectorsSet(void *el, int xyzChanged)
{
  GeVectors *e= el;
  MeshXYSet(el, xyzChanged);
  e->u= gistD.u;
  e->v= gistD.v;
  e->scale= gistD.scale;
  e->l= gistA.l;
  e->f= gistA.f;
  e->vect= gistA.vect;
  return 0;
}

static int ContoursSet(void *el, int xyzChanged)
{
  GeContours *e= el;
  int oldN= e->nLevels;
  MeshXYSet(el, xyzChanged);
  e->z= gistD.z;
  e->nLevels= gistD.nLevels;
  e->levels= gistD.levels;
  e->l= gistA.l;
  e->dl= gistA.dl;
  e->m= gistA.m;
  if (xyzChanged & CHANGE_Z) {
    if (e->groups) {
      KillConGrps(e->groups, oldN);
      if (oldN!=gistD.nLevels) {
        p_free(e->groups);
        e->groups= 0;
      }
    }
    if (gistD.nLevels>0) {
      if (!e->groups)
        e->groups= (GeLines **)p_malloc(sizeof(GeLines *)*gistD.nLevels);
      if (!e->groups || Gd_MakeContours(e)) return 1;
    }
  }
  return 0;
}

/* ARGSUSED */
static int SystemSet(void *el, int xyzChanged)
{
  GeSystem *e= el;
  e->el.hidden= gistD.hidden;
  e->el.legend= gistD.legend;
  return 0;
}

/* ------------------------------------------------------------------------ */
/* Draw virtual function calls Gp and Ga level rendering routines */

static int LinesDraw(void *el, int xIsLog, int yIsLog)
{
  GeLines *e= el;
  GpReal *px= xIsLog? e->xlog : e->x;
  GpReal *py= yIsLog? e->ylog : e->y;
  long n= e->n;
  if (e->el.hidden || n<=0) return 0;
  gistA.l= e->l;
  gistA.dl= e->dl;
  gistA.m= e->m;
  return GaLines(n, px, py);
}

static int DisjointDraw(void *el, int xIsLog, int yIsLog)
{
  GeDisjoint *e= el;
  GpReal *px= xIsLog? e->xlog : e->x;
  GpReal *py= yIsLog? e->ylog : e->y;
  GpReal *qx= xIsLog? e->xqlog : e->xq;
  GpReal *qy= yIsLog? e->yqlog : e->yq;
  long n= e->n;
  if (e->el.hidden || n<=0) return 0;
  gistA.l= e->l;
  return GpDisjoint(n, px, py, qx, qy);
}

static int TextDraw(void *el, int xIsLog, int yIsLog)
{
  GeText *e= el;
  GpReal x0, y0;
  if (e->el.hidden || !e->text) return 0;
  x0= xIsLog? SAFELOG(e->x0) : e->x0;
  y0= yIsLog? SAFELOG(e->y0) : e->y0;
  gistA.t= e->t;
  return GpText(x0, y0, e->text);
}

static int CellsDraw(void *el, int xIsLog, int yIsLog)
{
  GeCells *e= el;
  GpReal px, py, qx, qy;
  int value;
  if (e->el.hidden) return 0;
  px= xIsLog? SAFELOG(e->px) : e->px;
  py= yIsLog? SAFELOG(e->py) : e->py;
  qx= xIsLog? SAFELOG(e->qx) : e->qx;
  qy= yIsLog? SAFELOG(e->qy) : e->qy;
  gistA.rgb = e->rgb;
  value = GpCells(px, py, qx, qy, e->width, e->height, e->width, e->colors);
  gistA.rgb = 0;  /* modest attempt at backward compatibility */
  return value;
}

static int PolysDraw(void *el, int xIsLog, int yIsLog)
{
  int result= 0;
  GePolys *e= el;
  GpReal *px= xIsLog? e->xlog : e->x;
  GpReal *py= yIsLog? e->ylog : e->y;
  GpColor *colors= e->colors;
  int rgb = colors? e->rgb : 0;
  long n= e->n;
  long *pn= e->pn;
  long i;
  if (e->el.hidden || n<=0) return 0;
  gistA.e= e->e;
  if (!colors) gistA.f.color =  BG_COLOR;
  if (n<2 || pn[1]>1) {
    long j = 0;
    for (i=0 ; i<n ; i++) {
      if (rgb)
        gistA.f.color=P_RGB(colors[j],colors[j+1],colors[j+2]), j+=3;
      else if (colors)
        gistA.f.color = colors[i];
      result|= GpFill(pn[i], px, py);
      px+= pn[i];
      py+= pn[i];
    }
  } else {
    long j = 3;
    long i0= pn[0]-1;
    for (i=1 ; i<n ; i++) {
      if (rgb)
        gistA.f.color=P_RGB(colors[j],colors[j+1],colors[j+2]), j+=3;
      else if (colors)
        gistA.f.color = colors[i];
      result|= GaFillMarker(pn[0], px, py, px[i0+i], py[i0+i]);
    }
  }
  return result;
}

static void MeshDrawSet(GaQuadMesh *mesh, void *vMeshEl,
                        int xIsLog, int yIsLog)
{
  GeMesh *meshEl= vMeshEl;
  GaQuadMesh *msh= &meshEl->mesh;
  GpReal *x= xIsLog? meshEl->xlog : msh->x;
  GpReal *y= yIsLog? meshEl->ylog : msh->y;
  mesh->iMax= msh->iMax;
  mesh->jMax= msh->jMax;
  mesh->x= x;
  mesh->y= y;
  mesh->reg= msh->reg;
  mesh->triangle= msh->triangle;
}

static int MeshDraw(void *el, int xIsLog, int yIsLog)
{
  GeMesh *e= el;
  GaQuadMesh mesh;
  if (e->el.hidden) return 0;
  MeshDrawSet(&mesh, el, xIsLog, yIsLog);
  gistA.l= e->l;
  return GaMesh(&mesh, e->region, e->boundary, e->inhibit);
}

static int FilledDraw(void *el, int xIsLog, int yIsLog)
{
  GeFill *e= el;
  GaQuadMesh mesh;
  if (e->el.hidden) return 0;
  MeshDrawSet(&mesh, el, xIsLog, yIsLog);
  gistA.e= e->e;
  gistA.rgb = e->rgb;
  return GaFillMesh(&mesh, e->region, e->colors, e->nColumns);
}

static int VectorsDraw(void *el, int xIsLog, int yIsLog)
{
  GeVectors *e= el;
  GaQuadMesh mesh;
  if (e->el.hidden) return 0;
  MeshDrawSet(&mesh, el, xIsLog, yIsLog);
  gistA.l= e->l;
  gistA.f= e->f;
  gistA.vect= e->vect;
  return GaVectors(&mesh, e->region, e->u, e->v, e->scale);
}

static int ContoursDraw(void *el, int xIsLog, int yIsLog)
{
  GeContours *e= el;
  int nLevels= e->nLevels;
  GeLines **groups= e->groups;
  int value= 0;
  if (e->el.hidden || nLevels<=0) return 0;
  if (!groups) return 1;
  while (nLevels--) value|= Gd_DrawRing(*groups++, xIsLog, yIsLog, 0, 1);
  return value;
}

static int SystemDraw(void *el, int xIsLog, int yIsLog)
     /* NOTE: xIsLog input is used in a very non-standard way as the
        index of this system.  This is possible since xIsLog and yIsLog
        are otherwise meaningless in SystemDraw.  */
{
  GeSystem *e= el;
  int vflags, hflags= e->flags;
  int systemCounter= xIsLog;   /* Yes, this is non-standard usage */
  GpBox port, *tickIn;
  if (e->el.hidden || !e->elements) return 0;

  xIsLog= hflags & D_LOGX;
  yIsLog= hflags & D_LOGY;
  GpSetTrans(&e->trans);

  /* In order to prevent needless GaTick calls, a special feature
     is built into GdBeginSy.  */
  hflags= e->ticks.horiz.flags;
  vflags= e->ticks.vert.flags;
  if (vflags & TICK_C || hflags & TICK_C) tickIn= 0;
  else {
    GpReal tlen= e->ticks.vert.tickLen[0];
    GpReal twid= 0.5*e->ticks.vert.tickStyle.width*DEFAULT_LINE_WIDTH;
    tlen= (vflags&TICK_IN)? ((vflags&TICK_OUT)? 0.5 : 1.0)*tlen : 0.0;

    tickIn= &port;
    port= e->trans.viewport;
    if (vflags & TICK_L) port.xmin-= e->ticks.vert.tickOff + tlen + twid;
    if (vflags & TICK_U) port.xmax+= e->ticks.vert.tickOff - tlen - twid;

    tlen= e->ticks.horiz.tickLen[0];
    twid= 0.5*e->ticks.horiz.tickStyle.width*DEFAULT_LINE_WIDTH;
    tlen= (hflags&TICK_IN)? ((hflags&TICK_OUT)? 0.5 : 1.0)*tlen : 0.0;
    if (hflags & TICK_L) port.ymin-= e->ticks.horiz.tickOff + tlen + twid;
    if (hflags & TICK_U) port.ymax+= e->ticks.horiz.tickOff - tlen - twid;
  }

  hflags= GdBeginSy(&e->el.box, tickIn, &e->trans.viewport,
                    e->el.number, systemCounter);

  /* Draw the elements for this coordinate system before the ticks.  */
  gistClip= 1;   /* turn on clipping for elements */
  if (hflags & 1) Gd_DrawRing(e->elements, xIsLog, yIsLog, e, 0);

  /* Draw tick marks on top of elements.  If the user has chosen a style
     where the ticks overlap the viewport, he probably wants the ticks
     to obstruct his data anyway. */
  gistClip= 0;   /* turn off clipping for ticks */
  if (hflags & 2) GaAltTick(&e->ticks, xIsLog, yIsLog,
                            e->xtick, e->xlabel, e->ytick, e->ylabel);
  return 0;
}

/* ------------------------------------------------------------------------ */
/* Scan virtual function gets logs if reqd, sets box, scans xy  */

static int ScanMn(long n, GpReal *x, GpReal *y, GpReal ymin,
                  GpReal *xmin, GpReal *xmax)
{
  GpReal xn, xx;
  long i;
  for (i=0 ; i<n ; i++) if (y[i]>=ymin) break;
  if (i>=n) return 0;
  xn= xx= x[i++];
  for ( ; i<n ; i++) if (y[i]>=ymin) {
    if (x[i]<xn) xn= x[i];
    else if (x[i]>xx) xx= x[i];
  }
  *xmin= xn;
  *xmax= xx;
  return 1;
}

static int ScanMx(long n, GpReal *x, GpReal *y, GpReal ymax,
                  GpReal *xmin, GpReal *xmax)
{
  GpReal xn, xx;
  long i;
  for (i=0 ; i<n ; i++) if (y[i]<=ymax) break;
  if (i>=n) return 0;
  xn= xx= x[i++];
  for ( ; i<n ; i++) if (y[i]<=ymax) {
    if (x[i]<xn) xn= x[i];
    else if (x[i]>xx) xx= x[i];
  }
  *xmin= xn;
  *xmax= xx;
  return 1;
}

static int ScanMnMx(long n, GpReal *x, GpReal *y, GpReal ymin, GpReal ymax,
                    GpReal *xmin, GpReal *xmax)
{
  GpReal xn, xx;
  long i;
  for (i=0 ; i<n ; i++) if (y[i]>=ymin && y[i]<=ymax) break;
  if (i>=n) return 0;
  xn= xx= x[i++];
  for ( ; i<n ; i++) if (y[i]>=ymin && y[i]<=ymax) {
    if (x[i]<xn) xn= x[i];
    else if (x[i]>xx) xx= x[i];
  }
  *xmin= xn;
  *xmax= xx;
  return 1;
}

static void ScanRXY(long n, GpReal *x, GpReal *y,
                    int flags, GpBox *limits, GpBox *box)
{
  int dxmin= flags & D_XMIN,  dxmax= flags & D_XMAX;
  int dymin= flags & D_YMIN,  dymax= flags & D_YMAX;
  int any;

  if (dxmin || dxmax) {
    GpReal xmin, xmax;
    if (dymin) {
      if (dymax) { xmin= box->xmin;  xmax= box->xmax; any= 1; }
      else if (box->ymin>limits->ymax) any= 0;
      else any= ScanMx(n, x, y, limits->ymax, &xmin, &xmax);
    } else if (box->ymax<limits->ymin) {
      any= 0;
    } else {
      if (dymax) any= ScanMn(n, x, y, limits->ymin, &xmin, &xmax);
      else if (box->ymin>limits->ymax) any= 0;
      else any= ScanMnMx(n, x, y, limits->ymin, limits->ymax, &xmin, &xmax);
    }
    if (any) {
      if (dxmin) limits->xmin= xmin;
      if (dxmax) limits->xmax= xmax;
    } else {  /* GdScan requires min>max if no curves visible */
      if (dxmin) {
        if (dxmax) limits->xmax= 0.0;
        if (limits->xmax>0.0) limits->xmin= 1.1*limits->xmax;
        else limits->xmin= 0.9*limits->xmax+1.0;
      } else { /* dxmax is set */
        if (limits->xmin>0.0) limits->xmax= 0.9*limits->xmin;
        else limits->xmax= 1.1*limits->xmin-1.0;
      }
    }
  }
  if (dymin || dymax) {
    GpReal ymin, ymax;
    if (dxmin) {
      if (dxmax) { ymin= box->ymin;  ymax= box->ymax; any= 1; }
      else if (box->xmin>limits->xmax) any= 0;
      else any= ScanMx(n, y, x, limits->xmax, &ymin, &ymax);
    } else if (box->xmax<limits->xmin) {
      any= 0;
    } else {
      if (dxmax) any= ScanMn(n, y, x, limits->xmin, &ymin, &ymax);
      else if (box->xmin>limits->xmax) any= 0;
      else any= ScanMnMx(n, y, x, limits->xmin, limits->xmax, &ymin, &ymax);
    }
    if (any) {
      if (dymin) limits->ymin= ymin;
      if (dymax) limits->ymax= ymax;
    } else {  /* GdScan requires min>max if no curves visible */
      if (dymin) {
        if (dymax) limits->ymax= 0.0;
        if (limits->ymax>0.0) limits->ymin= 1.1*limits->ymax;
        else limits->ymin= 0.9*limits->ymax+1.0;
      } else { /* dymax is set */
        if (limits->ymin>0.0) limits->ymax= 0.9*limits->ymin;
        else limits->ymax= 1.1*limits->ymin-1.0;
      }
    }
  }
}

static int GetLogZ(long n, GpReal *z, GpReal **zlog,
                   GpReal *zmin, GpReal *zmax)
{
  GpReal *zl= (GpReal *)p_malloc(sizeof(GpReal)*n);
  *zlog= zl;
  if (zl) {
    long i;
    for (i=0 ; i<n ; i++) zl[i]= SAFELOG(z[i]);
    if (zmin) Gd_ScanZ(n, zl, zmin, zmax);
  } else {
    strcpy(gistError, "memory manager failed in Gd log function");
    return -1;
  }
  return 0;
}

static int Get_LogZ(long n, long nndc, GpReal *z, GpReal **zlog,
                    GpReal *zmin, GpReal *zmax)
{
  GpReal *zl= (GpReal *)p_malloc(sizeof(GpReal)*n);
  *zlog= zl;
  if (zl) {
    long i;
    for (i=0 ; i<nndc ; i++) zl[i]= z[i];
    for ( ; i<n ; i++) zl[i]= SAFELOG(z[i]);
    if (zmin) Gd_ScanZ(n-nndc, zl+nndc, zmin, zmax);
  } else {
    strcpy(gistError, "memory manager failed in Gd_log function");
    return -1;
  }
  return 0;
}

static int LinesScan(void *el, int flags, GpBox *limits)
{
  GeLines *e= el;
  GpReal *x, *y;

  /* First, get log values if necessary, and set box */
  if (flags & D_LOGX) {
    if (!e->xlog && GetLogZ(e->n, e->x, &e->xlog,
                            &e->logBox.xmin, &e->logBox.xmax)) return 1;
    e->el.box.xmin= e->logBox.xmin;
    e->el.box.xmax= e->logBox.xmax;
    x= e->xlog;
  } else {
    e->el.box.xmin= e->linBox.xmin;
    e->el.box.xmax= e->linBox.xmax;
    x= e->x;
  }
  if (flags & D_LOGY) {
    if (!e->ylog && GetLogZ(e->n, e->y, &e->ylog,
                            &e->logBox.ymin, &e->logBox.ymax)) return 1;
    e->el.box.ymin= e->logBox.ymin;
    e->el.box.ymax= e->logBox.ymax;
    y= e->ylog;
  } else {
    e->el.box.ymin= e->linBox.ymin;
    e->el.box.ymax= e->linBox.ymax;
    y= e->y;
  }

  if (flags & D_RESTRICT) {
    /* Scan points, restricting x limits to lie within fixed y limits
       and vice-versa.  Assume that limits.min<limits.max.  */
    ScanRXY(e->n, x, y, flags, limits, &e->el.box);
  } else {
    /* Unrestricted limits are either fixed or same as bounding box.  */
    if (flags & D_XMIN) limits->xmin= e->el.box.xmin;
    if (flags & D_XMAX) limits->xmax= e->el.box.xmax;
    if (flags & D_YMIN) limits->ymin= e->el.box.ymin;
    if (flags & D_YMAX) limits->ymax= e->el.box.ymax;
  }

  return 0;
}

static int DisjointScan(void *el, int flags, GpBox *limits)
{
  GeDisjoint *e= el;
  GpReal *x, *y, *xq, *yq;
  GpReal xymin, xymax;

  /* First, get log values if necessary, and set box */
  if (flags & D_LOGX) {
    if (!e->xlog && GetLogZ(e->n, e->x, &e->xlog,
                            &e->logBox.xmin, &e->logBox.xmax)) return 1;
    e->el.box.xmin= e->logBox.xmin;
    e->el.box.xmax= e->logBox.xmax;
    x= e->xlog;
    if (!e->xqlog) {
      if (GetLogZ(e->n, e->xq, &e->xqlog, &xymin, &xymax)) return 1;
      if (xymin<e->el.box.xmin) e->el.box.xmin= e->logBox.xmin;
      if (xymax>e->el.box.xmax) e->el.box.xmax= e->logBox.xmax;
    }
    xq= e->xqlog;
  } else {
    e->el.box.xmin= e->linBox.xmin;
    e->el.box.xmax= e->linBox.xmax;
    x= e->x;
    xq= e->xq;
  }
  if (flags & D_LOGY) {
    if (!e->ylog && GetLogZ(e->n, e->y, &e->ylog,
                            &e->logBox.ymin, &e->logBox.ymax)) return 1;
    e->el.box.ymin= e->logBox.ymin;
    e->el.box.ymax= e->logBox.ymax;
    y= e->ylog;
    if (!e->yqlog) {
      if (GetLogZ(e->n, e->yq, &e->yqlog, &xymin, &xymax)) return 1;
      if (xymin<e->el.box.ymin) e->el.box.ymin= e->logBox.ymin;
      if (xymax>e->el.box.ymax) e->el.box.ymax= e->logBox.ymax;
    }
    yq= e->yqlog;
  } else {
    e->el.box.ymin= e->linBox.ymin;
    e->el.box.ymax= e->linBox.ymax;
    y= e->y;
    yq= e->yq;
  }

  if (flags & D_RESTRICT) {
    /* Scan points, restricting x limits to lie within fixed y limits
       and vice-versa.  Assume that limits.min<limits.max.  */
    GpBox box;
    ScanRXY(e->n, x, y, flags, limits, &e->el.box);
    ScanRXY(e->n, xq, yq, flags, &box, &e->el.box);
    GpSwallow(limits, &box);
  } else {
    /* Unrestricted limits are either fixed or same as bounding box.  */
    if (flags & D_XMIN) limits->xmin= e->el.box.xmin;
    if (flags & D_XMAX) limits->xmax= e->el.box.xmax;
    if (flags & D_YMIN) limits->ymin= e->el.box.ymin;
    if (flags & D_YMAX) limits->ymax= e->el.box.ymax;
  }

  return 0;
}

static int TextScan(void *el, int flags, GpBox *limits)
{
  GeText *e= el;
  GpReal x0= e->x0;
  GpReal y0= e->y0;

  if (flags & D_LOGX) x0= SAFELOG(x0);
  if (flags & D_LOGY) y0= SAFELOG(y0);

  if (flags & D_XMIN) limits->xmin= x0;
  if (flags & D_XMAX) limits->xmax= x0;
  if (flags & D_YMIN) limits->ymin= y0;
  if (flags & D_YMAX) limits->ymax= y0;
  return 0;
}

static int CellsScan(void *el, int flags, GpBox *limits)
{
  GeCells *e= el;
  GpReal x[2], y[2];

  if (e->px<e->qx) { x[0]= e->px;  x[1]= e->qx; }
  else { x[0]= e->qx;  x[1]= e->px; }
  if (e->py<e->qy) { y[0]= e->py;  y[1]= e->qy; }
  else { y[0]= e->qy;  y[1]= e->py; }

  /* First, get log values if necessary, and set box */
  if (flags & D_LOGX) {
    e->el.box.xmin= SAFELOG(x[0]);
    e->el.box.xmax= SAFELOG(x[1]);
  } else {
    e->el.box.xmin= x[0];
    e->el.box.xmax= x[1];
  }
  if (flags & D_LOGY) {
    e->el.box.ymin= SAFELOG(y[0]);
    e->el.box.ymax= SAFELOG(y[1]);
  } else {
    e->el.box.ymin= y[0];
    e->el.box.ymax= y[1];
  }

  if (flags & D_XMIN) limits->xmin= e->el.box.xmin;
  if (flags & D_XMAX) limits->xmax= e->el.box.xmax;
  if (flags & D_YMIN) limits->ymin= e->el.box.ymin;
  if (flags & D_YMAX) limits->ymax= e->el.box.ymax;

  return 0;
}

static int PolysScan(void *el, int flags, GpBox *limits)
{
  GePolys *e= el;
  GpReal *x, *y;
  long i, ntot= 0;
  long nndc= (e->n<2 || e->pn[1]>1)? 0 : e->pn[0];

  /* compute total number of points */
  for (i=0 ; i<e->n ; i++) ntot+= e->pn[i];

  /* First, get log values if necessary, and set box */
  if (flags & D_LOGX) {
    if (!e->xlog && Get_LogZ(ntot, nndc, e->x, &e->xlog,
                             &e->logBox.xmin, &e->logBox.xmax)) return 1;
    e->el.box.xmin= e->logBox.xmin;
    e->el.box.xmax= e->logBox.xmax;
    x= e->xlog;
  } else {
    e->el.box.xmin= e->linBox.xmin;
    e->el.box.xmax= e->linBox.xmax;
    x= e->x;
  }
  if (flags & D_LOGY) {
    if (!e->ylog && Get_LogZ(ntot, nndc, e->y, &e->ylog,
                             &e->logBox.ymin, &e->logBox.ymax)) return 1;
    e->el.box.ymin= e->logBox.ymin;
    e->el.box.ymax= e->logBox.ymax;
    y= e->ylog;
  } else {
    e->el.box.ymin= e->linBox.ymin;
    e->el.box.ymax= e->linBox.ymax;
    y= e->y;
  }

  if (flags & D_RESTRICT) {
    /* Scan points, restricting x limits to lie within fixed y limits
       and vice-versa.  Assume that limits.min<limits.max.  */
    ScanRXY(ntot-nndc, x+nndc, y+nndc, flags, limits, &e->el.box);
  } else {
    /* Unrestricted limits are either fixed or same as bounding box.  */
    if (flags & D_XMIN) limits->xmin= e->el.box.xmin;
    if (flags & D_XMAX) limits->xmax= e->el.box.xmax;
    if (flags & D_YMIN) limits->ymin= e->el.box.ymin;
    if (flags & D_YMAX) limits->ymax= e->el.box.ymax;
  }

  return 0;
}

static int MeshXYScan(void *vMeshEl, int flags, GpBox *limits, GpBox *box)
{
  GeMesh *meshEl= vMeshEl;
  GaQuadMesh *mesh= &meshEl->mesh;
  GpReal *x, *y;

  /* First, get log values if necessary, and set box */
  if (flags & D_LOGX) {
    long len= mesh->iMax*mesh->jMax;
    int region= meshEl->region;
    GpReal xmin, xmax;
    long i, j, iMax= mesh->iMax;
    int *reg= mesh->reg, first= 1;

    if (!meshEl->xlog && GetLogZ(len, mesh->x, &meshEl->xlog, 0, 0))
      return 1;
    for (i=0 ; i<len ; ) {
      Gd_NextMeshBlock(&i, &j, len, iMax, reg, region);
      if (i>=len) break;
      Gd_ScanZ(j-i, meshEl->xlog+i, &xmin, &xmax);
      if (first) {
        meshEl->logBox.xmin= xmin;
        meshEl->logBox.xmax= xmax;
        first= 0;
      } else {
        if (xmin<meshEl->logBox.xmin) meshEl->logBox.xmin= xmin;
        if (xmax>meshEl->logBox.xmax) meshEl->logBox.xmax= xmax;
      }
      i= j+1;
    }
    box->xmin= meshEl->logBox.xmin;
    box->xmax= meshEl->logBox.xmax;
    x= meshEl->xlog;
  } else {
    box->xmin= meshEl->linBox.xmin;
    box->xmax= meshEl->linBox.xmax;
    x= mesh->x;
  }
  if (flags & D_LOGY) {
    long len= mesh->iMax*mesh->jMax;
    int region= meshEl->region;
    GpReal ymin, ymax;
    long i, j, iMax= mesh->iMax;
    int *reg= mesh->reg, first= 1;

    if (!meshEl->ylog && GetLogZ(len, mesh->y, &meshEl->ylog, 0, 0))
      return 1;
    for (i=0 ; i<len ; ) {
      Gd_NextMeshBlock(&i, &j, len, iMax, reg, region);
      if (i>=len) break;
      Gd_ScanZ(j-i, meshEl->ylog+i, &ymin, &ymax);
      if (first) {
        meshEl->logBox.ymin= ymin;
        meshEl->logBox.ymax= ymax;
        first= 0;
      } else {
        if (ymin<meshEl->logBox.ymin) meshEl->logBox.ymin= ymin;
        if (ymax>meshEl->logBox.ymax) meshEl->logBox.ymax= ymax;
      }
      i= j+1;
    }
    box->ymin= meshEl->logBox.ymin;
    box->ymax= meshEl->logBox.ymax;
    y= meshEl->ylog;
  } else {
    box->ymin= meshEl->linBox.ymin;
    box->ymax= meshEl->linBox.ymax;
    y= mesh->y;
  }

  if (flags & D_RESTRICT) {
    /* Scan points, restricting x limits to lie within fixed y limits
       and vice-versa.  Assume that limits.min<limits.max.  */
    long len= mesh->iMax*mesh->jMax;
    int region= meshEl->region;
    GpBox tbox;
    long i, j, iMax= mesh->iMax;
    int *reg= mesh->reg, first= 1;
    tbox= *limits;
    for (i=0 ; i<len ; ) {
      Gd_NextMeshBlock(&i, &j, len, iMax, reg, region);
      if (i>=len) break;
      ScanRXY(j-i, x+i, y+i, flags, limits, &tbox);
      if (first) { *box= tbox;  first= 0; }
      else GpSwallow(box, &tbox);
      i= j+1;
    }
  } else {
    /* Unrestricted limits are either fixed or same as bounding box.  */
    if (flags & D_XMIN) limits->xmin= box->xmin;
    if (flags & D_XMAX) limits->xmax= box->xmax;
    if (flags & D_YMIN) limits->ymin= box->ymin;
    if (flags & D_YMAX) limits->ymax= box->ymax;
  }

  return 0;
}

static int MeshScan(void *el, int flags, GpBox *limits)
{
  GeMesh *e= el;
  return MeshXYScan(el, flags, limits, &e->el.box);
}

static int FilledScan(void *el, int flags, GpBox *limits)
{
  GeFill *e= el;
  return MeshXYScan(el, flags, limits, &e->el.box);
}

static int VectorsScan(void *el, int flags, GpBox *limits)
{
  GeVectors *e= el;
  return MeshXYScan(el, flags, limits, &e->el.box);
}

static int ContoursScan(void *el, int flags, GpBox *limits)
{
  GeContours *e= el;
  GpBox lims= *limits;
  GeLines *elx, *el0, **groups= e->groups;
  int i, value= 0, none= 1;
  for (i=0 ; i<e->nLevels ; i++) {
    el0= *groups++;
    if ((elx= el0)) do {
      value|= LinesScan(elx, flags, &lims);
      if (none) { *limits= lims;   e->el.box= lims; }
      else { GpSwallow(limits, &lims);   GpSwallow(&e->el.box, &lims); }
      none= 0;
      elx= (GeLines *)elx->el.next;
    } while (elx != el0);
  }
  if (none) value= MeshXYScan(el, flags, limits, &e->el.box);
  return value;
}

/* ARGSUSED */
static int SystemScan(void *el, int flags, GpBox *limits)
{
  return 0;   /* cannot ever happen... */
}

/* ------------------------------------------------------------------------ */
/* Margin virtual function returns margin box */

/* ARGSUSED */
static void NoMargin(void *el, GpBox *margin)
{
  margin->xmin= margin->xmax= margin->ymin= margin->ymax= 0.0;
}

static void LinesMargin(void *el, GpBox *margin)
{
  /* This only accounts for line width, ignoring other decorations--
     other decorations seem unlikely outside coordinate systems */
  GeLines *lines= el;
  margin->xmin= margin->xmax= margin->ymin= margin->ymax=
    0.5*lines->l.width*DEFAULT_LINE_WIDTH;
}

static void DisjointMargin(void *el, GpBox *margin)
{
  GeDisjoint *lines= el;
  margin->xmin= margin->xmax= margin->ymin= margin->ymax=
    0.5*lines->l.width*DEFAULT_LINE_WIDTH;
}

static void TextMargin(void *el, GpBox *margin)
{
  /* The actual text box cannot be computed without text metric data--
     the following is a crude guess based on character counts and an
     assumed width/height ratio of 0.6 (as in 9x15) and descent/height
     ratio of 0.2.  This should be close for Courier, but it probably
     way off in width for the proportional fonts.  */
  GeText *text= el;
  GpReal width, x0, y0, dx, dy;
  int alignH, alignV;
  int nLines= GtTextShape(text->text, &text->t, (WidthFunction)0, &width);

  dx= text->t.height*width*0.6;
  dy= text->t.height*((GpReal)nLines);

  GtGetAlignment(&text->t, &alignH, &alignV);
  if (alignH==TH_LEFT) {
    x0= 0.0;
  } else if (alignH==TH_CENTER) {
    x0= -0.5*dx;
  } else {
    x0= -dx;
  }
  if (alignV==TV_TOP || alignV==TV_CAP) {
    y0= -dy;
  } else if (alignH==TV_HALF) {
    y0= -0.1*text->t.height - 0.5*dy;
  } else if (alignH==TV_BASE) {
    y0= -0.2*text->t.height;
  } else {
    y0= 0.0;
  }

  if (gistA.t.orient==TX_RIGHT) {
    margin->xmin= x0;
    margin->xmax= x0 + dx;
    margin->ymin= y0;
    margin->ymax= y0 + dy;
  } else if (gistA.t.orient==TX_LEFT) {
    margin->xmin= x0 - dx;
    margin->xmax= x0;
    margin->ymin= y0 - dy;
    margin->ymax= y0;
  } else if (gistA.t.orient==TX_UP) {
    margin->xmin= y0;
    margin->xmax= y0 + dy;
    margin->ymin= x0;
    margin->ymax= x0 + dx;
  } else {
    margin->xmin= y0 - dy;
    margin->xmax= y0;
    margin->ymin= x0 - dx;
    margin->ymax= x0;
  }
}

static void MeshMargin(void *el, GpBox *margin)
{
  GeMesh *mesh= el;
  margin->xmin= margin->xmax= margin->ymin= margin->ymax=
    0.5*mesh->l.width*DEFAULT_LINE_WIDTH;
}

/* ARGSUSED */
static void VectorsMargin(void *el, GpBox *margin)
{
  /* This is a wild guess-- otherwise must scan (u, v) --
     should never arise in practice */
  /* GeVectors *vec= el; */
  margin->xmin= margin->xmax= margin->ymin= margin->ymax= 0.05;
}

static void ContoursMargin(void *el, GpBox *margin)
{
  /* Should never actually happen */
  GeContours *con= el;
  margin->xmin= margin->xmax= margin->ymin= margin->ymax=
    0.5*con->l.width*DEFAULT_LINE_WIDTH;
}

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