#include "krcd.h"
#include <limits.h>
#include <math.h>

#include <gtk/gtk.h>	/* For pango so we can get the display length of text strings */

/* ToDoes: Convert casting arrows from text to SVG because we lose the font (it becomes times not helvetica in firefox) */
/*  superscript x too? */
char *styleNames[] = {
    "main",
    "modifier",
    "footer",
    "circle-label",
    "box-header",
    "notes",
    "dashed",
    "dotted",
    "line",
    "fat-line",
    "filled",
    "hidden",
    NULL
};

/* For styles which stroke, this is the stroke-width/2 for even stroke width and (stroke-width-1)/2 for odd */
static int style_stroke_off[] = {
    0,
    0,
    0,
    0,
    0,
    0,
    1,
    1,
    1,
    2,
    0,
    0
};

/* for text styles, this is the point size */
static int style_pointsize[] = {
    42,
    30,
    21,
    30,
    25,
    19,
    0,
    0,
    0,
    0,
    0,
    0
};

static int style_isbold[] = {
    1,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
};

typedef struct svgPoint {
    float x,y;
} Point;

typedef float Matrix[6];

/* ************************************************************************** */
/* ***************************** Bounding Boxes ***************************** */
/* ************************************************************************** */

static void matMul(Matrix to, Matrix first, Matrix second) {
    float v0, v1, v2, v3, v4, v5;
    /* multiplies two 3x2 transformation matrices (adding in the implicit row (0 0 1)) */
    /* the 'v's are used so that "to" can be the same as first or second */

    v0 = first[0]*second[0] + first[1]*second[3];
    v1 = first[0]*second[1] + first[1]*second[4];
    v2 = first[0]*second[2] + first[1]*second[5] + first[2];

    v3 = first[3]*second[0] + first[4]*second[3];
    v4 = first[3]*second[1] + first[4]*second[4];
    v5 = first[3]*second[2] + first[4]*second[5] + first[5];

    to[0] = v0;
    to[1] = v1;
    to[2] = v2;
    to[3] = v3;
    to[4] = v4;
    to[5] = v5;
}

static void transform( Point *to, Matrix matrix, Point *from) {
    float v0, v1;

    v0 = matrix[0]*from->x + matrix[1]*from->y + matrix[2];
    v1 = matrix[3]*from->x + matrix[4]*from->y + matrix[5];

    to->x = v0;
    to->y = v1;
}

static enum style findStyle(xmlChar *temp) {
    int i;
    char *str, *pt, *ctemp = (char *) temp;
    /* the class attribute may be a comma separated list of classnames */

    for ( i=0; styleNames[i]!=NULL; ++i ) {
	str = strstr(ctemp, styleNames[i]);
	if ( str==NULL )
    continue;
	for ( pt=str; pt>ctemp && pt[-1]==' '; --pt )
	    ;
	if ( pt!=ctemp && pt[-1]!=',' )
    continue;
	pt = str + strlen( styleNames[i]);
	if ( *pt==',' || *pt=='\0' )
return( i );
    }
return( st_unknown );
}

