/*  $Header$

    This is dvipdfm, a DVI to PDF translator.
    Copyright (C) 1998, 1999 by Mark A. Wicks

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    
    The author may be contacted via the e-mail address

	mwicks@kettering.edu
*/

#include <stdio.h>
#include "system.h"
#include "mem.h"
#include "error.h"
#include "mfileio.h"
#include "numbers.h"
#include "pkfont.h"
#include "pdfobj.h"
#include "pdflimits.h"
#include "tfm.h"
#include "ctype.h"

pdf_obj *pk_encoding_ref = NULL;
static unsigned char verbose = 0, debug = 0;
static unsigned font_dpi = 600;

void pk_set_verbose(void)
{
  if (verbose < 255) {
    verbose += 1;
  }
}

void pk_set_dpi (int dpi)
{
  font_dpi = dpi;
}


static void make_pk_encoding_ref (void)
{
  static char first = 1;
  if (first) {
    int i;
    pdf_obj *encoding, *differences;
    encoding = pdf_new_dict();
    pdf_add_dict (encoding, 
		  pdf_new_name ("Type"),
		  pdf_new_name ("Encoding"));
    differences = pdf_new_array();
    pdf_add_dict (encoding,
		  pdf_new_name ("Differences"),
		  differences);
    pdf_add_array (differences, pdf_new_number (0.0));
    for (i=0; i<256; i++) {
      sprintf (work_buffer, "x%x", i);
      pdf_add_array (differences, pdf_new_name(work_buffer));
    }
    pk_encoding_ref = pdf_ref_obj (encoding);
    pdf_release_obj (encoding);
  }
  first = 0;
  return;
}

static void release_pk_encoding_ref (void)
{
  pdf_release_obj (pk_encoding_ref);
}

struct a_pk_font 
{
  pdf_obj *direct, *indirect;
  char *tex_name, *pk_file_name;
  double ptsize;
  char *used_chars;
} *pk_fonts;

void init_pk_record (struct a_pk_font *p)
{
  int i;
  p->used_chars = NEW (256, char);
  for (i=0; i<256; i++) {
    (p->used_chars)[i] = 0;
  }
  p->tex_name = NULL;
  p->pk_file_name = NULL;
}


int num_pk_fonts = 0, max_pk_fonts = 0;

