/*
**	dispatch.c - dispatcher for Font DataType class
**	Copyright © 1995-96 Michael Letowski
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <graphics/displayinfo.h>
#include <graphics/gfx.h>
#include <graphics/modeid.h>
#include <graphics/rastport.h>
#include <graphics/text.h>
#include <intuition/classes.h>
#include <datatypes/datatypes.h>
#include <datatypes/datatypesclass.h>
#include <datatypes/pictureclass.h>
#include <diskfont/diskfont.h>
#include <utility/hooks.h>
#include <utility/tagitem.h>
#include <support/types.h>
#include <support/graphics.h>

#include <stdlib.h>

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/diskfont.h>
#include <proto/datatypes.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/utility.h>
#include <clib/support_protos.h>

#include "classbase.h"
#include "dispatch.h"
#include "prefs.h"
#include "otag.h"


/****** font.datatype/font.datatype *****************************************
*
*   NAME
*       font.datatype -- data type for Amiga bitmap and outline fonts.
*
*   FUNCTION
*       The font data type, a sub-class of the picture.datatype, is used
*       to load (and display) Amiga bitmap and outline fonts. All available
*       sizes of a given font are loaded into memory and then rendered into
*       screen, taking user preferences into account.
*       By default this data type will render each font size in one line,
*       in ascending order. Whole characters set for the given font and size
*       will be rendered.
*
*       User preferences are stored in font.prefs file, located in either
*       PROGDIR:Prefs/DataTypes/ or ENV:DataTypes/ directory (searched in
*       that order). A preferences file is an ASCII file parsed with the
*       following ReadArgs() template:
*
*           STRINGS/M,CENTER=CENTRE/S,INV=INVERSE/S,FN=FONTNAME/S,
*           DPI/K,FG=FOREGROUND/K,BG=BACKGROUND/K
*
*       STRINGS are multiple texts that are to be rendered instead of the
*       default character set. Each string is rendered in one line.
*       CENTRE indicates that each line should be centered on screen.
*       INVERSE indicates foreground/background color inversion. Does not
*       work for color fonts.
*       FONTNAME indicates that for each font's size the name and size of
*       that font should be rendered before any user text.
*       DPI describes font's aspect ratio, in XDPI/A/N,YDPI/A/N format.
*       Default values for aspect ratio are DiskFont's defaults.
*       FOREGROUND describes foreground color for two-color fonts. The string
*       is parsed with R=RED/A/N,G=GREEN/A/N,B=BLUE/A/N template to extract
*       red, green and blue components for a given color. Default foreground
*       color is black. This option does not work for color fonts.
*       BACKGROUND describes background color for two-color fonts. The string
*       is parsed with R=RED/A/N,G=GREEN/A/N,B=BLUE/A/N template to extract
*       red, green and blue components for a given color. Default background
*       color is white. This option does not work for color fonts.
*
*   METHODS
*       OM_NEW -- Create a new picture object from a .font file and all
*                 associated size files. The source must be a file.
*
*   NOTES
*       Since version 39.4 a font to be loaded does not need to be in
*       FONTS: path. The data type will temporarily extend FONTS: assign
*       to cover given font.
*       If bitmaps are created for an outline font, the data type will load
*       only those sizes, for which bitmaps exist. Otherwise all DiskFont
*       default sizes will be loaded.
*       For color fonts the color is set to that of biggest size.
*
*   BUGS
*       Loading outline fonts with multiple size requires 'a lot' of memory.
*       OpenDiskFont() uses a lot of stack and this causes problems with
*       small-stack clients like original IPrefs.
*
*   SEE ALSO
*       picture.datatype, diskfont.library, bullet.library.
*
*****************************************************************************
*
*/


/*
**	Private constants
*/
#define IDS_CNT				4													/* Number of ModeIDs */

#define COL_WHITE			0xFF											/* 'white' colour */
#define COL_BLACK			0x00											/* 'black' colour */

#define DEF_XDPI			72												/* Default X-axis DPI */
#define DEF_YDPI			80												/* Default Y-axis DPI */

#define FONTSPATH			"FONTS"										/* Assign name without ':' */

/* 256 for ASCII set, 2 for terminator */
#define MAX_CHAR			(256+2)
/* 32 chars for font name, 6 for size, 2 for terminator */
#define MAX_CHAR2			(32+6+2)
/* Total number of strings */
#define MAX_STRING		32

