/*
** MUPage.c
**
** TclOO class  Page_C  implementation.
**
** This is a basic class wrapping the pdf_page* entity.
**
** Oct2020 - ABU fecit
*/

/* See https://en.cppreference.com/w/c/experimental/dynamic/strdup */
#define __STDC_WANT_LIB_EXT2__ 1

#include "asprintf.h"
#include <string.h>
#include "tcloo_util.h"

#include <mupdf/pdf.h>
#include "xpdf_extension.h"

#include "MUDoc.h"
#include "MUPage_Images.h"
#include "MUPage_Annots.h"
#include "MUPage.h"


 /* 
 ** ==========================================================================
 ** Instance data
 **   basic new/destroy
 ** ==========================================================================
 */

typedef
	struct MUPage_s {
		fz_context *ctx;    // it's a ref , not owned
		pdf_page *page;
		int pageNum;
		CollectionOf_xGraftedPages *docGrafts;   // it's a ref, not owned
		fz_display_list *dispList;
		fz_stext_page *stext;   
	} MUPage;


static
MUPage *MU_new(void) {
	MUPage *self = (MUPage *)calloc(1,sizeof(MUPage));
	if( self == NULL ) {
		return NULL;
	}
	self->ctx = NULL;
	self->page = NULL;
	self->pageNum = -1;
	self->docGrafts = NULL;
	self->dispList  = NULL;
	self->stext = NULL;
	return self;
}


  // reset self->dispList (and hence the dependant self->text)
static
void MU_ResetCache( MUPage *self ) {
 	if ( self->dispList ) { 
		fz_drop_display_list(self->ctx, self->dispList); 
		self->dispList = NULL;
	}
	if ( self->stext ) {
		fz_drop_stext_page(self->ctx, self->stext);
		self->stext = NULL;
	}
}

static
void MU_destroy(MUPage* self) {
	if (self==NULL) return;
	fz_try(self->ctx) {
		MU_ResetCache(self);  // empty self->dispList  and self->stext
		if ( self->page ) { fz_drop_page(self->ctx, (fz_page *)self->page); }
	} fz_catch(self->ctx) {
		// ignore errors; you can do nothing ...
	}
	free( self );
}

 /* 
 ** ==========================================================================
 ** TclOO Class constructor/destructor
 ** ==========================================================================
 */

static 
void MY_DeleteOOMetadata( ClientData metadata ) {
	if (metadata) MU_destroy((MUPage*)metadata);
}


	// Metadata type holding MUPage data
static 
const Tcl_ObjectMetadataType MY_MetadataType = {
	TCL_OO_METADATA_VERSION_CURRENT,
	"mupdf::Page",
	MY_DeleteOOMetadata,
	NoCloneMethod
};

    // std utility for extracting metadata (i.e. object's data)
    //  from std args of tcloo-methods
static
MUPage* MY_Metadata( Tcl_ObjectContext objectCtx ) {
	Tcl_Object thisObject = Tcl_ObjectContextObject(objectCtx);
	return (MUPage*)Tcl_ObjectGetMetadata(thisObject, &MY_MetadataType);    
}

 // CONSTRUCTOR:
 // Tcl Usage::
 //   mupdf::Page new $doc pageNumber
static
TclResCode MY_Constructor( METHOD_ARGS ) {
	// no clientData
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 2) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "_docObj_  _pagenum_");
		return TCL_ERROR;
	}
	MUDoc *pDoc = xTcl_GetMUDocFromObj(interp,objv[0]);
	if ( !pDoc ) {
		// TODO: add more details to the error message 
		return TCL_ERROR;
	}
	int pageNum;
	if( Tcl_GetIntFromObj(interp,objv[1],&pageNum) == TCL_ERROR ) {
		return TCL_ERROR;
	}
	
	fz_context* ctx = MUDoc_context(pDoc);  //just an alias
	pdf_document* doc = MUDoc_pdf_document(pDoc);
	
	/// First page is 0 !
	int nPages = pdf_count_pages(ctx,doc);
	if( pageNum < 0 || pageNum > nPages-1 ) {
		Tcl_SetObjResult(interp,Tcl_NewStringObj("page number out of bound.",-1));
		return TCL_ERROR;	
	}
	
	MUPage* muPage = MU_new();
	if (!muPage) {
		Tcl_SetObjResult(interp,Tcl_NewStringObj("mupdf::Page alloc failed.",-1));
		return TCL_ERROR;
	}
	muPage->ctx = ctx;  // just a weak reference    
	muPage->pageNum = pageNum;
	muPage->docGrafts = MUDoc_xGraftedPages(pDoc); 
	fz_try( ctx) {
		muPage->page = pdf_load_page(ctx, doc, pageNum);
	} fz_catch (ctx) {
		MU_destroy( muPage );
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
		return TCL_ERROR;
	}
	
	// save data in object's metadata
	Tcl_ObjectSetMetadata(thisObject, &MY_MetadataType, (ClientData) muPage);
	return TCL_OK;
}

static
TclResCode MY_Destructor( METHOD_ARGS ) {
	Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
	 // Reset metadata:  
	 //   this will invoke MY_DeleteOOMetaData that in turns invoke MU_destroy ..
	Tcl_ObjectSetMetadata(thisObject, &MY_MetadataType, NULL);
	return TCL_OK;	
}


	 // WARNING !!
	 // These Tcl_MethodType structs must be fixed (i.e. static or in the heap)
	 //  they cannot be defined on the stack, since the Tcl_NewMethod stores its address
static const Tcl_MethodType MYConstructorType = {
	TCL_OO_METHOD_VERSION_CURRENT,
	"CONSTRUCTOR",
	MY_Constructor,	
	NULL,
	NULL
};
static const Tcl_MethodType MYDestructorType = {
	TCL_OO_METHOD_VERSION_CURRENT,
	"DESTRUCTOR",
	MY_Destructor,	
	NULL,
	NULL
};


 /* 
 ** ==========================================================================
 ** TclOO methods
 ** ==========================================================================
 */

	// Forward declarations and Tcl_MethodType definitions.
	// ...  remember to update the methods[] list in MUxx_Setup()
DEF_METHODTYPE(_update)
DEF_METHODTYPE(pagenumber)
DEF_METHODTYPE(size)
DEF_METHODTYPE(text)
DEF_METHODTYPE(blocks)
DEF_METHODTYPE(lines)

DEF_METHODTYPE(savePNG)
#ifdef USE_TK_STUBS	
DEF_METHODTYPE(saveImage)
#endif

DEF_METHODTYPE(images)

DEF_METHODTYPE(annots)
DEF_METHODTYPE(annot)



 // =====  method: pagenumber -================================================

	/*
	** Tcl Usage:   
	**  $pageObj pagenumber
	**  
	** returns its page number  (first page is 0)
	*/
