//========================================================================
//
// Apdf.c
//
// Copyright 1999 Emmanuel Lesueur
//
//========================================================================

//#define DEBUGMSG
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libraries/asl.h>
#include <libraries/mui.h>
#include <libraries/gadtools.h>
#include <libraries/iffparse.h>
#include <dos/dostags.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <graphics/layers.h>
#include <hardware/blit.h>
#include <cybergraphics/cybergraphics.h>
#include <workbench/startup.h>
#include <workbench/workbench.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>
#include <proto/cybergraphics.h>
#include <proto/icon.h>
#include <proto/diskfont.h>
#include <proto/iffparse.h>
#include "config.h"
#include "AComm.h"

#ifdef POWERUP
#   include <powerup/ppclib/message.h>
#   include <powerup/ppclib/tasks.h>
#   include <powerup/proto/ppc.h>
#   include "AGfxcomm.h"
#endif

#define DB(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 STR(x)   #x
#   define REG(r,a) a __asm(STR(r))
#endif

static void copystr(char** p,const char* q) {
    if(q) {
	size_t l=strlen(q)+1;
	char* t=malloc(l);
	if(t) {
	    if(*p)
		free(*p);
	    *p=t;
	    memcpy(t,q,l);
	}
    }
}

static char* gzipcmd_arg;
static char* urlcmd_arg;
static char* deficon_arg;
static char* diskobj_arg;
static int page_arg=1;
static int zoom_arg=1;
static int colors_arg=16;
static int cachesz_arg=256;
static int blocsz_arg=4;
#define TEMPLATE "PDFFILE,P=PAGE/N/K,Z=ZOOM/N/K,C=COLORS/N/K,G=GZIPCMD/K,U=URLCMD/K,I=DEFICON/K,F=FONTMAP/M,D=DISKOBJECT/K,S=CACHESIZE/N/K,B=CACHEBLOCSIZE/N/K"
#define PDFFILE ((const char*)args[0])
#define PAGE    (args[1]?*(int*)args[1]:page_arg)
#define ZOOM    (args[2]?*(int*)args[2]:zoom_arg)
#define COLORS  (args[3]?*(int*)args[3]:16)
#define GZIPCMD (args[4]?(const char*)args[4]:(gzipcmd_arg?gzipcmd_arg:"gzip -d -q"))
#define URLCMD  (args[5]?(const char*)args[5]:(urlcmd_arg?urlcmd_arg:"OpenURL %s"))
#define DEFICON (args[6]?(const char*)args[6]:(deficon_arg?deficon_arg:"env:sys/def_icon"))
#define FONTMAP ((const char**)args[7])
#define DISKOBJ (args[8]?(const char*)args[8]:diskobj_arg)
#define CACHESZ (args[9]?*(int*)args[9]:cachesz_arg)
#define BLOCSZ  (args[10]?*(int*)args[10]:blocsz_arg)
#define NARGS   11
#define SET_PAGE(x)    (args[1]=NULL,page_arg=x)
#define SET_ZOOM(x)    (args[2]=NULL,zoom_arg=x)
#define SET_COLORS(x)  (args[3]=NULL,colors_arg=x)
#define SET_CACHESZ(x) (args[9]=NULL,cachesz_arg=x)
#define SET_BLOCSZ(x)  (args[10]=NULL,blocsz_arg=x)
static LONG args[NARGS];


#if defined(__SASC) || defined(__libnix__)
char __stdiowin[]="CON://600/200/Apdf/AUTO/WAIT/CLOSE";
extern struct WBStartup *_WBenchMsg;

static void parse_tooltypes(BPTR lock,const char* name) {
    struct DiskObject *dob;
    BPTR olddir=CurrentDir(lock);
    if(dob=GetDiskObject((char*)name)) {
	if(dob->do_ToolTypes) {
	    char* s=FindToolType(dob->do_ToolTypes,"PAGE");
	    if(s)
		SET_PAGE(atoi(s));
	    s=FindToolType(dob->do_ToolTypes,"ZOOM");
	    if(s)
		SET_ZOOM(atoi(s));
	    s=FindToolType(dob->do_ToolTypes,"COLORS");
	    if(s)
		SET_COLORS(atoi(s));
	    s=FindToolType(dob->do_ToolTypes,"GZIPCMD");
	    if(s)
		copystr(&gzipcmd_arg,s);
	    s=FindToolType(dob->do_ToolTypes,"URLCMD");
	    if(s)
		copystr(&urlcmd_arg,s);
	    s=FindToolType(dob->do_ToolTypes,"DEFICON");
	    if(s)
		copystr(&deficon_arg,s);
	    s=FindToolType(dob->do_ToolTypes,"DISKOBJECT");
	    if(s)
		copystr(&diskobj_arg,s);
	    s=FindToolType(dob->do_ToolTypes,"CACHESIZE");
	    if(s)
		SET_CACHESZ(atoi(s));
	    s=FindToolType(dob->do_ToolTypes,"CACHEBLOCSIZE");
	    if(s)
		SET_BLOCSZ(atoi(s));
	}
	FreeDiskObject(dob);
    }
    CurrentDir(olddir);
}
#endif



#define MYTAG_START       TAG_USER
#define MYATTR_Page       (MYTAG_START+1)
#define MYATTR_Zoom       (MYTAG_START+2)
#define MYATTR_Selected   (MYTAG_START+5)
#define MYATTR_Selection  (MYTAG_START+6)
#define MYATTR_NewDoc     (MYTAG_START+7)
#define MYATTR_NumPages   (MYTAG_START+8)
#define MYATTR_Value      (MYTAG_START+9)
#define MYATTR_ListID     (MYTAG_START+10)

#define MYM_Reset         (MYTAG_START+100)

#define ID_ABOUT          1
#define ID_ABOUTMUI       2
#define ID_SEARCH         3
#define ID_OPEN           4
#define ID_SAVEAS         5
#define ID_WRITE          6
#define ID_MUIPREFS       7
#define ID_COPY           8
#define ID_DEFAULTFONTS   9
#define ID_DEFFONTS       10
#define ID_DOCFONTS       11
#define ID_APPLYFONTMAP   12
#define ID_SCANFONTS      13
#define ID_ABORT          14
#define ID_OPENSCANFONTS  15
#define ID_PRINT          16

#define MYASSERT(x) if(!(x)) { printf("Internal error: %s/%d\n",__FILE__,__LINE__); exit(EXIT_FAILURE); }

#define  ID_FTXT        MAKE_ID('F','T','X','T')
#define  ID_CHRS        MAKE_ID('C','H','R','S')

#if defined(__SASC) || defined(__libnix__)
long __stack=30000;
#endif

#define btop    1
#define bbottom 1
#define bleft   1
#define bright  1

struct IntuitionBase* IntuitionBase;
struct GfxBase* GfxBase;
struct Library* LayersBase;
struct UtilityBase* UtilityBase;
struct Library* MUIMasterBase;
struct Library* PPCLibBase;
struct Library* CyberGfxBase;
struct Library* IconBase;
struct Library* DiskfontBase;
struct Library* IFFParseBase;

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

BPTR input,oldinput;
BPTR progdir;
char* progname;
BPTR docdir;
char* docname;
struct RDArgs* rda;
struct DiskObject* diskobj;
struct MUI_CustomClass* mcc;
struct MUI_CustomClass* slider_mcc;
struct MUI_CustomClass* rotate_slider_mcc;
struct MUI_CustomClass* list_mcc;
Object* App;
Object* pageobj;
Object* zoomobj;
Object* bitmapobj;
Object* vgroup;
Object* win;
Object* linkobj;
Object* searchobj;
Object* psstartobj;
Object* psstopobj;
Object* scanstartobj;
Object* scanstopobj;
Object* pagegroup;
Object* gaugewin;
Object* gaugeobj;
Object* abortobj;
struct FileRequester* req;
BPTR olddir;
#ifdef POWERUP
void* elfobject;
PPCPort* port;
PPCPort* reply_port;
PPCPortList* portlist;
PPCMsg* startupmsg;
struct StartupData* startup_data;
size_t startup_length;
PPCTask* ppctask;
PPCPort* ports[3];
PPCMsg* quitmsg;
PPCMsg* cmdmsg;
BOOL cmd_sent;
void* cmddata;
PPCPort* ppcport;
BOOL quitting=FALSE;
BOOL ppc_quitted=TRUE;
#endif

struct clip_rect {
    struct clip_rect *next;
    short minx;
    short miny;
    short maxx;
    short maxy;
};

struct clip_poly {
    struct clip_poly *next;
    int num;
    short data[1];
};

struct font_entry {
    struct TextFont *font;
    short style;
    short pad;
};

static short area_minx,area_miny,area_maxx,area_maxy;
static short* area_buf;

void cleararea(void) {
    DB(printf("--cleararea\n");)
    if(area_buf) {
	free(area_buf);
	area_buf=NULL;
    }
}

void initarea(struct RastPort* rp,int n) {
    static struct AreaInfo areainfo;
    DB(printf("--initarea(%d)\n",n);)
    cleararea();
    if(area_buf=malloc((n+1)*5)) {
	InitArea(&areainfo,area_buf,n+1);
	rp->AreaInfo=&areainfo;
    }
    area_minx=32767;
    area_miny=32767;
    area_maxx=-32768;
    area_maxy=-32768;
}

void areapoly(struct RastPort* rp,int n,short* p,short dx,short dy) {
    DB(printf("--areapoly(%d,%d,%d)\n",n,dx,dy);)
    if(rp->AreaInfo) {
	int k=n;
	while(--k>=0) {
	    if(*p>area_maxx)
		area_maxx=*p;
	    if(*p<area_minx)
		area_minx=*p;
	    ++p;
	    if(*p>area_maxy)
		area_maxy=*p;
	    if(*p<area_miny)
		area_miny=*p;
	    ++p;
	}
	p-=2*n;
	AreaMove(rp,p[0]-dx,p[1]-dy);
	while(--n) {
	    p+=2;
	    AreaDraw(rp,p[0]-dx,p[1]-dy);
	}
    }
}

void areaend(struct RastPort* rp) {
    DB(printf("--areaend\n");)
    if(rp->AreaInfo) {
	int w=area_maxx-area_minx+17;
	int h=area_maxy-area_miny+1;
	void* plane;
	if(plane=AllocRaster(w,h)) {
	    struct TmpRas tmpras;
	    InitTmpRas(&tmpras,plane,RASSIZE(w,h));
	    rp->TmpRas=&tmpras;
	    AreaEnd(rp);
	    WaitBlit();
	    rp->TmpRas=NULL;
	    FreeRaster(plane,w,h);
	}
	free(area_buf);
	area_buf=NULL;
	rp->AreaInfo=NULL;
    }
}




/* workaround for a bug in egcs */
static void MyReadPixelArray(APTR dest,UWORD dx,UWORD dy,UWORD mod,struct RastPort* rp,UWORD x,UWORD y,UWORD w,UWORD h,UBYTE fmt) {
    ReadPixelArray(dest,dx,dy,mod,rp,x,y,w,h,fmt);
}
static 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);
}


struct DocData {
    int width;
    int height;
    int page;
    int num_pages;
    int page_width;
    int page_height;
    int zoom;
    int last_page;
    int last_width;
    int last_height;
    int last_zoom;
    struct ColorMap* colormap;
    struct BitMap *friendbm;
    struct BitMap *bm;
    struct Layer_Info *layerinfo;
    struct Layer *layer;
    struct Region *region;
    struct BitMap *clipbm;
    struct Layer_Info *cliplayerinfo;
    struct Layer *cliplayer;
    struct Region *clipregion;
    struct Region *clipregion2;
    struct clip_rect *rects;
    struct clip_poly *polys;
    Object* obj;
    struct Rectangle selection;
    struct Rectangle old_selection;
    struct font_entry *fonts;
    int maxfont;
    ULONG* pens;
    int curpen;
    int maxpen;
    struct RastPort rp;
    short depth;
    short dx;
    short dy;
    short clipwidth;
    short clipheight;
    short minx;
    short maxx;
    short miny;
    short maxy;
    BOOL hidden;
    BOOL selected;
    BOOL selecting;
    BOOL select_displayed;
#ifndef POWERUP
    struct RastPort** rpp;
    short* dxp;
    short* dyp;
#endif
};
typedef struct DocData DocData;

#ifndef POWERUP
void initgfx(DocData** dp,struct RastPort** rpp,short* dxp,short* dyp) {
    DocData* dat=INST_DATA(mcc->mcc_Class,bitmapobj);
    *dp=dat;
    *rpp=&dat->rp;
    dat->rpp=rpp;
    dat->dxp=dxp;
    dat->dyp=dyp;
}
#endif

/*
 *  Pen sharing functions.
 */

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

