/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


/*
 * $Header: /home/gda/dxcvs/dx/src/exec/dxmods/caption.c,v 1.3 1999/05/10 15:45:22 gda Exp $
 */


#include <string.h>
#include <dx/dx.h>
#include <math.h>

#define FLAG_VIEWPORT 0
#define FLAG_PIXEL    1
#define ABS(a)      ((a)>0.0 ? (a) : -(a))


static
Object
Caption(char **s, int n, Point p, int flag, Point ref, double align,
	double h, char *f, Vector x, Vector y)
{
    Matrix m;
    Vector z;
    Object a[100], b=NULL, c=NULL, o=NULL, oo=NULL, font;
    float ascent, descent, widths[100], width, height, lead=.133;
    Group g=NULL;
    int i, vertical;
    float xr, yr, xMin, xMax, yMin, yMax;
    Vector xlate;

    /* check vectors */
    if (DXLength(x)==0) {
	DXSetError(ERROR_BAD_PARAMETER, "#11822", "direction");
        return ERROR;
    }
    if (DXLength(y)==0) {
	DXSetError(ERROR_BAD_PARAMETER, "#11822","up");
        return ERROR;
    }

    /* for error recovery */
    for (i=0; i<n; i++)
	a[i] = NULL;

    /* interpret flag */
    if (flag==FLAG_VIEWPORT)
	flag = SCREEN_VIEWPORT;
    else if (flag==FLAG_PIXEL)
	flag = SCREEN_PIXEL;
    else {
	DXSetError(ERROR_BAD_PARAMETER, "#10070", "flag");
        return ERROR;
    }

    /* uses cache */
    if (!f)
	f = "variable";
    font = DXGetFont(f, &ascent, &descent);
    if (!font) {
        DXSetError(ERROR_INVALID_DATA,"cannot open %s font", f); 
	goto error;
    }
    /* group to hold caption(s) */
    g = DXNewGroup();
    if (!g) goto error;

    /* generate the caption(s), determine overall width,height */
    /* width is the total length of the caption */
    for (i=0, width=0; i<n; i++) {
	a[i] = DXGeometricText(s[i], font, &widths[i]);
	if (!a[i]) goto error;
	if (widths[i] > width)
	    width = widths[i];
    }

    /* the height is the total letter height */
    height = n*(ascent+descent) + (n-1)*lead;

    x = DXNormalize(x);
    if (ABS(x.x) < ABS(x.y)) 
      vertical=1;
    else
      vertical=0;
    y = DXNormalize(y);
    z = DXNormalize(DXCross(x, y));

    {
	float r = sqrt(x.x*x.x + x.y*x.y);
	xr = x.x / r;
	yr = x.y / r;
    }

#define MINMAX(a,b)		\
{ float xx = (a)*xr - (b)*yr;	\
  float yy = (a)*yr + (b)*xr;	\
  if (xx > xMax) xMax = xx;	\
  if (yy > yMax) yMax = yy;	\
  if (xx < xMin) xMin = xx;	\
  if (yy < yMin) yMin = yy;	\
}

    xMin = yMin = DXD_MAX_FLOAT;
    xMax = yMax = -DXD_MAX_FLOAT;
    MINMAX(0, -descent);
    MINMAX(width, -descent);
    MINMAX(width, height-descent);
    MINMAX(0, height-descent);

#undef MINMAX

    xlate.x = ((xMin + ref.x*(xMax-xMin))*h);
    xlate.y = ((yMin + ref.y*(yMax-yMin))*h);

    /* place the captions, put them in the group */
    for (i=0; i<n; i++) {
	float xx, yy;
	float X = (align*(width - widths[i]))*h;
	float Y = (((n-1)-i)*(ascent+descent+lead))*h;

	xx = (X*xr + Y*(-yr)) - xlate.x;
	yy = (X*yr + Y*xr) - xlate.y;

        /* compute the transform */
	m.A[0][0] = x.x*h;  m.A[0][1] = x.y*h;  m.A[0][2] = x.z*h;
	m.A[1][0] = y.x*h;  m.A[1][1] = y.y*h;  m.A[1][2] = y.z*h;
	m.A[2][0] = z.x*h;  m.A[2][1] = z.y*h;  m.A[2][2] = z.z*h;

        m.b[0] = xx;
        m.b[1] = yy;
        m.b[2] = 0; 

	/* create the object */
	b = (Object)DXNewXform(a[i], m);
	if (!b) goto error;
	a[i] = NULL;
	c = (Object)DXNewScreen(b, flag, 1);
	if (!c) goto error;
	b = NULL;
	if (!DXSetMember(g, NULL, c)) goto error;
	c = NULL;
    }

    /* overall position */
    o = (Object)DXNewXform((Object)g, DXTranslate(p));
    if (!o) goto error;
    g = NULL;

    /* make it immovable */
    oo = (Object)DXNewScreen(o, SCREEN_STATIONARY, 0);
    if (!oo) goto error;

    /* all ok */
    DXDelete(font);
    return oo;

error:
    for (i=0; i<n; i++)
	DXDelete(a[i]);
    DXDelete(b);
    DXDelete(c);
    DXDelete(o);
    DXDelete((Object)g);
    DXDelete(font);
    return NULL;
}

