/* $Header: /afs/athena.mit.edu/astaff/project/atdev/src/plotter/RCS/XYErrorPlot.c,v 3.5 95/05/18 12:53:12 dot Exp Locker: dot $ */

/*******************************************************************
  Copyright (C) 1990 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.

***************************************************************** */

#include <stdio.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#ifdef _AtDevelopment_
#include "Scale.h"    
#include "XYErrorPlotP.h"
#include "AtConverters.h"
#else
#include <At/Scale.h>
#include <At/XYErrorPlotP.h>
#include <At/AtConverters.h>
#endif

#ifdef SYSV
#define bcopy(s1, s2, l) memcpy (s2, s1, l)
#endif /* SYSV */

#define SUPERCLASS &atXYPlotClassRec

#define offset(field) XtOffset(AtXYErrorPlotWidget, field)
static XtResource resources[] = {
  { XtNpercentError, XtCPercentError, XtRDouble, sizeof(double),
      offset(xyerrorplot.percentError), XtRString, "0.0" },
  { XtNerrorsArePercentages, XtCErrorsArePercentages, XtRBoolean,
      sizeof(Boolean), offset(xyerrorplot.errorsArePercentages),
      XtRImmediate, (caddr_t) True },
  { XtNerrorsAreAbsolute, XtCErrorsAreAbsolute, XtRBoolean,
      sizeof(Boolean), offset(xyerrorplot.errorsAreAbsolute),
      XtRImmediate, (caddr_t) False },
  { XtNerrorAbove, XtCErrorAbove, XtRDoubleArray, sizeof(double *),
      offset(xyerrorplot.errorAbove), XtRImmediate, NULL },
  { XtNerrorBelow, XtCErrorBelow, XtRDoubleArray, sizeof(double *),
      offset(xyerrorplot.errorBelow), XtRImmediate, NULL },
  { XtNerrorBarWidth, XtCErrorBarWidth, XtRDimension, sizeof(Dimension),
      offset(xyerrorplot.errorBarWidth), XtRImmediate, (caddr_t) 4 },
};
#undef offset

static void ClassInitialize(WidgetClass);
static void Initialize(AtXYErrorPlotWidget, AtXYErrorPlotWidget);
static void Destroy(AtXYErrorPlotWidget);
static Boolean SetValues(AtXYErrorPlotWidget, AtXYErrorPlotWidget,
			 AtXYErrorPlotWidget);
static void ComputeError(AtXYErrorPlotWidget);
static void Draw(Display *, Window, AtPlotWidget, AtScale *, AtScale *,
		 Region);
static void Recompute(AtPlotWidget, AtScale *, AtScale *);
static void DrawPS(FILE *, AtPlotWidget, AtScale * , AtScale *);
static void DrawIcon(Display *, Window, AtPlotWidget, int, int, int, int);
static void DrawIconPS(FILE *, AtPlotWidget, int, int, int, int);

AtXYErrorPlotClassRec atXYErrorPlotClassRec = {
  { /* core part */
    /* superclass         */    (WidgetClass) SUPERCLASS,
    /* class_name         */    "AtXYErrorPlot",
    /* widget_size        */    sizeof(AtXYErrorPlotRec),
    /* class_initialize   */    ClassInitialize,
    /* class_part_initialize*/  NULL,
    /* class_inited       */    FALSE,
    /* initialize         */    (XtInitProc) Initialize,
    /* initialize_hook    */    NULL,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* pad                */    0,
    /* resources          */    resources,
    /* num_resources      */    XtNumber(resources),
    /* xrm_class          */    NULLQUARK,
    /* pad                */    FALSE,
    /* pad                */    FALSE,
    /* pad                */    FALSE,
    /* pad                */    FALSE,
    /* destroy            */    (XtWidgetProc)Destroy,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* set_values         */    (XtSetValuesFunc) SetValues,
    /* set_values_hook    */    NULL,
    /* pad                */    NULL,
    /* get_values_hook    */    NULL,
    /* pad                */    NULL,
    /* version            */    XtVersion,
    /* callback_offsets   */    NULL,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* extension            */  NULL
  },
      /* AtPlotClassPart initialization */
  {
    /* draw()       */   Draw,
    /* drawIcon()   */   DrawIcon,
    /* resize()     */   Recompute,
    /* rescale()    */   Recompute,
    /* drawPS()     */	 DrawPS,
    /* drawIconPS() */   DrawIconPS,
    /* checkhit()   */   NULL,
  },
};