static void clear_pens(DocData* 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 add_pen(DocData* dat,ULONG r,ULONG g,ULONG b) {
    DB(printf("--addpen %d=%08lx,%08lx,%08lx\n",dat->curpen,r,g,b);)
    if(dat->curpen>=dat->maxpen) {
	int maxpen=((dat->curpen*3)>>1)+16;
	ULONG* p=realloc(dat->pens,maxpen*sizeof(*dat->pens));
	if(p) {
	    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 add_pens(DocData* dat,ULONG* t,int num) {
    while(--num>=0) {
	t[0]=add_pen(dat,t[1],t[2],t[3]);
	GetRGB32(dat->colormap,t[0],1,t+1);
	t+=4;
    }
}

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

/*
 *  Font handling functions.
 */

void openfont(DocData* dat,int id,const char* name,short size,short style) {
    if(id>=dat->maxfont) {
	int maxfont=(id*3)/2+16;
	struct font_entry* p=realloc(dat->fonts,maxfont*sizeof(*p));
	if(p) {
	    memset(p+dat->maxfont,0,(maxfont-dat->maxfont)*sizeof(*p));
	    dat->fonts=p;
	    dat->maxfont=maxfont;
	}
    }
    if(id<dat->maxfont) {
	struct TextAttr ta;
	ta.ta_Name=(char*)name;
	ta.ta_YSize=size;
	ta.ta_Style=style;
	ta.ta_Flags=0;
	dat->fonts[id].style=style;
	if(!(dat->fonts[id].font=OpenDiskFont(&ta)))
	    printf("Can't open font \"%s\"/%d.\n",name,size);
    }
}


void closefont(DocData* dat,int id) {
    if(id<dat->maxfont && dat->fonts[id].font) {
	CloseFont(dat->fonts[id].font);
	dat->fonts[id].font=NULL;
    }
}

void setfont(DocData* dat,int id) {
    if(id<dat->maxfont && dat->fonts[id].font) {
	SetFont(&dat->rp,dat->fonts[id].font);
	SetSoftStyle(&dat->rp,dat->fonts[id].style,FSF_BOLD|FSF_ITALIC);
    }
}

static void clear_fonts(DocData* dat) {
    int k;
    for(k=0;k<dat->maxfont;++k)
	if(dat->fonts[k].font)
	    CloseFont(dat->fonts[k].font);
    free(dat->fonts);
    dat->fonts=NULL;
    dat->maxfont=0;
}

/*
 *  Clip handling functions.
 */

static void free_clip_polys(DocData *dat) {
    struct clip_poly *p;
    DB(printf("--free_clip_polys\n");)
    p=dat->polys;
    while(p) {
	struct clip_poly *q=p->next;
	free(p);
	p=q;
    }
    dat->polys=NULL;
}

static void free_clip_rects(DocData *dat) {
    struct clip_rect *p;
    DB(printf("--free_clip_rects\n");)
    p=dat->rects;
    while(p) {
	struct clip_rect *q=p->next;
	free(p);
	p=q;
    }
    dat->rects=NULL;
}

static void clip_path(DocData* dat,int n,short* p) {
    short dx=dat->dx;
    short dy=dat->dy;
    if(dx || dy) {
	while(--n>=0) {
	    *p++-=dx;
	    *p++-=dy;
	}
    }
}

static void tfr_clip(DocData *dat) {
    DB(printf("--tfr_clip\n");)
    if(dat->bm && dat->polys && dat->clipbm) {
	/*
	 * Non rectangular clipping region.
	 *
	 * Ideally, we would draw a mask and call
	 * BltMaskBitMapRastPort. However, due to the
	 * bogus way the mask parameter is given to this function
	 * it is quite unusable with anything but standard
	 * non-interleaved bitmaps. So we emulate this
	 * function instead. This is slow, uses lots of memory
	 * and is non-optimal, but this is rarely used anyway...
	 */
	struct BitMap *bm1;
	struct BitMap *bm2;
	struct RastPort rp1;
	struct RastPort rp2;
	UBYTE* mask;
	UBYTE* src;
	UBYTE* dest;
	size_t sz=((dat->clipwidth+15)&~15)*dat->clipheight+4;
	DB(printf("--non trivial tfr_clip\n");)
	InitRastPort(&rp1);
	if(mask=malloc(sz)) {
	    if(bm1=AllocBitMap(dat->clipwidth,dat->clipheight,1,BMF_MINPLANES,dat->bm)) {
		if(bm2=AllocBitMap(dat->clipwidth,1,1,BMF_MINPLANES,bm1)) {
		    struct Layer_Info *layerinfo;
		    struct Layer *layer;
		    rp1.BitMap=bm1;
		    if(layerinfo=NewLayerInfo()) {
			if(layer=CreateUpfrontLayer(layerinfo,bm1,0,0,dat->clipwidth-1,dat->clipheight-1,LAYERSIMPLE,NULL)) {
			    int n;
			    struct clip_rect *q;
			    struct clip_poly *p;
			    InstallClipRegion(dat->cliplayer,NULL);
			    InstallClipRegion(layer,dat->clipregion2);
			    rp1.Layer=layer;
			    SetAPen(&rp1,1);
			    for(q=dat->rects;q;q=q->next)
				RectFill(&rp1,q->minx-dat->dx,q->miny-dat->dy,q->maxx-dat->dx,q->maxy-dat->dy);
			    n=0;
			    for(p=dat->polys;p;p=p->next)
				n+=p->num+1;
			    initarea(&rp1,n);
			    for(p=dat->polys;p;p=p->next)
				areapoly(&rp1,p->num,p->data,dat->dx,dat->dy);
			    areaend(&rp1);
			    InstallClipRegion(layer,NULL);
			    InstallClipRegion(dat->cliplayer,dat->clipregion2);
			    rp1.Layer=NULL;
			    rp2=rp1;
			    rp2.BitMap=bm2;
			    ReadPixelArray8(&rp1,0,0,dat->clipwidth-1,dat->clipheight-1,mask,&rp2);
			    DeleteLayer(0,layer);
			}
			DisposeLayerInfo(layerinfo);
		    }
		    FreeBitMap(bm2);
		}
		FreeBitMap(bm1);
	    }
	    if(dat->depth<=8) {
		if(src=malloc(sz)) {
		    if(bm2=AllocBitMap(dat->clipwidth,1,dat->depth,BMF_MINPLANES,dat->bm)) {
			rp2=dat->rp;
			rp2.BitMap=bm2;
			rp2.Layer=NULL;
			dat->rp.BitMap=dat->clipbm;
			dat->rp.Layer=NULL;
			ReadPixelArray8(&dat->rp,0,0,dat->clipwidth-1,dat->clipheight-1,src,&rp2);
			if(dest=malloc(sz)) {
			    UBYTE *s=src;
			    UBYTE *d=dest;
			    UBYTE *m=mask;
			    UBYTE x=(1<<dat->depth)-1;
			    dat->rp.BitMap=dat->bm;
			    ReadPixelArray8(&dat->rp,dat->dx,dat->dy,dat->dx+dat->clipwidth-1,dat->dy+dat->clipheight-1,dest,&rp2);
			    do {
				UBYTE t=((BYTE)(*m<<7)>>7)&x;
				*d=(*d&~t)|(*s&t);
				++d;
				++s;
				++m;
			    } while(--sz);
			    dat->rp.Layer=dat->layer;
			    WritePixelArray8(&dat->rp,dat->dx,dat->dy,dat->dx+dat->clipwidth-1,dat->dy+dat->clipheight-1,dest,&rp2);
			    free(dest);
			}
			FreeBitMap(bm2);
		    }
		    free(src);
		}
	    } else {
		if(src=malloc(sz*3)) {
		    UWORD mod=((dat->clipwidth+15)&~15)*3;
		    dat->rp.BitMap=dat->clipbm;
		    dat->rp.Layer=NULL;
		    MyReadPixelArray(src,0,0,mod,&dat->rp,0,0,dat->clipwidth,dat->clipheight,RECTFMT_RGB);
		    if(dest=malloc(sz*3)) {
			UBYTE *s=src;
			UBYTE *d=dest;
			UBYTE *m=mask;
			dat->rp.BitMap=dat->bm;
			MyReadPixelArray(dest,0,0,mod,&dat->rp,dat->dx,dat->dy,dat->clipwidth,dat->clipheight,RECTFMT_RGB);
			do {
			    UBYTE t=(BYTE)(*m<<7)>>7;
			    *d=(*d&~t)|(*s&t);
			    ++d;
			    ++s;
			    *d=(*d&~t)|(*s&t);
			    ++d;
			    ++s;
			    *d=(*d&~t)|(*s&t);
			    ++d;
			    ++s;
			    ++m;
			} while(--sz);
			dat->rp.Layer=dat->layer;
			MyWritePixelArray(dest,0,0,mod,&dat->rp,dat->dx,dat->dy,dat->clipwidth,dat->clipheight,RECTFMT_RGB);
			free(dest);
		    }
		    free(src);
		}
	    }
	    free(mask);
	}
    }
}

void clear_clip(DocData* dat) {
    DB(printf("--clear_clip\n");)
    if(dat->polys) {
	tfr_clip(dat);
	free_clip_polys(dat);
    }
    free_clip_rects(dat);
    dat->rp.Layer=dat->layer;
    dat->rp.BitMap=dat->bm;
    if(dat->clipregion) {
	InstallClipRegion(dat->layer,dat->region);
	DisposeRegion(dat->clipregion);
	dat->clipregion=NULL;
    }
    if(dat->clipregion2) {
	InstallClipRegion(dat->cliplayer,NULL);
	DisposeRegion(dat->clipregion2);
	dat->clipregion2=NULL;
    }
    if(dat->cliplayer) {
	DeleteLayer(0,dat->cliplayer);
	dat->cliplayer=NULL;
    }
    if(dat->cliplayerinfo) {
	DisposeLayerInfo(dat->cliplayerinfo);
	dat->cliplayerinfo=NULL;
    }
    FreeBitMap(dat->clipbm);
    dat->clipbm=NULL;
    dat->dx=0;
    dat->dy=0;
#ifndef POWERUP
    *dat->dxp=0;
    *dat->dyp=0;
#endif
}

void init_clip(DocData* dat) {
    DB(printf("--init_clip\n");)
    clear_clip(dat);
    dat->minx=dat->width;
    dat->miny=dat->height;
    dat->maxx=-1;
    dat->maxy=-1;
}

void rect_clip(DocData* dat,short x1,short y1,short x2,short y2) {
    struct clip_rect *q;
    DB(printf("--rect_clip (%d,%d)-(%d,%d)\n",x1,y1,x2,y2);)
    if(dat->minx>x1)
	dat->minx=x1;
    if(dat->miny>y1)
	dat->miny=y1;
    if(dat->maxx<x2)
	dat->maxx=x2;
    if(dat->maxy<y2)
	dat->maxy=y2;
    if(q=malloc(sizeof(*q))) {
	q->next=dat->rects;
	dat->rects=q;
	q->minx=x1;
	q->miny=y1;
	q->maxx=x2;
	q->maxy=y2;
    }
}

void poly_clip(DocData *dat,int n,short* p) {
    DB(printf("--poly_clip %d:",n);)
    if(n) {
	size_t sz=sizeof(struct clip_poly)+(n*2-1)*sizeof(short);
	struct clip_poly *q;
	if(q=malloc(sz)) {
	    q->next=dat->polys;
	    dat->polys=q;
	    q->num=n;
	    memcpy(q->data,p,n*2*sizeof(short));
	}
	do {
	    DB(printf(" (%d,%d)",p[0],p[1]);)
	    if(dat->minx>p[0])
		dat->minx=p[0];
	    if(dat->maxx<p[0])
		dat->maxx=p[0];
	    if(dat->miny>p[1])
		dat->miny=p[1];
	    if(dat->maxy<p[1])
		dat->maxy=p[1];
	    p+=2;
	} while(--n);
    }
    DB(printf("\n");)
}

void install_clip(DocData* dat) {
    DB(printf("--install_clip\n");)
    dat->rp.Layer=dat->layer;
    if(dat->polys) {
	if(dat->minx<0)
	    dat->minx=0;
	if(dat->miny<0)
	    dat->miny=0;
	if(dat->maxx>dat->width-1)
	    dat->maxx=dat->width-1;
	if(dat->maxy>dat->height-1)
	    dat->maxy=dat->height-1;
	dat->clipwidth=dat->maxx-dat->minx+1;
	dat->clipheight=dat->maxy-dat->miny+1;
	DB(printf("--install_clip %d×%d\n",dat->clipwidth,dat->clipheight);)
	if(dat->clipwidth>0 && dat->clipheight>0 &&
	   (dat->clipbm=AllocBitMap(dat->clipwidth,dat->clipheight,dat->depth,BMF_MINPLANES,dat->bm))) {
	    if(dat->cliplayerinfo=NewLayerInfo()) {
		if(dat->cliplayer=CreateUpfrontHookLayer(dat->cliplayerinfo,
							 dat->clipbm,
							 0,0,
							 dat->clipwidth-1,
							 dat->clipheight-1,
							 LAYERSIMPLE,
							 LAYERS_NOBACKFILL,
							 NULL)) {
		    if(dat->clipregion2=NewRegion()) {
			struct Rectangle rect;
			rect.MinX=0;
			rect.MinY=0;
			rect.MaxX=dat->clipwidth-1;
			rect.MaxY=dat->clipheight-1;
			if(OrRectRegion(dat->clipregion2,&rect)) {
			    InstallClipRegion(dat->cliplayer,dat->clipregion2);
			    DB(printf("--installed.\n");)
			    dat->rp.BitMap=dat->clipbm;
			    dat->rp.Layer=dat->cliplayer;
			    dat->dx=dat->minx;
			    dat->dy=dat->miny;
#ifndef POWERUP
			    *dat->dxp=dat->dx;
			    *dat->dyp=dat->dy;
#endif
			    BltBitMapRastPort(dat->bm,dat->dx,dat->dy,&dat->rp,0,0,dat->clipwidth,dat->clipheight,0xc0);
			    return;
			}
			DisposeRegion(dat->clipregion2);
			dat->clipregion2=NULL;
		    }
		    DeleteLayer(0,dat->cliplayer);
		    dat->cliplayer=NULL;
		}
		DisposeLayerInfo(dat->cliplayerinfo);
		dat->cliplayerinfo=NULL;
	    }
	    FreeBitMap(dat->clipbm);
	    dat->clipbm=NULL;
	} else
	    printf("Can't allocate bitmap\n");
    } else if(dat->rects) {
	if(dat->clipregion=NewRegion()) {
	    struct clip_rect *p;
	    struct Rectangle rect;
	    for(p=dat->rects;p;p=p->next) {
		rect.MinX=p->minx;
		rect.MinY=p->miny;
		rect.MaxX=p->maxx;
		rect.MaxY=p->maxy;
		OrRectRegion(dat->clipregion,&rect);
	    }
	    rect.MinX=0;
	    rect.MinY=0;
	    rect.MaxX=dat->width-1;
	    rect.MaxY=dat->height-1;
	    AndRectRegion(dat->clipregion,&rect); /* Can't fail */
	    InstallClipRegion(dat->layer,dat->clipregion);
	} else
	    InstallClipRegion(dat->layer,dat->region);
    } else {
	DB(printf("--simple install_clip\n");)
	InstallClipRegion(dat->layer,dat->region);
    }
}

/*
 *  Misc functions.
 */

static void sleep(BOOL x) {
    if(App)
	set(App,MUIA_Application_Sleep,x);
}

static void quit(void) {
#ifdef POWERUP
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,quitmsg,NULL,0,MSGID_QUIT);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
    quitting=TRUE;
#else
    exit(EXIT_SUCCESS);
#endif
}


/*
 *  PowerUP inter process communication functions.
 */

#ifdef POWERUP

static int get_info(int* num_pages) {
    struct msg_info* p=cmddata;
    PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_INFO);
    PPCWaitPort(reply_port);
    PPCGetMessage(reply_port);
    *num_pages=p->num_pages;
    return p->base.success;
}

static int get_page_size(int page,int* width,int* height) {
    struct msg_pagesize* p=cmddata;
    p->page=page;
    PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_PAGESIZE);
    PPCWaitPort(reply_port);
    PPCGetMessage(reply_port);
    *width=p->width;
    *height=p->height;
    return p->base.success;
}

static int create_doc(BPTR dir,const char* filename,size_t cachesz,size_t blocsz) {
    if(!quitting && !ppc_quitted) {
	struct msg_createdoc* p=cmddata;
	p->dir=dir;
	strncpy(p->filename,filename,sizeof(p->filename)-1);
	p->filename[sizeof(p->filename)-1]='\0';
	p->cachesz=cachesz;
	p->blocsz=blocsz;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_CREATEDOC);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	return p->base.success;
    }
    return 0;
}

#if 0
static void delete_doc(void) {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_DELETEDOC);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}
#endif

static void create_output_dev(int depth,struct ColorMap* colormap) {
    if(!quitting && !ppc_quitted) {
	struct msg_create_output_dev* p=(struct msg_create_output_dev*)cmddata;
	p->depth=depth;
	p->colormap=colormap;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_CREATEOUTPUTDEV);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

static void delete_output_dev(void) {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_DELETEOUTPUTDEV);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

static int simple_find(const char* str,int top,int* xmin,int* ymin,int* xmax,int* ymax) {
    if(!quitting && !ppc_quitted) {
	struct msg_find* p=cmddata;
	p->top=top;
	p->xmin=*xmin;
	p->ymin=*ymin;
	p->xmax=*xmax;
	p->ymax=*ymax;
	strcpy(p->str,str);
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_SIMPLEFIND);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	*xmin=p->xmin;
	*ymin=p->ymin;
	*xmax=p->xmax;
	*ymax=p->ymax;
	return p->base.success;
    }
    return 0;
}

static int init_find(const char* str) {
    if(!quitting && !ppc_quitted) {
	struct msg_find* p=cmddata;
	strcpy(p->str,str);
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_INITFIND);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	return p->base.success;
    }
    return 0;
}

static int find(int start,int stop,int* xmin,int* ymin,int* xmax,int* ymax,int* page) {
    if(!quitting && !ppc_quitted) {
	struct msg_find* p=cmddata;
	p->top=start;
	p->bottom=stop;
	/*p->xmin=*xmin;
	p->ymin=*ymin;
	p->xmax=*xmax;
	p->ymax=*ymax;
	p->page=*page;*/
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_FIND);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	*xmin=p->xmin;
	*ymin=p->ymin;
	*xmax=p->xmax;
	*ymax=p->ymax;
	*page=p->page;
	return p->base.success;
    }
    return 0;
}

static void end_find() {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_ENDFIND);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

static int chklink(int x,int y,void** action,char* str,size_t len) {
    if(!quitting && !ppc_quitted) {
	struct msg_chklink* p=cmddata;
	p->x=x;
	p->y=y;
	p->action=*action;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_CHKLINK);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	strncpy(str,p->str,len);
	*action=p->action;
	return p->base.success;
    }
    return 0;
}

static int dolink(int* x,int* y,int* state,int* page,char* buf,size_t len) {
    if(!quitting && !ppc_quitted) {
	struct msg_dolink* p=cmddata;
	p->x=*x;
	p->y=*y;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_DOLINK);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	*state=p->state;
	*page=p->page;
	*x=p->x;
	*y=p->y;
	strncpy(buf,p->str,len);
	return p->base.success;
    }
    return 0;
}

static int init_write(const char* filename,int first_page,int last_page,int zoom,int rotate,int type) {
    if(!quitting && !ppc_quitted) {
	struct msg_write* p=cmddata;
	strncpy(p->filename,filename,sizeof(p->filename)-1);
	p->filename[sizeof(p->filename)-1]='\0';
	p->first_page=first_page;
	p->last_page=last_page;
	p->zoom=zoom;
	p->rotate=rotate;
	p->type=type;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_INITWRITE);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	return p->base.success;
    }
    return 0;
}

static void writefile(int first_page,int last_page) {
    if(!quitting && !ppc_quitted) {
	struct msg_write* p=cmddata;
	p->first_page=first_page;
	p->last_page=last_page;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_WRITE);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

static void end_write() {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_ENDWRITE);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

static char* gettext(int xmin,int ymin,int xmax,int ymax) {
    char* r=NULL;
    if(!quitting && !ppc_quitted) {
	struct msg_gettext* p=cmddata;
	p->xmin=xmin;
	p->ymin=ymin;
	p->xmax=xmax;
	p->ymax=ymax;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_GETTEXT);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	if(p->base.success) {
	    char* buf=NULL;
	    size_t sz=p->size+1;
	    char* q;
	    if(sz>sizeof(union msg_max)) {
		buf=PPCAllocVec(sz,MEMF_ANY);
		if(buf)
		    q=buf;
		else {
		    q=cmddata;
		    sz=sizeof(union msg_max);
		}
	    } else
		q=cmddata;
	    PPCSendMessage(ppcport,cmdmsg,q,sz,MSGID_GETTEXT);
	    PPCWaitPort(reply_port);
	    PPCGetMessage(reply_port);
	    if(r=malloc(sz))
		memcpy(r,q,sz);
	    if(buf)
		PPCFreeVec(buf);
	}
    }
    return r;
}

