
/* ensure fileno() gets defined */
#define _POSIX_SOURCE

/* and mkdtemp() */
#define _BSD_SOURCE

#include<stdio.h>
#include<stdlib.h>  /* exit() */
#include<stdarg.h>
#include<string.h>
#include<math.h>
#include<sys/types.h>
#include<ctype.h>
#include<sys/mman.h> /* mmap */
#include<zlib.h>
#include<unistd.h>

#include "fig2pdf.h"

#define FLATE 8

#ifdef NEED_STRNSTR
static char *strnstr(const char *s1, const char *s2, size_t len){
  size_t l1,l2;

  /* Don't go past any NULL in s1 */
  for(l1=0;(l1<len)&&(s1[l1]);l1++);

  l2=strlen(s2);
  if (l2==0) return NULL;

  while(l1>=l2){
    if(!strncmp(s1,s2,l2)) return (char*)s1;
    s1++;
    l1--;
  }
  return NULL;
}
#endif

static struct ref {int type; char *p;
  int gen; int strm; int off; char *end;} *refs;

static char *pdf_end;
static int xref_size;

static void get_dict(char **st, char **end);
static void get_stream(char **st, char **end);
static char* get_obj(int obj, int gen);

static int dict_get_int(char *dict, char *key, int *value, int len);
static int dict_get_quad(char *dict, char *key, double *value, int len);
char* dict_get_key(char *dict, char *key, int len);

static void extract_resource(char *dict, char *end,
			     char *template, char **extras, int *count);
static void extract_dict(char *fd1, char *fd2);
static void strn_join(char **s1, char *s2, int n);
static void add_ends(void);
void settmpdir(void);
void cm_obj(double llx, double lly, double width, double height,
	    double cropbox[4],double angle, int depth, int fl);

char *extra_fonts=NULL;
static int extra_font_count=0;

char *egstate=NULL;
static int egstate_count=0;

char *extra_xobjs=NULL;
static int extra_xobj_count=0;

static struct stream_tr {char *old; char *new;
  struct stream_tr *next;} *strm_tr=NULL;
static int output_strm(char *p, int len);

static struct free_me {void *p; struct free_me *last;} *garbage=NULL;

static void **ends;

extern double text_scale;
extern int obj_count;

static int count=0;
char dirname[20]="";

#define PATH_MAX 257