WidgetClass atXYErrorPlotWidgetClass = (WidgetClass) &atXYErrorPlotClassRec;

#define AtWarning(w,msg) XtWarning(msg)

/**********************************************************************/

static void ClassInitialize(WidgetClass class)
{
  AtRegisterDoubleConverter();
}

/**********************************************************************/

static void SetBoundingBox(AtXYErrorPlotWidget w)
{
  double xmin, ymin, xmax, ymax;
  int i, halfwidth;
  Arg arg;
  BoundingBox b;
  
  if (w->xyplot.numPoints == 0) {
    xmin = ymin = xmax = ymax = 0.0;
  }
  else {
    xmin = xmax = w->xyplot.xpts[0];
    for(i = 1; i < w->xyplot.numPoints; i++) {
      if (w->xyplot.xpts[i] < xmin)
	xmin = w->xyplot.xpts[i];
      if (w->xyplot.xpts[i] > xmax)
	xmax = w->xyplot.xpts[i];
    }
    halfwidth = w->xyerrorplot.errorBarWidth / 2;
    xmin -= halfwidth;
    xmax += w->xyerrorplot.errorBarWidth - halfwidth;
    
    ymin = w->xyerrorplot.belowpts[0];
    ymax = w->xyerrorplot.abovepts[0];
    for(i = 1; i < w->xyplot.numPoints; i++) {
      if (w->xyerrorplot.belowpts[i] < ymin)
	ymin = w->xyerrorplot.belowpts[i];
      if (w->xyerrorplot.abovepts[i] > ymax)
	ymax = w->xyerrorplot.abovepts[i];
    }
  }
  
  b.xmin = xmin;
  b.ymin = ymin;
  b.xmax = xmax;
  b.ymax = ymax;
  XtSetArg(arg, XtNboundingBox, &b);
  XtSetValues(w, &arg, 1);
}

/**********************************************************************/

static void Initialize(AtXYErrorPlotWidget request, AtXYErrorPlotWidget new)
{
  /*** XXXX should do some error checking here like in XYPlot ***/

  if (new->xyerrorplot.percentError == 0.0)  {
    new->xyerrorplot.errabove = (double *)
      XtMalloc(sizeof(double) * new->xyplot.numPoints);
    bcopy((char *) new->xyerrorplot.errorAbove, 
	  (char *) new->xyerrorplot.errabove,
	  sizeof(double) * new->xyplot.numPoints);
    
    if (new->xyerrorplot.errbelow != NULL)  {
      new->xyerrorplot.errbelow = (double *) 
	XtMalloc(sizeof(double) * new->xyplot.numPoints);
      bcopy((char *) new->xyerrorplot.errorBelow, 
	    (char *) new->xyerrorplot.errbelow,
	    sizeof(double) * new->xyplot.numPoints);
    }
    else
      new->xyerrorplot.errbelow = NULL;
  }
  else  {
    new->xyerrorplot.errabove = NULL;
    new->xyerrorplot.errbelow = NULL;
  }
  
  new->xyerrorplot.abovepts = (double *)
    XtMalloc(sizeof(double) * new->xyplot.numPoints);
  new->xyerrorplot.belowpts = (double *)
    XtMalloc(sizeof(double) * new->xyplot.numPoints);
  new->xyerrorplot.abovepix = (XPoint *) 
    XtCalloc(new->xyplot.numPoints, sizeof(XPoint));
  new->xyerrorplot.belowpix = (XPoint *) 
    XtCalloc(new->xyplot.numPoints, sizeof(XPoint));
  
  new->xyerrorplot.valid = False;
  
  ComputeError(new);
  /* figure out our bounding box */
  SetBoundingBox(new);
}

/**********************************************************************/

