/*	XGFont.cpp
 *
 *		The font management code
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XError.h>
#include <XDraw.h>
#include <string.h>

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

XGPDynArray<XGFont> XGFont::gFontArray;


/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGFont::XGFont
 *
 *		Create a font, marked as attached. Normally you would create a
 *	font by using the LoadFont or GetFontVarint routines. If you create
 *	a font using this mechanism, this attempts to create a default font,
 *	but does not initialize it yet.
 *
 *		That means you can create a font by hand by writing:
 *
\begin{verbatim}
		XGFont *font = new XGFont;
		font->SetFontName("Geneva");
		font->SetFontSize(9);
		font->SetFontFlags(KXGFontBold);
		
		XGDraw draw(this);
		draw.SetFont(font);
\end{verbatim}
 */

XGFont::XGFont()
{
	/*
	 *	Create a font
	 */
	
	fAttach = 1;
	fFontResID = -1;
	
	fFontFlags = 0;
	fFontSize = 9;
	fFontName[0] = 0;
	
#if OPT_MACOS == 1
	fFontID = -1;
#endif

#if OPT_WINOS == 1
	fFontHandle = NULL;
#endif

#if OPT_XWIN == 1
	fFontData = NULL;
#endif
	
	/*
	 *	Add it to the list of fonts I'm managing
	 */
	
	gFontArray += this;
}

/*$	XGFont::~XGFont
 *
 *		Delete a font. Normally you should use the 'Detach' method
 *	instead of directly destroying this object; otherwise, the font
 *	cache can become confused.
 */

XGFont::~XGFont()
{
	long i,len;
	
	/*
	 *	Barf if I'm locked or attached
	 */
	
	Assert(fAttach <= 0);
	
	/*
	 *	Remove me from the font array
	 */
	
	len = gFontArray.Length();
	for (i = 0; i < len; i++) {
		if (gFontArray[i] == this) {
			gFontArray.Delete(i);
		}
	}
	
	/*
	 *	(Windows) Delete the font handle
	 */
	
#if OPT_WINOS == 1
	if (fFontHandle) ::DeleteObject(fFontHandle);
	fFontHandle = NULL;
#endif

#if OPT_XWIN == 1
	if (fFontData) ::XFreeFont(_GDisplay,fFontData);
	fFontData = NULL;
#endif
}

/************************************************************************/
/*																		*/
/*	Attach/Detach semantics												*/
/*																		*/
/************************************************************************/

/*$	XGFont::Detach
 *
 *		Detach; this detaches this font, and when this hits zero, deletes
 *	this font from the font cache.
 */

void XGFont::Detach(void)
{
	fAttach--;
	if (fAttach <= 0) delete this;
}

/************************************************************************/
/*																		*/
/*	Attach/Detach semantics												*/
/*																		*/
/************************************************************************/

/*$	XGFont::LoadFont
 *
 *		Load the font from a resource. If the font resource identifier
 *	is 0, this creates a system font; if 1, this creates the default
 *	application font, and 2, this creates a default mono-spaced font.
 */