void readpdf(char *file1,double llx,double lly,
	     double width,double height,double angle,int depth,int fl)
{
  struct pdf_file {char *filename; double bb[4]; int obj;
    struct pdf_file *next;};
  int i,j,off,kids_obj,kids_gen,contents_obj,contents_gen;
  int root,pages_obj,pages_gen,array,stream_len;
  int count,start,no,inc,gen,w1,w2,w3,d1,d2,d3,debug,old=0;
  long pdf_len,x;
  char *in,*p,*xrefp,*xref_dend,*p2,*p3,*p4,*p5,*st,*end;
  FILE *fpdf;
  double cropbox[4];
  static struct pdf_file  pdf_images={NULL, {0,0,0,0}, 0, NULL};
  struct stream_tr *s,*s2;
  struct free_me *g,*g2;
  struct pdf_file *img;

  debug=flags&DEBUG_MASK;

  img=&pdf_images;
  if ((img->filename)&&(strcmp(img->filename,file1)==0)) old=1;
  if (old==0){
    while (img->next){
      img=img->next;
      if ((img->filename)&&(strcmp(img->filename,file1)==0)){
	old=1;
	break;
      }
    }
  }

  if (old){
    if (debug) fprintf(stderr,"Reusing PDF file %s\n",file1);
    cm_obj(llx,lly,width,height,img->bb,angle,depth,fl);
    objects[obj_count-1].child=img->obj;
    return;
  }


  fpdf=fopen(file1,"rb");
  if(!fpdf){
    fprintf(stderr,"Error, unable to open %s for reading.\n",file1);
    exit(1);
  }

  fseek(fpdf,0,SEEK_END);
  pdf_len=ftell(fpdf);
  
  in=mmap(NULL,pdf_len,PROT_READ,MAP_PRIVATE,fileno(fpdf),0);
  
  if(in==MAP_FAILED){
    fprintf(stderr,"Error, mmap of %s failed.\n",file1);
    perror("");
    exit(1);
  }
  pdf_end=in+(pdf_len-1);


  /* Try to find startxref by reading backwards */

  p=pdf_end;
  while((*p=='\n')||(*p=='\r')) p--; /* Consume EOL */

  while((*p!='\n')&&(*p!='\r')) p--;

  if(strncmp(p+1,"%%EOF",5)!=0){
    fprintf(stderr,"Invalid PDF file, no %%%%EOF.\n");
    exit(1);
  }

  while((*p=='\n')||(*p=='\r')) p--;
  while((*p!='\n')&&(*p!='\r')) p--;
  if(sscanf(p+1,"%ld",&x)!=1){
    fprintf(stderr,"Error reading xref offset\n");
    exit(1);
  }
  xrefp=in+x;

  while((*p=='\n')||(*p=='\r')) p--;
  while((*p!='\n')&&(*p!='\r')) p--;
  if(strncmp(p+1,"startxref",9)!=0){
    fprintf(stderr,"Invalid PDF file, startxref not antepenultimate line.\n");
    exit(1);
  }

  /* We would now like to find both size and root */

  if (!dict_get_int(xrefp,"/Size",&xref_size,pdf_end-xrefp)){
    fprintf(stderr,"Invalid PDF file, size of xref dict not found.\n");
    exit(1);
  }
    
  p2=dict_get_key(xrefp,"/Root",pdf_end-xrefp);
  if (!p){
    fprintf(stderr,"Invalid PDF file, root object not found.\n");
    exit(1);
  }
  i=sscanf(p2,"%d",&root);
  if (i!=1){
    fprintf(stderr,"Invalid PDF file, root object not found.\n");
    exit(1);
  }

  if (debug>1)
    fprintf(stderr,"xref dict of %d objects, root object %d\n",xref_size,root);

  refs=malloc(xref_size*sizeof(struct ref));
  if (!refs){
    fprintf(stderr,"Malloc error.\n");
    exit(1);
  }

  ends=malloc((xref_size+1)*sizeof(void*));
  if (!ends){
    fprintf(stderr,"Malloc error.\n");
    exit(1);
  }

  for(i=0;i<xref_size;i++) ends[i]=in;

  if (strncmp(xrefp,"xref",4)==0){ /* Classic xref table */
    if (debug>1) fprintf(stderr,"Classic xref table found\n");
    p=xrefp+4;
    count=1;
    while(count<xref_size){
      sscanf(p,"%d %d%n",&start,&no,&inc); /* section header */
      p+=inc;
      for(i=start;i<start+no;i++){
        sscanf(p,"%d %d%n",&off,&gen,&inc);
	p+=inc;
        while(*p==' ') p++;
        if(*p=='n'){
          refs[i].type=1;
	  refs[i].p=in+off;
          refs[i].gen=gen;
          refs[i].strm=0;
          refs[i].off=0;
          refs[i].end=pdf_end;
	}
	else{
          refs[i].type=1;
	  refs[i].p=NULL;
          refs[i].gen=0;
          refs[i].strm=0;
          refs[i].off=0;
          refs[i].end=NULL;
	}
        count++;
        p++;
	while((*p=='\n')||(*p=='\r')||(*p==' ')) p++;
      }
    }
  }
  else{ /* v1.5 xref stream */
    xrefp=strnstr(xrefp,"<<",20);
    p2=xrefp;
    xref_dend=pdf_end;
    get_dict(&xrefp,&xref_dend);

    /* Must have a W entry */
    p=dict_get_key(xrefp,"/W",xref_dend-xrefp);
    if (!p){
      fprintf(stderr,"No W in Xref dictionary.\n");
      exit(1);
    }
    if(sscanf(p,"[%d %d %d]",&w1,&w2,&w3)!=3){
      fprintf(stderr,"Invalid W in Xref dictionary.\n");
      exit(1);
    }

    p=p2;
    p2=pdf_end;
    get_stream(&p,&p2);

    for(i=0;i<xref_size;i++){
      d1=*(unsigned char*)p++;
      for(j=1;j<w1;j++) d1=(d1<<8)+*(unsigned char*)p++;
      d2=*(unsigned char*)p++;
      for(j=1;j<w2;j++) d2=(d2<<8)+*(unsigned char*)p++;
      d3=*(unsigned char*)p++;
      for(j=1;j<w3;j++) d3=(d3<<8)+*(unsigned char*)p++;
      refs[i].type=d1;
      if(d1==1){
	refs[i].p=in+d2;
        refs[i].gen=d3;
        refs[i].strm=0;
	refs[i].off=0;
        refs[i].end=pdf_end;
      }
      else if(d1==2){
	refs[i].p=NULL;
	refs[i].gen=0;
	refs[i].strm=d2;
	refs[i].off=d3;
        refs[i].end=NULL;
      }
      if (i==0){  /* d1 will be zero, a free object */
	refs[i].p=in;
        refs[i].gen=d3;
        refs[i].strm=0;
	refs[i].off=0;
        refs[i].end=pdf_end;
      }
    }
  }

  if (debug>2){
    for(i=0;i<xref_size;i++){
      fprintf(stderr,"Xref %d: %d %d %d\n",i,refs[i].p?(int)(refs[i].p-in):0,
	      refs[i].strm,refs[i].off);
    }
  }

  add_ends();

  p=get_obj(root,0);
  p2=refs[root].end;
  get_dict(&p,&p2);
  p3=dict_get_key(p,"/Pages",(int)(p2-p));
  if (!p3) {
    fprintf(stderr,"No /Pages entry found\n");
    exit(1);
  }
  if(sscanf(p3,"%d %d%n",&pages_obj,&pages_gen,&off)!=2){
    fprintf(stderr,"Error reading /Pages entry\n");
    exit(1);
  }
  p3+=off;
  while((*p3=='\n')||(*p3=='\r')||(*p3==' ')) p3++;
  if (*p3!='R'){
    fprintf(stderr,"Error reading /Pages entry\n");
    exit(1);
  }

  st=get_obj(pages_obj,pages_gen);
  end=refs[pages_obj].end;
  get_dict(&st,&end);

  dict_get_int(st,"/Count",&i,end-st);

  if (debug>1) fprintf(stderr,"Page count: %d\n",i);

  if (i!=1){
    fprintf(stderr,"Not a single page file, exiting\n");
    exit(1);
  }
 
  /* The Kids entry must be an array containing a single indirect reference */

  p3=dict_get_key(st,"/Kids",end-st);

  if ((!p3)||(*p3!='[')){
    fprintf(stderr,"Unable to find /Kids containing array\n");
    exit(1);
  }
  p3++;

  i=sscanf(p3,"%d %d%n",&kids_obj,&kids_gen,&off);
  p3+=off;
  while((*p3==' ')||(*p3=='\n')||(*p3=='\r')) p3++;
  if ((i!=2)||(*p3!='R')){
    fprintf(stderr,"Error parsing /Kids array\n");
    exit(1);
  }

  if (debug>1) fprintf(stderr,"Kids object: %d %d\n",kids_obj,kids_gen);
  
  st=get_obj(kids_obj,kids_gen);
  end=refs[kids_obj].end;
  get_dict(&st,&end);

  if (debug>2){
    fprintf(stderr,"Page object:\n");
    fprintf(stderr,"%.*s\n\n",(int)(end-st),st);
  }

  if(dict_get_quad(st,"/CropBox",cropbox,end-st)==0){
    if(dict_get_quad(st,"/MediaBox",cropbox,end-st)==0){
      fprintf(stderr,"Error: no form of MediaBox found\n");
      exit(1);
    }
  }

  if (debug>1) fprintf(stderr,"CropBox [%lf %lf %lf %lf]\n",cropbox[0],
		     cropbox[1],cropbox[2],cropbox[3]);

  if ((fl&NATURAL)==NATURAL){
    height=cropbox[3]-cropbox[1];
    width=cropbox[2]-cropbox[0];
  }

  p3=dict_get_key(st,"/Resources",end-st);
  if (!p3) {
    fprintf(stderr,"No resource dictionary found in Page object!\n");
    exit(1);
  }
  p4=end;
  get_dict(&p3,&p4);
  if (debug>2)
    fprintf(stderr,"Resource dictionary:\n%.*s\n",(int)(p4-p3),p3);

  /* Here insert call to resource dictionary munging routine. */

  /* Do we have any fonts? */

  p5=dict_get_key(p3,"/Font",p4-p3);
  if (p5) extract_resource(p5,p4,"/EF%d",&extra_fonts,&extra_font_count);

  p5=dict_get_key(p3,"/ExtGState",p4-p3);
  if (p5) extract_resource(p5,p4,"/EG%d",&egstate,&egstate_count);

  p5=dict_get_key(p3,"/XObject",p4-p3);
  if (p5) extract_resource(p5,p4,"/EX%d",&extra_xobjs,&extra_xobj_count);

  p3=dict_get_key(st,"/Contents",end-st);

  if (!p3){
    fprintf(stderr,"Unable to find /Contents\n");
    exit(1);
  }

  /* This must be either a stream (starting with a dictionary),
   * or an array of references to streams
   */

  array=0;
  if (*p3=='['){
    array=1;
  }
  else{
    i=sscanf(p3,"%d %d%n",&contents_obj,&contents_gen,&off);
    p3+=off;
    while((*p3==' ')||(*p3=='\n')||(*p3=='\r')) p3++;
    if ((i!=2)||(*p3!='R')){
      fprintf(stderr,"Error parsing /Contents entry\n");
      exit(1);
    }
    
    if (debug>1)
      fprintf(stderr,"Contents object: %d %d\n",contents_obj,contents_gen);

    p3=get_obj(contents_obj,contents_gen);
    if(!p3){
      fprintf(stderr,"Unable to find /Contents object\n");
      exit(1);
    }
    if (*p3=='[') array=1;
  }

  if (array) p3++;

  /* Emit first a separate co-ordinate transform object */

  cm_obj(llx,lly,width,height,cropbox,angle,depth,fl);

  objects[obj_count-1].child=cref+1;

  if (debug>2){
    fprintf(stderr,"Conversion arrays says:\n");
    s=strm_tr;
    while(s){
      fprintf(stderr,"%s -> %s\n",s->old,s->new);
      s=s->next;
    }
  }

  /* We have no idea how long a stream we are about to emit */

  OBJ_PRN("<</Length %d 0 R>> stream\n",cref+1);
  stream_len=0;

  do{
    if (array){
      i=sscanf(p3,"%d %d%n",&contents_obj,&contents_gen,&off);
      p3+=off;
      while((*p3==' ')||(*p3=='\n')||(*p3=='\r')) p3++;
      if ((i!=2)||(*p3!='R')){
	fprintf(stderr,"Error parsing /Contents entry\n");
	exit(1);
      }
      p3++;      
      while((*p3==' ')||(*p3=='\n')||(*p3=='\r')) p3++;
    }

    p=get_obj(contents_obj,contents_gen);
    p2=refs[contents_obj].end;
    get_stream(&p,&p2);
    stream_len+=output_strm(p,(int)(p2-p));
    //    stream_len+=fprintf(pdf,"%.*s\n",(int)(p2-p),p);
  } while ((array==1)&&(*p3!=']'));

  stream_len+=fprintf(pdf,"Q\n");
  xref[cref]+=stream_len;
  xref[cref]+=fprintf(pdf,"endstream\nendobj\n\n");

  if (img->filename!=NULL){
    while(img->next) img=img->next;   /* move to end of list */
    img->next=malloc(sizeof(struct pdf_file));
    if (!img->next){
      fprintf(stderr,"Malloc error in read_eps\n");
      exit(1);
    }
    img=img->next;
  }

  img->next=NULL;
  img->filename=malloc(strlen(file1)+1);
  if (!img->filename){
    fprintf(stderr,"Malloc error in read_eps\n");
    exit(1);
  }
  strcpy(img->filename,file1);

  img->bb[0]=cropbox[0];
  img->bb[1]=cropbox[1];
  img->bb[2]=cropbox[2];
  img->bb[3]=cropbox[3];
  img->obj=cref;

  //  add_obj(cref,depth);
  OBJ_PRN("%d\nendobj\n\n",stream_len);

  /* Clean up */

  munmap(in,pdf_len);
  free(refs);
  free(ends);

  /* Reset static variables local to individual PDF file*/

  /* Do not reset  extra_font*, these are really global */

  s=strm_tr;
  while(s){
    s2=s->next;
    free(s->old);
    free(s->new);
    free(s);
    s=s2;
  }
  strm_tr=NULL;

  g=garbage;
  while(g){
    free(g->p);
    g2=g->last;
    free(g);
    g=g2;
  }
  garbage=NULL;

}

