/***********************************************************************/
/* 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>


#include <stdio.h>
#include <dx/dx.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

#if  defined(DXD_NON_UNIX_DIR_SEPARATOR)
#define DX_DIR_SEPARATOR ';'
#else
#define DX_DIR_SEPARATOR ':'
#endif


static Error verifyfont(Group font, char *);
static Error getname(char *dirlist, char *file, char *result);

/* changed.  used to read old proprietary format; now uses import
 *  to read a normal .dx format file.
 *
 * a font is a group of 256 fields, one for each character value,
 * with 2 attributes attached to the group: 
 *  "font ascent" which is a single float of max height above baseline
 *  "font descent" which is a single positive float of max depth below baseline
 * (these are for all chars combined in this font)  the combined height
 * should be 1.0
 *
 * each field must have 2d or 3d positions and either line or triangle 
 * connections, and have one attribute:
 *  "char width" which is a float for the spacing of this char
 * eventually the points or connections can have colors and/or normals.
 */

Object
DXGetFont(char *name, float *ascent, float *descent)
{
    Group font = NULL;
    char *tbuf = NULL;
    char *dir = NULL;
    float fm;

    if (!name)
	return NULL;

    /* look in cache first */
    font = (Group)DXGetCacheEntry(name, 0, 0);
    if (font)
	goto done;

    /* where to look */
    dir = (char *) getenv("DXFONTS");
    if (!dir)
	dir = (char *) getenv("DXEXECROOT");
    if (!dir)
	dir = (char *) getenv("DXROOT");
    if (!dir)
	dir = "/usr/lpp/dx";

#define XTRA 32  /* enough extra room for "../fonts/.. .dx" */    

    tbuf = (char *)DXAllocateLocalZero(strlen(dir) + strlen(name) + XTRA);
    if (!tbuf)
	goto error;
    
    /* try the places listed in the environment variable.
     *  the filename to open is returned in tbuf.
     */
    if (!getname(dir, name, tbuf))
	goto error;

    /* try to open the file */
    font = (Group)DXImportDX(tbuf, NULL, NULL, NULL, NULL);
    if (!font) {
	DXAddMessage("as font file");
	goto error;
    }

    /* make sure the object structure is ok */
    if (!verifyfont(font, name))
	goto error;

    /* put in cache */
    DXReference((Object)font);
    if (!DXSetCacheEntry((Object)font, 0.0, name, 0, 0))
	goto error;
    
  done:
    /* return the info if the caller asks for it */
    if (!DXGetFloatAttribute((Object)font, "font ascent", &fm)) {
	DXSetError(ERROR_BAD_PARAMETER, "#10810", name);
	goto error;
    }
    if (ascent)
	*ascent = fm;
    
    if (!DXGetFloatAttribute((Object)font, "font descent", &fm)) {
	DXSetError(ERROR_BAD_PARAMETER, "#10812", name);
	goto error;
    }
    if (descent)
	*descent = fm;
    
    
    DXFree((Pointer)tbuf);
    return (Object)font;

  error:
    DXDelete((Object)font);
    DXFree((Pointer)tbuf);
    return NULL;
}

/* find the font file
 */