int pk_font (const char *tex_name, double ptsize, int tfm_font_id, char
	     *res_name)
{
  int i, j;
  int firstchar, lastchar;
  pdf_obj *tmp1;
  for (i=0; i<num_pk_fonts; i++) {
    if (!strcmp (tex_name, pk_fonts[i].tex_name) &&
	(ptsize == pk_fonts[i].ptsize))
      break;
  }
  if (i == num_pk_fonts) {
    char *pk_file_name;
    kpse_glyph_file_type kpse_file_info;
    if ((pk_file_name = kpse_find_glyph(tex_name,
					font_dpi*ptsize/tfm_get_design_size(tfm_font_id)+0.5,
					kpse_pk_format,
					&kpse_file_info))) {
      /* Make sure there is enough room in pk_fonts for this entry */
      if (num_pk_fonts >= max_pk_fonts) {
	max_pk_fonts += MAX_FONTS;
	pk_fonts = RENEW (pk_fonts, max_pk_fonts, struct a_pk_font);
      }
      init_pk_record (pk_fonts+i);
      pk_fonts[i].pk_file_name = NEW (strlen(pk_file_name)+1, char);
      strcpy (pk_fonts[i].pk_file_name, pk_file_name);
      pk_fonts[i].tex_name = NEW (strlen (tex_name)+1, char);
      strcpy (pk_fonts[i].tex_name, tex_name);
      pk_fonts[i].ptsize = ptsize;
      pk_fonts[i].direct = pdf_new_dict ();
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("Type"),
		    pdf_new_name ("Font"));
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("Name"),
		    pdf_new_name (res_name));
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("Subtype"),
		    pdf_new_name ("Type3"));
      firstchar = tfm_get_firstchar(tfm_font_id);
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("FirstChar"),
		    pdf_new_number (firstchar));
      lastchar = tfm_get_lastchar(tfm_font_id);
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("LastChar"),
		    pdf_new_number (lastchar));
      tmp1 = pdf_new_array ();
      for (j=firstchar; j<=lastchar; j++) {
	pdf_add_array (tmp1,
		       pdf_new_number(ROUND(tfm_get_width (tfm_font_id, j)*1000.0,0.01)));
      }
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("Widths"),
		    tmp1);
      {
	double max_height, max_depth, max_width;
	max_height = tfm_get_max_height (tfm_font_id)*1000.0;
	max_depth = tfm_get_max_depth (tfm_font_id)*1000.0;
	max_width = tfm_get_max_width (tfm_font_id)*1000.0;
	max_height = ROUND(max_height, 1.0);
	max_depth = ROUND(max_depth, 1.0);
	max_width = ROUND(max_width, 1.0);
	tmp1 = pdf_new_array();
	pdf_add_array (tmp1, pdf_new_number(-0.10*max_width));
	pdf_add_array (tmp1,
		       pdf_new_number(-max_depth-0.20*(max_height+max_depth)));
	pdf_add_array (tmp1, pdf_new_number(1.10*max_width));
	pdf_add_array (tmp1, pdf_new_number(max_height+0.10*(max_height+max_depth)));
	pdf_add_dict (pk_fonts[i].direct, 
		      pdf_new_name ("FontBBox"), tmp1);
      }
      {
	tmp1 = pdf_new_array();
	pdf_add_array (tmp1, pdf_new_number (0.001));
	pdf_add_array (tmp1, pdf_new_number (0.0));
	pdf_add_array (tmp1, pdf_new_number (0.0));
	pdf_add_array (tmp1, pdf_new_number (0.001));
	pdf_add_array (tmp1, pdf_new_number (0.0));
	pdf_add_array (tmp1, pdf_new_number (0.0));
	pdf_add_dict (pk_fonts[i].direct, pdf_new_name ("FontMatrix"),
		      tmp1);
      }
      if (pk_encoding_ref == NULL) {
	make_pk_encoding_ref();
      }
      pdf_add_dict (pk_fonts[i].direct,
		    pdf_new_name ("Encoding"),
		    pdf_link_obj (pk_encoding_ref));
      pk_fonts[i].indirect = pdf_ref_obj (pk_fonts[i].direct);
      num_pk_fonts += 1;
    } else { /* Found no glyph file */
      i = -1;
    }
  }
  return i;
}

pdf_obj *pk_font_resource (int pk_id)
{
  if (pk_id >= 0 && pk_id < num_pk_fonts)
    return pdf_link_obj (pk_fonts[pk_id].indirect);
  else {
    fprintf (stderr, "pk_id = %d\n", pk_id);
    ERROR ("pk_font_resource:  Invalid pk_id\n");
  }
}

char *pk_font_used (int pk_id)
{
  if (pk_id >= 0 && pk_id < num_pk_fonts) 
    return pk_fonts[pk_id].used_chars;
  else {
    fprintf (stderr, "pk_id = %d\n", pk_id);
    ERROR ("pk_font_used:  Invalid pk_id\n");
  }
}


static FILE *pk_file;

static void do_skip(unsigned long length) 
{
  unsigned long i;
  for (i=0; i<length; i++) 
    fgetc (pk_file);
  return;
}

static void add_raster_data (pdf_obj *glyph, long w, long h,
			     int dyn_f, int run_color,
			     unsigned char *pk_data,
			     unsigned char *eod)

     /* First define some macros to be used as "in-line" functions */
#define advance_nybble() \
{ \
  if (partial_byte) { \
    partial_byte=0; \
    pk_data++; \
  } else \
    partial_byte=1; \
}

#define current_nybble() (partial_byte? (*pk_data%16): (*pk_data/16))
#define set_bits(n) {\
  int i; \
  for (i=0; i<(n); i++) { \
    row[next_col] |= (128u>>next_bit++); \
    if (next_bit > 7) { \
      next_bit = 0; \
      next_col += 1; \
    } \
  } \
}

