/* p_font.c
 * Copyright (C) 1997-98 Thomas Merz. All rights reserved.
 *
 * PDFlib font embedding routines
 */

#include <string.h>

#include "p_intern.h"
#include "afmparse.h"
#include "ansi_e.h"
#include "macrom_e.h"
#include "pdfdoc_e.h"

#define MAXFILENAME	256
#define LINEBUFLEN	256

/* Bit positions for font descriptor flag */
#define FIXEDWIDTH	(long) (1L<<0)
#define SERIF		(long) (1L<<1)
#define SYMBOL		(long) (1L<<2)
#define SCRIPT		(long) (1L<<3)
#define ADOBESTANDARD	(long) (1L<<5)
#define ITALIC		(long) (1L<<6)
#define SMALLCAPS	(long) (1L<<17)
#define FORCEBOLD	(long) (1L<<18)

/* dummy values for stem width */
#define DEFAULT_STEMWIDTH	75
#define DEFAULT_STEMWIDTH_SEMI	105
#define DEFAULT_STEMWIDTH_BOLD	130

/* PDF's base 14 fonts, guaranteed to be always available */
const char *pdf_base14fonts[] = {
  "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique",
  "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique",
  "Symbol",
  "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic",
  "ZapfDingbats",
  NULL
};

const char *pdf_encoding_names[encoding_count] = {
    NULL, "PDFDocEncoding", "MacRomanEncoding", 
    "MacExpertEncoding", "WinAnsiEncoding"
};

PDF_encodingvector *pdf_encoding[encoding_count] = {
    NULL, &pdf_pdfdoc, &pdf_macroman, NULL, &pdf_winansi
};

/* Type 1 font portions: ASCII, encrypted, zeros */
typedef enum { ascii, encrypted, zeros } pdf_t1portion;

typedef struct {
    pdf_t1portion portion;
    long length1, length2, length3;
    FILE *fontfile;
} t1_private_data;

static void
t1data_init(PDF *p, PDF_data_source *src)
{
    t1_private_data *t1_private;

    t1_private = (t1_private_data *) src->private_data;

    t1_private->portion = ascii;
    t1_private->length1 = 0;
    t1_private->length2 = 0;
    t1_private->length3 = 0;

    src->buffer_start = (byte *) PDF_malloc(LINEBUFLEN, "t1data_init");
    src->buffer_length = LINEBUFLEN;
}

static bool
t1data_fill(PDF *p, PDF_data_source *src)
{
    static const char HexToBin[] = {
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0,
	10, 11, 12, 13, 14, 15 }; 
    char *s, *c;
    int i;
    t1_private_data *t1_private;

    t1_private = (t1_private_data *) src->private_data;

    if ((s = fgets((char *) src->buffer_start, LINEBUFLEN, 
    	t1_private->fontfile))== NULL)
	return false;

    /* check for line of zeros: set zero flag if found */
    for (c = s; *c == '0'; c++)
	;
    if (*c == '\n')
	t1_private->portion = zeros;

    src->next_byte = src->buffer_start;

    switch (t1_private->portion) {
	case ascii:
	    t1_private->length1 += strlen(s);
	    src->bytes_available = strlen(s);
	    break;

	case encrypted:
	    t1_private->length2 += strlen(s)/2;	/* ASCII -> binary */
	    src->bytes_available = strlen(s)/2;

	    /* convert ASCII to binary in-place */
	    for (i=0; s[i] != '\n'; i += 2)
	    {
		if (s[i] < '0' || s[i] > 'F' || s[i+1] < '0' || s[i+1] > 'F')
		    pdf_error(p, PDF_WARN, 
			"Bogus Type 1 font ASCII data (%c, %c)", s[i], s[i+1]);
		s[i/2] = 16*HexToBin[s[i] - '0'] + HexToBin[s[i+1] - '0'];
	    }
	    break;

	case zeros:
	    t1_private->length3 += strlen(s);
	    src->bytes_available = strlen(s);
	    break;
    }

    if (t1_private->portion != encrypted &&
		!strncmp((const char *)s, "currentfile eexec", 17))
	t1_private->portion = encrypted;

    return true;
}

static void
t1data_terminate(PDF *p, PDF_data_source *src)
{
    PDF_free((void *) src->buffer_start);
}