static
TclResCode Method_pagenumber( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
	}
	Tcl_SetObjResult(interp, Tcl_NewIntObj(self->pageNum));
	return TCL_OK;
}


 // =====  method: images-=======================================================

 /*
** ============================================================================
** ImagePattern and ImagePositionalPattern
** ============================================================================
*/
    // .. some auxiliary functions ...
enum { 
	SYM_P_NUMOFPAGES = 0, 
	SYM_p_CURRPAGE, 
	SYM_I_NUMOFIMAGES, // n. of images in the current page
	SYM_i_CURRIMAGE    // image idx in the current page
};

	/*
	**  SymPosition is a small CONSTANT array that will be initialized
	**  at runtime by Alloc_SymPosition()
	*/
static int* SymPosition = NULL;

	// side effect: SymPosition array allocated and initialized
static
TclResCode Alloc_ImagePatternSymPosition (Tcl_Interp* interp) {
	
	const char* symbols = Tcl_GetVar(interp, "mupdf::_IMG_PATTERN_SYMBOLS", TCL_GLOBAL_ONLY);
	if (!symbols) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("internal error: _IMG_PATTERN_SYMBOLS undefined",-1));
		return TCL_ERROR;
	}
	
	int N = (int)strlen(symbols);
	SymPosition = (int*)ckalloc(N*sizeof(int));
	int i;
	for (i=0 ; i < N; ++i) {
	switch (symbols[i]) {
		case 'P': SymPosition[SYM_P_NUMOFPAGES] = i ; break;
		case 'p': SymPosition[SYM_p_CURRPAGE] = i ; break;
		case 'I': SymPosition[SYM_I_NUMOFIMAGES] = i ; break;
		case 'i': SymPosition[SYM_i_CURRIMAGE] = i ; break;
		default: {
			Tcl_SetObjResult(interp, Tcl_ObjPrintf(
				"internal error: _IMG_PATTERN_SYMBOLS has an unmanaged symbol '%c'", symbols[i]));
			return TCL_ERROR;
		}
	} 
	}
	return TCL_OK;
}

static
int HasPatternThisSymbol( Tcl_Interp* interp, const char* pattern, int symbol ) {
	Tcl_Obj* usedSymbolsObj = NULL;
	if (pattern && pattern[0] != '\0') {
		// compute the positional_pattern
		Tcl_Obj* command = Tcl_ObjPrintf("mupdf::_used_symbols %s", pattern);
		Tcl_IncrRefCount(command);
		TclResCode resCode = Tcl_EvalObjEx(interp, command, TCL_EVAL_DIRECT|TCL_GLOBAL_ONLY);
		Tcl_DecrRefCount(command);
		if ( resCode == TCL_ERROR ) {
			// this is very BAD...
			// lets' say the answer is 'false'
			return 0;
		}
		usedSymbolsObj = Tcl_GetObjResult(interp);
	} else {
		// use precomputed _IMG_USED_SYMBOLS
		usedSymbolsObj = Tcl_GetVar2Ex(interp, "mupdf::_IMG_USED_SYMBOLS", NULL, TCL_GLOBAL_ONLY);
	}
	if( !usedSymbolsObj ) {
		// this is very BAD...
		// lets' say the answer is 'false'
		return 0;
	}
	Tcl_IncrRefCount(usedSymbolsObj); // retain the object !!
	// check if resObj (as a string) contains the symbol
	int hasSymbol = (strchr( Tcl_GetString(usedSymbolsObj), symbol ) == NULL ? 0 : 1);
	Tcl_DecrRefCount(usedSymbolsObj);
	
	return hasSymbol;
}

static
int nOfDigits(int n) {
	int ndigits = 1;
	if( n>=10 ) { ndigits = 2; }
	if( n>=100 ) { ndigits = 3; }
	if( n>=1000 ) { ndigits = 4; }   // WOW 
	return ndigits;
}

	/*
	** Compute or retrieve the default positional_pattern.
	**
	** Return a *new* string
	** On error return NULL.
	** NOTE: the returned string (if not NULL) should be released with free()
	*/
static
char* NewImagePositionalPattern(Tcl_Interp* interp, const char* pattern) {
	Tcl_Obj* posPatternObj = NULL;
	if (pattern && pattern[0] != '\0') {
		// compute the positional_pattern
		Tcl_Obj* command = Tcl_ObjPrintf("mupdf::_positional_pattern %s", pattern);
		Tcl_IncrRefCount(command);
		TclResCode resCode = Tcl_EvalObjEx(interp, command, TCL_EVAL_DIRECT|TCL_GLOBAL_ONLY);
		Tcl_DecrRefCount(command);
		if ( resCode == TCL_ERROR ) {
			return NULL;
		}
		posPatternObj = Tcl_GetObjResult(interp);
	} else {
		// use precomputed _IMG_POSITIONAL_PATTERN
		posPatternObj = Tcl_GetVar2Ex(interp, "mupdf::_IMG_POSITIONAL_PATTERN", NULL, TCL_GLOBAL_ONLY);
	}
	if (!posPatternObj) {
		// this is a fatal error... you should check it 
		//  .. maybe at startup ...
		Tcl_SetObjResult(interp, Tcl_NewStringObj("internal error: IMG_POSITIONAL_PATTERN undefined",-1));
		return NULL;
	}
	char *posPatternStr = strdup(Tcl_GetString(posPatternObj));
	
	return posPatternStr;
}

 // return a new string.
 // this string should be released with free()
static
char* new_fullnameWithoutExt(const char* outputDir, const char* basename) {
	char* fullnameWithoutExt = NULL;
	char *addedFileSep = "";
	
	if (outputDir == NULL) {
		outputDir = "";
		addedFileSep = "";
	} else {
	size_t dirLen = strlen(outputDir);
		if (dirLen > 0 && outputDir[dirLen-1] != '/' ) {
			addedFileSep = "/";
		} else {
			addedFileSep = "";
		}
	}
	asprintf(&fullnameWithoutExt, "%s%s%s", outputDir, addedFileSep, basename);
	return fullnameWithoutExt;
}



  
// TODO - split this proc ...

