/*
** MUDoc.c
**
** TclOO class  Doc_C  implementation.
**
** This is a basic class wrapping the pdf_document* entity.
**
**
** Oct2020 - ABU fecit
*/

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

#include <string.h>   // just for strdup()
#include "tcloo_util.h"
#include <mupdf/pdf.h>
#include "xpdf_extension.h"
#include "xpdf_form.h"
#include "MUPage.h"
#include "MUDoc.h"

/*
**  Preliminary definition of some auxiliary data structures and methods
**
**  xGraftedPage's new/destroy  and  CollectionOf_xGraftedPages
**  xGraftMap's    new/destroy  and  CollectionOf_xGraftMaps
*/

   // see xGraftedPages.h
DEFINE_COLLECTION_OF(xGraftedPage *,xGraftedPages)

static 
xGraftedPage* xGraftedPage_new(fz_context* ctx, const char* ID, pdf_obj* xobj) {
    xGraftedPage* p;
    p = malloc(sizeof(xGraftedPage));
    if (p) {
        p->ID = NULL;
        p->ctx = ctx;
        p->ID =strdup(ID);
        pdf_keep_obj(ctx,xobj);  // no need to fz_try/fz_catch
        p->xobj = xobj;
    }
    return p;
}

static 
void xGraftedPage_destroy(xGraftedPage* p) {
    if (p) {
        if( p->ID ) free(p->ID);
        pdf_drop_obj(p->ctx, p->xobj);  // no need to fz_try/fz_catch
        free(p);
    }
}


typedef struct xGraftMap_s {
    char* ID;
    fz_context* ctx;
    pdf_graft_map* map;
} xGraftMap;

static
xGraftMap* xGraftMap_new(fz_context* ctx, const char* ID, pdf_graft_map* map) {
    xGraftMap* p = malloc(sizeof(xGraftMap));
    if (p) {
        p->ID = strdup(ID);
        p->ctx = ctx;
        p->map =map;    
    }
    return p;
}

static
void xGraftMap_destroy(xGraftMap* p) {
    if (p) {
        free(p->ID);
        pdf_drop_graft_map(p->ctx,p->map);
        free(p);
    }
}

DECLARE_COLLECTION_OF(xGraftMap *, xGraftMaps)
DEFINE_COLLECTION_OF(xGraftMap *,  xGraftMaps)

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

typedef
	struct MUDoc_s {
		fz_context *ctx;
		pdf_document *doc;
		enum {AUTH_FAILED, AUTH_NOT_REQUIRED, AUTH_USER, AUTH_OWNER} authentication;
		char* fullname;  // stored as a *normalized* path
		Tcl_WideInt originalSavedTime;
		int changesYetExported;
		CollectionOf_xGraftedPages *graftedPages;
		int graftedPagesSerialNo;
		CollectionOf_xGraftMaps *graftMaps;
		
		struct {
			CollectionOf_xFormFields*  fieldMap;
			CollectionOf_xFormWidgets* widgetMap;
		} cache;
		int hasFlattenedFields;
		char* upwd;
		char* opwd;
		int hasChangedPwd;
		Tcl_Interp* interp;
		Tcl_Obj* warningList;
	} MUDoc;



static
void _my_mupdf_message_callback(void *userdata, const char* prefix, const char *message) {
	MUDoc* data = userdata;

	Tcl_Interp* interp = data->interp;
	
	int printWarning = 1;
	const char* varName = "mupdf::_PRINT_WARNINGS"; 
	Tcl_Obj* valueObj = Tcl_GetVar2Ex(interp, varName, NULL, TCL_GLOBAL_ONLY);
	if (!valueObj) {
		// this is a bad story ... let's restore the Tcl var to "true"
		Tcl_SetVar(interp, varName, "true", TCL_GLOBAL_ONLY);
		fprintf(stderr, "WARNING: internal variable \"%s\" has been deleted. Now restored.", varName);
	} else {
		if ( Tcl_GetBooleanFromObj(interp, valueObj, &printWarning) != TCL_OK ) {
			// another bad story .. someone changed the Tcl var directly bypassing bool checks!
			//  let's restore it (to "true")
			Tcl_SetVar(interp, varName, "true", TCL_GLOBAL_ONLY);
			fprintf(stderr, "WARNING: value for the internal variable \"%s\" must be boolean. Now restored to \"true\"", varName);
		}
	}	
	
	if (printWarning) {
		fprintf(stderr,"%s: %s\n",prefix, message);
	}
	Tcl_ListObjAppendElement(NULL, data->warningList, 
				Tcl_ObjPrintf("%s: %s", prefix, message)
	);
}

static
void my_mupdf_warning_callback(void *userdata, const char *message) {
	_my_mupdf_message_callback(userdata, "MuPDF Warning:", message);
}
static
void my_mupdf_error_callback(void *userdata, const char *message) {
	_my_mupdf_message_callback(userdata, "MuPDF Error:", message);
}



  // forward declarations
static void MU_destroy(MUDoc* self);
    
static
MUDoc*	MU_new(Tcl_Interp* interp) {
	MUDoc* This = (MUDoc *)calloc(1,sizeof(MUDoc));
	if( This == NULL ) return NULL;

     // initialize all but the dynamic data (later ...)
    This->ctx = NULL;
    This->doc = NULL;
    This->authentication = AUTH_NOT_REQUIRED;
    This->fullname = NULL;       // stored as a *normalized* path
    This->originalSavedTime = 0;
	This->changesYetExported = 0;
    This->graftedPages = NULL;
    This->graftedPagesSerialNo = 0;
    This->graftMaps = NULL;
	This->cache.fieldMap = NULL;
	This->cache.widgetMap = NULL;
	This->hasFlattenedFields = 0;
	This->opwd = NULL;
	This->upwd = NULL;
	This->hasChangedPwd = 0;
	This->warningList = NULL;
	This->interp = interp;
	
    // ----  now initialize the dynamic data ----------------------------------
    This->ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
	if(! This->ctx) goto InitError ;

    This->graftedPages = CollectionOf_xGraftedPages_New(xGraftedPage_destroy);
    if (! This->graftedPages) goto InitError ;    

    This->graftMaps = CollectionOf_xGraftMaps_New(xGraftMap_destroy);
    if (! This->graftMaps) goto InitError ;    
    
    This->opwd = strdup("");
    if (! This->opwd) goto InitError ;

    This->upwd = strdup("");
    if (! This->upwd) goto InitError ;
    
	This->warningList = Tcl_NewListObj(0,NULL);
	Tcl_IncrRefCount(This->warningList);

	 // set my custom callback for MUPDF errors/warnings 
	fz_set_warning_callback(This->ctx, my_mupdf_warning_callback, This);
	fz_set_error_callback(This->ctx, my_mupdf_error_callback, This);

    return This;
    
InitError:

	MU_destroy(This);        			    
	return NULL;
}


	// resetting the field cache means dropping the field_map and widget_map.
	// they will be recreated on the next request (see getFieldMap, getWidgetMap)
static
void reset_formfield_cache( MUDoc *self ) {
    CollectionOf_xFormFields_Destroy(self->cache.fieldMap);
    CollectionOf_xFormWidgets_Destroy(self->cache.widgetMap);
    self->cache.fieldMap = NULL;
    self->cache.widgetMap = NULL;
}

static
void MU_destroy(MUDoc* self) {
    if (!self) return;
      // these strings were allocated by strdup  ...       
    if ( self->upwd ) free(self->upwd);
    if ( self->opwd ) free(self->opwd);
	if ( self->fullname ) free(self->fullname);

    CollectionOf_xGraftedPages_Destroy(self->graftedPages);
    CollectionOf_xGraftMaps_Destroy(self->graftMaps);
	if (self->warningList) { 
		Tcl_DecrRefCount(self->warningList);
		self->warningList = NULL;
	}
	 // BUGFIX: remove custom callback for MUPDF errors/warnings
	 //  since these callbacks will be called by fz_drop_context()
	fz_set_warning_callback(self->ctx, NULL, NULL);
	fz_set_error_callback(self->ctx, NULL, NULL);
		
	fz_context *ctx = self->ctx ; // just an alias
    if (!ctx) return;

	fz_try(ctx) {
		reset_formfield_cache(self);	 
		if ( self->doc ) { pdf_drop_document(ctx, self->doc); }
	} fz_catch(ctx) {
	   ;  // ignore errors; you can do nothing ...
	}
	
    fz_drop_context(ctx);
	free( self );
}

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

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

	// Metadata type that holds MUDoc data
static 
const Tcl_ObjectMetadataType MY_MetadataType = {
    TCL_OO_METADATA_VERSION_CURRENT,
    "mupdf::Doc",
    MY_DeleteOOMetadata,
    NoCloneMethod
};

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