static void svgParseTrans(Matrix to, xmlChar *transtext, Matrix initial) {
    Matrix trans, cur;
    char *end, *end2;

    memset( trans, 0, sizeof(trans));
    trans[0] = trans[4] = 1;

    while ( *transtext ) {
	if ( xmlStrncmp(transtext,(xmlChar *) "matrix(",7)==0 ) {
	    transtext += 7;
	    /* There's no xmlStrtod() */
	    /* Matrix elements in unexpected order */
	    cur[0] = strtod((char *) transtext,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    cur[1] = strtod(end,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    cur[3] = strtod(end,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    cur[4] = strtod(end,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    cur[2] = strtod(end,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    cur[5] = strtod(end,&end);
	} else if ( xmlStrncmp(transtext,(xmlChar *) "translate(",10)==0 ) {
	    transtext += 10;
	    cur[0] = cur[4]= 1;
	    cur[1] = cur[3]= 0;
	    cur[2] = strtod((char *) transtext,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    /* if y is not provided it is assumed to be 0. and this does that */
	    cur[5] = strtod(end,&end);
	} else if ( xmlStrncmp(transtext,(xmlChar *) "scale(",6)==0 ) {
	    transtext += 6;
	    memset(cur,0,sizeof(cur));
	    cur[0] = strtod((char *) transtext,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    /* if sy is not provided it is assumed to be sx */
	    cur[4] = strtod(end,&end2);
	    if ( end==end2 )
		cur[4] = cur[0];
	    else
		end = end2;
	} else if ( xmlStrncmp(transtext,(xmlChar *) "rotate(",7)==0 ) {
	    float angle, cx, cy;
	    Matrix temp, temp2;
	    transtext += 7;
	    angle = strtod((char *) transtext,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    /* if cx,cy are not provided they are assumed 0 */
	    cx = strtod(end,&end);
	    while ( isspace(*end) || *end==',' ) ++end;
	    cy = strtod(end,&end);
	    memset(temp,0,sizeof(temp));
	    temp[0] = temp[4] = 1;
	    temp[2] = -cx;
	    temp[5] = -cy;
	    memset(temp2,0,sizeof(temp2));
	    temp2[0] = temp2[4] = cos(angle*3.141592653589793/180.);
	    temp2[1] = -(temp2[3] = sin(angle*3.141592653589793/180.));
	    matMul(temp,temp2,temp);
	    memset(temp2,0,sizeof(temp2));
	    temp[0] = temp[4] = 1;
	    temp2[2] = cx;
	    temp2[5] = cy;
	    matMul(cur,temp,temp2);
	} else if ( xmlStrncmp(transtext,(xmlChar *) "skewX(",6)==0 ) {
	    float angle;
	    transtext += 6;
	    memset(cur,0,sizeof(cur));
	    cur[0] = cur[4] = 1;
	    angle = strtod((char *) transtext,&end);
	    cur[1] = sin(angle*3.141592653589793/180.);
	} else if ( xmlStrncmp(transtext,(xmlChar *) "skewY(",6)==0 ) {
	    transtext += 6;
	    memset(cur,0,sizeof(cur));
	    cur[0] = cur[4] = 1;
	    float angle;
	    angle = strtod((char *) transtext,&end);
	    cur[3] = sin(angle);
	} else {
	    fprintf( stderr, "Unknown transform command %s\n", (char *) transtext );
    break;
	}
	while ( isspace(*end) ) ++end;
	if ( *end!=')' ) {
	    fprintf( stderr, "Failed to parse transform command %s near %s\n", (char *) transtext, end );
    break;
	}
	++end;
	matMul(trans,trans,cur);
	while ( isspace(*end) ) ++end;
	if ( *end==',' ) ++end;
	while ( isspace(*end) ) ++end;
	transtext = (xmlChar *) end;
    }
    matMul(to,initial,trans);
}

static void _svgLineBB(xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *temp;
    Point here;

    if ( !xmlHasProp(svg, (xmlChar *) "x1") || !xmlHasProp(svg, (xmlChar *) "y1") ||
	     !xmlHasProp(svg, (xmlChar *) "x2") || !xmlHasProp(svg, (xmlChar *) "y2") ) {
	fprintf( stderr, "%ld: Bad line, missing some of x1,x2,y1,y2.\n", xmlGetLineNo(svg) );
    } else {
	temp = xmlGetProp(svg, (xmlChar *) "x1");
	here.x = strtod((char *) temp,NULL);
	xmlFree(temp);

	temp = xmlGetProp(svg, (xmlChar *) "y1");
	here.y = strtod((char *) temp,NULL);
	xmlFree(temp);

	transform(&here,trans,&here);

	bb->minx = bb->maxx = here.x;
	bb->miny = bb->maxy = here.y;

	temp = xmlGetProp(svg, (xmlChar *) "x2");
	here.x = strtod((char *) temp,NULL);
	xmlFree(temp);

	temp = xmlGetProp(svg, (xmlChar *) "y2");
	here.y = strtod((char *) temp,NULL);
	xmlFree(temp);

	transform(&here,trans,&here);

	if ( here.x<bb->minx )
	    bb->minx = here.x;
	if ( here.x>bb->maxx )
	    bb->maxx = here.x;

	if ( here.y<bb->miny )
	    bb->miny = here.y;
	if ( here.y>bb->maxy )
	    bb->maxy = here.y;
    }
}

static void _svgPolyBB(xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *temp;
    Point here;
    char *end;

    if ( !xmlHasProp(svg, (xmlChar *) "points") ) {
	fprintf( stderr, "%ld: Bad polygon/line, missing points.\n", xmlGetLineNo(svg) );
    } else {
	temp = xmlGetProp(svg, (xmlChar *) "points");
	here.x = strtod((char *) temp,&end);
	while ( isspace( *end ) || *end==',' ) ++end;
	here.y = strtod(end,&end);
	while ( isspace( *end ) || *end==',' ) ++end;

	transform(&here,trans,&here);

	bb->minx = bb->maxx = here.x;
	bb->miny = bb->maxy = here.y;

	while ( *end ) {
	    char *end2 = end;
	    here.x = strtod(end,&end);
	    while ( isspace( *end ) || *end==',' ) ++end;
	    here.y = strtod(end,&end);
	    while ( isspace( *end ) || *end==',' ) ++end;

	    if ( end==end2 ) {
		fprintf( stderr, "%ld: Failed to parse polygon/line near %s\n", xmlGetLineNo(svg), end );
	break;
	    }

	    transform(&here,trans,&here);

	    if ( here.x<bb->minx )
		bb->minx = here.x;
	    if ( here.x>bb->maxx )
		bb->maxx = here.x;

	    if ( here.y<bb->miny )
		bb->miny = here.y;
	    if ( here.y>bb->maxy )
		bb->maxy = here.y;
	}
	
	xmlFree(temp);
    }
}

static void _svgCircleBB(xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *temp;
    Point here;
    float r;

    if ( !xmlHasProp(svg, (xmlChar *) "cx") || !xmlHasProp(svg, (xmlChar *) "cy") ||
	     !xmlHasProp(svg, (xmlChar *) "r") ) {
	fprintf( stderr, "%ld: Bad circle, missing some of cx,cy,r.\n", xmlGetLineNo(svg) );
    } else {
	temp = xmlGetProp(svg, (xmlChar *) "cx");
	here.x = strtod((char *) temp,NULL);
	xmlFree(temp);

	temp = xmlGetProp(svg, (xmlChar *) "cy");
	here.y = strtod((char *) temp,NULL);
	xmlFree(temp);

	transform(&here,trans,&here);

	temp = xmlGetProp(svg, (xmlChar *) "r");
	r = strtod((char *) temp,NULL);
	xmlFree(temp);
	if ( r<0 ) r = -r;

	bb->minx = here.x-r;
	bb->maxx = here.x+r;
	bb->miny = here.y-r;
	bb->maxy = here.y+r;
    }
}

static void _figureRectangleBB(Point *here, float width, float height,
	Matrix trans, struct krcd_bb *bb) {
    Point there;

    transform(&there,trans,here);

    bb->minx = bb->maxx = there.x;
    bb->miny = bb->maxy = there.y;

    here->x += width;
    transform(&there,trans,here);

    if ( there.x<bb->minx )
	bb->minx = there.x;
    if ( there.x>bb->maxx )
	bb->maxx = there.x;

    if ( there.y<bb->miny )
	bb->miny = there.y;
    if ( there.y>bb->maxy )
	bb->maxy = there.y;

    here->y += height;
    transform(&there,trans,here);

    if ( there.x<bb->minx )
	bb->minx = there.x;
    if ( there.x>bb->maxx )
	bb->maxx = there.x;

    if ( there.y<bb->miny )
	bb->miny = there.y;
    if ( there.y>bb->maxy )
	bb->maxy = there.y;

    here->x -= width;
    transform(&there,trans,here);

    if ( there.x<bb->minx )
	bb->minx = there.x;
    if ( there.x>bb->maxx )
	bb->maxx = there.x;

    if ( there.y<bb->miny )
	bb->miny = there.y;
    if ( there.y>bb->maxy )
	bb->maxy = there.y;
}

static void _svgRectBB(xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *temp;
    Point here;
    float width, height;

    if ( !xmlHasProp(svg, (xmlChar *) "width") || !xmlHasProp(svg, (xmlChar *) "height") ) {
	fprintf( stderr, "%ld: Bad rect, missing some of width,height.\n", xmlGetLineNo(svg) );
    } else {
	if ( xmlHasProp(svg, (xmlChar *) "x")) {
	    temp = xmlGetProp(svg, (xmlChar *) "x");
	    here.x = strtod((char *) temp,NULL);
	    xmlFree(temp);
	} else
	    here.x = 0;

	if ( xmlHasProp(svg, (xmlChar *) "y")) {
	    temp = xmlGetProp(svg, (xmlChar *) "y");
	    here.y = strtod((char *) temp,NULL);
	    xmlFree(temp);
	} else
	    here.y = 0;

	temp = xmlGetProp(svg, (xmlChar *) "width");
	width = strtod((char *) temp,NULL);
	xmlFree(temp);

	temp = xmlGetProp(svg, (xmlChar *) "height");
	height = strtod((char *) temp,NULL);
	xmlFree(temp);

	_figureRectangleBB(&here, width, height, trans, bb);
    }
}

static void _svgElipseBB(xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *temp;
    Point here;
    float width, height;

    if ( !xmlHasProp(svg, (xmlChar *) "cx") || !xmlHasProp(svg, (xmlChar *) "cy") ) {
	fprintf( stderr, "%ld: Bad elipse, missing some of cx,cy.\n", xmlGetLineNo(svg) );
    } else if ( !xmlHasProp(svg, (xmlChar *) "rx") || !xmlHasProp(svg, (xmlChar *) "ry") ) {
	fprintf( stderr, "%ld: Bad elipse, missing some of rx,ry.\n", xmlGetLineNo(svg) );
    } else {
	/* Get the bounding box of the rotated rectangle in which it sits */
	if ( xmlHasProp(svg, (xmlChar *) "cx")) {
	    temp = xmlGetProp(svg, (xmlChar *) "cx");
	    here.x = strtod((char *) temp,NULL);
	    xmlFree(temp);
	} else
	    here.x = 0;

	if ( xmlHasProp(svg, (xmlChar *) "cy")) {
	    temp = xmlGetProp(svg, (xmlChar *) "cy");
	    here.y = strtod((char *) temp,NULL);
	    xmlFree(temp);
	} else
	    here.y = 0;

	temp = xmlGetProp(svg, (xmlChar *) "rx");
	width = strtod((char *) temp,NULL);
	xmlFree(temp);

	temp = xmlGetProp(svg, (xmlChar *) "ry");
	height = strtod((char *) temp,NULL);
	xmlFree(temp);

	here.x -= width;
	here.y -= height;
	width = 2*width;
	height = 2*height;

	_figureRectangleBB(&here, width, height, trans, bb);
    }
}

static void _svgPathBB(xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *temp;
    char *pt;
    char *end;
    int cmd;
    Point here, there, last = { 0,0 };
    int first = true;

    if ( !xmlHasProp(svg, (xmlChar *) "d") ) {
	fprintf( stderr, "%ld: Bad path, missing d.\n", xmlGetLineNo(svg) );
    } else {
	temp = xmlGetProp(svg, (xmlChar *) "d");

	pt = (char *) temp;
	while ( isspace(*pt)) ++pt;
	for ( ; *pt; ) {
	    switch ( (cmd = *pt++) ) {
	      case 'M': case 'm':
		here.x = strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.y = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
	      break;
	      case 'l': case 'L':
		here.x = strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.y = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		if ( cmd=='l' ) {
		    here.x += last.x;
		    here.y += last.y;
		}
	      break;
	      case 'h': case 'H':
		here.x = strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.y = last.y;
		if ( cmd=='h' ) {
		    here.x += last.x;
		}
	      break;
	      case 'v': case 'V':
		here.y = strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.x = last.x;
		if ( cmd=='v' ) {
		    here.y += last.y;
		}
	      break;
	      case 'z': case 'Z':
	continue;

	      /* I don't do bezier's right, I only look at endpoints, not at intermediate min/max */
	      case 'c': case 'C':	/* cubic bezier */
	        /* skip one control point */
		strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		pt = end;

		/* Fall through */
	      case 'q': case 'Q': case 's': case 'S':	/* quadratic bezier, cubic with omitted control point */
	        /* skip one control point */
		strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;

		here.x = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.y = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		if ( cmd=='c' || cmd=='q' || cmd=='s' ) {
		    here.x += last.x;
		    here.y += last.y;
		}
	      break;
	      case 't': case 'T':	/* quad bezier with omitted control point */
		here.x = strtod(pt,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.y = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		if ( cmd=='t' ) {
		    here.x += last.x;
		    here.y += last.y;
		}
	      break;
	      case 'a': case 'A':	/* arc. I don't do this right, I only parse it */
	        fprintf( stderr, "%ld: Warning: arc command in path, I don't calculate the bb properly\n", xmlGetLineNo(svg) );
	        /* skip junk */
		strtod(pt,&end);	/* rx */
		while ( isspace( *end ) || *end==',' ) ++end;
		strtod(end,&end);	/* ry */
		while ( isspace( *end ) || *end==',' ) ++end;
		strtod(end,&end);	/* rotation */
		while ( isspace( *end ) || *end==',' ) ++end;
		strtod(end,&end);	/* large-arg flag */
		while ( isspace( *end ) || *end==',' ) ++end;
		strtod(end,&end);	/* sweep flag */
		while ( isspace( *end ) || *end==',' ) ++end;

		here.x = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		here.y = strtod(end,&end);
		while ( isspace( *end ) || *end==',' ) ++end;
		if ( cmd=='a' ) {
		    here.x += last.x;
		    here.y += last.y;
		}
	      break;
	      default:
	        fprintf( stderr, "%ld: Unknown command letter in path '%c', in %s\n", xmlGetLineNo(svg), cmd, (char *) temp );
return;
	    }
	    last = here;
/* printf( "here=(%.0f,%.0f) ", here.x, here.y ); */

	    transform(&there,trans,&here);
	    if ( first ) {
		first = false;
		bb->minx = bb->maxx = there.x;
		bb->miny = bb->maxy = there.y;
	    } else {
		if ( there.x<bb->minx )
		    bb->minx = there.x;
		if ( there.x>bb->maxx )
		    bb->maxx = there.x;

		if ( there.y<bb->miny )
		    bb->miny = there.y;
		if ( there.y>bb->maxy )
		    bb->maxy = there.y;
	    }
/* printf( "bb=(minx=%.0f miny=%.0f maxx=%.0f maxy=%.0f)\n", bb->minx, bb->miny, bb->maxx, bb->maxy ); */
	    pt = end;
	}
	xmlFree(temp);
    }
}

static cairo_surface_t *surface;
static cairo_t *cr = NULL;
static PangoLayout *perStyle[st_last];
static PangoFontDescription *desc;

static PangoLayout *_pangoFindLayoutStyle(enum style style, int lineno) {

    if ( style==st_unknown ) {
	fprintf( stderr, "%d: Can't figure out what font to use for a <text> element\n", lineno);
return( NULL );
    }

    if ( perStyle[style]==NULL ) {
	if ( cr==NULL ) {
	    surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 100, 100);
	    cr = cairo_create(surface);
	    /* I only need these to provide a context on which to create the layouts */
	    desc = pango_font_description_new();
	    pango_font_description_set_family(desc,"Helvetica, Ariel, sans-serif");
	}
	pango_font_description_set_weight(desc,style_isbold[style]?PANGO_WEIGHT_BOLD:PANGO_WEIGHT_NORMAL);
	pango_font_description_set_absolute_size(desc,style_pointsize[style]*PANGO_SCALE);
	perStyle[style] = pango_cairo_create_layout(cr);
	pango_layout_set_font_description(perStyle[style],desc);
    }
return( perStyle[style]);
}

static PangoLayout *_pangoFindLayout(xmlNodePtr svg) {
    enum style style;

    if ( xmlHasProp(svg,(xmlChar *) "class") ) {
	xmlChar *temp;
	temp = xmlGetProp(svg, (xmlChar *) "class");
	style = findStyle(temp);
	xmlFree(temp);
    }
return( _pangoFindLayoutStyle(style,xmlGetLineNo(svg)));
}

static void _svgTextBB(xmlDocPtr doc,xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    xmlChar *str = xmlNodeListGetString(doc,svg->children, 1);
    Point here;
    PangoRectangle extents;
    PangoLayout *layout;
    xmlChar *temp;
    float x, y;

    if ( str==NULL )
return;

    layout = _pangoFindLayout(svg);
    if ( layout==NULL )
return;

    pango_layout_set_text(layout,(char *) str,-1);
    pango_layout_get_pixel_extents(layout,NULL,&extents);

    temp = xmlGetProp(svg, (xmlChar *) "x");
    if ( temp!=NULL ) {
	x = strtod((char *) temp,NULL);
	xmlFree(temp);
    } else
	x = 0;

    temp = xmlGetProp(svg, (xmlChar *) "y");
    if ( temp!=NULL ) {
	y = strtod((char *) temp,NULL);
	xmlFree(temp);
    } else
	y = 0;

/* printf( "text x=%.0f y=%.0f extents=(x=%d y=%d width=%d height=%d)\n",
  x,y, extents.x, extents.y, extents.width, extents.height );  */
    here.x = extents.x+x; here.y = extents.y+y-extents.height;
    _figureRectangleBB(&here, extents.width, extents.height, trans, bb);
/* printf( "bb=(minx=%.0f miny=%.0f maxx=%.0f maxy=%.0f)\n", bb->minx, bb->miny, bb->maxx, bb->maxy ); */

    xmlFree(str);
}

static void _krcdSvgBB(xmlDocPtr doc, xmlNodePtr svg,Matrix trans, struct krcd_bb *bb) {
    enum style style = st_unknown;
    Matrix mytrans;
    float *origtrans = trans;

    if ( svg->type!=XML_ELEMENT_NODE )
return;
    if ( xmlHasProp(svg,(xmlChar *) "class") ) {
	xmlChar *temp;
	temp = xmlGetProp(svg, (xmlChar *) "class");
	style = findStyle(temp);
	xmlFree(temp);
    }
    if ( xmlHasProp(svg,(xmlChar *) "transform") ) {
	xmlChar *temp;
	temp = xmlGetProp(svg, (xmlChar *) "transform");
	svgParseTrans(mytrans, temp, trans);
	xmlFree(temp);
	trans = mytrans;
    }

    if ( xmlStrcmp(svg->name,(xmlChar *) "line") == 0 ) {
	_svgLineBB(svg, trans, bb);
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "polygon") == 0 ||
	    xmlStrcmp(svg->name,(xmlChar *) "polyline") == 0 ) {
	_svgPolyBB(svg, trans, bb);
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "circle") == 0 ) {
	_svgCircleBB(svg, trans, bb);
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "rect") == 0 ) {
	_svgRectBB(svg, trans, bb);
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "elipse") == 0 ) {
	_svgElipseBB(svg, trans, bb);
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "path") == 0 ) {
	_svgPathBB(svg, trans, bb);
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "text") == 0 ) {
	if ( !krcdTextNeedsMassaging(doc,svg))
	    _svgTextBB(doc, svg, trans, bb);
	else {
	    xmlNodePtr massaged;

	    massaged = krcdMassageText(doc,svg);
	    _krcdSvgBB(doc,massaged,origtrans,bb);
	    xmlFreeNodeList(massaged);
	}
    } else if ( xmlStrcmp(svg->name,(xmlChar *) "g") == 0 ) {
	struct krcd_bb tempbb;
	xmlNodePtr child;
	int first = 1;

	for ( child = svg->children; child!=NULL; child=child->next ) {
	    if ( child->type!=XML_ELEMENT_NODE )
	continue;
	    else if ( first ) {
		_krcdSvgBB(doc,child,trans,bb);
		first = false;
	    } else {
		memset(&tempbb,0,sizeof(tempbb));
		_krcdSvgBB(doc,child,trans,&tempbb);
		if ( tempbb.minx<bb->minx )
		    bb->minx = tempbb.minx;
		if ( tempbb.miny<bb->miny )
		    bb->miny = tempbb.miny;
		if ( tempbb.maxx>bb->maxx )
		    bb->maxx = tempbb.maxx;
		if ( tempbb.maxy>bb->maxy )
		    bb->maxy = tempbb.maxy;
	    }
	}
	/* Sort of kludgey. If I have text with a modifier which I enter as svg */
	/*  then want to do a vertical center on the capheight, not on the bounding */
	/*  box which will include the modifier text (because that aligns things */
	/*  nicely), then set the class to one of our text styles and we'll force */
	/*  the bb to caps-height */
	if ( krcdTextCapHeight(style)!= 0 )
	    bb->maxy = bb->miny + krcdTextCapHeight(style);
    } else {
	fprintf( stderr, "Unknown svg node (%s) when finding bounding box.\n",
		(char *) svg->name );
    }
    if ( style!=st_unknown ) {
	int off = style_stroke_off[style];
	bb->minx -= off;
	bb->maxx -= off;
	bb->miny -= off;
	bb->maxy -= off;
    }
}

void krcdSvgBB(xmlDocPtr doc,xmlNodePtr svg,struct krcd_bb *bb) {
    Matrix trans;

    memset( trans, 0, sizeof(trans));
    trans[0] = trans[4] = 1;
    memset(bb,0,sizeof(*bb));
    _krcdSvgBB(doc,svg,trans,bb);
}

void krcdSvgListBB(xmlDocPtr doc,xmlNodePtr svg,struct krcd_bb *bb) {
    xmlNodePtr child;
    struct krcd_bb tempbb;
    Matrix trans;
    int first=1;

    memset( trans, 0, sizeof(trans));
    trans[0] = trans[4] = 1;
    memset(bb,0,sizeof(*bb));

    for ( child = svg; child!=NULL; child=child->next ) {
	if ( child->type!=XML_ELEMENT_NODE )
    continue;
	else if ( first ) {
	    _krcdSvgBB(doc,child,trans,bb);
	    first = 0;
	} else {
	    memset(&tempbb,0,sizeof(tempbb));
	    _krcdSvgBB(doc,child,trans,&tempbb);
	    if ( tempbb.minx<bb->minx )
		bb->minx = tempbb.minx;
	    if ( tempbb.miny<bb->miny )
		bb->miny = tempbb.miny;
	    if ( tempbb.maxx>bb->maxx )
		bb->maxx = tempbb.maxx;
	    if ( tempbb.maxy>bb->maxy )
		bb->maxy = tempbb.maxy;
	}
    }
}

/* ************************************************************************** */
/* ********************** Get rid of combining circles ********************** */
/* ************************************************************************** */

static char *_formatFloat(char *buffer,float val ) {
    int len;

    sprintf( buffer, "%.3f", val );
    /* remove trailing 0s and decimal point */
    len = strlen( buffer );
    while ( len>1 && buffer[len-1]=='0' )
	buffer[len--] = '\0';
    if ( buffer[len-1]=='.' )
	buffer[len-1]='\0';
return( buffer );
}

static xmlNodePtr _newText(unsigned char *start, unsigned char *end, float x, float y,
	enum style style, int inactive ) {
    xmlNodePtr text, textNode;
    char buffer[20];
    char *temp;
    
    text = xmlNewNode(NULL,(xmlChar *)"text");
    xmlNewProp(text,(xmlChar *) "class",(xmlChar *) styleNames[style]);
/* I wanted an inactive class, but browsers don't seem to support multiple classes on svg (or I'm stupid) */
    if ( inactive )
	xmlNewProp(text,(xmlChar *) "style",(xmlChar *) "fill: grey");

    xmlNewProp(text,(xmlChar *) "x",(xmlChar *) _formatFloat(buffer,x));
    xmlNewProp(text,(xmlChar *) "y",(xmlChar *) _formatFloat(buffer,y));

    temp = strndup((char *) start,end-start);
    textNode = xmlNewText((xmlChar *) temp);
    xmlAddChild(text,textNode);
    free(temp);
return( text );
}

static int _krcdTextNeedsMassaging(xmlChar *str) {
    unsigned char *pt;

    for ( pt = (unsigned char *) str; *pt; ++pt )
	if ( pt[0]==0xE2 && pt[1]==0x83 && (pt[2]==0x9E || pt[2]==0x9D || pt[2]==0x94 || pt[2]==0x95))
return( 1 );
    /* 9D is circle, 9E square */

return( 0 );
}

int krcdTextNeedsMassaging(xmlDocPtr doc,xmlNodePtr text) {
    xmlChar *str = xmlNodeListGetString(doc,text->children, 1);
    int ret;

    ret = _krcdTextNeedsMassaging(str);
    xmlFree(str);

return( ret );
}

xmlNodePtr krcdMassageString(xmlChar *str, enum style style, int inactive,
	float x, float y, int lineno) {
    /* Turn a text string containing combining circles/squares into a */
    /*  list of texts and circle and square nodes */
    /* Combining circles/squares are rendered differently on different platforms */
    /*  and sometimes look ugly, sometimes the square and circle are different */
    /*  sizes, and anyway we want them consistent */
    /* we need to copy class, transform */
    PangoRectangle extents;
    PangoLayout *layout;
    unsigned char *pt, *start;
    xmlNodePtr g,temp;
    int square_size;
    int square_pad;
    int cap_height;
    float cx, cy;
    char buffer[80];

    if ( !_krcdTextNeedsMassaging(str) )
return( _newText(str,str+strlen((char *) str),x,y,style,inactive));

    layout = _pangoFindLayoutStyle(style,lineno);
    if ( layout==NULL )
return( NULL );

    /* we want the square/circle to enclose a digit */
    /* Helvetica's cap-height is 1469/2048 of the font-size */
    /* but we want at least 2 pixels of space above that and another 2 below */
    /* and then some space around it */
    cap_height = style_pointsize[style]* 1469/2048;
    square_pad = cap_height/6;
    square_size = cap_height + 2*square_pad;
    
    g = xmlNewNode(NULL,(xmlChar *)"g");
    
    for ( start = pt=str; *pt!='\0' ; ) {
	if ( *pt==0xE2 && pt[1]==0x83 && (pt[2]==0x9D /* circle */ || pt[2]==0x9E /* square */ || pt[2]==0x94 /* anticlockwise arrow */ || pt[2]==0x95 /* clockwise arrow */) ) {
	    if ( pt==start) {
		fprintf( stderr, "%d: combining circle/square/arrow must have a preceding character\n", lineno );
		pt+=3;
		start=pt;
    continue;
	    }
	    /* Bug here. Only handle ASCII characters, with no other combiners, no utf */
	    if ( start<pt-1 ) {
		/* I suspect svg will ignore leading spaces, so don't produce any */
		while ( start[0]==' ' ) {
		    x += .278 * style_pointsize[style];
		    ++start;
		}
	    }
	    if ( start<pt-1 ) {
		unsigned char *ept = pt-1;
		while ( ept>=start && ept[0]==' ' )
		    --ept;
		if ( start<ept ) {
		    /* add any un combined text */
		    temp = _newText(start,ept,x,y,style,inactive);
		    xmlAddChild(g,temp);
		    pango_layout_set_text(layout,(char *) start,ept-start);
		    pango_layout_get_pixel_extents(layout,NULL,&extents);
		    x += extents.width;
		    start = pt-1;
		}
	    }
	    pango_layout_set_text(layout,(char *) start,1);
	    pango_layout_get_pixel_extents(layout,NULL,&extents);
	    cx = x + square_pad + square_size/2;
	    if ( pt[2]==0x94 || pt[2]==0x95 )
		cx = x+extents.width/2;
	    cy = y - cap_height/2;
	    if ( pt[2]==0x9D ) {
		temp = xmlNewNode(NULL,(xmlChar *)"circle");
		xmlNewProp(temp, (xmlChar *) "cx", (xmlChar *) _formatFloat(buffer,cx));
		xmlNewProp(temp, (xmlChar *) "cy", (xmlChar *) _formatFloat(buffer,cy));
		xmlNewProp(temp, (xmlChar *) "r", (xmlChar *) _formatFloat(buffer,square_size/2.0+1));
		xmlNewProp(temp, (xmlChar *) "class", (xmlChar *) "line" );
		if ( inactive )
		    xmlNewProp(temp, (xmlChar *) "style", (xmlChar *) "stroke: grey" );
	    } else if ( pt[2]==0x9E ) {
		temp = xmlNewNode(NULL,(xmlChar *)"rect");
		xmlNewProp(temp, (xmlChar *) "x",(xmlChar *) _formatFloat(buffer,x+square_pad));
		xmlNewProp(temp, (xmlChar *) "y",(xmlChar *) _formatFloat(buffer,y-square_size + 1));
		xmlNewProp(temp, (xmlChar *) "width",(xmlChar *) _formatFloat(buffer,square_size));
		xmlNewProp(temp, (xmlChar *) "height",(xmlChar *) _formatFloat(buffer,square_size));
		xmlNewProp(temp, (xmlChar *) "class", (xmlChar *) "line" );
		if ( inactive )
		    xmlNewProp(temp, (xmlChar *) "style", (xmlChar *) "stroke: grey" );
	    } else if ( pt[2]==0x94 || pt[2]==0x95 ) {
		/* do the right thing for "main" (cap height 30) and scale it */
		xmlNodePtr subg = xmlNewNode(NULL,(xmlChar *)"g");

		sprintf(buffer,"scale(%.2f), translate(%.1f,%.1f)", cap_height / 30.0, cx, y );
		xmlNewProp(subg, (xmlChar *) "transform",(xmlChar *) buffer);

		temp = xmlNewNode(NULL,(xmlChar *)"path");
		xmlNewProp(temp, (xmlChar *) "class", (xmlChar *) "fat-line" );
		if ( inactive )
		    xmlNewProp(temp, (xmlChar *) "style", (xmlChar *) "stroke: grey" );
		xmlNewProp(temp, (xmlChar *) "d",(xmlChar *) "M-10,-36 q10,-10 20,0" );
		xmlAddChild(subg,temp);

		temp = xmlNewNode(NULL,(xmlChar *)"path");
		if ( inactive )
		    xmlNewProp(temp, (xmlChar *) "fill", (xmlChar *) "grey" );
		else
		    xmlNewProp(temp, (xmlChar *) "class", (xmlChar *) "filled" );
		if ( pt[2]==0x94 ) { /* anti-clockwise arrow, cast up */
		    xmlNewProp(temp, (xmlChar *) "d",(xmlChar *) "M-10,-34 l-2,-10 q3,5 5,7 q2,2 7,5z" );
		} else {
		    xmlNewProp(temp, (xmlChar *) "d",(xmlChar *) "M10,-34 l-10,2 q5,-3 7,-5 q2,-2 7,-5z" );
		}
		xmlAddChild(subg,temp);
		temp = subg;
	    }
	    xmlAddChild(g,temp);
	    temp = _newText(start,pt,cx-extents.width/2,y,style,inactive); 
	    xmlAddChild(g,temp);
	    if ( pt[2]!=0x94 || pt[2]!=0x95 )
		x += square_size + 2*square_pad;
	    pt+=3;
	    start=pt;
	} else
	    ++pt;
    }
    if ( pt!=start ) {
	/* add any trailing text */
	if ( start[0]==' ' ) {
	    x += .278 * style_pointsize[style];
	    ++start;
	}
    }
    if ( pt!=start ) {
	temp = _newText(start,pt,x,y,style,inactive);
	xmlAddChild(g,temp);
    }

return( g );
}

xmlNodePtr krcdMassageText(xmlDocPtr doc, xmlNodePtr text) {
    /* Turn a text string containing combining circles/squares into a */
    /*  list of texts and circle and square nodes */
    /* Combining circles/squares are rendered differently on different platforms */
    /*  and sometimes look ugly, sometimes the square and circle are different */
    /*  sizes, and anyway we want them consistent */
    /* we need to copy class, transform */
    xmlChar *str = xmlNodeListGetString(doc,text->children, 1);
    enum style style = st_unknown;
    float x,y;
    xmlChar *tempstr;
    xmlNodePtr g;
    xmlAttrPtr prop;
    int isinactive = false;

    if ( xmlHasProp(text,(xmlChar *) "class") ) {
	tempstr = xmlGetProp(text, (xmlChar *) "class");
	style = findStyle(tempstr);
	xmlFree(tempstr);
    }
    if ( xmlHasProp(text,(xmlChar *) "style") ) {
	tempstr = xmlGetProp(text, (xmlChar *) "style");
	if ( strstr((char *) tempstr,"fill:")!=NULL )
	    isinactive = true;
	xmlFree(tempstr);
    }

    if ( xmlHasProp(text,(xmlChar *) "x") ) {
	tempstr = xmlGetProp(text, (xmlChar *) "x");
	x = strtod((char *) tempstr,NULL);
	xmlFree(tempstr);
    } else
	x = 0;

    if ( xmlHasProp(text,(xmlChar *) "y") ) {
	tempstr = xmlGetProp(text, (xmlChar *) "y");
	y = strtod((char *) tempstr,NULL);
	xmlFree(tempstr);
    } else
	y = 0;

    g = krcdMassageString( str, style, isinactive, x, y, (int) xmlGetLineNo(text) );
    if ( g!=NULL && (prop = xmlHasProp(text,(xmlChar *) "transform"))!=NULL )
	xmlCopyProp(g,prop);

    xmlFree(str);
return( g );
}

/* ************************************************************************** */
/* ***************************** String Widths ****************************** */
/* ************************************************************************** */

int krcdTextCapHeight(enum style style) {
    if ( style==st_unknown )
return( 0 );

return( style_pointsize[style]*1469/2048 );
}

/* Bug: multiple spaces. svg interprets them as one, while layouts don't */
int krcdTextWidth(char *str, int len, enum style style, xmlDocPtr doc) {
    PangoRectangle extents;
    PangoLayout *layout;
    int ch;

    if ( str==NULL || len==0 )
return( 0 );

    if ( len==-1 )
	len = strlen(str);
    if ( len==0 )
return( 0 );
    ch = str[len]; str[len] = '\0';
    if ( _krcdTextNeedsMassaging((xmlChar *) str) ) {
	struct krcd_bb bb;
	xmlNodePtr massaged = krcdMassageString((xmlChar *) str, style, false, 0, 0, 0);
	str[len] = ch;
	krcdSvgBB(doc, massaged,&bb);
	xmlFreeNodeList(massaged);
return( bb.maxx - bb.minx );
    }
    str[len] = ch;
    
    
    layout = _pangoFindLayoutStyle(style,0);
    if ( layout==NULL )
return( 0 );
    pango_layout_set_text( layout, str, len );
    pango_layout_get_pixel_extents( layout, NULL, &extents );
return( extents.width );
}

int krcdTextMainModKern(char *str) {
    switch ( str[strlen(str)-1] ) {
      case 'F':
return( -style_pointsize[st_main] * 820/2048 );
      case 'P':
return( -style_pointsize[st_main] * 898/2048 );
      case 'T':
return( -style_pointsize[st_main] * 400/2048 );		/* algorithmicly: 495 but that looks too close */
      case 'Y':
return( -style_pointsize[st_main] * 555/2048 );
      default:
return( 0 );
    }
}

int krcdTextMainModWidth(char *main, char *mod) {
    int len=0;

    len = krcdTextWidth(main,-1,st_main,NULL);
    if ( mod!=NULL ) {
	int modlen = len + krcdTextMainModKern(main);
	modlen += krcdTextWidth(mod,-1,st_modifier,NULL);
	if ( modlen > len )
	    len = modlen;
    }
return( len );
}