/* Emit a co-ordinate transform object */
void cm_obj(double llx, double lly, double width, double height,
	    double cropbox[4],double angle, int depth, int fl){
  int i,flipped;
  char pdfcm[1000];
  double xtmp;

  flipped=fl&1;

  i=sprintf(pdfcm,"q 1 0 0 1 %.2f %.2f cm\n",llx,lly);

  if (fl&NATURAL){
    width=cropbox[2]-cropbox[0];
    height=cropbox[3]-cropbox[1];
  }

  if (((flipped==0)&&(width*height<0))||
      ((flipped==1)&&(width*height>0))){
    xtmp=width;
    width=-height;
    height=-xtmp;
  }

  bb_fix(llx,lly,1);
  bb_fix(llx+width*cos(angle),lly+width*sin(angle),1);
  bb_fix(llx-height*sin(angle),lly+height*cos(angle),1);
  bb_fix(llx+width*cos(angle)-height*sin(angle),
	 lly+width*sin(angle)+height*cos(angle),1);


  if ((fl&NATURAL)==0){
    i+=sprintf(pdfcm+i,"%.2f 0 0 %.2f 0 0 cm\n",
			width,height);
    if ((cropbox[0]!=0.0)||(cropbox[1]!=0.0))
      i+=sprintf(pdfcm+i,"1 0 0 1 %.2f %.2f cm\n",
		 -cropbox[0],-cropbox[1]);
    i+=sprintf(pdfcm+i,"%.6f 0 0 %.6f 0 0 cm\n",
			1.0/(cropbox[2]-cropbox[0]),
			1.0/(cropbox[3]-cropbox[1]));
  }
  else{
    if ((cropbox[0]!=0.0)||(cropbox[1]!=0.0))
      i+=sprintf(pdfcm+i,"1 0 0 1 %.2f %.2f cm\n",
		 -cropbox[0],-cropbox[1]);
  }

  if (((flipped==0)&&(width*height<0))||
      ((flipped==1)&&(width*height>0)))
    i+=sprintf(pdfcm+i,"0 1 1 0 0 0 cm\n");

  if (angle!=0)
    i+=sprintf(pdfcm+i,"%.2f %.2f %.2f %.2f 0 0 cm\n",
			cos(angle),sin(angle),-sin(angle),cos(angle));

  OBJ_PRN("<</Length %d>> stream\n%sendstream\nendobj\n\n",
	  strlen(pdfcm),pdfcm);
  add_obj(cref,depth);

}


