/*
 * zkfvflib.c --- Kanji font operator for VFlib.
 * Copyright (C) ASAYAMA Kazunori  1994.
 */

#include "math_.h"
#include "memory_.h"
#include "ghost.h"
#include "gp.h"
#include "oper.h"
#include "errors.h"
#include "gspath.h"
#include "gsmatrix.h"
#include "gscoord.h"
#include "gsstate.h"
#include "state.h"
#include "stream.h"
#include "files.h"

/* Including the header file for VFlib. */
#include "VF.h"
#ifdef __MSDOS__
#  include "dos_.h"
#endif

/* Forward declaration */
extern int kf_is_vchar(P1(int));
extern int kf_vmatrix(P5(int, floatp, floatp, gs_rect *, gs_matrix *));
private int draw_font(P2(const ref *, int));
private int open_font(P1(const ref *));
     
#define D_X_CL	(OUTLINE_SIZE * 0.5 + OUTLINE_OFFSET)
#define D_Y_CL	(OUTLINE_SIZE * 0.5 + OUTLINE_OFFSET)
#define	D_Y_BL	(OUTLINE_SIZE * 0.9 + OUTLINE_OFFSET)

#define B_X_CL  500		/* horizontal center in BuildChar */
#define B_Y_CL  400		/* vertical center in BuildChar */
#define B_Y_BL  0		/* base line in BuildChar */

#ifndef VFONTCAP_BASE
#  define VFONTCAP_BASE "vfontcap"
#endif
#ifndef MAXPATHLEN
#  define MAXPATHLEN 256
#endif

/* zkfvflib */
int
zkfVFlib(register os_ptr op)
{
  ref *font_name;
  int code, len, jis_code, wmode;
  
  check_type(op[-2], t_integer);                /* JIS Code */
  check_type(op[-1], t_integer);                /* WMode */
  check_type(op[0], t_string);          /* Font Name */ 
  
  font_name = op;
  
  jis_code = op[-2].value.intval;
  wmode = op[-1].value.intval;
  
  if(wmode && kf_is_vchar(jis_code)) {
    /* vertical kanji character */
    gs_matrix smat, vmat;
    gs_rect bbox;
    
    if((code = gs_gsave(igs)) < 0)
      return code;
    if((code = draw_font(font_name, jis_code)) < 0)
      return code;
    if((code = gs_flattenpath(igs)) < 0)
      return code;
    if((code = gs_pathbbox(igs, &bbox)) < 0)
      return code;
    if((code = gs_grestore(igs)) < 0)
      return code;
    
    gs_currentmatrix(igs, &smat);
    kf_vmatrix(jis_code, 
               (floatp)B_X_CL, (floatp)B_Y_CL, &bbox, &vmat);
    if((code = gs_concat(igs, &vmat)) < 0)
      return code;
    if((code = draw_font(font_name, jis_code)) < 0)
      return code;
    gs_setmatrix(igs, &smat);
  }
  else if((code = draw_font(font_name, jis_code)) < 0) {
    return code;
  }
  
  pop(3);
  return 0;
}

/* -------- Initialization procedure -------- */

op_def zkfVFlib_op_defs[] = {
  {"3kfVFlib", zkfVFlib},
  op_def_end(0)
  };

/* -------- Internal routines -------- */

typedef struct index_item_s {
  struct index_item_s *next;
  char *str;
  int fd;
} index_item;
private index_item *hash_index[256];

/* Hash function */
private int 
hash(const ref *str)
{
  unsigned char hash;
  int i;
  byte *b;
  
  i = r_size(str);
  b = str->value.bytes;
  for(hash = 0;i>0; i--,b++)
    hash ^= *b;
  
  return hash;
}

/* store to hash table */
private int
store(const ref *str, int fd)
{
  int key = hash(str);
  index_item *item;
  int len = r_size(str);
  
  if((item = (index_item *)malloc(sizeof(index_item))) == 0)
    return e_VMerror;
  if((item->str = (char *)malloc(len+1)) == 0)
    return e_VMerror;
  memcpy(item->str,str->value.bytes,len);
  item->str[len] = 0;
  item->next = hash_index[key];
  item->fd = fd;
  hash_index[key] = item;
  return fd;
}

/* search hash table. */
private int
search(const ref *str)
{
  index_item *item = hash_index[hash(str)];
  int len = r_size(str);
  char *b = (char *)str->value.bytes;
  
  while(item) {
    if(strlen(item->str) == len && strncmp(b,item->str,len) == 0)
      return item->fd;		/* Found */
    item = item->next;
  }
  return -1;			/* Not found */
}

