/*
 * Attrs.c -- Various attributes manipulation routines.
 *
 * Authors		: Patrick Lecoanet.
 * Creation date	: Fri Dec 31 10:03:34 1999
 *
 * $Id: Attrs.c,v 1.9 2003/05/09 15:01:14 lecoanet Exp $
 */

/*
 *  Copyright (c) 1993 - 1999 CENA, Patrick Lecoanet --
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this code; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include "Attrs.h"
#include "Item.h"
#include "List.h"
#include "Geo.h"
#include "WidgetInfo.h"

#include <memory.h>
#include <stdlib.h>


static const char rcsid[] = "$Id: Attrs.c,v 1.9 2003/05/09 15:01:14 lecoanet Exp $";
static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";


/*
 ****************************************************************
 *
 * Code for reliefs.
 *
 ****************************************************************
 */
#define RELIEF_FLAT_SPEC	"flat"
#define RELIEF_RAISED_SPEC	"raised"
#define RELIEF_SUNKEN_SPEC	"sunken"
#define RELIEF_GROOVE_SPEC	"groove"
#define RELIEF_RIDGE_SPEC	"ridge"
#define RELIEF_ROUND_RAISED_SPEC "roundraised"
#define RELIEF_ROUND_SUNKEN_SPEC "roundsunken"
#define RELIEF_ROUND_GROOVE_SPEC "roundgroove"
#define RELIEF_ROUND_RIDGE_SPEC	"roundridge"
#define RELIEF_SUNKEN_RULE_SPEC "sunkenrule"
#define RELIEF_RAISED_RULE_SPEC	"raisedrule"

int
ZnGetRelief(ZnWInfo		*wi,
	    char		*name,
	    ZnReliefStyle	*relief)
{
  size_t length;
  
  length = strlen(name);
  if (strncmp(name, RELIEF_FLAT_SPEC, length) == 0) {
    *relief = ZN_RELIEF_FLAT;
  }
  else if (strncmp(name, RELIEF_SUNKEN_SPEC, length) == 0) {
    *relief = ZN_RELIEF_SUNKEN;
  }
  else if ((strncmp(name, RELIEF_RAISED_SPEC, length) == 0) && (length >= 2)) {
    *relief = ZN_RELIEF_RAISED;
  }
  else if ((strncmp(name, RELIEF_RIDGE_SPEC, length) == 0) && (length >= 2)) {
    *relief = ZN_RELIEF_RIDGE;
  }
  else if (strncmp(name, RELIEF_GROOVE_SPEC, length) == 0) {
    *relief = ZN_RELIEF_GROOVE;
  }
  else if ((strncmp(name, RELIEF_ROUND_SUNKEN_SPEC, length) == 0) && (length >= 6)) {
    *relief = ZN_RELIEF_ROUND_SUNKEN;
  }
  else if ((strncmp(name, RELIEF_ROUND_RAISED_SPEC, length) == 0) && (length >= 7)) {
    *relief = ZN_RELIEF_ROUND_RAISED;
  }
  else if ((strncmp(name, RELIEF_ROUND_RIDGE_SPEC, length) == 0) && (length >= 7)) {
    *relief = ZN_RELIEF_ROUND_RIDGE;
  }
  else if ((strncmp(name, RELIEF_ROUND_GROOVE_SPEC, length) == 0) && (length >= 6)) {
    *relief = ZN_RELIEF_ROUND_GROOVE;
  }
  else if ((strncmp(name, RELIEF_SUNKEN_RULE_SPEC, length) == 0) && (length >= 7)) {
    *relief = ZN_RELIEF_SUNKEN_RULE;
  }
  else if ((strncmp(name, RELIEF_RAISED_RULE_SPEC, length) == 0) && (length >= 7)) {
    *relief = ZN_RELIEF_RAISED_RULE;
  }
  else {
    Tcl_AppendResult(wi->interp, "bad relief \"", name, "\": must be ",
		     RELIEF_FLAT_SPEC, ", ",
		     RELIEF_RAISED_SPEC, ", ",
		     RELIEF_SUNKEN_SPEC, ", ",
		     RELIEF_GROOVE_SPEC, ", ",
		     RELIEF_RIDGE_SPEC, ", ",
		     RELIEF_ROUND_RAISED_SPEC, ", ",
		     RELIEF_ROUND_SUNKEN_SPEC, ", ",
		     RELIEF_ROUND_GROOVE_SPEC, ", ",
		     RELIEF_ROUND_RIDGE_SPEC, ", ",
		     RELIEF_SUNKEN_RULE_SPEC, ", ",
		     RELIEF_RAISED_RULE_SPEC,
		     NULL);
    return TCL_ERROR;
  }
  if (!wi->render) {
    *relief = *relief & ~(ZN_RELIEF_ROUND|ZN_RELIEF_RULE);
  }

  return TCL_OK;
}