/* return pointers to start and end of stream */
void get_stream(char **st, char **end){
  char *p,*p2,*dst,*dend;
  int len,err,comp;
  unsigned long len2;
  struct free_me *f;

  dst=*st;
  dend=*end;
  get_dict(&dst,&dend);

  if(!dict_get_int(dst,"/Length",&len,dend-dst)){
    fprintf(stderr,"No Length in Xref dictionary.\n");
    exit(1);
  }

  /* Are we filtered */
  comp=0;
  p=dict_get_key(dst,"/Filter",dend-dst);
  if (p){
    if (strncmp(p,"/FlateDecode",12)==0) comp=FLATE;
    else{
      fprintf(stderr,"Unknown filter in xref dictionary.\n");
      exit(1);
    }
  }

  /* Find stream */
  p=strnstr(dend,"stream",20);
  if (!p){
    fprintf(stderr,"No stream keyword in stream object.\n");
    exit(1);
  }
  p+=6;
  while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;

  if (comp==FLATE){
    len2=3*len;
    p2=malloc(len2+1);
    err=uncompress((Bytef*)p2,&len2,(Bytef*)p,len);
    while(err==Z_BUF_ERROR){
      len2*=2;
      p2=realloc(p2,len2+1);
      if (!p2){
	fprintf(stderr,"Realloc error\n");
	exit(1);
      }
      err=uncompress((Bytef*)p2,&len2,(Bytef*)p,len);
    }      
    if (err){
      fprintf(stderr,"Zlib error: %d.\n",err);
      exit(1);
    }
    p=realloc(p2,len2+1);
    p[len2]=0;  /* Null terminate uncompressed object so that sscanf
                 * does not wander off its end */
    /* Save pointer so we can deallocate it later */
    f=malloc(sizeof(struct free_me));
    f->last=garbage;
    f->p=p;
    garbage=f;
  }
  else len2=len;

  *st=p;
  *end=p+len2;

}

/* return pointers to start and end of dictionary */
void get_dict(char **st, char **end){
  char *p;
  int depth,obj,gen,skip;

  p=*st;

  while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;

  /* Now we should have either << or nn mm R */

  if ((*p=='<')&&(*(p+1)=='<')){
    p+=2;
    *st=p;
    depth=1;
    while(depth){
      while((*p!='<')&&(*p!='>')&&(p<*end)) p++;
      if((*p=='<')&&(*(p+1)=='<')) {depth++;p++;}
      if((*p=='>')&&(*(p+1)=='>')) {depth--;p++;}
      p++;      
    }
    *end=p-2;
  }
  else if ((*p>='0')&&(*p<='9')){
    if (sscanf(p,"%d %d%n",&obj,&gen,&skip)!=2){
      fprintf(stderr,"Confused reading dict\n");
      exit(1);
    }
    p+=skip;
    while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;
    if (*p!='R'){
      fprintf(stderr,"Confused reading dict -- expected R\n");
      exit(1);
    }
    *st=get_obj(obj,gen);
    *end=refs[obj].end;
    get_dict(st,end);
  }
  else{
    fprintf(stderr,"Failed to find dict\n");
    exit(1);
  }
}