static
Tcl_Obj* mu_images(Tcl_Interp* interp, MUPage *self, char *op, char *imageID, char *outputDir, const char* pattern, int useAlpha) {
	// to do ... define an  enum  MUPAGE_OP_EXTRACT_IMAGES , MUPAGE_OP_LISTIMAGES
	
	fz_context * const ctx = self->ctx; //useful alias
	
	// NOTE:
	// the following parameters are meaningful only for op "extract"
	//      outputDir, pattern, useAlpha
	// the following local variables are meaningful only for op "extract"
	//  mustAddImageNumber, positionalPattern, posPatternObj, zobjv 
	// troppi !! fai struct extract_data
	
	char* posPatternStr = NULL; 
	Tcl_Obj* zobjv[4]; 
	
	// preparing some useful data for the "extract" op.
	if ( strcmp(op,"extract") == 0 ) {
		
		int nOfImages;
		fz_try(ctx) {
			nOfImages = xpdf_number_of_images(ctx, self->page);
		} fz_catch(ctx) {
			nOfImages = -1;
		}
		// if pattern is NULL or "" , the default pattern is used.
		//   retrieve or compute the positional-pattern string
		posPatternStr = NewImagePositionalPattern(interp, pattern);
		if (!posPatternStr) {
			// error message already in interp's result
			return NULL;
		}
		// remember to release posPatternStr and zobjv elements !!!
		
		if ( imageID == NULL  ||  imageID[0] == '\0' ) {
			// we should process all the images in this page,
			// then check if we must add the image number to the pattern.
			if ( nOfImages > 1 &&  ! HasPatternThisSymbol( interp, pattern, 'i' ) ) {
				// extend the posPatternStr for including the 'i' symbol
				char *extend=NULL;
				asprintf(&extend, "%s-%%%d$0%dd", posPatternStr, SYM_i_CURRIMAGE, nOfDigits(nOfImages));
				free(posPatternStr);
				posPatternStr=extend;
			}
		}
		zobjv[SymPosition[SYM_P_NUMOFPAGES]] = Tcl_NewIntObj(pdf_count_pages(ctx,self->page->doc));
		// 0th page should be "1"   ! 
		zobjv[SymPosition[SYM_p_CURRPAGE]] = Tcl_NewIntObj(self->pageNum+1);
		zobjv[SymPosition[SYM_I_NUMOFIMAGES]] = Tcl_NewIntObj(nOfImages);
		zobjv[SymPosition[SYM_i_CURRIMAGE]] = Tcl_NewIntObj(0); // later ..
		
		// safety check.
		// before entering the extract loop, check if posPatternStr is valid !!
		Tcl_Obj* dummyObj = Tcl_Format(interp, posPatternStr, sizeof(zobjv)/sizeof(zobjv[0]), zobjv );
		if (dummyObj == NULL ) {
			// This is BAD !   posPattern is not valid !            
			Tcl_SetObjResult(interp,Tcl_ObjPrintf("internal error: posPatternStr \"%s\" is not valid.",posPatternStr)); 
			
			Tcl_DecrRefCount(dummyObj);
			for ( int i = 0; i < sizeof(zobjv)/sizeof(zobjv[0]); ++i ) {
				if( zobjv[i] ) Tcl_DecrRefCount(zobjv[i]);
			}
			
			return NULL;
		}
	}
	
	 ////// === scan the page ....
	Tcl_Obj * biglistObj = Tcl_NewListObj(0,NULL);
	
	pdf_obj * resourceObj = NULL;
	
	fz_try(ctx) {
		resourceObj = pdf_page_resources(ctx,self->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 elements (just some of these are images)
			int currImageIdx = 0;
			for( int i=0 ; i<n ; ++i ) { 
				const char *imName = pdf_to_name(ctx,pdf_dict_get_key(ctx,xObj,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;
				
				++currImageIdx;
				char imID[10];  // "im" + 7digits + \0
				snprintf(imID,sizeof(imID),"i%02d",currImageIdx);
				
				 // skip this image if imageID != imID
				 // NOTE: if ImageID is NULL, then process all images
				if ( imageID && strcmp(imageID,imID)!= 0 ) continue;
				
				Tcl_Obj* listObj;
				if ( strcmp(op,"list") == 0 ) {
					// ///////////////////////////////////////////////////
					listObj = mu_image_info(interp, ctx, imDict, imID);
					// ///////////////////////////////////////////////////
					
					//  could it be NULL ??
				} else {  // op is "extract"
					
					Tcl_SetIntObj(zobjv[SymPosition[SYM_i_CURRIMAGE]],currImageIdx);
					Tcl_Obj* fnameObj = Tcl_Format(interp, posPatternStr, sizeof(zobjv)/sizeof(zobjv[0]), zobjv );
					if (fnameObj == NULL ) {
						// this was previously checked.
						// This is VERY BAD !
						// what can I do ??
						break;
					}
					char* filenameWithoutExt = new_fullnameWithoutExt(outputDir, Tcl_GetString(fnameObj));
					Tcl_DecrRefCount(fnameObj); // free fnameObj
					
					// ///////////////////////////////////////////////////
					listObj = mu_image_extract( interp, ctx, self->page->doc, imDict, useAlpha, imID, imName, filenameWithoutExt);
					// ///////////////////////////////////////////////////
					free(filenameWithoutExt);
				}
				// safet check,but is NULL its's really a bad story !
				if (listObj)
					Tcl_ListObjAppendElement(interp, biglistObj, listObj);
			} // end for loop
		}
	} fz_always (ctx) {
		pdf_unmark_obj(ctx, resourceObj);  // ? should I free the resources ?
		// release all the resources
		if ( strcmp(op,"extract") == 0 ) {
			if (posPatternStr) free(posPatternStr);
			for ( int i = 0; i < sizeof(zobjv)/sizeof(zobjv[0]); ++i ) {
				if( zobjv[i] ) Tcl_DecrRefCount(zobjv[i]);
			}
		}
	} fz_catch(ctx) {
		//  free biglistObj !!
		Tcl_DecrRefCount(biglistObj);
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
		biglistObj = NULL;
	}
	
	return biglistObj;
}



static
TclResCode Method_images( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc < 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "list|extract");
		return TCL_ERROR;
	}
	char *op = Tcl_GetString(objv[0]);
	
	// initialize default values
	char *imageID = NULL;
	char *outputDir = NULL;
	char *pattern = NULL;
	int useAlpha = 1; // boolean
	
	int argIdx = 1;
	if( strcmp(op,"list") == 0 ) {
		// valid options are: -id 
		while (argIdx <objc) {
			char *arg = Tcl_GetString(objv[argIdx++]);
			// ? -id
			if( strcmp(arg,"-id") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("\"-id\" option requires an image-id.",-1));
					return TCL_ERROR;
				}
				imageID = Tcl_GetString(objv[argIdx++]);
			// ? other ?
			} else {
				Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unrecognized option \"%s\"; valid values are: -id",arg) );
				return TCL_ERROR;			
			}
		}	
	} else if (strcmp(op,"extract") == 0 ) {
		// valid options are: -id, -dir, -as, -transparency 
		
		while (argIdx <objc) {
			char *arg = Tcl_GetString(objv[argIdx++]);
			if( strcmp(arg,"-id") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-id\" option requires an image-id.",-1));
					return TCL_ERROR;
				}
				imageID = Tcl_GetString(objv[argIdx++]);
			} else if( strcmp(arg,"-dir") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-dir\" option requires the ouput directory's name.",-1));
					return TCL_ERROR;
				}
				outputDir = Tcl_GetString(objv[argIdx++]);
			} else if( strcmp(arg,"-transparency") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-transparency\" option requires a boolean value.",-1));
					return TCL_ERROR;
				}
				if( Tcl_GetBooleanFromObj(interp,objv[argIdx++],&useAlpha) == TCL_ERROR ) {
					return TCL_ERROR;
				}
			} else if( strcmp(arg,"-as") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-as\" option requires a pattern.",-1));
					return TCL_ERROR;
				}
				pattern = Tcl_GetString(objv[argIdx++]);
			// ? other ?
			} else {
				Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unrecognized option \"%s\"; valid values are: -id,-dir,-as or -transparency",arg) );
				return TCL_ERROR;			
			}
		}		
	} else {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unrecognized subcommand \"%s\"; valid subcommands are: list or extract",op) );
		return TCL_ERROR;				
	}
	
	Tcl_Obj* listObj = mu_images(interp, self, op, imageID, outputDir, pattern, useAlpha);
	if (!listObj) {
		return TCL_ERROR;
	}
	Tcl_SetObjResult(interp, listObj);
	return TCL_OK;
}

 // =====  method: _update -==============================================
