/* This is the main file for hacking dvips to do HyperPostScript
 * Written by Mark D. Doyle 11/94. It is (C) Copyright 1994 by Mark D. Doyle
 * and the University of California. You may modify and use this program to
 * your heart's content, so long as you send modifications to Mark Doyle and
 * abide by the rest of the dvips copyrights. 
 */
#include "dvips.h"
#include <kpathsea/c-ctype.h>

#define NAME 0
#define HREF 1
#define skip_space(s) for( ; *s == ' ' ; s++) ;

int CALLED_FROM_END_BOX = 0;
Boolean inHTMLregion = 0 ;
Boolean NO_ERROR = 1 ;
Boolean ERROR = 0 ;
/*integer HREF_COUNT ;   */
Boolean ISHREF = 0 ;
Boolean POPPED = 0 ;
Boolean PUSHED = 0 ;

void do_html() ; /* convert html special into hps link */
int href_or_name() ; /* Check for href or name in special */
int parseref() ; /* Look for = quotes. Return string between quotes */
int get_string() ; /* Return string between quotes */
int do_link() ; /* handle href in special */
unsigned hash_string() ;
char *current_name ;
int current_type ;
int current_pushcount ;
extern integer pushcount ;


#define GoTo 0
#define GoToR 1
#define Launch 2

typedef struct rectangle {
  double llx ; /* lower left x coor */
  double lly ; /* lower left y coor */
  double urx ; /* upper right x coor */
  double ury ; /* upper right y coor */
  } dvipsRectangle ; 

typedef struct hps_link {
  int action ; /* GoTo, GoToR, or Launch */
  char *file ;   /* PDF-style system-independent filename - unused now */
  dvipsRectangle rect ;  /* rectangle for location of link */
  int border[5] ; /* Border info --- with dashes*/
  int srcpg ; /* Page of location */
  int page ;  /* Page of destination --- left out is same as srcpg */
  int vert_dest ; /* Vertical position of destination on page */
  double color[3] ; /* color: length of array gives colorspace (3 <=> RGB)*/
  char *title ; /* Title of link */
  int type ; /* Internal or external */
  } Hps_link ;

struct nlist { /* hashtable entry */
  struct nlist *next ; /* next entry in chain */
  char *name ; /* defined name */
  Hps_link *defn ; /* Link associated with name */
  } ;

typedef struct rect_list { /* linked list of rectangles */
  struct rect_list *next ; /* next rectangle in the chain */
  dvipsRectangle rect ;
  } Rect_list ;

struct link_stack { /* a stack to handle nested links */
  struct link_stack *next ;
  Hps_link *item ;
  } ;

Hps_link *pop_link() ; /* pop a link off link_stack */
void push_link() ;  /* push a link onto link_stack */
int stack_count = 0 ; /* keep count of stack */

Rect_list *current_rect_list ;

#define HASHSIZE 1223 
static struct nlist *link_targets[HASHSIZE] ; /* names .... */
static struct nlist *link_sources[HASHSIZE] ; /* hrefs .... */
static struct link_stack *l_stack ; /* stack of un-ended links */

#ifdef KPATHSEA_TYPES_H
#define dup_str xstrdup
#else
char *dup_str() ; /* duplicate a string */
#endif
Hps_link *link_dup() ; /* duplicate a link struct */
struct nlist *lookup_link() ; /* lookup a link in one of the hashtables */
struct nlist *install_link() ; /* install a link in one of the hashtables */
void *rem_link() ; /* remove a link from a hashtable NOT USED */

Hps_link *target_link() ; /* Finds a target link given an href */
int href_name_match() ; /* Matches strings for hashtable lookup */
double dvi_to_hps_conv() ; /* Converts dvi coordinates to hps coor */
void stamp_pdfm() ; /* Print a pdfmark with link info into the file */
void set_bitfile() ; /* Redirect the output to a different file */
void finish_hps() ; /* Put together all of the files and output them out */
int count_targets() ; /* Count the number of targets that have been defined */
void do_target_dict() ; /* Produce PostScript dictionary for targets */
extern void copyfile() ;
extern void open_output() ;
extern Boolean removecomments ;
Boolean noprocset ; /* Leave out BeginProc and EndProc comments */
int vert_loc() ; /* Give a vertical position for the FitH param */
void vertical_in_hps() ; /* Handle broken line href's */
void end_current_box() ;
void update_box() ;