#define skip_bits(n) {\
  next_col += (n)/8; \
  next_bit += (n)%8; \
  if (next_bit > 7) { \
    next_bit -= 8; \
    next_col += 1; \
  } \
}

#define get_bit(p,n) (p[(n)/8] & (128u>>((n)%8)))

{
  long i, w_bytes, repeat_count, run_count = 0;
  int partial_byte = 0;
  unsigned char *row;
  w_bytes = (w%8 == 0)? w/8: (w/8+1);
  row = NEW (w_bytes, unsigned char);
  /* Make sure we output "h" rows of data */
  if (dyn_f == 14) {
    for (i=0; i<h; i++) {
      int next_col = 0, next_bit = 0;
      int j;
      for (j=0; j<w_bytes; j++) row[j] = 0;
      for (j=0; j<w; j++) {
	if (get_bit(pk_data,i*w+j)) {
	  skip_bits(1);
	}
	else {
	  set_bits(1);
	}
      }
      pdf_add_stream (glyph, (char *) row, w_bytes);
    }
  } else
    for (i=0; i<h; i++) {
      int next_col = 0, next_bit = 0;
      int j, row_bits_left = w;
      /* Initialize row to all zeros */
      for (j=0; j<w_bytes; j++) row[j] = 0;
      repeat_count = 0;
      /* Fill any run left over from previous rows */
      if (run_count != 0) {
	int nbits;
	nbits = MIN (w, run_count);
	run_count -= nbits;
	switch (run_color) {
	case 1:  /* This is actually white ! */
	  set_bits(nbits);
	  break;
	case 0:
	  skip_bits(nbits);
	  break;
	}
	row_bits_left -= nbits;
      }
      /* Read nybbles until we have a full row */
      while (pk_data < eod && row_bits_left>0) {
	int com_nyb;
	long packed = 0;
	com_nyb = current_nybble();
	if (com_nyb == 15) {
	  repeat_count = 1;
	  advance_nybble();
	  continue;
	}
	if (com_nyb == 14) {
	  advance_nybble();
	}
	/* Get a packed number */
	{
	  int nyb;
	  nyb = current_nybble();
	  /* Test for single nybble case */
	  if (nyb > 0 && nyb <= dyn_f) {
	    packed = nyb;
	    advance_nybble();
	  }
	  if (nyb > dyn_f) {
	    advance_nybble();
	    packed = (nyb-dyn_f-1)*16+current_nybble()+dyn_f+1; 
	    advance_nybble();
	  }
	  if (nyb == 0) {
	    int nnybs = 1;
	    while (current_nybble() == 0) {
	      advance_nybble();
	      nnybs += 1;
	    }
	    packed = 0;
	    while (nnybs) {
	      packed = packed*16 + current_nybble();
	      advance_nybble();
	      nnybs -= 1;
	    }
	    packed += (13-dyn_f)*16-15+dyn_f;
	  }
	}
	if (com_nyb == 14) {
	  repeat_count = packed;
	  continue;
	}
	{
	  int nbits;
	  run_count = packed;    
	  run_color = !run_color;
	  nbits = MIN (row_bits_left, run_count);
	  run_count -= nbits;
	  row_bits_left -= nbits;
	  switch (run_color) {
	  case 1: 
	    set_bits(nbits);
	    break;
	  case 0:
	    skip_bits(nbits);
	    break;
	  }
	}
	continue;
      }
      pdf_add_stream (glyph, (char *) row, w_bytes);
      /* Duplicate the row "repeat_count" times */
      for (j=0; j<repeat_count; j++) {
	pdf_add_stream (glyph, (char *) row, w_bytes);
      }
      /* Skip repeat_count interations */
      i += repeat_count;
    }
  RELEASE (row);
}

static void do_preamble(void)
{
  /* Check for id byte */
  if (fgetc (pk_file) == 89) {
    /* Skip comment */
    do_skip (get_unsigned_byte (pk_file));
    /* Skip other header info.  It's normally used for verifying this
       is the file wethink it is */
    do_skip (16);
  } else {
    ERROR ("embed_pk_font: PK ID byte is incorrect.  Are you sure this is a PK file?");
  }
  return;
}