char *
ZnNameOfRelief(ZnReliefStyle relief)
{
  switch (relief) {
  case ZN_RELIEF_FLAT:
    return RELIEF_FLAT_SPEC;
  case ZN_RELIEF_SUNKEN:
    return RELIEF_SUNKEN_SPEC;
  case ZN_RELIEF_RAISED:
    return RELIEF_RAISED_SPEC;
  case ZN_RELIEF_GROOVE:
    return RELIEF_GROOVE_SPEC;
  case ZN_RELIEF_RIDGE:
    return RELIEF_RIDGE_SPEC;
  case ZN_RELIEF_ROUND_SUNKEN:
    return RELIEF_ROUND_SUNKEN_SPEC;
  case ZN_RELIEF_ROUND_RAISED:
    return RELIEF_ROUND_RAISED_SPEC;
  case ZN_RELIEF_ROUND_GROOVE:
    return RELIEF_ROUND_GROOVE_SPEC;
  case ZN_RELIEF_ROUND_RIDGE:
    return RELIEF_ROUND_RIDGE_SPEC;
  case ZN_RELIEF_SUNKEN_RULE:
    return RELIEF_SUNKEN_RULE_SPEC;
  case ZN_RELIEF_RAISED_RULE:
    return RELIEF_RAISED_RULE_SPEC;
  default:
    return "unknown relief";
  }
}

/*
 ****************************************************************
 *
 * Code for borders.
 *
 ****************************************************************
 */
#define BORDER_LEFT_SPEC	"left"
#define BORDER_RIGHT_SPEC	"right"
#define	BORDER_TOP_SPEC		"top"
#define BORDER_BOTTOM_SPEC	"bottom"
#define BORDER_CONTOUR_SPEC	"contour"
#define BORDER_COUNTER_OBLIQUE_SPEC	"counteroblique"
#define BORDER_OBLIQUE_SPEC	"oblique"
#define NO_BORDER_SPEC		"noborder"

int
ZnGetBorder(ZnWInfo	*wi,
	    Tcl_Obj	*name,
	    ZnBorder	*border)
{
  unsigned int j, len, largc;
  Tcl_Obj      **largv;
  char	       *str;

  *border = ZN_NO_BORDER;
  if (Tcl_ListObjGetElements(wi->interp, name,
			     &largc, &largv) == TCL_ERROR) {
  border_error:
    Tcl_AppendResult(wi->interp, "bad line shape \"", Tcl_GetString(name),
		     "\": must be a list of ",
		     BORDER_LEFT_SPEC, ", ",
		     BORDER_RIGHT_SPEC, ", ",
		     BORDER_TOP_SPEC, ", ",
		     BORDER_BOTTOM_SPEC, ", ",
		     BORDER_COUNTER_OBLIQUE_SPEC, ", ",
		     BORDER_OBLIQUE_SPEC, " or ",
		     BORDER_CONTOUR_SPEC, ", ",
		     NO_BORDER_SPEC, " alone",
		     NULL);
    return TCL_ERROR;
  }
  for (j = 0; j < largc; j++) {
    str = Tcl_GetString(largv[j]);
    len = strlen(str);
    if (strncmp(str, BORDER_LEFT_SPEC, len) == 0) {
      *border |= ZN_LEFT_BORDER;
    }
    else if (strncmp(str, BORDER_RIGHT_SPEC, len) == 0) {
      *border |= ZN_RIGHT_BORDER;
    }
    else if (strncmp(str, BORDER_TOP_SPEC, len) == 0) {
      *border |= ZN_TOP_BORDER;
    }
    else if (strncmp(str, BORDER_BOTTOM_SPEC, len) == 0) {
      *border |= ZN_BOTTOM_BORDER;
    }
    else if (strncmp(str, BORDER_CONTOUR_SPEC, len) == 0) {
      *border |= ZN_CONTOUR_BORDER;
    }
    else if (strncmp(str, BORDER_OBLIQUE_SPEC, len) == 0) {
      *border |= ZN_OBLIQUE;
    }
    else if (strncmp(str, BORDER_COUNTER_OBLIQUE_SPEC, len) == 0) {
      *border |= ZN_COUNTER_OBLIQUE;
    }
    else if (strncmp(str, NO_BORDER_SPEC, len) == 0) {
      *border = ZN_NO_BORDER;
    }
    else {
      goto border_error;
    }
  }
  return TCL_OK;
}

/*
 * name must be large enough to hold the returned string.
 * 64 chars should be enough with the current values.
 */