/*
 *-----------------------------------------------------------------------------*
 * internal .. auxiliary for MY_Constructor()
 *  try to load filename and init all the MUDoc structure
 *  On error, the error message is saved in the interp result
 *-----------------------------------------------------------------------------
 */
static
TclResCode mudoc_open(Tcl_Interp* interp, MUDoc* mudoc, const char* filename) {
	fz_context* ctx = mudoc->ctx;  // just an alias
	fz_var(mudoc);
	fz_try(ctx) {
		fz_register_document_handlers(ctx);
		mudoc->doc = pdf_open_document(ctx, filename);
		 // be careful: since pdf_needs_password() resets the key,
		 //  it cannot made public, or you may open a pdf with a password (successfullY)
		 //  and then if you recall pdf_needs_password(), the key will be reset !        
		if (pdf_needs_password(ctx,mudoc->doc)) {
			mudoc->authentication = AUTH_FAILED;
			 // even with AUTH_FAILED, doc could be 'unlocked' later with insert_password.
		}
		mudoc->fullname = xTcl_FSGetNormalizedPath(filename);
		mudoc->originalSavedTime = xTcl_GetModificationTime(interp,filename);
	} fz_catch(ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
		return TCL_ERROR;
	}
	return TCL_OK;
} 
 // CONSTRUCTOR:
 // Tcl usage::
 //   mupdf::Doc new filename
 //   mupdf::Doc create name filename
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 != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "_filename_");
		return TCL_ERROR;
	}
	char *filename = Tcl_GetString(objv[0]);
	
	MUDoc* muDoc = MU_new(interp);
	if (!muDoc) {
		Tcl_SetObjResult(interp,Tcl_NewStringObj("mupdf::Doc alloc failed.",-1));
		return TCL_ERROR;
	}
	
	if ( mudoc_open(interp, muDoc, filename) != TCL_OK ) {
		MU_destroy(muDoc);
		return TCL_ERROR;
	}
	
	// save data in object's metadata
	Tcl_ObjectSetMetadata(thisObject, &MY_MetadataType, (ClientData) muDoc);
	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(warnings)
DEF_METHODTYPE(resetwarnings)
DEF_METHODTYPE(wasrepaired)

DEF_METHODTYPE(version)
DEF_METHODTYPE(fullname)
DEF_METHODTYPE(authentication)
DEF_METHODTYPE(opwd)
DEF_METHODTYPE(upwd)
DEF_METHODTYPE(_insertpassword)
DEF_METHODTYPE(npages)

DEF_METHODTYPE(haschanges)
    // just for testing, then remove ...
    DEF_METHODTYPE(canbesavedincrementally)
DEF_METHODTYPE(export)

DEF_METHODTYPE(fields)
DEF_METHODTYPE(signatures)
DEF_METHODTYPE(addsigfield)
DEF_METHODTYPE(field)
DEF_METHODTYPE(flatten)
DEF_METHODTYPE(fieldattrib)

DEF_METHODTYPE(grafts)
DEF_METHODTYPE(graft)
DEF_METHODTYPE(_RemoveGraftMap)
DEF_METHODTYPE(embed)

DEF_METHODTYPE(portfolio)

DEF_METHODTYPE(anchor)

 // =====  method: version =====================================================

     /*
    ** Tcl Usage:   
    **  $docObj version
    **  
    ** return the document's internal PDF-version.
    */
static
TclResCode Method_version( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *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_ObjPrintf(
				"%d.%d",
				self->doc->version/10 , self->doc->version%10 
			));
	
	return TCL_OK;
}

 // =====  method: fullname ===================================================

     /*
    ** Tcl Usage:   
    **  $docObj fullname
    **  
    ** return the fully normalized pathname of the pdf-file.
    */
static
TclResCode Method_fullname( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *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_NewStringObj(self->fullname,-1));
	return TCL_OK;
}

 // =====  method: authentication =============================================

     /*
    ** Tcl Usage:   
    **  $docObj authentication
    **  
    ** return the current authentication mode for $docObj.
    ** It may be 
    **   none (no auth required),
    **   user (opened with user's password), 
    ** owner (opened with owner's password).
    */
static
TclResCode Method_authentication( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;

	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL);
		return TCL_ERROR;
    }
 	// //////////////////////////////////////////////////
	Tcl_Obj *resObj;
	switch (self->authentication) {
	 case AUTH_NOT_REQUIRED: { resObj = Tcl_NewStringObj("none",-1); break; }
	 case AUTH_OWNER:        { resObj = Tcl_NewStringObj("owner",-1); break; }
	 case AUTH_USER:         { resObj = Tcl_NewStringObj("user",-1); break; }
	 case AUTH_FAILED:
	 default:                { resObj = Tcl_NewStringObj("failed",-1); break; }
	}
	// ////////////////////////////////////////////////// 
    Tcl_SetObjResult(interp, resObj);
	return TCL_OK;
}

 // =====  method: opwd =======================================================

     /*
    ** Tcl Usage:   
    **  $docObj opwd password|""
    **  
    ** add or change owner-password.
    ** In order to reset the password,set it to "".
    */
static
TclResCode Method_opwd( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;

	if (objc != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "password or \"\"");
		return TCL_ERROR;
    }
    const char* pwd  = Tcl_GetString(objv[0]);   
    free(self->opwd); self->opwd = strdup(pwd);
    self->hasChangedPwd = 1;
	return TCL_OK;
}

 // =====  method: upwd =======================================================
     /*
    ** Tcl Usage:   
    **  $docObj upwd password|""
    **  
    ** add or change user-password.
    ** In order to reset the password,set it to "".
    */
static
TclResCode Method_upwd( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;

	if (objc != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "password or \"\"");
		return TCL_ERROR;
    }
    const char* pwd  = Tcl_GetString(objv[0]);   
    free(self->upwd); self->upwd = strdup(pwd);
    self->hasChangedPwd = 1;
	return TCL_OK;
}

 // =====  method: _insertpassword ============================================
 // this is a not-public method.
 // returns true/false
static
TclResCode Method__insertpassword( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;

	if (objc != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "password");
		return TCL_ERROR;
    }
    const char* password  = Tcl_GetString(objv[0]);   

    // -----------------------------------------------------------------------
	int res;
    // no need for fz_try/catch block ..
    res = pdf_authenticate_password(self->ctx,self->doc,password);
	if (res ==  0)  { self->authentication = AUTH_FAILED;}
	if (res & 0x01) { self->authentication = AUTH_NOT_REQUIRED;}
	 // if user's and owners's password are identical, pdf_authenticate_password
	 // returns 0x06 (user AND owner !).
	 // In this case, just return AUTH_OWNER
	if (res & 0x02) { self->authentication = AUTH_USER;}
	if (res & 0x04) { self->authentication = AUTH_OWNER;}
    int success = (self->authentication != AUTH_FAILED);    
    // -----------------------------------------------------------------------
    
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(success));
	return TCL_OK;
}

 // =====  method: npages ====================================================
     /*
    ** Tcl Usage:   
    **  $docObj npages
    **  
    **  return the number of pages.
    */
static
TclResCode Method_npages( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *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(pdf_count_pages(self->ctx, self->doc)));   
	return TCL_OK;
}

 // =====  method: haschanges =================================================

    // auxiliary for Method_haschanges() ..
static 
int mu_has_changes( MUDoc *self ) {
    // if new upwd/opwd have been set, then it's a change!
    // if pdf had a pswd,then it's internally changed !
    return (
        self->opwd[0] != '\0'  
         ||
        self->upwd[0] != '\0' 
         ||
        self->changesYetExported 
         || 
        self->hasChangedPwd 
         ||
        self->authentication != AUTH_NOT_REQUIRED
         || 
        pdf_has_unsaved_changes(self->ctx, self->doc)   // no need for fz_try/catch
    );
}

    /*
    ** Tcl Usage:   
    **  $docObj haschanges
    **  
    **  return true if the document was changed.
    **   ( including password change)
    */
static
TclResCode Method_haschanges( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *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_NewBooleanObj( mu_has_changes( self )));
	return TCL_OK;
}




 // Auxiliary for exporting files
 /*
  * mu_canbesavedincrementally() :
  * This is a stronger condition than the original one provided by mupdf.
  *  mu_canbesavedincrementally == true
  *  implies
  *   * pdf_can_be_saved_incrementally(self->doc)  (i.e. it's not repaired, .. not having hybrid xrefs)
  *   * self->fullname exists and it has not been modified.
  *
  * NOTE: 
  * On crypted files,
  *  before muPDF 1.16, pdf_can_be_saved_incrementally() returned  FALSE
  *  after  muPDF 1.16  -- returns TRUE (but only if pwds weren't changed)
  */
