/* 
  ==========================================================================
  Collection of general-purpose mupdf utilities
  ---
  NOTE: all these functions may raise exceptions and therefore should be called
        from a fz_try/fz_catch context  (even indirectly)
        
  Oct 2020 - A.Buratti fecit
  ==========================================================================
*/

#include <string.h>
#include "xpdf_extension.h"

/* 
** ==========================================================================
**  simple geometry utilities
** ==========================================================================
*/

fz_point  xfz_rect_center(fz_rect rect) {
	fz_point P;
	P.x = (rect.x0+rect.x1)/2.0;
	P.y = (rect.y0+rect.y1)/2.0;
	return P;
}

/* 
** ==========================================================================
**  xpdf image utilities
** ==========================================================================
*/

 // return FZ_IMAGE_JPEG or ...other ...  FZ_IMAGE_UNKNOWN
 // *this is safe from fz-exceptions*                                               
int xfz_image_type( fz_context* ctx, fz_image* image ) {
	int type = FZ_IMAGE_UNKNOWN;
	fz_var(type);
	fz_try(ctx) {
		fz_compressed_buffer *cBuff = fz_compressed_image_buffer(ctx,image); 
		type = (cBuff == NULL) ? FZ_IMAGE_UNKNOWN : cBuff->params.type;
		
		 // Note that some JPEG images are CMKY ; if you save them as FZ_IMAGE_JPEG
		 // they will be 'inverted' or worst. Save them as png !
		 // Note: use fz_colorspace_is_subtractive()  for testing if colorspace (OR altcolorspace) is subtractive (i.e. CMYK) 
		if (image->use_colorkey  ||  image->use_decode || image->mask || fz_colorspace_is_subtractive(ctx , image->colorspace))
			type = FZ_IMAGE_PNG;
	} fz_catch(ctx) {
		; // nothing to do !
	}
	return type;
}


void xfz_image_savejpeg(fz_context *ctx, fz_image* image, const char *filename) {
	fz_output *out = NULL;
	
	fz_var(out);
	fz_try(ctx) {
		fz_compressed_buffer *cBuff = fz_compressed_image_buffer(ctx,image);  
		unsigned char *data;
		size_t len = fz_buffer_storage(ctx, cBuff->buffer, &data);
		
		out = fz_new_output_with_path(ctx, filename, 0);
		fz_write_data(ctx, out, data, len);
	} fz_always(ctx) {
		fz_drop_output(ctx, out);
	} fz_catch(ctx) {
		fz_rethrow(ctx);
	}
}

void xfz_image_savepng(fz_context *ctx, fz_image *image, int useAlpha, const char *filename) {
	fz_pixmap *pix = NULL;
	fz_device *pDev = NULL;
	
	fz_var(pix);
	fz_var(pDev);
	fz_try(ctx) {
		fz_irect iclip;
		iclip.x0 = 0;
		iclip.y0 = 0;
		iclip.x1 = image->w;
		iclip.y1 = image->h;
		
		  // ?? should I use a different gs_device (gray, cmky ...) based on bpc or colorspace ?
		 //  .. anyway fz_device_rgb works with all kind of images so far tested ..
		pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), iclip, NULL, useAlpha );
		  // pure transparency
		fz_clear_pixmap(ctx,pix);    // set everything to 0, including alpha (if set)
		
		pDev = fz_new_draw_device(ctx, fz_identity, pix);
		if (image->mask) {
			fz_rect  bbox;
			bbox.x0 = iclip.x0;
			bbox.y0 = iclip.y0;
			bbox.x1 = iclip.x1;
			bbox.y1 = iclip.y1;
			fz_matrix mtx = fz_pre_scale( fz_identity, image->mask->w, image->mask->h );
			fz_clip_image_mask( ctx, pDev, image->mask, mtx, bbox);
		}
		
		fz_matrix mtx = fz_pre_scale(fz_identity,image->w,image->h);
		if ( image->colorspace ) {
			fz_fill_image(ctx, pDev, image, mtx, 1, fz_default_color_params);
		} else {
			fz_clear_pixmap_with_value(ctx, pix, 255);   // set every pixel to 255 (i.e white, plus alpha (if exists) to 255, 
			float color[4];
			color[0] = 0.0f;
			color[1] = 0.0f;
			color[2] = 0.0f;
			color[3] = 0.0f;
			 // by setting alpha 0 , all is rendered transparent
			fz_fill_image_mask(ctx, pDev, image, mtx, fz_device_gray(ctx), color, 1, fz_default_color_params);
		}
		if (image->mask) {
			fz_pop_clip(ctx, pDev);
		}
		fz_save_pixmap_as_png(ctx, pix, filename);
		
	} fz_always(ctx) {
		if(pDev) {
			fz_close_device(ctx,pDev);
			fz_drop_device(ctx,pDev);
		}
		if(pix) fz_drop_pixmap(ctx,pix);
	} fz_catch(ctx) {
		fz_rethrow(ctx);
	}
}


 // on error, returns -1  no ....
  // may raise an exception