static Error 
getname(char *dirlist, char *file, char *result)
{
    int fd = -1;
    char *dirbuf = NULL;
    char *dp, *cp;

    /* if absolute pathname, don't fool with directories.
     */
#if	!defined(DXD_NON_UNIX_DIR_SEPARATOR)
    if (file[0] == '/') {
#else
    if (file[0] == '/' || file[0] == '\\' || file[1] == ':') {
#endif
	strcpy(result, file);
	return OK;
    }

    /* allocate space to construct variations of the given filename.
     *  get enough space the first time so we can construct any variation
     *  without having to reallocate (xtra defined up above).
     */
    dirbuf = (char *)DXAllocateLocalZero(strlen(dirlist) + strlen(file) + XTRA);
    if (!dirbuf)
	return ERROR;


    /* allow dirlist to be a colon separated list of directory names.
     *  try dir/name, dir/name.dx, dir/fonts/name, dir/fonts/name.dx
     */
    dp = dirlist;
    while (dp) {

	strcpy(dirbuf, dp);
	if ((cp = strchr(dirbuf, DX_DIR_SEPARATOR)) != NULL)
	    *cp = '\0';
	strcat(dirbuf, "/");
	strcat(dirbuf, file);
	if ((fd = open(dirbuf, O_RDONLY)) >= 0)
	    goto done;
	
	strcat(dirbuf, ".dx");
	if ((fd = open(dirbuf, O_RDONLY)) >= 0)
	    goto done;
	
	strcpy(dirbuf, dp);
	if ((cp = strchr(dirbuf, DX_DIR_SEPARATOR)) != NULL)
	    *cp = '\0';
	strcat(dirbuf, "/fonts/");
	strcat(dirbuf, file);
	if ((fd = open(dirbuf, O_RDONLY)) >= 0)
	    goto done;
	
	strcat(dirbuf, ".dx");
	if ((fd = open(dirbuf, O_RDONLY)) >= 0)
	    goto done;
	
	dp = strchr(dp, DX_DIR_SEPARATOR);
	if (dp)
	    dp++;
    }

  error:
    /* only gets here on error */
    DXSetError(ERROR_INVALID_DATA, "#10800", file);
    result[0] = '\0';
    DXFree((Pointer)dirbuf);
    return ERROR;
    

  done:
    if (fd >= 0)
	close(fd);

    strcpy(result, dirbuf);
    DXFree((Pointer)dirbuf);
    return OK;
}

static Error 
verifyfont(Group font, char *name)
{
    Object subo;
    Array a;
    float fm;
    char *dbuf = NULL;
    char *tbuf = NULL;
    char *dir;
    int items;


    /* do sanity checks now on things we will assume are ok later 
     */
    if (DXGetObjectClass((Object)font) != CLASS_GROUP) {
	DXSetError(ERROR_BAD_PARAMETER, "#10802", name);
	goto error;
    }

    if (!DXGetMemberCount(font, &items) || items != 256) {
	DXSetError(ERROR_BAD_PARAMETER, "#10802", name);
	goto error;
    }

    for (items = 0; subo = DXGetEnumeratedMember(font, items, NULL); items++) {
	if (DXGetObjectClass(subo) != CLASS_FIELD) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10804", name);
	    goto error;
	}
	if (DXEmptyField((Field)subo))
	    continue;
    
	if (!DXGetFloatAttribute((Object)subo, "char width", &fm)) {
	    DXSetError(ERROR_BAD_PARAMETER, "#10806", name);
	    goto error;
	}
    }

    if (!DXGetFloatAttribute((Object)font, "font ascent", &fm)) {
	DXSetError(ERROR_BAD_PARAMETER, "#10810", name);
	goto error;
    }

    if (!DXGetFloatAttribute((Object)font, "font descent", &fm)) {
	DXSetError(ERROR_BAD_PARAMETER, "#10812", name);
	goto error;
    }

    return OK;
    
  error:
    return ERROR;
}