static
int mu_canbesavedincrementally(Tcl_Interp* interp, MUDoc* self) {
     // no need for fz_try/catch
	if( ! pdf_can_be_saved_incrementally(self->ctx, self->doc) ) return 0;
//  ?? hasFlattenedFields ??  still used ??
    if( self->hasFlattenedFields ) return 0;

    if ( self->authentication != AUTH_NOT_REQUIRED || self->hasChangedPwd ) return 0;
                
	Tcl_Obj *pathObj = Tcl_NewStringObj(self->fullname,-1);
	Tcl_IncrRefCount(pathObj);
#ifndef R_OK
	#define R_OK 04
#endif
	int status = Tcl_FSAccess(pathObj, R_OK);
	Tcl_DecrRefCount(pathObj);
	if( status != 0 ) { return 0; }

	if( self->originalSavedTime < xTcl_GetModificationTime(interp, self->fullname) ) {
        return 0; 
    }
	return 1;
}



#if 1
 // =====  method: canbesavedincrementally ====================================

    // just for testing, then remove ..
static
TclResCode Method_canbesavedincrementally( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *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_NewBooleanObj(mu_canbesavedincrementally(interp, self)));
	return TCL_OK;
}
#endif



 // 
 // Method export 
 // Internal Note:
 // target filename should NOT be the self's filename
 // nor another opened pdf.
 //   This MUST be checked by the caller

#if 0
occhio !!!   non mi ricordo quale, ma devi forzare un flag per
  fare in modo che alla chiusura venga ricreata la appearance dei widget !!!!
 (? ma serveancora dato che comunque passo gia io con pdf_update_page ?)
 diciamo che forzare questo flag risulta molto pesante, dato che riapre e aggiorna *tutte* le pagine !
 quindi meglio se lo faccio io esplicitamente quando tocco i fields .. o iwidgets ...
   .. o ci sono altri casi in cui sarebbe necessario forzare ?)
#endif

static
TclResCode Method_export( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "filename");
		return TCL_ERROR;
	}
	char *filename = Tcl_GetString(objv[0]);
	
	pdf_write_options pdf_opts = pdf_default_write_options;
	// warning DONT'T do  pdf_opts.do_sanitize = 1 ; ... crash ...
	
	// ---  set encrypt options
	if (self->authentication == AUTH_NOT_REQUIRED) {
		if ( self->opwd[0] == '\0'  &&  self->upwd[0] == '\0' ) {
			pdf_opts.do_encrypt = PDF_ENCRYPT_KEEP; // unchanged encryption
		
		} else {
		 // o/upwd_utf8 are fixed arrays of 128bytes;
		 //  ; use  (sizeof(..opwd_utf8))
			strncpy(pdf_opts.opwd_utf8,self->opwd,sizeof(pdf_opts.opwd_utf8));
			strncpy(pdf_opts.upwd_utf8,self->upwd,sizeof(pdf_opts.upwd_utf8)); 
			pdf_opts.do_encrypt = PDF_ENCRYPT_RC4_128;
		}
	} else {
		// original pdf had a pswd
		if ( self->hasChangedPwd ) {
			if ( self->opwd[0] == '\0'  &&  self->upwd[0] == '\0' ) {
				pdf_opts.do_encrypt = PDF_ENCRYPT_NONE; // removed encryption
			} else {
				strncpy(pdf_opts.opwd_utf8,self->opwd,sizeof(pdf_opts.opwd_utf8));
				strncpy(pdf_opts.upwd_utf8,self->upwd,sizeof(pdf_opts.upwd_utf8)); 
				pdf_opts.do_encrypt = PDF_ENCRYPT_RC4_128;
			}
		} else {
			pdf_opts.do_encrypt = PDF_ENCRYPT_KEEP;  // unchanged encrypt
		}
	}
	// -- check if pdf can be saved incrementally.
	pdf_opts.do_incremental=0;
	if ( mu_canbesavedincrementally(interp, self) ) {
		pdf_opts.do_incremental=1;
	}
	
	// == now,  DO save !! --------------------------------------
	fz_context* ctx = self->ctx;
	fz_try(ctx) {
		int hadChanges = mu_has_changes(self);
		
		// WARNING: in same cases the saved pdf could be entirelly reorganized
		//  see   sample/Form-Duplicated1.pdf   even in incremental mode.
		// For this reason the cache should be reset !
		reset_formfield_cache(self);
		 // no more need to update fields/annotations appearance;
		 // they have been already updated
		pdf_opts.do_appearance = 0;
		if ( pdf_opts.do_incremental )
			pdf_save_snapshot(ctx,self->doc,filename);
		else
			pdf_save_document(ctx,self->doc,filename,&pdf_opts);
		
		if( hadChanges )
			self->changesYetExported = 1;
		} fz_catch(ctx) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
			return TCL_ERROR;
		}
		return TCL_OK;
}


 // =====  method: fields =====================================================

    // .. auxiliary functions for method field

 /*
 ** mu_fieldObj_value() is a replacement for pdf_field_value().
 **    pdf_field_value() doesn't work for listboxes, signatures, and other
 **     special widgets ...
 **
 ** The following implementation passes all tests so far.
 **
 **
 **  Its /V entry (if exists) may be:
 **  * a string
 **  * a stream
 **  * a name              (for checkbox and radiobutton)
 **  * an array of strings (for combobox and listbox)
 **  * a dictionary  (just for signature fields)
 **
 **  result is returned as a Tcl-object of type String or a list of Strings.
 **  (or NULL on error)
 ** Note that Tcl-Strings are able to contain NULL characters 
 */
static
Tcl_Obj *mu_fieldObj_value(fz_context* ctx, pdf_obj* field) {
	Tcl_Obj *res = NULL;
	fz_var(res);
	fz_try(ctx) {
		pdf_obj *valueObj = pdf_dict_get_inheritable(ctx, field, PDF_NAME(V));
		
		if( valueObj == NULL ) {
			res = Tcl_NewStringObj("",-1);
		
		} else if ( pdf_is_string(ctx,valueObj) || pdf_is_stream(ctx,valueObj) ) {
			char *buf = pdf_load_stream_or_string_as_utf8(ctx, valueObj);
			res = Tcl_NewStringObj(buf,-1);
			fz_free(ctx,buf);
		
		} else if ( pdf_is_name(ctx,valueObj) ) {
			const char *pName = pdf_to_name(ctx,valueObj);
			res = Tcl_NewByteArrayObj( (const unsigned char *)pName, (int)strlen(pName) ); // !! implicitely convert to utf-8...
			// why the hell ?? a pdf-name may be utf8 ??
		} else if ( pdf_is_array(ctx,valueObj) ) {
			int nElem = pdf_array_len(ctx, valueObj);
			res = Tcl_NewListObj(0,NULL);
			for (int i = 0; i < nElem; i++) {
				pdf_obj *elemObj = pdf_array_get(ctx, valueObj, i);  // it must be a string !
				
				int len = (int)pdf_to_str_len(ctx, elemObj);
				char *buf = pdf_to_str_buf(ctx, elemObj);
				Tcl_ListObjAppendElement(NULL, res, Tcl_NewByteArrayObj((const unsigned char *)buf,len));  // ??? implicitely convert to utf-8...
			}
			
		} else if ( pdf_is_dict(ctx,valueObj) ) {
			if ( strcmp( xpdf_fieldObj_typename(ctx, field),"signature" ) == 0 ) {
				res = Tcl_NewStringObj( "<<signature>>", -1 );
			} else {
				res = Tcl_NewStringObj( "<<dictionary>>", -1 );
			}
			
		} else {
			res = Tcl_NewStringObj( "?WTF?", -1 );
		}
	} fz_catch(ctx) {
		return NULL;
	}	
	return res;
}

	// get the CollectionOf_xFormFields from the self->cache
	//  or build it on fly.
static
CollectionOf_xFormFields* getFieldMap(MUDoc* self) {
	if( self->cache.fieldMap == NULL ) {
		CollectionOf_xFormFields *map;
		map = CollectionOf_xFormFields_New(xFormField_Destroy);
		if (Build_CollectionOf_xFormFields(self->ctx, self->doc, map) == -1) {
			CollectionOf_xFormFields_Destroy(map);
			return NULL;  // ?? what can I do ??
		}
		self->cache.fieldMap = map;
	}
	return self->cache.fieldMap;
}


 // mu_list_fields() returns a new Tcl-list made of lists of 3 items:
 //    fieldfullname   (a string)
 //    typename        (a string)
 //    value           (a string or a list of strings)
 // on error return NULL (with error message in interp's result)

  //  if onlySignatures, return only data about signature fields