XGFont *XGFont::LoadFont(short resID)
{
	XGFont *f;
	
	/*
	 *	Search for the font and return it if it's already loaded.
	 */
	
#if OPT_MACOS || OPT_XWIN			// printer kludge workaround
	long i,len;

	len = gFontArray.Length();		// skip for Windows because the same
	for (i = 0; i < len; i++) {		// font handle works differently from
		f = gFontArray[i];			// printer to printer... ###
		if (f->fFontResID == resID) {
			f->Attach();
			return f;
		}
	}
#endif
	
	/*
	 *	Create the font if it ain't loaded
	 */

	f = new XGFont;
	if (resID == 0) {
		/*
		 *	Create default system font for dialogs
		 */
		
#if OPT_MACOS == 1
		f->SetFontName("");
		f->SetFontSize(0);
		f->SetFontFlags(0);
#endif

#if OPT_WINOS == 1
		f->SetFontName("Arial");
		f->SetFontSize(9);
		f->SetFontFlags(KXGFontBold);
#endif

#if OPT_XWIN == 1
		f->SetFontName("");
		f->SetFontSize(10);
		f->SetFontFlags(KXGFontBold);
#endif
	} else if (resID == 1) {
		/*
		 *	Create default application font
		 */
		
#if OPT_MACOS == 1
		f->SetFontName("Geneva");
		f->SetFontSize(9);
		f->SetFontFlags(0);
#endif

#if OPT_WINOS == 1
		f->SetFontName("Arial");
		f->SetFontSize(8);
		f->SetFontFlags(0);
#endif

#if OPT_XWIN == 1
		f->SetFontName("");
		f->SetFontSize(10);
		f->SetFontFlags(0);
#endif
	} else if (resID == 2) {
		/*
		 *	Create default mono-spaced
		 */
		
#if OPT_MACOS == 1
		f->SetFontName("Monaco");
		f->SetFontSize(9);
		f->SetFontFlags(0);
#endif

#if OPT_WINOS == 1
		f->SetFontName("Courier New");
		f->SetFontSize(8);
		f->SetFontFlags(0);
#endif

#if OPT_XWIN == 1
		f->SetFontName("Courier");
		f->SetFontSize(10);
		f->SetFontFlags(0);
#endif
	} else {
		/*
		 *	Load font from resource
		 */
	
#if OPT_MACOS == 1
		XGSFontResource *fptr;
		Handle h;
		short flags;
		
		h = GetResource('MFnt',resID);
		if (h == NULL) {
			f->Detach();
			return NULL;
		}
		fptr = (XGSFontResource *)(*h);
		f->SetFontName(fptr->name);
		f->SetFontSize(fptr->size);
		
		flags = 0;
		if (fptr->fBold)      flags |= KXGFontBold;
		if (fptr->fItalic)    flags |= KXGFontItalic;
		if (fptr->fUnderline) flags |= KXGFontUnderline;
		if (fptr->fFixed)     flags |= KXGFontFixFont;
		if (fptr->fSerif)     flags |= KXGFontSerifFont;
		f->SetFontFlags(flags);
		
		ReleaseResource(h);
#endif

#if OPT_WINOS == 1
		/*
		 *	### Things to do...
		 *
		 *	This should load the font from the resource specified. Right
		 *	now, this isn't defined. I need to go in and plug this back in.
		 */
		
		return NULL;
#endif

#if OPT_XWIN == 1
		/*
		 *	### Things to do...
		 *
		 *	This should load the font from the resource specified. Right
		 *	now, this isn't defined. I need to go in and plug this back in.
		 */
		
		return NULL;
#endif

	}
	
	/*
	 *	Now I have a new font, mark it as such
	 */
	
	f->fFontResID = resID;
	return f;
}

/*$	XGFont::GetFontVariant
 *
 *		This allows me to get a font which is exactly like the font I have
 *	on hand, but with the 'underline', 'italic', or 'bold' attributes
 *	different. This also sets the 'attach' flag
 */

XGFont *XGFont::GetFontVariant(XGFont *f, short newflags)
{
	char name[64],buffer[64];
	short flags;
	short size;
	long i,len;
	XGFont *ptr;
	
	/*
	 *	Initialize
	 */
	
	newflags &= 0x00FF;				// strip font type flags
	
	/*
	 *	Get font attributes from the old font
	 */
	
	f->GetFontName(name);
	flags = (f->GetFontFlags() & 0xFF00) | newflags;
	size = f->GetFontSize();
	
	/*
	 *	Search for the specified font
	 */
	
	len = gFontArray.Length();
	for (i = 0; i < len; i++) {
		ptr = gFontArray[i];
		if (flags != ptr->GetFontFlags()) continue;
		if (size != ptr->GetFontSize()) continue;
		
		ptr->GetFontName(buffer);
		if (!strcmp(buffer,name)) {
			/*
			 *	Found it!
			 */
			
			ptr->Attach();
			return ptr;
		}
	}

	/*
	 *	If the font is not found, create it
	 */
	
	ptr = new XGFont;
	ptr->SetFontName(name);
	ptr->SetFontSize(size);
	ptr->SetFontFlags(flags);

	return ptr;
}


/************************************************************************/
/*																		*/
/*	Font Attributes														*/
/*																		*/
/************************************************************************/

/*$	XGFont::GetFontName
 *
 *		Get the name of this font
 */

void XGFont::GetFontName(char *f) const
{
	strcpy(f,fFontName);
}

/*$	XGFont::SetFontFlags
 *
 *		Set the font flags. This <<only>> works if there is only one
 *	attached object; the assumption is that this is not in use or
 *	shared with someone else
 */

void XGFont::SetFontFlags(short f)
{
	if (fAttach != 1) return;
	
#if OPT_WINOS == 1
	if (fFontHandle) ::DeleteObject(fFontHandle);
	fFontHandle = NULL;
#endif

#if OPT_XWIN == 1
	if (fFontData) ::XFreeFont(_GDisplay,fFontData);
	fFontData = NULL;
#endif

	fFontFlags = f;
	fFontResID = -1;
}

/*$	XGFont::SetFontName
 *
 *		Set the font name. This <<only>> works if there is only one
 *	attached object; the assumption is that this is not in use or
 *	shared with someone else
 */

