/*---------------------------------------------------------------------------*
 |              PDFlib - A library for generating PDF on the fly             |
 +---------------------------------------------------------------------------+
 | Copyright (c) 1997-2006 Thomas Merz and PDFlib GmbH. All rights reserved. |
 +---------------------------------------------------------------------------+
 |                                                                           |
 |    This software is subject to the PDFlib license. It is NOT in the       |
 |    public domain. Extended versions and commercial licenses are           |
 |    available, please check http://www.pdflib.com.                         |
 |                                                                           |
 *---------------------------------------------------------------------------*/

/* $Id: p_xgstate.c,v 1.71.2.2 2009/06/04 11:22:04 york Exp $
 *
 * Extended graphics state handling
 *
 */

#define P_XGSTATE_C

#include "p_intern.h"
#include "p_font.h"

static const pdc_keyconn pdf_blendmode_pdfkeylist[] =
{
    {"Normal",          BM_Normal},
    {"Multiply",        BM_Multiply},
    {"Screen",          BM_Screen},
    {"Overlay",         BM_Overlay},
    {"Darken",          BM_Darken},
    {"Lighten",         BM_Lighten},
    {"ColorDodge",      BM_ColorDodge},
    {"ColorBurn",       BM_ColorBurn},
    {"HardLight",       BM_HardLight},
    {"SoftLight",       BM_SoftLight},
    {"Difference",      BM_Difference},
    {"Exclusion",       BM_Exclusion},
    {"Hue",             BM_Hue},
    {"Saturation",      BM_Saturation},
    {"Color",           BM_Color},
    {"Luminosity",      BM_Luminosity},
    {NULL, 0}
};

/*  external graphic state  */
struct pdf_extgstateresource_s
{
    pdc_id      obj_id;                 /* object id of this resource */
    pdc_bool    used_on_current_page;   /* this resource used on current page */

    pdc_id      font_obj;               /* font to use */
    pdc_scalar  font_size;              /* at what size */

    pdc_scalar  line_width;
    int         line_cap;
    int         line_join;
    pdc_scalar  miter_limit;
    pdc_scalar* dash_array;
    int         dash_count;
    pdc_scalar  dash_phase;

    pdf_renderingintent       ri;
    pdc_bool    stroke_adjust;
    pdc_bool    overprint_stroke;
    pdc_bool    overprint_fill;
    int         overprint_mode;

    /*
        The following entries which take functions are not implemented
        since PDFlib has no concept of a function at this time.

        BG      - black generation
        BG2     - black generation
        UCR     - undercolor-removal
        UCR2    - undercolor-removal
        TR      - transfer
        TR2     - transfer
        HT      - halftone
    */

    pdc_scalar       flatness;
    pdc_scalar       smoothness;

    /* PDF 1.4 additions */
    pdf_blendmode blendmode;            /* blend mode */
    pdc_scalar  opacity_fill;           /* fill opacity level */
    pdc_scalar  opacity_stroke;         /* stroke opacity level */
    pdc_bool    alpha_is_shape;
    pdc_bool    text_knockout;
};

pdc_id
pdf_get_gstate_id(PDF *p, int gstate)
{
    /* TODO: is this required for ExtGStates used in Shadings? */
    p->extgstates[gstate].used_on_current_page = pdc_true;

    return (p->extgstates[gstate].obj_id);
}