void
ZnNameOfBorder(ZnBorder border,
	       char	*name)
{
  if (border == ZN_NO_BORDER) {
    strcpy(name, NO_BORDER_SPEC);
    return;
  }
  name[0] = 0;
  if ((border & ZN_CONTOUR_BORDER) == ZN_CONTOUR_BORDER) {
    strcat(name, BORDER_CONTOUR_SPEC);
  }
  else {
    if (border & ZN_LEFT_BORDER) {
      strcat(name, BORDER_LEFT_SPEC);
    }
    if (border & ZN_RIGHT_BORDER) {  
      if (name[0] != 0) {
	strcat(name, " ");
      }
      strcat(name, BORDER_RIGHT_SPEC);
    }
    if (border & ZN_TOP_BORDER) {
      if (name[0] != 0) {
	strcat(name, " ");
      }
      strcat(name, BORDER_TOP_SPEC);
    }
    if (border & ZN_BOTTOM_BORDER) {
      if (name[0] != 0) {
	    strcat(name, " ");
      }
      strcat(name, BORDER_BOTTOM_SPEC);
    }
  }
  if (border & ZN_OBLIQUE) {
    if (name[0] != 0) {
      strcat(name, " ");
    }
    strcat(name, BORDER_OBLIQUE_SPEC);
  }
  if (border & ZN_COUNTER_OBLIQUE) {
    if (name[0] != 0) {
      strcat(name, " ");
    }
    strcat(name, BORDER_COUNTER_OBLIQUE_SPEC);
  }
}

/*
 ****************************************************************
 *
 * Code for line shapes.
 *
 ****************************************************************
 */
#define STRAIGHT_SPEC		"straight"
#define RIGHT_LIGHTNING_SPEC	"rightlightning"
#define LEFT_LIGHTNING_SPEC	"leftlightning"
#define RIGHT_CORNER_SPEC	"rightcorner"
#define LEFT_CORNER_SPEC	"leftcorner"
#define DOUBLE_RIGHT_CORNER_SPEC  "doublerightcorner"
#define DOUBLE_LEFT_CORNER_SPEC	"doubleleftcorner"

int
ZnGetLineShape(ZnWInfo		*wi,
	       char		*name,
	       ZnLineShape	*line_shape)
{
  unsigned int  len;

  len = strlen(name);
  if (strncmp(name, STRAIGHT_SPEC, len) == 0) {
    *line_shape = ZN_LINE_STRAIGHT;
  }
  else if (strncmp(name, RIGHT_LIGHTNING_SPEC, len) == 0) {
    *line_shape = ZN_LINE_RIGHT_LIGHTNING;
  }
  else if (strncmp(name, LEFT_LIGHTNING_SPEC, len) == 0) {
    *line_shape = ZN_LINE_LEFT_LIGHTNING;
  }
  else if (strncmp(name, RIGHT_CORNER_SPEC, len) == 0) {
    *line_shape = ZN_LINE_RIGHT_CORNER;
  }
  else if (strncmp(name, LEFT_CORNER_SPEC, len) == 0) {
    *line_shape = ZN_LINE_LEFT_CORNER;
  }
  else if (strncmp(name, DOUBLE_RIGHT_CORNER_SPEC, len) == 0) {
    *line_shape = ZN_LINE_DOUBLE_RIGHT_CORNER;
  }
  else if (strncmp(name, DOUBLE_LEFT_CORNER_SPEC, len) == 0) {
    *line_shape = ZN_LINE_DOUBLE_LEFT_CORNER;
  }
  else  {
    Tcl_AppendResult(wi->interp, "bad line shape \"", name, "\": must be ",
		     STRAIGHT_SPEC, ", ",
		     RIGHT_LIGHTNING_SPEC, ", ",
		     LEFT_LIGHTNING_SPEC, ", ",
		     RIGHT_CORNER_SPEC, ", ",
		     LEFT_CORNER_SPEC, ", ",
		     DOUBLE_RIGHT_CORNER_SPEC, ", ",
		     DOUBLE_LEFT_CORNER_SPEC,
		     NULL);
    return TCL_ERROR;
  }
  return TCL_OK;
}

char *
ZnNameOfLineShape(ZnLineShape line_shape)
{
  switch (line_shape) {
  case ZN_LINE_STRAIGHT:
    return STRAIGHT_SPEC;
  case ZN_LINE_RIGHT_LIGHTNING:
    return RIGHT_LIGHTNING_SPEC;
  case ZN_LINE_LEFT_LIGHTNING:
    return LEFT_LIGHTNING_SPEC;
  case ZN_LINE_RIGHT_CORNER:
    return RIGHT_CORNER_SPEC;
  case ZN_LINE_LEFT_CORNER:
    return LEFT_CORNER_SPEC;
  case ZN_LINE_DOUBLE_RIGHT_CORNER:
    return DOUBLE_RIGHT_CORNER_SPEC;
  case ZN_LINE_DOUBLE_LEFT_CORNER:
    return DOUBLE_LEFT_CORNER_SPEC;
  default:
    return "unknown line shape";
  }
}

