/* p_basic.c
 * Copyright (C) 1997-98 Thomas Merz. All rights reserved.
 *
 * PDFlib general routines
 */

#include <stdlib.h>
#include <time.h>
#include <string.h>

#include "p_intern.h"

/* 
 * The names arrays must be kept in sync with the corresponding
 * enum types in pdf.h
 */
const char *pdf_filter_names[compression_count] = {
    "", "LZWDecode", "RunLengthDecode", "CCITTFaxDecode",
    "DCTDecode", "FlateDecode"
};

const char *pdf_colorspace_names[colorspace_count] = {
    "DeviceGray", "DeviceRGB", "DeviceCMYK", 
    "CalGray", "CalRGB", "Lab", 
    "Indexed", "Pattern", "Separation"
};

const char *pdf_transition_names[] = {
    "", "Split", "Blinds", "Box", "Wipe", "Dissolve", "Glitter", "R"
};

void
pdf_default_error_handler(int level, const char* fmt, va_list ap)
{
    switch (level) {
	case PDF_INFO:
		fprintf(stderr, "PDFlib note: ");
		vfprintf(stderr, fmt, ap);
		fputc('\n', stderr);
		break;

	case PDF_WARN:
		fprintf(stderr, "PDFlib warning: ");
		vfprintf(stderr, fmt, ap);
		fputc('\n', stderr);
		break;

	case PDF_INTERNAL:
		fprintf(stderr, "PDFlib internal error: ");
		vfprintf(stderr, fmt, ap);
		fputc('\n', stderr);
		exit(88);
		break;

	case PDF_FATAL:
	default:
		fprintf(stderr, "PDFlib fatal error: ");
		vfprintf(stderr, fmt, ap);
		fputc('\n', stderr);
		exit(99);
    }
}

void
pdf_error(PDF *p, int level, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    (*p->info->error_handler)(level, fmt, ap);
    va_end(ap);
}

void *
PDF_malloc(size_t size, char *caller)
{
    void *ret;

    if ((ret = calloc(size, 1)) == NULL) {
	fprintf(stderr, "PDFLIB: couldn't allocate memory in %s!\n", caller);
    }

    return ret;
}

void
PDF_free(void *mem)
{
    free(mem);
}

PDF_info*
PDF_get_info(void)
{
    PDF_info *info;

    info = (PDF_info *) PDF_malloc(sizeof(PDF_info), "PDF_get_info");

    info->binary_mode 		= false;
    info->required_compatibility= PDF1_1;

    /* Info dictionary entries */
    info->Keywords		= NULL;
    info->Subject		= NULL;
    info->ModDate		= NULL;
    info->Title			= NULL;
    info->CreationDate		= NULL;
    info->Creator		= NULL;
    info->Producer		= NULL;
    info->Author		= NULL;

    info->error_handler		= pdf_default_error_handler;

    info->fontpath		= PDF_DEFAULT_FONT_PATH;
    return info;
}