static Error
getvec(Object o, Vector *v)
{
    int rc = DXExtractParameter(o, TYPE_FLOAT, 3, 1, (Pointer)v)
	  || DXExtractParameter(o, TYPE_FLOAT, 2, 1, (Pointer)v);
    v->z = 0;
    return rc;
}

Error
m_Caption(Object *in, Object *out)
{
  Vector x, y;
  Point p, ref;
  Array positionobject=NULL;
  char *s[100], *f, tmpstring[1000], buf[1000], *s1[100], *newstring;
  float h, align, *pptr;
  int flag, n, vertical, horizontal, i, j, k, count=0;
  
  /* string(s) */
  if (!in[0]) {
    DXSetError(ERROR_BAD_PARAMETER, "#10000", "string");
    goto error;
  } else {
    for (n=0; n<100; n++)
      if (!DXExtractNthString(in[0], n, &s[n]))
	break;
    if (!n) {
      DXSetError(ERROR_BAD_PARAMETER, "#10200","string");
      goto error;
    }
  }	
  
  /* look for newlines in the string */
  count = 0;
  for (i=0; i<n; i++) {
    strcpy(tmpstring, s[i]);
    j=0;
    k=0;
    while (tmpstring[j] != '\0') {
      if (tmpstring[j]=='\n') {
	buf[k]='\0';
        newstring = (char *)DXAllocate(strlen(buf)+1);
        strcpy(newstring, buf);
	s1[count]=newstring; 
	k=0;
        count++;
      }
      else { 
	buf[k] = tmpstring[j];
	k++; 
      }
      j++;
    }
    buf[k]='\0';
    newstring = (char *)DXAllocate(strlen(buf)+1);
    strcpy(newstring, buf);
    s1[count]=newstring;
    count++;
  }

  /* position */
  p = DXPt(.5,.05,0);
  if (in[1] && !getvec(in[1], &p)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10231","position", 2, 3);
    goto error;
  }
  
  /* flag */
  flag = FLAG_VIEWPORT;
  if (in[2] && !DXExtractInteger(in[2], &flag)) {
    /* flag must be either 0 or 1 */
    DXSetError(ERROR_BAD_PARAMETER, "#10070", "flag");
    goto error;
  }
  
  /* reference point */
  ref = (flag==FLAG_VIEWPORT? p : DXPt(0,0,0));
  if (in[3] && !getvec(in[3], &ref)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10231","reference", 2, 3);
    goto error;
  }
  
  /* baseline */
  x = DXVec(1,0,0);
  if (in[7] && !getvec(in[7], &x)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10231","direction", 2, 3);
    goto error;
  }
  
  
  /* should depend on whether the captions are vertical or 
     horizontal */
  x = DXNormalize(x);
  horizontal = 0;
  vertical = 0;
  if ((x.x == 0) && (x.y == 1))
    vertical=1;
  if ((x.y == 0) && (x.x == 1))
    horizontal=1;
  /* alignment for multi-line captions */
  /* if truly vertical, use ref.y, if truly horizontal use ref.x,
     else use 0 */
  align = (flag==FLAG_VIEWPORT? (vertical ? ref.y : 
				 (horizontal ? ref.x : 0)) : 0);
  if (in[4] && !DXExtractFloat(in[4], &align)) {
    /* alignment must be a scalar value */
    DXSetError(ERROR_BAD_PARAMETER, "#10080","alignment");
    goto error;
  }
  
  /* height */
  h = 15;
  if (in[5] && !DXExtractFloat(in[5], &h)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10080", "height");
    goto error;
  }
  
  /* font */
  f = NULL;
  if (in[6] && !DXExtractString(in[6], &f)) {
    /* font must be a string */
    DXSetError(ERROR_BAD_PARAMETER, "#10200", "font");
    goto error;
  }
  
  
  /* up */
  y = DXVec(-x.y,x.x,0);
  if (in[8] && !getvec(in[8], &y)) {
    DXSetError(ERROR_BAD_PARAMETER, "#10231", "up", 2, 3);
    goto error;
  }
  
  /* do it */
  out[0] = Caption(s1, count, p, flag, ref, align, h, f, x, y);
  if (!out[0])
    goto error;
  /* apply the original string as an attribute */
  if (!DXSetAttribute(out[0], "Caption string", in[0]))
    goto error;
  /* apply the caption position as an attribute */
  positionobject = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 2);
  if (!DXAddArrayData(positionobject, 0, 1, NULL))
    goto error;
  pptr = DXGetArrayData(positionobject);
  if (!pptr) goto error;
  pptr[0] = p.x;
  pptr[1] = p.y;
  if (!DXSetAttribute(out[0], "Caption position", (Object)positionobject))
    goto error;
  /* apply the caption flag as an attribute */
  if (!DXSetIntegerAttribute(out[0], "Caption flag", flag))
    goto error;
 
  for (i=0; i<count; i++) {
     DXFree((Pointer)s1[i]);
  }
  return OK;
error:
  for (i=0; i<count; i++) {
     DXFree((Pointer)s1[i]);
  }
  return ERROR;
}