/* Definitions of Explicit Graphics State options */
static const pdc_defopt pdf_create_gstate_options[] =
{
    {"alphaisshape", pdc_booleanlist, PDC_OPT_PDC_1_4, 1, 1, 0.0, 0.0, NULL},

    {"blendmode", pdc_keywordlist, PDC_OPT_BUILDOR | PDC_OPT_PDC_1_4, 1, 20,
      0.0, 0.0, pdf_blendmode_pdfkeylist},

/* These features do not work in Acrobat (5.0.1)
    {"dasharray", pdc_scalarlist, PDC_OPT_PDC_1_3, 1, 8,
      PDF_SMALLREAL, PDC_FLOAT_MAX, NULL},

    {"dashphase", pdc_scalarlist, PDC_OPT_PDC_1_3, 1, 1,
      0.0, PDC_FLOAT_MAX, NULL},

    {"fontsize", pdc_scalarlist, PDC_OPT_PDC_1_3, 1, 1,
      PDF_SMALLREAL, PDC_FLOAT_MAX, NULL},

    {"font", pdc_fonthandle, PDC_OPT_PDC_1_3 | PDC_OPT_REQUIRIF1, 1, 1,
      0, 0, NULL},
*/

    {"flatness", pdc_scalarlist, PDC_OPT_PDC_1_3, 1, 1,
      PDF_SMALLREAL, PDC_FLOAT_MAX, NULL},

    {"linecap", pdc_integerlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 2.0,
      pdf_linecap_keylist},

    {"linejoin", pdc_integerlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 2.0,
      pdf_linejoin_keylist},

    {"linewidth", pdc_scalarlist, PDC_OPT_PDC_1_3, 1, 1,
      PDF_SMALLREAL, PDC_FLOAT_MAX, NULL},

    {"miterlimit", pdc_scalarlist, PDC_OPT_PDC_1_3, 1, 1, 1.0, PDC_FLOAT_MAX,
      NULL},

    {"opacityfill", pdc_scalarlist, PDC_OPT_PDC_1_4 | PDC_OPT_PERCENT,
     1, 1, 0.0, 1.0, NULL},

    {"opacitystroke", pdc_scalarlist, PDC_OPT_PDC_1_4 | PDC_OPT_PERCENT,
     1, 1, 0.0, 1.0, NULL},

    {"overprintfill", pdc_booleanlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 0.0, NULL},

    {"overprintmode", pdc_integerlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 1.0, NULL},

    {"overprintstroke", pdc_booleanlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 0.0, NULL},

    {"renderingintent", pdc_keywordlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 0.0,
      pdf_renderingintent_pdfkeylist},

    {"smoothness", pdc_scalarlist, PDC_OPT_PDC_1_3 | PDC_OPT_PERCENT,
     1, 1, 0.0, 1.0, NULL},

    {"strokeadjust", pdc_booleanlist, PDC_OPT_PDC_1_3, 1, 1, 0.0, 0.0, NULL},

    {"textknockout", pdc_booleanlist, PDC_OPT_PDC_1_4, 1, 1, 0.0, 0.0, NULL},

    PDC_OPT_TERMINATE
};

static void
pdf_init_extgstateresource(pdf_extgstateresource *egsr)
{
    egsr->used_on_current_page	= pdc_false;

    /* we need to tell which parms have been set and which haven't,
    ** so we initialize to invalid values. even boolean parms are
    ** declared as integers, so we can set them to -1 here.
    */
    egsr->font_obj		= PDC_NEW_ID;
    egsr->font_size             = pdc_undef;

    egsr->line_width            = pdc_undef;
    egsr->line_cap              = pdc_undef;
    egsr->line_join             = pdc_undef;
    egsr->miter_limit           = pdc_undef;

    egsr->dash_array		= NULL;
    egsr->dash_count		= 0;
    egsr->dash_phase		= 0.0;

    egsr->ri                    = AutoIntent;
    egsr->stroke_adjust         = pdc_undef;
    egsr->overprint_stroke      = pdc_undef;
    egsr->overprint_fill        = pdc_undef;
    egsr->overprint_mode        = pdc_undef;
    egsr->flatness              = pdc_undef;
    egsr->smoothness            = pdc_undef;

    egsr->blendmode		= BM_None;
    egsr->opacity_stroke        = pdc_undef;
    egsr->opacity_fill          = pdc_undef;
    egsr->alpha_is_shape        = pdc_undef;
    egsr->text_knockout         = pdc_undef;
}

static void
pdf_grow_extgstates(PDF *p)
{
    int i;

    p->extgstates = (pdf_extgstateresource *) pdc_realloc(p->pdc, p->extgstates,
	sizeof(pdf_extgstateresource) * 2 * p->extgstates_capacity,
	"pdf_grow_extgstates");

    for (i = p->extgstates_capacity; i < 2 * p->extgstates_capacity; i++) {
	pdf_init_extgstateresource( &p->extgstates[i] );
    }

    p->extgstates_capacity *= 2;
}

void
pdf_init_extgstates(PDF *p)
{
    static const char fn[] = "pdf_init_extgstates";
    int i;

    p->extgstates_number = 0;
    p->extgstates_capacity = EXTGSTATE_CHUNKSIZE;

    p->extgstates = (pdf_extgstateresource *)
    pdc_malloc(p->pdc,
               sizeof(pdf_extgstateresource) * p->extgstates_capacity, fn);

    for (i = 0; i < p->extgstates_capacity; i++) {
	pdf_init_extgstateresource( &p->extgstates[i] );
    }
}

void
pdf_write_page_extgstates(PDF *p)
{
    int i, total = 0;

    for (i = 0; i < p->extgstates_number; i++)
	if (p->extgstates[i].used_on_current_page)
	    total++;

    if (total > 0)
    {
	pdc_puts(p->out, "/ExtGState");
	pdc_begin_dict(p->out);
    }

    if (total > 0)
    {
	for (i = 0; i < p->extgstates_number; i++)
	{
	    if (p->extgstates[i].used_on_current_page)
	    {
		p->extgstates[i].used_on_current_page = pdc_false; /* reset */
		pdc_printf(p->out, "/GS%d", i);
		pdc_objref(p->out, "", p->extgstates[i].obj_id);
	    }
	}

	    pdc_end_dict(p->out);
    }
}