static
TclResCode Method__update( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
	}
	////////////////////////////////////////////////////////
	MU_ResetCache( self );
	fz_try(self->ctx) {
		pdf_update_page(self->ctx,self->page);
	} fz_catch(self->ctx) {
		; //  nothing to do !
	}
	////////////////////////////////////////////////////////
	return TCL_OK;
}


 // =====  method: size-=======================================================

	/*
	** Tcl Usage:   
	**  $pageObj size
	**  
	** returns the physical size in points ( 1pt = 1/72 inch )
	** Note that when a pixmap is generated with zoom = 1, 1pt -> 1 pixel.
	*/
static
TclResCode Method_size( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
	}
	////////////////////////////////////////////////////////
	fz_rect rect = pdf_bound_page(self->ctx, self->page);
	Tcl_Obj *listPtr = Tcl_NewListObj(0,NULL);
	// x0,y0 are always 0,0
	Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewDoubleObj(rect.x1));
	Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewDoubleObj(rect.y1));
	////////////////////////////////////////////////////////
	
	Tcl_SetObjResult(interp, listPtr);
	return TCL_OK;
}

 // =====  method: text-=======================================================

	/* auxiliary for Method_text(), Method_blocks()  and others ..
	**
	**  mu_setup_stext() sets the self->stext (and eventually the self->dispList)
	** On error, if interp is not NULL, leaves an error message in interp's result
	*/
static
TclResCode mu_setup_stext(Tcl_Interp* interp, MUPage *self) {
	if ( self->stext ) return TCL_OK;
	
	fz_var(self->dispList);
	fz_var(self->stext);
	
	fz_try(self->ctx) {
		if( ! self->dispList ) {
			self->dispList = fz_new_display_list_from_page(self->ctx, (fz_page *)self->page);
		}
		fz_stext_options opts;
		opts.flags = 0;
		opts.flags |= FZ_STEXT_PRESERVE_WHITESPACE;
		opts.flags |= FZ_STEXT_PRESERVE_IMAGES;
		opts.flags |= FZ_STEXT_INHIBIT_SPACES;
		self->stext = fz_new_stext_page_from_display_list(
						self->ctx, self->dispList, &opts /*NULL*/
					); 
	} fz_catch(self->ctx) {
		fz_drop_stext_page(self->ctx,self->stext);
		self->stext = NULL;
		if (interp) Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR;
	}
	return TCL_OK;
}


static 
Tcl_Obj *mu_text(Tcl_Interp* interp, MUPage* self) {
	fz_context * const ctx = self->ctx; //useful alias
	Tcl_Obj* tclStringObj = NULL;
	fz_buffer *buff = NULL;
	fz_var(buff);
	fz_try(ctx) {
		buff = fz_new_buffer_from_stext_page(ctx, self->stext);
		tclStringObj = Tcl_NewStringObj(fz_string_from_buffer(ctx, buff),-1);
	} fz_always(ctx) {
		if(buff) fz_drop_buffer(ctx,buff);
	} fz_catch(ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
		tclStringObj = NULL;
	}
	return tclStringObj;
}


	/*
	** based on fz_new_buffer_from_stext_page() from fitz/util.c
	*/

enum BBOX_MODE {
	BBOX_NONE = 0,
	BBOX_BLOCKS,
	BBOX_LINES
};

static 
Tcl_Obj *mu_text_and_bbox(Tcl_Interp* interp, MUPage* self, enum BBOX_MODE bboxMode) {
	fz_context * const ctx = self->ctx; //useful alias
	
	fz_stext_block *block;
	fz_stext_line *line;
	fz_stext_char *ch;
	fz_buffer *buf;

	buf = fz_new_buffer(ctx, 256);
	Tcl_Obj *blocksList = Tcl_NewListObj(0,NULL);

	for (block = self->stext->first_block; block; block = block->next)	{
		if (block->type == FZ_STEXT_BLOCK_TEXT) {
			Tcl_Obj *objv[4];
			objv[0] = Tcl_NewDoubleObj(block->bbox.x0);
			objv[1] = Tcl_NewDoubleObj(block->bbox.y0);
			objv[2] = Tcl_NewDoubleObj(block->bbox.x1);
			objv[3] = Tcl_NewDoubleObj(block->bbox.y1);
			Tcl_Obj *blockBBox = Tcl_NewListObj(4,objv);
			Tcl_ListObjAppendElement(0, blocksList, blockBBox);
		
			Tcl_Obj *linesList = NULL;
			if ( bboxMode == BBOX_LINES ) {
				linesList = Tcl_NewListObj(0,NULL);
			}
			for (line = block->u.t.first_line; line; line = line->next) {
				if ( bboxMode == BBOX_LINES ) {
					objv[0] = Tcl_NewDoubleObj(line->bbox.x0);
					objv[1] = Tcl_NewDoubleObj(line->bbox.y0);
					objv[2] = Tcl_NewDoubleObj(line->bbox.x1);
					objv[3] = Tcl_NewDoubleObj(line->bbox.y1);
					Tcl_Obj *lineBBox = Tcl_NewListObj(4,objv);
					Tcl_ListObjAppendElement(0, linesList, lineBBox);
				}
				for (ch = line->first_char; ch; ch = ch->next) {
					fz_append_rune(ctx, buf, ch->c);
				}
				if ( bboxMode == BBOX_LINES ) {
					Tcl_Obj *tclStringObj = Tcl_NewStringObj(fz_string_from_buffer(self->ctx, buf),-1);
					Tcl_ListObjAppendElement(0, linesList, tclStringObj);
					fz_clear_buffer(ctx,buf);
				} else {
					if (line->next) {
						fz_append_byte(ctx, buf, '\n');
					}
				}	
			}
			if ( bboxMode == BBOX_LINES ) {
				Tcl_ListObjAppendElement(0, blocksList, linesList);
			} else {
				Tcl_Obj *tclStringObj = Tcl_NewStringObj(fz_string_from_buffer(self->ctx, buf),-1);
				Tcl_ListObjAppendElement(0, blocksList, tclStringObj);
				fz_clear_buffer(ctx,buf);
			}
		}
	}
	if(buf) fz_drop_buffer(ctx,buf);
	return blocksList;
}

	/*
	** Tcl Usage:   
	**  $pageObj text ?options?
	**  where options are
	**    -bbox none|blocks|lines
	**  
	** Extract text (with optional bounding-boxes) from the page.
	** Return ...
	*/