static void add_fontmap(const char* pdffont,const char* afont,
			int m,int style,int encoding) {
    if(!quitting && !ppc_quitted) {
	struct msg_fontmap* p=cmddata;
	strncpy(p->pdffont,pdffont,sizeof(p->pdffont)-1);
	p->pdffont[sizeof(p->pdffont)-1]='\0';
	strncpy(p->afont,afont,sizeof(p->afont)-1);
	p->afont[sizeof(p->afont)-1]='\0';
	p->m=m;
	p->style=style;
	p->encoding=encoding;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_ADDFONTMAP);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

#if 0
static void rem_fontmap(const char* pdffont) {
    if(!quitting && !ppc_quitted) {
	struct msg_fontmap* p=cmddata;
	strncpy(p->pdffont,pdffont,sizeof(p->pdffont)-1);
	p->pdffont[sizeof(p->pdffont)-1]='\0';
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_REMFONTMAP);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}
#endif

static int init_scan(void) {
    if(!quitting && !ppc_quitted) {
	struct msg_scan_fonts* p=cmddata;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_INITSCAN);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	return p->base.success;
    }
    return 0;
}

static int scan_fonts(int first_page,int last_page) {
    if(!quitting && !ppc_quitted) {
	struct msg_scan_fonts* p=cmddata;
	p->first_page=first_page;
	p->last_page=last_page;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_SCANFONTS);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	return p->base.success;
    }
    return 0;
}

static int scan_default_fonts(void) {
    if(!quitting && !ppc_quitted) {
	struct msg_scan_fonts* p=cmddata;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_DEFAULTFONTS);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	return p->base.success;
    }
    return 0;
}

static void end_scan_fonts(void) {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_ENDSCANFONTS);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

static int get_font(int n,char* pdffont,int pdffontlen,
		     char* afont,int afontlen,
		     int* m,int* style,int* encoding) {
    if(!quitting && !ppc_quitted) {
	struct msg_fontmap* p=cmddata;
	p->m=n;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_GETFONT);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
	strncpy(pdffont,p->pdffont,pdffontlen-1);
	pdffont[pdffontlen-1]='\0';
	strncpy(afont,p->afont,afontlen-1);
	afont[afontlen-1]='\0';
	*m=p->m;
	*style=p->style;
	*encoding=p->encoding;
	return p->base.success;
    } else
	return 0;
}


static void clear_fontmap(void) {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_CLEARFONTMAP);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}

/*static void set_fontmap(void) {
    if(!quitting && !ppc_quitted) {
	PPCSendMessage(ppcport,cmdmsg,NULL,0,MSGID_SETFONTMAP);
	PPCWaitPort(reply_port);
	PPCGetMessage(reply_port);
    }
}*/

static PPCMsg* putimage(DocData* dat,PPCMsg* msg,short x,short y,short w,short h) {
    UBYTE* pic;
    struct BitMap* bm;
    PPCReplyMessage(msg);
    PPCWaitPort(port);
    msg=PPCGetMessage(port);
    pic=(UBYTE*)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
    if(bm=AllocBitMap((w+15)&~15,1,8,BMF_MINPLANES,dat->bm)) {
	struct RastPort rp2;
	rp2=dat->rp;
	rp2.Layer=NULL;
	rp2.BitMap=bm;
	WritePixelArray8(&dat->rp,x,y,x+w-1,y+h-1,pic,&rp2);
	WaitBlit();
	FreeBitMap(bm);
    }
    return msg;
}

static PPCMsg* getimage(DocData* dat,PPCMsg* msg,short x,short y,short w,short h) {
    UBYTE* pic;
    struct BitMap* bm;
    PPCReplyMessage(msg);
    PPCWaitPort(port);
    msg=PPCGetMessage(port);
    pic=(UBYTE*)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
    if(bm=AllocBitMap((w+15)&~15,1,8,BMF_MINPLANES,dat->bm)) {
	struct RastPort rp2;
	rp2=dat->rp;
	rp2.Layer=NULL;
	rp2.BitMap=bm;
	ReadPixelArray8(&dat->rp,x,y,x+w-1,y+h-1,pic,&rp2);
	FreeBitMap(bm);
    }
    return msg;
}

static PPCMsg* putimage24(DocData* dat,PPCMsg* msg,short x,short y,short w,short h) {
    UBYTE* pic;
    PPCReplyMessage(msg);
    PPCWaitPort(port);
    msg=PPCGetMessage(port);
    pic=(UBYTE*)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
    MyWritePixelArray(pic,0,0,w*3,&dat->rp,x,y,w,h,RECTFMT_RGB);
    return msg;
}

static PPCMsg* getimage24(DocData* dat,PPCMsg* msg,short x,short y,short w,short h) {
    UBYTE* pic;
    PPCReplyMessage(msg);
    PPCWaitPort(port);
    msg=PPCGetMessage(port);
    pic=(UBYTE*)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
    MyReadPixelArray(pic,0,0,w*3,&dat->rp,x,y,w,h,RECTFMT_RGB);
    return msg;
}

static PPCMsg* decode(DocData* dat,PPCMsg* msg,short* p,short* q) {
    while(p<q) {
	switch(*p++) {
	  case AGFX_STARTPAGE:
	    reset_pens(dat);
	    SetRast(&dat->rp,get_pen(dat,1));
	    SetAPen(&dat->rp,get_pen(dat,0));
	    break;
	  /*case AGFX_ENDPAGE:
	    ***
	    break;*/
	  case AGFX_PENCOLOR: {
	    ULONG r=((ULONG*)p)[0];
	    ULONG g=((ULONG*)p)[1];
	    ULONG b=((ULONG*)p)[2];
	    p+=6;
	    add_pen(dat,r,g,b);
	    break;
	  }
	  case AGFX_SETAPEN:
	    SetAPen(&dat->rp,get_pen(dat,*p++));
	    break;
	  case AGFX_OPENFONT:
	    openfont(dat,*(int*)p,(const char*)(p+3),p[p[2]+3],p[p[2]+4]);
	    p+=p[2]+5;
	    break;
	  case AGFX_CLOSEFONT:
	    closefont(dat,*(int*)p);
	    p+=2;
	    break;
	  case AGFX_SETFONT:
	    setfont(dat,*(int*)p);
	    p+=2;
	    break;
	  case AGFX_LINEPTRN:
	    dat->rp.LinePtrn=*p++;
	    dat->rp.linpatcnt=*p++;
	    break;
	  case AGFX_POLYDRAW:
	    ++p;
	    if(p[-1]) {
		clip_path(dat,p[-1],p);
		Move(&dat->rp,p[0],p[1]);
		PolyDraw(&dat->rp,p[-1]-1,p+2);
	    }
	    p+=p[-1]*2;
	    break;
	  case AGFX_ADDCHAR:
	    clip_path(dat,1,p);
	    Move(&dat->rp,p[0],p[1]);
	    Text(&dat->rp,((char*)p)+5,1);
	    p+=3;
	    break;
	  case AGFX_RECTFILL:
	    clip_path(dat,2,p);
	    RectFill(&dat->rp,p[0],p[1],p[2],p[3]);
	    p+=4;
	    break;
	  case AGFX_INITAREA:
	    initarea(&dat->rp,*p++);
	    break;
	  case AGFX_AREAPOLY:
	    ++p;
	    if(p[-1]) {
		clip_path(dat,p[-1],p);
		areapoly(&dat->rp,p[-1],p,0,0);
	    }
	    p+=p[-1]*2;
	    break;
	  case AGFX_AREAEND:
	    areaend(&dat->rp);
	    break;
	  case AGFX_INITCLIP:
	    init_clip(dat);
	    break;
	  case AGFX_POLYCLIP:
	    ++p;
	    poly_clip(dat,p[-1],p);
	    p+=p[-1]*2;
	    break;
	  case AGFX_RECTCLIP:
	    rect_clip(dat,p[0],p[1],p[2],p[3]);
	    p+=4;
	    break;
	  case AGFX_INSTALLCLIP:
	    install_clip(dat);
	    break;
	  case AGFX_GETCOLORS: {
	    int num=*p++;
	    ULONG* t;
	    PPCReplyMessage(msg);
	    PPCWaitPort(port);
	    msg=PPCGetMessage(port);
	    p=q=NULL;
	    t=(ULONG*)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
	    add_pens(dat,t,num);
	    break;
	  }
	  case AGFX_IMAGE:
	    clip_path(dat,1,p);
	    msg=putimage(dat,msg,p[0],p[1],p[2],p[3]);
	    p=q=NULL;
	    break;
	  case AGFX_IMAGE24:
	    clip_path(dat,1,p);
	    msg=putimage24(dat,msg,p[0],p[1],p[2],p[3]);
	    p=q=NULL;
	    break;
	  case AGFX_GETIMAGE:
	    clip_path(dat,1,p);
	    msg=getimage(dat,msg,p[0],p[1],p[2],p[3]);
	    p=q=NULL;
	    break;
	  case AGFX_GETIMAGE24:
	    clip_path(dat,1,p);
	    msg=getimage24(dat,msg,p[0],p[1],p[2],p[3]);
	    p=q=NULL;
	    break;
	  default:
	    MYASSERT(FALSE);
	    break;
	}
    }
    return msg;
}

static void show_page(DocData* dat,int page,int zoom) {
    if(!quitting && !ppc_quitted && dat->rp.BitMap) {
	struct msg_page* p=(struct msg_page*)cmddata;

	p->page_num=page;
	p->zoom=zoom;
	PPCSendMessage(ppcport,cmdmsg,p,sizeof(*p),MSGID_PAGE);

	while(1) {
	    PPCMsg* msg;

	    PPCWaitPortList(portlist);

	    while(msg=PPCGetMessage(port)) {
		switch(PPCGetMessageAttr(msg,PPCMSGTAG_MSGID)) {
#ifdef USE_GFX_PIPE
		  case MSGID_GFXPIPE: {
		    volatile struct GfxPipe *pipe=(volatile struct GfxPipe *)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
		    short* p=pipe->readp;
		    while(!pipe->done) {
			short* q=pipe->writep;
			if(p<q) {
			    msg=decode(dat,msg,p,q);
			    p=q;
			    pipe->readp=q;
			}
		    }
		    msg=decode(dat,msg,p,pipe->writep);
		    break;
		  }
#endif
		  case MSGID_GFX: {
		    short* p=(short*)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
		    short* q=p+PPCGetMessageAttr(msg,PPCMSGTAG_DATALENGTH)/2;
		    msg=decode(dat,msg,p,q);
		    break;
		  }

		  default:
		    MYASSERT(FALSE);
		    break;
		}
		WaitBlit();
		PPCReplyMessage(msg);
	    }
	    if(msg=PPCGetMessage(reply_port)) {
		if(msg==startupmsg)   // should not happen, but if the user
		    ppc_quitted=TRUE; // kills the ppc task for some reason,
				      // this helps cleanup a bit.
		else {
		    MYASSERT(msg==cmdmsg);
		}
		break;
	    }
	}
    }
}
#else
int get_info(int* num_pages);
int get_page_size(int page,int* width,int* height);
int create_doc(BPTR,const char* filename,size_t,size_t);
int create_output_dev(int depth,struct ColorMap* colormap);
void delete_output_dev(void);
int simple_find(const char* str,int top,int* xmin,int* ymin,int* xmax,int* ymax);
int init_find(const char* str);
int find(int start,int stop,int* xmin,int* ymin,int* xmax,int* ymax,int* page);
void end_find(void);
int chklink(int x,int y,void** action,char* str,size_t len);
int dolink(int* x,int* y,int* state,int* page,char* buf,size_t len);
int init_file(const char* filename,int first_page,int last_page,int zoom,int rotate,int type);
void writefile(int first_page,int last_page);
void end_write(void);
char* gettext(int xmin,int ymin,int xmax,int ymax);
int show_page(int page,int zoom);
int init(int,const char*);
void add_fontmap(const char*,const char*,int,int,int);
/*void rem_fontmap(const char*);*/
void clear_fontmap(void);
/*void set_fontmap(void);*/
int init_scan(void);
int scan_fonts(int,int);
int scan_default_fonts(void);
void end_scan_fonts(void);
int get_font(int,char*,int,char*,int,int*,int*,int*);

#endif

static BOOL setbitmap(DocData*);

static void display_page(DocData* dat) {
    if(dat->last_page!=dat->page ||
       dat->last_zoom!=dat->zoom) {
	if(dat->last_width!=dat->width || dat->last_height!=dat->height)
	    setbitmap(dat);
	if(dat->rp.BitMap) {
	    sleep(TRUE);
	    dat->selected=FALSE;

#ifdef POWERUP
	    show_page(dat,dat->page,zoomDPI[dat->zoom-minZoom]);
#else
	    show_page(dat->page,zoomDPI[dat->zoom-minZoom]);
#endif
	    tfr_clip(dat);
	    clear_clip(dat);
	    dat->last_page=dat->page;
	    dat->last_zoom=dat->zoom;
	    sleep(FALSE);
	}
    }
}

static void search(const char* str) {
    struct Rectangle rect;
    int pg,pages;
    int page,top,bottom,xmin,ymin,xmax,ymax;
    BOOL found=FALSE;
    get(bitmapobj,MYATTR_Page,&page);
    get(bitmapobj,MYATTR_Selection,&rect);
    bottom=0;

    if(rect.MinX==-1)
	top=1;
    else {
	top=0;
	xmin=rect.MaxX;
	ymin=(rect.MinY+rect.MaxY)/2;
	xmax=0;
	ymax=0;
	if(simple_find(str,top,&xmin,&ymin,&xmax,&ymax))
	    found=TRUE;
    }
    if(!found) {
	const int step=4;
	int k,l=0;
	struct List* winlist;
	Object* o;
	struct Node* state;
	ULONG sigs=0;
	get(pageobj,MUIA_Numeric_Max,&pages);
	get(App,MUIA_Application_WindowList,&winlist);
	for(state=winlist->lh_Head;o=NextObject(&state);)
	    if(o!=gaugewin)
		set(o,MUIA_Window_Sleep,TRUE);
	SetAttrs(gaugeobj,
		 MUIA_Gauge_Current,0,
		 MUIA_Gauge_Max,pages,
		 TAG_END);
	SetAttrs(gaugewin,
		 MUIA_Window_Title,"Searching...",
		 MUIA_Window_Open,TRUE,
		 TAG_END);
	pg=page;
	if(init_find(str)) {
	    LONG r=0;
	    for(k=pg+1;k<=pages;k+=step) {
		int j=k+step<=pages?step:pages+1-k;
		if(find(k,k+j,&xmin,&ymin,&xmax,&ymax,&page)) {
		    found=TRUE;
		    break;
		}
		set(gaugeobj,MUIA_Gauge_Current,l+=j);
		r=DoMethod(App,MUIM_Application_NewInput,&sigs);
		if(r==MUIV_Application_ReturnID_Quit ||
		   r==ID_ABORT)
		   break;
	    }
	    if(!found && r!=ID_ABORT && r!=MUIV_Application_ReturnID_Quit) {
		for(k=1;k<pg;k+=step) {
		    int j=k+step<pg?step:pg-k;
		    if(find(k,k+j,&xmin,&ymin,&xmax,&ymax,&page)) {
			found=TRUE;
			break;
		    }
		    set(gaugeobj,MUIA_Gauge_Current,l+=j);
		    r=DoMethod(App,MUIM_Application_NewInput,&sigs);
		    if(r==MUIV_Application_ReturnID_Quit ||
		       r==ID_ABORT)
			break;
		}
	    }
	    end_find();
	}
	if(found) {
	    set(pageobj,MYATTR_Value,page);
	    simple_find(str,TRUE,&xmin,&ymin,&xmax,&ymax);
	}
	set(gaugewin,MUIA_Window_Open,FALSE);
	get(App,MUIA_Application_WindowList,&winlist);
	for(state=winlist->lh_Head;o=NextObject(&state);)
	    if(o!=gaugewin)
		set(o,MUIA_Window_Sleep,FALSE);
    }
    if(found) {
	LONG left,width,top,height,t;
	rect.MinX=xmin;
	rect.MinY=ymin;
	rect.MaxX=xmax;
	rect.MaxY=ymax;
	set(bitmapobj,MYATTR_Selection,&rect);
	xmin+=bleft;
	ymin+=btop;
	xmax+=bleft;
	ymax+=btop;
	get(vgroup,MUIA_Width,&width);
	get(vgroup,MUIA_InnerRight,&t);
	width-=t;
	get(vgroup,MUIA_InnerLeft,&t);
	width-=t;
	get(vgroup,MUIA_Height,&height);
	get(vgroup,MUIA_InnerBottom,&t);
	height-=t;
	get(vgroup,MUIA_InnerTop,&t);
	height-=t;
	get(vgroup,MUIA_Virtgroup_Left,&left);
	get(vgroup,MUIA_Virtgroup_Top,&top);
	if(xmin<left)
	    left=xmin;
	else if(xmax>left+width)
	    left=xmax-width;
	if(ymin<top)
	    top=ymin;
	else if(ymax>top+height)
	    top=ymax-height;
	SetAttrs(vgroup,
		 MUIA_Virtgroup_Left,left,
		 MUIA_Virtgroup_Top,top,
		 TAG_END);
    } else {
	set(pageobj,MYATTR_Selection,NULL);
	set(linkobj,MUIA_Text_Contents,"Not found");
    }
}

