/*************************************************************************\
 *                                                                       *
 * docbitmap.c                                                           *
 *                                                                       *
 * Copyright 1999-2000 Emmanuel Lesueur                                  *
 *                                                                       *
\*************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libraries/mui.h>
#include <libraries/iffparse.h>
#include <dos/dostags.h>
#include <exec/nodes.h>
#include <exec/execbase.h>
#ifdef HAVE_CYBERGRAPHICS_CYBERGRAPHICS_H
#   include <cybergraphics/cybergraphics.h>
#elif defined(HAVE_CYBERGRAPHX_CYBERGRAPHICS_H)
#   include <cybergraphx/cybergraphics.h>
#endif
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <devices/inputevent.h>
#define NO_INLINE_STDARG
#include <proto/muimaster.h>
#undef NO_INLINE_STDARG
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#define NO_INLINE_STDARG
#include <proto/intuition.h>
#undef NO_INLINE_STDARG
#include <proto/layers.h>
#include <proto/utility.h>
#if defined(HAVE_CYBERGRAPHICS_CYBERGRAPHICS_H) || \
    defined(HAVE_CYBERGRAPHX_CYBERGRAPHICS_H)
#   include <proto/cybergraphics.h>
#endif
#include <proto/iffparse.h>
#include <proto/keymap.h>
#include "AComm.h"
#include "Apdf.h"
#include "docbitmap.h"
#include "document.h"
#include "doclens.h"
#include "prefs.h"

#define DB(x)       //x
#define Static      static
#define MEMDBG(x)   //x

#ifdef __SASC
#   define SAVEDS   __saveds
#   define ASM      __asm
#   define REG(r,a) register __ ## r a
#elif defined(__GNUC__)
#   define SAVEDS
#   define ASM
#   define REG(r,a) a __asm(#r)
#endif


#define historySize 50
struct History {
    int page;
    BPTR dir;
    char *name;
};
typedef struct History History;

struct DocBitmapData {
    int xoffset;
    int yoffset;
    int width;
    int height;
    int page;
    int num_pages;
    int page_width;
    int page_height;
    int page_rotate;
    int zoom;
    int zoomdpi;
    int dpi;
    int rotate;
    int last_page;
    int last_width;
    int last_height;
    int last_dpi;
    int last_rotate;
    unsigned char* bitmap;
    struct AOutputDev *aout;
    struct BitMap* tempbm1;
    struct BitMap* tempbm2;
    int tempbmwidth;
    struct ColorMap* colormap;
    struct comm_info *comm_info;
    Object *vgroup;
    Object *doc;
    Object *docchanged;
    Object *lens;
    struct PDFDoc *pdfdoc;
    struct Rectangle selection;
    struct Rectangle old_selection;
    void* last_action;
    ULONG* pens;
    int curpen;
    int maxpen;
    int modulo;
    int depth;
    int bpp;
    BOOL hidden;
    BOOL selected;
    BOOL selecting;
    BOOL select_displayed;
    int histCur;
    int histTot;
    History history[historySize];
    int cropx1;
    int cropy1;
    int cropx2;
    int cropy2;
    BOOL shown;
    BOOL needvgroup;
    int textAAlevel;
    int strokeAAlevel;
    int fillAAlevel;
    LONG select_color;
    int last_lens_x;
    int last_lens_y;
    BOOL adjusting;
    BOOL is_cgfx;
//    BOOL dontdrawbg;
#define dontdrawbg 0
};

/* zoom factor is 1.2 (similar to DVI magsteps) */
int zoomDPI[maxStdZoom-minZoom+1]={
  29, 35, 42, 50, 60,
  72,
  86, 104, 124, 149, 179
};

void sleep(Object *obj,BOOL x) {
    if(obj)
	get(obj,MUIA_ApplicationObject,&obj);
    if(obj)
	set(obj,MUIA_Application_Sleep,x);
}

/* workaround for a bug in egcs */
void MyWritePixelArray(APTR src,UWORD sx,UWORD sy,UWORD mod,struct RastPort* rp,UWORD x,UWORD y,UWORD w,UWORD h,UBYTE fmt);

/*void MyWritePixelLine8(struct RastPort *rp,int x,int y,int w,UBYTE *p,struct RastPort *temprp) {
    WritePixelLine8(rp,x,y,w,p,temprp);
}

void MyWritePixelArray8(struct RastPort *rp,int x1,int y1,int x2,int y2,UBYTE *p,struct RastPort *temprp) {
    WritePixelArray8(rp,x1,y1,x2,y2,p,temprp);
}*/

/*
 *  Server interface functions.
 */

Static int get_page_size(DocBitmapData *dat) {
    struct comm_info *ci=dat->comm_info;
    struct msg_pagesize* p=ci->cmddata;
    int r;
    p->doc=dat->pdfdoc;
    p->page=dat->page;
    r=(*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_PAGESIZE);
    dat->page_width=p->width;
    dat->page_height=p->height;
    dat->page_rotate=p->rotate;
    return r;
}

Static void create_output_dev(DocBitmapData* dat) {
    struct comm_info *ci=dat->comm_info;
    struct msg_create_output_dev* p=ci->cmddata;
    p->depth=dat->depth;
    p->port=ci->sv->reply_port;
    if((*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_CREATEOUTPUTDEV))
	dat->aout=p->out;
    else
	dat->aout=NULL;
}

Static void delete_output_dev(DocBitmapData *dat) {
    struct comm_info *ci=dat->comm_info;
    struct msg_delete_output_dev* p=ci->cmddata;
    p->out=dat->aout;
    (*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_DELETEOUTPUTDEV);
}

Static void set_AA_levels(DocBitmapData *dat) {
    struct comm_info *ci=dat->comm_info;
    struct msg_setaalevels* p=ci->cmddata;
    p->out=dat->aout;
    p->text=dat->textAAlevel;
    p->stroke=dat->strokeAAlevel;
    p->fill=dat->fillAAlevel;
    (*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_SETAALEVELS);
}

Static int simple_find(DocBitmapData *dat,const char* str,int top,int* xmin,int* ymin,int* xmax,int* ymax) {
    struct comm_info *ci=dat->comm_info;
    struct msg_find* p=ci->cmddata;
    int r=0;
    if(dat->aout) {
	p->aout=dat->aout;
	p->top=top;
	p->xmin=*xmin;
	p->ymin=*ymin;
	p->xmax=*xmax;
	p->ymax=*ymax;
	strcpy(p->str,str);
	r=(*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_SIMPLEFIND);
	*xmin=p->xmin;
	*ymin=p->ymin;
	*xmax=p->xmax;
	*ymax=p->ymax;
    }
    return r;
}

Static int chklink(DocBitmapData *dat,int x,int y,struct LinkAction** action,char* str,size_t len) {
    struct comm_info *ci=dat->comm_info;
    struct msg_chklink* p=ci->cmddata;
    int r=0;
    if(dat->aout) {
	p->doc=dat->pdfdoc;
	p->aout=dat->aout;
	p->x=x;
	p->y=y;
	p->action=*action;
	r=(*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_CHKLINK);
	strncpy(str,p->str,len);
	*action=p->action;
    }
    return r;
}

Static int doaction(DocBitmapData *dat,struct LinkAction **action,
		    int* state,int *page,int *x,int *y,char* buf,size_t len) {
    int r=0;
    if(dat->aout) {
	struct comm_info *ci=dat->comm_info;
	struct msg_doaction* p=ci->cmddata;
	p->doc=dat->pdfdoc;
	p->aout=dat->aout;
	p->action=*action;
	r=(*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_DOACTION);
	*action=p->action;
	*state=p->state;
	*page=p->page;
	*x=p->x;
	*y=p->y;
	strncpy(buf,p->str,len);
    }
    return r;
}

Static char* gettext(DocBitmapData *dat,int xmin,int ymin,int xmax,int ymax) {
    char* r=NULL;
    struct comm_info *ci=dat->comm_info;
    struct msg_gettext* p=ci->cmddata;
    if(dat->aout) {
	p->aout=dat->aout;
	p->xmin=xmin;
	p->ymin=ymin;
	p->xmax=xmax;
	p->ymax=ymax;
	LOCK; /*init_exchange(dat->comm_info);*/
	if((*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_GETTEXT)) {
	    char* buf=NULL;
	    size_t sz=p->size+1;
	    char* q;
	    if(sz>sizeof(union msg_max)) {
		buf=(*ci->sv->MyAllocSharedMem)(ci->sv,sz);
		if(buf)
		    q=buf;
		else {
		    q=dat->comm_info->cmddata;
		    sz=sizeof(union msg_max);
		}
	    } else
		q=dat->comm_info->cmddata;
	    (*ci->sv->exchange_raw_msg)(ci,q,sz,MSGID_GETTEXT);
	    if(r=MyAllocMem(sz))
		memcpy(r,q,sz);
	    (*ci->sv->MyFreeSharedMem)(ci->sv,buf);
	}
	UNLOCK; /*end_exchange(dat->comm_info);*/
    }
    return r;
}