static
TclResCode Method_text( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;

	enum BBOX_MODE bboxMode = BBOX_NONE; // default
	int argIdx = 0;
	while ( argIdx < objc ) {
		char *arg = Tcl_GetString(objv[argIdx++]);
		if (strcmp(arg,"-bbox")==0) {
			arg = Tcl_GetString(objv[argIdx++]);
			if (strcmp(arg,"none")==0) {
				bboxMode = BBOX_NONE;
			} else if (strcmp(arg,"blocks")==0) {
				bboxMode = BBOX_BLOCKS;
			} else if (strcmp(arg,"lines")==0) {
				bboxMode = BBOX_LINES;
			} else {
				Tcl_WrongNumArgs(interp, skip, objv-skip, "?-bbox none|blocks|lines" );
				return TCL_ERROR;
			}
		} else {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "?-bbox none|blocks|lines" );
			return TCL_ERROR;		
		}
	}
	
	if ( mu_setup_stext(interp, self) != TCL_OK ) {
		return TCL_ERROR;
	}
	Tcl_Obj* resObj;
	switch (bboxMode) {
	  case BBOX_NONE:
		resObj = mu_text(interp,self);
	  	break;
	  case BBOX_BLOCKS:
	  case BBOX_LINES:
		resObj = mu_text_and_bbox(interp, self,bboxMode);
	  	break;
	}
	if (!resObj) {
		return TCL_ERROR;
	}
	Tcl_SetObjResult(interp,resObj);
	return TCL_OK;
}

 // =====  method: block-======================================================

	// ... auxiliary for Method_blocks()

static      
Tcl_Obj * mu_getBlocks(Tcl_Interp* interp, MUPage *self) {
	if ( mu_setup_stext(interp, self) != TCL_OK ) {
		// error message in interp's result
		return NULL;
	}
	
	Tcl_Obj *blocksList = Tcl_NewListObj(0,NULL);
	fz_stext_block *block;
	for (block = self->stext->first_block; block; block = block->next) {
		Tcl_Obj *objv[5];
		switch (block->type) {
		  case FZ_STEXT_BLOCK_IMAGE:
			objv[0] = Tcl_NewStringObj("imageblock",-1);
			break;
		  case FZ_STEXT_BLOCK_TEXT:
			objv[0] = Tcl_NewStringObj("textblock", -1);
			break;
		}
		objv[1]=Tcl_NewDoubleObj(block->bbox.x0); 
		objv[2]=Tcl_NewDoubleObj(block->bbox.y0); 
		objv[3]=Tcl_NewDoubleObj(block->bbox.x1); 
		objv[4]=Tcl_NewDoubleObj(block->bbox.y1);
		
		Tcl_Obj *blockList = Tcl_NewListObj(5,objv);
		
		Tcl_ListObjAppendElement(0, blocksList, blockList);
	}
	
	return blocksList;
}

	/*
	** Tcl Usage:   
	**  $pageObj blocks
	**  
	**  return the list of image/text blocks in $pageObj. 
	**  A block is a list of 5 elements with the following format:
	**    textblock|imageblock x0 y0 x1 y1
	*/
static
TclResCode Method_blocks( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
	}
	
	////////////////////////////////////////////////////////	
	Tcl_Obj *strObj = mu_getBlocks(interp, self);
	if ( strObj == NULL ) {
		// error message already stored in interp result
		return TCL_ERROR;	
	}
	////////////////////////////////////////////////////////
	Tcl_SetObjResult(interp,strObj);
	return TCL_OK;
}

 // =====  method: lines -=====================================================

	/* auxiliary for Method_lines()
	*/


#if !defined(min)
	#define min(a,b) ((a<b)?a:b)
	#define max(a,b) ((a>b)?a:b)
#endif

static
Tcl_Obj * mu_getLines(Tcl_Interp* interp, MUPage* self) {
	
	if ( mu_setup_stext(interp,self) != TCL_OK ) {
		// error message in interp's result
		return NULL;
	}
	
	fz_stext_block *block;
	
	Tcl_Obj *blocksList = Tcl_NewListObj(0,NULL);
	
	for (block = self->stext->first_block; block; block = block->next) {
		if (block->type != FZ_STEXT_BLOCK_TEXT) continue;
		
		for (fz_stext_line* line = block->u.t.first_line ; line ; line = line ->next ) {
			Tcl_Obj *objv[4];  
			objv[0]=Tcl_NewDoubleObj(line->bbox.x0); 
			objv[1]=Tcl_NewDoubleObj(line->bbox.y0); 
			objv[2]=Tcl_NewDoubleObj(line->bbox.x1); 
			objv[3]=Tcl_NewDoubleObj(line->bbox.y1);
			
			Tcl_Obj *blockList = Tcl_NewListObj(4,objv);
			
			Tcl_ListObjAppendElement(0, blocksList, blockList);
		}
	}
	return blocksList;
}

	/*
	** Tcl Usage:   
	**  $pageObj lines
	**  
	**  return the list of the bbox of the text lines in $pageObj.
	**  A bbox is a list of 4 elements with the following format:
	**     x0 y0 x1 y1
	*/
static
TclResCode Method_lines( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
	}
	
	////////////////////////////////////////////////////////	
	Tcl_Obj *strObj = mu_getLines(interp,self);
	if ( strObj == NULL ) {
		// error message already stored in interp result
		return TCL_ERROR;	
	}
	////////////////////////////////////////////////////////
	Tcl_SetObjResult(interp,strObj);
	return TCL_OK;
}


 // =====  method: annots -====================================================
	/*
	** Tcl Usage:   
	**  $pageObj annots
	**  
	**  return the list of the annotation-id $pageObj.
	**  An annotation-id is simply ann integer (...xref)
	*/