float
PDF_stringwidth(PDF *p, byte *text)
{
    byte *cp;
    float width = 0.0;

    for (cp = text; *cp; cp++)
	width += p->current_font->size * p->current_font->FontWidths[*cp]/1000;

    return width;
}

bool
pdf_read_afm(PDF *p, char *fontname, PDF_encoding enc)
{
    FILE	*afmfile;
    int		*widths;
    FontInfo	*fi;
    int		i, j;
    char	filename[MAXFILENAME], *charname;
    CharMetricInfo *cmi;

    /* HACK */
    widths = p->current_font->FontWidths;

    /* check for afm file */
    sprintf(filename, "%s.afm", fontname);

    /* try current directory */
    afmfile = fopen(filename, "r");

    if (afmfile == NULL && p->info->fontpath) {
	/* try font path if caller supplied one */
	sprintf(filename, "%s%s%s.afm", p->info->fontpath, PATHSEP, fontname);
	afmfile = fopen(filename, "r");
    }
    if (afmfile == NULL) {
	pdf_error(p, PDF_WARN, "Couldn't open AFM file for font %s", fontname);
	return false;
    }

    /* parse AFM file */
    if (pdf_parseFile(afmfile, &fi, P_G | P_M) != ok) {
	fclose(afmfile);
	pdf_error(p, PDF_FATAL, "Error parsing AFM file %s", filename);
	return false;
    }

    fclose(afmfile);

    if (fi->cmi == NULL) {
	pdf_error(p, PDF_FATAL, 
		"Couldn't parse char metrics in AFM file %s", filename);
	return false;
    }
    /* 
     * Generate character width array according to chosen encoding vector
     * or the font's default encoding vector.
     */
    if (enc != builtin && 
    	strcmp(fi->gfi->encodingScheme, "AdobeStandardEncoding")) {
	pdf_error(p, PDF_WARN, 
	    "Symbol font %s doesn't use builtin encoding", fontname);
    }
    if (enc == builtin &&
    	!strcmp(fi->gfi->encodingScheme, "AdobeStandardEncoding")) {
	pdf_error(p, PDF_WARN, "Text font %s uses builtin encoding",
		    fontname);
	enc = builtin;
    }

    if (enc != builtin && pdf_encoding[enc]) {
	for (i = 0; i < 256; i++) {	/* text font */
	    charname = (*pdf_encoding[enc])[i];
	    widths[i] = 0;
	    if (charname == NULL)	/* unencoded character */
		continue;
	    for (j = 0, cmi = fi->cmi; j < fi->numOfChars; ++j, ++cmi) {
		if (!strcmp(cmi->name, charname)) {
		    widths[i] = cmi->wx;
		    break;
		}
	    }
	}
    } else {				/* symbol or pi font */
	for (i = 0; i < 256; i++)
		widths[i] = 0;
	for (i = 0, cmi = fi->cmi; i < fi->numOfChars; i++, cmi++) {
	    if (cmi->code >= 0 && cmi->code < 256)
		widths[cmi->code] = cmi->wx;
	}
    }

    /* HACK */
    /* copy relevant data to current_font struct */
    p->current_font->ascender		= fi->gfi->ascender;
    p->current_font->descender		= fi->gfi->descender;
    p->current_font->capHeight		= fi->gfi->capHeight;
    p->current_font->isFixedPitch	= fi->gfi->isFixedPitch;
    p->current_font->italicAngle	= fi->gfi->italicAngle;
    p->current_font->llx		= fi->gfi->fontBBox.llx;
    p->current_font->lly		= fi->gfi->fontBBox.lly;
    p->current_font->urx		= fi->gfi->fontBBox.urx;
    p->current_font->ury		= fi->gfi->fontBBox.ury;
    p->current_font->StdVW		= fi->gfi->StdVW;
    p->current_font->StdHW		= fi->gfi->StdHW;
    p->current_font->underlinePosition	= fi->gfi->underlinePosition;
    p->current_font->underlineThickness	= fi->gfi->underlineThickness;

    strcpy(p->current_font->fontName,	  fi->gfi->fontName);
    strcpy(p->current_font->encodingScheme,  fi->gfi->encodingScheme);
    strcpy(p->current_font->weight,	  fi->gfi->weight);

    /* 
     * If we don't know the exact stem width we use default values
     * according to the font weight (regular, semi, or bold)
     */
    if (p->current_font->StdVW == 0) {
	if (!strcmp(p->current_font->weight, "Semibold"))
	    p->current_font->StdVW = DEFAULT_STEMWIDTH_SEMI;
	else if (!strcmp(p->current_font->weight, "Bold"))
	    p->current_font->StdVW = DEFAULT_STEMWIDTH_BOLD;
	else
	    p->current_font->StdVW = DEFAULT_STEMWIDTH;
    }

    /* free AFM parser's storage */
    freeStorage(fi);

    return true;
}