static
Tcl_Obj* mu_list_fields( Tcl_Interp*interp, MUDoc* self, int onlySignatures, int onlyNames ) {
    Tcl_Obj *listPtr = Tcl_NewListObj(0,NULL);

    CollectionOf_xFormFields* const mapOfFields = getFieldMap(self);
	if ( mapOfFields == NULL ) {
		return listPtr;  // empty list
	}

     // things are a little bit complicated because mapOfFields may contain
     //  duplicated fieldnames (fields with more than one widget), 
     //  and we must list fieldnames without duplicates ! 
     
    CollectionCursor cursor;
    xErrorCode xresCode;
    int i;
	for (
        i =0 , xresCode = CollectionOf_xFormFields_First(mapOfFields,&cursor);
        xresCode == 0;
        ++i, xresCode = CollectionOf_xFormFields_Next(&cursor)
        ) {         
		xFormField* pxFormField = CollectionOf_xFormFields_Value(&cursor);

		int isDuplicated = 0;
        CollectionCursor cursor2;
        xErrorCode xresCode2;
    	int j;
        for (
            j =0 , xresCode2 = CollectionOf_xFormFields_First(mapOfFields,&cursor2);
            j < i ;
            ++j, xresCode2 = CollectionOf_xFormFields_Next(&cursor2)
            ) {
    		xFormField* pxFormField2 = CollectionOf_xFormFields_Value(&cursor2);
                     
			if( strcmp(pxFormField2->fieldname, pxFormField->fieldname) == 0 ) {
				isDuplicated = 1;
				break;
			}
		}
		if( isDuplicated ) continue;  // skip it

    	pdf_obj *field = pxFormField->fieldObj;		
        const char *fieldname = pxFormField->fieldname;

        if (onlySignatures) {
            if ( pdf_field_type(self->ctx,field) == PDF_WIDGET_TYPE_SIGNATURE ) {
                if (onlyNames) {
                    Tcl_ListObjAppendElement(interp, listPtr, mu_fieldObj_value(self->ctx,field));
                } else {                
                    Tcl_Obj *sublistPtr = Tcl_NewListObj(0,NULL);
                    Tcl_ListObjAppendElement(interp, sublistPtr, Tcl_NewStringObj(fieldname,-1));
                    Tcl_ListObjAppendElement(interp, sublistPtr, mu_fieldObj_value(self->ctx,field));
                    Tcl_ListObjAppendElement(interp, listPtr, sublistPtr);
                }
            }
            // else .. do nothing
        } else {
            if (onlyNames) {
                Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(fieldname,-1));
            } else {
                Tcl_Obj *sublistPtr = Tcl_NewListObj(0,NULL);
                Tcl_ListObjAppendElement(interp, sublistPtr, Tcl_NewStringObj(fieldname,-1));
                const char *typename = xpdf_fieldObj_typename(self->ctx,field);
                Tcl_ListObjAppendElement(interp, sublistPtr, Tcl_NewStringObj(typename,-1));        
                Tcl_ListObjAppendElement(interp, sublistPtr, mu_fieldObj_value(self->ctx,field));
                Tcl_ListObjAppendElement(interp, listPtr, sublistPtr);
            }
        }
	}
	return listPtr;
}

    /*
    ** Tcl Usage:   
    **  $docObj fields
    **  $docObj fields names
    */
static
TclResCode Method_fields( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc > 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "?names?");
		return TCL_ERROR;
	}
	int onlyNames = 0;
	if ( objc == 1 ) {
		const char* op = Tcl_GetString(objv[0]);
		if (strcmp(op,"names")==0 ) {
			onlyNames = 1;
		} else {
			Tcl_SetObjResult(interp, Tcl_ObjPrintf("unrecognized option \"%s\", Valid options are: names",op));
			return TCL_ERROR;
		}
	}
	// ////////////////////////////////////////////////// 
	Tcl_Obj *listPtr = mu_list_fields(interp, self, 0, onlyNames);
	// ////////////////////////////////////////////////// 
	if ( listPtr == NULL ) {
		return TCL_ERROR;	
	}
	Tcl_SetObjResult(interp,listPtr);
	return TCL_OK;
}

 // =====  method: signatures ================================================

    /*
    ** Tcl Usage:   
    **  $docObj signatures
    **  $docObj signatures names
    */
static
TclResCode Method_signatures( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;
    
    if (objc > 1) {
        Tcl_WrongNumArgs(interp, skip, objv-skip, "?names?");
        return TCL_ERROR;
    }
    int onlyNames = 0;
    if ( objc == 1 ) {
        const char* op = Tcl_GetString(objv[0]);
        if (strcmp(op,"names")==0 ) {
            onlyNames = 1;
        } else {
            Tcl_SetObjResult(interp, Tcl_ObjPrintf("unrecognized option \"%s\", Valid options are: names",op));
            return TCL_ERROR;
        }
    }
	// ////////////////////////////////////////////////// 
	Tcl_Obj *listPtr = mu_list_fields(interp, self, 1, onlyNames);
	// ////////////////////////////////////////////////// 
	if ( listPtr == NULL ) {
		return TCL_ERROR;	
	}	    
    Tcl_SetObjResult(interp,listPtr);
    return TCL_OK;
}

 // =====  method: addsigfield ================================================
	/*
	** Tcl Usage:   
	**  $docObj addsigfield $pageObj fieldname x0 y0 x1 y1
	*/
static
TclResCode Method_addsigfield( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 6) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "_fieldname_ _pageNum_ x0 y0 x1 y1" );
		return TCL_ERROR;
	}
	
	char *fieldname = Tcl_GetString(objv[0]);
	int pageNum;
	if (Tcl_GetIntFromObj(interp, objv[1],&pageNum) != TCL_OK) {
		return TCL_ERROR;
	}
	pdf_page* page=NULL;
	fz_try(self->ctx) {
		page = pdf_load_page(self->ctx, self->doc, pageNum);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR;
	}
	
	TclResCode resCode = TCL_OK;
	double x;
	fz_rect rect;
	if( Tcl_GetDoubleFromObj(interp,objv[2],&x) == TCL_ERROR ) {
		resCode = TCL_ERROR;
		goto endOfFun;
	}
	rect.x0 = x;
	if( Tcl_GetDoubleFromObj(interp,objv[3],&x) == TCL_ERROR ) {
	resCode = TCL_ERROR;
	goto endOfFun;
	}
	rect.y0 = x;
	if( Tcl_GetDoubleFromObj(interp,objv[4],&x) == TCL_ERROR ) {
	resCode = TCL_ERROR;
	goto endOfFun;
	}
	rect.x1 = x;
	if( Tcl_GetDoubleFromObj(interp,objv[5],&x) == TCL_ERROR ) {
		resCode = TCL_ERROR;
		goto endOfFun;
	}
	rect.y1 = x;
	
	pdf_annot* annot = NULL;
	fz_var(annot);
	fz_try(self->ctx) {
		annot = xpdf_addsigfield(self->ctx, page, fieldname, rect);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));    
		resCode = TCL_ERROR;
		goto endOfFun;
	}
	
	// == don't reset the cache ; update it ...
	pdf_obj* annot_obj = pdf_annot_obj(self->ctx,annot);
	if ( self->cache.fieldMap ) {
		xFormField* pxFormField = xFormField_New(self->ctx, fieldname, annot_obj);
		CollectionOf_xFormFields_Add(self->cache.fieldMap, pxFormField);
	}
	if ( self->cache.widgetMap ) {
		xFormWidget* pxWidget = xFormWidget_New(self->ctx,annot_obj,annot_obj, pageNum);
		CollectionOf_xFormWidgets_Add(self->cache.widgetMap, pxWidget);
	}
endOfFun:
	if (page) fz_drop_page(self->ctx, (fz_page*)page);
	
	return resCode;
}


 // =====  method: field ======================================================

    // .. some auxiliary functions ...
    
 // return the field value as a new TclString (or a list of TclStrings)
 //  or null on error
static
Tcl_Obj * mu_get_field(Tcl_Interp* interp, MUDoc *self, const char* fullname) {
	pdf_obj *fieldObj = xpdf_fieldObj_get( self->ctx, self->doc, fullname );
	if (!fieldObj) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown field \"%s\"",fullname));
		return NULL;
	}
	return mu_fieldObj_value(self->ctx, fieldObj);
}

    // get the CollectionOf_xFormWidgets from the self->cache
    //  or build it on fly.