/*
 ****************************************************************
 *
 * Code for line styles.
 *
 ****************************************************************
 */
#define SIMPLE_SPEC		"simple"
#define	DASHED_SPEC		"dashed"
#define DOTTED_SPEC		"dotted"
#define	MIXED_SPEC		"mixed"

int
ZnGetLineStyle(ZnWInfo		*wi,
	       char		*name,
	       ZnLineStyle	*line_style)
{
  unsigned int len;

  len = strlen(name);
  if (strncmp(name, SIMPLE_SPEC, len) == 0)
    *line_style = ZN_LINE_SIMPLE;
  else if (strncmp(name, DASHED_SPEC, len) == 0)
    *line_style = ZN_LINE_DASHED;
  else if (strncmp(name, MIXED_SPEC, len) == 0)
    *line_style = ZN_LINE_MIXED;
  else if (strncmp(name, DOTTED_SPEC, len) == 0)
    *line_style = ZN_LINE_DOTTED;
  else  {
    Tcl_AppendResult(wi->interp, "bad line style \"", name, "\": must be ",
		     SIMPLE_SPEC, ", ",
		     DASHED_SPEC, ", ",
		     DOTTED_SPEC, ", ",
		     MIXED_SPEC,
		     NULL);
    return TCL_ERROR;	      
  }
  return TCL_OK;
}

char *
ZnNameOfLineStyle(ZnLineStyle line_style)
{
  switch (line_style) {
  case ZN_LINE_SIMPLE:
    return SIMPLE_SPEC;
  case ZN_LINE_DASHED:
    return DASHED_SPEC;
  case ZN_LINE_MIXED:
    return MIXED_SPEC;
  case ZN_LINE_DOTTED:
    return DOTTED_SPEC;
  default:
    return "unknown line style";
  }
}

/*
 ****************************************************************
 *
 * Code for leader anchors.
 *
 * Format is: lChar leftLeaderAnchor [ lChar rightLeaderAnchor]
 *
 * If lChar is a '|', leftLeaderAnchor and rightLeaderAnchor are the indices
 * of the fields that serve to anchor the label's leader. More specifically
 * the bottom left corner of the left field and the bottom right corner of
 * the right field are used as the anchors.
 * If lChar is '%', leftLeaderAnchor and rightLeaderAnchor should be
 * specified as 'valxval', 'val' being a percentage (max 100) of the
 * width/height of the label bounding box.
 * If rightLeaderAnchor is not specified it defaults to leftLeaderAnchor.
 * If neither of them are specified, the center of the label is used as an
 * anchor.
 *
 ****************************************************************
 */
int
ZnGetLeaderAnchors(ZnWInfo		*wi,
		   char			*name,
		   ZnLeaderAnchors	*leader_anchors)
{
  int	anchors[4];
  int	index, num_tok, anchor_index=0;

  *leader_anchors = NULL;
  while (*name && (*name == ' ')) {
    name++;
  }
  while (*name && (anchor_index < 4)) {
    switch (*name) {
    case '|':
      num_tok = sscanf(name, "|%d%n", &anchors[anchor_index], &index);
      if (num_tok != 1) {
      la_error:
	Tcl_AppendResult(wi->interp, " incorrect leader anchors \"",
			 name, "\"", NULL);
	return TCL_ERROR;
      }
      anchors[anchor_index+1] = -1;
      break;
    case '%':
      num_tok = sscanf(name, "%%%dx%d%n", &anchors[anchor_index],
		       &anchors[anchor_index+1], &index);
      if (num_tok != 2) {
	goto la_error;
      }
      if (anchors[anchor_index] < 0) {
	anchors[anchor_index] = 0;
      }
      if (anchors[anchor_index] > 100) {
	anchors[anchor_index] = 100;
      }
      if (anchors[anchor_index+1] < 0) {
	anchors[anchor_index+1] = 0;
      }
      if (anchors[anchor_index+1] > 100) {
	anchors[anchor_index+1] = 100;
      }
      break;
    default:
      goto la_error;
    }
    anchor_index += 2;
    name += index;
  }
  /*
   * If empty, pick the default (center of the bounding box).
   */
  if (anchor_index != 0) {
    *leader_anchors = ZnMalloc(sizeof(ZnLeaderAnchorsStruct));
    (*leader_anchors)->left_x = anchors[0];
    (*leader_anchors)->left_y = anchors[1];
    if (anchor_index == 2) {
      (*leader_anchors)->right_x = (*leader_anchors)->left_x;
      (*leader_anchors)->right_y = (*leader_anchors)->left_y;
    }
    else {
      (*leader_anchors)->right_x = anchors[2];
      (*leader_anchors)->right_y = anchors[3];
    }
  }
  return TCL_OK;
}