ULONG get_pen(DocBitmapData* dat,ULONG r,ULONG g,ULONG b);
void get_pens(DocBitmapData* dat,ULONG* t,int num);

Static void show_page(DocBitmapData* dat,int page,int zoom,int rotate) {
    struct comm_info *ci=dat->comm_info;
    struct msg_page *p=ci->cmddata;
    p->aout=dat->aout;
    p->doc=dat->pdfdoc;
    p->page_num=page;
    p->zoom=zoom;
    p->rotate=rotate;
    p->xoffset=dat->xoffset;
    p->yoffset=dat->yoffset;
    p->width=dat->width;
    p->height=dat->height;
    p->modulo=dat->modulo;
    p->bpp=dat->bpp;
    LOCK;/*init_exchange(dat->comm_info);*/
    (*ci->sv->exchange_msg)(ci,&p->base,sizeof(*p),MSGID_PAGE);
    MEMDBG(*(ULONG*)((int)dat->bitmap+dat->modulo*dat->height*dat->bpp)=0xbbbbbbbb;)
    (*ci->sv->send_msg)(ci,dat->bitmap,dat->modulo*dat->height*dat->bpp MEMDBG(+4),MSGID_PAGE);

    while(1) {
	void *msg;
	void *data;
	size_t length;
	int id;

	msg=(*ci->sv->get_msg)(ci,&data,&length,&id);

	switch(id) {
	  case MSGID_PAGE:
	    UNLOCK;/*end_exchange(dat->comm_info);*/
	    MEMDBG(if(*(ULONG*)((int)dat->bitmap+dat->modulo*dat->height*dat->bpp)!=0xbbbbbbbb)
		printf("bytes past bm trashed: %08lx\n",
		       *(ULONG*)((int)dat->bitmap+dat->modulo*dat->height*dat->bpp));)
	    return;

	  case MSGID_GETCOLORS: {
	    struct msg_getcolors *m=data;
	    get_pens(dat,m->table,m->num);
	    break;
	  }

	  case MSGID_GETCOLOR: {
	    struct msg_getcolor *m=data;
	    m->base.success=get_pen(dat,m->r,m->g,m->b);
	    break;
	  }
	}
	(*ci->sv->reply_msg)(ci,msg);
    }
}

/*
 *  Virtual group class that doesn't react to MMB.
 */

SAVEDS ASM ULONG vgDispatcher(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1,struct MUIP_HandleInput* msg)) {
    if(msg->MethodID==MUIM_HandleInput && msg->imsg &&
       msg->imsg->Class==IDCMP_MOUSEBUTTONS &&
       (msg->imsg->Code==MIDDLEDOWN || msg->imsg->Code==MIDDLEUP)) {
	struct List *list;
	APTR state;
	Object *o;
	get(obj,MUIA_Group_ChildList,&list);
	state=list->lh_Head;
	while(o=NextObject(&state))
	    DoMethodA(o,(APTR)msg);
	return 0;
    }
    return DoSuperMethodA(cl,obj,(APTR)msg);
}

struct MUI_CustomClass *vgroup_mcc;


/*
 *  Pen sharing functions.
 */

/*ULONG get_pen(DocBitmapData* dat,short n) {
    DB(printf("--getpen(%d)\n",n);)
    return n<dat->maxpen?dat->pens[n]:1;
}*/

Static void clear_pens(DocBitmapData* dat) {
    ULONG* p=dat->pens;
    int n;
    for(n=dat->curpen<dat->maxpen?dat->curpen:dat->maxpen;--n>=0;)
	ReleasePen(dat->colormap,p[n]);
    dat->curpen=0;
}

ULONG get_pen(DocBitmapData* dat,ULONG r,ULONG g,ULONG b) {
    DB(printf("--getpen %d=%08lx,%08lx,%08lx\n",dat->curpen,r,g,b);)
    if(dat->curpen>=dat->maxpen) {
	int maxpen=((dat->curpen*3)>>1)+16;
	ULONG* p=MyAllocMem(maxpen*sizeof(*dat->pens));
	if(p) {
	    memcpy(p,dat->pens,dat->maxpen*sizeof(*dat->pens));
	    MyFreeMem(dat->pens);
	    dat->maxpen=maxpen;
	    dat->pens=p;
	}
    }
    if(dat->curpen<dat->maxpen)
	return dat->pens[dat->curpen++]=ObtainBestPenA(dat->colormap,r,g,b,NULL);
    else
	return 1;
}

void get_pens(DocBitmapData* dat,ULONG* t,int num) {
    while(--num>=0) {
	t[0]=get_pen(dat,t[1],t[2],t[3]);
	GetRGB32(dat->colormap,t[0],1,t+1);
	t+=4;
    }
}

/*void reset_pens(DocBitmapData* dat) {
    clear_pens(dat);
    add_pen(dat,0,0,0);
    add_pen(dat,0xffffffffUL,0xffffffffUL,0xffffffffUL);
}*/


/*
 *  Bitmap management.
 */

Static void clearbitmap(DocBitmapData* dat) {
    if(dat->bitmap) {
	(*dat->comm_info->sv->MyFreeSharedMem)(dat->comm_info->sv,dat->bitmap);
	dat->bitmap=NULL;
	dat->last_width=-1;
    }
}

Static BOOL setbitmap(DocBitmapData* dat) {
    if(!dat->aout || !dat->pdfdoc || dat->width<1 || dat->height<1)
	return FALSE;
    if(!dat->hidden && (!dat->bitmap ||
			dat->width!=dat->last_width ||
			dat->height!=dat->last_height)) {
	size_t sz;
	/*if(dat->bpp==1)
	    dat->modulo=((dat->width+15)&~15);
	else*/
	    dat->modulo=dat->width;
	sz=dat->modulo*dat->height*dat->bpp MEMDBG(+4);
	clearbitmap(dat);
	dat->last_width=dat->width;
	dat->last_height=dat->height;
	if(sz>0) {
	    dat->bitmap=(*dat->comm_info->sv->MyAllocSharedMem)(dat->comm_info->sv,sz);
	    if(!dat->bitmap)
		return FALSE;
	}
    }
    return TRUE;
}


Static void display_page(Object *obj,DocBitmapData* dat) {
    if((dat->last_page!=dat->page ||
	dat->last_dpi!=dat->dpi ||
	dat->last_rotate!=dat->rotate ||
	dat->last_width!=dat->width ||
	dat->last_height!=dat->height) &&
	dat->dpi>0) {
	if(dat->last_width!=dat->width || dat->last_height!=dat->height)
	    setbitmap(dat);
	if(dat->bitmap) {
	    sleep(obj,TRUE);
	    dat->selected=FALSE;
	    show_page(dat,dat->page,dat->dpi,dat->rotate);
	    dat->last_page=dat->page;
	    dat->last_dpi=dat->dpi;
	    dat->last_rotate=dat->rotate;
	    sleep(obj,FALSE);
	}
    }
}


/*
 *  Methods.
 */

Static ULONG mHistoryPush(struct IClass *cl,Object *obj,struct MYP_DocBitmap_HistoryPush *msg) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    while(dat->histCur<dat->histTot) {
	struct History *h;
	--dat->histTot;
	h=dat->history+dat->histTot;
	MyFreeMem(h->name);
	UnLock(h->dir);
    }
    if(dat->histCur==historySize) {
	MyFreeMem(dat->history->name);
	UnLock(dat->history->dir);
	memmove(dat->history,dat->history+1,(historySize-1)*sizeof(struct History));
	--dat->histCur;
	--dat->histTot;
    }
    dat->history[dat->histCur].page=msg->page;
    dat->history[dat->histCur].dir=DupLock(msg->dir);
    dat->history[dat->histCur].name=NULL;
    if(msg->name)
	CopyStr(&dat->history[dat->histCur].name,msg->name);;
    ++dat->histCur;
    ++dat->histTot;
    return 0;
}