int xpdf_number_of_images(fz_context* ctx, pdf_page* page) {
	pdf_obj * resourceObj = NULL;
	int count = 0;
	
	resourceObj = pdf_page_resources(ctx,page); 
	pdf_mark_obj(ctx, resourceObj); 
	pdf_obj * xObj = pdf_dict_get(ctx, resourceObj, PDF_NAME(XObject) ); 
	if( xObj ) {
		int n = pdf_dict_len(ctx,xObj);  // number of elems in xObj ...
		for( int i=0 ; i<n ; ++i ) { 
			pdf_obj* imDict = pdf_dict_get_val(ctx,xObj,i); 
			
			if ( ! pdf_is_dict(ctx, imDict) ) 
				continue;  // !! WTF !!
			
			 // skip if imDict is not an Image
			if ( ! pdf_name_eq(ctx, pdf_dict_get(ctx, imDict, PDF_NAME(Subtype)), PDF_NAME(Image)) )
				continue;
			
			++count;
		}
	}
	return count;
}



/* 
** ==========================================================================
**  xpdf xobject utilities
** ==========================================================================
*/

/*
** create a new XObject in pdf_document, embedding the Xobject xobj1
*/
pdf_obj* const xpdf_xobject_new(fz_context *ctx, pdf_document *doc, 
			pdf_obj *xobj1, fz_matrix mtx, fz_rect bbox )
{
	pdf_obj *xobj2;
	
		//-------------------------------------------------------------
		// create referencing XObject (controls actual display)
		//-------------------------------------------------------------
		// fill reference to xobj1 into its /Resources
		//-------------------------------------------------------------
	pdf_obj *subres  = pdf_new_dict(ctx, doc, 5);
	pdf_obj *subres1 = pdf_new_dict(ctx, doc, 5);
	
	 // embeddedname is not required to be unique,since it's local ..
	const char *resourceName = "Z";
	fz_buffer * resourceBuff = fz_new_buffer(ctx, 6);  // !! WARNING harcoded string length !!
	fz_append_string(ctx, resourceBuff, "/Z Do");
	
	pdf_dict_puts(ctx, subres1, resourceName, xobj1);
	pdf_dict_put_drop(ctx, subres, PDF_NAME(XObject), subres1);
	
	xobj2 = pdf_new_xobject(ctx, doc, bbox, mtx, subres, resourceBuff);
	fz_drop_buffer(ctx, resourceBuff);	        
	
	pdf_drop_obj(ctx, subres);
	return xobj2;
}