static
CollectionOf_xFormWidgets* getWidgetMap(MUDoc* self) {
	if( self->cache.widgetMap == NULL ) {
        CollectionOf_xFormWidgets *map;
        map = CollectionOf_xFormWidgets_New(xFormWidget_Destroy);
		if (Build_CollectionOf_xFormWidgets(self->ctx, self->doc, map) == -1) {
            CollectionOf_xFormWidgets_Destroy(map);
            return NULL;  // ?? what can I do ??
        }
        self->cache.widgetMap = map;
	}
	return self->cache.widgetMap;
}

#include "xCollection.h"
typedef size_t Int; // ..so that its sizeof is equal to sizeof(void *)
DECLARE_COLLECTION_OF(Int,Int)
DEFINE_COLLECTION_OF(Int,Int)

 /*
 **  callback for CollectionOf_Int_Match
 */
int pagenum_Match(const Int num, const void* _numkey) {
    Int numkey = (Int)_numkey;
    return (num == numkey);
}

static
TclResCode mu_set_field(Tcl_Interp* interp, MUDoc *self, const char *fullname, const char *valueStr) {
	fz_context *ctx = self->ctx;  // just an alias
    /*
    ** Summary:
    ** This is rather complicated, because:
    **  a) a given fieldname may appear more times in the field-table
    **  b) a field may have more related widgets on the same or different pages
    **  c) loading/dropping a page for updating the widgets is an expensive operation.
    ** For these reasons (and especially (c)), first we update all the fieldObjs
    ** related to fieldname and at the same time collect all the involved
    ** widgets, or better the involved pages, storing these pages in a CollectionOf_Int
    ** (i.e a collection of page numbers - without duplicates).
    ** Finally, for each page-number in this collection of page-numbers,
    **   we load the page, update it and drop it.
    ** Done.
    ** As a final note, the already opened pages should be 'refreshed' but this
    **  is a job for the caller (i.e. the tcl-layer)
    */
	
	CollectionOf_xFormFields* mapOfFields;
	CollectionOf_xFormWidgets* mapOfWidgets;
	CollectionOf_Int* involvedPages = CollectionOf_Int_New(NULL);
    int fieldFound = 0;
    
	fz_var(involvedPages);
	fz_try(ctx) {
		
		mapOfFields = getFieldMap(self);
		mapOfWidgets = getWidgetMap(self);

        CollectionCursor cursor;
        xErrorCode xresCode;
    	for (
            xresCode = CollectionOf_xFormFields_First(mapOfFields,&cursor);
            xresCode == 0;
            xresCode = CollectionOf_xFormFields_Next(&cursor)
            ) {         
    		xFormField* pxFormField = CollectionOf_xFormFields_Value(&cursor);

			if( strcmp(fullname, pxFormField->fieldname) == 0 ) {
                fieldFound = 1;    
                // ?? maybe the last param should be 1
				pdf_set_field_value(ctx,self->doc, pxFormField->fieldObj, valueStr, 0);
				// take note of all the pages where fieldObj has one or more widgets
                CollectionCursor cursor2;
                xErrorCode xresCode2;
            	for (
                    xresCode2 = CollectionOf_xFormWidgets_First(mapOfWidgets,&cursor2);
                    xresCode2 == 0 ;
                    xresCode2 = CollectionOf_xFormWidgets_Next(&cursor2)
                    ) {         
            		xFormWidget* pxWidget = CollectionOf_xFormWidgets_Value(&cursor2);
					if ( pdf_objcmp(ctx, pxWidget->fieldObj, pxFormField->fieldObj) == 0 ) {
                        // only add the pagenumber if not present yet !
                        int pageNumber = pxWidget->pageNumber;
                        if ( !CollectionOf_Int_Match(involvedPages, pagenum_Match, (Int)pageNumber, NULL) ) {
                            CollectionOf_Int_Add(involvedPages, pageNumber);                        
                        }
					}					
				}
			}
		}

		 // update all the involved pages (rebuild widgets apeearances ..)	
    	for (
            xresCode = CollectionOf_Int_First(involvedPages,&cursor);
            xresCode == 0 ;
            xresCode = CollectionOf_Int_Next(&cursor)
            ) {         
    		int pageNum = CollectionOf_Int_Value(&cursor);
			pdf_page *page;

			page = pdf_load_page(ctx,self->doc,pageNum);
			pdf_update_page(ctx,page);
			fz_drop_page(ctx, (fz_page *)page);

		}
	} fz_always( ctx ) {
		CollectionOf_Int_Destroy(involvedPages);
	} fz_catch( ctx ) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));	
        return TCL_ERROR;
	}
    if( !fieldFound ) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown field \"%s\"", fullname));
        return TCL_ERROR;
    }
    return TCL_OK;
}

     /*
    ** Tcl Usage:   
    **  $docObj field _fieldname_
    **    return the field value
    **  $docObj field _fieldname_  _newvalue_
    **    set a new value for _fieldname_   ( and updated all its widgets appearances )
    **  
    */
static
TclResCode Method_field( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;
    
    if (objc != 1 && objc != 2) {
        Tcl_WrongNumArgs(interp, skip, objv-skip, "fieldname ?value?" );
        return TCL_ERROR;
    }

    TclResCode resCode = TCL_OK;
    char *fieldname = Tcl_GetString(objv[0]);

	if ( objc == 1 ) {
		// ////////////////////////////////////////////////// 
		Tcl_Obj *val = mu_get_field(interp, self, fieldname);
		// ////////////////////////////////////////////////// 
		if ( ! val ) {
			return TCL_ERROR;	
		}
		Tcl_SetObjResult(interp, val);
        resCode = TCL_OK;
	} else {
		// ////////////////////////////////////////////////// 
		resCode = mu_set_field( interp, self, fieldname, Tcl_GetString(objv[1]));
		// //////////////////////////////////////////////////
	}
    return resCode;
}

 // =====  method: flatten  =================================================
    // .. some preliminary functions ..
    
 /*
 **  callback for CollectionOf_FieldEntry_Match
 */
static
int Fieldname_Match(const xFormField* data, const void* _fieldname) {
    const char* fieldname = _fieldname;
    return strcmp(data->fieldname,fieldname) == 0  ? 1 : 0;
}

 /*
 **  callback for CollectionOf_FieldEntry_Match
 */
static
int FieldObj_Match(const xFormField* data, const void* _fieldObj) {
    pdf_obj* fieldObj = (pdf_obj*)_fieldObj;
    return pdf_objcmp(data->ctx, data->fieldObj, fieldObj) == 0 ? 1 : 0;
}

 /*
 **  callback for CollectionOf_WidgetEntry_Match
 */
static
int WidgetObj_Match(const xFormWidget* data, const void* _widgetObj) {
    pdf_obj* widgetObj = (pdf_obj*)_widgetObj;
    return pdf_objcmp(data->ctx, data->widgetObj, widgetObj) == 0 ? 1 : 0;
}

 // callback for qsort()
static
int PageNumberSort(const void* _pRec1, const void* _pRec2 ) {
    const xFormWidget** pRec1 = (const xFormWidget**)_pRec1; 
    const xFormWidget** pRec2 = (const xFormWidget**)_pRec2;
    if ((*pRec1)->pageNumber > (*pRec2)->pageNumber) return 1;
    if ((*pRec1)->pageNumber < (*pRec2)->pageNumber) return -1;
    return 0;
}

	 /*
	** Tcl Usage:   
	**  $docObj flatten fieldname ?fieldname ...?
	**
	** Note: it's not an errorif a given fieldname is listed more than one time
	*/