/* return pointer to object */
char* get_obj(int obj, int gen){
  int i,j,k,l,n,first,last;
  long len2;
  char *st,*p,*p2,*d_st,*d_end,*end;

  if ((obj<0)||(obj>xref_size)){
    fprintf(stderr,"Invalid object number %d\n",obj);
    exit(1);
  }

  if ((refs[obj].p)&&(refs[obj].strm==0)){
    p=refs[obj].p;
    if (sscanf(p,"%d %d%n",&i,&j,&n)!=2){
      fprintf(stderr,"Bad object %d\n",obj);
      exit(1);
    }
    p+=n;
    if (i!=obj){
      fprintf(stderr,"Bad object %d\n",obj);
      exit(1);
    }
    while((*p=='\n')||(*p=='\r')||(*p==' ')) p++;
    if (strncmp(p,"obj",3)!=0){
      fprintf(stderr,"Bad object %d\n",obj);
      exit(1);
    }
    p+=3;
    while((*p=='\n')||(*p=='\r')||(*p==' ')) p++;
    return p;
  }

  if (refs[obj].p) return refs[obj].p;

  if (refs[obj].type!=2){
    fprintf(stderr,"Invalid object %d\n",obj);
    exit(1);
  }

  /* We have an object stream which we have never decoded */

  p=get_obj(refs[obj].strm,0);
  
  /* We should now have a dictionary followed by a stream */

  st=p;
  d_st=p;
  end=pdf_end;
  d_end=pdf_end;
  get_dict(&d_st,&d_end);

  p=dict_get_key(d_st,"/Type",d_end-d_st);
  if (!p){
    fprintf(stderr,"Invalid object stream %d\n",obj);
    exit(1);
  }
  if (strncmp(p,"/ObjStm",7)!=0){
    fprintf(stderr,"Invalid object stream %d\n",obj);
    exit(1);
  }

  if(!dict_get_int(d_st,"/N",&n,d_end-d_st)){
    fprintf(stderr,"No N in Objstm dictionary.\n");
    exit(1);
  }

  if(!dict_get_int(d_st,"/First",&first,d_end-d_st)){
    fprintf(stderr,"No First in Objstm dictionary.\n");
    exit(1);
  }

  get_stream(&st,&end);
  len2=end-st;
  p=st;

  p2=p;
  last=-1;
  for(i=0;i<n;i++){
    if(sscanf(p2,"%d %d%n",&j,&k,&l)!=2){    
      fprintf(stderr,"Error in objstrm header\n");
      exit(1);
    }
    p2+=l;
    refs[j].p=p+first+k;
    if (last>=0) refs[last].end=p+first+k-1;
    last=j;
  }
  refs[j].end=p+len2;

  if(!refs[obj].p){
    fprintf(stderr,"Inconsistency in objstrm\n");
    exit(1);
  }

  return refs[obj].p;
 
}

/* Get int from dictionary, cope with indirect objects */
static int dict_get_int(char *dict, char *key, int *value, int len){
  char *p;
  int i,gen,off;

  p=dict_get_key(dict,key,len);
  if (p==NULL) return 0;

  i=sscanf(p,"%d %d%n",value,&gen,&off);

  if (i==1) return 1;
  if (i==0) return 0;

  p+=off;
  while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;
  if (*p!='R') return 0;
  /* We have an indirect reference */
  p=get_obj(*value,gen);
  if(!p){
    fprintf(stderr,"Int is indirect ref to unfound obj\n");
    exit(1);
  }
  i=sscanf(p,"%d",value);
  if (i==1) return 1;
  return 0;
}

static int dict_get_quad(char *dict, char *key, double *value, int len){
  char *p;
  int i,obj,gen,off;

  p=dict_get_key(dict,key,len);
  if (p==NULL) return 0;

  while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;

  if ((*p>='0')&&(*p<='9')){ /* We have an indirect reference */
    i=sscanf(p,"%d %d%n",&obj,&gen,&off);
    p+=off;
    while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;
    if ((i!=2)||(*p!='R')) return 0;
    p=get_obj(obj,gen);
    if(!p){
      fprintf(stderr,"Quad is indirect ref to unfound obj\n");
      exit(1);
    }
    while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;
  }    

  if (*p!='[') return 0;
  p++;
  i=sscanf(p,"%lf %lf %lf %lf",value,value+1,value+2,value+3);
  if (i!=4) return 0;
  return 1;

}

/* Return pointer to first non whitespace after key in dict,
 * of NULL if key not found
 */

char* dict_get_key(char *dict, char *key, int len){
  char *p;

  p=dict;
  while(p<dict+len){
    p=strnstr(p,key,(dict+len)-p);
    if (p==NULL) return p;
    p+=strlen(key);
    if (!isalnum(*p)) break;
  }
  while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;
  return p;

}

static void extract_resource(char *dict, char *end,
			     char *template, char **extras, int *count){
  char *d,*p2;
  char *fd1,*fd2;
  struct stream_tr *s;
  int off;
  char scratch[30];

  get_dict(&dict,&end);

    /* We now have a dictionary of /key font_dict
     *  or /key indirection_to_font_dict
     * We will emit using indirections
     */

  d=dict;

  while(d<end){

    while((*d==' ')||(*d=='\n')||(*d=='\r')) d++;
    if (d>=end) break;

    if (*d!='/'){
      fprintf(stderr,"Confused parsing /Fonts resource\n");
      exit(1);
    }

    p2=d+1;
    while(isalnum(*p2)) p2++;

    /* Copy key */

    //    strn_join(&extra_fonts,d,(p2-d));
    if (!strm_tr){
      strm_tr=malloc(sizeof(struct stream_tr));
      strm_tr->next=NULL;
      s=strm_tr;
    }
    else{
      s=strm_tr;
      while (s->next) s=s->next;
      s->next=malloc(sizeof(struct stream_tr));
      s=s->next;
      s->next=NULL;
    }
    s->old=malloc((p2-d)+1);
    strncpy(s->old,d,p2-d);
    s->old[p2-d]=0;

    (*count)++;
    sprintf(scratch,template,*count);
    s->new=malloc(strlen(scratch)+1);
    strcpy(s->new,scratch);
    strn_join(extras,scratch,strlen(scratch));
    sprintf(scratch," %d 0 R ",cref+1);
    strn_join(extras,scratch,strlen(scratch));
    
  /* Now should have a dictionary */

    d=p2;
    while((*d==' ')||(*d=='\n')||(*d=='\r')) d++;
    p2=d;

    fd1=p2;
    fd2=end;
    get_dict(&fd1,&fd2);

    if (*d=='<') /* It was an inline dictionary */
      d=fd2+2; /* Skip over final >> of inline dictionary */
    else{
      sscanf(d,"%*d %*d%n",&off);
      d+=off;
      while((*d==' ')||(*d=='\n')||(*d=='\r')) d++;
      if (*d!='R'){
	fprintf(stderr,"Confused in extract_resource\n");
	exit(1);
      }
      d++;
    }
   
    extract_dict(fd1,fd2);
  }


}