/*
 * name must be large enough to hold the returned string.
 */
void
ZnNameOfLeaderAnchors(ZnLeaderAnchors leader_anchors,
		      char	      *name)
{
  unsigned int	count;
  
  if (!leader_anchors) {
    strcpy(name, "%50x50");
  }
  else {
    if (leader_anchors->left_y < 0) {
      count = sprintf(name, "|%d", leader_anchors->left_x);
    }
    else {
      count = sprintf(name, "%%%dx%d", leader_anchors->left_x,
		      leader_anchors->left_y);
    }
    name += count;
    if (leader_anchors->right_y < 0) {
      sprintf(name, "|%d", leader_anchors->right_x);
    }
    else {
      sprintf(name, "%%%dx%d", leader_anchors->right_x, leader_anchors->right_y);
    }
  }
}

/*
 ******************************************************************
 *
 * Code for label formats.
 *
 ******************************************************************
 */
static Tcl_HashTable	format_cache;
static ZnBool		format_inited = False;


static char
CharToAttach(int attach)
{
  switch (attach) {
  case '>':
    return ZN_LF_ATTACH_FWD;
  case '<':
    return ZN_LF_ATTACH_BWD;
  case '^':
    return ZN_LF_ATTACH_LEFT;
  case '$':
    return ZN_LF_ATTACH_RIGHT;
  case '+':
  default:
    return ZN_LF_ATTACH_PIXEL;
  }
}

static char
CharToDim(int	dim)
{
  switch (dim) {
  case 'f':
    return ZN_LF_DIM_FONT;
  case 'i':
    return ZN_LF_DIM_ICON;
  case 'a':
    return ZN_LF_DIM_AUTO;
  case 'l':
    return ZN_LF_DIM_LABEL;
  case 'x':
  default:
    return ZN_LF_DIM_PIXEL;
  }
}

/*
 * The new format is as follow. Parameters between [] are
 * optional and take default values when omitted. The spaces can appear
 * between blocks but not inside.
 *
 *	[ WidthxHeight ] [ field0Spec ][ field1Spec ]...[ fieldnSpec ]
 *
 * Width and Height set the size of the clipping box surrounding
 * the label. If it is not specified, there will be no clipping.
 * It it is specified alone it is the size of the only displayed
 * field (0).
 *
 * fieldSpec is:
 *	sChar fieldWidth sChar fieldHeight [pChar fieldX pChar fieldY].
 *
 * Each field description refers to the field of same index in the field
 * array.
 * If sChar is 'x', the dimension is in pixel. If sChar is 'f', the
 * dimension is in percentage of the mean width/height of a character (in the
 * field font). If sChar is 'i', the dimension is in percentage of the size
 * of the image in the field. If sChar is 'a', the dimension is automatically
 * adjusted to match the field's content plus the given value in pixels.
 * If pChar is '+' the position is in pixel (possibly negative). If it is
 * '<' the position is the index of the field at the left/top of which the
 * current field should be  attached. If it is '>' the position is the index
 * of the field at the right/bottom of which the current field should be
 * attached. If pChar is '^' the position is the index of the field used to
 * align the left/top border (left on left or top on top). If pChar is '$' the
 * position is the index of the field used to align the right/bottom border
 * (right on right or bottom on bottom). 
 * The positional parameters can be omitted if there is only one field.
 *
 */