Static ULONG mRecalc(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    dat->width=10;
    dat->height=10;
    if(dat->pdfdoc) {
	if(get_page_size(dat)) {
	    int dpi;
	    int w=dat->page_width-dat->cropx1+dat->cropx2;
	    int h=dat->page_height-dat->cropy1+dat->cropy2;
	    int cx;
	    int cy;
	    BOOL err;
	    if(w<1)
		w=1;
	    if(h<1)
		h=1;
	    if((dat->rotate+dat->page_rotate)%180!=0) {
		int t=w;
		w=h;
		h=t;
	    }
	    switch((dat->rotate+dat->page_rotate)%360) {
	      case 90:
		cx=-dat->cropy2;
		cy=dat->cropx1;
		break;
	      case 180:
		cx=-dat->cropx2;
		cy=-dat->cropy2;
		break;
	      case 270:
		cx=dat->cropy1;
		cy=-dat->cropx2;
		break;
	      default:
		cx=dat->cropx1;
		cy=dat->cropy1;
		break;
	    }
	    if(dat->zoom==zoomPage || dat->zoom==zoomWidth) {
		if(dat->shown) {
		    int width;
		    int t;
		    if(dat->vgroup) {
			get(dat->vgroup,MUIA_Width,&width);
			get(dat->vgroup,MUIA_InnerLeft,&t);
			width-=t;
			get(dat->vgroup,MUIA_InnerRight,&t);
			width-=t;
		    } else
			get(obj,MUIA_Width,&width);
		    dpi=((width-bleft-bright)*72)/w;
		    if(dat->zoom==zoomPage) {
			int height;
			int hdpi;
			if(dat->vgroup) {
			    get(dat->vgroup,MUIA_Height,&height);
			    get(dat->vgroup,MUIA_InnerBottom,&t);
			    height-=t;
			    get(dat->vgroup,MUIA_InnerTop,&t);
			    height-=t;
			} else
			    get(obj,MUIA_Height,&height);
			hdpi=((height-btop-bbottom)*72)/h;
			if(hdpi<dpi)
			    dpi=hdpi;
		    }
		} else
		    dpi=zoomDPI[defZoom-minZoom];
	    } else if(dat->zoom==zoomCustom)
		dpi=dat->zoomdpi;
	    else
		dpi=zoomDPI[dat->zoom-minZoom];
	    err=dpi<=0;
	    if(dat->page_width*dpi>0x1fff*72) {
		dpi=0x1fff*72/dat->page_width;
		err=TRUE;
	    }
	    if(dat->page_height*dpi>0x1fff*72) {
		dpi=0x1fff*72/dat->page_height;
		err=TRUE;
	    }
	    /*if(err)
		printf("Zoom factor too large.\n");*/
	    dat->dpi=dpi;
	    dat->xoffset=(cx*dpi)/72;
	    dat->yoffset=(cy*dpi)/72;
	    dat->width=(w*dpi)/72;
	    dat->height=(h*dpi)/72;
	    if(!dat->shown)
		dat->dpi=-1;
	}
    } else
	dat->dpi=-1;
    return 0;
}

Static ULONG mSet(struct IClass *cl,Object *obj,struct opSet *msg) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    struct TagItem *tags=msg->ops_AttrList;
    struct TagItem *tag;
    BOOL redraw=FALSE;
    BOOL no_history=FALSE;
    while(tag=NextTagItem(&tags)) {
	switch(tag->ti_Tag) {
	  case MYATTR_DocBitmap_NoHistory:
	    no_history=tag->ti_Data;
	    break;

	  case MYATTR_DocBitmap_Page: {
	    int p=(int)tag->ti_Data;
	    if(p<1)
		p=1;
	    else if(p>dat->num_pages)
		p=dat->num_pages;
	    if(dat->page!=p) {
		if(!no_history && dat->doc)
		    DoMethod(obj,MYM_DocBitmap_HistoryPush,dat->page,0,NULL);
		dat->page=p;
		redraw=TRUE;
		if(dat->vgroup)
		    set(dat->vgroup,MUIA_Virtgroup_Top,0);
	    }
	    break;
	  }
	  case MYATTR_DocBitmap_Zoom:
	    if(dat->zoom!=(int)tag->ti_Data &&
	       tag->ti_Data>=minZoom && tag->ti_Data<maxZoom) {
		dat->zoom=(int)tag->ti_Data;
	       if(dat->needvgroup==(dat->zoom==zoomPage)) {
		   set(obj,MYATTR_DocBitmap_NeedVGroup,!dat->needvgroup);
		   dat->needvgroup=!dat->needvgroup;
	       }
		redraw=TRUE;
	    }
	    break;

	  case MYATTR_DocBitmap_DPI:
	    if(dat->zoom!=zoomCustom || dat->dpi!=tag->ti_Data) {
		dat->zoomdpi=(int)tag->ti_Data;
		dat->zoom=zoomCustom;
		redraw=TRUE;
	    }
	    break;

	  case MYATTR_DocBitmap_Rotate:
	    if(dat->rotate!=(int)tag->ti_Data) {
		dat->rotate=(int)tag->ti_Data;
		redraw=TRUE;
	    }
	    break;

	  case MYATTR_DocBitmap_CropBox: {
	    struct MYS_DocBitmap_CropBox *box=(APTR)tag->ti_Data;
	    static struct MYS_DocBitmap_CropBox zero={0,0,0,0};
	    if(!box)
		box=&zero;
	    if(dat->cropx1!=box->left || dat->cropy1!=box->top ||
	       dat->cropx2!=-box->right || dat->cropy2!=-box->bottom) {
		dat->cropx1=box->left;
		dat->cropy1=box->top;
		dat->cropx2=-box->right;
		dat->cropy2=-box->bottom;
		dat->last_dpi=-1;
		redraw=TRUE;
	    }
	    break;
	  }

	  case MYATTR_DocBitmap_Selection:
	    if(tag->ti_Data) {
		dat->selection=*(struct Rectangle*)tag->ti_Data;
		if(dat->selection.MinX<0)
		    dat->selection.MinX=0;
		else if(dat->selection.MinX>=dat->width)
		    dat->selection.MinX=dat->width-1;
		if(dat->selection.MaxX<0)
		    dat->selection.MaxX=0;
		else if(dat->selection.MaxX>=dat->width)
		    dat->selection.MaxX=dat->width-1;
		if(dat->selection.MinY<0)
		    dat->selection.MinY=0;
		else if(dat->selection.MinY>=dat->height)
		    dat->selection.MinY=dat->height-1;
		if(dat->selection.MaxY<0)
		    dat->selection.MaxY=0;
		else if(dat->selection.MaxY>=dat->height)
		    dat->selection.MaxY=dat->height-1;
		dat->selected=TRUE;
		redraw=TRUE;
	    } else if(dat->selected) {
		dat->selected=FALSE;
		redraw=TRUE;
	    }
	    break;

	  case MYATTR_DocBitmap_Document:
	    if(dat->doc!=(Object*)tag->ti_Data) {
		struct MYS_DocBitmap_CropBox box;
		ULONG page,zoom,rotate,textaa,fillaa,strokeaa;
		get((Object*)tag->ti_Data,MYATTR_Document_NumPages,&dat->num_pages);
		get((Object*)tag->ti_Data,MYATTR_Document_OpenPage,&page);
		page=GetTagData(MYATTR_DocBitmap_Page,page,msg->ops_AttrList);
		if(!no_history && dat->doc) {
		    const char *name;
		    BPTR dir;
		    get(dat->doc,MYATTR_Document_Dir,&dir);
		    get(dat->doc,MYATTR_Document_Name,&name);
		    DoMethod(obj,MYM_DocBitmap_HistoryPush,dat->page,dir,name);
		}
		dat->doc=(Object*)tag->ti_Data;
		get(dat->doc,MYATTR_Document_PDFDoc,&dat->pdfdoc);
		get(dat->doc,MYATTR_Document_OpenZoom,&zoom);
		get(dat->doc,MYATTR_Document_OpenRotate,&rotate);
		get(dat->doc,MYATTR_Document_OpenCropBox,&box);
		get(dat->doc,MYATTR_Document_OpenTextAA,&textaa);
		get(dat->doc,MYATTR_Document_OpenFillAA,&fillaa);
		get(dat->doc,MYATTR_Document_OpenStrokeAA,&strokeaa);
		dat->last_page=-1;
		dat->last_rotate=-1;
		dat->last_dpi=-1;
		dat->page=page;
		SetAttrs(obj,
			 MYATTR_DocBitmap_NoHistory,TRUE,
			 MYATTR_DocBitmap_Page,page,
			 MYATTR_DocBitmap_Zoom,zoom,
			 MYATTR_DocBitmap_Rotate,rotate,
			 MYATTR_DocBitmap_CropBox,&box,
			 MYATTR_DocBitmap_TextAA,textaa,
			 MYATTR_DocBitmap_FillAA,fillaa,
			 MYATTR_DocBitmap_StrokeAA,strokeaa,
			 TAG_END);
		redraw=TRUE;
		set(obj,MYATTR_DocBitmap_DocChanged,dat->doc);
	    }
	    break;

	  case MYATTR_DocBitmap_DocChanged:
	    dat->docchanged=(Object*)tag->ti_Data;
	    break;

	  case MYATTR_DocBitmap_TextAA:
	    if(dat->textAAlevel!=(int)tag->ti_Data) {
		dat->textAAlevel=(int)tag->ti_Data;
		set_AA_levels(dat);
		dat->last_page=-1;
		redraw=TRUE;
	    }
	    break;
	  case MYATTR_DocBitmap_StrokeAA:
	    if(dat->strokeAAlevel!=(int)tag->ti_Data) {
		dat->strokeAAlevel=(int)tag->ti_Data;
		set_AA_levels(dat);
		dat->last_page=-1;
		redraw=TRUE;
	    }
	    break;
	  case MYATTR_DocBitmap_FillAA:
	    if(dat->fillAAlevel!=(int)tag->ti_Data) {
		dat->fillAAlevel=(int)tag->ti_Data;
		set_AA_levels(dat);
		dat->last_page=-1;
		redraw=TRUE;
	    }
	    break;

	 case MYATTR_DocBitmap_VGroup:
	   if(dat->vgroup!=(Object*)tag->ti_Data) {
	       dat->vgroup=(Object*)tag->ti_Data;
	       redraw=TRUE;
	   }
	   break;

	 /*case MYATTR_DocBitmap_DrawBg:
	   dat->dontdrawbg=!tag->ti_Data;
	   break;*/
	}

    }
    if(redraw) {
	mRecalc(cl,obj);
	if(dat->dpi==-1 || msg->MethodID==OM_NEW)
	    ;
	else if((dat->width!=dat->last_width || dat->height!=dat->last_height) &&
		dat->vgroup) {
	    DoMethod(dat->vgroup,MUIM_Group_InitChange);
	    DoMethod(dat->vgroup,MUIM_Group_ExitChange);
	} else
	    MUI_Redraw(obj,MADF_DRAWOBJECT);
    }
    if(msg->MethodID==OM_NEW)
	return 0;
    else
	return DoSuperMethodA(cl,obj,(Msg)msg);
}