PDF *
PDF_open(FILE *fp, PDF_info *info)
{
    time_t	timer;
    struct tm	ltime;
    PDF		*p;

    if (info == NULL)
	return (PDF *) NULL;

    if ((p = (PDF *) PDF_malloc(sizeof(PDF), "PDF_open")) == NULL)
	return (PDF *) NULL;

    p->fp		= fp;
    p->info		= info;

#ifdef DEBUG
	/* We override the caller's option for debugging! */
    p->info->binary_mode 		= true;
#endif

    if (ftell(p->fp) == -1)
	pdf_error(p, PDF_FATAL, "Problem with PDF output file!");

    (void) fputs("%PDF-1.1\n", p->fp);		/* Header */
    /* write binary magic number for ASCII and binary files! */
    (void) fputs("%\342\343\317\323\n", p->fp);

    p->info_id = pdf_begin_obj(p, NEW_ID);	/* Info object */

    pdf_begin_dict(p);
    if(p->info->Keywords)
	(void) fprintf(p->fp, "/Keywords (%s)\n", p->info->Keywords);
    if(p->info->Subject)
	(void) fprintf(p->fp, "/Subject (%s)\n", p->info->Subject);
    if(p->info->Title)
	(void) fprintf(p->fp, "/Title (%s)\n", p->info->Title);
    if(p->info->Creator)
	(void) fprintf(p->fp, "/Creator (%s)\n", p->info->Creator);
    if(p->info->Author)
	(void) fprintf(p->fp, "/Author (%s)\n", p->info->Author);

    time(&timer);
    ltime = *localtime(&timer);
    (void) fprintf(p->fp, "/CreationDate (D:%04d%02d%02d%02d%02d%02d)\n",
	    ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday,
	    ltime.tm_hour, ltime.tm_min, ltime.tm_sec);

    (void) fprintf(p->fp,"/Producer (%s)\n", PDFLIB_VERSION);
    pdf_end_dict(p);
    pdf_end_obj(p);

    p->current_page	= 0;
    p->outlines_id	= 0;
    p->pages_id		= pdf_alloc_id(p);
    p->root_id		= pdf_alloc_id(p);
    p->open_outlines	= 0;
    p->image_number	= 0;
    p->outline_count	= 0;

    /* HACK, should use font cache */
    p->current_font	= (pdf_font *) PDF_malloc(sizeof(pdf_font), "PDF_open");

    return p;
}

/* separate finalize routine requested by Just van Rossum
 * for supporting the Python binding
 */

void
pdf_finalize(PDF *p)
{
    fclose(p->fp);


    /* HACK, should use font cache */
    PDF_free((void *)p->current_font);

    PDF_free((void *)p->info);
    PDF_free((void *)p);
    /* TODO: free item list */
}

void
PDF_close(PDF *p)
{
    long	pos;
    int		page;
    id		i;

    if (p->current_page == 0 ) {		/* avoid empty document */
	PDF_begin_page(p, 100.0, 100.0);	/* arbitrary page size */
	PDF_end_page(p);
	pdf_error(p, PDF_WARN, "Empty document!");
    }

    p->open_action = pdf_begin_obj(p, NEW_ID);
    (void) fprintf(p->fp,"[ %ld 0 R /Fit ]\n", p->pages[1]);
    pdf_end_obj(p);

    pdf_put_fonts(p);				/* Font objects */

    pdf_begin_obj(p, p->pages_id);	/* root Pages object */
    pdf_begin_dict(p);
    (void) fputs("/Type /Pages\n", p->fp);
    (void) fprintf(p->fp,"/Count %d\n", p->current_page);

    (void) fputs("/Kids [", p->fp);
    for (page = 1; page <= p->current_page; page++) {
	(void) fprintf(p->fp,"%ld 0 R", p->pages[page]);
	(void) fputs(page % 6 ? " " : "\n", p->fp);
    }
    (void) fputs("]\n", p->fp);

    pdf_end_dict(p);
    pdf_end_obj(p);

    pdf_begin_obj(p, p->root_id);		/* Catalog or Root object */
    pdf_begin_dict(p);
    (void) fputs("/Type /Catalog\n", p->fp);

    /* make the first page fit in the window */
    (void) fprintf(p->fp,"/OpenAction %ld 0 R\n", p->open_action);

    /* open outlines if the document has any */
    if (p->outline_count > 0)
	(void) fputs("/PageMode /UseOutlines\n", p->fp);

    						/* Pages object */
    (void) fprintf(p->fp,"/Pages %ld 0 R\n", p->pages_id);
    if (p->outlines_id != 0)
	(void) fprintf(p->fp,"/Outlines %ld 0 R\n", p->outlines_id);

    pdf_end_dict(p);
    pdf_end_obj(p);

    pdf_write_outlines(p);

    /* don't write any objects after this check! */
    for (i = 1; i <= p->currentobj; i++) {
	if (p->file_offset[i] == BAD_ID) {
	    pdf_error(p, PDF_WARN, 
	    	"Object %ld allocated but not used (dummy inserted)!", i);
	    pdf_begin_obj(p, i);
	    pdf_end_obj(p);
	}
    }

    pos = ftell(p->fp);					/* xref table */
    (void) fputs("xref\n", p->fp);
    (void) fprintf(p->fp,"0 %ld\n", p->currentobj + 1);
    (void) fputs("0000000000 65535 f \n", p->fp);
    for (i = 1; i <= p->currentobj; i++) {
	(void) fprintf(p->fp,"%010ld 00000 n \n", p->file_offset[i]);
#ifdef FOOBAR
	(void) fprintf(p->fp,"%010ld 00000 n\n", p->file_offset[i]);
#endif
    }

    (void) fputs("trailer\n", p->fp);
    pdf_begin_dict(p);
    (void) fprintf(p->fp,"/Size %ld\n", p->currentobj + 1);
    (void) fprintf(p->fp,"/Info %ld 0 R\n", p->info_id);
    (void) fprintf(p->fp,"/Root %ld 0 R\n", p->root_id);
    pdf_end_dict(p);
    (void) fputs("startxref\n", p->fp);
    (void) fprintf(p->fp,"%ld\n", pos);
    (void) fputs("%%EOF\n", p->fp);

    pdf_finalize(p);

}