static void save(const char* filename,int first_page,int last_page,int zoom,int rotate,int type) {
    const int step=4;
    int k,l=0;
    struct List* winlist;
    Object* o;
    struct Node* state;
    ULONG sigs=0;
    get(App,MUIA_Application_WindowList,&winlist);
    for(state=winlist->lh_Head;o=NextObject(&state);)
	if(o!=gaugewin)
	    set(o,MUIA_Window_Sleep,TRUE);
    SetAttrs(gaugeobj,
	     MUIA_Gauge_Current,0,
	     MUIA_Gauge_Max,last_page-first_page+1,
	     TAG_END);
    SetAttrs(gaugewin,
	     MUIA_Window_Title,"Saving...",
	     MUIA_Window_Open,TRUE,
	     TAG_END);
    if(init_write(filename,first_page,last_page,zoom,rotate,type)) {
	LONG r;
	for(k=first_page;k<=last_page;k+=step) {
	    int j=k+step<=last_page?step:last_page+1-k;
	    writefile(k,k+j);
	    set(gaugeobj,MUIA_Gauge_Current,l+=j);
	    r=DoMethod(App,MUIM_Application_NewInput,&sigs);
	    if(r==MUIV_Application_ReturnID_Quit ||
	       r==ID_ABORT)
		break;
	}
	end_write();
    } else
	set(linkobj,MUIA_Text_Contents,"Failed...");
    set(gaugewin,MUIA_Window_Open,FALSE);
    get(App,MUIA_Application_WindowList,&winlist);
    for(state=winlist->lh_Head;o=NextObject(&state);)
	if(o!=gaugewin)
	    set(o,MUIA_Window_Sleep,FALSE);
}

static int scan_doc_fonts(int first_page,int last_page) {
    const int step=4;
    int k,l=0;
    struct List* winlist;
    Object* o;
    struct Node* state;
    ULONG sigs=0;
    int ret=0;
    get(App,MUIA_Application_WindowList,&winlist);
    for(state=winlist->lh_Head;o=NextObject(&state);)
	if(o!=gaugewin)
	    set(o,MUIA_Window_Sleep,TRUE);
    SetAttrs(gaugeobj,
	     MUIA_Gauge_Current,0,
	     MUIA_Gauge_Max,last_page-first_page+1,
	     TAG_END);
    SetAttrs(gaugewin,
	     MUIA_Window_Title,"Scanning...",
	     MUIA_Window_Open,TRUE,
	     TAG_END);
    if(init_scan()) {
	LONG r;
	for(k=first_page;k<=last_page;k+=step) {
	    int j=k+step<=last_page?step:last_page+1-k;
	    ret=scan_fonts(k,k+j);
	    set(gaugeobj,MUIA_Gauge_Current,l+=j);
	    r=DoMethod(App,MUIM_Application_NewInput,&sigs);
	    if(r==MUIV_Application_ReturnID_Quit ||
	       r==ID_ABORT)
		break;
	}
	if(!ret)
	    end_scan_fonts();
    }
    set(gaugewin,MUIA_Window_Open,FALSE);
    get(App,MUIA_Application_WindowList,&winlist);
    for(state=winlist->lh_Head;o=NextObject(&state);)
	if(o!=gaugewin)
	    set(o,MUIA_Window_Sleep,FALSE);
    return ret;
}

static void check_link(int x,int y) {
    static void* last_action;
    void* action=last_action;
    char str[256];
    if(chklink(x,y,&action,str,sizeof(str))) {
	if(action!=last_action) {
	    last_action=action;
	    set(linkobj,MUIA_Text_Contents,str);
	}
    } else if(last_action) {
	last_action=NULL;
	set(linkobj,MUIA_Text_Contents,NULL);
    }
}

static void get_page_info(void) {
    int num_pages;
    get_info(&num_pages);
    SetAttrs(bitmapobj,
	     MYATTR_Zoom,defZoom,
	     MYATTR_NumPages,num_pages,
	     MYATTR_Page,1,
	     MYATTR_NewDoc,TRUE,
	     TAG_END);
    SetAttrs(pageobj,
	     MUIA_Numeric_Max,num_pages,
	     MYATTR_Value,1,
	     TAG_END);
    set(psstartobj,MUIA_Numeric_Max,num_pages);
    set(psstopobj,MUIA_Numeric_Max,num_pages);
    set(scanstartobj,MUIA_Numeric_Max,num_pages);
    set(scanstopobj,MUIA_Numeric_Max,num_pages);
    set(zoomobj,MYATTR_Value,defZoom);
}

static int do_link(int x,int y) {
    int state,page,r;
    char buf[256];
    if(r=dolink(&x,&y,&state,&page,buf,sizeof(buf))) {
	switch(state) {
	  case st_changed:
	    copystr(&docname,buf);
	    get_page_info();
	    /* fall through */
	  case st_unchanged:
	    if(page!=-1)
		set(pageobj,MYATTR_Value,page);
	    set(linkobj,MUIA_Text_Contents,NULL);
	    break;

	  case st_run:
	    if(MUI_Request(App,win,0,"Execute command ?","Ok|Cancel",buf))
		system(buf);
	    break;

	  case st_url:
	    if(URLCMD) {
		char buf2[256];
		buf2[0]='r';
		buf2[1]='u';
		buf2[2]='n';
		buf2[3]=' ';
		sprintf(buf2+4,URLCMD,buf);
		system(buf2);
	    }
	    break;
	}
	if(x!=-1 || y!=-1)
	    SetAttrs(vgroup,
		     x==-1?TAG_IGNORE:MUIA_Virtgroup_Left,x,
		     y==-1?TAG_IGNORE:MUIA_Virtgroup_Top,y,
		     TAG_END);
    }
    return r;
}

static void toclip(int x1,int y1,int x2,int y2) {
    char* txt;
    if(x1>x2) {
	int t=x1;
	x1=x2;
	x2=t;
    }
    if(y1>y2) {
	int t=y1;
	y1=y2;
	y2=t;
    }
    if(txt=gettext(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,ID_FTXT,ID_FORM,IFFSIZE_UNKNOWN) ||
		      PushChunk(iff,0,ID_CHRS,sz) ||
		      WriteChunkBytes(iff,txt,sz)!=sz ||
		      PopChunk(iff) ||
		      PopChunk(iff))
		       printf("Error writing to clipboard.\n");
		   CloseIFF(iff);
		}
		CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
	    }
	    FreeIFF(iff);
	}
	free(txt);
    }
}


static void clearbitmap(DocData* dat) {
    dat->rp.BitMap=NULL;
    dat->rp.Layer=NULL;
    if(dat->region) {
	InstallClipRegion(dat->layer,NULL);
	DisposeRegion(dat->region);
	dat->region=NULL;
    }
    if(dat->layer) {
	DeleteLayer(0,dat->layer);
	dat->layer=NULL;
    }
    if(dat->layerinfo) {
	DisposeLayerInfo(dat->layerinfo);
	dat->layerinfo=NULL;
    }
    WaitBlit();
    FreeBitMap(dat->bm);
    dat->bm=NULL;
}

static BOOL setbitmap(DocData* dat) {
    if(!dat->hidden && (!dat->bm ||
			dat->width!=dat->last_width ||
			dat->height!=dat->last_height)) {
	clearbitmap(dat);
	dat->last_width=dat->width;
	dat->last_height=dat->height;

	if(dat->bm=AllocBitMap(dat->width,dat->height,dat->depth,BMF_MINPLANES,dat->friendbm)) {
	    dat->dx=0;
	    dat->dy=0;
	    if((dat->layerinfo=NewLayerInfo()) &&
	       (dat->layer=CreateUpfrontLayer(dat->layerinfo,dat->bm,0,0,dat->width-1,dat->height-1,LAYERSIMPLE,NULL)) &&
	       (dat->region=NewRegion())) {
		struct Rectangle rect;
		rect.MinX=0;
		rect.MinY=0;
		rect.MaxX=dat->width-1;
		rect.MaxY=dat->height-1;
		if(OrRectRegion(dat->region,&rect)) {
		    InstallClipRegion(dat->layer,dat->region);
		    dat->rp.Layer=dat->layer;
		    dat->rp.BitMap=dat->bm;
		    return TRUE;
		}
	    }
	    printf("Can't install clipping region.\n");
	} else
	    printf("Can't alloc bitmap.\n");
	clearbitmap(dat);
	return FALSE;
    }
    return TRUE;
}

#define DocObject NewObject(mcc->mcc_Class,NULL

static ULONG mNew(struct IClass *cl,Object *obj,Msg msg) {
    DocData* dat;

    if(obj=(Object *)DoSuperMethodA(cl,obj,msg)) {
	dat=INST_DATA(cl,obj);
	memset(dat,0,sizeof(*dat));
	InitRastPort(&dat->rp);
	SetDrMd(&dat->rp,JAM1);
	dat->hidden=TRUE;
	dat->last_page=-1;
	dat->obj=obj;
	set(obj,MUIA_FillArea,FALSE);
    }
    return (ULONG)obj;
}

static ULONG mDispose(struct IClass *cl,Object *obj,Msg msg) {
    DocData* dat=INST_DATA(cl,obj);
    free(dat->pens);
    clear_fonts(dat);
    return DoSuperMethodA(cl,obj,msg);
}

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

	  case MYATTR_Page:
	    if(dat->page!=(int)tag->ti_Data) {
		dat->page=(int)tag->ti_Data;
		if(dat->page<1)
		    dat->page=1;
		else if(dat->page>dat->num_pages)
		    dat->page=dat->num_pages;
		redraw=TRUE;
		set(vgroup,MUIA_Virtgroup_Top,0);
	    }
	    break;

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

	  case MYATTR_Selection:
	    if(tag->ti_Data) {
		dat->selection=*(struct Rectangle*)tag->ti_Data;
		dat->selected=TRUE;
		redraw=TRUE;
	    } else if(dat->selected) {
		dat->selected=FALSE;
		redraw=TRUE;
	    }
	    break;
	  case MYATTR_NewDoc:
	    dat->last_page=-1;
	    dat->last_zoom=-1;
	    redraw=TRUE;
	    break;
	}
    }
    if(redraw) {
	if(get_page_size(dat->page,&dat->page_width,&dat->page_height)) {
	    dat->width=(dat->page_width*zoomDPI[dat->zoom-minZoom])/72;
	    dat->height=(dat->page_height*zoomDPI[dat->zoom-minZoom])/72;
	    if(dat->width!=dat->last_width || dat->height!=dat->last_height) {
		DoMethod(vgroup,MUIM_Group_InitChange);
		DoMethod(vgroup,MUIM_Group_ExitChange);
	    } else
		MUI_Redraw(obj,MADF_DRAWOBJECT);
	}
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
}


static ULONG mGet(struct IClass *cl,Object *obj,struct opGet *msg) {
    DocData* dat=INST_DATA(cl,obj);
    switch(msg->opg_AttrID) {
      case MYATTR_Page:
	*msg->opg_Storage=dat->page;
	return TRUE;
      case MYATTR_Selected:
	*msg->opg_Storage=dat->selected;
	break;
      case MYATTR_Selection:
	*(struct Rectangle*)msg->opg_Storage=dat->selection;
	return TRUE;
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
}

static ULONG mSetup(struct IClass *cl,Object *obj,Msg msg) {
    DocData* dat=INST_DATA(cl,obj);

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

    dat->colormap=_screen(obj)->ViewPort.ColorMap;
    dat->depth=GetBitMapAttr(_screen(obj)->RastPort.BitMap,BMA_DEPTH);
    if(dat->depth>8 && !CyberGfxBase)
	dat->depth=8;
    sleep(TRUE);
    create_output_dev(dat->depth,dat->colormap);
    sleep(FALSE);
    dat->friendbm=_screen(obj)->RastPort.BitMap;
    dat->hidden=FALSE;
    dat->selected=FALSE;
    dat->selecting=FALSE;
    setbitmap(dat);
    MUI_RequestIDCMP(obj,IDCMP_MOUSEMOVE|IDCMP_MOUSEBUTTONS|IDCMP_VANILLAKEY|IDCMP_RAWKEY);
    return TRUE;
}

static ULONG mCleanup(struct IClass *cl,Object *obj,Msg msg) {
    DocData* dat=INST_DATA(cl,obj);
    MUI_RejectIDCMP(obj,IDCMP_MOUSEMOVE|IDCMP_MOUSEBUTTONS|IDCMP_VANILLAKEY|IDCMP_RAWKEY);
    clearbitmap(dat);
    clear_pens(dat);
    dat->hidden=TRUE;
    delete_output_dev();
    dat->last_page=-1;
    clear_fonts(dat);
    return DoSuperMethodA(cl,obj,msg);
}

static ULONG mReset(struct IClass *cl,Object *obj,Msg msg) {
    DocData* dat=INST_DATA(cl,obj);
    if(!dat->hidden) {
	sleep(TRUE);
	delete_output_dev();
	dat->last_page=-1;
	clear_fonts(dat);
	create_output_dev(dat->depth,dat->colormap);
	MUI_Redraw(obj,MADF_DRAWOBJECT);
	sleep(FALSE);
    }
    return 0;
}

static ULONG mAskMinMax(struct IClass *cl,Object *obj,struct MUIP_AskMinMax *msg) {
    DocData* dat=INST_DATA(cl,obj);
    int w=dat->width+bright+bleft;
    int h=dat->height+bbottom+btop;
    DoSuperMethodA(cl,obj,(Msg)msg);
    msg->MinMaxInfo->MinWidth+=w;
    msg->MinMaxInfo->DefWidth+=w;
    msg->MinMaxInfo->MaxWidth+=MUI_MAXMAX;
    msg->MinMaxInfo->MinHeight+=h;
    msg->MinMaxInfo->DefHeight+=h;
    msg->MinMaxInfo->MaxHeight+=MUI_MAXMAX;
    return 0;
}

static ULONG mDraw(struct IClass *cl,Object *obj,struct MUIP_Draw *msg) {
    DocData* dat=INST_DATA(cl,obj);
    int x,y,w,h;
    struct RastPort *rp;
    DoSuperMethodA(cl,obj,(Msg)msg);
    x=_mleft(obj);
    y=_mtop(obj);
    w=_mwidth(obj);
    h=_mheight(obj);
    rp=_rp(obj);
    if(dat->bm) {
	if(msg->flags&MADF_DRAWOBJECT) {
	    display_page(dat);
	    if(w>dat->width+2 || h>dat->height+2) {
		int h1=(h-dat->height-2)/2;
		int h2=h-dat->height-h1;
		int w1=(w-dat->width-2)/2;
		int w2=w-dat->width-w1;
		h-=h1+h2;
		if(h1)
		    DoMethod(obj,MUIM_DrawBackground,x,y,w,h1,0);
		if(h2)
		    DoMethod(obj,MUIM_DrawBackground,x,y+h1+h,w,h2,0);
		w-=w1+w2;
		y+=h1;
		if(w1)
		    DoMethod(obj,MUIM_DrawBackground,x,y,w1,h,0);
		if(w2)
		    DoMethod(obj,MUIM_DrawBackground,x+w1+w,y,w2,h,0);
		x+=w1;
	    }
	    if(w && h) {
		SetAPen(rp,_dri(obj)->dri_Pens[SHINEPEN]);
		Move(rp,x,y+h-1);
		Draw(rp,x,y);
		Draw(rp,x+w-1,y);
		if(w>1 && h>1) {
		    SetAPen(rp,_dri(obj)->dri_Pens[SHADOWPEN]);
		    Move(rp,x+w-1,y+1);
		    Draw(rp,x+w-1,y+h-1);
		    Draw(rp,x+1,y+h-1);
		    if(w>2 && h>2)
			BltBitMapRastPort(dat->bm,0,0,rp,x+1,y+1,w-2,h-2,0xc0);
		}
	    }
	} else if(msg->flags&MADF_DRAWUPDATE) {
	    x+=(w-dat->width-2)/2;
	    w=dat->width-2;
	    y+=(h-dat->height-2)/2;
	    h=dat->height-2;
	    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;
		    BltBitMapRastPort(dat->bm,x1,y3,rp,x+1+x1,y+1+y3,1,y4-y3+1,0xc0);
		    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;
		    BltBitMapRastPort(dat->bm,x2,y3,rp,x+1+x2,y+1+y3,1,y4-y3+1,0xc0);
		    if(x2>=w)
			x2=w-1;
		}
		if(y1>=0 && y1<h)
		    BltBitMapRastPort(dat->bm,x1,y1,rp,x+1+x1,y+1+y1,x2-x1+1,1,0xc0);
		if(y2>=0 && y2<h)
		    BltBitMapRastPort(dat->bm,x1,y2,rp,x+1+x1,y+1+y2,x2-x1+1,1,0xc0);
	    }
	}
	if(msg->flags&(MADF_DRAWOBJECT|MADF_DRAWUPDATE)) {
	    dat->select_displayed=dat->selected || dat->selecting;
	    if(dat->select_displayed) {
		SetAPen(rp,_dri(obj)->dri_Pens[FILLPEN]);
		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;
	    }
	}
    } else if(w && h) {
	EraseRect(_rp(obj),x,y,x+w-1,y+h-1);
    }
    return 0;
}