void XGFont::SetFontName(const char *f)
{
	if (fAttach != 1) return;
	
#if OPT_WINOS == 1
	if (fFontHandle) ::DeleteObject(fFontHandle);
	fFontHandle = NULL;
#endif

#if OPT_XWIN == 1
	if (fFontData) ::XFreeFont(_GDisplay,fFontData);
	fFontData = NULL;
#endif

	strcpy(fFontName,f);
	fFontResID = -1;

#if OPT_MACOS == 1
	fFontID = -1;
#endif
}

/*$	XGFont::SetFontSize
 *
 *		Set the font size. This <<only>> works if there is only one
 *	attached object; the assumption is that this is not in use or
 *	shared with someone else
 */

void XGFont::SetFontSize(short f)
{
	if (fAttach != 1) return;
	
#if OPT_WINOS == 1
	if (fFontHandle) ::DeleteObject(fFontHandle);
	fFontHandle = NULL;
#endif

#if OPT_XWIN == 1
	if (fFontData) ::XFreeFont(_GDisplay,fFontData);
	fFontData = NULL;
#endif

	fFontSize = f;
	fFontResID = -1;
}

/************************************************************************/
/*																		*/
/*	Font Hooks															*/
/*																		*/
/************************************************************************/

#if OPT_MACOS == 1

/*$	XGFont::SetFont
 *
 *		Set the font according to the attributes specified. Used internally
 *	by YAAF, normally you would never call this routine.
 */

void XGFont::SetFont(void)
{
	short flags;
	unsigned char buffer[64];
	
	if (fFontID == -1) {
		/*
		 *	Get the font ID
		 */
	
		if (fFontName[0] == 0) fFontID = 0;			/* System font kludge */
		else {
			strcpy((char *)buffer,fFontName);
			c2pstr((char *)buffer);
			
			/*
			 *	Get the font
			 */
			
			::GetFNum(buffer,&fFontID);
			if (fFontID == 0) {
				/*
				 *	Okay, the font ID doesn't exist. Pick either monaco,
				 *	geneva, courier or times
				 */
				
				if (fFontFlags & KXGFontFixFont) {
					/*
					 *	Fixed fonts are Courier or Monaco.
					 */
					
					if (fFontFlags & KXGFontSerifFont) {
						::GetFNum("\pCourier",&fFontID);
						if (fFontID == 0) fFontID = 4;	// go to Monaco
					} else {
						fFontID = 4;
					}
				} else {
					/*
					 *	Variable with fonts are Geneva or Times
					 */
					
					if (fFontFlags & KXGFontSerifFont) {
						::GetFNum("\pTimes",&fFontID);
						if (fFontID == 0) fFontID = 1;
					} else {
						fFontID = 1;
					}
				}
			}
		}
	}
	
	/*
	 *	Now set the parameters
	 */
	
	::TextFont(fFontID);
	::TextSize(fFontSize);
	flags = 0;
	if (fFontFlags & KXGFontBold) flags |= 1;
	if (fFontFlags & KXGFontItalic) flags |= 2;
	if (fFontFlags & KXGFontUnderline) flags |= 4;
	::TextFace(flags);
}

#endif // OPT_MACOS


#if OPT_WINOS == 1

/*$	XGFont::GetFont
 *
 *		Get the font handle associated with this font. Used by YAAF,
 *	normally you would never use this routine.
 */

HFONT XGFont::GetFont(HDC dc)
{
	LOGFONT lf;
	bool f;
	
	/*
	 *	DC is used for information only. If NULL, this gets
	 *	an information device context
	 */
	
	if (dc) {
		f = true;
	} else {
		f = false;
		dc = ::CreateDC("DISPLAY",NULL,NULL,NULL);
	}
	
	/*
	 *	Is the font already built? If so, return it
	 */
	
	if (fFontHandle) return fFontHandle;
	
	/*
	 *	Run through the font list
	 */
	
	lf.lfHeight = -MulDiv(fFontSize,GetDeviceCaps(dc,LOGPIXELSY),72);
	lf.lfWidth = 0;
	lf.lfEscapement = 0;
	lf.lfOrientation = 0;
	lf.lfWeight = (fFontFlags & KXGFontBold) ? FW_BOLD : FW_NORMAL;
	lf.lfItalic = (fFontFlags & KXGFontItalic) ? true : false;
	lf.lfUnderline = (fFontFlags & KXGFontUnderline) ? true : false;
	lf.lfStrikeOut = 0;
	lf.lfCharSet = ANSI_CHARSET;			// what the heck.
	lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
	lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
	lf.lfQuality = DEFAULT_QUALITY;
	
	if (fFontFlags & KXGFontFixFont) {
		lf.lfPitchAndFamily = FIXED_PITCH;
	} else {
		lf.lfPitchAndFamily = VARIABLE_PITCH;
	}
	if (fFontFlags & KXGFontSerifFont) {
		lf.lfPitchAndFamily |= FF_ROMAN;
	} else {
		lf.lfPitchAndFamily |= FF_SWISS;
	}
	
	strcpy(lf.lfFaceName,fFontName);
	
	/*
	 *	Release stuff
	 */
	
	if (!f) {
		::DeleteDC(dc);
	}
	
	/*
	 *	Now generate the font
	 */
	
	fFontHandle = ::CreateFontIndirect(&lf);
	return fFontHandle;
}