/* Get font's name */
#define FontName(f)		((f)->tf_Message.mn_Node.ln_Name)

/* Convenient cast */
#define CFont(f)			((struct ColorTextFont *)(f))

typedef int (SFUNC)(void const *, void const *);


/*
**	Private functions prototypes
*/
STATIC ASM Object *Dispatch(R_A0 Class *cl, R_A2 Object *o, R_A1 Msg msg);
STATIC LBOOL GetFont(struct ClassBase *cb, Object *o, struct TagItem *attrs);

/* Font management */
STATIC struct TextFont **OpenFonts(struct ClassBase *cb, struct Opts *opts,
																		struct FontContentsHeader *fch, STRPTR name);
STATIC VOID CloseFonts(struct ClassBase *cb, struct TextFont **f, ULONG cnt);

/* Rendering */
STATIC LBOOL GetWH(struct ClassBase *cb, struct RastPort *rp, struct Opts *opt,
										struct TextFont *f, ULONG *w, ULONG *h);
STATIC LBOOL Render(struct ClassBase *cb, struct RastPort *rp, struct Opts *opt,
										struct TextFont **f, ULONG cnt, ULONG w);
STATIC STRPTR *PrepStrings(struct ClassBase *cb, struct Opts *opt, 
														struct TextFont *f, ULONG *cnt);
STATIC LONG SortFunc(struct TextFont **tf1, struct TextFont **tf2);


/*
**	Public functions
*/
Class *InitClass(struct ClassBase *cb)
{
	Class *CL;

	/* Create our class (no instance) */
	if(CL=MakeClass(DATATYPENAME,SUPERCLASSNAME,NULL,NULL,0))
	{
		CL->cl_Dispatcher.h_Entry=(HOOKFUNC)Dispatch;
		CL->cl_UserData=(ULONG)cb;
		AddClass(CL);
	}
	return(CL);
}	/* InitClass */


/*
**	Level 1 private functions
*/
STATIC ASM Object *Dispatch(R_A0 Class *cl, R_A2 Object *o, R_A1 Msg msg)
{
	APTR Dummy=(APTR)GetFont;											/* Trick! - prevent inlining */
	struct ClassBase *cb=(struct ClassBase *)cl->cl_UserData;
	Object *Obj;

	switch(msg->MethodID)
	{
		case OM_NEW:																/* We know this method */
			if(Obj=(Object *)DoSuperMethodA(cl,o,msg))
				unless(GetFont(cb,Obj,((struct opSet *)msg)->ops_AttrList))
				{
					CoerceMethod(cl,Obj,OM_DISPOSE);
					Obj=NULL;
				}
			break;
		default:																		/* Let the superclass handle it */
			Obj=(Object *)DoSuperMethodA(cl,o,msg);
			break;
	}
	return(Obj);
}	/* Dispatch */