static void upkey(void) {
    LONG top,height,totheight,t;
    get(vgroup,MUIA_Virtgroup_Height,&totheight);
    get(vgroup,MUIA_Virtgroup_Top,&top);
    get(vgroup,MUIA_Height,&height);
    get(vgroup,MUIA_InnerBottom,&t);
    height-=t;
    get(vgroup,MUIA_InnerTop,&t);
    height-=t;
    if(top>0)
	set(vgroup,MUIA_Virtgroup_Top,top-height+10);
    else {
	DoMethod(pageobj,MUIM_Numeric_Decrease,1);
	set(vgroup,MUIA_Virtgroup_Top,totheight-height);
    }
}

static void downkey(void) {
    LONG top,height,totheight,t;
    get(vgroup,MUIA_Virtgroup_Height,&totheight);
    get(vgroup,MUIA_Virtgroup_Top,&top);
    get(vgroup,MUIA_Height,&height);
    get(vgroup,MUIA_InnerBottom,&t);
    height-=t;
    get(vgroup,MUIA_InnerTop,&t);
    height-=t;
    if(top<totheight-height)
	set(vgroup,MUIA_Virtgroup_Top,top+height-10);
    else {
	DoMethod(pageobj,MUIM_Numeric_Increase,1);
	set(vgroup,MUIA_Virtgroup_Top,0);
    }
}