#endif // OPT_WINOS

#if OPT_XWIN == 1

/*$	XGFont::GetFont
 *
 *		Get the font reference associated with this font specification.
 *	Used internally by YAAF, normally you would never call this routine.
 */

Font XGFont::GetFont()
{
	char buffer[256];
	char *deffont;
	
	if (fFontData) return fFontData->fid;

	/*
	 *	Construct the font name
	 */
	
	if (fFontName[0]) {
		/*
		 *	First, try to get the font by name.
		 */
		
		sprintf(buffer,"-*-%s-%s-%s-normal--*-%d*",
					   fFontName,
					   (fFontFlags & KXGFontBold) ? "bold" : "medium",
					   (fFontFlags & KXGFontItalic) ? "i" : "r",
					   fFontSize * 10);
		fFontData = XLoadQueryFont(_GDisplay,buffer);
		if (fFontData) return fFontData->fid;
		
		if (fFontFlags & KXGFontItalic) {
			/* Try oblique for italic */
			sprintf(buffer,"-*-%s-%s-o-normal--*-%d*",
						   fFontName,
						   (fFontFlags & KXGFontBold) ? "bold" : "medium",
						   fFontSize * 10);
			fFontData = XLoadQueryFont(_GDisplay,buffer);
			if (fFontData) return fFontData->fid;
		}
	}
	
	/*
	 *	Now try the font by it's fixed/unfixed width attribute by
	 *	picking names I think may be there
	 */
	
	if (fFontFlags & KXGFontFixFont) {
		deffont = "courier";
	} else if (fFontFlags & KXGFontSerifFont) {
		deffont = "times";
	} else {
		deffont = "helvetica";
	}
	
	sprintf(buffer,"-*-%s-%s-%s-normal--*-%d*",
				   deffont,
				   (fFontFlags & KXGFontBold) ? "bold" : "medium",
				   (fFontFlags & KXGFontItalic) ? "i" : "r",
				   fFontSize * 10);
	fFontData = XLoadQueryFont(_GDisplay,buffer);
	if (fFontData) return fFontData->fid;
	if (fFontFlags & KXGFontItalic) {
		/* Try oblique for italic */
		sprintf(buffer,"-*-%s-%s-o-normal--*-%d*",
					   deffont,
					   (fFontFlags & KXGFontBold) ? "bold" : "medium",
					   fFontSize * 10);
		fFontData = XLoadQueryFont(_GDisplay,buffer);
		if (fFontData) return fFontData->fid;
	}
	
	/*
	 *	Now try without any font names
	 */
	
	sprintf(buffer,"*%s-%s-normal--*-%d-*-*-%s*",
				   (fFontFlags & KXGFontBold) ? "bold" : "medium",
				   (fFontFlags & KXGFontItalic) ? "i" : "r",
				   fFontSize * 10,
				   (fFontFlags & KXGFontFixFont) ? "m" : "p");
	fFontData = XLoadQueryFont(_GDisplay,buffer);
	if (fFontData) return fFontData->fid;
	if (fFontFlags & KXGFontItalic) {
		/* Try oblique for italic */
		sprintf(buffer,"*%s-o-normal--*-%d-*-*-%s*",
					   deffont,
					   (fFontFlags & KXGFontBold) ? "bold" : "medium",
					   fFontSize * 10,
					   (fFontFlags & KXGFontFixFont) ? "m" : "p");
		fFontData = XLoadQueryFont(_GDisplay,buffer);
		if (fFontData) return fFontData->fid;
	}
	
	/*
	 *	If we get here, panic. Use a default font
	 */
	
	fFontData = XLoadQueryFont(_GDisplay,"*-normal-r-*-100*");
	if (fFontData) return fFontData->fid;
	return NULL;
}

/*$	XGFont::GetFontStruct
 *
 *		Get the font structure for this thing. Used internally by 
 *	YAAF, normally you would never call this routine.
 */

XFontStruct *XGFont::GetFontStruct()
{
	GetFont();			// force loading structure
	return fFontData;
}

#endif // OPT_XWIN