#define SHORT_FORM 1
#define MED_FORM 2
#define LONG_FORM 3
static void do_character (unsigned char flag, int pk_id, pdf_obj *char_procs)
{
  int format;
  unsigned long packet_length = 0, code = 0;
  /* Last three bits of flag determine packet size in a complex way */
  if ((flag & 4) == 0) {
    format = SHORT_FORM;
  } else if ((flag & 7) == 7) {
    format = LONG_FORM;
  } else {
    format = MED_FORM;
  }
  if (debug) {
    fprintf (stderr, "packet format: %d\n", format);
  }
  switch (format) {
  case SHORT_FORM:
    packet_length = (flag & 3) * 256u + get_unsigned_byte (pk_file);
    code = get_unsigned_byte (pk_file);
    break;
  case MED_FORM:
    packet_length = (flag & 3) * 65536ul + get_unsigned_pair(pk_file);
    code = get_unsigned_byte (pk_file);
    break;
  case LONG_FORM:
    packet_length = get_unsigned_quad (pk_file);
    code = get_unsigned_quad (pk_file);
    if (code > 255)
      ERROR ("Unable to handle long characters in PK files");
    break;
  }
  if (debug) {
    fprintf (stderr, "\npk_do_character: code=%lu, packet_length=%lu\n",
	     code, packet_length);
  }
  if ((pk_fonts[pk_id].used_chars)[code%256]) {
    int dyn_f;
    unsigned long tfm_width = 0;
    long dm=0, dx=0, dy=0, w=0, h=0;
    int hoff=0, voff=0;
    dyn_f = flag/16;
    switch (format) {
    case SHORT_FORM:
      tfm_width = get_unsigned_triple (pk_file);
      dm = get_unsigned_byte (pk_file);
      w = get_unsigned_byte (pk_file);
      h = get_unsigned_byte (pk_file);
      hoff = get_signed_byte (pk_file);
      voff = get_signed_byte (pk_file);
      packet_length -= 8;
      break;
    case MED_FORM:
      tfm_width = get_unsigned_triple (pk_file);
      dm = get_unsigned_pair (pk_file);
      w = get_unsigned_pair (pk_file);
      h = get_unsigned_pair (pk_file);
      hoff = get_signed_pair (pk_file);
      voff = get_signed_pair (pk_file);
      packet_length -= 13;
      break;
    case LONG_FORM:
      tfm_width = get_signed_quad (pk_file);
      dx = get_signed_quad (pk_file);
      dy = get_signed_quad (pk_file);
      w = get_signed_quad (pk_file);
      h = get_signed_quad (pk_file);
      hoff = get_signed_quad (pk_file);
      voff = get_signed_quad (pk_file);
      packet_length -= 28;
      break;
    }
    {
      pdf_obj *glyph;
      double char_width, llx, lly, urx, ury;
      double pix2charu;
      int len;
      pix2charu = 72000.0/((double) font_dpi)/pk_fonts[pk_id].ptsize;
      char_width = tfm_width / (double) (1<<20) * 1000.0;
      char_width = ROUND (char_width, 0.01);
      llx = -hoff*pix2charu;
      lly = (voff-h)*pix2charu;
      urx = (w-hoff)*pix2charu;
      ury = voff*pix2charu;
      glyph = pdf_new_stream(STREAM_COMPRESS);
      /* The following line is a "metric" for the PDF reader */
      len = sprintf (work_buffer, "%.2f %.2f %.2f %.2f %.2f %.2f d1",
		     char_width, 0.0,
		     llx, lly, urx, ury);
      pdf_add_stream (glyph, work_buffer, len);
      /* For now add an outline box for debugging purposes */
      /*      len = sprintf (work_buffer, " %.2f %.2f m %.2f %.2f l %.2f %.2f l %.2f %.2f l s",
		     llx, lly, urx, lly, urx, ury, llx, ury);
		     pdf_add_stream (glyph, work_buffer, len); */
      /* Scale and translate origin to lower left corner for raster data */
      len = sprintf (work_buffer, " q %.2f 0 0 %.2f %.2f %.2f cm",
		     w*pix2charu, h*pix2charu, llx, lly);
      pdf_add_stream (glyph, work_buffer, len);
      if (w != 0 && h != 0 && packet_length != 0) {
	unsigned char *pk_data;
	long read_len;
	len = sprintf (work_buffer, "\nBI\n/W %ld\n/H %ld\n/IM true\n/BPC 1\n/I true\n",
		       w, h);
	pdf_add_stream (glyph, work_buffer, len);
	len = sprintf (work_buffer, "ID ");
	pdf_add_stream (glyph, work_buffer, len);
	pk_data = NEW (packet_length, unsigned char);
	if ((read_len=fread (pk_data, 1, packet_length, pk_file))!=
	    packet_length) {
	  fprintf (stderr, "packet length should be %ld, but only %ld bytes were read\n",
		   packet_length, read_len);
	  ERROR ("Error reading character packet from PK file\n");
	}
	add_raster_data (glyph, w, h, dyn_f, (flag&8)>>3, pk_data, pk_data+packet_length);
	RELEASE (pk_data);
	len = sprintf (work_buffer, "\nEI");
	pdf_add_stream (glyph, work_buffer, len);
      } /* Otherwise we embed an empty stream :-( */
      len = sprintf (work_buffer, "\nQ");
      pdf_add_stream (glyph, work_buffer, len);
      sprintf (work_buffer, "x%x", (int)code%256);
      pdf_add_dict (char_procs, pdf_new_name (work_buffer),
		    pdf_ref_obj (glyph));
      pdf_release_obj (glyph);
    }
  } else {
    if (debug)
      fprintf (stderr, "\npk_do_character: Skipping code=%ld, length=%ld\n", code,
	       packet_length);
    do_skip (packet_length);
  }
}