static void Destroy(AtXYErrorPlotWidget w)
{
  XtFree(w->xyerrorplot.errabove);
  XtFree(w->xyerrorplot.errbelow);
  XtFree(w->xyerrorplot.abovepts);
  XtFree(w->xyerrorplot.belowpts);
  XtFree(w->xyerrorplot.abovepix);
  XtFree(w->xyerrorplot.belowpix);
}

/**********************************************************************/

static Boolean SetValues(AtXYErrorPlotWidget current,
			 AtXYErrorPlotWidget request,
			 AtXYErrorPlotWidget new)
{
#define Changed(field) (new->xyerrorplot.field != current->xyerrorplot.field)
  Boolean compute = False, abovechanged = False, belowchanged = False;
  int i;

  if (Changed(errorBarWidth))
    new->plot.redisplay = True;
  
  if (Changed(percentError) || Changed(errorsArePercentages) ||
      Changed(errorsAreAbsolute))
    compute = True;

  /* check errorAbove; not just the pointer but the values too */
  if (Changed(errorAbove))
    abovechanged = True;
  else
    for (i = 0; i < new->xyplot.numPoints; i++)
      if (current->xyerrorplot.errabove[i] != new->xyerrorplot.errorAbove[i])  {
	abovechanged = True;
	break;
      }
  /* check errorBelow; not just the pointer but the values too */
  if (Changed(errorBelow))
    belowchanged = True;
  else
    for (i = 0; i < new->xyplot.numPoints; i++)
      if (current->xyerrorplot.errbelow[i] != new->xyerrorplot.errorBelow[i])  {
	belowchanged = True;
	break;
      }

  /* Need to check XYPlot's numPoints resource, because if it changed,
     we have to resize the error arrays and reread them. */
  if (current->xyplot.numPoints != new->xyplot.numPoints)  {
    new->xyerrorplot.errabove = (double *) 
      XtRealloc(new->xyerrorplot.errabove, new->xyplot.numPoints * 
		sizeof(double));
    new->xyerrorplot.errbelow = (double *) 
      XtRealloc(new->xyerrorplot.errbelow, new->xyplot.numPoints * 
		sizeof(double));
    new->xyerrorplot.abovepts = (double *) 
      XtRealloc(new->xyerrorplot.abovepts, new->xyplot.numPoints * 
		sizeof(double));
    new->xyerrorplot.belowpts = (double *) 
      XtRealloc(new->xyerrorplot.belowpts, new->xyplot.numPoints * 
		sizeof(double));
    new->xyerrorplot.abovepix = (XPoint *)
      XtRealloc(new->xyerrorplot.abovepix, new->xyplot.numPoints * 
		sizeof(XPoint));
    new->xyerrorplot.belowpix = (XPoint *) 
      XtRealloc(new->xyerrorplot.belowpix, new->xyplot.numPoints * 
		sizeof(XPoint));
    abovechanged = belowchanged = True;
  }

  /* Also need to check XYPlot's yData resource, because if the y values
     change, our errorbars will change as well.  Unfortunately, we can't
     because the copied ydata has already been changed to mirror the new
     value.  As a (not so great) substitute, check to see if xyplot's
     valid flag is False.  */

  if (new->xyplot.valid = False)
    compute = True;

  if (abovechanged)  {
    bcopy((char *) new->xyerrorplot.errorAbove, 
	  (char *) new->xyerrorplot.errabove,
	  sizeof(double) * new->xyplot.numPoints);
    compute = True;
  }
  if (belowchanged)  {
    bcopy((char *) new->xyerrorplot.errorBelow, 
	  (char *) new->xyerrorplot.errbelow,
	  sizeof(double) * new->xyplot.numPoints);
    compute = True;
  }

  if (compute)  {
    new->xyerrorplot.valid = False;
    ComputeError(new);
    SetBoundingBox(new);
    new->plot.redisplay = True;
  }

  if (((new->plot.redisplay) || (new->xyplot.refresh)) &&
      (new->object.widget_class == atXYErrorPlotWidgetClass)) {
    Arg a;
    new->plot.redisplay = new->xyplot.refresh = False;
    XtSetArg(a, XtNneedsRedraw, True);
    XtSetValues(new, &a, 1);        /* set a constraint resource */
  }

  return False;
#undef Changed    
}