Static ULONG mNew(struct IClass *cl,Object *obj,struct opSet *msg) {
    DocBitmapData* dat;
    struct comm_info *ci=(struct comm_info*)GetTagData(MYATTR_DocBitmap_CommInfo,0,msg->ops_AttrList);
    if(!ci)
	obj=NULL;
    else if(obj=(Object *)DoSuperMethodA(cl,obj,(Msg)msg)) {
	dat=INST_DATA(cl,obj);
	memset(dat,0,sizeof(*dat));
	dat->hidden=TRUE;
	dat->dpi=-1;
	dat->last_dpi=-1;
	dat->last_page=-1;
	dat->comm_info=ci;
	dat->vgroup=(Object*)GetTagData(MYATTR_DocBitmap_VGroup,0,msg->ops_AttrList);
	dat->textAAlevel=1;
	dat->needvgroup=dat->vgroup!=NULL;
	set(obj,MUIA_FillArea,FALSE);
	mSet(cl,obj,msg);
    }
    return (ULONG)obj;
}

Static ULONG mDispose(struct IClass *cl,Object *obj,Msg msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    int n;
    MyFreeMem(dat->pens);
    for(n=0;n<dat->histTot;++n) {
	MyFreeMem(dat->history[n].name);
	UnLock(dat->history[n].dir);
    }
    return DoSuperMethodA(cl,obj,msg);
}