//-----------------------------------------------------------------------------
// Insert a buffer as a new separate /Contents object of a page.
//  Based on showPDFpage() from PyMuPDF .
//
// Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com
// License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html
//
// Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a
// lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is
// maintained and developed by Artifex Software, Inc. https://artifex.com.
//-----------------------------------------------------------------------------
int JM_insert_contents(fz_context *ctx, pdf_document *pdf,
                        pdf_obj *pageref, fz_buffer *newcont, int overlay)
{
    int xref = 0;
    pdf_obj *newconts = NULL;
    pdf_obj *carr = NULL;
    fz_var(newconts);
    fz_var(carr);
    fz_try(ctx) {
        pdf_obj *contents = pdf_dict_get(ctx, pageref, PDF_NAME(Contents));
        newconts = pdf_add_stream(ctx, pdf, newcont, NULL, 0);
        xref = pdf_to_num(ctx, newconts);
        if (pdf_is_array(ctx, contents)) {
            if (overlay) // append new object
                pdf_array_push(ctx, contents, newconts);
            else // prepend new object
                pdf_array_insert(ctx, contents, newconts, 0);
        } else {
            carr = pdf_new_array(ctx, pdf, 5);
            if (overlay) {
                if (contents)
                    pdf_array_push(ctx, carr, contents);
                pdf_array_push(ctx, carr, newconts);
            } else {
                pdf_array_push(ctx, carr, newconts);
                if (contents)
                    pdf_array_push(ctx, carr, contents);
            }
            pdf_dict_put(ctx, pageref, PDF_NAME(Contents), carr);
        }
    }
    fz_always(ctx) {
        pdf_drop_obj(ctx, newconts);
        pdf_drop_obj(ctx, carr);
    }
    fz_catch(ctx) {
        fz_rethrow(ctx);
    }
    return xref;
}

  // before adding new contents, protect a page againts malformed contents
void xpdf_page_wrapContents( fz_context* ctx, pdf_page* page ) {
	fz_buffer* qBuffer = fz_new_buffer_from_shared_data(ctx, (const unsigned char*)"q\n", 3);
	JM_insert_contents(ctx, page->doc, page->obj, qBuffer, 0);
	fz_drop_buffer(ctx,qBuffer);

	fz_buffer* QBuffer = fz_new_buffer_from_shared_data(ctx, (const unsigned char*)"\nQ", 3);
	JM_insert_contents(ctx, page->doc, page->obj, QBuffer, 1);
	fz_drop_buffer(ctx,QBuffer);
}


	// NOTE: bbox coords are in (PDF) form coord sys.
void xpdf_xobject_embed(fz_context *ctx, pdf_document *doc, pdf_page *toPage,
			pdf_obj *xobj1, fz_matrix mtx, fz_rect bbox, int overlay )
{
	pdf_obj *xobj2;
	
	//-------------------------------------------------------------
	// create referencing XObject (controls actual display)
	//-------------------------------------------------------------
	xobj2 = xpdf_xobject_new(ctx, doc, xobj1, mtx, bbox );
	
	//-------------------------------------------------------------
	// update target page:
	//-------------------------------------------------------------
	// 1. resources object
	//-------------------------------------------------------------
	char embeddedName[20] ;
	sprintf(embeddedName,"xmupdf-%d", pdf_to_num(ctx, xobj2));
	
	{
		pdf_obj *resources = pdf_dict_get(ctx, toPage->obj, PDF_NAME(Resources));
		pdf_obj *xobjs = pdf_dict_get(ctx, resources, PDF_NAME(XObject));
		if (!xobjs) {
			// has no XObject dict yet: create one
			xobjs = pdf_new_dict(ctx, doc, 10);
			pdf_dict_put(ctx, resources, PDF_NAME(XObject), xobjs);
		}
		
		pdf_dict_puts(ctx, xobjs, embeddedName, xobj2);
	}
	
	// 2. contents object
	//-------------------------------------------------------------
	{
		fz_buffer *nres = fz_new_buffer(ctx, 50);       // buffer for Do-command
		fz_try(ctx) {
			fz_append_string(ctx, nres, " q /");    // Do-command
			fz_append_string(ctx, nres, embeddedName);
			fz_append_string(ctx, nres, " Do Q ");
			
			JM_insert_contents(ctx, doc, toPage->obj, nres, overlay);
		} fz_always(ctx) {
			fz_drop_buffer(ctx, nres);
		} fz_catch(ctx) {
			fz_rethrow(ctx);
		}
	}
}


/* 
** ==========================================================================
**  xpdf annot utilities
** ==========================================================================
*/