/*
**	Level 2 private functions
*/
STATIC LBOOL GetFont(struct ClassBase *cb, Object *o, struct TagItem *attrs)
{
	STATIC CONST ULONG SourceIDs[IDS_CNT]=					/* Font flag->mode selector */
	{
		LORES_KEY,HIRES_KEY,LORESLACE_KEY,HIRESLACE_KEY
	};	/* ModeIDs */

	struct RastPort RP;
	struct Opts Opts;

	struct PrefsHandle *PH;
	struct FileInfoBlock *FIB;
	struct FontContentsHeader *FCH;
	struct TextFont **Fonts=NULL,*F;
	struct BitMapHeader *BMHD=NULL;
	struct ColorFontColors *CFC;
	struct ColorRegister *CMap=NULL;
	LONG *CRegs=NULL;

	STRPTR Title,Name=NULL;												/* Font file name & picture title */
	BPTR DirLock,FH=0;
	ULONG TallWide,ModeID;
	ULONG Width,Height,Depth;
	ULONG I,J,NumFonts;
	UBYTE ForeCol[3],BackCol[3],TCol;
	LBOOL AFlag=FALSE,Result=FALSE;

	/* Read preferences */
	PH=GetFontPrefs(cb,&Opts);

	/* Get default title */
	Title=(STRPTR)GetTagData(DTA_Name,NULL,attrs);

	/* Get file handle and BitMapHeader */
	GetDTAttrs(o,DTA_Handle,&FH,PDTA_BitMapHeader,&BMHD,TAG_DONE);
	try(FH && BMHD,	EXIT);

	/* Get font file name */
	try(FIB=AllocDosObject(DOS_FIB,NULL),	NO_FIB);/* Create FileInfoBlock */
	if(ExamineFH(FH,FIB))													/* Examine it */
		Name=FIB->fib_FileName;											/* Get name pointer */
	unless(Name)																	/* Still no name */
		if(Title)																		/* Use title to get name */
			Name=FilePart(Title);											/* Get file part of title */
	try(Name,	NO_NAME);

	/* Examine .font file, load sizes */
	if(DirLock=ParentOfFH(FH))
	{
		if(FCH=NewFC(cb,DirLock,Name))
		{
			AFlag=AssignAdd(FONTSPATH,DirLock);
			Fonts=OpenFonts(cb,&Opts,FCH,Name);
			NumFonts=FCH->fch_NumEntries;
			DisposeFC(cb,FCH);
		}
		if(AFlag)
			RemAssignList(FONTSPATH,DirLock);
		else
			UnLock(DirLock);
	}
	try(Fonts,	NO_FONTS);												/* Check if any fonts loaded */

	/* Set colors */
	for(I=0; I<3; I++)														/* For each of RGB triad */
	{
		if(Opts.opt_ForeFlag)	ForeCol[I]=clamp(Opts.opt_ForeCol[I],0,255);
		else									ForeCol[I]=COL_BLACK;
		if(Opts.opt_BackFlag)	BackCol[I]=clamp(Opts.opt_BackCol[I],0,255);
		else									BackCol[I]=COL_WHITE;
	}

	/* Calculate sizes */
	TallWide=0;
	Width=Height=0;
	Depth=1;
	InitRastPort(&RP);														/* Set up RastPort - here! */
	for(I=0; I<NumFonts; I++)
		if(F=Fonts[I])															/* Opened successfully */
		{
			try(GetWH(cb,&RP,&Opts,F,&Width,&Height),	ERROR);
			if(ftst(F->tf_Style,FSF_COLORFONT))				/* This is ColorFont */
				if(CFont(F)->ctf_Depth>Depth)						/* Deeper? */
					Depth=CFont(F)->ctf_Depth;						/* Set new depth */
			TallWide=(F->tf_Flags & (FPF_TALLDOT | FPF_WIDEDOT))>>FPB_TALLDOT;
		}

	/* Set up BitMap header */
	BMHD->bmh_Width=Width;												/* Fill in informations */
	BMHD->bmh_Height=Height;
	BMHD->bmh_Depth=Depth;

	/* Get display mode id */
	ModeID=BestModeID(BIDTAG_DesiredWidth,	Width,
										BIDTAG_DesiredHeight,	Height,
										BIDTAG_Depth,					Depth,
										BIDTAG_SourceID,			SourceIDs[TallWide & (IDS_CNT-1)],
										TAG_DONE);

	/* Set colors */
	SetDTAttrs(o,NULL,NULL,PDTA_NumColors,1<<Depth,TAG_DONE);
	GetDTAttrs(o,PDTA_ColorRegisters,&CMap,PDTA_CRegs,&CRegs,TAG_DONE);
	try(CMap && CRegs,	ERROR);
	if(Depth==1)																	/* B&W font */
	{
		if(Opts.opt_Inverse)												/* Inversion? */
			for(I=0; I<3; I++)												/* For each of RGB triad */
				tswap(ForeCol[I],BackCol[I],TCol);			/* Swap foreground/background */
		for(I=0; I<3; I++)													/* For each of RGB triad */
		{
			((UBYTE *)&CMap[0])[I]=BackCol[I];
			CRegs[I+0]=Color32(BackCol[I]);
			((UBYTE *)&CMap[1])[I]=ForeCol[I];
			CRegs[I+3]=Color32(ForeCol[I]);
		}
	}
	else																					/* Some color fonts */
		for(I=0; I<NumFonts; I++)										/* For each font */
			if(F=Fonts[I])														/* Is it valid? */
				if(ftst(F->tf_Style,FSF_COLORFONT))			/* Is it color? */
					if(CFC=CFont(F)->ctf_ColorFontColors)	/* Color table valid? */
						for(J=0; J<CFC->cfc_Count; J++)			/* Fore each color in table */
						{
							CMap[J].red=		(CFC->cfc_ColorTable[J] & 0x0F00)>>4;
							CRegs[J*3+0]=		Color32(CMap[J].red);
							CMap[J].green=	(CFC->cfc_ColorTable[J] & 0x00F0);
							CRegs[J*3+1]=		Color32(CMap[J].green);
							CMap[J].blue=		(CFC->cfc_ColorTable[J] & 0x000F)<<4;
							CRegs[J*3+2]=		Color32(CMap[J].blue);
						}

	/* Prepare bitmap */
	if(RP.BitMap=AllocBitMap(Width,Height,Depth,
														BMF_CLEAR | BMF_INTERLEAVED | BMF_DISPLAYABLE,NULL))
	{
		/* Do rendering */
		if(Result=Render(cb,&RP,&Opts,Fonts,NumFonts,Width))
			SetDTAttrs(o,NULL,NULL,DTA_ObjName,				Title,
															DTA_NominalHoriz,	Width,
															DTA_NominalVert,	Height,
															PDTA_BitMap,			RP.BitMap,
															PDTA_ModeID,			ModeID,
															TAG_DONE);
		else																				/* Could not render */
			FreeBitMap(RP.BitMap);
	}

	/* Cleanup */
	catch(ERROR,		);
	catch(NO_FONTS,	CloseFonts(cb,Fonts,NumFonts));
	catch(NO_NAME,	);
	catch(NO_FIB,		FreeDosObject(DOS_FIB,FIB));	/* MUST be freed here, not before! */
	catch(EXIT,			);
	if(PH)					FreeFontPrefs(cb,PH);
	return(Result);
}	/* GetFont */