Static ULONG mGet(struct IClass *cl,Object *obj,struct opGet *msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    switch(msg->opg_AttrID) {
      case MYATTR_DocBitmap_Page:
	*msg->opg_Storage=dat->page;
	return TRUE;
      case MYATTR_DocBitmap_Zoom:
	*msg->opg_Storage=dat->zoom;
	return TRUE;
      case MYATTR_DocBitmap_DPI:
	*msg->opg_Storage=dat->zoomdpi;
	return TRUE;
      case MYATTR_DocBitmap_CropBox: {
	struct MYS_DocBitmap_CropBox *box=(APTR)msg->opg_Storage;
	box->left=dat->cropx1;
	box->top=dat->cropy1;
	box->right=-dat->cropx2;
	box->bottom=-dat->cropy2;
	break;
      }
      case MYATTR_DocBitmap_Rotate:
	*msg->opg_Storage=dat->rotate;
	return TRUE;
      case MYATTR_DocBitmap_Selected:
	*msg->opg_Storage=dat->selected;
	break;
      case MYATTR_DocBitmap_Selection:
	*(struct Rectangle*)msg->opg_Storage=dat->selection;
	return TRUE;
      case MYATTR_DocBitmap_Document:
	*(Object**)msg->opg_Storage=dat->doc;
	return TRUE;
      case MYATTR_DocBitmap_DocChanged:
	*(Object**)msg->opg_Storage=dat->docchanged;
	return TRUE;
      case MYATTR_DocBitmap_TextAA:
	*msg->opg_Storage=dat->textAAlevel;
	return TRUE;
      case MYATTR_DocBitmap_StrokeAA:
	*msg->opg_Storage=dat->strokeAAlevel;
	return TRUE;
      case MYATTR_DocBitmap_FillAA:
	*msg->opg_Storage=dat->fillAAlevel;
	return TRUE;
      case MYATTR_DocBitmap_Run:
      case MYATTR_DocBitmap_URL:
      case MYATTR_DocBitmap_Open:
      case MYATTR_DocBitmap_OpenNew:
      case MYATTR_DocBitmap_PageNew:
	*msg->opg_Storage=0;
	return TRUE;
      case MYATTR_DocBitmap_NeedVGroup:
       *msg->opg_Storage=dat->needvgroup;
       return TRUE;
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
}

Static ULONG mSetup(struct IClass *cl,Object *obj,Msg msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    struct Screen *scr;
    struct BitMap *bm;

    if(!DoSuperMethodA(cl,obj,(Msg)msg))
	return FALSE;

    scr=_screen(obj);
    dat->colormap=scr->ViewPort.ColorMap;
    bm=scr->RastPort.BitMap;
    dat->is_cgfx=FALSE;
#if defined(HAVE_CYBERGRAPHICS_CYBERGRAPHICS_H) || \
    defined(HAVE_CYBERGRAPHX_CYBERGRAPHICS_H)
    if(CyberGfxBase && IsCyberModeID(GetVPModeID(&scr->ViewPort)))
	dat->is_cgfx=TRUE;
#endif
    dat->depth=GetBitMapAttr(bm,BMA_DEPTH);
    if(dat->depth>8 && !dat->is_cgfx)
	dat->depth=8;
    if(dat->depth<=8)
	dat->bpp=1;
    else
	dat->bpp=4;
    create_output_dev(dat);
    dat->hidden=FALSE;
    dat->selected=FALSE;
    dat->selecting=FALSE;
    dat->bitmap=NULL;
    set_AA_levels(dat);
    dat->select_color=MUI_ObtainPen(muiRenderInfo(obj),(struct MUI_PenSpec *)DoMethod(config,MUIM_Dataspace_Find,MYCFG_Apdf_SelectColor),0);
    MUI_RequestIDCMP(obj,IDCMP_MOUSEMOVE|IDCMP_MOUSEBUTTONS|IDCMP_RAWKEY);
    return TRUE;
}

Static ULONG mCleanup(struct IClass *cl,Object *obj,Msg msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    MUI_RejectIDCMP(obj,IDCMP_MOUSEMOVE|IDCMP_MOUSEBUTTONS|IDCMP_RAWKEY);
    MUI_ReleasePen(muiRenderInfo(obj),dat->select_color);
    WaitBlit();
    FreeBitMap(dat->tempbm1);
    FreeBitMap(dat->tempbm2);
    dat->tempbm1=NULL;
    dat->tempbm2=NULL;
    dat->tempbmwidth=0;
    clearbitmap(dat);
    clear_pens(dat);
    dat->hidden=TRUE;
    delete_output_dev(dat);
    dat->last_page=-1;
    return DoSuperMethodA(cl,obj,msg);
}

Static ULONG mShow(struct IClass *cl,Object *obj,Msg msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    dat->shown=TRUE;
    return DoSuperMethodA(cl,obj,msg);
}

Static ULONG mHide(struct IClass *cl,Object *obj,Msg msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    dat->shown=FALSE;
    DoMethod(obj,MYM_DocBitmap_DestroyLens);
    return DoSuperMethodA(cl,obj,msg);
}

Static ULONG mReset(struct IClass *cl,Object *obj,Msg msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    if(!dat->hidden) {
	sleep(obj,TRUE);
	dat->last_page=-1;
	MUI_Redraw(obj,MADF_DRAWOBJECT);
	sleep(obj,FALSE);
    }
    return 0;
}

Static ULONG mAskMinMax(struct IClass *cl,Object *obj,struct MUIP_AskMinMax *msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    int w,h;
    DoSuperMethodA(cl,obj,(Msg)msg);
    mRecalc(cl,obj);
    w=dat->width+bright+bleft;
    h=dat->height+bbottom+btop;
    msg->MinMaxInfo->MinWidth+=dat->zoom==zoomPage ||
(dat->zoom==zoomWidth && !dat->adjusting)?10:w;
    msg->MinMaxInfo->DefWidth+=w;
    msg->MinMaxInfo->MaxWidth+=MUI_MAXMAX;
    msg->MinMaxInfo->MinHeight+=dat->zoom==zoomPage ||
(dat->zoom==zoomWidth && !dat->adjusting)?10:h;
    msg->MinMaxInfo->DefHeight+=h;
    msg->MinMaxInfo->MaxHeight+=MUI_MAXMAX;
    return 0;
}

Static void blit_bitmap(DocBitmapData* dat,int x1,int y1,struct RastPort* rp,
			int x,int y,int w,int h) {
    if(x<0) {
	w+=x;
	x1-=x;
	x=0;
    }
    if(y<0) {
	h+=y;
	y1-=y;
	y=0;
    }
    if(w<=0 || h<=0)
	return;
#if defined(HAVE_CYBERGRAPHICS_CYBERGRAPHICS_H) || \
    defined(HAVE_CYBERGRAPHX_CYBERGRAPHICS_H)
    if(!dat->is_cgfx) {
#endif
	/* dat->bpp==1 here */
	/*
	 *  WritePixelArray8 destroys the array, so we must
	 *  copy it first. It also doesn't allow blitting
	 *  a sub-array, so we had to do something anyway.
	 */
	/*
	 *  WritePixelArray8 doesn't work properly when the
	 *  array is a vertical line, which can happen in an
	 *  indirect way through clipping (e.g. when the
	 *  user scrolls the vgroup).
	 *  To avoid that problem, we use a temporary bitmap,
	 *  without clipping, to which we blit the array. Then
	 *  we blit the bitmap to the final rastport.
	 */
	int m=(w+15)&~15;
	unsigned char *q;
	/*if(w==1) {
	    int m2=dat->modulo;
	    unsigned char *p=dat->bitmap+y1*m2+x1;
	    while(--h>=0) {
		SetAPen(rp,*p);
		WritePixel(rp,x,y++);
		p+=m2;
	    }
	    return;
	}*/
	if(q=MyAllocMem(32*m)) {
	    if(dat->tempbmwidth<w) {
		WaitBlit();
		FreeBitMap(dat->tempbm1);
		FreeBitMap(dat->tempbm2);
		dat->tempbm1=AllocBitMap(m,32,dat->depth,BMF_MINPLANES,rp->BitMap);
		dat->tempbm2=AllocBitMap(m,1,dat->depth,BMF_MINPLANES,dat->tempbm1);
		if(dat->tempbm1 && dat->tempbm2)
		    dat->tempbmwidth=m;
		else {
		    dat->tempbmwidth=0;
		    FreeBitMap(dat->tempbm1);
		    FreeBitMap(dat->tempbm2);
		    dat->tempbm1=NULL;
		    dat->tempbm2=NULL;
		}
	    }
	    if(dat->tempbmwidth) {
		int m2=dat->modulo;
		unsigned char *p=dat->bitmap+y1*m2+x1;
		struct RastPort temprp1=*rp;
		struct RastPort temprp2;
		temprp1.Layer=NULL;
		temprp2=temprp1;
		temprp1.BitMap=dat->tempbm1;
		temprp2.BitMap=dat->tempbm2;
		while(h>0) {
		    unsigned char *r=q;
		    int l=h<32?h:32;
		    int k=l;
		    do {
			memcpy(r,p,w);
			r+=m;
			p+=m2;
		    } while(--k);
		    WaitBlit();
		    WritePixelArray8(&temprp1,0,0,w-1,l-1,q,&temprp2);
		    BltBitMapRastPort(dat->tempbm1,0,0,rp,x,y,w,l,0xc0);
		    y+=l;
		    h-=l;
		}
	    }
	    MyFreeMem(q);
	}
#if defined(HAVE_CYBERGRAPHICS_CYBERGRAPHICS_H) || \
    defined(HAVE_CYBERGRAPHX_CYBERGRAPHICS_H)
    } else {
	MyWritePixelArray(dat->bitmap,x1,y1,dat->modulo*dat->bpp,
			  rp,x,y,w,h,dat->bpp==1?RECTFMT_LUT8:RECTFMT_RGBA);
    }
#endif
}

/* Workaround for a bug in gcc-2.95.1 */
Static __inline__ getwidth(Object *obj) {
    return _mwidth(obj);
}
Static __inline__ getheight(Object *obj) {
    return _mheight(obj);
}

Static ULONG mDraw(struct IClass *cl,Object *obj,struct MUIP_Draw *msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    int x,y,w,h;
    struct RastPort *rp;
    //BOOL dontdrawbg=dat->dontdrawbg && !dat->vgroup;
    DoSuperMethodA(cl,obj,(Msg)msg);

    rp=_rp(obj);
    x=_mleft(obj);
    y=_mtop(obj);
    //w=_mwidth(obj);
    //h=_mheight(obj);
    w=getwidth(obj);
    h=getheight(obj);

    if(msg->flags&MADF_DRAWOBJECT) {
	int lev,lev1=0;
	if(dat->zoom==zoomWidth && dat->vgroup && !dat->adjusting) {
	    int width;
	    int t;
	    get(dat->vgroup,MUIA_Width,&width);
	    get(dat->vgroup,MUIA_InnerLeft,&t);
	    width-=t;
	    get(dat->vgroup,MUIA_InnerRight,&t);
	    width-=t;
	    if(width!=dat->width+bleft+bright) {
		mRecalc(cl,obj);
		if(dat->width!=dat->last_width || dat->height!=dat->last_height) {
		    dat->adjusting=TRUE;
		    DoMethod(dat->vgroup,MUIM_Group_InitChange);
		    DoMethod(dat->vgroup,MUIM_Group_ExitChange);
		    dat->adjusting=FALSE;
		    return 0;
		}
	    }
	} else if(dat->dpi==-1 ||
		  (dat->zoom==zoomPage &&
		   (w!=dat->width+bleft+bright || h!=dat->height+btop+bbottom)))
	    mRecalc(cl,obj);
	if(dat->dpi!=-1 && dat->width>0 && dat->height>0) {
	    if(w>dat->width+bleft+bright || h>dat->height+btop+bbottom) {
		int h1=(h-dat->height-btop-bbottom)/2;
		int h2=h-dat->height-btop-bbottom-h1;
		int w1=(w-dat->width-bleft-bright)/2;
		int w2=w-dat->width-bleft-bright-w1;
		h-=h1+h2;
		if(h1>0 && !dontdrawbg)
		    DoMethod(obj,MUIM_DrawBackground,x,y,w,h1,x,y,0);
		if(h2>0 && !dontdrawbg)
		    DoMethod(obj,MUIM_DrawBackground,x,y+h1+h,w,h2,x,y+h1+h,0);
		w-=w1+w2;
		y+=h1;
		if(w1>0 && !dontdrawbg)
		    DoMethod(obj,MUIM_DrawBackground,x,y,w1,h,x,y,0);
		if(w2>0 && !dontdrawbg)
		    DoMethod(obj,MUIM_DrawBackground,x+w1+w,y,w2,h,x+w1+w,y,0);
		x+=w1;
	    }
	    if(w>=shadowpixels+2*borderpixels &&
	       h>=shadowpixels+2*borderpixels) {
		if(!dontdrawbg) {
		    DoMethod(obj,MUIM_DrawBackground,x+w-shadowpixels,y,shadowpixels,shadowpixels,x+w-shadowpixels,y,0);
		    DoMethod(obj,MUIM_DrawBackground,x,y+h-shadowpixels,shadowpixels,shadowpixels,x,y+h-shadowpixels,0);
		}
		SetAPen(rp,_dri(obj)->dri_Pens[SHADOWPEN]);
		RectFill(rp,x,y,x+w-shadowpixels-borderpixels-1,y+borderpixels-1);
		RectFill(rp,x,y+borderpixels,x+borderpixels-1,y+h-shadowpixels-borderpixels-1);
		RectFill(rp,x+w-shadowpixels-borderpixels,y,x+w-shadowpixels-1,y+shadowpixels-1);
		RectFill(rp,x,y+h-shadowpixels-borderpixels,x+shadowpixels-1,y+h-shadowpixels-1);
		RectFill(rp,x+w-shadowpixels-borderpixels,y+shadowpixels,x+w-1,y+h-shadowpixels-borderpixels-1);
		RectFill(rp,x+shadowpixels,y+h-shadowpixels-borderpixels,x+w-1,y+h-1);

		if((dat->last_page!=dat->page ||
		    dat->last_dpi!=dat->dpi ||
		    dat->last_rotate!=dat->rotate ||
		    dat->last_width!=dat->width ||
		    dat->last_height!=dat->height) &&
		    dat->curpen) {
		    //SetAPen(rp,_dri(obj)->dri_Pens[SHINEPEN]);
		    //RectFill(rp,x+bleft,y+btop,x+w-bleft-bright-1,y+h-btop-bbottom-1);
		    clear_pens(dat);
		}
		display_page(obj,dat);
		if(dat->bitmap) {
		    blit_bitmap(dat,0,0,rp,x+bleft,y+btop,w-bleft-bright,h-btop-bbottom);
		} else {
		    SetAPen(rp,_dri(obj)->dri_Pens[SHINEPEN]);
		    RectFill(rp,x+bleft,y+btop,x+w-bleft-bright-1,y+h-btop-bbottom-1);
		}
	    }
	}
    } else if(msg->flags&MADF_DRAWUPDATE && dat->bitmap) {
	x+=(w-dat->width-bleft-bright)/2;
	w=dat->width;
	y+=(h-dat->height-btop-bbottom)/2;
	h=dat->height;
	if(dat->select_displayed) {
	    int x1=dat->old_selection.MinX;
	    int y1=dat->old_selection.MinY;
	    int x2=dat->old_selection.MaxX;
	    int y2=dat->old_selection.MaxY;
	    if(x1>x2) {
		int t=x2;
		x2=x1;
		x1=t;
	    }
	    if(y1>y2) {
		int t=y2;
		y2=y1;
		y1=t;
	    }
	    if(x1>=0 && x1<w && y2>=0 && y1<h) {
		int y3=y1<0?0:y1;
		int y4=y2>=h?h-1:y2;
		blit_bitmap(dat,x1,y3,rp,x+bleft+x1,y+btop+y3,1,y4-y3+1);
	    } else if(x1<0)
		x1=0;
	    if(x2>=0 && x2<w && y2>=0 && y1<h) {
		int y3=y1<0?0:y1;
		int y4=y2>=h?h-1:y2;
		blit_bitmap(dat,x2,y3,rp,x+bleft+x2,y+btop+y3,1,y4-y3+1);
	    } else if(x2>=w)
		x2=w-1;
	    if(x1<=x2) {
		if(y1>=0 && y1<h)
		    blit_bitmap(dat,x1,y1,rp,x+bleft+x1,y+btop+y1,x2-x1+1,1);
		if(y2>=0 && y2<h)
		    blit_bitmap(dat,x1,y2,rp,x+bleft+x1,y+btop+y2,x2-x1+1,1);
	    }
	}
    }
    if(dat->bitmap && msg->flags&(MADF_DRAWOBJECT|MADF_DRAWUPDATE)) {
	dat->select_displayed=dat->selected || dat->selecting;
	if(dat->select_displayed) {
	    SetAPen(rp,MUIPEN(dat->select_color));
	    Move(rp,x+dat->selection.MinX+bleft,y+dat->selection.MinY+btop);
	    Draw(rp,x+dat->selection.MinX+bleft,y+dat->selection.MaxY+btop);
	    Draw(rp,x+dat->selection.MaxX+bleft,y+dat->selection.MaxY+btop);
	    Draw(rp,x+dat->selection.MaxX+bleft,y+dat->selection.MinY+btop);
	    Draw(rp,x+dat->selection.MinX+bleft,y+dat->selection.MinY+btop);
	    dat->old_selection=dat->selection;
	}
    }
    if(!dat->bitmap && w && h)
	EraseRect(rp,x,y,x+w-1,y+h-1);
    return 0;
}


Static ULONG mHistory(struct IClass *cl,Object *obj,struct History *h) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    Object *doc=dat->doc;
    int p=h->page;
    if(h->name) {
	const char* name;
	BPTR dir;
	Object *app,*win;
	get(doc,MYATTR_Document_Dir,&dir);
	get(doc,MYATTR_Document_Name,&name);
	get(obj,MUIA_ApplicationObject,&app);
	get(obj,MUIA_WindowObject,&win);
	doc=getDocument(dat->comm_info,app,win,h->dir,h->name);
	if(!doc) {
	    ++dat->histCur;
	    return 0;
	}
	CopyStr(&h->name,name);
	UnLock(h->dir);
	h->dir=DupLock(dir);
    }
    h->page=dat->page;
    SetAttrs(obj,MYATTR_DocBitmap_NoHistory,TRUE,
	     MYATTR_DocBitmap_Document,doc,
	     MYATTR_DocBitmap_Page,p,
	     TAG_END);
    return 1;
}