static void extract_dict(char *fd1, char *fd2){

  char *p,*p0,*p2;
  int i,fix,debug,stream_len;
  static int count=0;
  static int convert[10][3];
  static int depth=0;

  depth++;

  debug=flags&DEBUG_MASK;
  stream_len=0;

  dict_get_int(fd1,"/Length",&stream_len,fd2-fd1);

  OBJ_PRN("<<\n");
    
  /* Look for R's in a generic fashion */
    
  p=fd1;
  p0=fd1;
    
  while(p<fd2){
    while((*p!='R')&&(p<fd2)) p++;
    if (p>=fd2) break;
    p2=p+1;
    /* R must be followed by non-alphanumeric */
    if (isalnum(*(p+1))) {p++;continue;}
    /* R must be preceded by whitespace */
    if ((*(p-1)!=' ')&&(*(p-1)!='\n')&&(*(p-1)!='\n')) {p++;continue;}
    p--;
    /* Backspace through whitespace */
    while((*p==' ')||(*p=='\n')||(*p=='\r')) p--;
    /* Backspace through integer */
    while((*p>='0')&&(*p<='9')) p--;
    /* Backspace through whitespace */
    while((*p==' ')||(*p=='\n')||(*p=='\r')) p--;
    /* Backspace through integer */
    while((*p>='0')&&(*p<='9')) p--;
    p++;
    i=sscanf(p,"%d %d",&(convert[count][0]),&(convert[count][1]));
    if (i!=2){
      fprintf(stderr,"Error reading R in font\n");
      exit(1);
    }
    if (count==0) convert[count][2]=cref+1;
    else convert[count][2]=convert[count-1][2]+1;
    fwrite(p0,1,p-p0,pdf);
    xref[cref]+=p-p0;
    xref[cref]+=fprintf(pdf,"%d 0 R",convert[count][2]);
    count++;
    p0=p2;
    p=p2;
  }
  
  fwrite(p0,1,fd2-p0,pdf);
  xref[cref]+=fd2-p0;
  xref[cref]+=fprintf(pdf,">>\n");

  if (stream_len){ /* We might be a stream, not a dictionary */
    while((*p==' ')||(*p=='\n')||(*p=='\r')||(*p=='>')) p++;
    if (strncmp(p,"stream",6)==0){
      p+=6;
      while((*p==' ')||(*p=='\n')||(*p=='\r')) p++;
      xref[cref]+=fprintf(pdf,"stream\n");
      fwrite(p,1,stream_len,pdf);
      xref[cref]+=stream_len;
      xref[cref]+=fprintf(pdf,"\nendstream\n");
    }
  }
  
  xref[cref]+=fprintf(pdf,"endobj\n\n");
  
  /* Emit some raw objects */

  if (depth==1){
    for(i=0;i<count;i++){
      fix=0;
      p=get_obj(convert[i][0],convert[i][1]);
      /* Is this another dict? */
      p2=p;
      while((*p2=='\n')||(*p2=='\r')||(*p2==' ')) p2++;
      if (*p2=='<'){
	p2=refs[convert[i][0]].end;
	while((*p2=='\n')||(*p2=='\r')||(*p2==' ')) p2--;
	if (*p2=='>'){
	  fd1=p;
	  fd2=p2;
	  if (debug>2) fprintf(stderr,"Dictionary recurring\n");
	  get_dict(&fd1,&fd2);
	  //	  extract_dict(fd1,fd2,convert[count-1][2]+1);
	  extract_dict(fd1,fd2);
	  fix=1;
	}
      }
      if (fix==0){
	OBJ_PRN("");
	fwrite(p,1,refs[convert[i][0]].end-p,pdf);
	xref[cref]+=refs[convert[i][0]].end-p;
	xref[cref]+=fprintf(pdf,"\nendobj\n\n");
      }
    }
    count=0;
  }
  depth--;
}
  
static void strn_join(char **s1, char *s2, int n){
  int len=0,i;
  char *p;

  if (*s1) len=strlen(*s1);

  *s1=realloc(*s1,len+n+1);
  if (!s1){
    fprintf(stderr,"Realloc failed in str_join\n");
    exit(1);
  }
  p=*s1+len;
  for(i=0;i<n;i++) *(p++)=*(s2++);
  *p=0;
}

int qcmp(const void *a, const void *b){
  void **x,**y;
  
  x=(void**)a;
  y=(void**)b;

  if (*x<*y) return -1;
  if (*x>*y) return 1;
  return 0;
}

static void add_ends(void){
  int i,j;
  char *p;

  ends[0]=refs[0].p;
  for(i=1;i<xref_size;i++){
    if(refs[i].strm==0){
      p=refs[i].p;
      if (!p) continue;
      p--;
      /* Backspace through whitespace */
      while((*p==' ')||(*p=='\n')||(*p=='\r')) p--;
      /* Should now have string "endobj" */
      if (strncmp(p-5,"endobj",6)==0){
	p-=6;
	ends[i]=p;
      }
      else ends[i]=refs[i].p-1;
    }
  }
  ends[i]=pdf_end+1;

  qsort(ends,xref_size,sizeof(void *),qcmp);

  for(i=0;i<xref_size;i++){
    if(refs[i].strm==0){
      p=refs[i].p;
      if (!p) continue;
      if (refs[i].end!=pdf_end) continue;
      for(j=0;(j<xref_size)&&((char*)ends[j]<p);j++);
      refs[i].end=ends[j];
    }
  }
}