extern int vactualdpi ; 
extern int pagecounter ;
extern void cmdout() ;
extern integer hhmem, vvmem ;
extern integer vpapersize ;
/* This was hardcoded to letter size.  The denominator converts scaled
   points to big points (65536*72.27/72). */
#undef PAGESIZE /* HP-UX 10 defines for itself.  */
#define PAGESIZE ((int) (vpapersize/65781.76))
#define MARGIN 72
#define VERTICAL 0
#define HORIZONTAL 1
#define FUDGE 2.0

/* For later use 
* static char *dest_key[9] = {"Fit", "FitB", "FitW", "FitH", "FitBH"
               "FitR", "FitV", "FitBV", "XYZ"} ; 
*/

extern FILE *bitfile  ; 
extern char *oname  ; 

extern void error() ;

extern integer pagenum ;
extern integer hh ;
extern integer vv ;
extern integer hoff, voff ;

char *hs = NULL ; /* html string to be handled */
char *url_name = NULL ; /* url between double quotes */

/* parse anchor into link */
void
do_html P1C(char *, s){
url_name = (char *)mymalloc(strlen(s)+1) ;
hs = s ;
/*HREF_COUNT = 0 ; */
skip_space(hs) ; /* skip spaces */
if ( TOLOWER(*hs) == 'a') { /* valid start */
  POPPED = FALSE ;
  if (inHTMLregion && (stack_count < 1)) {
   error("previous html not closed with /A");
   return;
  } else {
     inHTMLregion = TRUE ;
     current_rect_list = NULL ;
     }
  hs++ ;
  skip_space(hs) ;
  while(*hs != '\0') {
/* parse for names and href */
  if (!href_or_name()) { 
    error("Bad HMTL:") ;
  error(s) ;
  error("!") ;
    break ;
    }
  skip_space(hs) ;
      }
    } else if ( (*hs == '/') && (TOLOWER(hs[1]) == 'a') ) {
              if (!inHTMLregion) {
                error("In html, /A found without previous A") ;
		return;
          } else if (stack_count == 1){ /* in nested specials if not 1 */
            inHTMLregion = FALSE ;
                    }
       end_current_box(hh, vv,2) ; /* 2 indicates /a close */
       }
              else {
                error( "No A in html special") ;
        error(s) ;
        /*error("!") ;*/
                }
      
return ;
}

int
href_or_name() {
if ((strncmp(hs, "href", 4) == 0) || (strncmp(hs, "HREF", 4) == 0)) {
    ISHREF = TRUE ;
    } else if ((strncmp(hs, "name", 4) == 0) 
              || (strncmp(hs, "NAME", 4) == 0)) {
        ISHREF = FALSE ; 
        } else {
          error("Not valid href or name html reference") ;
          return(ERROR) ;
          }
  if(!parseref()) return(ERROR) ;
  if (url_name) {
    if( ISHREF ) {
      do_link(url_name, HREF) ;
    } else do_link(url_name, NAME) ;
  } else {
      error("No string in qoutes ") ;
      return(ERROR) ;
      }
  return(NO_ERROR) ;
}

int
parseref() {
int i = 0 ;
  for(i=0 ; i++ < 4 ; hs++) ; /* skip href or name in html string */
  skip_space(hs) ;
  if (*hs == '=' ) {
    hs++ ;
    } else {
      error("No equal sign") ;
      return(ERROR) ;
      }
  if(!get_string()) return(ERROR) ;
  return(NO_ERROR) ; /* extract stuff between double quotes */
}
        
int
get_string() {
char *v = url_name ; 

  skip_space(hs) ;
 /* hash_name() ; */
  if (*hs == '"') {
    for(hs++ ; *hs != '"' && *hs != '\0' ; *v++ = *hs++) ;  /* need to esc " */
    if(*hs == '"') {
      hs++ ;
    *v++ = '\0' ;
      return(NO_ERROR) ;
      } else {
        error("Improper href. Missing closing double quote") ;
        return(ERROR) ;
        }
    } else {
      error("Improper href. Missing opening double quote") ;
      return(ERROR) ;
    }
}