Static ULONG mBackward(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    if(dat->histCur>0 && mHistory(cl,obj,dat->history+dat->histCur-1)) {
	--dat->histCur;
	return 1;
    }
    return 0;
}

Static ULONG mForward(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    if(dat->histCur<dat->histTot && mHistory(cl,obj,dat->history+dat->histCur)) {
	++dat->histCur;
	return 1;
    }
    return 0;
}

Static LONG mRLeft(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    set(obj,MYATTR_DocBitmap_Rotate,(dat->rotate+270)%360);
    return 0;
}

Static LONG mRRight(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    set(obj,MYATTR_DocBitmap_Rotate,(dat->rotate+90)%360);
    return 0;
}

Static void upkey(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    if(dat->vgroup) {
	LONG top,height,totheight,t;
	get(dat->vgroup,MUIA_Virtgroup_Height,&totheight);
	get(dat->vgroup,MUIA_Virtgroup_Top,&top);
	get(dat->vgroup,MUIA_Height,&height);
	get(dat->vgroup,MUIA_InnerBottom,&t);
	height-=t;
	get(dat->vgroup,MUIA_InnerTop,&t);
	height-=t;
	if(top>0)
	    set(dat->vgroup,MUIA_Virtgroup_Top,top-height+10);
	else if(dat->page>1) {
	    set(obj,MYATTR_DocBitmap_Page,dat->page-1);
	    set(dat->vgroup,MUIA_Virtgroup_Top,totheight-height);
	}
    } else if(dat->page>1) {
       set(obj,MYATTR_DocBitmap_Page,dat->page-1);
    }
}

Static void downkey(struct IClass *cl,Object *obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    if(dat->vgroup) {
	LONG top,height,totheight,t;
	get(dat->vgroup,MUIA_Virtgroup_Height,&totheight);
	get(dat->vgroup,MUIA_Virtgroup_Top,&top);
	get(dat->vgroup,MUIA_Height,&height);
	get(dat->vgroup,MUIA_InnerBottom,&t);
	height-=t;
	get(dat->vgroup,MUIA_InnerTop,&t);
	height-=t;
	if(top<totheight-height)
	    set(dat->vgroup,MUIA_Virtgroup_Top,top+height-10);
	else if(dat->page<dat->num_pages) {
	    set(obj,MYATTR_DocBitmap_Page,dat->page+1);
	    set(dat->vgroup,MUIA_Virtgroup_Top,0);
	}
    } else if(dat->page<dat->num_pages) {
       set(obj,MYATTR_DocBitmap_Page,dat->page+1);
    }
}

struct MYP_DocBitmap_Action {
    ULONG MethodID;
    struct LinkAction *action;
    int shift;
};

Static ULONG mAction(struct IClass *cl,Object *obj,struct MYP_DocBitmap_Action *msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    int r=0;
    int state,page;
    char buf[256];
    int x,y;
    struct LinkAction *action=msg->action;
    state=st_init;
    while(action && (r=doaction(dat,&action,&state,&page,&x,&y,buf,sizeof(buf)))) {
	switch(state) {
	  case st_newdoc:
	  case st_newdocnw:
	    set(obj,state==st_newdocnw || msg->shift?
		MYATTR_DocBitmap_OpenNew:MYATTR_DocBitmap_Open,buf);
	    break;

	  case st_goto:
	    if(page!=-1)
		set(obj,msg->shift?MYATTR_DocBitmap_PageNew:
		    MYATTR_DocBitmap_Page,page);
	    set(obj,MYATTR_DocBitmap_Message,NULL);
	    break;

	  case st_nextpage:
	    set(obj,msg->shift?MYATTR_DocBitmap_PageNew:MYATTR_DocBitmap_Page,dat->page+1);
	    set(obj,MYATTR_DocBitmap_Message,NULL);
	    break;

	  case st_prevpage:
	    set(obj,msg->shift?MYATTR_DocBitmap_PageNew:MYATTR_DocBitmap_Page,dat->page-1);
	    set(obj,MYATTR_DocBitmap_Message,NULL);
	    break;

	  case st_goback:
	    mBackward(cl,obj);
	    set(obj,MYATTR_DocBitmap_Message,NULL);
	    break;

	  case st_run:
	    set(obj,MYATTR_DocBitmap_Run,buf);
	    break;

	  case st_url:
	    set(obj,MYATTR_DocBitmap_URL,buf);
	    break;
	}
	if(state!=st_newdoc && state!=st_newdocnw)
	    state=st_init;
	if(dat->vgroup && (x!=-1 || y!=-1))
	    SetAttrs(dat->vgroup,
		     x==-1?TAG_IGNORE:MUIA_Virtgroup_Left,x,
		     y==-1?TAG_IGNORE:MUIA_Virtgroup_Top,y,
		     TAG_END);
    }
    return r;
}

Static void compute_cropbox(DocBitmapData *dat,struct MYS_DocBitmap_CropBox *p,
			    int x,int y,int dpi) {
    int x0=(x+dat->xoffset)*72/dat->dpi;
    int y0=(y+dat->yoffset)*72/dat->dpi;
    int w=dat->page_width;
    int h=dat->page_height;
    int w0=30;//*72/dat->dpi;
    int h0=30;//*72/dat->dpi;
    dat->last_lens_x=x;
    dat->last_lens_y=y;
    switch((dat->rotate+dat->page_rotate)%360) {
      case 90: {
	int t=x0;
	x0=y0;
	y0=h-t;
	break;
      }
      case 180:
	x0=w-x0;
	y0=h-y0;
	break;
      case 270: {
	int t=x0;
	x0=w-y0;
	y0=t;
	break;
      }
    }
    if(x0-w0<0)
	x0=w0;
    if(x0+w0>w)
	x0=w-w0;
    if(y0-h0<0)
	y0=h0;
    if(y0+h0>h)
	y0=h-h0;
    p->left=x0-w0;
    if(p->left<0)
	p->left=0;
    p->top=y0-h0;
    if(p->top<0)
	p->top=0;
    p->right=w-x0-w0;
    if(p->right<0)
	p->right=0;
    p->bottom=h-y0-h0;
    if(p->bottom<0)
	p->bottom=0;
}