ZnLabelFormat
ZnLFCreate(Tcl_Interp	*interp,
	   char		*format_str,
	   unsigned int	num_fields)
{
  ZnList	fields;
  Tcl_HashEntry	*entry;
  ZnFieldFormatStruct field_struct;
  ZnFieldFormat	field_array;
  ZnLabelFormat	format;
  int		width, height;
  ZnDim		c_width=0.0, c_height=0.0;
  int		index, num_tok, num_ffs, new;
  unsigned int	field_index=0;
  char		*ptr = format_str, *next_ptr;
  char		x_char, y_char;

  if (!format_inited) {
    Tcl_InitHashTable(&format_cache, TCL_STRING_KEYS);
    format_inited = True;
  }
  entry = Tcl_CreateHashEntry(&format_cache, format_str, &new);
  if (!new) {
    format = (ZnLabelFormat) Tcl_GetHashValue(entry);
    if (format->num_fields <= num_fields) {
      format->ref_count++;
      return format;
    }
    else {
      Tcl_AppendResult(interp, "too many fields in label format: \"",
		       format_str, "\"", NULL);
      return NULL;
    }
  }
  
  fields = ZnListNew(1, sizeof(ZnFieldFormatStruct));
  
  /*
   * Try to see if it starts with a number or a leader spec.
   */
  while (*ptr && (*ptr == ' ')) {
    ptr++;
  }
  if (!*ptr) {
    goto lf_error_syn;
  }
  if ((*ptr != 'x') && (*ptr != 'f') && (*ptr != 'i') &&
      (*ptr != 'a') && (*ptr != 'l')) {
    c_width = (ZnDim) strtod(ptr, &next_ptr);
    if ((ptr == next_ptr) || (*next_ptr != 'x')) {
    lf_error_syn:
      Tcl_AppendResult(interp, "invalid label format specification \"",
		       ptr, "\"", NULL);
    lf_error:
      Tcl_DeleteHashEntry(entry);
      ZnListFree(fields);
      return NULL;
    }
    ptr = next_ptr+1;
    c_height = (ZnDim) strtod(ptr, &next_ptr);
    if (ptr == next_ptr) {
      goto lf_error_syn;
    }
    ptr = next_ptr;
  }
  if (!*ptr) {
    /* It is a simple spec, one field. */
    field_struct.x_attach = field_struct.y_attach = ZN_LF_ATTACH_PIXEL;
    field_struct.x_dim = field_struct.y_dim = ZN_LF_DIM_PIXEL;
    field_struct.x_spec = field_struct.y_spec = 0;
    field_struct.width_spec = (short) c_width;
    field_struct.height_spec = (short) c_height;
    c_width = c_height = 0.0;
    ZnListAdd(fields, &field_struct, ZnListTail);
    goto lf_end_parse;
  }
  
  /*
   * Parse the field specs.
   */
 lf_parse2:
  while (*ptr && (*ptr == ' ')) {
    ptr++;
  }
  if (!*ptr) {
    goto lf_end_parse;
  }
  /* Preset the default field values. */
  field_struct.x_spec = field_struct.y_spec = 0;
  field_struct.x_attach = field_struct.y_attach = ZN_LF_ATTACH_PIXEL;
  field_struct.x_dim = field_struct.y_dim = ZN_LF_DIM_PIXEL;
  if ((*ptr == 'x') || (*ptr == 'f') || (*ptr == 'i') ||
      (*ptr == 'a') || (*ptr == 'l')) {
    num_tok = sscanf(ptr, "%c%d%c%d%n", &x_char, &width,
		     &y_char, &height, &index);
    if (num_tok != 4) {
      goto lf_error_syn;
    }
    if (width < 0) {
      width = 0;
    }
    if (height < 0) {
      height = 0;
    }
    field_struct.x_dim = CharToDim(x_char);
    field_struct.y_dim = CharToDim(y_char);

    ptr += index;
    if ((*ptr == '>') || (*ptr == '<') || (*ptr == '+') ||
	(*ptr == '^') || (*ptr == '$')) {
      num_tok = sscanf(ptr, "%c%d%c%d%n", &x_char, &field_struct.x_spec,
		       &y_char, &field_struct.y_spec, &index);
      if (num_tok != 4) {
	goto lf_error_syn;
      }
      field_struct.x_attach = CharToAttach(x_char);
      field_struct.y_attach = CharToAttach(y_char);

      ptr += index;
    }
    else if (!*ptr || (field_index != 0)) {
      /* An incomplete field spec is an error if there are several fields. */
      Tcl_AppendResult(interp, "incomplete field in label format: \"",
		       ptr-index, "\"", NULL);
      goto lf_error;		
    }
    if (field_index >= num_fields) {
      Tcl_AppendResult(interp, "too many fields in label format: \"",
		       format_str, "\"", NULL);
      goto lf_error;
    }
    field_struct.width_spec = (short) width;
    field_struct.height_spec = (short) height;
    ZnListAdd(fields, &field_struct, ZnListTail);
    field_index++;
    goto lf_parse2;
  }
  else {
    goto lf_error_syn;
  }
  
 lf_end_parse:
  field_array = (ZnFieldFormat) ZnListArray(fields);
  num_ffs = ZnListSize(fields);
  
  format = (ZnLabelFormat) ZnMalloc(sizeof(ZnLabelFormatStruct) +
				    (num_ffs-1) * sizeof(ZnFieldFormatStruct));
  format->clip_width = (short) c_width;
  format->clip_height = (short) c_height;
  format->num_fields = num_ffs;
  memcpy(&format->fields, field_array, num_ffs * sizeof(ZnFieldFormatStruct));
  ZnListFree(fields);

  format->ref_count = 1;
  format->entry = entry;
  Tcl_SetHashValue(entry, (ClientData) format);
  
  return format;
}


ZnLabelFormat
ZnLFDuplicate(ZnLabelFormat	lf)
{
  lf->ref_count++;
  return lf;
}