Object
DXGeometricText(char *s, Object font, float *stringwidth)
{
    unsigned char *p;
    double offset;
    Array a, points_array = NULL, conn_array = NULL;
    Field f, newf = NULL;
    float charwidth;
    Pointer conn, newconn, pos;
    float *newpos;
    Point *points, *newpoints;
    Line *lines, *newlines;
    Triangle *tris, *newtris;
    int pointdim, connsize;
    int npoints, nconnect, np, nc, i;
    int p1, q1, r1;
    char *conntype;
    static RGBColor white = {1,1,1};


    /* output field: newpoints.  do connections after you know whether
     * they are lines or triangles.
     */
    newf = DXNewField();
    if (!newf)
	goto error;

    points_array = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
    if (!points_array) 
	goto error;
    if (!DXSetComponentValue(newf, "positions", (Object)points_array))
	goto error;
    npoints = 0;

    offset = 0.0;

    /* for each character in the string... 
     */
    for (p=(unsigned char *)s; *p; p++) {

	f = (Field) DXGetEnumeratedMember((Group)font, *p, NULL);
	if (!f)
	    continue;

	if (!DXGetFloatAttribute((Object)f, "char width", &charwidth))
	    charwidth = 0.0;

	/* get the points and lines/triangles associated with this character.
	 * allow empty fields to have a width.
	 */
	a = (Array) DXGetComponentValue(f, "connections");
	DXGetArrayInfo(a, &nc, NULL, NULL, NULL, NULL);
	if (!a || nc <= 0) {
	    offset += charwidth;
	    continue;
	}
	newconn = DXGetArrayData(a);
	
	/* make the new connections component only after you've seen the 
	 * first connections component so you can make the right type.
	 */
	if (!conn_array) {
	    if (!DXGetStringAttribute((Object)a, "element type", &conntype) ||
		(strcmp(conntype, "lines") && strcmp(conntype, "triangles"))) {
		DXSetError(ERROR_INVALID_DATA, "#10814", "object");
		goto error;
	    }
	    connsize = strcmp(conntype, "lines") ? 3 : 2;
	    conn_array = DXNewArray(TYPE_INT, CATEGORY_REAL, 1, connsize);
	    if (!conn_array) 
		goto error;
	    DXSetConnections(newf, connsize == 2 ? "lines" : "triangles", 
			     conn_array);
	    nconnect = 0;
	}

	a = (Array) DXGetComponentValue(f, "positions");
	if (!a) {
	    offset += charwidth;
	    continue;
	}
	newpos = (float *)DXGetArrayData(a);
	DXGetArrayInfo(a, NULL, NULL, NULL, NULL, &pointdim);
	if (pointdim != 2 && pointdim != 3) {
	    DXSetError(ERROR_INVALID_DATA, "#10816", "object");
	    goto error;
	}
	np = nc * connsize;

	/* add new points, connections */
	if (!DXAddArrayData(points_array, npoints, np, NULL)) 
	    goto error;
	newpoints = ((Point *) DXGetArrayData(points_array)) + npoints;
	if (!DXAddArrayData(conn_array, nconnect, nc, NULL)) 
	    goto error;
	conn = (Pointer)((int *)DXGetArrayData(conn_array) + 
			 nconnect * connsize);
	
	if (connsize == 2) {
	    lines = (Line *)conn;
	    newlines = (Line *)newconn;
	    for (i=0; i<nc; i++) {
		p1 = newlines[i].p;
		q1 = newlines[i].q;
		newpoints[2*i] = DXPt(newpos[p1*pointdim + 0] + offset, 
				      newpos[p1*pointdim + 1], 
				      pointdim==2?0.0:newpos[p1*pointdim + 2]);
		newpoints[2*i+1] = DXPt(newpos[q1*pointdim + 0] + offset, 
				      newpos[q1*pointdim + 1], 
				      pointdim==2?0.0:newpos[q1*pointdim + 2]);
		lines[i] = DXLn(npoints+2*i, npoints+2*i+1);
	    }
	} else {
	    tris = (Triangle *)conn;
	    newtris = (Triangle *)newconn;
	    for (i=0; i<nc; i++) {
		p1 = newtris[i].p;
		q1 = newtris[i].q;
		r1 = newtris[i].r;
		newpoints[3*i] = DXPt(newpos[p1*pointdim + 0] + offset, 
				      newpos[p1*pointdim + 1], 
				      pointdim==2?0.0:newpos[p1*pointdim + 2]);
		newpoints[3*i+1] = DXPt(newpos[q1*pointdim + 0] + offset, 
				      newpos[q1*pointdim + 1], 
				      pointdim==2?0.0:newpos[q1*pointdim + 2]);
		newpoints[3*i+2] = DXPt(newpos[r1*pointdim + 0] + offset, 
				      newpos[r1*pointdim + 1], 
				      pointdim==2?0.0:newpos[r1*pointdim + 2]);
		tris[i] = DXTri(npoints+3*i, npoints+3*i+1, npoints+3*i+2);
	    }
	}

	offset += charwidth;
	npoints += np;
	nconnect += nc;
    }

    /* colors */
    a = (Array) DXNewConstantArray(npoints, (Pointer)&white, 
				   TYPE_FLOAT, CATEGORY_REAL, 1, 3);
    if (!a) 
	goto error;
    DXSetComponentValue(newf, "colors", (Object)a);


    if (stringwidth)
        *stringwidth = offset;

    DXSetStringAttribute((Object)newf, "Text string", s);

    return (Object) DXEndField(newf);
    
  error:    
    DXDelete((Object)newf);
    return NULL;
}