Static ULONG mDestroyLens(struct IClass *cl,Object *obj) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    Object *app;
    get(obj,MUIA_ApplicationObject,&app);
    DoMethod(app,OM_REMMEMBER,dat->lens);
    MUI_DisposeObject(dat->lens);
    dat->lens=NULL;
}

Static ULONG mHandleInput(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg) {
    DocBitmapData* dat=INST_DATA(cl,obj);
    int x=_mleft(obj);
    int y=_mtop(obj);
    int w=_mwidth(obj);
    int h=_mheight(obj);
    x+=(w-dat->width-bleft-bright)/2+bleft;
    w=dat->width;
    y+=(h-dat->height-btop-bbottom)/2+btop;
    h=dat->height;

    #define _between(a,x,b) ((x)>=(a) && (x)<(b))
    #define _isinobject(u,v) (_between(x,(u),x+w) && _between(y,(v),y+h))

    if(msg->imsg) {
	if(dat->lens) {
	    switch(msg->imsg->Class) {
	      case IDCMP_MOUSEBUTTONS:
	      case IDCMP_RAWKEY:
		DoMethod(obj,MYM_DocBitmap_DestroyLens);
		break;
	      case IDCMP_MOUSEMOVE: {
		int dx=dat->last_lens_x-msg->imsg->MouseX;
		int dy=dat->last_lens_y-msg->imsg->MouseY;
		if(dx<-5 || dx>5 || dy<-5 || dy>5) {
		    struct MYS_DocBitmap_CropBox box;
		    compute_cropbox(dat,&box,msg->imsg->MouseX-x,msg->imsg->MouseY-y,300);
		    set(dat->lens,MYATTR_DocLens_CropBox,&box);
		}
		return 0;
	      }
	    }
	}

	switch(msg->imsg->Class) {
	  case IDCMP_MOUSEBUTTONS: {
	    int mx=msg->imsg->MouseX;
	    int my=msg->imsg->MouseY;
	    if(msg->imsg->Code==MIDDLEDOWN ||
	       (msg->imsg->Code==SELECTDOWN &&
		msg->imsg->Qualifier&IEQUALIFIER_CONTROL)) {
		if(_isinobject(mx,my)) {
		    struct MYS_DocBitmap_CropBox box;
		    int dpi=300;
		    compute_cropbox(dat,&box,mx-x,my-y,dpi);
		    dat->lens=DocLensObject,
			MYATTR_DocLens_CommInfo,dat->comm_info,
			MYATTR_DocLens_Document,dat->doc,
			MYATTR_DocLens_Page,dat->page,
			MYATTR_DocLens_DPI,dpi,
			MYATTR_DocLens_Rotate,dat->rotate,
			MYATTR_DocLens_CropBox,&box,
			MYATTR_DocLens_TextAA,dat->textAAlevel,
			MYATTR_DocLens_StrokeAA,dat->strokeAAlevel,
			MYATTR_DocLens_FillAA,dat->fillAAlevel,
			MUIA_Window_IsSubWindow,TRUE,
			End;
		    if(dat->lens) {
			Object *app;
			get(obj,MUIA_ApplicationObject,&app);
			if(app) {
			    DoMethod(app,OM_ADDMEMBER,dat->lens);
			    set(dat->lens,MUIA_Window_Open,TRUE);
			}
		    }
		}
	    } else if(msg->imsg->Code==SELECTDOWN) {
		if(_isinobject(mx,my)) {
		    struct LinkAction* action=dat->last_action;
		    char str[256];
		    dat->selection.MinX=mx-x;
		    dat->selection.MinY=my-y;
		    dat->selection.MaxX=dat->selection.MinX;
		    dat->selection.MaxY=dat->selection.MinY;
		    mx+=dat->xoffset;
		    my+=dat->yoffset;
		    if(chklink(dat,mx-x,my-y,&action,str,sizeof(str)) && action) {
			DoMethod(obj,MYM_DocBitmap_Action,action,
				 msg->imsg->Qualifier&(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT));
		    } else  {
			dat->selecting=TRUE;
			dat->selected=TRUE;
			MUI_Redraw(obj,MADF_DRAWUPDATE);
		    }
		} else {
		    dat->selected=FALSE;
		    dat->selecting=FALSE;
		}
	    } else if(msg->imsg->Code==SELECTUP) {
		if(dat->selecting) {
		    dat->selection.MaxX=mx-x;
		    if(dat->selection.MaxX<0)
			dat->selection.MaxX=0;
		    else if(dat->selection.MaxX>=w)
			dat->selection.MaxX=w-1;
		    dat->selection.MaxY=my-y;
		    if(dat->selection.MaxY<0)
			dat->selection.MaxY=0;
		    else if(dat->selection.MaxY>=h)
			dat->selection.MaxY=h-1;
		    dat->selected=TRUE;
		    dat->selecting=FALSE;
		    MUI_Redraw(obj,MADF_DRAWUPDATE);
		}
	    }
	    break;
	  }
	  case IDCMP_MOUSEMOVE: {
	    int mx=msg->imsg->MouseX;
	    int my=msg->imsg->MouseY;
	    if(dat->selecting) {
		dat->selection.MaxX=mx-x;
		if(dat->selection.MaxX<0)
		    dat->selection.MaxX=0;
		else if(dat->selection.MaxX>=w)
		    dat->selection.MaxX=w-1;
		dat->selection.MaxY=my-y;
		if(dat->selection.MaxY<0)
		    dat->selection.MaxY=0;
		else if(dat->selection.MaxY>=h)
		    dat->selection.MaxY=h-1;
		MUI_Redraw(obj,MADF_DRAWUPDATE);
	    } else if(_isinobject(mx,my)) {
		struct LinkAction* action=dat->last_action;
		char str[256];
		mx+=dat->xoffset;
		my+=dat->yoffset;
		if(chklink(dat,mx-x,my-y,&action,str,sizeof(str))) {
		    if(action!=dat->last_action) {
			dat->last_action=action;
			set(obj,MYATTR_DocBitmap_Message,str);
		    }
		} else if(dat->last_action) {
		    dat->last_action=NULL;
		    set(obj,MYATTR_DocBitmap_Message,NULL);
		}
	    }
	    break;
	  }
	  case IDCMP_RAWKEY: {
	    struct InputEvent ie={0};
	    char buf[4];
	    int n;
	    ie.ie_Class=IECLASS_RAWKEY;
	    ie.ie_SubClass=0;
	    ie.ie_Code=msg->imsg->Code;
	    ie.ie_Qualifier=msg->imsg->Qualifier;
	    ie.ie_EventAddress=*(APTR*)msg->imsg->IAddress;
	    n=MapRawKey(&ie,buf,sizeof(buf),NULL);
	    if(n==1) {
		switch(buf[0]) {
		  case 8:  /* BS */
		    upkey(cl,obj);
		    break;
		  case ' ':
		    downkey(cl,obj);
		    break;
		  case 'l':
		    DoMethod(obj,MYM_DocBitmap_RLeft);
		    break ;
		  case 'r':
		    DoMethod(obj,MYM_DocBitmap_RRight);
		    break ;
		  case 'c':
		    DoMethod(obj,MYM_DocBitmap_Crop);
		    break;
		  case 'u':
		    DoMethod(obj,MYM_DocBitmap_UnCrop);
		    break;
		  case '+':
		    if(dat->zoom<maxZoom-1)
			set(obj,MYATTR_DocBitmap_Zoom,dat->zoom+1);
		    break;
		  case '-':
		    if(dat->zoom>minZoom)
			set(obj,MYATTR_DocBitmap_Zoom,dat->zoom-1);
		    break;

		}
	    }
	    break;
	  }
	}
    }
    switch(msg->muikey) {
      case MUIKEY_UP:
	upkey(cl,obj);
	break;
      case MUIKEY_DOWN:
	downkey(cl,obj);
	break;
      case MUIKEY_PAGEUP:
	if(dat->page>1)
	    set(obj,MYATTR_DocBitmap_Page,dat->page-1);
	break;
      case MUIKEY_PAGEDOWN:
	if(dat->page<dat->num_pages)
	    set(obj,MYATTR_DocBitmap_Page,dat->page+1);
	break;
      case MUIKEY_TOP:
	set(obj,MYATTR_DocBitmap_Page,1);
	break;
      case MUIKEY_BOTTOM:
	set(obj,MYATTR_DocBitmap_Page,dat->num_pages);
	break;
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
    #undef _between
    #undef _isinobject
}

struct MYP_DocBitmap_Search {
    ULONG MethodID;
    const char *str;
    int top;
    int *xmin;
    int *ymin;
    int *xmax;
    int *ymax;
};

Static ULONG mSearch(struct IClass *cl,Object* obj,struct MYP_DocBitmap_Search *p) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    int r;
    *p->xmin+=dat->xoffset;
    *p->ymin+=dat->yoffset;
    *p->xmax+=dat->xoffset;
    *p->ymax+=dat->yoffset;
    r=simple_find(dat,p->str,p->top,p->xmin,p->ymin,p->xmax,p->ymax);
    if(r) {
	*p->xmin-=dat->xoffset;
	*p->ymin-=dat->yoffset;
	*p->xmax-=dat->xoffset;
	*p->ymax-=dat->yoffset;
    }
    return r;
}