void
ZnLFDelete(ZnLabelFormat	lf)
{
  lf->ref_count--;
  if (lf->ref_count == 0) {
    Tcl_DeleteHashEntry(lf->entry);
    ZnFree(lf);
  }
}


char *
ZnLFGetString(ZnLabelFormat	lf)
{
  return Tcl_GetHashKey(&format_cache, lf->entry);

#if 0
  ZnFieldFormat	ff;
  char		*ptr;
  char		x_char, y_char, w_char, h_char;
  unsigned int	i, count;
  
  ptr = str;
  if ((lf->clip_width != 0) || (lf->clip_height != 0)) {
    count = sprintf(ptr, "%dx%d", lf->clip_width, lf->clip_height);
    ptr += count;
  }
  if (lf->left_y < 0) {
    count = sprintf(ptr, "|%d", lf->left_x);
  }
  else {
    count = sprintf(ptr, "%%%dx%d", lf->left_x, lf->left_y);
  }
  ptr += count;
  if (lf->right_y < 0) {
    count = sprintf(ptr, "|%d", lf->right_x);
  }
  else {
    count = sprintf(ptr, "%%%dx%d", lf->right_x, lf->right_y);
  }
  ptr += count;
  for (i = 0; i < lf->num_fields; i++) {
    ff = &lf->fields[i];
    x_char = AttachToChar(ff->x_attach);
    y_char = AttachToChar(ff->y_attach);
    w_char = DimToChar(ff->x_dim);
    h_char = DimToChar(ff->y_dim);
    count = sprintf(ptr, "%c%d%c%d%c%d%c%d",
		    w_char, ff->width_spec, h_char, ff->height_spec,
		    x_char, ff->x_spec, y_char, ff->y_spec);
    ptr += count;
  }
  *ptr = 0;
#endif
}


/*
 * If the clip box has both its width and its height
 * set to zero, it means that there is no clipbox.
 */
ZnBool
ZnLFGetClipBox(ZnLabelFormat	lf,
	       ZnDim		*w,
	       ZnDim		*h)
{
  if ((lf->clip_width == 0) && (lf->clip_height == 0)) {
    return False;
  }

  *w = (ZnDim) lf->clip_width;
  *h = (ZnDim) lf->clip_height;
  
  return True;
}


void
ZnLFGetField(ZnLabelFormat	lf,
	     unsigned int	field,
	     char		*x_attach,
	     char		*y_attach,
	     char		*x_dim,
	     char		*y_dim,
	     int		*x_spec,
	     int		*y_spec,
	     short		*width_spec,
	     short		*height_spec)
{
  ZnFieldFormat	fptr;

  fptr = &lf->fields[field];
  *x_attach = fptr->x_attach;
  *y_attach = fptr->y_attach;
  *x_dim = fptr->x_dim;
  *y_dim = fptr->y_dim;
  *x_spec = fptr->x_spec;
  *y_spec = fptr->y_spec;
  *width_spec = fptr->width_spec;
  *height_spec = fptr->height_spec;
}


/*
 ****************************************************************
 *
 * Code for line ends.
 *
 ****************************************************************
 */
static Tcl_HashTable	line_end_cache;
static ZnBool		line_end_inited = False;


ZnLineEnd
ZnLineEndCreate(Tcl_Interp	*interp,
		char		*line_end_str)
{
  Tcl_HashEntry	*entry;
  ZnLineEnd	le;
  int		new, argc;
  ZnReal	a, b, c;
  
  if (!line_end_inited) {
    Tcl_InitHashTable(&line_end_cache, TCL_STRING_KEYS);
    line_end_inited = True;
  }

  entry = Tcl_CreateHashEntry(&line_end_cache, line_end_str, &new);
  if (!new) {
    le = (ZnLineEnd) Tcl_GetHashValue(entry);
    le->ref_count++;
    return le;
  }

  argc = sscanf(line_end_str, "%lf %lf %lf", &a, &b, &c);
  if (argc == 3) {
    le = (ZnLineEnd) ZnMalloc(sizeof(ZnLineEndStruct));
    le->shape_a = a;
    le->shape_b = b;
    le->shape_c = c;
    le->entry = entry;
    le->ref_count = 1;
    Tcl_SetHashValue(entry, (ClientData) le);
    return le;
  }
  else {
    Tcl_AppendResult(interp, "incorrect line end spec: \"",
		     line_end_str, "\", should be: shapeA shapeB shapeC", NULL);
    return NULL;
  }
}


char *
ZnLineEndGetString(ZnLineEnd	le)
{
  return Tcl_GetHashKey(&line_end_cache, le->entry);
}


void
ZnLineEndDelete(ZnLineEnd	le)
{
  le->ref_count--;
  if (le->ref_count == 0) {
    Tcl_DeleteHashEntry(le->entry);
    ZnFree(le);
  }
}