static int output_strm(char *p, int len){
  char *p0,*end;
  struct stream_tr *s;
  int hit,output=0;

  end=p+len;
  p0=p;

  while(p<end){
    hit=0;
    while((p<end)&&(*p!='/')) p++;
    if (p==end) break;
    /* We might have a key we need to translate */
    s=strm_tr;
    while(s){
      if (strncmp(p,s->old,strlen(s->old))==0){
        if (isalnum(p[strlen(s->old)])==0){
	  hit=1;
	  break;
	}
      }
      s=s->next;
    }
    if (hit){
      fwrite(p0,1,p-p0,pdf);
      output+=p-p0;
      p+=strlen(s->old);
      output+=fprintf(pdf,"%s",s->new);
      p0=p;
    }
    else p++;
  }

  fwrite(p0,1,end-p0,pdf);
  fwrite("\n",1,1,pdf);
  output+=end-p0+1;
  return output;
}

/* Addendum for reading EPS files */

void str_cat(char **s1, char *s2){
  strn_join(s1,s2,strlen(s2));
}

void readeps(char *file1,double llx,double lly,
             double width,double height,double angle,int depth,int fl){
  struct eps_obj {char *filename; char *convfile; struct eps_obj *next;};
  static struct eps_obj eps_images={NULL,NULL,NULL};
  int debug,old=0;
  char *cmd;
  static int count=0;
  char pdffile[60];
  struct eps_obj *img;

  debug=flags&DEBUG_MASK;

  if (dirname[0]==0) settmpdir();

  img=&eps_images;
  if ((img->filename)&&(strcmp(img->filename,file1)==0)) old=1;
  if (old==0){
    while (img->next){
      img=img->next;
      if ((img->filename)&&(strcmp(img->filename,file1)==0)) {
	old=1;
	break;
      }
    }
  }

  if (old!=0){
    if(debug>2) fprintf(stderr,"readpdf(%s,%f,%f,%f,%f,%f,%d,%d);\n",
			img->convfile, llx, lly, width, height,
			angle, depth, fl);

    readpdf(img->convfile, llx, lly, width, height, angle, depth, fl);
    return;
  }


  /* Now can construct gs command line */

  sprintf(pdffile,"%s/x-eps-%d.pdf",dirname,count);
  count++;

  cmd=malloc(4);
  strcpy(cmd,"gs ");
  str_cat(&cmd,"-q -dSAFER -dNOPAUSE -dBATCH -dEPSCrop ");

  str_cat(&cmd,"-sOutputFile=\"");
  str_cat(&cmd,pdffile);
  str_cat(&cmd,"\" -sDEVICE=pdfwrite -c .setpdfwrite -f \"");
  str_cat(&cmd,file1);
  str_cat(&cmd,"\"");

  if (debug) fprintf(stderr,"About to run\n%s\n",cmd);

  if (system(cmd)){
    fprintf(stderr,"EPS to PDF conversion failed\n");
    exit(1);
  }

  if (img->filename!=NULL){
    while(img->next) img=img->next;   /* move to end of list */
    img->next=malloc(sizeof(struct eps_obj));
    if (!img->next){
      fprintf(stderr,"Malloc error in read_eps\n");
      exit(1);
    }
    img=img->next;
  }

  img->next=NULL;
  img->filename=malloc(strlen(file1)+1);
  if (!img->filename){
    fprintf(stderr,"Malloc error in read_eps\n");
    exit(1);
  }
  strcpy(img->filename,file1);
  img->convfile=malloc(strlen(pdffile)+1);
  if (!img->convfile){
    fprintf(stderr,"Malloc error in read_eps\n");
    exit(1);
  }
  strcpy(img->convfile,pdffile);


  if(debug>2) fprintf(stderr,"readpdf(%s,%f,%f,%f,%f,%f,%d,%d);\n",
		      pdffile, llx, lly, width, height, angle, depth, fl);

  readpdf(pdffile, llx, lly, width, height, angle, depth, fl);
  
}

void settmpdir(void){
  if (dirname[0]==0) {
    strcpy(dirname,"/tmp/f2pXXXXXX");
    if(!mkdtemp(dirname)){
      fprintf(stderr,"Failed to make temporary directory\n");
      exit(1);
    }
    fprintf(stderr,"Temporary directory is %s\n",dirname);
  }
}

/* Addendum for LaTeX text */