void xpdf_annot_flatten(fz_context* ctx, pdf_annot* annot) {
	if ( ! annot ) return;
	
	pdf_page* page = pdf_annot_page(ctx,annot);
	pdf_document* doc = page->doc;
	
	fz_matrix mtx = pdf_annot_transform(ctx,annot);
	pdf_obj* ap = pdf_annot_ap(ctx,annot);
	fz_rect bbox = pdf_xobject_bbox(ctx,ap);
	
	// bbox must be tranformed with the annot_ap mtx
	fz_matrix apmtx = pdf_xobject_matrix(ctx,ap);
	bbox = fz_transform_rect(bbox, apmtx);
	
	xpdf_xobject_embed(ctx,doc,page,ap,mtx,bbox,1);
	pdf_delete_annot(ctx, page, annot);
}



/* 
** ==========================================================================
**  xpdf field/fieldObj utilities
** ==========================================================================
*/
const char *xpdf_fieldObj_typename(fz_context *ctx, pdf_obj *field) {
	const char *name;
	switch (pdf_field_type(ctx,field)) {
	 case PDF_WIDGET_TYPE_BUTTON: name = "button"; break;
	 case PDF_WIDGET_TYPE_RADIOBUTTON: name = "radiobutton"; break;
	 case PDF_WIDGET_TYPE_CHECKBOX: name = "checkbox"; break;
	 case PDF_WIDGET_TYPE_TEXT: name = "text" ; break;
	 case PDF_WIDGET_TYPE_COMBOBOX: name = "combobox"; break;
	 case PDF_WIDGET_TYPE_LISTBOX: name = "listbox"; break;
	 case PDF_WIDGET_TYPE_SIGNATURE: name = "signature"; break;
	 default: name = "unknown" ; break;
	}
	return name;
}


 // warning: acroform may contain fields with the same (full) name !!
 // This is a weird but legal alternative for dealing with fields having more apperances.
 // ( the good-way of denoting such fields is: one field with with kids (appearances) ).
 // In these cases, ** this function return just the first matching field **.
pdf_obj * xpdf_fieldObj_get( fz_context *ctx, pdf_document *doc, const char *fullname ) {
	pdf_obj *acroformFields = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(Fields), NULL);
	return pdf_lookup_field(ctx, acroformFields, fullname);
}

static
int xpdf_fieldObj_delete_imp( fz_context *ctx, pdf_obj *arrObj, pdf_obj *fieldObj ) {
	if ( !arrObj ) return 0;
	int i, len;
	
	len = pdf_array_len(ctx, arrObj);
	for (i = 0; i < len; i++) {
		pdf_obj *iObj;
		iObj = pdf_array_get(ctx,arrObj,i);
		if (!pdf_objcmp(ctx, iObj, fieldObj)) {
			pdf_array_delete(ctx, arrObj, i);
			return 1;
		} else {
			pdf_obj *kids = pdf_dict_get(ctx,iObj,PDF_NAME(Kids));
			if ( xpdf_fieldObj_delete_imp( ctx, kids, fieldObj) ) {
				if ( pdf_array_len(ctx,kids) == 0 ) {
					pdf_array_delete( ctx, arrObj, i);
				}
				return 1;
			}
		}
	}
	return 0;
}

	// the pdf_obj *fieldObj is the terminal part of a (maybe composed) field.
	// This function removes the terminal part, and if its parent field
	// remains with no kids, then recursively remove the parent field.

    // WARNING: before deleting a field, you  must delete all its related widgets.
void xpdf_fieldObj_delete(fz_context* ctx, pdf_document* doc, pdf_obj* fieldObj) {
	pdf_obj *acroformFields;
	
	if (fieldObj == NULL)	return;
	/* Remove the field from the "Fields" array. */
	acroformFields = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(Fields), NULL);
	
	xpdf_fieldObj_delete_imp( ctx, acroformFields, fieldObj );
	
	/* The garbage collection pass when saving will remove the annot object,
	 * removing it here may break files if multiple pages use the same annot. */
	
	// to be conformant with PDF specs, if you removed all fields from /Root/Acroform/Fields,
	// then you should also remove /Root/Acroform
	if( pdf_array_len(ctx,acroformFields) == 0 ) {			
		pdf_dict_del(ctx, 
			pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root), NULL),
			PDF_NAME(AcroForm)
		);
	}
}

enum
{
	SigFlag_SignaturesExist = 1,
	SigFlag_AppendOnly = 2
};

 //  a new field and its widgets are the same  obj (pdf_annot)