#define PK_XXX1 240
#define PK_XXX2 241
#define PK_XXX3 242
#define PK_XXX4 243
#define PK_YYY 244
#define PK_POST 245
#define PK_NO_OP 246
#define PK_PRE 247

static void embed_pk_font (int pk_id)
{
  if (verbose)
    fprintf (stderr, "(%s", pk_fonts[pk_id].pk_file_name);
  if ((pk_file = MFOPEN (pk_fonts[pk_id].pk_file_name,
			FOPEN_RBIN_MODE))) {
    int pk_command_byte;
    pdf_obj *char_procs;
    char_procs = pdf_new_dict();
    while ((pk_command_byte = fgetc(pk_file)) >= 0 &&
	   pk_command_byte != PK_POST) {
      if (pk_command_byte < 240) {
	do_character (pk_command_byte, pk_id, char_procs);
      } else  /* A command byte */
	switch (pk_command_byte) {
	case PK_NO_OP:
	  break;
	case PK_XXX1:
	  do_skip(get_unsigned_byte(pk_file));
	  break;
	case PK_XXX2:
	  do_skip(get_unsigned_pair(pk_file));
	  break;
	case PK_XXX3:
	  do_skip(get_unsigned_triple(pk_file));
	  break;
	case PK_XXX4:
	  do_skip(get_unsigned_quad(pk_file));
	  break;
	case PK_YYY:
	  do_skip(4);
	  break;
	case PK_PRE:
	  do_preamble();
	  break;
	}
    }
    MFCLOSE (pk_file);
    if (verbose) 
      fprintf (stderr, ")\n");
    pdf_add_dict (pk_fonts[pk_id].direct,
		  pdf_new_name ("CharProcs"),
		  pdf_ref_obj(char_procs));
    pdf_release_obj (char_procs);
  } else {
    ERROR ("embed_pk_font: Failed to open PK file");
  }
}

void pk_close_all (void)
{
  int i;
  if (pk_encoding_ref)
    pdf_release_obj (pk_encoding_ref);
  for (i=0; i<num_pk_fonts; i++) {
    embed_pk_font (i);
    pdf_release_obj (pk_fonts[i].direct);
    pdf_release_obj (pk_fonts[i].indirect);
    RELEASE (pk_fonts[i].tex_name);
    RELEASE (pk_fonts[i].pk_file_name);
    RELEASE (pk_fonts[i].used_chars);
  }
  if (pk_fonts)
    RELEASE (pk_fonts);
}