/**********************************************************************/
/**********************************************************************/

static void ComputeError(AtXYErrorPlotWidget w)
{
  int i;
  double errpercent;

  /* if percentError is non-zero, it is used for all points */
  if (w->xyerrorplot.percentError != 0.0)  {
    errpercent = w->xyerrorplot.percentError / 100.0;
    for (i = 0; i < w->xyplot.numPoints; i++)  {
      w->xyerrorplot.belowpts[i] = w->xyplot.ypts[i] * (1.0 - errpercent);
      w->xyerrorplot.abovepts[i] = w->xyplot.ypts[i] * (1.0 + errpercent);
    }
  }
  else if (w->xyerrorplot.errorsArePercentages)  {
    if (w->xyerrorplot.errorBelow == NULL)
      for (i = 0; i < w->xyplot.numPoints; i++)  {
	errpercent = w->xyerrorplot.errorAbove[i] / 100.0;
	w->xyerrorplot.belowpts[i] = w->xyplot.ypts[i] * (1.0 - errpercent);
	w->xyerrorplot.abovepts[i] = w->xyplot.ypts[i] * (1.0 + errpercent);
      }
    else
      for (i = 0; i < w->xyplot.numPoints; i++)  {
	errpercent = w->xyerrorplot.errorAbove[i] / 100.0;
	w->xyerrorplot.abovepts[i] = w->xyplot.ypts[i] * (1.0 + errpercent);
	errpercent = w->xyerrorplot.errorBelow[i] / 100.0;
	w->xyerrorplot.belowpts[i] = w->xyplot.ypts[i] * (1.0 - errpercent);
      }
  }
  else if (w->xyerrorplot.errorsAreAbsolute)  {
    /* it's an error for errorBelow to be NULL in this case */
      bcopy(w->xyerrorplot.errorAbove, w->xyerrorplot.abovepts,
	    w->xyplot.numPoints * sizeof(double));
      bcopy(w->xyerrorplot.errorBelow, w->xyerrorplot.belowpts,
	    w->xyplot.numPoints * sizeof(double));
  }
  else  {
    if (w->xyerrorplot.errorBelow == NULL)
      for (i = 0; i < w->xyplot.numPoints; i++)  {
	w->xyerrorplot.belowpts[i] = w->xyplot.ypts[i] -
	  w->xyerrorplot.errorAbove[i];
	w->xyerrorplot.abovepts[i] = w->xyplot.ypts[i] +
	  w->xyerrorplot.errorAbove[i];
      }
    else
      for (i = 0; i < w->xyplot.numPoints; i++)  {
	w->xyerrorplot.belowpts[i] = w->xyplot.ypts[i] -
	  w->xyerrorplot.errorBelow[i];
	w->xyerrorplot.abovepts[i] = w->xyplot.ypts[i] +
	  w->xyerrorplot.errorAbove[i];
      }
  }
}

/**********************************************************************/

static void Recompute(AtPlotWidget plot, AtScale *xscale, AtScale *yscale)
{
  AtXYErrorPlotWidget p = (AtXYErrorPlotWidget) plot;
  int i;

  /* Call XYPlot's resize method first, because we need to use
     the resized XYPlots pixpts for the error bars */

  (*((SUPERCLASS)->plot_class.resize))(plot, xscale, yscale);

  for(i = 0; i < p->xyplot.numPoints; i++){
    p->xyerrorplot.belowpix[i].x = p->xyplot.pixpts[i].x;
    p->xyerrorplot.belowpix[i].y = 
      AtScaleUserToPixel(yscale, p->xyerrorplot.belowpts[i]);
    p->xyerrorplot.abovepix[i].x = p->xyplot.pixpts[i].x;
    p->xyerrorplot.abovepix[i].y = 
      AtScaleUserToPixel(yscale, p->xyerrorplot.abovepts[i]);
  }
  
  p->xyerrorplot.valid = True;
}

/**********************************************************************/