Static ULONG mRefresh(struct IClass *cl,Object* obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    int z=dat->zoom;
    dat->zoom=-1;
    dat->last_dpi=-1;
    set(obj,MYATTR_DocBitmap_Zoom,z);
    return 0;
}

Static ULONG mCrop(struct IClass *cl,Object* obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    if(dat->selected) {
	int x1=dat->selection.MinX;
	int y1=dat->selection.MinY;
	int x2=dat->selection.MaxX;
	int y2=dat->selection.MaxY;
	int t1,t2;
	if(x1>x2) {
	    int t=x1;
	    x1=x2;
	    x2=t;
	}
	if(y1>y2) {
	    int t=y1;
	    y1=y2;
	    y2=t;
	}
	x1=(x1*72)/dat->dpi;
	y1=(y1*72)/dat->dpi;
	x2=((x2-dat->width)*72)/dat->dpi;
	y2=((y2-dat->height)*72)/dat->dpi;
	switch((dat->rotate+dat->page_rotate)%360) {
	  case 90:
	    t1=x1;
	    t2=x2;
	    x1=y1;
	    x2=y2;
	    y1=-t2;
	    y2=-t1;
	    break;
	  case 180:
	    t1=x1;
	    x1=-x2;
	    x2=-t1;
	    t2=y1;
	    y1=-y2;
	    y2=-t2;
	    break;
	  case 270:
	    t1=x1;
	    t2=x2;
	    x1=-y2;
	    x2=-y1;
	    y1=t1;
	    y2=t2;
	    break;
	}
	dat->cropx1+=x1;
	dat->cropy1+=y1;
	dat->cropx2+=x2;
	dat->cropy2+=y2;
	dat->selected=FALSE;
	mRefresh(cl,obj);
    }
    return 0;
}

Static ULONG mUnCrop(struct IClass *cl,Object* obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    dat->cropx1=0;
    dat->cropy1=0;
    dat->cropx2=0;
    dat->cropy2=0;
    dat->selected=FALSE;
    mRefresh(cl,obj);
    return 0;
}

Static ULONG mToClip(struct IClass *cl,Object* obj) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    char* txt;
    ULONG ret=0;
    if(dat->selected) {
	int x1=dat->selection.MinX+dat->xoffset;
	int y1=dat->selection.MinY+dat->yoffset;
	int x2=dat->selection.MaxX+dat->xoffset;
	int y2=dat->selection.MaxY+dat->yoffset;
	if(x1>x2) {
	    int t=x1;
	    x1=x2;
	    x2=t;
	}
	if(y1>y2) {
	    int t=y1;
	    y1=y2;
	    y2=t;
	}
	if(txt=gettext(dat,x1,y1,x2,y2)) {
	    struct IFFHandle *iff;
	    if(iff=AllocIFF()) {
		if(iff->iff_Stream=(ULONG)OpenClipboard(PRIMARY_CLIP)) {
		    size_t sz=strlen(txt);
		    InitIFFasClip(iff);
		    if(!OpenIFF(iff,IFFF_WRITE)) {
			if(!PushChunk(iff,MAKE_ID('F','T','X','T'),ID_FORM,IFFSIZE_UNKNOWN) &&
			   !PushChunk(iff,0,MAKE_ID('C','H','R','S'),sz) &&
			   WriteChunkBytes(iff,txt,sz)==sz &&
			   !PopChunk(iff) &&
			   !PopChunk(iff))
			    ret=1;
			CloseIFF(iff);
		    }
		    CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
		}
		FreeIFF(iff);
	    }
	    MyFreeMem(txt);
	}
    }
    return ret;
}

Static ULONG mDragQuery(struct IClass *cl,Object* obj,struct MUIP_DragDrop *msg) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    Object *doc;
    get(msg->obj,MYATTR_DocBitmap_BoxGrabber,&doc);
    return doc==dat->doc?MUIV_DragQuery_Accept:MUIV_DragQuery_Refuse;
}

Static ULONG mDragDrop(struct IClass *cl,Object* obj,struct MUIP_DragDrop *msg) {
    DocBitmapData *dat=INST_DATA(cl,obj);
    Object *doc;
    get(msg->obj,MYATTR_DocBitmap_BoxGrabber,&doc);
    if(doc==dat->doc) {
	struct MYS_DocBitmap_CropBox box;
	box.left=dat->cropx1;
	box.top=dat->cropy1;
	box.right=-dat->cropx2;
	box.bottom=-dat->cropy2;
	set(msg->obj,MYATTR_DocBitmap_CropBox,&box);
    }
    return 0;
}

SAVEDS ASM ULONG dispatcher(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1, Msg msg)) {
    switch(msg->MethodID) {
      case OM_NEW          : return mNew        (cl,obj,(APTR)msg);
      case OM_DISPOSE      : return mDispose    (cl,obj,(APTR)msg);
      case OM_SET          : return mSet        (cl,obj,(APTR)msg);
      case OM_GET          : return mGet        (cl,obj,(APTR)msg);
      case MUIM_Setup      : return mSetup      (cl,obj,(APTR)msg);
      case MUIM_Cleanup    : return mCleanup    (cl,obj,(APTR)msg);
      case MUIM_Show       : return mShow       (cl,obj,(APTR)msg);
      case MUIM_Hide       : return mHide       (cl,obj,(APTR)msg);
      case MUIM_AskMinMax  : return mAskMinMax  (cl,obj,(APTR)msg);
      case MUIM_Draw       : return mDraw       (cl,obj,(APTR)msg);
      case MUIM_HandleInput: return mHandleInput(cl,obj,(APTR)msg);
      case MUIM_DragQuery  : return mDragQuery  (cl,obj,(APTR)msg);
      case MUIM_DragDrop   : return mDragDrop   (cl,obj,(APTR)msg);
      case MYM_DocBitmap_Reset:         return mReset(cl,obj,(APTR)msg);
      case MYM_DocBitmap_Search:        return mSearch(cl,obj,(APTR)msg);
      case MYM_DocBitmap_ToClip:        return mToClip(cl,obj);
      case MYM_DocBitmap_RLeft:         return mRLeft(cl,obj);
      case MYM_DocBitmap_RRight:        return mRRight(cl,obj);
      case MYM_DocBitmap_Crop:          return mCrop(cl,obj);
      case MYM_DocBitmap_UnCrop:        return mUnCrop(cl,obj);
      case MYM_DocBitmap_Refresh:       return mRefresh(cl,obj);
      case MYM_DocBitmap_Action:        return mAction(cl,obj,(APTR)msg);
      case MYM_DocBitmap_Backward:      return mBackward(cl,obj);
      case MYM_DocBitmap_Forward:       return mForward(cl,obj);
      case MYM_DocBitmap_HistoryPush:   return mHistoryPush(cl,obj,(APTR)msg);
      case MYM_DocBitmap_DestroyLens:   return mDestroyLens(cl,obj);
    }
    return DoSuperMethodA(cl,obj,msg);
}


struct MUI_CustomClass *docbitmap_mcc;
static int count;

BOOL createDocBitmapClass(void) {
    if(count++==0) {
	if(vgroup_mcc=MUI_CreateCustomClass(NULL,MUIC_Virtgroup,NULL,0,vgDispatcher)) {
	    if(docbitmap_mcc=MUI_CreateCustomClass(NULL,MUIC_Area,NULL,sizeof(DocBitmapData),dispatcher))
		return TRUE;
	    MUI_DeleteCustomClass(vgroup_mcc);
	}
	count=0;
	return FALSE;
    }
    return TRUE;
}

BOOL deleteDocBitmapClass(void) {
    if(count && --count==0) {
	MUI_DeleteCustomClass(docbitmap_mcc);
	MUI_DeleteCustomClass(vgroup_mcc);
    }
    return TRUE;
}

#if defined(HAVE_CYBERGRAPHICS_CYBERGRAPHICS_H) || \
    defined(HAVE_CYBERGRAPHX_CYBERGRAPHICS_H)
void MyWritePixelArray(APTR src,UWORD sx,UWORD sy,UWORD mod,struct RastPort* rp,UWORD x,UWORD y,UWORD w,UWORD h,UBYTE fmt) {
    WritePixelArray(src,sx,sy,mod,rp,x,y,w,h,fmt);
}
#endif