void
pdf_put_t1font(PDF *p, char *fontname, id font_id, int fontnumber, bool embed)
{
    FILE	*fontfile = NULL;
    char	filename[MAXFILENAME];
    int		*widths;
    int		i, j;
    unsigned long	fontflags;
    id		fontdescriptor_id, fontfile_id = 0l;
    id		length_id, length1_id, length2_id, length3_id;
    long	length, stream_start;
    PDF_data_source t1src;
    const char	**cp;
    PDF_encoding enc;

    enc		= p->current_font->encoding;

    /* Should use font cache instead */
    if (!pdf_read_afm(p, fontname, enc)) {	/* HACK */
	pdf_error(p, PDF_FATAL, "Couldn't read metrics for font %s", fontname);
	return;
    }

    widths	= p->current_font->FontWidths;

    /* check whether we have one of the base 14 fonts */
    for (cp = pdf_base14fonts; *cp; ++cp)
	if (!strcmp(*cp, fontname)) {
	    pdf_begin_obj(p, font_id);
	    pdf_begin_dict(p);

	    (void) fputs("/Type /Font\n", p->fp);
	    (void) fputs("/Subtype /Type1\n", p->fp);
	    (void) fprintf(p->fp, "/Name /F%d\n", fontnumber);

	    if (enc != builtin) {
		(void) fprintf(p->fp, "/Encoding /%s\n", 
			    pdf_encoding_names[enc]);
	    }
	    (void) fprintf(p->fp, "/BaseFont /%s\n", *cp);

	    pdf_end_dict(p);
	    pdf_end_obj(p);		/* font */

	    return;
	}

    /* check for font file */
    strcpy(filename, fontname);
    if (embed) {
	/* try current directory */
	fontfile = fopen(filename, "r");

	if (fontfile == NULL && p->info->fontpath) {
	    /* try font path if caller supplied one */
	    sprintf(filename, "%s%s%s", p->info->fontpath, PATHSEP, fontname);
	    fontfile = fopen(filename, "r");
	}
	if (fontfile == NULL) {
	    pdf_error(p, PDF_WARN, "Couldn't open font file %s", filename);
	    embed = false;
	}
    }

    /* Section 7.5: Font attributes */
    pdf_begin_obj(p, font_id);
    pdf_begin_dict(p);
    (void) fputs("/Type /Font\n", p->fp);
    (void) fputs("/Subtype /Type1\n", p->fp);
    (void) fprintf(p->fp, "/Name /F%d\n", fontnumber);
    (void) fputs("/FirstChar 0\n", p->fp);
    (void) fputs("/LastChar 255\n", p->fp);

    (void) fputs("/Widths [\n", p->fp);

    for (i = 0; i < 16; i++) {
	for (j = 0; j < 16; j++)
	    (void) fprintf(p->fp, " %d", widths[16*i + j]);
	(void) fputs("\n", p->fp);
    }

    (void) fputs("]\n", p->fp);

    if (enc != builtin) {
	(void) fprintf(p->fp, "/Encoding /%s\n", 
		    pdf_encoding_names[enc]);
    }
    (void) fprintf(p->fp, "/BaseFont /%s\n", fontname);

    fontdescriptor_id = pdf_alloc_id(p);
    (void) fprintf(p->fp, "/FontDescriptor %ld 0 R\n", fontdescriptor_id);

    pdf_end_dict(p);
    pdf_end_obj(p);		/* font */

    /* Section 7.8: font descriptors */
    pdf_begin_obj(p, fontdescriptor_id);
    pdf_begin_dict(p);
    (void) fputs( "/Type /FontDescriptor\n", p->fp);
    (void) fprintf(p->fp, "/Ascent %d\n", p->current_font->ascender);
    (void) fprintf(p->fp, "/CapHeight %d\n", p->current_font->capHeight);
    (void) fprintf(p->fp, "/Descent %d\n", p->current_font->descender);

    fontflags = 0;
    if (p->current_font->isFixedPitch)
    	fontflags |= FIXEDWIDTH;

    if (!strcmp(p->current_font->encodingScheme, "AdobeStandardEncoding"))
    	fontflags |= ADOBESTANDARD;
    else
    	fontflags |= SYMBOL;

    if (p->current_font->italicAngle < 0)
    	fontflags |= ITALIC;

    /* heuristic to identify (small) caps fonts */
    if (strstr(fontname, "Caps") ||
	!strcmp(fontname + strlen(fontname) - 2, "SC"))
    	fontflags |= SMALLCAPS;

    /* this doesn't catch all cases but will do for the moment */
    if (!strcmp(p->current_font->weight, "Bold"))
    	fontflags |= FORCEBOLD;

    (void) fprintf(p->fp, "/Flags %ld\n", fontflags);

    (void) fprintf(p->fp, "/FontBBox [%d %d %d %d ]\n",
		    p->current_font->llx,  p->current_font->lly,
		    p->current_font->urx,  p->current_font->ury);

    (void) fprintf(p->fp, "/FontName /%s\n", fontname);
    (void) fprintf(p->fp, "/ItalicAngle %d\n", 
			(int) (p->current_font->italicAngle));

    (void) fprintf(p->fp, "/StemV %d\n", p->current_font->StdVW);

    if (embed) {
	fontfile_id = pdf_alloc_id(p);
	(void) fprintf(p->fp, "/FontFile %ld 0 R\n", fontfile_id);
    }

    pdf_end_dict(p);
    pdf_end_obj(p);		/* font descriptor */

    /* Section 7.8.1: font files */
    if (embed) {
	pdf_begin_obj(p, fontfile_id);
	pdf_begin_dict(p);
	if (p->info->binary_mode == false)
	    (void) fputs("/Filter /ASCIIHexDecode\n", p->fp);

	length_id = pdf_alloc_id(p);
	length1_id = pdf_alloc_id(p);
	length2_id = pdf_alloc_id(p);
	length3_id = pdf_alloc_id(p);
	(void) fprintf(p->fp, "/Length %ld 0 R\n", length_id);
	(void) fprintf(p->fp, "/Length1 %ld 0 R\n", length1_id);
	(void) fprintf(p->fp, "/Length2 %ld 0 R\n", length2_id);
	(void) fprintf(p->fp, "/Length3 %ld 0 R\n", length3_id);

	/* Don't omit zeros since more data may follow at the end! */
	pdf_end_dict(p);

	pdf_begin_stream(p);			/* font data stream */
	stream_start = ftell(p->fp);

	t1src.init		= t1data_init;
	t1src.fill		= t1data_fill;
	t1src.terminate		= t1data_terminate;

	t1src.private_data = (unsigned char *)
			PDF_malloc(sizeof(t1_private_data), "pdf_put_t1font");
	((t1_private_data *) t1src.private_data)->fontfile = fontfile;

	if (p->info->binary_mode) {
	    t1src.init(p, &t1src);

	    while (t1src.fill(p, &t1src))
		(void) fwrite(t1src.next_byte, 1, t1src.bytes_available, p->fp);

	    t1src.terminate(p, &t1src);
	} else
	    pdf_ASCIIHexEncode(p, &t1src);

	length = ftell(p->fp) - stream_start;
	pdf_end_stream(p);
	pdf_end_obj(p);				/* font file object */

	pdf_begin_obj(p, length_id);		/* Length object */
	(void) fprintf(p->fp,"%ld\n", length);
	pdf_end_obj(p);

	pdf_begin_obj(p, length1_id);		/* Length1 object */
	(void) fprintf(p->fp,"%ld\n", 
		((t1_private_data *) t1src.private_data)->length1);
	pdf_end_obj(p);

	pdf_begin_obj(p, length2_id);		/* Length2 object */
	(void) fprintf(p->fp,"%ld\n",
		((t1_private_data *) t1src.private_data)->length2);
	pdf_end_obj(p);

	pdf_begin_obj(p, length3_id);		/* Length3 object */
	(void) fprintf(p->fp,"%ld\n",
		((t1_private_data *) t1src.private_data)->length3);
	pdf_end_obj(p);

	fclose(((t1_private_data *) t1src.private_data)->fontfile);
	PDF_free((void *) t1src.private_data);
    }
}