static
TclResCode Method_annots( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
	}
	////////////////////////////////////////////////////////	
	Tcl_Obj* resList = mu_annots(interp,self->ctx, self->page);
	if ( ! resList ) {
		// error message already stored in interp result
		return TCL_ERROR;	
	}
	////////////////////////////////////////////////////////
	Tcl_SetObjResult(interp,resList);
	return TCL_OK;
}


 // =====  method: annot -====================================================

	/*
	** Tcl Usage:   
	**  $pageObj annot create ....
	**  $pageObj annot delete _annotID_  ?_annotID ...?
	**  $pageObj annot flatten _annotID_ ?_annotID ...?
	**  $pageObj annot ?get? _annotID_
	**  $pageObj annot ?get? _annotID_ -option
	**  $pageObj annot ?set? _annotID_ -option value ...
	**  
	*/
TclResCode Method_annot( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc < 1 ) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "create|delete|flatten|?get?|?set?| ...");
		return TCL_ERROR;
	}
	
	// objc >= 1
	
	TclResCode res;
	pdf_annot* annot = NULL;
	
	// if argv[0] is an integer, then it's a short form of get/set
	{
		int annotID;
		if( Tcl_GetIntFromObj(interp, objv[0] ,&annotID) == TCL_OK ) {
			
			annot = get_annot_from_xref(self->ctx, self->page, annotID);
			if ( !annot ) {
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("invalid annotID \"%d\"",annotID));
				return TCL_ERROR;
			}
			// $p annot ID        (objc==1)
			// $p annot ID -aaa   (objc==2)
			// $p annot ID -aa ss ...    (objc > 2)
			if ( objc <= 2 ) {
				Tcl_Obj* resObj = mu_annot_get_attribs(interp, self->ctx, self->docGrafts, annot, objc-1, objv+1);
				if (resObj) {
					Tcl_SetObjResult(interp, resObj);
					res = TCL_OK;
				} else {
					res = TCL_ERROR;
				}
			} else {
				res = mu_annot_set_attribs(interp, self->ctx, self->docGrafts, annot, objc-1, objv+1);        
				if (res == TCL_OK) {
					MU_ResetCache(self);
				}
			}
			return res;
		}
		Tcl_ResetResult(interp);
	}
	
	// no short-form;  op is explicit
	
	char *op = Tcl_GetString(objv[0]);
	
	if ( strcmp(op,"create") == 0 ) {
		if (objc < 2) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "create _type_  _options_");  
			return TCL_ERROR;
		}
		annot = mu_annot_create(interp, self->ctx, self->docGrafts, self->page, Tcl_GetString(objv[1]), objc-2, objv+2);
		if (!annot) {
			// error already set in interp's state
			return TCL_ERROR;
		}
		MU_ResetCache(self);
		// from annot to xref
		Tcl_SetObjResult(interp,Tcl_NewIntObj(pdf_to_num(self->ctx, pdf_annot_obj(self->ctx,annot))));
		return TCL_OK;
	}
	
	
	if (strcmp(op,"delete") == 0 ) {
		// expected 1 or more ID
		if ( objc == 1 ) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "delete _ID_ ?_ID_...?");  
			return TCL_ERROR;
		}
		res =  mu_annot_delete(interp, self->ctx, self->page, objc-1, objv+1);
		MU_ResetCache(self);
		return res;
	}
	
	if (strcmp(op,"flatten") == 0 ) {
		// expected 1 or more ID
		if ( objc == 1 ) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "flatten _ID_ ?_ID_...?");  
			return TCL_ERROR;
		}
		res =  mu_annot_flatten(interp, self->ctx, self->page, objc-1, objv+1);
		MU_ResetCache(self);
		return res;
	}
	
	
	if ( strcmp(op,"get")== 0  ) {
		// expected 1 ID
		if ( objc == 1 ) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "get _ID_ ?_option_?");  
			return TCL_ERROR;
		}
		annot = mu_GetAnnotFromObj(interp, objv[1], self->ctx, self->page);
		if ( !annot ) {
			return TCL_ERROR;
		}
		Tcl_Obj* result = mu_annot_get_attribs(interp, self->ctx, self->docGrafts, annot, objc-2, objv+2);
		if  (!result) {
			return TCL_ERROR;
		}
		Tcl_SetObjResult(interp, result);
		return TCL_OK;
	}
	
	if ( strcmp(op,"set")== 0  ) {
		// expected 1 ID
		if ( objc == 1 ) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "set _ID_ _option_ _value_ ?_option_ _value_ ...?");
			return TCL_ERROR;
		}
		annot = mu_GetAnnotFromObj(interp, objv[1], self->ctx, self->page);
		if ( !annot ) {
			return TCL_ERROR;
		}
		res = mu_annot_set_attribs(interp, self->ctx, self->docGrafts, annot, objc-2, objv+2);
		if (res == TCL_OK) {
			MU_ResetCache(self);
		}
		return res;
	}
	
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("unrecognized annot's subcmd \"%s\"",op));
	return TCL_ERROR;
}


// return a new pixmap filled with the content of the displaylist
// the returned pixmap must be deallocated by calling  fz_drop_pixmap(ctx,pix);
// WARNING: may raise exceptions 
static
fz_pixmap* xfz_new_rendered_pixmap(fz_context* ctx, fz_display_list* dispList, fz_rect clip, double zoom, int overlay) {
	if( clip.x0 >= clip.x1 || clip.y0 >= clip.y1 ) {
		fz_throw(ctx, FZ_ERROR_GENERIC, "malformed clipping rectangle");
		// *** -->Exception
	}
	
	fz_rect pageRect = fz_bound_display_list(ctx, dispList);
	 // adjust clip: must be within pageRect
	clip = fz_intersect_rect( clip, pageRect );
	if( (clip.x1 - clip.x0) < 1E-6  ||  (clip.y1 - clip.y0) < 1E-6)  {
		fz_throw(ctx, FZ_ERROR_GENERIC, "clipping area does not intersect the page-area");
		// *** -->Exception
	}
	
	fz_matrix ctm;
	 /* Compute a transformation matrix for the desired zoom . */
	 /* The default resolution without scaling (zoom=1) is 72 dpi. */    
	ctm = fz_scale(zoom, zoom);
	clip = fz_transform_rect(clip, ctm);
	
	fz_irect iclip = fz_round_rect(clip);
	
	fz_device *pDev = NULL;
	fz_pixmap *pix = NULL;
	
	 // set a try-catch for removing the internally allocated resources ..
	fz_var(pix);  // ??? be sure it's not unwinded
	fz_var(pDev);  // ??? be sure it's not unwinded
	 /* Render page to an RGB pixmap. */
	fz_try(ctx) {
		pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), iclip, NULL, 1);
		
		if (overlay) {
			fz_clear_pixmap(ctx, pix); // black, alpha 0x00
		} else {
			fz_clear_pixmap_with_value(ctx, pix, 0xFF);  // fill WHITE, alpha 0xFF
		}
		pDev = fz_new_draw_device(ctx, fz_identity, pix);
		fz_run_display_list(ctx, dispList, pDev, ctm, clip, NULL);
	} fz_always(ctx) {
		if( pDev )  {
			fz_close_device(ctx,pDev);
			fz_drop_device(ctx,pDev);
		}
	} fz_catch(ctx) {
		if (pix) fz_drop_pixmap(ctx,pix);
		fz_rethrow(ctx);
		// *** -->Exception
	}
	return pix;
}