static void Draw(Display *dpy, Window win, AtPlotWidget plot,
		 AtScale *xscale, AtScale *yscale, Region region)
{
  AtXYErrorPlotWidget p = (AtXYErrorPlotWidget) plot;
  int i;
  int clipx, clipy;
  static GC tmpGc = NULL;
  Pixmap clip_mask;
  XRectangle clip_rect;
  int leftside, rightside;
  XSegment lines[3];
  XGCValues values;
  unsigned long valueMask;

  /* Call XYPlot's draw method first, before we do anything. */
  /* We'll draw on top of the completed XYPlot.              */

  (*((SUPERCLASS)->plot_class.draw))(dpy, win, plot, xscale, yscale, region);
  
  if (p->xyerrorplot.valid == False)
    Recompute(plot, xscale, yscale);

  if (tmpGc == (GC)NULL) {
    tmpGc = XCreateGC (dpy, win, 0, NULL);
  }
  valueMask = GCClipXOrigin | GCClipYOrigin | GCClipMask;
  XCopyGC (dpy, p->xyplot.solidGc, valueMask, tmpGc);

  if (region == NULL)  {
    clip_rect.x = AtScaleGetLowPix(xscale);
    clip_rect.y = AtScaleGetHighPix(yscale);
    clip_rect.width = AtScaleGetHighPix(xscale) - clip_rect.x;
    clip_rect.height = AtScaleGetLowPix(yscale) - clip_rect.y;
    XSetClipRectangles(dpy, p->xyplot.solidGc,0,0,&clip_rect, 1, Unsorted);
  }
  else
    XSetRegion(dpy, p->xyplot.solidGc, region);

  /* Now actually draw the error bars */
  leftside = p->xyerrorplot.errorBarWidth / 2;
  rightside = p->xyerrorplot.errorBarWidth - leftside;

  for (i = 0; i < p->xyplot.numPoints; i++)  {
    /* only draw the error bars if the error is non-zero */
    if (p->xyerrorplot.abovepix[i].y != p->xyerrorplot.belowpix[i].y)  {
      lines[0].x1 = p->xyerrorplot.abovepix[i].x - leftside;
      lines[0].y1 = p->xyerrorplot.abovepix[i].y;
      lines[0].x2 = p->xyerrorplot.abovepix[i].x + rightside;
      lines[0].y2 = p->xyerrorplot.abovepix[i].y;
      lines[1].x1 = p->xyerrorplot.belowpix[i].x - leftside;
      lines[1].y1 = p->xyerrorplot.belowpix[i].y;
      lines[1].x2 = p->xyerrorplot.belowpix[i].x + rightside;
      lines[1].y2 = p->xyerrorplot.belowpix[i].y;
      lines[2].x1 = p->xyerrorplot.abovepix[i].x;
      lines[2].y1 = p->xyerrorplot.abovepix[i].y;
      lines[2].x2 = p->xyerrorplot.belowpix[i].x;
      lines[2].y2 = p->xyerrorplot.belowpix[i].y;
      XDrawSegments(dpy, win, p->xyplot.solidGc, lines, 3);
    }
  }
    
  /* now restore the clip region */
  /* We depend on valueMask being unchanged */
  XCopyGC (dpy, tmpGc, valueMask, p->xyplot.solidGc);
}

/**********************************************************************/