int
do_link P2C(char *, s,  int, type) {

Hps_link *p ;

/*if (HREF_COUNT++ > 0) {
  error("!HTML string contains more than one href") ;
  } */
/* end the current box if we are starting a new one w/o closing the other
   i.e, we are nested and haven't found the first boxes /a */
 if ((stack_count != 0) && (CALLED_FROM_END_BOX == 0))
    end_current_box(hh, vv, 0) ; /* 0 means nested */
  p = (Hps_link *)mymalloc(sizeof(*p)) ;
  p->action = GoTo ;
  p->title = (char *)mymalloc(strlen(s)+1) ;
  p->title = s ;
  p->srcpg = pagecounter ;
  p->rect.llx = dvi_to_hps_conv(hh + hoff,HORIZONTAL) ; 
  p->rect.lly = dvi_to_hps_conv(vv + voff,VERTICAL)-FUDGE ;
  p->rect.urx = -1.0 ;
  p->rect.ury = -1.0 ;
  p->vert_dest = -1 ; 
  p->page = -1 ;
  p->color[0] = 0 ;
  p->color[1] = 0 ; /* Blue links */
  p->color[2] = 1 ;
  p->border[0] = 1 ; /* horizontal corner radius */
  p->border[1] = 1 ; /* vertical corner radius */
  p->border[2] = 1 ; /* box line width */
  p->border[3] = 3 ; /* dash on size */
  p->border[4] = 3 ;  /* dash off size */
  p->type = type ; /* set type to HREF or NAME */
 /*  current_name = (char *)malloc(strlen(s)+1) ; */
 /* current_name = s ; */
 /*  current_type = type ; */
  current_pushcount = pushcount ;
  install_link(s, p, type) ;
  push_link(p);
  return(NO_ERROR) ;
}
                 
unsigned 
hash_string P1C(char *, s) 
{
  unsigned hashval ;
  for (hashval = 0 ; *s != '\0' ; s++)
    hashval = *s + 31 * hashval ;
  return hashval % HASHSIZE ;
}

/* lookup a hashed name */ 

struct nlist *
lookup_link P2C(char *, s, int, type)
{
  struct nlist *np ;
  
  for(np = type ? link_sources[hash_string(s)] : link_targets[hash_string(s)] ;
        np != NULL ; np = np -> next)
      if (strcmp(s, np->name) == 0)
      return np ; /* found */
  return NULL ; /* not found */
}

struct nlist *
install_link P3C(char *, name, Hps_link *, defn, int, type)
{
  struct nlist *np ;
  unsigned hashval ;
    np = (struct nlist *)malloc(sizeof(*np)) ;
    if (np == NULL || (np->name = dup_str(name)) == NULL)
      return NULL ;
    hashval = hash_string(name) ;
    np->next = type ? link_sources[hashval] : link_targets[hashval] ;
    (type ? link_sources : link_targets)[hashval] = np ;
    if ((np->defn = link_dup(defn)) == NULL)
      return NULL ; 
  return np ;
}

void
push_link P1C(Hps_link *, ln)
{
 struct link_stack *ls ;
 ls = (struct link_stack *)malloc(sizeof(*ls)) ;
 if (ls == NULL) error(!"can't malloc on stack");
 ls->next = l_stack ;
 ls->item = ln ;
 l_stack = ls ;
 stack_count++ ;
}

Hps_link *pop_link()
{
 struct link_stack *temp ;
 Hps_link *hl ;
 /*hl = (Hps_link *)malloc(sizeof(*hl)) ; */
 if (l_stack == NULL) {
   error("stack empty; can't pop\n");
   error("!") ;
   }
 temp = l_stack ;
 hl = l_stack->item ;
 if(l_stack->next == NULL) {
  l_stack = NULL ;
  } else {
    l_stack = l_stack->next ;
  }
 free(temp) ; 
 stack_count--;
 return hl ; 
}

#ifndef KPATHSEA_TYPES_H
char *
dup_str P1C(char *, w) /* make a duplicate of s */
{
  char *p ;
  p = (char *)malloc(strlen(w)+1) ;
  if (p != NULL)
    strcpy(p,w) ;
  return p ;
}
#endif

Hps_link *
link_dup P1C(Hps_link *, s) /* make a duplicate link */
{
  Hps_link *p ;
  
  p = (Hps_link *) malloc(sizeof(*p)) ;
  if (p != NULL)
    p = s ;
  return p ;
}