/*
**	Font management
*/
STATIC struct TextFont **OpenFonts(struct ClassBase *cb, struct Opts *opts,
																		struct FontContentsHeader *fch, STRPTR name)
{
	struct TagItem MyTags[]=
	{
		{TA_DeviceDPI,	0},
		{TAG_DONE,			0}
	};	/* MyTags */

	struct TTextAttr TTA;

	struct TextFont **Fonts;
	struct TFontContents *TFC;
	LONG XDPI,YDPI;
	ULONG I,NumEntries=fch->fch_NumEntries;
	ULONG FCnt=0;																	/* Number of opened fonts */

	try(Fonts=AllocVec(NumEntries*sizeof(APTR),MEMF_CLEAR),	NO_FONTS);
	for(I=0; I<NumEntries; I++)
	{
		TFC=&TFontContents(fch)[I];								/* Get FontContents */
		TTA.tta_Name=name;												/* Copy attrs */
		TTA.tta_YSize=TFC->tfc_YSize;
		TTA.tta_Style=TFC->tfc_Style;
		TTA.tta_Flags=TFC->tfc_Flags | FPF_DISKFONT;
		if(ftst(TFC->tfc_Style,FSF_TAGGED))				/* Tags should be set */
			TTA.tta_Tags=(struct TagItem *)
										&TFC->tfc_FileName
											[MAXFONTPATH-(TFC->tfc_TagCount*sizeof(struct TagItem))];
		elif(opts->opt_DPIFlag)												/* DPI given - set our own DPI tags */
		{
			XDPI=clamp(opts->opt_XDPI,1,65535);
			YDPI=clamp(opts->opt_YDPI,1,65535);
			MyTags[0].ti_Data=(XDPI<<16) | YDPI;
			TTA.tta_Tags=MyTags;
			fset(TTA.tta_Style,FSF_TAGGED);
		}
		if(Fonts[I]=OpenDiskFont((struct TextAttr *)&TTA))
			FCnt++;
	}
	unless(FCnt)																	/* No font loaded */
		throw2(SetIoErr(ERROR_OBJECT_NOT_FOUND),	ERROR);
	qsort(Fonts,NumEntries,sizeof(APTR),(SFUNC *)SortFunc);
	return(Fonts);

	catch(ERROR,		);
	catch(NO_FONTS,	FreeVec(Fonts));
	return(NULL);
}	/* OpenFonts */

STATIC VOID CloseFonts(struct ClassBase *cb, struct TextFont **f, ULONG cnt)
{
	ULONG I;

	for(I=0; I<cnt; I++)
		if(f[I])
			CloseFont(f[I]);
	FreeVec(f);
}	/* CloseFonts */