static
TclResCode Method_flatten( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc < 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "fieldname ..." );
		return TCL_ERROR;
	}
	
	const CollectionOf_xFormFields* mapOfFields = getFieldMap(self);
	
	// check if input fields are valid ...
	// it's not an error if fieldnames are duplicated  ( they will be ignored ..)
	for( int i =0 ; i < objc ; ++i ) {
		const char* fieldname = Tcl_GetString(objv[i]);
		if ( CollectionOf_xFormFields_Match(mapOfFields,Fieldname_Match, fieldname, NULL) == 0 ) {
			Tcl_SetObjResult(interp, Tcl_ObjPrintf("unrecognized field-name \"%s\"", fieldname));
			return TCL_ERROR;
		}
	}
	// ASSERT: all input field names are valid (but be aware of duplicated fieldnames)
	
	/*
	** Preamble: the heavy processing cost is due to the opening of all the  pdf_page(s)
	**  containing the involved widgets.
	** If we process on a field by field basis, we could have the same pdf_page opened/processed/closed
	** more than once, because a field may have more than one widget, on the same or
	** different pages.
	** In order to minimize these heavy operations, we choose to collect all the
	** involved widgets (from all the involved fields) and sort them by page-numbers.
	** In this way, we may open a given pdf_page just once and the process (flatten) all the involved widgets in that page.
	*/ 
	
	const CollectionOf_xFormWidgets* mapOfWidgets = getWidgetMap(self);
	
	int N  = CollectionOf_xFormWidgets_Size(mapOfWidgets);
	
	xFormWidget** InvolvedWidgetArray = NULL;
	InvolvedWidgetArray = malloc(N*sizeof(xFormWidget*));
	if ( InvolvedWidgetArray == NULL ) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("Internal error - no more memory",-1));
		return TCL_ERROR;
	}
	int nInvolvedWidgets = 0;
	
	
	// Step 1
	// Collect all the involved widgets in InvolvedWidgetArray.
	// Sort that array by widget's pageNumber.
	// WARNING:: InvolvedWidgetArray contains (weak) references to entries of
	//   mapOfWidgets. Therefore they becomes invalid when entries of mapOfWidgets
	//   are removed !!
	{
		CollectionCursor widgetCursor;
		xErrorCode xresCode;
		for (
			xresCode = CollectionOf_xFormWidgets_First(mapOfWidgets,&widgetCursor);
			xresCode == 0 ;
			xresCode = CollectionOf_xFormWidgets_Next(&widgetCursor)
		) {
			// get the associated field, then its fieldname
			xFormWidget *w = CollectionOf_xFormWidgets_Value(&widgetCursor);
			CollectionCursor fieldCursor;
			if (CollectionOf_xFormFields_Match(mapOfFields,FieldObj_Match,w->fieldObj,&fieldCursor)) {
				xFormField *f = CollectionOf_xFormFields_Value(&fieldCursor);
				// is f->fieldname among the input fields ( objv[] )? 
				int found = 0;
				for (int i=0; i<objc ; ++i) {
					if (strcmp(f->fieldname,Tcl_GetString(objv[i])) == 0 ) {
						found = 1;
						break;
					}
				}
				if (found) {
					// Warning: this is a weak reference !
					InvolvedWidgetArray[nInvolvedWidgets++] = w;
				}
			}
		}
		/* sort by pageNumber */
		qsort(InvolvedWidgetArray, (size_t)nInvolvedWidgets, sizeof(xFormWidget*), PageNumberSort);
	}
	
	/* Step2 - flatten all involved widgets
	** Pay attention: some pdf_ calls may raise exceptions..
	**  be careful to leave the document in a consistent state even after these
	**  exceptions ...
	** 
	*/
	fz_try (self->ctx) {
		int i=0;
		while (i<nInvolvedWidgets) {
			int pageGroup = InvolvedWidgetArray[i]->pageNumber;
			
			pdf_page* page = pdf_load_page(self->ctx,self->doc,pageGroup);
		//  ?? exceptions ?
			if (!page)  break;   // weird !!!
			// prepare another fz_try for pdf_page_drop
			fz_try (self->ctx) {  
				do { // involvedWidgetArray partial loop
					pdf_annot* widget = xpdf_widgetObj_to_widget(self->ctx, page, InvolvedWidgetArray[i]->widgetObj);
					if (!widget) {  // this is unexpected. Better to skip !
						++i;
						continue;   // jump to do-while condition -------------
					}
					xpdf_annot_flatten(self->ctx, widget);
					
					// BUGFIX: when xFormWidget is removed from its Collection.
					// the involvedWidgetArray entry (pointing to xFormWidget)
					// becomes invalid !
					// Therefore BEFORE removing xFormWidgets, we must take 
					//  a copy of the xWidget->fieldObj to be used later !
					pdf_obj* savedFieldObj = InvolvedWidgetArray[i]->fieldObj;
					pdf_keep_obj(self->ctx,savedFieldObj);
					
					// once flattened, remove it from mapOfWidgets
					{
						CollectionCursor wCursor;
						pdf_obj* widgetObj = InvolvedWidgetArray[i]->widgetObj;
						if ( CollectionOf_xFormWidgets_Match(mapOfWidgets, WidgetObj_Match, widgetObj, &wCursor) ) {
							CollectionOf_xFormWidgets_RemoveAt(&wCursor);
						}
					}
					// NOTE: from now, involvedWidgetArray[i] is invalid !
					
					// remove the related field from the doc's field table
					//  and from the mapOfFields.
					// ... here the job is a little bit complicated ...
					
					// get the the related field's *fieldname*
					CollectionCursor fieldCursor;
					if (CollectionOf_xFormFields_Match(mapOfFields, FieldObj_Match, savedFieldObj, &fieldCursor)) {
						xFormField *f = CollectionOf_xFormFields_Value(&fieldCursor);
						// before removing the xFormField (f) from the mapOfFields,
						// make a deep-copy of f->fieldname
						char *saved_fieldname = strdup(f->fieldname);
						do {
							// re-evaluate at fieldCursor  ..
							//  this is un-necessary at the first iteration,
							//  but it's vital for the next iterations
							f = CollectionOf_xFormFields_Value(&fieldCursor);
							
							pdf_obj* fieldObj = f->fieldObj;
							// remove field from doc's field-table;
							xpdf_fieldObj_delete(self->ctx, self->doc, fieldObj);
							// remove field from mapOfFields
							CollectionOf_xFormFields_RemoveAt(&fieldCursor);
						} while ( CollectionOf_xFormFields_Match(mapOfFields, Fieldname_Match, saved_fieldname, &fieldCursor) );
						free(saved_fieldname);
					}
					pdf_drop_obj(self->ctx, savedFieldObj);
					++i;
				} while (i<nInvolvedWidgets && InvolvedWidgetArray[i]->pageNumber == pageGroup);
				pdf_update_page(self->ctx,page);
			} fz_always (self->ctx) {
				fz_drop_page(self->ctx, (fz_page*)page);
			} fz_catch (self->ctx) {
				fz_rethrow(self->ctx);
			}
		} // end of while-loop
	} fz_always (self->ctx) {
		free(InvolvedWidgetArray);
	} fz_catch (self->ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR;
	}
	return TCL_OK;
}


 // =====  method: fieldattrib ================================================

    // some auxiliary functions ...

static
TclResCode mu_get_fieldflags( Tcl_Interp* interp, MUDoc *self, const char *fieldname, int* flags ) {
	fz_try(self->ctx) {
		pdf_obj *field;
		field = xpdf_fieldObj_get( self->ctx, self->doc, fieldname );
		if (!field) {
			fz_throw( self->ctx, FZ_ERROR_GENERIC, "unknown field");
			// .. jump to fz_catch() section..
		}
		*flags = pdf_field_flags(self->ctx, field);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult( interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR; 
	}
	return TCL_OK;
}


static
TclResCode mu_set_fieldflags(Tcl_Interp*interp, MUDoc *self, const char *fieldname, unsigned int bitsTurnedOn, unsigned int bitsTurnedOff) {
	fz_context *ctx = self->ctx;  // just an alias
	fz_try(ctx) {
		CollectionOf_xFormFields* mapOfFields;
		pdf_obj *field;
		int flags;

		// BE CAREFUL; a given fieldname may have multiple occurrences in Acroform/Fields.
		//  These occurrences SHOULD have the same values.
		//  Therefore it's safe to get the value from the 1st occurrence,
		//  but then, you should update all the occurrences.
		 
		field = xpdf_fieldObj_get( ctx, self->doc, fieldname );
		if (!field) {
			fz_throw( ctx, FZ_ERROR_GENERIC, "unknown field");
			// .. jump to fz_catch() section..
		}

		flags = pdf_field_flags(ctx, field);
        flags &= ~bitsTurnedOff;
		flags |= bitsTurnedOn;
		
		mapOfFields = getFieldMap(self);
        CollectionCursor cursor;
        xErrorCode xresCode;
        for (
            xresCode = CollectionOf_xFormFields_First(mapOfFields,&cursor);
            xresCode == 0 ;
            xresCode = CollectionOf_xFormFields_Next(&cursor)
            ) {
    		xFormField* pxFormField = CollectionOf_xFormFields_Value(&cursor);
        
			if(strcmp(pxFormField->fieldname, fieldname)==0) {
				pdf_dict_put_int( ctx, pxFormField->fieldObj, PDF_NAME(Ff), flags);				
			}
		}				
		
	} fz_catch(ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
		return TCL_ERROR;;
	}
    return TCL_OK;
}
     /*
    ** Tcl Usage:   
    **  $docObj fieldattrib _fieldname_
    **  $docObj fieldattrib _fieldname_  _option_ _value_ ? _option_ _value_ ...?
    */
static
TclResCode Method_fieldattrib( METHOD_ARGS ) {
    int skip = Tcl_ObjectContextSkippedArgs(objectContext);
    MUDoc *self = MY_Metadata(objectContext);
    objc -= skip;
    objv += skip;
    
    if (objc < 1) {
        Tcl_WrongNumArgs(interp, skip, objv-skip, "fieldname ?-readonly bool?" );
        return TCL_ERROR;
    }

    char *fieldname = Tcl_GetString(objv[0]);

	if ( objc == 1 ) {
		// get values
		// //////////////////////////////////////////////////
         // curently only option "-readonly" is implemented 
		int bitflags;
        if( mu_get_fieldflags( interp, self, fieldname, &bitflags ) != TCL_OK ) {
			return TCL_ERROR;	
		}
		// decode values ...
		Tcl_Obj *res = Tcl_NewListObj(0,NULL);
		// decode values ...  currently only BIT_READONLY
		Tcl_ListObjAppendElement(interp,res, Tcl_NewStringObj("-readonly",-1));
		Tcl_ListObjAppendElement(interp,res, Tcl_NewBooleanObj(bitflags & PDF_FIELD_IS_READ_ONLY));

		Tcl_SetObjResult(interp, res);
		
	} else {
		// set values
		unsigned int bitsTurnedOn =  0x0000;  // 32 bit  (at least)
		unsigned int bitsTurnedOff = 0x0000;  // 32 bit  (at least)
		
		int argIdx = 1;
		while (argIdx < objc) {
			int bitField;
			int boolVal = 0;
			char *arg = Tcl_GetString(objv[argIdx++]);
			// ? -readonly
			if( strcmp(arg,"-readonly") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("\"-readonly\" option requires a boolean value.",-1));
					return TCL_ERROR;
				}
				if( Tcl_GetBooleanFromObj(interp,objv[argIdx++],&boolVal) == TCL_ERROR ) {
					return TCL_ERROR;
				}

				bitField = PDF_FIELD_IS_READ_ONLY;
					
			// ? other ?
			} else {
				Tcl_SetObjResult( interp, Tcl_ObjPrintf( "unrecognized option \"%s\"; valid values are: -readonly",arg) );
				return TCL_ERROR;			
			}

			if (boolVal) {
				bitsTurnedOn |= bitField;
				bitsTurnedOff &=  ~bitField;
			} else {
				bitsTurnedOff |= bitField;
				bitsTurnedOn &=  ~bitField;
			}
									
		}
		// ////////////////////////////////////////////////// 
		if ( mu_set_fieldflags(interp, self, fieldname, bitsTurnedOn, bitsTurnedOff) != TCL_OK ) {
			return TCL_ERROR;	
		}
		// ////////////////////////////////////////////////// 

	}
    return TCL_OK;
}



 // =====  method: grafts =====================================================