double
dvi_to_hps_conv P2C(int,  i, int, dir)
{ 
double hps_coor ;
/* Convert dvi integers into proper hps coordinates
   Take into account magnification and resolution that dvi file was
   produced at */ 
   hps_coor = dir ? (((i * 72.0) / vactualdpi) +MARGIN) : (PAGESIZE - ((i * 72.0) / (vactualdpi)) - MARGIN  ) ;
   return(hps_coor) ;
}

int
vert_loc P1C(int, i)
{
  int return_value ;
  return_value = (i + (PAGESIZE / 4) + FUDGE) ; 
  if ( return_value > PAGESIZE) {
    return((int)PAGESIZE) ;
    } else if (return_value <  (PAGESIZE / 4.0)) {
        return((PAGESIZE / 4)) ;
        } else return(return_value) ;
}

Hps_link *
dest_link P1C(char *, s)
{
/* Assume for now that only one entry with same NAME, i.e.
   Should be true for all legitimate papers.
   Also, assume prepending of # for names. */
  
  struct nlist *np ;
  
  s++ ; /* get rid of hashmark */
  for(np = link_targets[hash_string(s)] ; np != NULL ; np = np -> next) {
     if ( href_name_match(s, np->name)) {
      return (np->defn) ; /* found */
      }
      }
  error("Oh no, link not found in target hashtable") ;
 error(s) ;
 error("!") ;
  return NULL ; /* not found */
}

int
count_targets P1H(void)
{
int count=0 ;
int i ;
struct nlist *np ;

for (i = 0 ; i < HASHSIZE ; i++) 
  for(np = link_targets[i]  ; np != NULL ; np = np -> next) 
      count++ ; 
  return count ;      
}

void
do_targets P1H(void)
{

struct nlist *np ;
int i ;
Hps_link *dest ;

for (i = 0 ; i < HASHSIZE ; i++) 
  for(np = link_sources[i]  ; np != NULL ; np = np -> next) {
      if (np->name[0] == '#') { 
      dest = dest_link(np->name) ;
      np->defn->page = dest->srcpg ;
      np->defn->vert_dest = vert_loc((int) dest->rect.lly) ;
      } 
      }
}

void
do_target_dict P1H(void)
{
struct nlist *np ;
int i ;
(void) fprintf(bitfile, "HPSdict begin\n") ;
(void)fprintf(bitfile, "/TargetAnchors\n") ;
(void)fprintf(bitfile, "%d dict dup begin\n",count_targets()) ;

for (i = 0 ; i < HASHSIZE ; i++) 
  for(np = link_targets[i]  ; np != NULL ; np = np -> next) 
    (void)fprintf(bitfile, "(%s) [%d [%.0f %.0f %.0f %.0f] %d] def\n",
              np->defn->title, np->defn->srcpg, 
              np->defn->rect.llx, np->defn->rect.lly,
              np->defn->rect.urx, np->defn->rect.ury,
              vert_loc((int) np->defn->rect.lly)) ;  
  (void)fprintf(bitfile,"end targetdump-hook def\n") ;
}



int
href_name_match P2C(char *, h, char *, n)
{
  int count = 0 ;
  int name_length = strlen(n) ;
  while((*h == *n) & (*h != '\0') )  {
    count++ ;
    h++ ;
    n++ ;
    }
  if (name_length == count) {
    return 1 ;
    } else {
     /* printf(" count: %d \n", count) ; */
    return 0 ;
    }
}