/* initialize the library */
private int
init_vflib(void)
{
  char *pfcap;
  int code;

  /* initialize hash table */
  for (code=0;code<256;code++)
    hash_index[code] = NULL;

  pfcap = getenv("VFONTCAP");
  if (pfcap == NULL) { /* environment variable VFONTCAP is not set. */
    char *pfcapb = VFONTCAP_BASE;
    
    pfcap = malloc(MAXPATHLEN+3);
    if (pfcap == NULL) return e_VMerror;
    
    if (!access(pfcapb,0)) { /* found on current directory. */
#ifdef __MSDOS__
      getcwd(pfcap+2,MAXPATHLEN);
      if (strlen(pfcap+2)>=2 && pfcap[3] == ':') {
        /* file name includes drive name. */
        pfcap += 2;
      }
      else {
        union REGS r;
        r.h.ah = 0x19;
        intdos(&r,&r);
        pfcap[0] = 'a' + r.h.al;
        pfcap[1] = ':';
      }
#else
      getcwd(pfcap,MAXPATHLEN);
#endif
      strcat(pfcap,gp_file_name_concat_string(pfcap,strlen(pfcap),
                                               pfcapb,strlen(pfcapb)));
      strcat(pfcap,pfcapb);
    }
    else {
      FILE *fp;
      strcpy(pfcap,pfcapb);
      fp = lib_fopen(pfcap);
      if (fp) fclose(fp);  /* found on GS's searching path. */
      else { /* default vfontcap*/
        free(pfcap);
        pfcap = NULL;
      }
    }
  }
  code = VF_Init(pfcap);	/* Call initializing routine */
  if (code<0) {
    code = e_ioerror;
    eprintf("VFlib: Failure in initialization.\n");
  }
  return code;
}

/*
 * Open font 
 */
private int
open_font(const ref *font_name)
{
  static int initialized_f = 0;
  int code;
  
  if ( ! initialized_f ) {
    code = init_vflib();
    if (code < 0) return code;	/* error */
    initialized_f = 1; /* Now, initialization done. */
  }
  
  code = search(font_name);
  if (code < 0) {
    int len = r_size(font_name);
    char *fn = gs_malloc(len+1,1,"VFlib(open_font)");
    
    if (fn == NULL) return e_VMerror;
    memcpy(fn,font_name->value.bytes,len);
    fn[len] = 0;
    code = VF_OpenFont(fn);
    if (code < 0){
      eprintf1("VFlib: Cannot open the font `%s'.\n",fn);
      code = e_ioerror;
    }
    else {
      code = store(font_name,code);
    }
    gs_free(fn,len+1,1,"VFlib(open_font)");
  }
  
  return code;
}

/* Drawing routine. */

/* data coordination -> character coordination */
#define Y_FACTOR ((floatp)(B_Y_CL - B_Y_BL) / (floatp)(D_Y_CL - D_Y_BL))
#define X_FACTOR ((floatp)(Y_FACTOR > 0 ? Y_FACTOR : - Y_FACTOR))
#define map_x(x) ((floatp)B_X_CL + ((floatp)(x) - (floatp)D_X_CL) * X_FACTOR)
#define map_y(y) ((floatp)B_Y_CL + ((floatp)(y) - (floatp)D_Y_CL) * Y_FACTOR)

/* drawing */
#define moveto(x, y)    gs_moveto(igs, map_x(x), map_y(y))
#define lineto(x, y)    gs_lineto(igs, map_x(x), map_y(y))
#define curveto(x1, y1, x2, y2, x3, y3) \
  gs_curveto(igs, map_x(x1), map_y(y1), \
             map_x(x2), map_y(y2), map_x(x3), map_y(y3))
#define closepath() gs_closepath(igs)

#define	vf_is_clock(c)     (((c) & VFD_CWCURV) == VFD_CWCURV)
#define	vf_is_a_clock(c)   (((c) & VFD_CCWCURV) == VFD_CCWCURV)
#define	vf_is_bos(c)       (vf_is_clock(c) || vf_is_a_clock(c))
#define	vf_is_token(c)	   (((c) & VFD_TOKEN) == VFD_TOKEN)

#define vf_get_xy(ptr, x, y) \
  if (vf_is_bos(ptr)) return e_undefinedresult; \
  else (x = VFD_GET_X(ptr), y = VFD_GET_Y(ptr))
#define VFD_GMASK (VFD_LINE | VFD_ARC | VFD_BEZ)

private double
atan360(double y, double x)
{
  double ang;
  
  if((ang = atan2(y, x) * radians_to_degrees) < 0)
    ang += 360.0;
  return ang;
}