/*
**	Rendering
*/
STATIC LBOOL GetWH(struct ClassBase *cb, struct RastPort *rp, struct Opts *opt,
										struct TextFont *f, ULONG *w, ULONG *h)
{
	STRPTR CurStr,*Strs;
	ULONG W,J,SCnt;

	if(Strs=PrepStrings(cb,opt,f,&SCnt))					/* Allocate and init strings */
	{
		/* Calculate sizes */
		SetFont(rp,f);
		for(J=0; J<SCnt; J++)
		{
			CurStr=Strs[J];
			W=TextLength(rp,CurStr,strlen(CurStr));		/* Calculate len of this line */
			if(W>*w)																	/* If larger... */
				*w=W;																		/* Make it new width */
			*h+=f->tf_YSize;													/* Add to height */
		}
		FreeVec(Strs);															/* Free strings */
	}
	return(Strs!=NULL);
}	/* GetWH */

STATIC LBOOL Render(struct ClassBase *cb, struct RastPort *rp, struct Opts *opt,
										struct TextFont **f, ULONG cnt, ULONG w)
{
	struct TextFont *F;
	STRPTR CurStr,*Strs;
	ULONG SCnt,I,J,X,Y=0;
	LBOOL Rendered=FALSE;

	for(I=0; I<cnt; I++)													/* For each font size */
		if(F=f[I])																	/* Size opened? */
			if(Strs=PrepStrings(cb,opt,F,&SCnt))			/* Allocate and init strings */
			{
				Rendered=TRUE;
				/* Do rendering */
				SetFont(rp,F);													/* Make it a current font */
				for(J=0; J<SCnt; J++)
				{
					CurStr=Strs[J];
					X=opt->opt_Center ? (w-TextLength(rp,CurStr,strlen(CurStr)))/2 : 0;
					Move(rp,X,Y+F->tf_Baseline);
					Text(rp,CurStr,strlen(CurStr));
					Y+=F->tf_YSize;
				}
				FreeVec(Strs);													/* Free strings */
			}
	return(Rendered);
}	/* Render */

STATIC STRPTR *PrepStrings(struct ClassBase *cb, struct Opts *opt, 
														struct TextFont *f, ULONG *cnt)
{
	STRPTR S1,S2,CurStr;
	STRPTR *Strs,*Ss;
	ULONG I,Cnt,Temp;

	/* Calculate number of strings */
	Cnt=2;
	if(Ss=opt->opt_Strings)												/* Strings given */
		while(CurStr=*Ss++)													/* For each string */
			if(*CurStr)																/* Non-empty string */
				Cnt++;																	/* Increase count */

	/* Allocate and fill strings array */
	if(Strs=AllocVec(Cnt*sizeof(STRPTR)+MAX_CHAR+MAX_CHAR2,MEMF_CLEAR))
	{
		S1=(STRPTR)Strs+Cnt*sizeof(STRPTR);
		S2=S1+MAX_CHAR;
		Cnt=0;
		if(opt->opt_FontName && FontName(f))				/* FontName - first */
		{
			SNPrintf(S2,MAX_CHAR-1,"%s %ld",FontName(f),f->tf_YSize);
			Strs[Cnt++]=S2;														/* Init strings array */
		}
		Temp=Cnt;
		if(Ss=opt->opt_Strings)											/* User's strings given */
			while(CurStr=*Ss++)												/* While array not filled */
				if(*CurStr)															/* Non-empty string */
					Strs[Cnt++]=CurStr;										/* Fill in array */
		if(Cnt==0 || !Ss && Cnt==Temp)							/* Use charset */
		{
			for(I=f->tf_LoChar; I<=f->tf_HiChar; I++)
				S1[I - f->tf_LoChar]=I ? (CHAR)I : ' ';	/* Make ASCII array */
			Strs[Cnt++]=S1;
		}
		*cnt=Cnt;
	}
	return(Strs);
}	/* PrepStrings */

STATIC LONG SortFunc(struct TextFont **tf1, struct TextFont **tf2)
{
	if(*tf1 && *tf2)	return((LONG)(*tf1)->tf_YSize - (*tf2)->tf_YSize);
	else if(*tf1)			return(-1);
	else if(*tf2)			return(1);
	else							return(0);
}	/* SortFunc */