pdf_annot* xpdf_addsigfield(fz_context* ctx, pdf_page* page, const char* fieldname, fz_rect rect) {
	pdf_annot* annot = NULL;
	pdf_document* doc = page->doc;
	
	int old_sigflags = pdf_to_int(ctx, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm/SigFlags"));
	fz_var(annot);
	fz_try(ctx) {        
		 // TODO :  this check should be done by the caller
		if ( xpdf_fieldObj_get( ctx, doc, fieldname ) != NULL ) {
			fz_throw(ctx, FZ_ERROR_GENERIC, "Duplicated field name");
			//  ... jump to fz_catch section ...
		}
		
		annot = pdf_create_annot(ctx,page,PDF_ANNOT_WIDGET);
		pdf_set_annot_rect(ctx, annot, rect);
		
		// extend the annot so that it becomes a field, too !
		pdf_obj* annot_obj = pdf_annot_obj(ctx,annot);
		pdf_dict_put_drop(ctx, annot_obj, PDF_NAME(FT), PDF_NAME(Sig));    
		pdf_dict_put_text_string(ctx, annot_obj, PDF_NAME(T), fieldname);
		
		// just for signatures
		{
			int sigflags = (old_sigflags | (SigFlag_SignaturesExist|SigFlag_AppendOnly));
			pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), pdf_new_int(ctx, sigflags), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(SigFlags), NULL);
		}
		
		pdf_obj *form = NULL;   
		form = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm/Fields");
		if (!form) {
			form = pdf_new_array(ctx, doc, 1);
			pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), form, PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(Fields), NULL);
		}
		pdf_array_push(ctx, form, annot_obj);
		
		pdf_update_page(ctx,page);
		
	} fz_catch(ctx) {
		if (annot) pdf_delete_annot(ctx, page, annot);
		
		/* An empty Fields array may have been created, but that is harmless */
		
		// ?? what does it mean ??? 
		pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), pdf_new_int(ctx, old_sigflags), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(SigFlags), NULL);
		fz_rethrow(ctx);
	}
	return annot;
}


/*
** ==========================================================================
**  xpdf  widget utilities 
** ==========================================================================
*/

pdf_annot* xpdf_widgetObj_to_widget(fz_context* ctx, pdf_page* page, pdf_obj* widgetObj) {
	pdf_annot *widget;
	for ( widget = pdf_first_widget(ctx,page); widget != NULL;widget = pdf_next_widget(ctx,widget) ) {
		if (pdf_objcmp(ctx, widgetObj, pdf_annot_obj(ctx,widget))==0) {
			return widget;
		}
	}
	return NULL;
}

/*
** ==========================================================================
**  xpdf  page  utilities 
** ==========================================================================
*/

	/* 
	---------------------------------------------------------------
	Get the visibile area of the page, in user space coord sys.
	The page area is taken to be the crop box if it
	exists , otherwise the media box will be used.
	---------------------------------------------------------------
	*/
fz_rect xpdf_page_cropbox( fz_context *ctx, pdf_page *page ) {
	pdf_obj *box;
	box = pdf_dict_get_inheritable(ctx, page->obj, PDF_NAME(CropBox));
	if (!box) {
		box = pdf_dict_get_inheritable(ctx, page->obj, PDF_NAME(MediaBox));
	// MediaBox is mandatory
		if (! box) {
			fz_throw(ctx, FZ_ERROR_GENERIC,"page without an inheritable MediaBox");
		}
	}
	return pdf_to_rect(ctx, box);
}


/*
**  Based on showPDFpage() from PyMuPDF .
**  PuMuPDF is PyMuPDF is distributed under GNU GPL V3 (or later, at your choice).
**  © Copyright 2015-2018, Jorj X. McKie. 
*/


	// TODO
	// WARNING: this proc works even if fromPage comes from the same doc !
	//  it works but it's still under-optimized (i.e a deep-copy of the whole page is made! )
	//  I think that there will be space for optimizaztion


	// TODO ...  this function should be rewritten in a more generic way, 
	//   so that it could do a deep-copy of every xobject
	