/* darc3 */
private int
darc3(floatp x1, floatp y1, floatp x2, floatp y2, floatp x3, floatp y3)
{
  int code;
  floatp dx1, dy1, dx3, dy3, cx, cy, z, r, ang1, ang2, ang3, ang;
  
  x1 = map_x(x1);		y1 = map_y(y1);
  x2 = map_x(x2);		y2 = map_y(y2);
  x3 = map_x(x3);		y3 = map_y(y3);
  
  dx1 = x1 - x2;		dy1 = y1 - y2;
  dx3 = x3 - x2;		dy3 = y3 - y2;
  
  if((z = dx1*dy3 - dx3*dy1) == 0) {
    if((dx1 == dx3) && (dy1 == dy3)) {
      cx = dx1 / 2.0;
      cy = dy1 / 2.0;
      r = sqrt(cx*cx + cy*cy);
      ang = atan360(dy1 - cy, dx1 - cx);
      return gs_arc(igs, cx + x2, cy + y2, r, ang, ang+360);
    }
    else {
      if((code = gs_lineto(igs, x1, y1)) < 0)
	return code;
      return gs_lineto(igs, x3, y3);
    }
  }
  
  cx = ((dx1*dx1 + dy1*dy1)*dy3 - (dx3*dx3 + dy3*dy3)*dy1) / z / 2.0;
  cy = - ((dx1*dx1 + dy1*dy1)*dx3 - (dx3*dx3 + dy3*dy3)*dx1) / z / 2.0;
  r = sqrt(cx*cx + cy*cy);
  
  ang1 = atan360(dy1 - cy, dx1 - cx);
  ang2 = atan360(-cy, -cx);
  ang3 = atan360(dy3 - cy, dx3 - cx);
  
  if(ang1 == ang3)
    return gs_arc(igs, cx + x2, cy + y2, r, 0.0, 360.0);
  else if(ang1 < ang3) {
    if(ang1 <= ang2 && ang2 <= ang3)
      return gs_arc(igs, cx + x2, cy + y2, r, ang1, ang3);
    else
      return gs_arcn(igs, cx + x2, cy + y2, r, ang1, ang3);
  }
  else {
    if(ang3 <= ang2 && ang2 <= ang1)
      return gs_arcn(igs, cx + x2, cy + y2, r, ang1, ang3);
    else
      return gs_arc(igs, cx + x2, cy + y2, r, ang1, ang3);
  }
}

private int 
draw_font(const ref *font_name, int jis)
{
  int code;
  int font_fd;
  long *ptr,*outline;
  long token,next_token;
  int x0,x1,x2,x3,y0,y1,y2,y3;
  
  font_fd = open_font(font_name);
  if (font_fd < 0)
    return font_fd;
  
  /* Get out-line data. */
  outline = (long *)VF_GetOutline(jis,font_fd);
  
  if (outline == NULL)
    return 0;

  ptr = outline + 2;
  
  token = *ptr++;
  while(token) {
    /* The beginning of segment */
    if(!vf_is_bos(token))
      return e_undefinedresult;
    token &= VFD_GMASK;
    
    vf_get_xy(*ptr, x0, y0);
    ptr ++;
    x3 = x0; y3 = y0;
    code = moveto((floatp)x0, (floatp)y0);
    if (code<0) return code;
    
    while(!vf_is_bos(token) && token) {
      switch(token & VFD_GMASK) {
      case VFD_LINE:
        break;
      case VFD_ARC:
        x1 = x3; y1 = y3;
        vf_get_xy(*ptr, x2, y2);
        ptr ++;
        break;
      case VFD_BEZ:
        vf_get_xy(*ptr, x1, y1);
        ptr ++;
        vf_get_xy(*ptr, x2, y2);
        ptr ++;
        break;
      default:
        return e_undefinedresult;
      }
      if (vf_is_token(*ptr) || *ptr == 0) {
        next_token = *ptr++;
        if (vf_is_bos(next_token) || next_token == 0) {
          x3 = x0; y3 = y0;
        }
        else {
          vf_get_xy(*ptr, x3, y3);
          ptr ++;
        }
      }
      else {
        next_token = token;
        vf_get_xy(*ptr, x3, y3);
        ptr ++;
      }
      switch(token & VFD_GMASK) {
      case VFD_LINE:
        code = lineto((floatp)x3, (floatp)y3);
        break;
      case VFD_ARC:
        code = darc3((floatp)x1, (floatp)y1, 
                     (floatp)x2, (floatp)y2, 
                     (floatp)x3,  (floatp)y3);
        break;
      case VFD_BEZ:
        code = curveto((floatp)x1, (floatp)y1,
                       (floatp)x2, (floatp)y2,
                       (floatp)x3,  (floatp)y3);
        break;
      default:
        code = e_undefinedresult;
        break;
      }
      if(code<0) return code;
      
      token = next_token;
    }
    code = closepath();
    if (code<0) return code;
  }

  /* free buffer area. */
  VF_FreeOutline((char *)outline,font_fd);
  
  return 0;
}