ZnLineEnd
ZnLineEndDuplicate(ZnLineEnd	le)
{
  le->ref_count++;
  return le;
}


/*
 ******************************************************************
 *
 * Code for fill rules. They are directly inhereted from the
 * GLU tesselator constants.
 *
 ******************************************************************
 */
#define FILL_RULE_ODD_SPEC	 "odd"
#define FILL_RULE_NON_ZERO_SPEC	 "nonzero"
#define FILL_RULE_POSITIVE_SPEC	 "positive"
#define FILL_RULE_NEGATIVE_SPEC	 "negative"
#define FILL_RULE_ABS_GEQ_2_SPEC "abs_geq_2"

int
ZnGetFillRule(ZnWInfo	 *wi,
	      char	 *name,
	      ZnFillRule *fill_rule)
{
  unsigned int len;

  len = strlen(name);
  if (strncmp(name, FILL_RULE_ODD_SPEC, len) == 0) {
    *fill_rule = GLU_TESS_WINDING_ODD;
  }
  else if (strncmp(name, FILL_RULE_NON_ZERO_SPEC, len) == 0) {
    *fill_rule = GLU_TESS_WINDING_NONZERO;
  }
  else if (strncmp(name, FILL_RULE_POSITIVE_SPEC, len) == 0) {
    *fill_rule = GLU_TESS_WINDING_POSITIVE;
  }
  else if (strncmp(name, FILL_RULE_NEGATIVE_SPEC, len) == 0) {
    *fill_rule = GLU_TESS_WINDING_NEGATIVE;
  }
  else if (strncmp(name, FILL_RULE_ABS_GEQ_2_SPEC, len) == 0) {
    *fill_rule = GLU_TESS_WINDING_ABS_GEQ_TWO;
  }
  else  {
    Tcl_AppendResult(wi->interp, "bad fill rule \"", name, "\": must be ",
		     FILL_RULE_ODD_SPEC, ", ",
		     FILL_RULE_NON_ZERO_SPEC, ", ",
		     FILL_RULE_POSITIVE_SPEC, ", ",
		     FILL_RULE_NEGATIVE_SPEC, ", ",
		     FILL_RULE_ABS_GEQ_2_SPEC,
		     NULL);
    return TCL_ERROR;
  }
  return TCL_OK;
}

char *
ZnNameOfFillRule(ZnFillRule fill_rule)
{
  switch (fill_rule) {
  case GLU_TESS_WINDING_ODD:
    return FILL_RULE_ODD_SPEC;
  case GLU_TESS_WINDING_NONZERO:
    return FILL_RULE_NON_ZERO_SPEC;
  case GLU_TESS_WINDING_POSITIVE:
    return FILL_RULE_POSITIVE_SPEC;
  case GLU_TESS_WINDING_NEGATIVE:
    return FILL_RULE_NEGATIVE_SPEC;
  case GLU_TESS_WINDING_ABS_GEQ_TWO:
    return FILL_RULE_ABS_GEQ_2_SPEC;
  default:
    return "unknown fill rule";
  }
}


/*
 ******************************************************************
 *
 * Code for auto alignments in fields.
 *
 ******************************************************************
 */
int
ZnGetAutoAlign(ZnWInfo		*wi,
	       char		*name,
	       ZnAutoAlign	*aa)
{
  int       j;
  
  if (strcmp(name, "-") == 0) {
    aa->automatic = False;
  }
  else if (strlen(name) == 3) {
    aa->automatic = True;
    for (j = 0; j < 3; j++) {
      switch(name[j]) {
      case 'l':
      case 'L':
	aa->align[j] = TK_JUSTIFY_LEFT;
	break;
      case 'c':
      case 'C':
	aa->align[j] = TK_JUSTIFY_CENTER;
	break;
      case 'r':
      case 'R':
	aa->align[j] = TK_JUSTIFY_RIGHT;
	break;
      default:
	goto aa_error;
      }
    }
  }
  else {
  aa_error:
    Tcl_AppendResult(wi->interp, "invalid auto alignment specification \"", name,
		     "\" should be - or a triple of lcr", NULL);
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
 * name must be large enough to hold the returned string.
 * 64 chars should be enough with the current values.
 */
void
ZnNameOfAutoAlign(ZnAutoAlign *aa,
		  char	      *name)
{
  unsigned int i;
  
  if (aa->automatic == False) {
    strcpy(name, "-");
  }
  else {
    name[0] = 0;
    for (i = 0; i < 3; i++) {
      switch (aa->align[i]) {
      case TK_JUSTIFY_LEFT:
	strcat(name, "l");
	break;
      case TK_JUSTIFY_CENTER:
	strcat(name, "c");
	break;
      case TK_JUSTIFY_RIGHT:
	strcat(name, "r");
	break;
      }
    }
  }
}