#ifdef USE_TK_STUBS
 // =====  method: saveImage -=================================================

	// .. auxiliary for Method_saveImage()

#include <tk.h>

 // with support of display-list
static      
TclResCode mu_DLtoimage(Tcl_Interp *interp, MUPage *self, char *imageName, fz_rect clip, double zoom, int xTo, int yTo, int overlay) {

	Tk_PhotoHandle handle;
	handle = Tk_FindPhoto(interp, imageName);
	if ( handle == NULL ) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("image \"%s\" doesn't exist.", imageName));
		return TCL_ERROR;	
	}
	
	fz_pixmap* pix = NULL;	  
	
	 // set a try-catch for removing the internally allocated resources ..
	fz_var(self->dispList);
	fz_var(pix);  // ??? be sure it's not unwinded
	
	fz_try(self->ctx) {
		if( ! self->dispList ) {	
			self->dispList = fz_new_display_list_from_page(self->ctx,(fz_page *)self->page);
		}
		pix = xfz_new_rendered_pixmap(self->ctx, self->dispList, clip, zoom, overlay);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR;
	}
	
	// reuse photoBlock for the fz_pixmap:
	//  pixelsize and offset don't change !
	 // WARNING: be sure that pixelsSize = 4 !
	Tk_PhotoImageBlock photoBlock;
	Tk_PhotoGetImage(handle, &photoBlock);
	
	fz_matrix ctm;
	/* Compute a transformation matrix for the desired zoom . */
	/* The default resolution without scaling (zoom=1) is 72 dpi. */    
	ctm = fz_scale(zoom, zoom);
	clip = fz_transform_rect(clip, ctm);
	
	fz_irect iclip = fz_round_rect(clip);
	
	photoBlock.pixelPtr = fz_pixmap_samples(self->ctx, pix);
	photoBlock.width = iclip.x1-iclip.x0;
	photoBlock.height = iclip.y1-iclip.y0;
	photoBlock.pitch = photoBlock.width*photoBlock.pixelSize;
	int compositeMode = overlay ? TK_PHOTO_COMPOSITE_OVERLAY : TK_PHOTO_COMPOSITE_SET;
	Tk_PhotoPutBlock(interp, handle, &photoBlock, xTo, yTo, photoBlock.width, photoBlock.height, compositeMode );
	
	fz_drop_pixmap(self->ctx,pix);
	
	return TCL_OK;
}


	/*
	** Tcl usage:   
	**  $pageObj saveImage image ?-zoom zoom? ?-from x0 y0 x1 y1? ?-to x0 y0? ?-overlay true/false?
	**  
	**  render the page in an existing Tk's photo image.
	*/
static
TclResCode Method_saveImage( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc == 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "imagename ?-zoom z? ?-from x0 y0 x1 y1? ?-to x y? ?-overlay true/false?" );
		return TCL_ERROR;
	}
	// _objc >= 1
	char *imageName = Tcl_GetString(objv[0]);
	 // default values for options:
	int overlay = 0; 
	double zoom = 1.0;
	fz_rect  from = pdf_bound_page(self->ctx, self->page);
	int xTo = 0;
	int yTo = 0;
	int argIdx = 1;
	while (argIdx < objc) {
		char *arg = Tcl_GetString(objv[argIdx++]);
		// ? -zoom
// to do ..   a lot of common code with savePNG ...
		if( strcmp(arg,"-zoom") == 0 ) {
			if( argIdx +1 > objc ) {
				Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-zoom\" option requires a numerical, positive value.",-1)); 
				return TCL_ERROR;
			}
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&zoom) == TCL_ERROR ) {
				return TCL_ERROR;
			}
			if ( zoom <= 0.0 ) {
				Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-zoom\" option requires a numerical, positive value.",-1));
				return TCL_ERROR;			
			}
			// OK: zoom > 0.0
		// ? -from
		} else if( strcmp(arg,"-from") == 0 ) {
			if( argIdx +4 > objc ) {
				Tcl_SetObjResult(interp,Tcl_NewStringObj("\"-from\" option requires 4 numerical coords.",-1));
				return TCL_ERROR;
			}
			double x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.x0 = x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.y0 = x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.x1 = x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.y1 = x;
			// OK: from.x0, from.y0, from.x1, from.y1
		// ? -to
		} else if( strcmp(arg,"-to") == 0 ) {
			if( argIdx +2 > objc ) {
				Tcl_SetObjResult(interp,Tcl_ObjPrintf("\"%s\" option requires 2 numerical coords.",arg));
				return TCL_ERROR;
			}
			if( Tcl_GetIntFromObj(interp,objv[argIdx++],&xTo) == TCL_ERROR ) {
				return TCL_ERROR;
			}
			if( Tcl_GetIntFromObj(interp,objv[argIdx++],&yTo) == TCL_ERROR ) {
				return TCL_ERROR;
			}
			// OK: -to x y
		} else if( strcmp(arg,"-overlay") == 0 ) {
			if( argIdx +1 > objc ) {
				Tcl_SetObjResult(interp,Tcl_ObjPrintf("\"%s\" option requires a boolean value.",arg)); 
				return TCL_ERROR;
			}
			if( Tcl_GetBooleanFromObj(interp,objv[argIdx++],&overlay) == TCL_ERROR ) {
				return TCL_ERROR;
			}            
			//  OK: overlay            
		// ? other ?
		} else {
			Tcl_SetObjResult(interp,Tcl_ObjPrintf( "unrecognized option \"%s\"; valid values are: -zoom, -from, -to or -overlay",arg) );
			return TCL_ERROR;			
		}
	}	
	////////////////////////////////////////////////////////	
	if ( mu_DLtoimage( interp, self, imageName, from, zoom, xTo, yTo, overlay ) != TCL_OK ) {
		// error message already stored in interp result
		return TCL_ERROR;	
	}
	////////////////////////////////////////////////////////
	return TCL_OK;
}