static ULONG mHandleInput(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg) {
    DocData* 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-bleft-bright;
    y+=(h-dat->height-btop-bbottom)/2+btop;
    h=dat->height-btop-bbottom;

    #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) {
	switch(msg->imsg->Class) {
	  case IDCMP_MOUSEBUTTONS:
	    if(msg->imsg->Code==SELECTDOWN) {
		if(_isinobject(msg->imsg->MouseX,msg->imsg->MouseY)) {
		    dat->selection.MinX=msg->imsg->MouseX-x;
		    dat->selection.MinY=msg->imsg->MouseY-y;
		    dat->selection.MaxX=dat->selection.MinX;
		    dat->selection.MaxY=dat->selection.MinY;
		    if(!do_link(dat->selection.MinX,dat->selection.MinY)) {
			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=msg->imsg->MouseX-x;
		    if(dat->selection.MaxX<0)
			dat->selection.MaxX=0;
		    else if(dat->selection.MaxX>=w)
			dat->selection.MaxX=w-1;
		    dat->selection.MaxY=msg->imsg->MouseY-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:
	    if(dat->selecting) {
		dat->selection.MaxX=msg->imsg->MouseX-x;
		if(dat->selection.MaxX<0)
		    dat->selection.MaxX=0;
		else if(dat->selection.MaxX>=w)
		    dat->selection.MaxX=w-1;
		dat->selection.MaxY=msg->imsg->MouseY-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(msg->imsg->MouseX,msg->imsg->MouseY))
		check_link(msg->imsg->MouseX-x,msg->imsg->MouseY-y);
	    break;
	  /*case IDCMP_RAWKEY:
	    switch(msg->imsg->Code) {
	      case :
		break;
	    }
	    break; */
	  case IDCMP_VANILLAKEY:
	    switch(msg->imsg->Code) {
	      case 8:  /* BS */
		upkey();
		break;
	      case 'o':
		DoMethod(App,MUIM_Application_ReturnID,ID_OPEN);
		break;
	      case 'f':
		set(win,MUIA_Window_ActiveObject,(ULONG)searchobj);
		break;
	      case ' ':
		downkey();
		break;
	      case 'q':
		DoMethod(App,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit);
		break;
	      case '+':
		DoMethod(zoomobj,MUIM_Numeric_Increase,1);
		break;
	      case '-':
		DoMethod(zoomobj,MUIM_Numeric_Decrease,1);
		break;
	    }
	    break;
	}
    }
    switch(msg->muikey) {
      case MUIKEY_UP:
	upkey();
	break;
      case MUIKEY_DOWN:
	downkey();
	break;
      case MUIKEY_PAGEUP:
	DoMethod(pageobj,MUIM_Numeric_Decrease,1);
	break;
      case MUIKEY_PAGEDOWN:
	DoMethod(pageobj,MUIM_Numeric_Increase,1);
	break;
      case MUIKEY_TOP:
	set(pageobj,MUIA_Numeric_Value,1);
	break;
      case MUIKEY_BOTTOM: {
	LONG t;
	get(pageobj,MUIA_Numeric_Max,&t);
	set(pageobj,MUIA_Numeric_Value,t);
	break;
      }
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
    #undef _between
    #undef _isinobject
}

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_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 MYM_Reset       : return mReset      (cl,obj,(APTR)msg);
    }
    return DoSuperMethodA(cl,obj,msg);
}


/*
 * Custom slider class: maintains MYATTR_Value, that
 * is similar to MUIA_Numeric_Value, except that it is
 * updated only when the slider is released.
 */

struct SliderData {
    LONG value;
    BOOL pressed;
    BOOL lock;
};
typedef struct SliderData SliderData;

#define MySliderObject NewObject(slider_mcc->mcc_Class,NULL

static ULONG msNew(struct IClass *cl,Object *obj,Msg msg) {
    SliderData* dat;

    if(obj=(Object *)DoSuperMethodA(cl,obj,msg)) {
	dat=INST_DATA(cl,obj);
	dat->pressed=FALSE;
	dat->lock=FALSE;
    }
    return (ULONG)obj;
}

static ULONG msSet(struct IClass *cl,Object *obj,struct opSet *msg) {
    SliderData *dat=INST_DATA(cl,obj);
    struct TagItem *tags=msg->ops_AttrList;
    struct TagItem *tag;
    while(tag=NextTagItem(&tags)) {
	switch(tag->ti_Tag) {
	  case MYATTR_Value:
	    if(!dat->lock) {
		dat->lock=TRUE;
		set(obj,MUIA_Numeric_Value,tag->ti_Data);
		dat->lock=FALSE;
	    }
	    get(obj,MUIA_Numeric_Value,&dat->value);
	    break;
	  case MUIA_Numeric_Value: {
	    LONG r=DoSuperMethodA(cl,obj,(Msg)msg);
	    if(!dat->pressed && !dat->lock) {
		dat->lock=TRUE;
		set(obj,MYATTR_Value,tag->ti_Data);
		dat->lock=FALSE;
	    }
	    return (ULONG)r;
	  }
	}
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
}

static ULONG msGet(struct IClass *cl,Object *obj,struct opGet *msg) {
    switch(msg->opg_AttrID) {
      case MYATTR_Value: {
	SliderData* dat=INST_DATA(cl,obj);
	*msg->opg_Storage=dat->value;
	return TRUE;
      }
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
}

static ULONG msSetup(struct IClass *cl,Object *obj,Msg msg) {
    if(DoSuperMethodA(cl,obj,msg)) {
	MUI_RequestIDCMP(obj,IDCMP_MOUSEBUTTONS);
	return TRUE;
    } else
	return FALSE;
}


static ULONG msCleanup(struct IClass *cl,Object *obj,Msg msg) {
    MUI_RejectIDCMP(obj,IDCMP_MOUSEBUTTONS);
    return DoSuperMethodA(cl,obj,msg);
}


static ULONG msHandleInput(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg) {
    #define _between(a,x,b) ((x)>=(a) && (x)<=(b))
    #define _isinobject(x,y) (_between(_mleft(obj),(x),_mright(obj)) && _between(_mtop(obj),(y),_mbottom(obj)))
    SliderData* dat=INST_DATA(cl,obj);

    if(msg->imsg) {
	switch(msg->imsg->Class) {
	  case IDCMP_MOUSEBUTTONS:
	    if(msg->imsg->Code==SELECTDOWN) {
		if(_isinobject(msg->imsg->MouseX,msg->imsg->MouseY))
		    dat->pressed=TRUE;
	    } else if(msg->imsg->Code==SELECTUP) {
		ULONG r=DoSuperMethodA(cl,obj,(Msg)msg);
		LONG x;
		dat->pressed=FALSE;
		get(obj,MUIA_Numeric_Value,&x);
		set(obj,MYATTR_Value,x);
		return r;
	    }
	    break;
	}
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
    #undef _between
    #undef _isinobject
}

SAVEDS ASM ULONG mysliderdispatcher(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1,Msg msg)) {
    switch(msg->MethodID) {
      case OM_NEW          : return msNew        (cl,obj,(APTR)msg);
      case OM_SET          : return msSet        (cl,obj,(APTR)msg);
      case OM_GET          : return msGet        (cl,obj,(APTR)msg);
      case MUIM_Setup      : return msSetup      (cl,obj,(APTR)msg);
      case MUIM_Cleanup    : return msCleanup    (cl,obj,(APTR)msg);
      case MUIM_HandleInput: return msHandleInput(cl,obj,(APTR)msg);
    }
    return DoSuperMethodA(cl,obj,msg);
}


/*
 * Custom rotate slider class: displays multiples of 90°
 */

struct RotateSliderData {
    char buf[5];
};
typedef struct RotateSliderData RotateSliderData;

#define MyRotateSliderObject NewObject(rotate_slider_mcc->mcc_Class,NULL


SAVEDS ASM ULONG myrotatesliderdispatcher(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1,Msg msg)) {
    if(msg->MethodID==MUIM_Numeric_Stringify) {
	RotateSliderData *dat=INST_DATA(cl,obj);
	struct MUIP_Numeric_Stringify *m=(APTR)msg;
	sprintf(dat->buf,"%ld°",m->value*90);
	return (ULONG)dat->buf;
    } else
	return DoSuperMethodA(cl,obj,msg);
}


/*
 * Custom list class: allows drag and drop
 */

struct ListData {
    LONG id;
};
typedef struct ListData ListData;

#define MyListObject NewObject(list_mcc->mcc_Class,NULL

static ULONG mlNew(struct IClass *cl,Object *obj,struct opSet *msg) {
    ListData* dat;

    if(obj=(Object *)DoSuperMethodA(cl,obj,(Msg)msg)) {
	dat=INST_DATA(cl,obj);
	dat->id=GetTagData(MYATTR_ListID,0,msg->ops_AttrList);
    }
    return (ULONG)obj;
}

static ULONG mlGet(struct IClass *cl,Object *obj,struct opGet *msg) {
    switch(msg->opg_AttrID) {
      case MYATTR_ListID: {
	ListData* dat=INST_DATA(cl,obj);
	*msg->opg_Storage=dat->id;
	return TRUE;
      }
    }
    return DoSuperMethodA(cl,obj,(Msg)msg);
}

static ULONG mlDragQuery(struct IClass *cl,Object *obj,struct MUIP_DragDrop *msg) {
    ListData *dat=INST_DATA(cl,obj);
    LONG x;
    if(msg->obj==obj)
	return DoSuperMethodA(cl,obj,(Msg)msg);
    else if(get(msg->obj,MYATTR_ListID,&x) && x==dat->id)
	return MUIV_DragQuery_Accept;
    else
	return MUIV_DragQuery_Refuse;
}


static ULONG mlDragDrop(struct IClass *cl,Object *obj,struct MUIP_DragDrop *msg) {
    if(msg->obj==obj)
	return DoSuperMethodA(cl,obj,(Msg)msg);
    else {
	LONG dropmark;
	LONG id=MUIV_List_NextSelected_Start;
	get(obj,MUIA_List_DropMark,&dropmark);
	while(1) {
	    APTR entry;
	    DoMethod(msg->obj,MUIM_List_NextSelected,&id);
	    if(id==MUIV_List_NextSelected_End)
		break;
	    DoMethod(msg->obj,MUIM_List_GetEntry,id,&entry);
	    DoMethod(obj,MUIM_List_InsertSingle,entry,dropmark);
	}
	DoMethod(msg->obj,MUIM_List_Remove,MUIV_List_Remove_Selected);
	get(obj,MUIA_List_InsertPosition,&dropmark);
	set(obj,MUIA_List_Active,dropmark);
	set(msg->obj,MUIA_List_Active,MUIV_List_Active_Off);
	return 0;
    }
}


SAVEDS ASM ULONG mylistdispatcher(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1,Msg msg)) {
    switch(msg->MethodID) {
      case OM_NEW:         return mlNew      (cl,obj,(APTR)msg);
      case OM_GET:         return mlGet      (cl,obj,(APTR)msg);
      case MUIM_DragQuery: return mlDragQuery(cl,obj,(APTR)msg);
      case MUIM_DragDrop : return mlDragDrop (cl,obj,(APTR)msg);
    }
    return DoSuperMethodA(cl,obj,msg);
}



void cleanup(void) {

    if(MUIMasterBase) {
	MUI_FreeAslRequest(req);
	MUI_DisposeObject(App);
	if(list_mcc)
	    MUI_DeleteCustomClass(list_mcc);
	if(rotate_slider_mcc)
	    MUI_DeleteCustomClass(rotate_slider_mcc);
	if(slider_mcc)
	    MUI_DeleteCustomClass(slider_mcc);
	if(mcc)
	    MUI_DeleteCustomClass(mcc);
	CloseLibrary(MUIMasterBase);
    }

#ifdef POWERUP
    if(startup_data)
	PPCFreeVec(startup_data);

    if(startupmsg)
	PPCDeleteMessage(startupmsg);

    if(quitmsg)
	PPCDeleteMessage(quitmsg);

    if(cmdmsg)
	PPCDeleteMessage(cmdmsg);

    if(cmddata)
	PPCFreeVec(cmddata);

    if(portlist)
	PPCDeletePortList(portlist);

    if(port)
	PPCDeletePort(port);

    if(reply_port)
	PPCDeletePort(reply_port);

    if(elfobject)
	PPCUnLoadObject(elfobject);

    CloseLibrary(PPCLibBase);
#endif

    if(diskobj)
	FreeDiskObject(diskobj);

    CloseLibrary(CyberGfxBase);
    CloseLibrary(IFFParseBase);
    CloseLibrary(DiskfontBase);
    CloseLibrary(IconBase);
    CloseLibrary((struct Library*)UtilityBase);
    CloseLibrary(LayersBase);
    CloseLibrary((struct Library*)GfxBase);
    CloseLibrary((struct Library*)IntuitionBase);

    FreeArgs(rda);
    CurrentDir(olddir);
    UnLock(docdir);
    free(progname);
    free(docname);
    free(gzipcmd_arg);
    free(urlcmd_arg);
    free(deficon_arg);
    free(diskobj_arg);

    if(input) {
	SelectInput(oldinput);
	Close(input);
    }
}

/*
 *  MUI data and hooks.
 */
static struct NewMenu menudata[]={
    {NM_TITLE,"Project"     ,0,0,0,NULL},
    {NM_ITEM , "About..."   ,"?",0,0,(APTR)ID_ABOUT},
    {NM_ITEM , "About MUI...",0,0,0,(APTR)ID_ABOUTMUI},
    {NM_ITEM , NM_BARLABEL  ,0,0,0,NULL},
    {NM_ITEM , "Open..."    ,"O",0,0,(APTR)ID_OPEN},
    {NM_ITEM , "Save as..." ,"A",0,0,(APTR)ID_SAVEAS},
    {NM_ITEM , "Print..."   ,"P",0,0,(APTR)ID_PRINT},
    {NM_ITEM , NM_BARLABEL  ,0,0,0,NULL},
    {NM_ITEM , "Quit"       ,"Q",0,0,(APTR)MUIV_Application_ReturnID_Quit},
    {NM_TITLE,"Edit"        ,0,0,0,NULL},
    {NM_ITEM , "Copy"       ,"C",0,0,(APTR)ID_COPY},
    {NM_TITLE,"Settings"    ,0,0,0,NULL},
    {NM_ITEM , "Default font mapping..."   ,0,0,0,(APTR)ID_DEFFONTS},
    {NM_ITEM , "Document font mapping..."   ,0,0,0,(APTR)ID_DOCFONTS},
    {NM_ITEM , "MUI..."     ,0,0,0,(APTR)ID_MUIPREFS},
    {NM_END  ,NULL          ,0,0,0,NULL}
};


static const char* save_types[]={
    "Postscript (level 2)","Postscript (level 1)","Text","PBM/PPM Images","PBM/PPM/JPEG Images",NULL
};


static ULONG SAVEDS ASM setnum_func(REG(a2,Object* obj),REG(a1,Object** p)) {
    int n=1;
    get(*p,MUIA_String_Integer,&n);
    set(obj,MUIA_Numeric_Value,n);
    return 0;
}
static struct Hook setnum_hook={{NULL,NULL},setnum_func};


struct fontmapentry {
    char pdffont[128];
    char afont[128];
    int mwidth;
    int encoding;
    BOOL bold;
    BOOL italic;
};

static const char* encodings[]={
    "Latin 1","Latin 2","Symbols","Dingbats",NULL
};

static APTR SAVEDS ASM fontmapctor_func(REG(a2,APTR pool),REG(a1,struct fontmapentry* p)) {
    struct fontmapentry* q=AllocPooled(pool,sizeof(*q));
    if(q)
	*q=*p;
    return q;
}
static struct Hook fontmapctor_hook={{NULL,NULL},(ULONG(*)())fontmapctor_func};

static void SAVEDS ASM fontmapdtor_func(REG(a2,APTR pool),REG(a1,struct fontmapentry* p)) {
    FreePooled(pool,p,sizeof(*p));
}
static struct Hook fontmapdtor_hook={{NULL,NULL},(ULONG(*)())fontmapdtor_func};

static APTR SAVEDS ASM fontmapdisp_func(REG(a2,const char** array),REG(a1,struct fontmapentry* p)) {
    if(p) {
	static char buf[4];
	sprintf(buf,"%3d",p->mwidth);
	*array++=p->pdffont;
	*array++=p->afont;
	*array++=buf;
	*array++=p->bold?"×":" ";
	*array++=p->italic?"×":" ";
	*array++=encodings[p->encoding];
    } else {
	*array++="\33u\33bPDF font";
	*array++="\33u\33bAmiga font";
	*array++="\33u\33bScale";
	*array++="\33u\33bBold";
	*array++="\33u\33bItalic";
	*array++="\33u\33bEncoding";
    }
    return 0;
}
static struct Hook fontmapdisp_hook={{NULL,NULL},(ULONG(*)())fontmapdisp_func};

static void SAVEDS ASM fontreq_func(REG(a2,Object* obj),REG(a1,struct FontRequester* p)) {
    set(obj,MUIA_String_Contents,p->fo_Attr.ta_Name);
}
static struct Hook fontreq_hook={{NULL,NULL},(ULONG(*)())fontreq_func};

static void setfontmap(Object* o) {
    LONG entries;
    int k;
    get(o,MUIA_List_Entries,&entries);
    for(k=0;k<entries;++k) {
	struct fontmapentry* e;
	DoMethod(o,MUIM_List_GetEntry,k,&e);
	add_fontmap(e->pdffont,e->afont,e->mwidth,(e->bold?FSF_BOLD:0)|(e->italic?FSF_ITALIC:0),e->encoding);
    }
    /*set_fontmap();*/
}

static void parse_fontmap_entry(Object* obj,const char* q) {
    struct fontmapentry e;
    const char *p=q;
    while(*p && *p!='/')
	++p;
    if(*p=='/' && p!=q && p-q<sizeof(e.pdffont)) {
	memcpy(e.pdffont,q,p-q);
	e.pdffont[p-q]='\0';
	e.mwidth=100;
	e.bold=FALSE;
	e.italic=FALSE;
	e.encoding=0;
	if(*++p=='[') {
	    while(*++p && *p!=']') {
		switch(*p) {
		  case '1':
		    e.encoding=0;
		    break;
		  case '2':
		    e.encoding=1;
		    break;
		  case 's':
		    e.encoding=2;
		    break;
		  case 'z':
		    e.encoding=3;
		    break;
		  case 'B':
		    e.bold=TRUE;
		    break;
		  case 'I':
		    e.italic=TRUE;
		    break;
		  case '%': {
		    e.mwidth=0;
		    while(p[1]>='0' && p[1]<='9') {
			e.mwidth*=10;
			e.mwidth+=*++p-'0';
		    }
		    if(e.mwidth>999)
			e.mwidth=999;
		    else if(e.mwidth<1)
			e.mwidth=1;
		    break;
		  }
		}
	    }
	    if(*p!=']') {
		printf("Invalid fontmap entry: \"%s\".\n",q);
		return;
	    }
	    ++p;
	}
	strncpy(e.afont,p,sizeof(e.afont)-1);
	e.afont[sizeof(e.afont)-1]='\0';
	DoMethod(obj,MUIM_List_InsertSingle,&e,MUIV_List_Insert_Bottom);
    } else
	printf("Invalid fontmap entry: \"%s\".\n",q);
}

static void parse_fontmap(Object* o,const char** args) {
    while(*args) {
	parse_fontmap_entry(o,*args);
	++args;
    }
}

static void parse_fontmap_tooltypes(Object* o,BPTR lock,const char* name) {
    struct DiskObject *dob;
    BPTR olddir=CurrentDir(lock);
    if(dob=GetDiskObject((char*)name)) {
	char** p=(char**)dob->do_ToolTypes;
	if(p) {
	    while(*p) {
		if(!Strnicmp(*p,"FONTMAP=",8))
		    parse_fontmap_entry(o,*p+8);
		++p;
	    }
	}
	FreeDiskObject(dob);
    }
    CurrentDir(olddir);
}

static BOOL save_fontmap(Object* o,BPTR lock,const char* name) {
    struct DiskObject* dob;
    BOOL ret=FALSE;
    BPTR olddir=CurrentDir(lock);
    if((dob=GetDiskObject((char*)name)) ||
       (dob=GetDiskObjectNew((char*)DEFICON))) {
	char** oldtt=dob->do_ToolTypes;
	LONG entries;
	LONG numoldtt=0;
	char** p;
	char** newtt;

	get(o,MUIA_List_Entries,&entries);

	if(oldtt)
	    for(p=oldtt;*p;++p)
		if(Strnicmp(*p,"FONTMAP",7))
		    ++numoldtt;

	if(newtt=malloc(sizeof(*newtt)*(numoldtt+entries+1))) {
	    char** q=newtt;
	    int k;
	    BOOL err=FALSE;

	    if(oldtt)
		for(p=oldtt;*p;++p)
		    if(Strnicmp(*p,"FONTMAP",7))
			*q++=*p;

	    for(k=0;k<entries;++k) {
		struct fontmapentry* e;
		size_t l1,l2;
		char* s;

		DoMethod(o,MUIM_List_GetEntry,k,&e);
		l1=strlen(e->pdffont);
		l2=strlen(e->afont);
		if(s=malloc(l1+l2+32)) {
		    *q++=s;
		    memcpy(s,"FONTMAP=",8);
		    s+=8;
		    memcpy(s,e->pdffont,l1);
		    s+=l1;
		    *s++='/';
		    *s++='[';
		    *s++="12sz"[e->encoding];
		    if(e->bold)
			*s++='B';
		    if(e->italic)
			*s++='I';
		    if(e->mwidth!=100) {
			int m=e->mwidth;
			*s++='%';
			*s++='0'+m/100;
			m%=100;
			*s++='0'+m/10;
			m%=10;
			*s++='0'+m;
		    }
		    *s++=']';
		    memcpy(s,e->afont,l2+1);
		} else
		    err=TRUE;
	    }
	    *q=NULL;

	    dob->do_ToolTypes=newtt;
	    if(PutDiskObject((char*)name,dob) && !err)
		ret=TRUE;

	    p=newtt+numoldtt;
	    while(*p)
		free(*p++);
	    free(newtt);

	}
	dob->do_ToolTypes=oldtt;
	FreeDiskObject(dob);
    }
    CurrentDir(olddir);
    if(!ret)
	printf("Can't save fonts map.\n");
    return ret;
}

/*
 *  fontmap windows.
 */

struct fontmap_window {
    Object *win;
    Object *fontmapobj;
    BPTR* dir;
    char** filename;

    Object *pdffontobj;
    Object *afontobj;
    Object *popobj;
    Object *scaleobj;
    Object *boldobj;
    Object *italicobj;
    Object *encodingobj;
    Object *remfontobj;
};

static int changed_disabled; // kludge...

static void SAVEDS ASM activefonts_func(REG(a2,Object* obj),REG(a1,struct fontmap_window** p)) {
    struct fontmap_window* fmw=*p;
    LONG x;
    get(obj,MUIA_List_Active,&x);
    DoMethod(obj,MUIM_MultiSet,MUIA_Disabled,x==MUIV_List_Active_Off,
	     fmw->pdffontobj,fmw->popobj,fmw->scaleobj,fmw->boldobj,
	     fmw->italicobj,fmw->encodingobj,fmw->remfontobj,NULL);
    if(x!=MUIV_List_Active_Off) {
	struct fontmapentry* e;
	++changed_disabled;
	DoMethod(obj,MUIM_List_GetEntry,x,&e);
	MYASSERT(e);
	nnset(fmw->pdffontobj,MUIA_String_Contents,e->pdffont);
	nnset(fmw->afontobj,MUIA_String_Contents,e->afont);
	nnset(fmw->encodingobj,MUIA_Cycle_Active,e->encoding);
	nnset(fmw->scaleobj,MUIA_String_Integer,e->mwidth);
	nnset(fmw->boldobj,MUIA_Selected,e->bold);
	nnset(fmw->italicobj,MUIA_Selected,e->italic);
	--changed_disabled;
    }
}
static struct Hook activefonts_hook={{NULL,NULL},(ULONG(*)())activefonts_func};

static void SAVEDS ASM fontmapchanged_func(REG(a2,Object* obj),REG(a1,struct fontmap_window** q)) {
    struct fontmap_window* fmw=*q;
    LONG x;
    if(changed_disabled)
	return;
    get(obj,MUIA_List_Active,&x);
    if(x!=MUIV_List_Active_Off) {
	struct fontmapentry* e;
	const char* p;
	DoMethod(obj,MUIM_List_GetEntry,x,&e);
	MYASSERT(e);
	get(fmw->pdffontobj,MUIA_String_Contents,&p);
	strcpy(e->pdffont,p);
	get(fmw->afontobj,MUIA_String_Contents,&p);
	strcpy(e->afont,p);
	get(fmw->encodingobj,MUIA_Cycle_Active,&e->encoding);
	get(fmw->scaleobj,MUIA_String_Integer,&e->mwidth);
	if(e->mwidth<1)
	    e->mwidth=1;
	else if(e->mwidth>999)
	    e->mwidth=999;
	get(fmw->boldobj,MUIA_Selected,&x);
	e->bold=x;
	get(fmw->italicobj,MUIA_Selected,&x);
	e->italic=x;
	DoMethod(obj,MUIM_List_Redraw,MUIV_List_Redraw_Active);
    }
}
static struct Hook fontmapchanged_hook={{NULL,NULL},(ULONG(*)())fontmapchanged_func};

static void SAVEDS ASM addfont_func(REG(a2,Object* obj),REG(a1,struct fontmap_window** q)) {
    struct fontmap_window* fmw=*q;
    struct fontmapentry e;
    e.pdffont[0]='\0';
    e.afont[0]='\0';
    e.mwidth=100;
    e.encoding=0;
    e.bold=FALSE;
    e.italic=FALSE;
    DoMethod(obj,MUIM_List_InsertSingle,&e,MUIV_List_Insert_Bottom);
    set(obj,MUIA_List_Active,MUIV_List_Active_Bottom);
    set(fmw->win,MUIA_Window_ActiveObject,fmw->pdffontobj);
}
static struct Hook addfont_hook={{NULL,NULL},(ULONG(*)())addfont_func};

static void SAVEDS ASM savefontmap_func(REG(a2,Object* obj),REG(a1,struct fontmap_window** q)) {
    struct fontmap_window* fmw=*q;
    save_fontmap(fmw->fontmapobj,*fmw->dir,*fmw->filename);
}
static struct Hook savefontmap_hook={{NULL,NULL},(ULONG(*)())savefontmap_func};


static void make_fontmap_win(struct fontmap_window* fmw,const char* title,
			  BPTR* dir,char** filename,unsigned long id,
			  Object* scanfontsobj) {
    Object *applyfontmapobj,*saveobj;
    Object *addfontobj,*win,*fontmapobj;

    fmw->dir=dir;
    fmw->filename=filename;

    fontmapobj=fmw->fontmapobj=ListviewObject,
	MUIA_Listview_Input,TRUE,
	MUIA_Listview_DragType,MUIV_Listview_DragType_Immediate,
	MUIA_Listview_MultiSelect,MUIV_Listview_MultiSelect_Default,
	MUIA_Listview_List,MyListObject,
	    InputListFrame,
	    MUIA_List_Title,TRUE,
	    MUIA_List_Format,"BAR,BAR,P=\33l BAR,P=\33c BAR,P=\33c BAR,",
	    MUIA_List_ConstructHook,&fontmapctor_hook,
	    MUIA_List_DestructHook,&fontmapdtor_hook,
	    MUIA_List_DisplayHook,&fontmapdisp_hook,
	    MUIA_List_DragSortable,TRUE,
	    MUIA_List_ShowDropMarks,TRUE,
	    MYATTR_ListID,1,
	    End,
	End;

    win=fmw->win=WindowObject,
	MUIA_Window_Title,title,
	MUIA_Window_ID,id,
	MUIA_Window_DefaultObject,fontmapobj,
	WindowContents,VGroup,
	    Child,fontmapobj,
	    Child,HGroup,
		Child,addfontobj=SimpleButton("_Add"),
		Child,fmw->remfontobj=SimpleButton("_Remove"),
		End,
	    Child,ColGroup(2),
		Child,Label2("PDF Font"),
		Child,fmw->pdffontobj=StringObject,
		    StringFrame,
		    MUIA_String_AdvanceOnCR,TRUE,
		    MUIA_String_MaxLen,sizeof(((struct fontmapentry*)NULL)->pdffont),
		    MUIA_String_AttachedList,fontmapobj,
		    MUIA_CycleChain,TRUE,
		    MUIA_Disabled,TRUE,
		    End,
		Child,Label2("Amiga Font"),
		Child,fmw->popobj=PopaslObject,
		    MUIA_Popasl_Type,ASL_FontRequest,
		    MUIA_Popasl_StopHook,&fontreq_hook,
		    MUIA_Popstring_String,fmw->afontobj=StringObject,
			StringFrame,
			MUIA_String_MaxLen,sizeof(((struct fontmapentry*)NULL)->afont)-5,
			MUIA_String_AdvanceOnCR,TRUE,
			MUIA_String_AttachedList,fontmapobj,
			MUIA_CycleChain,TRUE,
			MUIA_Disabled,TRUE,
			End,
		    MUIA_Popstring_Button,PopButton(MUII_PopUp),
		    ASLFO_TitleText,"Select a font...",
		    End,
		End,
	    Child,HGroup,
		Child,Label1("_Scale"),
		Child,fmw->scaleobj=StringObject,
		    StringFrame,
		    MUIA_String_Accept,"0123456789",
		    MUIA_String_Integer,100,
		    MUIA_String_MaxLen,4,
		    MUIA_String_AdvanceOnCR,TRUE,
		    MUIA_String_AttachedList,fontmapobj,
		    MUIA_CycleChain,TRUE,
		    End,
		Child,Label1("_Bold"),
		Child,fmw->boldobj=MUI_MakeObject(MUIO_Checkmark,"_Bold"),
		Child,Label1("_Italic"),
		Child,fmw->italicobj=MUI_MakeObject(MUIO_Checkmark,"_Italic"),
		Child,Label1("_Encoding"),
		Child,fmw->encodingobj=MUI_MakeObject(MUIO_Cycle,"_Encoding",encodings),
		End,
	    Child,HGroup,
		Child,RectangleObject,
		    End,
		Child,saveobj=SimpleButton("Sa_ve"),
		Child,applyfontmapobj=SimpleButton("Appl_y"),
		Child,scanfontsobj,
		Child,RectangleObject,
		    End,
		End,
	    End,
	End;

    if(win) {
	DoMethod(App,OM_ADDMEMBER,win);

	DoMethod(win,MUIM_MultiSet,MUIA_Disabled,TRUE,
		 fmw->remfontobj,fmw->pdffontobj,fmw->popobj,fmw->encodingobj,
		 fmw->boldobj,fmw->italicobj,fmw->scaleobj,NULL);
	DoMethod(win,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
		 win,3,MUIM_Set,MUIA_Window_Open,FALSE);
	DoMethod(fontmapobj,MUIM_Notify,MUIA_List_Active,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&activefonts_hook,fmw);
	DoMethod(fmw->pdffontobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->pdffontobj,MUIM_Notify,MUIA_String_Contents,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->afontobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->afontobj,MUIM_Notify,MUIA_String_Contents,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->scaleobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->scaleobj,MUIM_Notify,MUIA_String_Contents,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->boldobj,MUIM_Notify,MUIA_Selected,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->italicobj,MUIM_Notify,MUIA_Selected,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(fmw->encodingobj,MUIM_Notify,MUIA_Cycle_Active,MUIV_EveryTime,
		 fontmapobj,3,MUIM_CallHook,&fontmapchanged_hook,fmw);
	DoMethod(addfontobj,MUIM_Notify,MUIA_Pressed,FALSE,
		 fontmapobj,3,MUIM_CallHook,&addfont_hook,fmw);
	DoMethod(fmw->remfontobj,MUIM_Notify,MUIA_Pressed,FALSE,
		 fontmapobj,2,MUIM_List_Remove,MUIV_List_Remove_Selected);
	DoMethod(applyfontmapobj,MUIM_Notify,MUIA_Pressed,FALSE,
		 App,2,MUIM_Application_ReturnID,ID_APPLYFONTMAP);
	DoMethod(saveobj,MUIM_Notify,MUIA_Pressed,FALSE,
		 fontmapobj,3,MUIM_CallHook,&savefontmap_hook,fmw);
    }
}

static void do_scan_fonts(struct fontmap_window* fmw,int n) {
    if(n) {
	int k;
	set(fmw->fontmapobj,MUIA_List_Quiet,TRUE);
	for(k=0;k<n;++k) {
	    struct fontmapentry e;
	    int style;
	    if(get_font(k,e.pdffont,sizeof(e.pdffont),e.afont,sizeof(e.afont),&e.mwidth,&style,&e.encoding)) {
		int entries;
		int k;
		e.bold=(style&FSF_BOLD)!=0;
		e.italic=(style&FSF_ITALIC)!=0;

		get(fmw->fontmapobj,MUIA_List_Entries,&entries);
		for(k=0;k<entries;++k) {
		    struct fontmapentry *e2;
		    DoMethod(fmw->fontmapobj,MUIM_List_GetEntry,k,&e2);
		    if(!strcmp(e.pdffont,e2->pdffont) &&
		       !strcmp(e.afont,e2->afont) &&
		       e.mwidth==e2->mwidth &&
		       e.bold==e2->bold &&
		       e.italic==e2->italic &&
		       e.encoding==e2->encoding)
			break;
		}
		if(k==entries)
		    DoMethod(fmw->fontmapobj,MUIM_List_InsertSingle,&e,MUIV_List_Insert_Bottom);
	    }
	}
	end_scan_fonts();
	set(fmw->fontmapobj,MUIA_List_Quiet,FALSE);
	set(fmw->win,MUIA_Window_ActiveObject,fmw->pdffontobj);
    }
}

static struct fontmap_window def_fmw;
static struct fontmap_window doc_fmw;

static void open_file(BPTR lock,const char* name) {
    sleep(TRUE);
    set(win,MUIA_Window_Title,"Loading...");
    if(create_doc(lock,name,CACHESZ,BLOCSZ)) {
	BPTR lock2=DupLock(lock);
	CurrentDir(lock2);
	UnLock(docdir);
	docdir=lock2;
	copystr(&docname,name);
	DoMethod(doc_fmw.fontmapobj,MUIM_List_Clear);
	parse_fontmap_tooltypes(doc_fmw.fontmapobj,docdir,docname);
	get_page_info();
    }
    set(win,MUIA_Window_Title,docname);
    sleep(FALSE);
}

static ULONG SAVEDS ASM appwin_func(REG(a2,Object* obj),REG(a1,struct AppMessage** p)) {
    struct AppMessage *msg=*p;
    if(msg->am_Version>=AM_VERSION && msg->am_NumArgs>=1)
	open_file(msg->am_ArgList->wa_Lock,msg->am_ArgList->wa_Name);
    return 0;
}
static struct Hook appwin_hook={{NULL,NULL},appwin_func};

static int open_req(void) {
    struct Screen *scr=NULL;
    if(win) /* NULL for the initial requester */
	get(win,MUIA_Window_Screen,&scr);
    if((req ||
	(req=MUI_AllocAslRequestTags(ASL_FileRequest,
				     ASLFR_TitleText,"Open...",
				     ASLFR_PositiveText,"Open",
				     ASLFR_NegativeText,"Cancel",
				     ASLFR_RejectIcons,TRUE,
				     ASLFR_DoPatterns,TRUE,
				     ASLFR_InitialPattern,"#?.pdf",
				     TAG_END))) &&
	MUI_AslRequestTags(req,
			   scr?ASLFR_Screen:TAG_IGNORE,scr,
			   TAG_END))
	return 1;
    return 0;
}

static void open_req_file(void) {
    if(req) {
	BPTR lock=Lock(req->fr_Drawer,ACCESS_READ);
	open_file(lock,req->fr_File);
	UnLock(lock);
    }
}

int main() {

    Object *nxtsrchobj,*strobj,*menu;
    Object *pssaveobj,*pscancelobj,*psrotateobj,*pszoomobj;
    Object *psstartstrobj,*psstopstrobj,*pstypeobj;
    Object *pspopobj,*pswin,*psfilenameobj;
    Object *scanstartstrobj,*scanstopstrobj,*scanwin,*scanobj;
    Object *defaultfontsobj,*scanfontsobj;

    ULONG sigs;
    struct Process* proc=(struct Process*)FindTask(NULL);

    olddir=CurrentDir(0);
    CurrentDir(olddir);

    atexit(&cleanup);

    IntuitionBase=(struct IntuitionBase*)OpenLibrary("intuition.library",39);
    GfxBase=(struct GfxBase*)OpenLibrary("graphics.library",39);
    LayersBase=OpenLibrary("layers.library",39);
    UtilityBase=(struct UtilityBase*)OpenLibrary("utility.library",39);
    IconBase=OpenLibrary("icon.library",39);
    DiskfontBase=OpenLibrary("diskfont.library",39);
    IFFParseBase=OpenLibrary("iffparse.library",39);
    if(!IntuitionBase || !GfxBase || !LayersBase || !UtilityBase ||
       !IconBase || !DiskfontBase || !IFFParseBase) {
	printf("Requires AmigaOS 3.0+\n");
	exit(EXIT_FAILURE);
    }

    if(proc->pr_CLI) {
	size_t l=16;
	char* path;
	const char* endpath;
	progdir=GetProgramDir();
	while(1) {
	    progname=malloc(l);
	    if(!progname)
		break;
	    if(GetProgramName(progname,l))
		break;
	    free(progname);
	    l+=16;
	}
	if(!(rda=ReadArgs(TEMPLATE,args,NULL))) {
	    PrintFault(IoErr(),"Error: ");
	    exit(EXIT_FAILURE);
	}
	if(PDFFILE) {
	    endpath=PathPart((char*)PDFFILE);
	    path=malloc(endpath-PDFFILE+1);
	    if(path) {
		memcpy(path,PDFFILE,endpath-PDFFILE);
		path[endpath-PDFFILE]='\0';
		docdir=Lock(path,ACCESS_READ);
		free(path);
		copystr(&docname,FilePart((char*)PDFFILE));
	    }
	}
    } else {
#if defined(__SASC) || defined(__libnix__)
	size_t l;
	if(_WBenchMsg && _WBenchMsg->sm_NumArgs>=1) {
	    progdir=_WBenchMsg->sm_ArgList[0].wa_Lock;
	    copystr(&progname,_WBenchMsg->sm_ArgList[0].wa_Name);
	    if(_WBenchMsg->sm_NumArgs>=2) {
		docdir=DupLock(_WBenchMsg->sm_ArgList[1].wa_Lock);
		copystr(&docname,_WBenchMsg->sm_ArgList[1].wa_Name);
	    }
	} else {
	    printf("I don't understand the startup method !?\n");
	    exit(EXIT_FAILURE);
	}
#else
	/* adapt for other compilers/libraries */
	exit(EXIT_FAILURE);
#endif
    }


    /* gzip sometimes tries to open "*" for input, */
    /* which opens and locks the stdiowin. */
    /* Let's prevent that: */
    if(input=Open("nil:",MODE_OLDFILE))
	oldinput=SelectInput(input);

    MUIMasterBase=OpenLibrary(MUIMASTER_NAME,MUIMASTER_VMIN);
    if(!MUIMasterBase) {
	printf("Can't open \"muimaster.library\".\n");
	exit(EXIT_FAILURE);
    }

    if(!docname && open_req()) {
	UnLock(docdir); /* there is a slight chance that docdir is locked... */
	docdir=Lock(req->fr_Drawer,ACCESS_READ);
	copystr(&docname,req->fr_File);
    }
    if(!progname || !docname)
	exit(EXIT_FAILURE);

    parse_tooltypes(progdir,progname);
    parse_tooltypes(docdir,docname);

    {
	BPTR olddir=CurrentDir(progdir);
	if(DISKOBJ)
	    diskobj=GetDiskObject((char*)DISKOBJ);
	else
	    diskobj=GetDiskObject(progname);
	CurrentDir(olddir);
    }

    CyberGfxBase=OpenLibrary("cybergraphics.library",41); /* may fail */

#ifdef POWERUP
    PPCLibBase=OpenLibrary("ppc.library",46);
    if(!PPCLibBase) {
	printf("Can't open \"ppc.library\".\n");
	exit(EXIT_FAILURE);
    }

    elfobject=PPCLoadObject("PROGDIR:apdf.elf");
    if(!elfobject) {
	printf("Can't load \"apdf.elf\".\n");
	exit(EXIT_FAILURE);
    }

    port=PPCCreatePort(NULL);
    reply_port=PPCCreatePort(NULL);
    if(!port || !reply_port) {
	printf("Can't create port.\n");
	exit(EXIT_FAILURE);
    }

    ports[0]=port;
    ports[1]=reply_port;
    ports[2]=NULL;
    portlist=PPCCreatePortList(ports,0);
    if(!portlist) {
	printf("Can't create port list.\n");
	exit(EXIT_FAILURE);
    }
#endif

    mcc=MUI_CreateCustomClass(NULL,MUIC_Area,NULL,sizeof(DocData),dispatcher);
    slider_mcc=MUI_CreateCustomClass(NULL,MUIC_Slider,NULL,sizeof(SliderData),mysliderdispatcher);
    rotate_slider_mcc=MUI_CreateCustomClass(NULL,MUIC_Slider,NULL,sizeof(RotateSliderData),myrotatesliderdispatcher);
    list_mcc=MUI_CreateCustomClass(NULL,MUIC_List,NULL,sizeof(ListData),mylistdispatcher);
    if(!mcc || !slider_mcc || !rotate_slider_mcc || !list_mcc) {
	printf("Can't create custom class.\n");
	exit(EXIT_FAILURE);
    }

    App=ApplicationObject,
	MUIA_Application_Title,         "Apdf",
	MUIA_Application_Version,       "$VER: " apdfVerString,
	MUIA_Application_Copyright,     xpdfCopyright,
	MUIA_Application_Author,        "Derek B. Noonburg, Emmanuel Lesueur",
	MUIA_Application_Description,   "PDF documents viewer",
	MUIA_Application_Base,          "Apdf",
	MUIA_Application_DiskObject,    diskobj,
	/*
	 *  Main window
	 */
	SubWindow,win=WindowObject,
	    MUIA_Window_Title,docname,
	    MUIA_Window_ID,MAKE_ID('A','P','D','F'),
	    MUIA_Window_Menustrip,menu=MUI_MakeObject(MUIO_MenustripNM,menudata,0),
	    MUIA_Window_AppWindow,TRUE,
	    WindowContents,VGroup,
		Child,ScrollgroupObject,
		    MUIA_Scrollgroup_Contents,vgroup=VirtgroupObject,
			VirtualFrame,
			Child,bitmapobj=DocObject,
			    MUIA_CycleChain,TRUE,
			    End,
			End,
		    End,
		Child,HGroup,
		    Child,Label2("Page"),
		    Child,pageobj=MySliderObject,
			SliderFrame,
			MUIA_Background,MUII_SliderBack,
			MUIA_Slider_Horiz,TRUE,
			MUIA_Numeric_Min,1,
			MUIA_Numeric_Max,1,
			MYATTR_Value,1,
			End,
		    Child,BalanceObject,
			End,
		    Child,strobj=StringObject,
			StringFrame,
			MUIA_String_Accept,"0123456789",
			MUIA_String_Contents,"1",
			MUIA_String_MaxLen,10,
			MUIA_Weight,10,
			MUIA_CycleChain,TRUE,
			End,
		    End,
		Child,HGroup,
		    Child,Label2("Zoom"),
		    Child,zoomobj=MySliderObject,
			SliderFrame,
			MUIA_Background,MUII_SliderBack,
			MUIA_Slider_Horiz,TRUE,
			MUIA_Numeric_Min,minZoom,
			MUIA_Numeric_Max,maxZoom,
			MUIA_Numeric_Value,defZoom,
			MUIA_CycleChain,TRUE,
			End,
		    Child,BalanceObject,
			End,
		    Child,Label2("Search"),
		    Child,searchobj=StringObject,
			StringFrame,
			MUIA_String_MaxLen,99,
			MUIA_Weight,200,
			MUIA_CycleChain,TRUE,
			End,
		    Child,nxtsrchobj=SimpleButton("_Next"),
		    Child,BalanceObject,
			End,
		    Child,linkobj=TextObject,
			TextFrame,
			MUIA_Background,MUII_TextBack,
			End,
		    End,
		End,
	    End,

	/*
	 *  Gauge window
	 */
	SubWindow,gaugewin=WindowObject,
	    MUIA_Window_ID,MAKE_ID('P','R','G','S'),
	    MUIA_Window_CloseGadget,FALSE,
	    WindowContents,VGroup,
		Child,gaugeobj=GaugeObject,
		    GaugeFrame,
		    MUIA_Gauge_Horiz,TRUE,
		    End,
		Child,HGroup,
		    Child,RectangleObject,
			End,
		    Child,abortobj=SimpleButton("_Abort"),
		    Child,RectangleObject,
			End,
		    End,
		End,
	    End,

	/*
	 *  Save window
	 */
	SubWindow,pswin=WindowObject,
	    MUIA_Window_Title,"Save as...",
	    MUIA_Window_ID,MAKE_ID('S','A','V','E'),
	    WindowContents,VGroup,
		Child,ColGroup(2),
		    Child,Label2("File"),
		    Child,pspopobj=PopaslObject,
			MUIA_Popasl_Type,ASL_FileRequest,
			MUIA_Popstring_String,psfilenameobj=StringObject,
			    StringFrame,
			    MUIA_String_MaxLen,255,
			    MUIA_String_AdvanceOnCR,TRUE,
			    MUIA_CycleChain,TRUE,
			    End,
			MUIA_Popstring_Button,PopButton(MUII_PopFile),
			ASLFR_TitleText,"Select a file name...",
			End,
		    Child,Label2("Mode"),
		    Child,pstypeobj=CycleObject,
			MUIA_Cycle_Entries,save_types,
			MUIA_CycleChain,TRUE,
			End,
		    Child,Label2("First page"),
		    Child,HGroup,
			Child,psstartobj=SliderObject,
			    SliderFrame,
			    MUIA_Background,MUII_SliderBack,
			    MUIA_Slider_Horiz,TRUE,
			    MUIA_Numeric_Min,1,
			    MUIA_Numeric_Max,1,
			    End,
			Child,BalanceObject,
			    End,
			Child,psstartstrobj=StringObject,
			    StringFrame,
			    MUIA_String_Accept,"0123456789",
			    MUIA_String_Contents,"1",
			    MUIA_String_MaxLen,10,
			    MUIA_String_AdvanceOnCR,TRUE,
			    MUIA_Weight,10,
			    MUIA_CycleChain,TRUE,
			    End,
			End,
		    Child,Label2("Last page"),
		    Child,HGroup,
			Child,psstopobj=SliderObject,
			    SliderFrame,
			    MUIA_Background,MUII_SliderBack,
			    MUIA_Slider_Horiz,TRUE,
			    MUIA_Numeric_Min,1,
			    MUIA_Numeric_Max,1,
			    End,
			Child,BalanceObject,
			    End,
			Child,psstopstrobj=StringObject,
			    StringFrame,
			    MUIA_String_Accept,"0123456789",
			    MUIA_String_Contents,"1",
			    MUIA_String_MaxLen,10,
			    MUIA_String_AdvanceOnCR,TRUE,
			    MUIA_Weight,10,
			    MUIA_CycleChain,TRUE,
			    End,
			End,
		    Child,Label2("Zoom"),
		    Child,pszoomobj=SliderObject,
			SliderFrame,
			MUIA_Background,MUII_SliderBack,
			MUIA_Slider_Horiz,TRUE,
			MUIA_Numeric_Min,minZoom,
			MUIA_Numeric_Max,maxZoom,
			MUIA_Numeric_Value,0,
			MUIA_CycleChain,TRUE,
			End,
		    Child,Label2("Rotate"),
		    Child,psrotateobj=MyRotateSliderObject,
			SliderFrame,
			MUIA_Background,MUII_SliderBack,
			MUIA_Slider_Horiz,TRUE,
			MUIA_Numeric_Min,0,
			MUIA_Numeric_Max,3,
			MUIA_Numeric_Value,0,
			MUIA_CycleChain,TRUE,
			End,
		    End,
		Child,HGroup,
		    Child,pssaveobj=SimpleButton("_Save"),
		    Child,pscancelobj=SimpleButton("_Cancel"),
		    End,
		End,
	    End,

	SubWindow,scanwin=WindowObject,
	    MUIA_Window_Title,"Scan...",
	    MUIA_Window_ID,MAKE_ID('S','C','A','N'),
	    WindowContents,VGroup,
		Child,HGroup,
		    Child,VGroup,
			Child,Label2("First page"),
			Child,Label2("Last page"),
			End,
		    Child,VGroup,
			Child,scanstartobj=SliderObject,
			    SliderFrame,
			    MUIA_Background,MUII_SliderBack,
			    MUIA_Slider_Horiz,TRUE,
			    MUIA_Numeric_Min,1,
			    MUIA_Numeric_Max,1,
			    End,
			Child,scanstopobj=SliderObject,
			    SliderFrame,
			    MUIA_Background,MUII_SliderBack,
			    MUIA_Slider_Horiz,TRUE,
			    MUIA_Numeric_Min,1,
			    MUIA_Numeric_Max,1,
			    End,
			End,
		    Child,BalanceObject,
			End,
		    Child,VGroup,
			Child,scanstartstrobj=StringObject,
			    StringFrame,
			    MUIA_String_Accept,"0123456789",
			    MUIA_String_Contents,"1",
			    MUIA_String_MaxLen,10,
			    MUIA_String_AdvanceOnCR,TRUE,
			    MUIA_Weight,10,
			    MUIA_CycleChain,TRUE,
			    End,
			Child,scanstopstrobj=StringObject,
			    StringFrame,
			    MUIA_String_Accept,"0123456789",
			    MUIA_String_Contents,"1",
			    MUIA_String_MaxLen,10,
			    MUIA_String_AdvanceOnCR,TRUE,
			    MUIA_Weight,10,
			    MUIA_CycleChain,TRUE,
			    End,
			End,
		    End,
		Child,HGroup,
		    Child,RectangleObject,
			End,
		    Child,scanobj=SimpleButton("_Scan"),
		    Child,RectangleObject,
			End,
		    End,
		End,
	    End,
	MUIA_Application_DropObject,win,
	End;

    if(!App) {
	printf("Can't create MUI application.\n");
	exit(EXIT_FAILURE);
    }

    make_fontmap_win(&def_fmw,"Default font mapping",
		     &progdir,&progname,MAKE_ID('F','N','T','1'),
		     defaultfontsobj=SimpleButton("_Scan"));
    make_fontmap_win(&doc_fmw,"Document font mapping",
		     &docdir,&docname,MAKE_ID('F','N','T','2'),
		     scanfontsobj=SimpleButton("_Scan..."));

    set(win,MUIA_Window_DefaultObject,bitmapobj);
    DoMethod(win,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
	     App,2,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit);
    DoMethod(win,MUIM_Notify,MUIA_AppMessage,MUIV_EveryTime,
	     win,3,MUIM_CallHook,&appwin_hook,MUIV_TriggerValue);
    DoMethod(pageobj,MUIM_Notify,MYATTR_Value,MUIV_EveryTime,
	     strobj,3,MUIM_Set,MUIA_String_Integer,MUIV_TriggerValue);
    DoMethod(strobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
	     pageobj,3,MUIM_CallHook,&setnum_hook,strobj);
    DoMethod(pageobj,MUIM_Notify,MYATTR_Value,MUIV_EveryTime,
	     bitmapobj,3,MUIM_Set,MYATTR_Page,MUIV_TriggerValue);
    DoMethod(zoomobj,MUIM_Notify,MYATTR_Value,MUIV_EveryTime,
	     bitmapobj,3,MUIM_Set,MYATTR_Zoom,MUIV_TriggerValue);
    DoMethod(searchobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
	     App,2,MUIM_Application_ReturnID,ID_SEARCH);
    DoMethod(nxtsrchobj,MUIM_Notify,MUIA_Selected,FALSE,
	     App,2,MUIM_Application_ReturnID,ID_SEARCH);

    DoMethod(pswin,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
	     pswin,3,MUIM_Set,MUIA_Window_Open,FALSE);
    DoMethod(psstartobj,MUIM_Notify,MUIA_Numeric_Value,MUIV_EveryTime,
	     psstartstrobj,3,MUIM_Set,MUIA_String_Integer,MUIV_TriggerValue);
    DoMethod(psstopobj,MUIM_Notify,MUIA_Numeric_Value,MUIV_EveryTime,
	     psstopstrobj,3,MUIM_Set,MUIA_String_Integer,MUIV_TriggerValue);
    DoMethod(psstartstrobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
	     psstartobj,3,MUIM_CallHook,&setnum_hook,psstartstrobj);
    DoMethod(psstopstrobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
	     psstopobj,3,MUIM_CallHook,&setnum_hook,psstopstrobj);
    DoMethod(pscancelobj,MUIM_Notify,MUIA_Pressed,FALSE,
	     pswin,3,MUIM_Set,MUIA_Window_Open,FALSE);
    DoMethod(pssaveobj,MUIM_Notify,MUIA_Pressed,FALSE,
	     App,2,MUIM_Application_ReturnID,ID_WRITE);

    DoMethod(defaultfontsobj,MUIM_Notify,MUIA_Pressed,FALSE,
	     App,2,MUIM_Application_ReturnID,ID_DEFAULTFONTS);
    DoMethod(scanfontsobj,MUIM_Notify,MUIA_Pressed,FALSE,
	     App,2,MUIM_Application_ReturnID,ID_OPENSCANFONTS);

    DoMethod(scanwin,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
	     scanwin,3,MUIM_Set,MUIA_Window_Open,FALSE);
    DoMethod(scanstartobj,MUIM_Notify,MUIA_Numeric_Value,MUIV_EveryTime,
	     scanstartstrobj,3,MUIM_Set,MUIA_String_Integer,MUIV_TriggerValue);
    DoMethod(scanstopobj,MUIM_Notify,MUIA_Numeric_Value,MUIV_EveryTime,
	     scanstopstrobj,3,MUIM_Set,MUIA_String_Integer,MUIV_TriggerValue);
    DoMethod(scanstartstrobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
	     scanstartobj,3,MUIM_CallHook,&setnum_hook,scanstartstrobj);
    DoMethod(scanstopstrobj,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
	     scanstopobj,3,MUIM_CallHook,&setnum_hook,scanstopstrobj);
    DoMethod(scanobj,MUIM_Notify,MUIA_Pressed,FALSE,
	     App,2,MUIM_Application_ReturnID,ID_SCANFONTS);

    DoMethod(abortobj,MUIM_Notify,MUIA_Selected,FALSE,
	     App,2,MUIM_Application_ReturnID,ID_ABORT);

#ifdef POWERUP
    {
	size_t l=strlen(GZIPCMD);
	startup_length=sizeof(struct StartupData)+1+l;
	startup_data=PPCAllocVec(startup_length,MEMF_ANY);
	startup_data->port=port;
	startup_data->in=Input();
	startup_data->out=Output();
	    /* libnix startup code does not initialize pr_CES */
	    /* when started from Workbench. Use Output() instead. */
	startup_data->err=proc->pr_CLI?proc->pr_CES:Output();
	startup_data->status=0;
	startup_data->flags=0;
	startup_data->colors=COLORS;
	startup_data->id=VMAGIC;
	memcpy(startup_data->gzipcmd,GZIPCMD,l+1);
    }

    quitmsg=PPCCreateMessage(reply_port,0);
    cmdmsg=PPCCreateMessage(reply_port,sizeof(union msg_max));
    startupmsg=PPCCreateMessage(reply_port,startup_length);
    cmddata=PPCAllocVec(sizeof(union msg_max),MEMF_ANY);
    if(!startupmsg || !quitmsg || !cmdmsg || !cmddata) {
	printf("Can't create message.\n");
	exit(EXIT_FAILURE);
    }

    ppctask=PPCCreateTaskTags(elfobject,
	PPCTASKTAG_NAME,"PDF decoder",
	PPCTASKTAG_STACKSIZE,50000,
	//PPCTASKTAG_PRIORITY,0,
	PPCTASKTAG_STARTUP_MSG,startupmsg,
	PPCTASKTAG_STARTUP_MSGDATA,startup_data,
	PPCTASKTAG_STARTUP_MSGLENGTH,startup_length,
	PPCTASKTAG_MSGPORT,TRUE,
	NP_Input,startup_data->in,
	NP_CloseInput,FALSE,
	NP_Output,startup_data->out,
	NP_CloseOutput,FALSE,
	NP_Error,startup_data->err,
	NP_CloseError,FALSE,
	TAG_END);
    if(!ppctask) {
	printf("Can't create PPC task.\n");
	exit(EXIT_FAILURE);
    }
    ppcport=(void*)PPCGetTaskAttrsTags(ppctask,PPCTASKINFOTAG_MSGPORT,NULL,TAG_END);

    while(1) {
	void* msg;
	PPCWaitPortList(portlist);
	if(msg=PPCGetMessage(port)) {
	    /* syncmsg */
	    BOOL ok=VMAGIC==(int)PPCGetMessageAttr(msg,PPCMSGTAG_DATA);
	    PPCReplyMessage(msg);
	    if(!ok) {
		printf("Wrong version of the PPC module.\n");
		quit();
	    }
	    break;
	} else if(PPCGetMessage(reply_port)) {
	    /* startupmsg */
	    printf("PPC task initialization failed.\n");
	    exit(EXIT_FAILURE);
	}
    }
    ppc_quitted=FALSE;
#else
    if(!init(COLORS,GZIPCMD))
	exit(EXIT_FAILURE);
#endif

#ifdef POWERUP
    if(quitting)
	;
    else
#endif
	 if(!create_doc(docdir,docname,CACHESZ,BLOCSZ))
	quit();
    else {
	CurrentDir(docdir);
	get_page_info();

	parse_fontmap_tooltypes(def_fmw.fontmapobj,progdir,progname);
	do_scan_fonts(&def_fmw,scan_default_fonts());
	parse_fontmap_tooltypes(doc_fmw.fontmapobj,docdir,docname);
	if(FONTMAP)
	    parse_fontmap(def_fmw.fontmapobj,FONTMAP);

	setfontmap(def_fmw.fontmapobj);
	setfontmap(doc_fmw.fontmapobj);

	set(pageobj,MUIA_Numeric_Value,PAGE);
	set(zoomobj,MUIA_Numeric_Value,ZOOM);

	set(win,MUIA_Window_Open,TRUE);
	{
	    LONG x;
	    get(win,MUIA_Window_Open,&x);
	    if(!x)
		quit();
	}
    }

    sigs=0;
    while(
#ifdef POWERUP
	  !ppc_quitted
#else
	  1
#endif
	   ) {
	void* msg;
	if(App) {
	    do {
		LONG id=DoMethod(App,MUIM_Application_NewInput,&sigs);
		switch(id) {
		  case MUIV_Application_ReturnID_Quit:
		    set(win,MUIA_Window_Open,FALSE);
		    quit();
		    break;

		  case ID_ABOUT:
		    MUI_Request(App,win,0,"Apdf "apdfVersion,"OK",
				MUIX_C
				"based on xpdf "xpdfVersion"\n"
				xpdfCopyright"\n"
				"derekn@foolabs.com\n\n"
				"Supports PDF version "pdfVersion"\n"
				"The PDF data structures, operators, and specification\n"
				"are copyright 1995 Adobe Systems Inc.\n\n"
				"http://www.foolabs.com/xpdf/\n\n"
				"Amiga port by Emmanuel Lesueur\n"
				"lesueur@club-internet.fr\n");
		    break;

		  case ID_ABOUTMUI:
		    DoMethod(App,MUIM_Application_AboutMUI,win);
		    break;

		  case ID_OPEN:
		    if(open_req())
			open_req_file();
		    break;

		  case ID_SEARCH: {
		    const char* str;
		    get(searchobj,MUIA_String_Contents,&str);
		    search(str);
		    break;
		  }
		  case ID_PRINT:
		    set(psfilenameobj,MUIA_String_Contents,"PRT:");
		    set(pstypeobj,MUIA_Cycle_Active,0);
		    /* Fall through */
		  case ID_SAVEAS: {
		    LONG x;
		    get(pageobj,MUIA_Numeric_Value,&x);
		    set(psstartobj,MUIA_Numeric_Value,x);
		    set(psstopobj,MUIA_Numeric_Value,x);
		    set(psrotateobj,MUIA_Numeric_Value,0);
		    set(pswin,MUIA_Window_Open,TRUE);
		    break;
		  }
		  case ID_WRITE: {
		    int first_page;
		    int last_page;
		    int zoom;
		    int rotate;
		    int type;
		    const char* filename;
		    LONG active;
		    get(psfilenameobj,MUIA_String_Contents,&filename);
		    get(psstartobj,MUIA_Numeric_Value,&first_page);
		    get(psstopobj,MUIA_Numeric_Value,&last_page);
		    get(pszoomobj,MUIA_Numeric_Value,&zoom);
		    get(psrotateobj,MUIA_Numeric_Value,&rotate);
		    get(pspopobj,MUIA_Popasl_Active,&active);
		    get(pstypeobj,MUIA_Cycle_Active,&type);
		    zoom=zoomDPI[zoom-minZoom];
		    rotate*=90;
		    if(!active && filename && last_page>=first_page) {
			SetAttrs(pswin,
				 MUIA_Window_Open,FALSE,
				 MUIA_Window_ActiveObject,psfilenameobj,
				 TAG_END);
			save(filename,first_page,last_page,zoom,rotate,type);
		    } else
			DisplayBeep(NULL);
		    break;
		  }
		  case ID_COPY: {
		    LONG selected;
		    get(bitmapobj,MYATTR_Selected,&selected);
		    if(selected) {
			struct Rectangle rect;
			get(bitmapobj,MYATTR_Selection,&rect);
			toclip(rect.MinX,rect.MinY,rect.MaxX,rect.MaxY);
		    }
		    break;
		  }

		  case ID_MUIPREFS:
		    DoMethod(App,MUIM_Application_OpenConfigWindow,0);
		    break;

		  case ID_DEFFONTS:
		    set(def_fmw.win,MUIA_Window_Open,TRUE);
		    break;

		  case ID_DOCFONTS:
		    set(doc_fmw.win,MUIA_Window_Open,TRUE);
		    break;

		  case ID_APPLYFONTMAP:
		    clear_fontmap();
		    setfontmap(def_fmw.fontmapobj);
		    setfontmap(doc_fmw.fontmapobj);
		    DoMethod(bitmapobj,MYM_Reset);
		    break;

		  case ID_OPENSCANFONTS: {
		    LONG x;
		    get(pageobj,MUIA_Numeric_Value,&x);
		    set(scanstartobj,MUIA_Numeric_Value,x);
		    set(scanstopobj,MUIA_Numeric_Value,x);
		    set(scanwin,MUIA_Window_Open,TRUE);
		    break;
		  }
		  case ID_SCANFONTS: {
		    int first_page;
		    int last_page;
		    get(scanstartobj,MUIA_Numeric_Value,&first_page);
		    get(scanstopobj,MUIA_Numeric_Value,&last_page);
		    if(last_page>=first_page) {
			do_scan_fonts(&doc_fmw,scan_doc_fonts(first_page,last_page));
			set(scanwin,MUIA_Window_Open,FALSE);
		    } else
			DisplayBeep(NULL);
		    break;
		  }

		  case ID_DEFAULTFONTS:
		    do_scan_fonts(&def_fmw,scan_default_fonts());
		    break;

		}
	    } while(sigs==0);
	}

#ifdef POWERUP
	PPCSetPortListAttr(portlist,PPCPORTLISTTAG_EXTENDEDSIGNALS,sigs|SIGBREAKF_CTRL_C);
	PPCWaitPortList(portlist);

	if(msg=PPCGetMessage(reply_port)) {
	    MYASSERT(msg==startupmsg);
	    ppc_quitted=TRUE;
	    exit(EXIT_SUCCESS);
	}
	if(PPCGetMessage(port)) {
	    MYASSERT(FALSE);
	}

	sigs=PPCGetPortListAttr(portlist,PPCPORTLISTTAG_RECEIVEDSIGNALS);
#else
	sigs=Wait(sigs|SIGBREAKF_CTRL_C);
#endif
	if(sigs&SIGBREAKF_CTRL_C) {
	    set(win,MUIA_Window_Open,FALSE);
	    quit();
	}
    }

    return EXIT_SUCCESS;
}