//-----------------------------------------------------------------------------
// Insert a deep copy of a pdf_page (from another document) in a pdf_document's stream.
//  Return the pdf_obj* of the new XObject 
//-----------------------------------------------------------------------------

pdf_obj* const xpdf_page_new_deepcopy(fz_context *ctx, pdf_document *toPdf, pdf_page *fromPage, pdf_graft_map *gmap)
{
	pdf_obj *xobj1;
	
	// need to create new XObject
	// Deep-copy resources object of source page
	fz_buffer *stream = NULL;
	pdf_obj *resources = NULL;
	pdf_obj *o = pdf_page_resources(ctx, fromPage);
	if (gmap)        // use graftmap when possible
		resources = pdf_graft_mapped_object(ctx, gmap, o);
	else
		resources = pdf_graft_object(ctx, toPdf, o);
	
	// get fromPageObj contents source; combine when several objects
	{
		int i;
		pdf_obj *contents = pdf_page_contents(ctx, fromPage);
		if (pdf_is_array(ctx, contents)) {
			// more than one!
			stream = fz_new_buffer(ctx, 1024);
			for (i=0; i < pdf_array_len(ctx, contents); i++) {
				fz_buffer *stream1 = pdf_load_stream(ctx, pdf_array_get(ctx, contents, i));
				fz_append_buffer(ctx, stream, stream1);
				fz_drop_buffer(ctx, stream1);
			}
		} else {
			stream = pdf_load_stream(ctx, contents);
		}
	}
	//-------------------------------------------------------------
	// create XObject representing the source page
	//-------------------------------------------------------------
	xobj1 = pdf_new_xobject(ctx, toPdf, xpdf_page_cropbox(ctx,fromPage), fz_identity, NULL, stream);
	// store source page contents
	//JM_update_stream(ctx, toPdf, xobj1, res);
	pdf_update_stream(ctx, toPdf, xobj1, stream, 0);  // ABU: uncompresses
	
	fz_drop_buffer(ctx, stream);
	
	// store spage resources
	pdf_dict_put_drop(ctx, xobj1, PDF_NAME(Resources), resources);
	
	return xobj1;
}

/*
** ==========================================================================
**  Color conversion between tk-colors and pdf-rgb-colors 
** ==========================================================================
*/

#include "XColors.h"

  //  "red" ->  {1.0, 0.0, 0.0}
  //  return 0 (OK) if tkcolor is valid,
  //         1 (ERR) if tkcolor is not a valid color 
int TkColorToPDFColor( const char* tkcolor, float rgb[3] ) {
	uint32_t ARGB;
	ARGB = ParseTkColor(tkcolor);
	if ( ARGB == INVALID_COLOR ) return 1; // err
	
	for( int i=2 ; i>=0 ; --i ) {
		rgb[i] = (float)((ARGB & 0xFF) / 255.0) ;
		ARGB >>= 8;
	}
	return 0;
}

  // n is the dimensione of the color array:
  //   1 gray
  //   3 rgb
  //   4 cmyk
  //  result "#RRGGBB" is placed in the tkcolor string (must be allocated by the caller)
  // if n is not valid, then result is ""
void PDFColorToTkColor( int n, float *color, char tkcolor[8]) {
	float rgb[3];
	switch (n) {
	  case 1:
		// gray
		for (int i=0; i<3; ++i )  rgb[i]=color[0];
		break;
	  case 3:
		// rgb  
		for (int i=0; i<3; ++i )  rgb[i]=color[i];
		break;
	  case 4: {
		// CMYK
		
		float C,M,Y,K;
		C=color[0];
		M=color[1];
		Y=color[2];
		K=color[3];
		
		/* red */   rgb[0] = (1.0-C)*(1.0-K);
		/* green */ rgb[1] = (1.0-M)*(1.0-K);
		/* blue */  rgb[2] = (1.0-Y)*(1.0-K);
		}
		break;
	default:
		strcpy(tkcolor,"invalid");
		return;
	}
	
	sprintf(tkcolor, "#%02x%02x%02x" , (int)(rgb[0]*255.0), (int)(rgb[1]*255.0), (int)(rgb[2]*255.0));
	return;
}