static
TclResCode Method_grafts( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, NULL );
		return TCL_ERROR;
	}
	////////////////////////////////////////////////////////	
	Tcl_Obj* listPtr = Tcl_NewListObj(0,NULL);
	CollectionCursor graftCursor;
	xErrorCode xresCode;
	for (
		xresCode = CollectionOf_xGraftedPages_First(self->graftedPages,&graftCursor);
		xresCode == 0 ;
		xresCode = CollectionOf_xGraftedPages_Next(&graftCursor)
	) {
		xGraftedPage* pGraftedPage = CollectionOf_xGraftedPages_Value(&graftCursor);
		Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(pGraftedPage->ID,-1));
	}
	// It may be an empty list but it cannot be NULL
	////////////////////////////////////////////////////////
	Tcl_SetObjResult(interp, listPtr);
	return TCL_OK;
}

 // =====  method: graft ======================================================

 // some auxiliary functions ....

 
 // return the id (a string) of the new graft.
 // on error returns NULL  (error message in interp's result)
static
const char* mu_graft_page(Tcl_Interp* interp, MUDoc* self, pdf_page* page, pdf_graft_map* map) {
	fz_context* ctx = self->ctx ; // just an alias
	const char* graftKey;
	
	fz_try( ctx ) {
		pdf_obj *xobj = NULL;
		
		xobj = xpdf_page_new_deepcopy(ctx, self->doc, page, map);
		// allocate a new block for xGraftedPage and save it in the
		//  self->graftedPages collection
		xGraftedPage* pxGpage;
		char ID[20]; // more than enough
		sprintf(ID, "XObj#%d", (self->graftedPagesSerialNo)++);
		
		pxGpage = xGraftedPage_new(ctx, ID, xobj);
		
		CollectionOf_xGraftedPages_Add(self->graftedPages,pxGpage);
		graftKey = pxGpage->ID;
	} fz_catch(ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(ctx),-1));
		return NULL;	
	}
	return graftKey;
}

static
int xGraftMap_MatchID(const xGraftMap* rec, const  void* _ID) {
	const char* ID = _ID; 
	return (strcmp(rec->ID,ID) == 0 ? 1 : 0);
}

	 /*
	** Tcl Usage:   
	**  $docObj graft $pageObj
	**  Note: the mpdf::Doc wrapper adds a parameter:
	**     the 'grafMaptid'    (a special id related to the source-document)
	*/
static
TclResCode Method_graft( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc < 1 || objc > 2) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "pageObj graftMapID" );
		return TCL_ERROR;
	}
	
	MUPage *fromPage;
	fromPage = xTcl_GetMUPageFromObj(interp, objv[0]);        
	if (fromPage == NULL) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a mupdf::Page", Tcl_GetString(objv[0])));			
		return TCL_ERROR;
	}
	const char* graftMapID = Tcl_GetString(objv[1]);
	
	// Get a (or create a new) graft_map for self->doc.
	// Self->doc may have distinct graft_maps, one for each 'src' doc.
	// It's caller's reponsability to provide a different graftMapID
	// for each source-document  (i.e. the page->doc)
	pdf_graft_map* pdf_gmap = NULL;
	
	{
	 CollectionCursor gmapCursor;
	 if (CollectionOf_xGraftMaps_Match(self->graftMaps, xGraftMap_MatchID, graftMapID, &gmapCursor)) {
	 xGraftMap *p;
		p = CollectionOf_xGraftMaps_Value(&gmapCursor);
		pdf_gmap = p->map;
	 }
	}
	if (!pdf_gmap) {
		// create a new map
		fz_var(pdf_gmap);
		fz_try(self->ctx) {
			pdf_gmap = pdf_new_graft_map(self->ctx, self->doc);
			// I don't trust the exception ...
			if (!pdf_gmap) {
				fz_throw(self->ctx, FZ_ERROR_GENERIC, "fz_malloc error");
			}
		} fz_catch(self->ctx) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("internal error: failed creating a new graft map",-1));
			return TCL_ERROR;
		}
		xGraftMap* p = xGraftMap_new(self->ctx,graftMapID,pdf_gmap);
		CollectionOf_xGraftMaps_Add(self->graftMaps,p);
		pdf_gmap = p->map;
	}
	
	////////////////////////////////////////////////////////	
	pdf_page* page= MUPage_pdf_page(fromPage);
	const char* graftKey = mu_graft_page(interp, self, page, pdf_gmap);
	////////////////////////////////////////////////////////	
	if ( graftKey == NULL ) {
		return TCL_ERROR;
	}
	Tcl_SetObjResult(interp, Tcl_NewStringObj(graftKey,-1) );
	return TCL_OK;
}

 // =====  method: _RemoveGraftMap  (hidden method) ===========================
static
TclResCode Method__RemoveGraftMap( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "_graftMapID_" );
		return TCL_ERROR;
	}
	const char* graftMapID = Tcl_GetString(objv[0]);
	
	CollectionCursor gmapCursor;
	if (CollectionOf_xGraftMaps_Match(self->graftMaps, xGraftMap_MatchID, graftMapID, &gmapCursor)) {
		CollectionOf_xGraftMaps_RemoveAt(&gmapCursor);
	}
	return TCL_OK;
}


 // =====  method: embed ====================================================
 
 // auxiliary functions defined in MUDoc_Embed.c

#include "MUDoc_Embed.h" 

static
int xGraftedPage_MatchID(const xGraftedPage* rec, const void* _ID) {
	const char* ID = _ID;
	return (strcmp(rec->ID,ID) == 0 ? 1 : 0);
}