#endif

 // =====  method: savePNG  -=================================================

	// .. auxiliary for Method_savePNG

static
TclResCode mu_savePNG(Tcl_Interp* interp, MUPage *self, char *filename, fz_rect clip, double zoom, int overlay) {
	
	fz_pixmap *pix = NULL;
	
	// set a try-catch for removing the internally allocated resources ..
	fz_var(self->dispList);
	fz_var(pix);  // ??? be sure it's not unwinded
	
	fz_try(self->ctx) {
		if( ! self->dispList ) {	
			self->dispList = fz_new_display_list_from_page(self->ctx,(fz_page *)self->page);
		}
		pix = xfz_new_rendered_pixmap(self->ctx, self->dispList, clip, zoom, overlay);
		
		fz_save_pixmap_as_png(self->ctx, pix, filename);
		
	} fz_always(self->ctx) {
		if( pix )   fz_drop_pixmap(self->ctx,pix);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult(interp,Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR;
	}
	return TCL_OK;
}

	/*
	** TCL Usage:   
	**  $pageObj savePNG filename ?-zoom z? ?-from x0 y0 x1 y1? -overlay true/false"
	**  
	**  render the page in a .png file.
	*/
static
TclResCode Method_savePNG( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUPage *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc == 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "filename ?-zoom z? ?-from x0 y0 x1 y1?" );
		return TCL_ERROR;
	}
	// objc >= 1
	char *filePNG = Tcl_GetString(objv[0]);
	// default values for options:
	double zoom = 1.0;
	fz_rect  from = pdf_bound_page(self->ctx, self->page);
	int overlay = 0;
	
	int argIdx = 1;
	while (argIdx < objc) {
		char *arg = Tcl_GetString(objv[argIdx++]);
		// ? -zoom
		if( strcmp(arg,"-zoom") == 0 ) {
			if( argIdx +1 > objc ) {
				Tcl_SetObjResult(interp,Tcl_ObjPrintf("\"%s\" option requires a numerical, positive value.",arg));
				return TCL_ERROR;
			}
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&zoom) == TCL_ERROR ) {
				return TCL_ERROR;
			}
			if ( zoom <= 0.0 ) {
				Tcl_SetObjResult(interp,Tcl_ObjPrintf("\"%s\" option requires a numerical, positive value.",arg));			
				return TCL_ERROR;
			}
			// OK: zoom > 0.0
		// ? -from
		} else if( strcmp(arg,"-from") == 0 ) {
			if( argIdx +4 > objc ) {
				Tcl_SetObjResult(interp,Tcl_ObjPrintf("\"%s\" option requires 4 numerical coords.",arg));
				return TCL_ERROR;
			}
			double x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.x0 = x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.y0 = x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.x1 = x;
			if( Tcl_GetDoubleFromObj(interp,objv[argIdx++],&x) == TCL_ERROR ) {
				return TCL_ERROR;
			} from.y1 = x;
			 // OK: from.x0, from.y0, from.x1, from.y1
		} else if( strcmp(arg,"-overlay") == 0 ) {
			if( argIdx +1 > objc ) {
				Tcl_SetObjResult(interp,Tcl_ObjPrintf("\"%s\" option requires a boolean value.",arg)); 
				return TCL_ERROR;
			}
			if( Tcl_GetBooleanFromObj(interp,objv[argIdx++],&overlay) == TCL_ERROR ) {
				return TCL_ERROR;
			}
			//  OK: overlay
			
		// ? other ?
		} else {
			Tcl_SetObjResult(interp,Tcl_ObjPrintf( "unrecognized option \"%s\"; valid values are: -zoom or -from",arg) );
			return TCL_ERROR;			
		}
	}
	////////////////////////////////////////////////////////	
	if ( mu_savePNG(interp, self, filePNG, from, zoom, overlay) != TCL_OK ) {
		// error message already stored in interp result
		return TCL_ERROR;	
	}
	////////////////////////////////////////////////////////
	return TCL_OK;
}

// ============================================================================
// public functions for the integration with other modules
// ============================================================================

fz_context* MUPage_fz_context(MUPage* mupage) {
	return (mupage ? mupage->ctx : NULL);
}

pdf_page*  MUPage_pdf_page(MUPage* mupage) {
	return (mupage ? mupage->page : NULL);
}

int        MUPage_pageNumber(MUPage* mupage) {
	return (mupage ? mupage->pageNum : -1);
}

fz_stext_page* MUPage_fz_stext_page(MUPage* mupage) {
	if (!mupage) return NULL;
	
	if ( mu_setup_stext(NULL, mupage) != TCL_OK ) {
		return NULL;
	}
	return mupage->stext;
}

MUPage* xTcl_GetMUPageFromObj( Tcl_Interp* interp, Tcl_Obj* objPtr ) {
	const Tcl_Object blObject = Tcl_GetObjectFromObj(interp, objPtr);
	if ( ! blObject ) {
		// error message set in interpreter's result
		return NULL;
	}
	ClientData clientData = Tcl_ObjectGetMetadata(blObject, &MY_MetadataType);
	if ( clientData == NULL ) {
		Tcl_SetObjResult(interp,Tcl_NewStringObj("Object must be a mupdf::Page",-1));
		return NULL;
	}
	return (MUPage*)clientData;
}


// ============================================================================
// MUPage module initialization
// ============================================================================

  /*
  ** Class Setup  - it MUST be called once in xxMAINxx.c
  **
  ** Warning: remember to add a dummy "oo::class create ..."" in xxMAINxx.tcl
  */
TclResCode  MUPage_Setup(Tcl_Interp *interp) {
	TclResCode res;
	Tcl_Class clazz = tcloox_getClass(interp, "mupdf::Page_C");
	if (clazz == NULL) { return TCL_ERROR; }
	
	tcloox_registerConstructor(interp, clazz, &MYConstructorType);
	tcloox_registerDestructor (interp, clazz, &MYDestructorType);
	
	const Tcl_MethodType * methods[] = {
		&MethodType__update,
		&MethodType_pagenumber,
		&MethodType_size,
		&MethodType_savePNG,
	#ifdef USE_TK_STUBS	
		&MethodType_saveImage,
	#endif
		&MethodType_blocks,
		&MethodType_lines,
		&MethodType_text,
		
		&MethodType_images,
		&MethodType_annots,
		&MethodType_annot,
		
		NULL
	};
	
	tcloox_registerMethods(interp, clazz, methods);
	
	res = Alloc_ImagePatternSymPosition(interp);
	return res;
}