void
pdf_begin_contents_section(PDF *p)
{
    if (p->contents != c_none)
	return;

    if (p->next_content > MAX_CONTENTS)
	pdf_error(p, PDF_FATAL, "Too many content sections on page (%ld)!",
			p->contents);

    							/* Contents object */
    p->contents_ids[p->next_content] = pdf_begin_obj(p, NEW_ID);
    p->contents	= c_stream;
    pdf_begin_dict(p);
    p->contents_length = pdf_alloc_id(p);
    (void) fprintf(p->fp,"/Length %ld 0 R\n", p->contents_length);
    pdf_end_dict(p);

    pdf_begin_stream(p);
    p->start_contents_pos = ftell(p->fp);
    p->next_content++;
}

void
pdf_end_contents_section(PDF *p)
{
    long length;

    if (p->contents == c_none)
	return;

    pdf_end_text(p);
    p->contents = c_none;

    length = ftell(p->fp) - p->start_contents_pos;
    pdf_end_stream(p);
    pdf_end_obj(p);

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

void
PDF_begin_page(PDF *p, float height, float width)
{
    if (++(p->current_page) > MAX_PAGES)
	pdf_error(p, PDF_FATAL, "Too many pages in document (%d)!",
		p->current_page);

    p->pages[p->current_page]	= pdf_alloc_id(p);

    p->height	= height;
    p->width	= width;

    p->next_content = 0;
    p->contents = c_none;
    pdf_begin_contents_section(p);
}

void
PDF_end_page(PDF *p)
{
    pdf_item	*i;
    int		index = 0;

    pdf_end_contents_section(p);

    pdf_begin_obj(p, p->pages[p->current_page]);	/* Page object */
    pdf_begin_dict(p);
    (void) fputs("/Type /Page\n", p->fp);
    (void) fprintf(p->fp,"/Parent %ld 0 R\n", p->pages_id);

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

    (void) fprintf(p->fp,"/MediaBox [0 0 %g %g]\n", p->height, p->width);

    if (p->duration > 0)
	(void) fprintf(p->fp, "/D %s\n", pdf_float(p->duration));

    if (p->transition != trans_none) {
	(void) fputs("/Trans ", p->fp);
	pdf_begin_dict(p);
	    (void) fprintf(p->fp, "/S /%s",
	    			pdf_transition_names[p->transition]);
	pdf_end_dict(p);
    }

    (void) fputs("/Contents [", p->fp);
    for (index = 0; index < p->next_content; index++) {
	(void) fprintf(p->fp,"%ld 0 R", p->contents_ids[index]);
	(void) fputs(index+1 % 6 ? " " : "\n", p->fp);
    }
    (void) fputs("]\n", p->fp);

    pdf_end_dict(p);
    pdf_end_obj(p);

    pdf_begin_obj(p, p->res_id);			/* Resource object */
    pdf_begin_dict(p);

    (void) fputs("/ProcSet [/PDF", p->fp);		/* ProcSet resources */
    if ( p->res.procset & ImageB)
	(void) fputs(" /ImageB", p->fp);
    if ( p->res.procset & ImageC)
	(void) fputs(" /ImageC", p->fp);
    if ( p->res.procset & ImageI)
	(void) fputs(" /ImageI", p->fp);
    if ( p->res.procset & Text)
	(void) fputs(" /Text", p->fp);
    (void) fputs("]\n", p->fp);

    if (p->res.font != (pdf_item *) NULL)
    {
	(void) fputs("/Font ", p->fp);			/* Font resources */

	pdf_begin_dict(p);
	    for(i = p->res.font, index = 0; i != (pdf_item *) NULL; i = i->next)
	    {
		(void) fprintf(p->fp,"/F%d %ld 0 R\n", index++, i->obj_id);
	    }
	pdf_end_dict(p);
    }

    if (p->res.xobject != (pdf_item *) NULL)
    {
	(void) fputs("/XObject ", p->fp);		/* XObject resources */

	pdf_begin_dict(p);
	    for(i = p->res.xobject, index = 0; i != (pdf_item *) NULL; i = i->next)
	    {
		(void) fprintf(p->fp,"/I%d %ld 0 R\n", index++, i->obj_id);
	    }
	pdf_end_dict(p);
    }

    pdf_end_dict(p);
    pdf_end_obj(p);
}

/* set page display duration for current and future pages */
void
PDF_set_duration(PDF *p, float t)
{
    p->duration = t;
}

/* set transition mode for current and future page */
void
PDF_set_transition(PDF *p, PDF_transition t)
{
    if (t >= transition_count) {
	pdf_error(p, PDF_WARN, "Illegal page transition value");
	return;
    }
    p->transition = t;
}

id
pdf_begin_obj(PDF *p, id obj_id)
{
    if (obj_id == NEW_ID)
	obj_id = pdf_alloc_id(p);

    p->file_offset[obj_id] = ftell(p->fp); 
    (void) fprintf(p->fp, "%ld 0 obj\n", obj_id);
    return obj_id;
}

id
pdf_alloc_id(PDF *p)
{
    p->currentobj++;
    if (p->currentobj >= MAX_ID)
	pdf_error(p, PDF_FATAL, "Too many objects (%ld)!", p->currentobj);

    /* only needed for verifying obj table in PDF_close() */
    p->file_offset[p->currentobj] = BAD_ID;

    return p->currentobj;
}

pdf_item *
pdf_add_res_font(PDF *p, char *basename)
{
    pdf_item *item, **last_item;

    last_item = &p->res.font;

    for (item = p->res.font; item != (pdf_item *) NULL; item = item->next)
    {
	if (!strcmp(item->basename, basename))
	    return item;			/* resource already listed */
	last_item = &item->next;
    }

    *last_item	= (pdf_item *) PDF_malloc(sizeof(pdf_item), "pdf_add_res_font");
    item	= *last_item;
    item->name	= (char *) PDF_malloc(6, "pdf_add_res_font");

    sprintf(item->name, "/F%d", p->font_number);
    p->font_number++;

    item->basename= (char *) PDF_malloc(strlen(basename)+1, "pdf_add_res_font");
    strcpy(item->basename, basename);
    item->obj_id	= pdf_alloc_id(p);
    item->next		= (pdf_item *) NULL;

    *last_item		= item;

    return item;
}

void
pdf_add_res_xobject(PDF *p, id xobj_id)
{
    pdf_item *item, **last_item;

    last_item = &p->res.xobject;

    for (item = p->res.xobject; item != (pdf_item *) NULL; item = item->next)
	last_item = &item->next;

    *last_item	= (pdf_item *) PDF_malloc(sizeof(pdf_item), "pdf_add_res_xobject");
    item	= *last_item;
    item->name	= (char *) PDF_malloc(6, "pdf_add_res_xobject");

    sprintf(item->name, "/I%d", p->xobject_number);
    p->xobject_number++;

    item->obj_id	= xobj_id;
    item->next		= (pdf_item *) NULL;

    *last_item		= item;
}