static void DrawPS(FILE *f, AtPlotWidget plot, AtScale *xs, AtScale *ys)
{
  AtXYErrorPlotWidget p = (AtXYErrorPlotWidget) plot;
  int i;
  double leftside, rightside;

  /* Call XYPlot's drawPS method first, before we do anything.   */
  /* We'll draw on top of the completed XYPlot PS rendering.     */

  (*((SUPERCLASS)->plot_class.drawPS))(f, plot, xs, ys);

  /*** XXX This means 1 pixel on the screen equals one point on paper. ***/
  leftside = (double) (p->xyerrorplot.errorBarWidth / 2);
  rightside = (double) (p->xyerrorplot.errorBarWidth - leftside);
  
  /* adjust scales so I get greater accuracy */
  AtScaleResize(xs, AtScaleGetLowPix(xs)*100, AtScaleGetHighPix(xs)*100);
  AtScaleResize(ys, AtScaleGetLowPix(ys)*100, AtScaleGetHighPix(ys)*100);

  fprintf(f, "%%%% BeginObject: AtXYErrorPlot\ngsave\n");

  for(i=0; i < p->xyplot.numPoints; i++)
    if (p->xyerrorplot.abovepts[i] != p->xyerrorplot.belowpts[i])  {
      double xpt = AtScaleUserToPixel(xs, p->xyplot.xpts[i]) / 100.0;
      double abovept = 
	AtScaleUserToPixel(ys, p->xyerrorplot.abovepts[i]) / 100.0;
      double belowpt = 
	AtScaleUserToPixel(ys, p->xyerrorplot.belowpts[i]) / 100.0;
      double left = xpt - leftside;
      double right = xpt + rightside;
      fprintf(f, "%g %g M\n", left, abovept);
      fprintf(f, "%g %g L\n", right, abovept);
      fprintf(f, "%g %g M\n", left, belowpt);
      fprintf(f, "%g %g L\n", right, belowpt);
      fprintf(f, "%g %g M\n", xpt, abovept);
      fprintf(f, "%g %g L\n", xpt, belowpt);
      if ((i % 15) == 0)
	fprintf(f, "stroke\n");
    }
  fprintf(f, "stroke\n");
  fprintf(f, "grestore\n%%%% EndObject\n");

  /* adjust scales back to original value */
  AtScaleResize(xs, AtScaleGetLowPix(xs)/100, AtScaleGetHighPix(xs)/100);
  AtScaleResize(ys, AtScaleGetLowPix(ys)/100, AtScaleGetHighPix(ys)/100);
}

/**********************************************************************/

static void DrawIcon(Display *dpy, Window win, AtPlotWidget pw, int x, 
		     int y, int width, int height)

{
  AtXYErrorPlotWidget p = (AtXYErrorPlotWidget) pw;
  XSegment lines[3];
  int leftside, rightside, center, top, bottom;

  /* Call XYPlot's drawicon method first, before we do anything. */
  /* We'll draw on top of the completed XYPlot icon.             */

  (*((SUPERCLASS)->plot_class.drawIcon))(dpy, win, pw, x, y, width, height);

  /* now draw an error bar */
  leftside = p->xyerrorplot.errorBarWidth / 2;
  rightside = p->xyerrorplot.errorBarWidth - leftside;
  center = x + (width / 2);
  top = y + 1;
  bottom = y + height - 1;

  lines[0].x1 = center - leftside;
  lines[0].y1 = top;
  lines[0].x2 = center + rightside;
  lines[0].y2 = top;
  lines[1].x1 = lines[0].x1;
  lines[1].y1 = bottom;
  lines[1].x2 = lines[0].x2;
  lines[1].y2 = bottom;
  lines[2].x1 = center;
  lines[2].y1 = top;
  lines[2].x2 = center;
  lines[2].y2 = bottom;
  XDrawSegments(dpy, win, p->xyplot.solidGc, lines, 3);

}

/**********************************************************************/

static void DrawIconPS(FILE *f, AtPlotWidget pw, int x, int y, 
		       int width, int height)

{
  AtXYErrorPlotWidget p = (AtXYErrorPlotWidget) pw;
  int leftside, rightside, center, left, right, top, bottom;
  /* Call XYPlot's drawIconPS method first, before we do anything.   */
  /* We'll draw on top of the completed XYPlot Icon PS rendering.    */

  (*((SUPERCLASS)->plot_class.drawIconPS))(f, pw, x, y, width, height);

  leftside = p->xyerrorplot.errorBarWidth / 2;
  rightside = p->xyerrorplot.errorBarWidth - leftside;
  center = x + (width / 2);
  left = center - leftside;
  right = center + rightside;
  top = y - 1;
  bottom = y - height + 1;
  fprintf(f, "%%%% BeginObject: AtXYErrorPlot icon\ngsave\n");

  fprintf(f, "%d %d M\n", left, top);
  fprintf(f, "%d %d L\n", right, top);
  fprintf(f, "%d %d M\n", left, bottom);
  fprintf(f, "%d %d L\n", right, bottom);
  fprintf(f, "%d %d M\n", center, top);
  fprintf(f, "%d %d L\n", center, bottom);
  fprintf(f, "stroke\n");
  fprintf(f, "grestore\n%%%% EndObject\n");
}