void
pdf_get_page_extgstates(PDF *p, pdf_reslist *rl)
{
    int i;

    for (i = 0; i < p->extgstates_number; i++) {
	if (p->extgstates[i].used_on_current_page) {
	    p->extgstates[i].used_on_current_page = pdc_false; /* reset */
	    pdf_add_reslist(p, rl, i);
	}
    }
}

void
pdf_mark_page_extgstate(PDF *p, int n)
{
    p->extgstates[n].used_on_current_page = pdc_true;
}

void
pdf_write_doc_extgstates(PDF *p)
{
    int i, j;

    pdf_extgstateresource *gs;

    for (i = 0; i < p->extgstates_number; i++)
    {
	gs = &p->extgstates[i];

	pdc_begin_obj(p->out, gs->obj_id);		/* ExtGState resource */
	pdc_begin_dict(p->out);

	pdc_puts(p->out, "/Type/ExtGState\n");

	if (gs->font_obj != PDC_NEW_ID)
	{
	    pdc_puts(p->out, "/Font");
	    pdc_begin_array(p->out);
	    pdc_objref(p->out, "", gs->font_obj);
	    pdc_printf(p->out, "%f", gs->font_size);
	    pdc_end_array(p->out);
	}

        if (gs->line_width != pdc_undef)
	    pdc_printf(p->out, "/LW %f\n", gs->line_width);

        if (gs->line_cap != pdc_undef)
	    pdc_printf(p->out, "/LC %d\n", gs->line_cap);

        if (gs->line_join != pdc_undef)
	    pdc_printf(p->out, "/LJ %d\n", gs->line_join);

        if (gs->miter_limit != pdc_undef)
	    pdc_printf(p->out, "/ML %f\n", gs->miter_limit);

	if (gs->dash_count > 0)
	{
	    pdc_printf(p->out, "/D");
	    pdc_begin_array(p->out);
	    pdc_begin_array(p->out);

	    for (j = 0; j < gs->dash_count; ++j)
		pdc_printf(p->out, "%f ", gs->dash_array[j]);

	    pdc_end_array_c(p->out);
            pdc_printf(p->out, "%f", gs->dash_phase);
	    pdc_end_array(p->out);
            /* but see page 157 of PDF Reference: integer */
	}

	if (gs->ri != AutoIntent)
            pdc_printf(p->out, "/RI/%s\n",
                pdc_get_keyword((long) gs->ri, pdf_renderingintent_pdfkeylist));

        if (gs->stroke_adjust != pdc_undef)
	    pdc_printf(p->out, "/SA %s\n", PDC_BOOLSTR(gs->stroke_adjust));

        if (gs->overprint_stroke != pdc_undef)
            pdc_printf(p->out, "/OP %s\n", PDC_BOOLSTR(gs->overprint_stroke));

        if (gs->overprint_fill != pdc_undef)
            pdc_printf(p->out, "/op %s\n", PDC_BOOLSTR(gs->overprint_fill));
        else if (gs->overprint_stroke == pdc_true)
            pdc_puts(p->out, "/op false\n");

        if (gs->overprint_mode != pdc_undef)
	    pdc_printf(p->out, "/OPM %d\n", gs->overprint_mode);

        if (gs->flatness != pdc_undef)
	    pdc_printf(p->out, "/FL %f\n", gs->flatness);

        if (gs->smoothness != pdc_undef)
	    pdc_printf(p->out, "/SM %f\n", gs->smoothness);

        if (gs->opacity_fill != pdc_undef)
	    pdc_printf(p->out, "/ca %f\n", gs->opacity_fill);

	if (gs->blendmode != BM_None) {
            const char *modename;
	    int modecount=0;

	    for (j = 0; ; j++) {
                if (!pdf_blendmode_pdfkeylist[j].word)
		    break;
                if (gs->blendmode & pdf_blendmode_pdfkeylist[j].code)
                    modecount++;
            }

	    pdc_printf(p->out, "/BM");

	    /*
	     * ACROBUG: Acrobat 7 doesn't like Blend mode arrays with a
	     * singly entry under some circumstances (many entries? images
	     * involved?) so we avoid the array if we have only one entry.
	     */
	    if (modecount > 1)
		pdc_begin_array(p->out);

	    for (j = 0; ; j++) {
                modename = pdf_blendmode_pdfkeylist[j].word;
                if (!modename) break;
                if (gs->blendmode & pdf_blendmode_pdfkeylist[j].code)
                    pdc_printf(p->out, "/%s", modename);
            }

	    if (modecount > 1)
		pdc_end_array(p->out);
	}

        if (gs->opacity_stroke != pdc_undef)
	    pdc_printf(p->out, "/CA %f\n", gs->opacity_stroke);

        if (gs->alpha_is_shape != pdc_undef)
	    pdc_printf(p->out, "/AIS %s\n", PDC_BOOLSTR(gs->alpha_is_shape));

        if (gs->text_knockout != pdc_undef)
	    pdc_printf(p->out, "/TK %s\n", PDC_BOOLSTR(gs->text_knockout));

	pdc_end_dict(p->out);
	pdc_end_obj(p->out);			/* ExtGState resource */
    }
}