void write_latex(char *text, int font, double size,
		 double llx, double lly, double angle, int colour, int depth){
  struct ltx_obj {char *text; char *font; char *file; struct ltx_obj *next;};
  static struct ltx_obj latex_objs={NULL,NULL,NULL,NULL};
  int i,old,debug;
  char old_dir[PATH_MAX];
  char s[20],cmd[100],out[20],log[20],ctmp[100];
  char *t2,*p,*p2;
  char *sfont=NULL;
  FILE *tex;
  struct ltx_obj *ltx;

  debug=flags&DEBUG_MASK;
  getcwd(old_dir,PATH_MAX);

  if (dirname[0]==0) settmpdir();
  
  chdir(dirname);

  sprintf(s,"x-%d.tex",count);
  sprintf(out,"x-%d.pdf",count);
  sprintf(log,"x-%d.out",count);

  tex=fopen(s,"w+");
  if (!tex) {
    fprintf(stderr,"Unable to create temp file %s\n",s);
    perror("");
    exit(1);
  }

  /* Need to replace '\\' with '\' */

  t2=malloc(strlen(text)+1);
  p=text;
  p2=t2;
  while(*p){
    while((*p)&&(*p!=0x5c)) *p2++=*p++;  /* \=0x5c */
    if (!*p) break;
    if (*(p+1)==0x5c) {
      *p2++=*p++;
      p++;
    }
  }
  *p2=0;

  if (((font>=0)&&(font<12))||((font>=24)&&(font<32)))
    str_cat(&sfont,"\\fontfamily{\\rmdefault}");
  if ((font>=12)&&(font<=15))
    str_cat(&sfont,"\\fontfamily{\\ttdefault}");
  if ((font>=16)&&(font<=23))
    str_cat(&sfont,"\\fontfamily{\\sfdefault}");
  if ((font<32)&&(font&1)) str_cat(&sfont,"\\fontshape{\\itdefault}");
  if ((font<32)&&(font&2)) str_cat(&sfont,"\\fontseries{\\bfdefault}");
  switch (font){
  case 257:
    str_cat(&sfont,"\\fontfamily{\\rmdefault}");
    break;
  case 258:
    str_cat(&sfont,"\\fontfamily{\\rmdefault}"
	    "\\fontseries{\\bfdefault}");
    break;
  case 259:
    str_cat(&sfont,"\\fontfamily{\\rmdefault}"
	    "\\fontshape{\\itdefault}");
    break;
  case 260:
    str_cat(&sfont,"\\fontfamily{\\sfdefault}");
    break;
  case 261:
    str_cat(&sfont,"\\fontfamily{\\ttdefault}");
    break;
  }

  sprintf(ctmp,"\\fontsize{%.2f}{%.2f}\n"
	  "\\selectfont\n",size,1.2*size);
  str_cat(&sfont,ctmp);

  ctmp[0]=0;
  if (colour){
    i=sprintf(ctmp,"\\color[rgb]{");
    p=fig_colours[colour];
    while(*p){
      if (*p==' ') ctmp[i++]=',';
      ctmp[i++]=*p;
      p++;
    }
    ctmp[i++]='}';
    ctmp[i]=0;
  }

  p=malloc(strlen(ctmp)+1);
  strcpy(p,ctmp);
  str_cat(&p,t2);
  free(t2);

  old=0;
  ltx=&latex_objs;
  if ((ltx->text)&&(strcmp(ltx->text,p)==0)&&
      (strcmp(ltx->font,sfont)==0)) old=1;
  if (old==0){
    while(ltx->next){
      ltx=ltx->next;
      if ((strcmp(ltx->text,p)==0)&&(strcmp(ltx->font,sfont)==0)){
	old=1;
	break;
      }
    }
  }

  if (old!=0){
    if (debug) fprintf(stderr,"Reusing result of previous LaTeX run\n");
    readpdf(ltx->file,llx,lly,-1,-1,angle,depth,NATURAL);
    return;
  }


  fprintf(tex,
	  "\\documentclass{article}\n"
	  "\\usepackage{calc,type1cm,color}\n"
	  "\\begin{document}\n"
	  "\\pdfpagewidth 0pt\n"
	  "\\pdfpageheight 0pt\n"
	  "\\pdfhorigin 1pt\n"
	  "\\pdfvorigin 1pt\n"
	  "\\hoffset 0pt\n"
	  "\\voffset 0pt\n"
	  "\\parindent 0pt\n"
	  "\\setlength{\\topmargin}{0pt}\n"
	  "\\setlength{\\headheight}{0pt}\n"
	  "\\setlength{\\headsep}{0pt}\n"
	  "\\setlength{\\marginparsep}{0pt}\n"
	  "\\setlength{\\marginparwidth}{0pt}\n"
	  "\\setlength{\\oddsidemargin}{0pt}\n");
  fprintf(tex,sfont);

  fprintf(tex,"\\newsavebox\\mybox\n"
	  "\\sbox{\\mybox}\n");

  fprintf(tex,"{\\strut");

  fprintf(tex," %s}\n",p);

  fprintf(tex,
	  "\\setlength{\\pdfpagewidth}"
	  "{\\wd\\mybox+2\\pdfhorigin}\n"
	  "\\setlength{\\pdfpageheight}"
	  "{\\ht\\mybox+\\dp\\mybox+2\\pdfvorigin}\n"
          "\\thispagestyle{empty}\n"
	  "\\usebox{\\mybox}\n"
	  "\\end{document}\n");

  fclose(tex);

  sprintf(cmd,"pdflatex %s < /dev/null > %s 2>&1",s,log);

  if (debug) fprintf(stderr,"Running %s\n",cmd);

  i=system(cmd);

  if(i){
    fprintf(stderr,"pdflatex failed\n");
    exit(1);
  }
    
  readpdf(out,llx,lly,-1,-1,angle,depth,NATURAL);

  count++;
  chdir(old_dir);

  if (ltx->file!=NULL){
    while(ltx->next) ltx=ltx->next;   /* move to end of list */
    ltx->next=malloc(sizeof(struct ltx_obj));
    if (!ltx->next){
      fprintf(stderr,"Malloc error in write_latex\n");
      exit(1);
    }
    ltx=ltx->next;
  }

  ltx->next=NULL;

  ltx->file=malloc(strlen(out)+1);
  if (!ltx->file){
    fprintf(stderr,"Malloc error in write_latex\n");
    exit(1);
  }
  strcpy(ltx->file,out);

  ltx->text=malloc(strlen(p)+1);
  if (!ltx->text){
    fprintf(stderr,"Malloc error in write_latex\n");
    exit(1);
  }
  strcpy(ltx->text,p);

  ltx->font=malloc(strlen(sfont)+1);
  if (!ltx->font){
    fprintf(stderr,"Malloc error in write_latex\n");
    exit(1);
  }
  strcpy(ltx->font,sfont);

  free(sfont);
  free(p);

}

void latex_clean(void){
  char old_dir[PATH_MAX];
  int debug;

  debug=flags&DEBUG_MASK;

  if (dirname[0]==0) return;
  if ((flags&DEBUG_MASK)>1){
    fprintf(stderr,"Temporary files in %s not deleted\n",dirname);
    return;
  }

  if (debug) fprintf(stderr,"Removing temporary directory %s\n",dirname);

  getcwd(old_dir,PATH_MAX);

  if(chdir(dirname)){
    fprintf(stderr,"chdir() failed in latex_clean\n");
    perror("");
    return;
  }

  system("rm x-*");
  chdir(old_dir);

  if(rmdir(dirname)){
    fprintf(stderr,"rmdir() failed in latex_clean\n");
    perror("");
    return;
  }

}