static
TclResCode Method_embed( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc < 2) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "graftKey pageNum ?-crop {x0 y0 x1 y1}? ?-to {x y}? ?-scale factor? ?-rotate degree? ?-over true/false?"  );
		return TCL_ERROR;
	}
	
	const char* graftKey = Tcl_GetString(objv[0]);
	// check if it's a valid Key
	xGraftedPage* p_graftedPage = NULL;
	CollectionCursor graftCursor;
	if ( CollectionOf_xGraftedPages_Match(self->graftedPages,xGraftedPage_MatchID, graftKey,&graftCursor)) {
		p_graftedPage = CollectionOf_xGraftedPages_Value(&graftCursor);
	}
	if(! p_graftedPage) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("invalid graft-key \"%s\"", graftKey) );
		return TCL_ERROR;	    
	}
	
	int pageNum;
	if (Tcl_GetIntFromObj(interp, objv[1],&pageNum) != TCL_OK) {
		return TCL_ERROR;
	}
	
	struct embedParams param;
		param.over = 1;
		param.zoom = 1.0;
		param.rotate = 0.0;
		param.has_to = 0;
		param.crop = fz_empty_rect;
	
	 // parse all the remaining options (starting from objv[2])
	if ( _parseEmbedPageOptions(interp, objc-2, objv+2, &param) != TCL_OK ) {
		// error message is left in the interpreter's result 
		return TCL_ERROR;		
	}
	
	// in order to check/adust the parameters, we need to load the page..
	TclResCode resCode = TCL_OK;
	pdf_page* toPage=NULL;
	
	fz_try(self->ctx) {
		toPage = pdf_load_page(self->ctx, self->doc, pageNum);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		resCode = TCL_ERROR;
		goto endOfFun;
	}
	
	if ( ! param.has_to ) {
		param.to = xfz_rect_center(pdf_bound_page(self->ctx, toPage));
	}
	if ( fz_is_empty_rect(param.crop) ) {
		param.crop = pdf_xobject_bbox(p_graftedPage->ctx, p_graftedPage->xobj);
	}
	
	////////////////////////////////////////////////////////
	resCode = mu_embed_xobj(interp, self->ctx, self->doc, toPage, p_graftedPage->xobj, param.to, 
		param.crop, param.zoom, param.rotate, param.over);
	////////////////////////////////////////////////////////
	
endOfFun:
	if (toPage) fz_drop_page(self->ctx, (fz_page*)toPage);
	
	return resCode;
}

 
 // =====  method: portfolio  =================================================
#include "MUDoc_Portfolio.h"

	 /*
	** Tcl Usage:   
	**  $docObj portfolio list
	**  $docObj portfolio extract ...
	*/
static
TclResCode Method_portfolio( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc < 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "list|extract ..." );
		return TCL_ERROR;
	}
	
	char *cmd = Tcl_GetString(objv[0]);
	
	if ( strcmp(cmd,"list") == 0 ) {
		// --- portfolio list ------------------------------------------------
		if(objc > 1) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "list" );
			return TCL_ERROR;
		}
		Tcl_Obj* resList = mu_ptf_list(interp, self->ctx, self->doc);
		if ( resList == NULL ) return TCL_ERROR;
		
		Tcl_SetObjResult(interp, resList);
		return TCL_OK;
		
	} else if ( strcmp(cmd,"extract") == 0 ) {
	// --- portfolio extract ----------------------------------------------
		if(objc < 2 ) {
			Tcl_WrongNumArgs(interp, skip, objv-skip, "extract index ?-dir outputdir?  ?-as filename?" );
			return TCL_ERROR;
		}
		
		int idx;
		if( Tcl_GetIntFromObj(interp,objv[1],&idx) == TCL_ERROR ) {
			return TCL_ERROR;
		}
		char *outputDir = NULL;
		char *filename = NULL;
		int argIdx = 2;
		while (argIdx <objc) {
			char *arg = Tcl_GetString(objv[argIdx++]);
			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,"-as") == 0 ) {
				if( argIdx +1 > objc ) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("\"-as\" option requires a filename.",-1));
					return TCL_ERROR;
				}
				filename = Tcl_GetString( objv[argIdx++] );
				// ? other ?
			} else {
				Tcl_SetObjResult(interp, Tcl_NewStringObj("unrecognized option; valid values are: -dir or -as",-1));
				return TCL_ERROR;
			}
		}
		return mu_ptf_extract(interp, self->ctx, self->doc, idx, outputDir, filename);
		
	} else {
		Tcl_SetObjResult(interp, Tcl_NewStringObj( "unknown portfolio subcommand: must be \"list\" or \"extract\".", -1));
		return TCL_ERROR;
	}

}


 // =====  method: anchor ====================================================
	/*
	** Tcl Usage:   
	**  $docObj anchor _name_
	**  
	**  return the location of anchor _name_ as list of 3 numbers:
	**  a page number ( -1 if _name_ is not found )
	**  x displacement
	**  y displacement
	**
	**  x and y are hints for locating the anchor: they represent the displacement
	** from the upper-left corner of the page (0,0).
	*/
static
TclResCode Method_anchor( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;
	
	if (objc != 1) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "name");
		return TCL_ERROR;
	}
	const char* name  = Tcl_GetString(objv[0]);
	
	float x0 = 0.0;
	float y0 = 0.0;
	// //////////////////////////////////////////////////
	int pageNum;
	fz_try(self->ctx) {
		pageNum = pdf_lookup_anchor(self->ctx, self->doc,name,&x0,&y0);
	} fz_catch(self->ctx) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(fz_caught_message(self->ctx),-1));
		return TCL_ERROR;
	}
	// //////////////////////////////////////////////////    
	Tcl_Obj *listPtr = Tcl_NewListObj(0,NULL);
		Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewIntObj(pageNum));
		Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewDoubleObj(x0));
		Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewDoubleObj(y0));
	
	Tcl_SetObjResult(interp, listPtr);   
	return TCL_OK;
}

 /*
 **  $pdfObj warnings       --> returns the warnings for $pdfObj
 */
static
TclResCode Method_warnings( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;

	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "");
		return TCL_ERROR;
	}
	Tcl_SetObjResult(interp, self->warningList);
	return TCL_OK;
}

static
TclResCode Method_resetwarnings( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;

	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "");
		return TCL_ERROR;
	}
	Tcl_DecrRefCount(self->warningList); // this will free the warningList

	self->warningList = Tcl_NewListObj(0,NULL);
	Tcl_IncrRefCount(self->warningList);

	return TCL_OK;
}

 /*
 **  $pdfObj wasrepaired   -> true/false
 */
static
TclResCode Method_wasrepaired( METHOD_ARGS ) {
	int skip = Tcl_ObjectContextSkippedArgs(objectContext);
	MUDoc *self = MY_Metadata(objectContext);
	objc -= skip;
	objv += skip;

	if (objc != 0) {
		Tcl_WrongNumArgs(interp, skip, objv-skip, "");
		return TCL_ERROR;
	}

	Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
		pdf_was_repaired(self->ctx, self->doc)
		));
	return TCL_OK;
}

// ============================================================================
// public functions for the integration with other modules  (MUPage  ...)
// ============================================================================

fz_context* MUDoc_context(MUDoc* mudoc) {
    return (mudoc ? mudoc->ctx : NULL);
}
pdf_document* MUDoc_pdf_document(MUDoc* mudoc)  {
    return (mudoc ? mudoc->doc : NULL);
}
CollectionOf_xGraftedPages* MUDoc_xGraftedPages(MUDoc *mudoc) {
    return mudoc->graftedPages;
}

MUDoc* xTcl_GetMUDocFromObj( 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::Doc",-1));
        return NULL;
    }
    return (MUDoc*)clientData;
}

// ============================================================================
// MUDoc module initialization
// ============================================================================

    // Warning: remember to add a dummy "oo::class create ..."" in mupdf.tcl
TclResCode  MUDoc_Setup(Tcl_Interp *interp) {
	Tcl_Class clazz = tcloox_getClass(interp, "mupdf::Doc_C");
	if (clazz == NULL) { return TCL_ERROR; }
	
	tcloox_registerConstructor(interp, clazz, &MYConstructorType);
	tcloox_registerDestructor (interp, clazz, &MYDestructorType);
	
	const Tcl_MethodType * methods[] = {       
		&MethodType_warnings,
		&MethodType_resetwarnings,
		&MethodType_wasrepaired,	
		  
		&MethodType_version,  
		&MethodType_fullname,
		&MethodType_authentication,
		&MethodType_opwd,  
		&MethodType_upwd,
		&MethodType__insertpassword,
		&MethodType_npages,
		
		&MethodType_haschanges,
			// just for testing, then remove ...
			&MethodType_canbesavedincrementally,
		&MethodType_export,
		
		&MethodType_fields,
		&MethodType_signatures,
		&MethodType_addsigfield,
		&MethodType_field,
		&MethodType_flatten,
		&MethodType_fieldattrib,
		
		&MethodType_grafts,
		&MethodType_graft,
		&MethodType__RemoveGraftMap,  // hidden method
		&MethodType_embed,
		
		&MethodType_portfolio,
		
		&MethodType_anchor,
		
		NULL
	};

	tcloox_registerMethods(interp, clazz, methods);
	return TCL_OK;
}