void
pdf_cleanup_extgstates(PDF *p)
{
    int i;

    if (!p->extgstates)
	return;

    for (i = 0; i < p->extgstates_number; i++) {
        if (p->extgstates[i].dash_array)
            pdc_free(p->pdc, p->extgstates[i].dash_array);
    }

    pdc_free(p->pdc, p->extgstates);
    p->extgstates = NULL;
}

int
pdf__create_gstate(PDF *p, const char *optlist)
{
    pdf_extgstateresource *gs;
    int slot = -1;
    int font = pdc_undef;
    int inum;
    pdc_clientdata data;
    pdc_resopt *results;

    if (optlist == NULL || !*optlist)
        pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "optlist", 0, 0, 0);

    slot = p->extgstates_number;
    if (slot == p->extgstates_capacity)
        pdf_grow_extgstates(p);

    p->extgstates_number++;
    gs = &p->extgstates[slot];
    gs->obj_id = pdc_alloc_id(p->out);

    /* parsing optlist */
    pdf_set_clientdata(p, &data);
    results = pdc_parse_optionlist(p->pdc, optlist, pdf_create_gstate_options,
                                   &data, pdc_true);

    pdc_get_optvalues("alphaisshape", results, &gs->alpha_is_shape, NULL);

    if (pdc_get_optvalues("blendmode", results, &inum, NULL))
        gs->blendmode = (pdf_blendmode) inum;

    gs->dash_count = pdc_get_optvalues("dasharray", results, NULL, NULL);
    gs->dash_array = (pdc_scalar *) pdc_save_lastopt(results, PDC_OPT_SAVEALL);

    pdc_get_optvalues("dashphase", results, &gs->dash_phase, NULL);

    pdc_get_optvalues("flatness", results, &gs->flatness, NULL);

    pdc_get_optvalues("font", results, &font, NULL);
    if (font != pdc_undef)
        gs->font_obj = p->fonts[font].obj_id;

    pdc_get_optvalues("fontsize", results, &gs->font_size, NULL);

    pdc_get_optvalues("linecap", results, &gs->line_cap, NULL);

    pdc_get_optvalues("linejoin", results, &gs->line_join, NULL);

    pdc_get_optvalues("linewidth", results, &gs->line_width, NULL);

    pdc_get_optvalues("miterlimit", results, &gs->miter_limit, NULL);

    pdc_get_optvalues("opacityfill", results, &gs->opacity_fill, NULL);

    pdc_get_optvalues("opacitystroke", results, &gs->opacity_stroke, NULL);

    pdc_get_optvalues("overprintfill", results, &gs->overprint_fill, NULL);

    pdc_get_optvalues("overprintmode", results, &gs->overprint_mode, NULL);

    pdc_get_optvalues("overprintstroke", results, &gs->overprint_stroke, NULL);

    if (pdc_get_optvalues("renderingintent", results, &inum, NULL))
        gs->ri = (pdf_renderingintent) inum;

    pdc_get_optvalues("smoothness", results, &gs->smoothness, NULL);

    pdc_get_optvalues("strokeadjust", results, &gs->stroke_adjust, NULL);

    pdc_get_optvalues("textknockout", results, &gs->text_knockout, NULL);

    pdc_cleanup_optionlist(p->pdc, results);



    return slot;
}

void
pdf__set_gstate(PDF *p, int gstate)
{
    pdf_extgstateresource *gs;

    pdf_check_handle(p, gstate, pdc_gstatehandle);

    pdc_printf(p->out, "/GS%d gs\n", gstate);
    p->extgstates[gstate].used_on_current_page = pdc_true;

    gs = &p->extgstates[gstate];
    if (gs->opacity_fill != pdc_undef || gs->opacity_stroke != pdc_undef)
        pdf_set_autotgroup(p, pdc_true);
}