void 
stamp_pdfm P2C(Hps_link *, pl, int, flag)
{
char tmpbuf[200] ;

  if (pl == NULL) {
    error("Null pointer, oh no!") ;
    return ;
 }
/*
if (pl->rect.llx == pl->rect.urx) {
   (void)sprintf(tmpbuf, "(%s) [[%.0f %.0f %.0f %.0f]\n", pl->title, pl->rect.llx, pl->rect.lly, pl->rect.urx, pl->rect.ury);
   printf("\nZero width box, skipping pdfmark:");
   printf(tmpbuf);
   return;
  } */
      /* print out the proper pdfm with local page info only 
       *  target info will be in the target dictionary */
if (!flag) { /* true if internal */
(void)sprintf(tmpbuf, 
" (%s) [[%.0f %.0f %.0f %.0f] [%d %d %d [%d %d]] [%.0f %.0f %.0f]] pdfm ", pl->title, pl->rect.llx, pl->rect.lly, pl->rect.urx, pl->rect.ury,
pl->border[0], pl->border[1], pl->border[2], pl->border[3],pl->border[4],
    pl->color[0], pl->color[1], pl->color[2]) ;
    cmdout(tmpbuf) ; 
    } else { /* external */
/* For external URL's, we just pass them through as a string. The hyperps
 * interpreter can then do what is wants with them.
 */
    (void)sprintf(tmpbuf," [[%.0f %.0f %.0f %.0f] [%d %d %d [%d %d]] [%.0f %.0f %.0f]] (%s) pdfm ", pl->rect.llx, pl->rect.lly, pl->rect.urx, pl->rect.ury,
		  pl->border[0], pl->border[1], pl->border[2], pl->border[3],pl->border[4],
		  pl->color[0], pl->color[1], pl->color[2], pl->title) ;
    cmdout(tmpbuf) ;
     } /* end external else */
}


void 
finish_hps P1H(void)
{
  fclose(bitfile) ;
  set_bitfile("head.tmp",1);
  do_targets() ;
  do_target_dict();
  (void)fprintf(bitfile, "TeXDict begin\n") ;
  (void)fprintf(bitfile, "%%%%EndSetup\n") ;
  fclose(bitfile) ;
  open_output() ;
  noprocset = 1 ;
  removecomments = 0;
  copyfile("head.tmp") ;
  copyfile("body.tmp") ;
  unlink("head.tmp");
  unlink("body.tmp");
}

void
set_bitfile P2C(const_string, s, int,  mode)
{  
if ((bitfile=fopen(s, mode ? "a" : "w"))==NULL) {
   error(s) ;
   error("!couldn't open file") ;
  }
}

void 
vertical_in_hps P1H(void) 
{ 
  if (current_type == NAME) return; /* Handle this case later */
  if (POPPED) update_box() ; 
}

void
end_current_box P3C(integer, x_coor, integer, y_coor,  int, mode_flag)
/* mode_ flag: 0 is nested, 1 is line_break, 2 is a /a html close*/
{
Hps_link *nl ;
  
switch (mode_flag) {
/* normal close on finding a /a. pop box off stack. If in  a nest
   (i.e. something still on stack), then you need to create another
   link for the one below. Do this by popping it off, gleaning the 
   title and type info, and doing another link. Nested names shouldn't
   be allowed.
*/
 case 2: 
  nl = pop_link() ;
  if (nl == NULL)  error("!Internal error: popped a null");
  if (stack_count != 0) { /* still something on the stack, so nested */
   current_name = dup_str(l_stack->item->title) ;
   current_type = l_stack->item->type ;
   update_box();
} 
  break;

/* line break/page break case. Pop the box, but push a new one at the 
   line beginning (update_box will initialize new link soon)
*/
 case 1:
  POPPED = TRUE ;
   /*  HREF_COUNT-- ;*/
   nl = pop_link() ;
   if (nl == NULL)  error("!Internal error: popped a null") ;
   current_name = dup_str(nl->title) ;
   current_type = nl->type ;
   CALLED_FROM_END_BOX = 1;
   do_link(current_name, current_type) ;
   CALLED_FROM_END_BOX = 0;
   break ;

/* nested link case. Box is being closed because a new html href or name
   special is being started before the /a of the previous one.
   We just want to close off the box, but we aren't going to pop it
   off because it needs to start again after inner specials are closed off.
*/
 case 0:
  nl = l_stack->item ;
  break; 
      }

/* close off the proper box */
nl->rect.urx = dvi_to_hps_conv(x_coor + hoff,HORIZONTAL) ;
nl->rect.ury = dvi_to_hps_conv(y_coor + voff, VERTICAL)-FUDGE+12.0 ;
if (nl->type == HREF)
 stamp_pdfm(nl, (nl->title[0] != '#')) ;
}

void 
update_box P1H(void) 
{
  if (POPPED) POPPED = FALSE ; 
  l_stack->item->rect.llx = dvi_to_hps_conv(hh + hoff,HORIZONTAL) ;
  l_stack->item->rect.lly = dvi_to_hps_conv(vv + voff,VERTICAL)-FUDGE ;
  l_stack->item->srcpg = pagecounter ;
}

